Compare commits
13 Commits
main
...
telackey/9
Author | SHA1 | Date | |
---|---|---|---|
578fd933a7 | |||
35d23ca858 | |||
e06de5f4b7 | |||
6e64b60295 | |||
3205478878 | |||
916724f1e2 | |||
56b010f512 | |||
023a640252 | |||
09c9214e4c | |||
2f1cde16b7 | |||
b449d88b6c | |||
07030044ec | |||
0a9d68f4e8 |
@ -39,7 +39,7 @@ jobs:
|
|||||||
- name: "Print Python version"
|
- name: "Print Python version"
|
||||||
run: python3 --version
|
run: python3 --version
|
||||||
- name: "Install shiv"
|
- name: "Install shiv"
|
||||||
run: pip install shiv==1.0.6
|
run: pip install shiv
|
||||||
- name: "Generate build version file"
|
- name: "Generate build version file"
|
||||||
run: ./scripts/create_build_tag_file.sh
|
run: ./scripts/create_build_tag_file.sh
|
||||||
- name: "Build local shiv package"
|
- name: "Build local shiv package"
|
||||||
|
@ -35,7 +35,7 @@ jobs:
|
|||||||
- name: "Print Python version"
|
- name: "Print Python version"
|
||||||
run: python3 --version
|
run: python3 --version
|
||||||
- name: "Install shiv"
|
- name: "Install shiv"
|
||||||
run: pip install shiv==1.0.6
|
run: pip install shiv
|
||||||
- name: "Build local shiv package"
|
- name: "Build local shiv package"
|
||||||
id: build
|
id: build
|
||||||
run: |
|
run: |
|
||||||
|
@ -33,7 +33,7 @@ jobs:
|
|||||||
- name: "Print Python version"
|
- name: "Print Python version"
|
||||||
run: python3 --version
|
run: python3 --version
|
||||||
- name: "Install shiv"
|
- name: "Install shiv"
|
||||||
run: pip install shiv==1.0.6
|
run: pip install shiv
|
||||||
- name: "Generate build version file"
|
- name: "Generate build version file"
|
||||||
run: ./scripts/create_build_tag_file.sh
|
run: ./scripts/create_build_tag_file.sh
|
||||||
- name: "Build local shiv package"
|
- name: "Build local shiv package"
|
||||||
|
@ -33,7 +33,7 @@ jobs:
|
|||||||
- name: "Print Python version"
|
- name: "Print Python version"
|
||||||
run: python3 --version
|
run: python3 --version
|
||||||
- name: "Install shiv"
|
- name: "Install shiv"
|
||||||
run: pip install shiv==1.0.6
|
run: pip install shiv
|
||||||
- name: "Generate build version file"
|
- name: "Generate build version file"
|
||||||
run: ./scripts/create_build_tag_file.sh
|
run: ./scripts/create_build_tag_file.sh
|
||||||
- name: "Build local shiv package"
|
- name: "Build local shiv package"
|
||||||
|
@ -33,7 +33,7 @@ jobs:
|
|||||||
- name: "Print Python version"
|
- name: "Print Python version"
|
||||||
run: python3 --version
|
run: python3 --version
|
||||||
- name: "Install shiv"
|
- name: "Install shiv"
|
||||||
run: pip install shiv==1.0.6
|
run: pip install shiv
|
||||||
- name: "Generate build version file"
|
- name: "Generate build version file"
|
||||||
run: ./scripts/create_build_tag_file.sh
|
run: ./scripts/create_build_tag_file.sh
|
||||||
- name: "Build local shiv package"
|
- name: "Build local shiv package"
|
||||||
|
@ -33,7 +33,7 @@ jobs:
|
|||||||
- name: "Print Python version"
|
- name: "Print Python version"
|
||||||
run: python3 --version
|
run: python3 --version
|
||||||
- name: "Install shiv"
|
- name: "Install shiv"
|
||||||
run: pip install shiv==1.0.6
|
run: pip install shiv
|
||||||
- name: "Generate build version file"
|
- name: "Generate build version file"
|
||||||
run: ./scripts/create_build_tag_file.sh
|
run: ./scripts/create_build_tag_file.sh
|
||||||
- name: "Build local shiv package"
|
- name: "Build local shiv package"
|
||||||
|
@ -35,7 +35,7 @@ jobs:
|
|||||||
- name: "Print Python version"
|
- name: "Print Python version"
|
||||||
run: python3 --version
|
run: python3 --version
|
||||||
- name: "Install shiv"
|
- name: "Install shiv"
|
||||||
run: pip install shiv==1.0.6
|
run: pip install shiv
|
||||||
- name: "Generate build version file"
|
- name: "Generate build version file"
|
||||||
run: ./scripts/create_build_tag_file.sh
|
run: ./scripts/create_build_tag_file.sh
|
||||||
- name: "Build local shiv package"
|
- name: "Build local shiv package"
|
||||||
|
@ -35,7 +35,7 @@ jobs:
|
|||||||
- name: "Print Python version"
|
- name: "Print Python version"
|
||||||
run: python3 --version
|
run: python3 --version
|
||||||
- name: "Install shiv"
|
- name: "Install shiv"
|
||||||
run: pip install shiv==1.0.6
|
run: pip install shiv
|
||||||
- name: "Generate build version file"
|
- name: "Generate build version file"
|
||||||
run: ./scripts/create_build_tag_file.sh
|
run: ./scripts/create_build_tag_file.sh
|
||||||
- name: "Build local shiv package"
|
- name: "Build local shiv package"
|
||||||
|
@ -32,7 +32,7 @@ jobs:
|
|||||||
- name: "Print Python version"
|
- name: "Print Python version"
|
||||||
run: python3 --version
|
run: python3 --version
|
||||||
- name: "Install shiv"
|
- name: "Install shiv"
|
||||||
run: pip install shiv==1.0.6
|
run: pip install shiv
|
||||||
- name: "Generate build version file"
|
- name: "Generate build version file"
|
||||||
run: ./scripts/create_build_tag_file.sh
|
run: ./scripts/create_build_tag_file.sh
|
||||||
- name: "Build local shiv package"
|
- name: "Build local shiv package"
|
||||||
|
@ -33,7 +33,7 @@ jobs:
|
|||||||
- name: "Print Python version"
|
- name: "Print Python version"
|
||||||
run: python3 --version
|
run: python3 --version
|
||||||
- name: "Install shiv"
|
- name: "Install shiv"
|
||||||
run: pip install shiv==1.0.6
|
run: pip install shiv
|
||||||
- name: "Generate build version file"
|
- name: "Generate build version file"
|
||||||
run: ./scripts/create_build_tag_file.sh
|
run: ./scripts/create_build_tag_file.sh
|
||||||
- name: "Build local shiv package"
|
- name: "Build local shiv package"
|
||||||
|
@ -11,5 +11,3 @@ tomli==2.0.1
|
|||||||
validators==0.22.0
|
validators==0.22.0
|
||||||
kubernetes>=28.1.0
|
kubernetes>=28.1.0
|
||||||
humanfriendly>=10.0
|
humanfriendly>=10.0
|
||||||
python-gnupg>=0.5.2
|
|
||||||
requests>=2.3.2
|
|
||||||
|
@ -4,9 +4,5 @@ source ${CERC_CONTAINER_BASE_DIR}/build-base.sh
|
|||||||
SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
|
SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
|
||||||
|
|
||||||
# Two-stage build is to allow us to pick up both the upstream repo's files, and local files here for config
|
# Two-stage build is to allow us to pick up both the upstream repo's files, and local files here for config
|
||||||
docker build -t cerc/ping-pub-base:local ${build_command_args} -f $SCRIPT_DIR/Dockerfile.base $CERC_REPO_BASE_DIR/cosmos-explorer
|
docker build -t cerc/ping-pub-base:local ${build_command_args} -f $SCRIPT_DIR/Dockerfile.base $CERC_REPO_BASE_DIR/explorer
|
||||||
if [[ $? -ne 0 ]]; then
|
|
||||||
echo "FATAL: Base container build failed, exiting"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
docker build -t cerc/ping-pub:local ${build_command_args} -f $SCRIPT_DIR/Dockerfile $SCRIPT_DIR
|
docker build -t cerc/ping-pub:local ${build_command_args} -f $SCRIPT_DIR/Dockerfile $SCRIPT_DIR
|
||||||
|
@ -10,7 +10,7 @@ repos:
|
|||||||
- git.vdb.to/cerc-io/registry-sdk
|
- git.vdb.to/cerc-io/registry-sdk
|
||||||
- git.vdb.to/cerc-io/laconic-registry-cli
|
- git.vdb.to/cerc-io/laconic-registry-cli
|
||||||
- git.vdb.to/cerc-io/laconic-console
|
- git.vdb.to/cerc-io/laconic-console
|
||||||
- git.vdb.to/cerc-io/cosmos-explorer
|
- github.com/ping-pub/explorer
|
||||||
npms:
|
npms:
|
||||||
- registry-sdk
|
- registry-sdk
|
||||||
- laconic-registry-cli
|
- laconic-registry-cli
|
||||||
|
@ -2,50 +2,4 @@
|
|||||||
|
|
||||||
The Package Registry Stack supports a build environment that requires a package registry (initially for NPM packages only).
|
The Package Registry Stack supports a build environment that requires a package registry (initially for NPM packages only).
|
||||||
|
|
||||||
## Setup
|
Setup instructions can be found [here](../build-support/README.md).
|
||||||
|
|
||||||
* Setup required repos and build containers:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
laconic-so --stack package-registry setup-repositories
|
|
||||||
laconic-so --stack package-registry build-containers
|
|
||||||
```
|
|
||||||
|
|
||||||
* Create a deployment:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
laconic-so --stack package-registry deploy init --output package-registry-spec.yml
|
|
||||||
# Update port mapping in the laconic-loaded.spec file to resolve port conflicts on host if any
|
|
||||||
|
|
||||||
laconic-so --stack package-registry deploy create --deployment-dir package-registry-deployment --spec-file package-registry-spec.yml
|
|
||||||
```
|
|
||||||
|
|
||||||
* Start the deployment:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
laconic-so deployment --dir package-registry-deployment start
|
|
||||||
```
|
|
||||||
|
|
||||||
* The local gitea registry can now be accessed at <http://localhost:3000> (the username and password can be taken from the deployment logs)
|
|
||||||
|
|
||||||
* Configure the hostname `gitea.local`:
|
|
||||||
|
|
||||||
Update `/etc/hosts`:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
sudo nano /etc/hosts
|
|
||||||
|
|
||||||
# Add the following line
|
|
||||||
127.0.0.1 gitea.local
|
|
||||||
```
|
|
||||||
|
|
||||||
Check resolution:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
ping gitea.local
|
|
||||||
|
|
||||||
PING gitea.local (127.0.0.1) 56(84) bytes of data.
|
|
||||||
64 bytes from localhost (127.0.0.1): icmp_seq=1 ttl=64 time=0.147 ms
|
|
||||||
64 bytes from localhost (127.0.0.1): icmp_seq=2 ttl=64 time=0.033 ms
|
|
||||||
...
|
|
||||||
```
|
|
||||||
|
@ -14,7 +14,6 @@
|
|||||||
# along with this program. If not, see <http:#www.gnu.org/licenses/>.
|
# along with this program. If not, see <http:#www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import base64
|
|
||||||
|
|
||||||
from kubernetes import client
|
from kubernetes import client
|
||||||
from typing import Any, List, Set
|
from typing import Any, List, Set
|
||||||
@ -261,12 +260,12 @@ class ClusterInfo:
|
|||||||
for f in os.listdir(cfg_map_path):
|
for f in os.listdir(cfg_map_path):
|
||||||
full_path = os.path.join(cfg_map_path, f)
|
full_path = os.path.join(cfg_map_path, f)
|
||||||
if os.path.isfile(full_path):
|
if os.path.isfile(full_path):
|
||||||
data[f] = base64.b64encode(open(full_path, 'rb').read()).decode('ASCII')
|
data[f] = open(full_path, 'rt').read()
|
||||||
|
|
||||||
spec = client.V1ConfigMap(
|
spec = client.V1ConfigMap(
|
||||||
metadata=client.V1ObjectMeta(name=f"{self.app_name}-{cfg_map_name}",
|
metadata=client.V1ObjectMeta(name=f"{self.app_name}-{cfg_map_name}",
|
||||||
labels={"configmap-label": cfg_map_name}),
|
labels={"configmap-label": cfg_map_name}),
|
||||||
binary_data=data
|
data=data
|
||||||
)
|
)
|
||||||
result.append(spec)
|
result.append(spec)
|
||||||
return result
|
return result
|
||||||
|
@ -21,19 +21,15 @@ import sys
|
|||||||
import tempfile
|
import tempfile
|
||||||
import time
|
import time
|
||||||
import uuid
|
import uuid
|
||||||
import yaml
|
|
||||||
|
|
||||||
import click
|
import click
|
||||||
import gnupg
|
|
||||||
|
|
||||||
from stack_orchestrator.deploy.images import remote_image_exists
|
from stack_orchestrator.deploy.images import remote_image_exists
|
||||||
from stack_orchestrator.deploy.webapp import deploy_webapp
|
from stack_orchestrator.deploy.webapp import deploy_webapp
|
||||||
from stack_orchestrator.deploy.webapp.util import (
|
from stack_orchestrator.deploy.webapp.util import (
|
||||||
AttrDict,
|
|
||||||
LaconicRegistryClient,
|
LaconicRegistryClient,
|
||||||
TimedLogger,
|
TimedLogger,
|
||||||
build_container_image,
|
build_container_image,
|
||||||
confirm_auction,
|
|
||||||
push_container_image,
|
push_container_image,
|
||||||
file_hash,
|
file_hash,
|
||||||
deploy_to_k8s,
|
deploy_to_k8s,
|
||||||
@ -43,7 +39,6 @@ from stack_orchestrator.deploy.webapp.util import (
|
|||||||
match_owner,
|
match_owner,
|
||||||
skip_by_tag,
|
skip_by_tag,
|
||||||
confirm_payment,
|
confirm_payment,
|
||||||
load_known_requests,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -60,10 +55,7 @@ def process_app_deployment_request(
|
|||||||
force_rebuild,
|
force_rebuild,
|
||||||
fqdn_policy,
|
fqdn_policy,
|
||||||
recreate_on_deploy,
|
recreate_on_deploy,
|
||||||
webapp_deployer_record,
|
payment_address,
|
||||||
gpg,
|
|
||||||
private_key_passphrase,
|
|
||||||
config_upload_dir,
|
|
||||||
logger,
|
logger,
|
||||||
):
|
):
|
||||||
logger.log("BEGIN - process_app_deployment_request")
|
logger.log("BEGIN - process_app_deployment_request")
|
||||||
@ -115,31 +107,14 @@ def process_app_deployment_request(
|
|||||||
)
|
)
|
||||||
|
|
||||||
# 4. get build and runtime config from request
|
# 4. get build and runtime config from request
|
||||||
env = {}
|
|
||||||
if app_deployment_request.attributes.config:
|
|
||||||
if "ref" in app_deployment_request.attributes.config:
|
|
||||||
with open(
|
|
||||||
f"{config_upload_dir}/{app_deployment_request.attributes.config.ref}",
|
|
||||||
"rb",
|
|
||||||
) as file:
|
|
||||||
record_owner = laconic.get_owner(app_deployment_request)
|
|
||||||
decrypted = gpg.decrypt_file(file, passphrase=private_key_passphrase)
|
|
||||||
parsed = AttrDict(yaml.safe_load(decrypted.data))
|
|
||||||
if record_owner not in parsed.authorized:
|
|
||||||
raise Exception(
|
|
||||||
f"{record_owner} not authorized to access config {app_deployment_request.attributes.config.ref}"
|
|
||||||
)
|
|
||||||
if "env" in parsed.config:
|
|
||||||
env.update(parsed.config.env)
|
|
||||||
|
|
||||||
if "env" in app_deployment_request.attributes.config:
|
|
||||||
env.update(app_deployment_request.attributes.config.env)
|
|
||||||
|
|
||||||
env_filename = None
|
env_filename = None
|
||||||
if env:
|
if (
|
||||||
|
app_deployment_request.attributes.config
|
||||||
|
and "env" in app_deployment_request.attributes.config
|
||||||
|
):
|
||||||
env_filename = tempfile.mktemp()
|
env_filename = tempfile.mktemp()
|
||||||
with open(env_filename, "w") as file:
|
with open(env_filename, "w") as file:
|
||||||
for k, v in env.items():
|
for k, v in app_deployment_request.attributes.config["env"].items():
|
||||||
file.write("%s=%s\n" % (k, shlex.quote(str(v))))
|
file.write("%s=%s\n" % (k, shlex.quote(str(v))))
|
||||||
|
|
||||||
# 5. determine new or existing deployment
|
# 5. determine new or existing deployment
|
||||||
@ -252,13 +227,19 @@ def process_app_deployment_request(
|
|||||||
dns_lrn,
|
dns_lrn,
|
||||||
deployment_dir,
|
deployment_dir,
|
||||||
app_deployment_request,
|
app_deployment_request,
|
||||||
webapp_deployer_record,
|
payment_address,
|
||||||
logger,
|
logger,
|
||||||
)
|
)
|
||||||
logger.log("Publication complete.")
|
logger.log("Publication complete.")
|
||||||
logger.log("END - process_app_deployment_request")
|
logger.log("END - process_app_deployment_request")
|
||||||
|
|
||||||
|
|
||||||
|
def load_known_requests(filename):
|
||||||
|
if filename and os.path.exists(filename):
|
||||||
|
return json.load(open(filename, "r"))
|
||||||
|
return {}
|
||||||
|
|
||||||
|
|
||||||
def dump_known_requests(filename, requests, status="SEEN"):
|
def dump_known_requests(filename, requests, status="SEEN"):
|
||||||
if not filename:
|
if not filename:
|
||||||
return
|
return
|
||||||
@ -304,12 +285,8 @@ def dump_known_requests(filename, requests, status="SEEN"):
|
|||||||
help="How to handle requests with an FQDN: prohibit, allow, preexisting",
|
help="How to handle requests with an FQDN: prohibit, allow, preexisting",
|
||||||
default="prohibit",
|
default="prohibit",
|
||||||
)
|
)
|
||||||
@click.option("--record-namespace-dns", help="eg, lrn://laconic/dns", required=True)
|
@click.option("--record-namespace-dns", help="eg, lrn://laconic/dns")
|
||||||
@click.option(
|
@click.option("--record-namespace-deployments", help="eg, lrn://laconic/deployments")
|
||||||
"--record-namespace-deployments",
|
|
||||||
help="eg, lrn://laconic/deployments",
|
|
||||||
required=True,
|
|
||||||
)
|
|
||||||
@click.option(
|
@click.option(
|
||||||
"--dry-run", help="Don't do anything, just report what would be done.", is_flag=True
|
"--dry-run", help="Don't do anything, just report what would be done.", is_flag=True
|
||||||
)
|
)
|
||||||
@ -336,38 +313,21 @@ def dump_known_requests(filename, requests, status="SEEN"):
|
|||||||
)
|
)
|
||||||
@click.option(
|
@click.option(
|
||||||
"--min-required-payment",
|
"--min-required-payment",
|
||||||
help="Requests must have a minimum payment to be processed (in alnt)",
|
help="Requests must have a minimum payment to be processed",
|
||||||
default=0,
|
default=0,
|
||||||
)
|
)
|
||||||
@click.option("--lrn", help="The LRN of this deployer.", required=True)
|
@click.option(
|
||||||
|
"--payment-address",
|
||||||
|
help="The address to which payments should be made. "
|
||||||
|
"Default is the current laconic account.",
|
||||||
|
default=None,
|
||||||
|
)
|
||||||
@click.option(
|
@click.option(
|
||||||
"--all-requests",
|
"--all-requests",
|
||||||
help="Handle requests addressed to anyone (by default only requests to"
|
help="Handle requests addressed to anyone (by default only requests to"
|
||||||
"my payment address are examined).",
|
"my payment address are examined).",
|
||||||
is_flag=True,
|
is_flag=True,
|
||||||
)
|
)
|
||||||
@click.option(
|
|
||||||
"--auction-requests",
|
|
||||||
help="Handle requests with auction id set (skips payment confirmation).",
|
|
||||||
is_flag=True,
|
|
||||||
default=False,
|
|
||||||
)
|
|
||||||
@click.option(
|
|
||||||
"--config-upload-dir",
|
|
||||||
help="The directory containing uploaded config.",
|
|
||||||
required=True,
|
|
||||||
)
|
|
||||||
@click.option(
|
|
||||||
"--private-key-file", help="The private key for decrypting config.", required=True
|
|
||||||
)
|
|
||||||
@click.option(
|
|
||||||
"--registry-lock-file", help="File path to use for registry mutex lock", default=None
|
|
||||||
)
|
|
||||||
@click.option(
|
|
||||||
"--private-key-passphrase",
|
|
||||||
help="The passphrase for the private key.",
|
|
||||||
required=True,
|
|
||||||
)
|
|
||||||
@click.pass_context
|
@click.pass_context
|
||||||
def command( # noqa: C901
|
def command( # noqa: C901
|
||||||
ctx,
|
ctx,
|
||||||
@ -390,13 +350,8 @@ def command( # noqa: C901
|
|||||||
recreate_on_deploy,
|
recreate_on_deploy,
|
||||||
log_dir,
|
log_dir,
|
||||||
min_required_payment,
|
min_required_payment,
|
||||||
lrn,
|
payment_address,
|
||||||
config_upload_dir,
|
|
||||||
private_key_file,
|
|
||||||
private_key_passphrase,
|
|
||||||
all_requests,
|
all_requests,
|
||||||
auction_requests,
|
|
||||||
registry_lock_file,
|
|
||||||
):
|
):
|
||||||
if request_id and discover:
|
if request_id and discover:
|
||||||
print("Cannot specify both --request-id and --discover", file=sys.stderr)
|
print("Cannot specify both --request-id and --discover", file=sys.stderr)
|
||||||
@ -429,18 +384,6 @@ def command( # noqa: C901
|
|||||||
)
|
)
|
||||||
sys.exit(2)
|
sys.exit(2)
|
||||||
|
|
||||||
tempdir = tempfile.mkdtemp()
|
|
||||||
gpg = gnupg.GPG(gnupghome=tempdir)
|
|
||||||
|
|
||||||
# Import the deployer's public key
|
|
||||||
result = gpg.import_keys(open(private_key_file, "rb").read())
|
|
||||||
if 1 != result.imported:
|
|
||||||
print(
|
|
||||||
f"Failed to load private key file: {private_key_file}.",
|
|
||||||
file=sys.stderr,
|
|
||||||
)
|
|
||||||
sys.exit(2)
|
|
||||||
|
|
||||||
main_logger = TimedLogger(file=sys.stderr)
|
main_logger = TimedLogger(file=sys.stderr)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@ -448,17 +391,11 @@ def command( # noqa: C901
|
|||||||
include_tags = [tag.strip() for tag in include_tags.split(",") if tag]
|
include_tags = [tag.strip() for tag in include_tags.split(",") if tag]
|
||||||
exclude_tags = [tag.strip() for tag in exclude_tags.split(",") if tag]
|
exclude_tags = [tag.strip() for tag in exclude_tags.split(",") if tag]
|
||||||
|
|
||||||
laconic = LaconicRegistryClient(laconic_config, log_file=sys.stderr, mutex_lock_file=registry_lock_file)
|
laconic = LaconicRegistryClient(laconic_config, log_file=sys.stderr)
|
||||||
webapp_deployer_record = laconic.get_record(lrn, require=True)
|
if not payment_address:
|
||||||
payment_address = webapp_deployer_record.attributes.paymentAddress
|
payment_address = laconic.whoami().address
|
||||||
main_logger.log(f"Payment address: {payment_address}")
|
|
||||||
|
|
||||||
if min_required_payment and not payment_address:
|
main_logger.log(f"Payment address: {payment_address}")
|
||||||
print(
|
|
||||||
f"Minimum payment required, but no payment address listed for deployer: {lrn}.",
|
|
||||||
file=sys.stderr,
|
|
||||||
)
|
|
||||||
sys.exit(2)
|
|
||||||
|
|
||||||
# Find deployment requests.
|
# Find deployment requests.
|
||||||
# single request
|
# single request
|
||||||
@ -471,7 +408,7 @@ def command( # noqa: C901
|
|||||||
if all_requests:
|
if all_requests:
|
||||||
requests = laconic.app_deployment_requests()
|
requests = laconic.app_deployment_requests()
|
||||||
else:
|
else:
|
||||||
requests = laconic.app_deployment_requests({"deployer": lrn})
|
requests = laconic.app_deployment_requests({"to": payment_address})
|
||||||
|
|
||||||
if only_update_state:
|
if only_update_state:
|
||||||
if not dry_run:
|
if not dry_run:
|
||||||
@ -550,7 +487,7 @@ def command( # noqa: C901
|
|||||||
if all_requests:
|
if all_requests:
|
||||||
deployments = laconic.app_deployments()
|
deployments = laconic.app_deployments()
|
||||||
else:
|
else:
|
||||||
deployments = laconic.app_deployments({"deployer": lrn})
|
deployments = laconic.app_deployments({"by": payment_address})
|
||||||
deployments_by_request = {}
|
deployments_by_request = {}
|
||||||
for d in deployments:
|
for d in deployments:
|
||||||
if d.attributes.request:
|
if d.attributes.request:
|
||||||
@ -589,36 +526,11 @@ def command( # noqa: C901
|
|||||||
requests_to_check_for_payment.append(r)
|
requests_to_check_for_payment.append(r)
|
||||||
|
|
||||||
requests_to_execute = []
|
requests_to_execute = []
|
||||||
for r in requests_to_check_for_payment:
|
if min_required_payment:
|
||||||
if r.attributes.auction:
|
for r in requests_to_check_for_payment:
|
||||||
if auction_requests:
|
|
||||||
if confirm_auction(
|
|
||||||
laconic,
|
|
||||||
r,
|
|
||||||
lrn,
|
|
||||||
payment_address,
|
|
||||||
main_logger
|
|
||||||
):
|
|
||||||
main_logger.log(f"{r.id}: Auction confirmed.")
|
|
||||||
requests_to_execute.append(r)
|
|
||||||
else:
|
|
||||||
main_logger.log(
|
|
||||||
f"Skipping request {r.id}: unable to verify auction."
|
|
||||||
)
|
|
||||||
dump_known_requests(state_file, [r], status="SKIP")
|
|
||||||
else:
|
|
||||||
main_logger.log(
|
|
||||||
f"Skipping request {r.id}: not handling requests with auction."
|
|
||||||
)
|
|
||||||
dump_known_requests(state_file, [r], status="SKIP")
|
|
||||||
elif min_required_payment:
|
|
||||||
main_logger.log(f"{r.id}: Confirming payment...")
|
main_logger.log(f"{r.id}: Confirming payment...")
|
||||||
if confirm_payment(
|
if confirm_payment(
|
||||||
laconic,
|
laconic, r, payment_address, min_required_payment, main_logger
|
||||||
r,
|
|
||||||
payment_address,
|
|
||||||
min_required_payment,
|
|
||||||
main_logger,
|
|
||||||
):
|
):
|
||||||
main_logger.log(f"{r.id}: Payment confirmed.")
|
main_logger.log(f"{r.id}: Payment confirmed.")
|
||||||
requests_to_execute.append(r)
|
requests_to_execute.append(r)
|
||||||
@ -627,8 +539,8 @@ def command( # noqa: C901
|
|||||||
f"Skipping request {r.id}: unable to verify payment."
|
f"Skipping request {r.id}: unable to verify payment."
|
||||||
)
|
)
|
||||||
dump_known_requests(state_file, [r], status="UNPAID")
|
dump_known_requests(state_file, [r], status="UNPAID")
|
||||||
else:
|
else:
|
||||||
requests_to_execute.append(r)
|
requests_to_execute = requests_to_check_for_payment
|
||||||
|
|
||||||
main_logger.log(
|
main_logger.log(
|
||||||
"Found %d unsatisfied request(s) to process." % len(requests_to_execute)
|
"Found %d unsatisfied request(s) to process." % len(requests_to_execute)
|
||||||
@ -653,7 +565,7 @@ def command( # noqa: C901
|
|||||||
)
|
)
|
||||||
run_log_file = open(run_log_file_path, "wt")
|
run_log_file = open(run_log_file_path, "wt")
|
||||||
run_reg_client = LaconicRegistryClient(
|
run_reg_client = LaconicRegistryClient(
|
||||||
laconic_config, log_file=run_log_file, mutex_lock_file=registry_lock_file
|
laconic_config, log_file=run_log_file
|
||||||
)
|
)
|
||||||
|
|
||||||
build_logger = TimedLogger(run_id, run_log_file)
|
build_logger = TimedLogger(run_id, run_log_file)
|
||||||
@ -671,10 +583,7 @@ def command( # noqa: C901
|
|||||||
force_rebuild,
|
force_rebuild,
|
||||||
fqdn_policy,
|
fqdn_policy,
|
||||||
recreate_on_deploy,
|
recreate_on_deploy,
|
||||||
webapp_deployer_record,
|
payment_address,
|
||||||
gpg,
|
|
||||||
private_key_passphrase,
|
|
||||||
config_upload_dir,
|
|
||||||
build_logger,
|
build_logger,
|
||||||
)
|
)
|
||||||
status = "DEPLOYED"
|
status = "DEPLOYED"
|
||||||
@ -695,5 +604,3 @@ def command( # noqa: C901
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
main_logger.log("UNCAUGHT ERROR:" + str(e))
|
main_logger.log("UNCAUGHT ERROR:" + str(e))
|
||||||
raise e
|
raise e
|
||||||
finally:
|
|
||||||
shutil.rmtree(tempdir, ignore_errors=True)
|
|
||||||
|
@ -1,220 +0,0 @@
|
|||||||
# Copyright ©2023 Vulcanize
|
|
||||||
# This program is free software: you can redistribute it and/or modify
|
|
||||||
# it under the terms of the GNU Affero General Public License as published by
|
|
||||||
# the Free Software Foundation, either version 3 of the License, or
|
|
||||||
# (at your option) any later version.
|
|
||||||
|
|
||||||
# This program is distributed in the hope that it will be useful,
|
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
# GNU Affero General Public License for more details.
|
|
||||||
|
|
||||||
# You should have received a copy of the GNU Affero General Public License
|
|
||||||
# along with this program. If not, see <http:#www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
import sys
|
|
||||||
import json
|
|
||||||
|
|
||||||
import click
|
|
||||||
|
|
||||||
from stack_orchestrator.deploy.webapp.util import (
|
|
||||||
AttrDict,
|
|
||||||
LaconicRegistryClient,
|
|
||||||
TimedLogger,
|
|
||||||
load_known_requests,
|
|
||||||
AUCTION_KIND_PROVIDER,
|
|
||||||
AuctionStatus,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def process_app_deployment_auction(
|
|
||||||
ctx,
|
|
||||||
laconic: LaconicRegistryClient,
|
|
||||||
request,
|
|
||||||
current_status,
|
|
||||||
reveal_file_path,
|
|
||||||
bid_amount,
|
|
||||||
logger,
|
|
||||||
):
|
|
||||||
# Fetch auction details
|
|
||||||
auction_id = request.attributes.auction
|
|
||||||
auction = laconic.get_auction(auction_id)
|
|
||||||
if not auction:
|
|
||||||
raise Exception(f"Unable to locate auction: {auction_id}")
|
|
||||||
|
|
||||||
# Check auction kind
|
|
||||||
if auction.kind != AUCTION_KIND_PROVIDER:
|
|
||||||
raise Exception(f"Auction kind needs to be ${AUCTION_KIND_PROVIDER}, got {auction.kind}")
|
|
||||||
|
|
||||||
if current_status == "PENDING":
|
|
||||||
# Skip if pending auction not in commit state
|
|
||||||
if auction.status != AuctionStatus.COMMIT:
|
|
||||||
logger.log(f"Skipping pending request, auction {auction_id} status: {auction.status}")
|
|
||||||
return "SKIP", ""
|
|
||||||
|
|
||||||
# Check max_price
|
|
||||||
bid_amount_int = int(bid_amount)
|
|
||||||
max_price_int = int(auction.maxPrice.quantity)
|
|
||||||
if max_price_int < bid_amount_int:
|
|
||||||
logger.log(f"Skipping auction {auction_id} with max_price ({max_price_int}) less than bid_amount ({bid_amount_int})")
|
|
||||||
return "SKIP", ""
|
|
||||||
|
|
||||||
# Bid on the auction
|
|
||||||
reveal_file_path = laconic.commit_bid(auction_id, bid_amount_int)
|
|
||||||
logger.log(f"Commited bid on auction {auction_id} with amount {bid_amount_int}")
|
|
||||||
|
|
||||||
return "COMMIT", reveal_file_path
|
|
||||||
|
|
||||||
if current_status == "COMMIT":
|
|
||||||
# Return if auction still in commit state
|
|
||||||
if auction.status == AuctionStatus.COMMIT:
|
|
||||||
logger.log(f"Auction {auction_id} status: {auction.status}")
|
|
||||||
return current_status, reveal_file_path
|
|
||||||
|
|
||||||
# Reveal bid
|
|
||||||
if auction.status == AuctionStatus.REVEAL:
|
|
||||||
laconic.reveal_bid(auction_id, reveal_file_path)
|
|
||||||
logger.log(f"Revealed bid on auction {auction_id}")
|
|
||||||
|
|
||||||
return "REVEAL", reveal_file_path
|
|
||||||
|
|
||||||
raise Exception(f"Unexpected auction {auction_id} status: {auction.status}")
|
|
||||||
|
|
||||||
if current_status == "REVEAL":
|
|
||||||
# Return if auction still in reveal state
|
|
||||||
if auction.status == AuctionStatus.REVEAL:
|
|
||||||
logger.log(f"Auction {auction_id} status: {auction.status}")
|
|
||||||
return current_status, reveal_file_path
|
|
||||||
|
|
||||||
# Return if auction is completed
|
|
||||||
if auction.status == AuctionStatus.COMPLETED:
|
|
||||||
logger.log(f"Auction {auction_id} completed")
|
|
||||||
return "COMPLETED", ""
|
|
||||||
|
|
||||||
raise Exception(f"Unexpected auction {auction_id} status: {auction.status}")
|
|
||||||
|
|
||||||
raise Exception(f"Got request with unexpected status: {current_status}")
|
|
||||||
|
|
||||||
|
|
||||||
def dump_known_auction_requests(filename, requests, status="SEEN"):
|
|
||||||
if not filename:
|
|
||||||
return
|
|
||||||
known_requests = load_known_requests(filename)
|
|
||||||
for r in requests:
|
|
||||||
known_requests[r.id] = {"revealFile": r.revealFile, "status": status}
|
|
||||||
with open(filename, "w") as f:
|
|
||||||
json.dump(known_requests, f)
|
|
||||||
|
|
||||||
|
|
||||||
@click.command()
|
|
||||||
@click.option(
|
|
||||||
"--laconic-config", help="Provide a config file for laconicd", required=True
|
|
||||||
)
|
|
||||||
@click.option(
|
|
||||||
"--state-file",
|
|
||||||
help="File to store state about previously seen auction requests.",
|
|
||||||
required=True,
|
|
||||||
)
|
|
||||||
@click.option(
|
|
||||||
"--bid-amount",
|
|
||||||
help="Bid to place on application deployment auctions (in alnt)",
|
|
||||||
required=True,
|
|
||||||
)
|
|
||||||
@click.option(
|
|
||||||
"--registry-lock-file", help="File path to use for registry mutex lock", default=None
|
|
||||||
)
|
|
||||||
@click.option(
|
|
||||||
"--dry-run", help="Don't do anything, just report what would be done.", is_flag=True
|
|
||||||
)
|
|
||||||
@click.pass_context
|
|
||||||
def command(
|
|
||||||
ctx,
|
|
||||||
laconic_config,
|
|
||||||
state_file,
|
|
||||||
bid_amount,
|
|
||||||
registry_lock_file,
|
|
||||||
dry_run,
|
|
||||||
):
|
|
||||||
if int(bid_amount) < 0:
|
|
||||||
print("--bid-amount cannot be less than 0", file=sys.stderr)
|
|
||||||
sys.exit(2)
|
|
||||||
|
|
||||||
logger = TimedLogger(file=sys.stderr)
|
|
||||||
|
|
||||||
try:
|
|
||||||
laconic = LaconicRegistryClient(laconic_config, log_file=sys.stderr, mutex_lock_file=registry_lock_file)
|
|
||||||
auctions_requests = laconic.app_deployment_auctions()
|
|
||||||
|
|
||||||
previous_requests = {}
|
|
||||||
logger.log(f"Loading known auctions from {state_file}...")
|
|
||||||
previous_requests = load_known_requests(state_file)
|
|
||||||
|
|
||||||
# Process new requests first
|
|
||||||
auctions_requests.sort(key=lambda r: r.createTime)
|
|
||||||
auctions_requests.reverse()
|
|
||||||
|
|
||||||
requests_to_execute = []
|
|
||||||
|
|
||||||
for r in auctions_requests:
|
|
||||||
logger.log(f"BEGIN: Examining request {r.id}")
|
|
||||||
result_status = "PENDING"
|
|
||||||
reveal_file_path = ""
|
|
||||||
try:
|
|
||||||
application = r.attributes.application
|
|
||||||
|
|
||||||
# Handle already seen requests
|
|
||||||
if r.id in previous_requests:
|
|
||||||
# If it's not in commit or reveal status, skip the request as we've already seen it
|
|
||||||
current_status = previous_requests[r.id].get("status", "")
|
|
||||||
result_status = current_status
|
|
||||||
if current_status not in ["COMMIT", "REVEAL"]:
|
|
||||||
logger.log(f"Skipping request {r.id}, we've already seen it.")
|
|
||||||
continue
|
|
||||||
|
|
||||||
reveal_file_path = previous_requests[r.id].get("revealFile", "")
|
|
||||||
logger.log(f"Found existing auction request {r.id} for application {application}, status {current_status}.")
|
|
||||||
else:
|
|
||||||
# It's a fresh request, check application record
|
|
||||||
app = laconic.get_record(application)
|
|
||||||
if not app:
|
|
||||||
logger.log(f"Skipping request {r.id}, cannot locate app.")
|
|
||||||
result_status = "ERROR"
|
|
||||||
continue
|
|
||||||
|
|
||||||
logger.log(f"Found pending auction request {r.id} for application {application}.")
|
|
||||||
|
|
||||||
# Add requests to be processed
|
|
||||||
requests_to_execute.append((r, result_status, reveal_file_path))
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
result_status = "ERROR"
|
|
||||||
logger.log(f"ERROR: examining request {r.id}: " + str(e))
|
|
||||||
finally:
|
|
||||||
logger.log(f"DONE: Examining request {r.id} with result {result_status}.")
|
|
||||||
if result_status in ["ERROR"]:
|
|
||||||
dump_known_auction_requests(state_file, [AttrDict({"id": r.id, "revealFile": reveal_file_path})], result_status)
|
|
||||||
|
|
||||||
logger.log(f"Found {len(requests_to_execute)} request(s) to process.")
|
|
||||||
|
|
||||||
if not dry_run:
|
|
||||||
for r, current_status, reveal_file_path in requests_to_execute:
|
|
||||||
logger.log(f"Processing {r.id}: BEGIN")
|
|
||||||
result_status = "ERROR"
|
|
||||||
try:
|
|
||||||
result_status, reveal_file_path = process_app_deployment_auction(
|
|
||||||
ctx,
|
|
||||||
laconic,
|
|
||||||
r,
|
|
||||||
current_status,
|
|
||||||
reveal_file_path,
|
|
||||||
bid_amount,
|
|
||||||
logger,
|
|
||||||
)
|
|
||||||
except Exception as e:
|
|
||||||
logger.log(f"ERROR {r.id}:" + str(e))
|
|
||||||
finally:
|
|
||||||
logger.log(f"Processing {r.id}: END - {result_status}")
|
|
||||||
dump_known_auction_requests(state_file, [AttrDict({"id": r.id, "revealFile": reveal_file_path})], result_status)
|
|
||||||
except Exception as e:
|
|
||||||
logger.log("UNCAUGHT ERROR:" + str(e))
|
|
||||||
raise e
|
|
@ -1,124 +0,0 @@
|
|||||||
# Copyright ©2023 Vulcanize
|
|
||||||
# This program is free software: you can redistribute it and/or modify
|
|
||||||
# it under the terms of the GNU Affero General Public License as published by
|
|
||||||
# the Free Software Foundation, either version 3 of the License, or
|
|
||||||
# (at your option) any later version.
|
|
||||||
|
|
||||||
# This program is distributed in the hope that it will be useful,
|
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
# GNU Affero General Public License for more details.
|
|
||||||
|
|
||||||
# You should have received a copy of the GNU Affero General Public License
|
|
||||||
# along with this program. If not, see <http:#www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
import sys
|
|
||||||
|
|
||||||
import click
|
|
||||||
import yaml
|
|
||||||
|
|
||||||
from stack_orchestrator.deploy.webapp.util import (
|
|
||||||
AUCTION_KIND_PROVIDER,
|
|
||||||
TOKEN_DENOM,
|
|
||||||
LaconicRegistryClient,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def fatal(msg: str):
|
|
||||||
print(msg, file=sys.stderr)
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
|
|
||||||
@click.command()
|
|
||||||
@click.option(
|
|
||||||
"--laconic-config", help="Provide a config file for laconicd", required=True
|
|
||||||
)
|
|
||||||
@click.option(
|
|
||||||
"--app",
|
|
||||||
help="The LRN of the application to deploy.",
|
|
||||||
required=True,
|
|
||||||
)
|
|
||||||
@click.option(
|
|
||||||
"--commits-duration",
|
|
||||||
help="Auction commits duration (in seconds) (default: 600).",
|
|
||||||
default=600,
|
|
||||||
)
|
|
||||||
@click.option(
|
|
||||||
"--reveals-duration",
|
|
||||||
help="Auction reveals duration (in seconds) (default: 600).",
|
|
||||||
default=600,
|
|
||||||
)
|
|
||||||
@click.option(
|
|
||||||
"--commit-fee",
|
|
||||||
help="Auction bid commit fee (in alnt) (default: 100000).",
|
|
||||||
default=100000,
|
|
||||||
)
|
|
||||||
@click.option(
|
|
||||||
"--reveal-fee",
|
|
||||||
help="Auction bid reveal fee (in alnt) (default: 100000).",
|
|
||||||
default=100000,
|
|
||||||
)
|
|
||||||
@click.option(
|
|
||||||
"--max-price",
|
|
||||||
help="Max acceptable bid price (in alnt).",
|
|
||||||
required=True,
|
|
||||||
)
|
|
||||||
@click.option(
|
|
||||||
"--num-providers",
|
|
||||||
help="Max acceptable bid price (in alnt).",
|
|
||||||
required=True,
|
|
||||||
)
|
|
||||||
@click.option(
|
|
||||||
"--dry-run",
|
|
||||||
help="Don't publish anything, just report what would be done.",
|
|
||||||
is_flag=True,
|
|
||||||
)
|
|
||||||
@click.pass_context
|
|
||||||
def command(
|
|
||||||
ctx,
|
|
||||||
laconic_config,
|
|
||||||
app,
|
|
||||||
commits_duration,
|
|
||||||
reveals_duration,
|
|
||||||
commit_fee,
|
|
||||||
reveal_fee,
|
|
||||||
max_price,
|
|
||||||
num_providers,
|
|
||||||
dry_run,
|
|
||||||
):
|
|
||||||
laconic = LaconicRegistryClient(laconic_config)
|
|
||||||
|
|
||||||
app_record = laconic.get_record(app)
|
|
||||||
if not app_record:
|
|
||||||
fatal(f"Unable to locate app: {app}")
|
|
||||||
|
|
||||||
provider_auction_params = {
|
|
||||||
"kind": AUCTION_KIND_PROVIDER,
|
|
||||||
"commits_duration": commits_duration,
|
|
||||||
"reveals_duration": reveals_duration,
|
|
||||||
"denom": TOKEN_DENOM,
|
|
||||||
"commit_fee": commit_fee,
|
|
||||||
"reveal_fee": reveal_fee,
|
|
||||||
"max_price": max_price,
|
|
||||||
"num_providers": num_providers,
|
|
||||||
}
|
|
||||||
auction_id = laconic.create_deployment_auction(provider_auction_params)
|
|
||||||
print("Deployment auction created:", auction_id)
|
|
||||||
|
|
||||||
if not auction_id:
|
|
||||||
fatal("Unable to create a provider auction")
|
|
||||||
|
|
||||||
deployment_auction = {
|
|
||||||
"record": {
|
|
||||||
"type": "ApplicationDeploymentAuction",
|
|
||||||
"application": app,
|
|
||||||
"auction": auction_id,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if dry_run:
|
|
||||||
print(yaml.dump(deployment_auction))
|
|
||||||
return
|
|
||||||
|
|
||||||
# Publish the deployment auction record
|
|
||||||
laconic.publish(deployment_auction)
|
|
@ -1,91 +0,0 @@
|
|||||||
# Copyright ©2023 Vulcanize
|
|
||||||
# This program is free software: you can redistribute it and/or modify
|
|
||||||
# it under the terms of the GNU Affero General Public License as published by
|
|
||||||
# the Free Software Foundation, either version 3 of the License, or
|
|
||||||
# (at your option) any later version.
|
|
||||||
|
|
||||||
# This program is distributed in the hope that it will be useful,
|
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
# GNU Affero General Public License for more details.
|
|
||||||
|
|
||||||
# You should have received a copy of the GNU Affero General Public License
|
|
||||||
# along with this program. If not, see <http:#www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
import base64
|
|
||||||
import click
|
|
||||||
import sys
|
|
||||||
import yaml
|
|
||||||
|
|
||||||
from urllib.parse import urlparse
|
|
||||||
|
|
||||||
from stack_orchestrator.deploy.webapp.util import LaconicRegistryClient
|
|
||||||
|
|
||||||
|
|
||||||
@click.command()
|
|
||||||
@click.option(
|
|
||||||
"--laconic-config", help="Provide a config file for laconicd", required=True
|
|
||||||
)
|
|
||||||
@click.option("--api-url", help="The API URL of the deployer.", required=True)
|
|
||||||
@click.option(
|
|
||||||
"--public-key-file",
|
|
||||||
help="The public key to use. This should be a binary file.",
|
|
||||||
required=True,
|
|
||||||
)
|
|
||||||
@click.option(
|
|
||||||
"--lrn", help="eg, lrn://laconic/deployers/my.deployer.name", required=True
|
|
||||||
)
|
|
||||||
@click.option(
|
|
||||||
"--payment-address",
|
|
||||||
help="The address to which payments should be made. "
|
|
||||||
"Default is the current laconic account.",
|
|
||||||
default=None,
|
|
||||||
)
|
|
||||||
@click.option(
|
|
||||||
"--min-required-payment",
|
|
||||||
help="List the minimum required payment (in alnt) to process a deployment request.",
|
|
||||||
default=0,
|
|
||||||
)
|
|
||||||
@click.option(
|
|
||||||
"--dry-run",
|
|
||||||
help="Don't publish anything, just report what would be done.",
|
|
||||||
is_flag=True,
|
|
||||||
)
|
|
||||||
@click.pass_context
|
|
||||||
def command( # noqa: C901
|
|
||||||
ctx,
|
|
||||||
laconic_config,
|
|
||||||
api_url,
|
|
||||||
public_key_file,
|
|
||||||
lrn,
|
|
||||||
payment_address,
|
|
||||||
min_required_payment,
|
|
||||||
dry_run,
|
|
||||||
):
|
|
||||||
laconic = LaconicRegistryClient(laconic_config)
|
|
||||||
if not payment_address:
|
|
||||||
payment_address = laconic.whoami().address
|
|
||||||
|
|
||||||
pub_key = base64.b64encode(open(public_key_file, "rb").read()).decode("ASCII")
|
|
||||||
hostname = urlparse(api_url).hostname
|
|
||||||
webapp_deployer_record = {
|
|
||||||
"record": {
|
|
||||||
"type": "WebappDeployer",
|
|
||||||
"version": "1.0.0",
|
|
||||||
"apiUrl": api_url,
|
|
||||||
"name": hostname,
|
|
||||||
"publicKey": pub_key,
|
|
||||||
"paymentAddress": payment_address,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if min_required_payment:
|
|
||||||
webapp_deployer_record["record"][
|
|
||||||
"minimumPayment"
|
|
||||||
] = f"{min_required_payment}alnt"
|
|
||||||
|
|
||||||
if dry_run:
|
|
||||||
yaml.dump(webapp_deployer_record, sys.stdout)
|
|
||||||
return
|
|
||||||
|
|
||||||
laconic.publish(webapp_deployer_record, [lrn])
|
|
@ -1,77 +0,0 @@
|
|||||||
from functools import wraps
|
|
||||||
import os
|
|
||||||
import time
|
|
||||||
|
|
||||||
# Define default file path for the lock
|
|
||||||
DEFAULT_LOCK_FILE_PATH = "/tmp/registry_mutex_lock_file"
|
|
||||||
LOCK_TIMEOUT = 30
|
|
||||||
LOCK_RETRY_INTERVAL = 3
|
|
||||||
|
|
||||||
|
|
||||||
def acquire_lock(client, lock_file_path, timeout):
|
|
||||||
# Lock alreay acquired by the current client
|
|
||||||
if client.mutex_lock_acquired:
|
|
||||||
return
|
|
||||||
|
|
||||||
while True:
|
|
||||||
try:
|
|
||||||
# Check if lock file exists and is potentially stale
|
|
||||||
if os.path.exists(lock_file_path):
|
|
||||||
with open(lock_file_path, 'r') as lock_file:
|
|
||||||
timestamp = float(lock_file.read().strip())
|
|
||||||
|
|
||||||
# If lock is stale, remove the lock file
|
|
||||||
if time.time() - timestamp > timeout:
|
|
||||||
print(f"Stale lock detected, removing lock file {lock_file_path}")
|
|
||||||
os.remove(lock_file_path)
|
|
||||||
else:
|
|
||||||
print(f"Lock file {lock_file_path} exists and is recent, waiting...")
|
|
||||||
time.sleep(LOCK_RETRY_INTERVAL)
|
|
||||||
continue
|
|
||||||
|
|
||||||
# Try to create a new lock file with the current timestamp
|
|
||||||
fd = os.open(lock_file_path, os.O_CREAT | os.O_EXCL | os.O_RDWR)
|
|
||||||
with os.fdopen(fd, 'w') as lock_file:
|
|
||||||
lock_file.write(str(time.time()))
|
|
||||||
|
|
||||||
client.mutex_lock_acquired = True
|
|
||||||
print(f"Registry lock acquired, {lock_file_path}")
|
|
||||||
|
|
||||||
# Lock successfully acquired
|
|
||||||
return
|
|
||||||
|
|
||||||
except FileExistsError:
|
|
||||||
print(f"Lock file {lock_file_path} exists, waiting...")
|
|
||||||
time.sleep(LOCK_RETRY_INTERVAL)
|
|
||||||
|
|
||||||
|
|
||||||
def release_lock(client, lock_file_path):
|
|
||||||
try:
|
|
||||||
os.remove(lock_file_path)
|
|
||||||
|
|
||||||
client.mutex_lock_acquired = False
|
|
||||||
print(f"Registry lock released, {lock_file_path}")
|
|
||||||
except FileNotFoundError:
|
|
||||||
# Lock file already removed
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def registry_mutex():
|
|
||||||
def decorator(func):
|
|
||||||
@wraps(func)
|
|
||||||
def wrapper(self, *args, **kwargs):
|
|
||||||
lock_file_path = DEFAULT_LOCK_FILE_PATH
|
|
||||||
if self.mutex_lock_file:
|
|
||||||
lock_file_path = self.mutex_lock_file
|
|
||||||
|
|
||||||
# Acquire the lock before running the function
|
|
||||||
acquire_lock(self, lock_file_path, LOCK_TIMEOUT)
|
|
||||||
try:
|
|
||||||
return func(self, *args, **kwargs)
|
|
||||||
finally:
|
|
||||||
# Release the lock after the function completes
|
|
||||||
release_lock(self, lock_file_path)
|
|
||||||
|
|
||||||
return wrapper
|
|
||||||
|
|
||||||
return decorator
|
|
@ -1,262 +0,0 @@
|
|||||||
# Copyright ©2023 Vulcanize
|
|
||||||
# This program is free software: you can redistribute it and/or modify
|
|
||||||
# it under the terms of the GNU Affero General Public License as published by
|
|
||||||
# the Free Software Foundation, either version 3 of the License, or
|
|
||||||
# (at your option) any later version.
|
|
||||||
|
|
||||||
# This program is distributed in the hope that it will be useful,
|
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
# GNU Affero General Public License for more details.
|
|
||||||
|
|
||||||
# You should have received a copy of the GNU Affero General Public License
|
|
||||||
# along with this program. If not, see <http:#www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
import shutil
|
|
||||||
import sys
|
|
||||||
import tempfile
|
|
||||||
from datetime import datetime
|
|
||||||
import base64
|
|
||||||
|
|
||||||
import gnupg
|
|
||||||
import click
|
|
||||||
import requests
|
|
||||||
import yaml
|
|
||||||
|
|
||||||
from stack_orchestrator.deploy.webapp.util import (
|
|
||||||
AUCTION_KIND_PROVIDER,
|
|
||||||
AuctionStatus,
|
|
||||||
LaconicRegistryClient,
|
|
||||||
)
|
|
||||||
from dotenv import dotenv_values
|
|
||||||
|
|
||||||
|
|
||||||
def fatal(msg: str):
|
|
||||||
print(msg, file=sys.stderr)
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
|
|
||||||
@click.command()
|
|
||||||
@click.option(
|
|
||||||
"--laconic-config", help="Provide a config file for laconicd", required=True
|
|
||||||
)
|
|
||||||
@click.option(
|
|
||||||
"--app",
|
|
||||||
help="The LRN of the application to deploy.",
|
|
||||||
required=True,
|
|
||||||
)
|
|
||||||
@click.option(
|
|
||||||
"--auction-id",
|
|
||||||
help="Deployment auction id. Can be used instead of deployer and payment.",
|
|
||||||
)
|
|
||||||
@click.option(
|
|
||||||
"--deployer",
|
|
||||||
help="The LRN of the deployer to process this request.",
|
|
||||||
)
|
|
||||||
@click.option("--env-file", help="environment file for webapp")
|
|
||||||
@click.option("--config-ref", help="The ref of an existing config upload to use.")
|
|
||||||
@click.option(
|
|
||||||
"--make-payment",
|
|
||||||
help="The payment to make (in alnt). The value should be a number or 'auto' to use the deployer's minimum required payment.",
|
|
||||||
)
|
|
||||||
@click.option(
|
|
||||||
"--use-payment", help="The TX id of an existing, unused payment", default=None
|
|
||||||
)
|
|
||||||
@click.option("--dns", help="the DNS name to request (default is autogenerated)")
|
|
||||||
@click.option(
|
|
||||||
"--dry-run",
|
|
||||||
help="Don't publish anything, just report what would be done.",
|
|
||||||
is_flag=True,
|
|
||||||
)
|
|
||||||
@click.pass_context
|
|
||||||
def command( # noqa: C901
|
|
||||||
ctx,
|
|
||||||
laconic_config,
|
|
||||||
app,
|
|
||||||
auction_id,
|
|
||||||
deployer,
|
|
||||||
env_file,
|
|
||||||
config_ref,
|
|
||||||
make_payment,
|
|
||||||
use_payment,
|
|
||||||
dns,
|
|
||||||
dry_run,
|
|
||||||
):
|
|
||||||
if auction_id and deployer:
|
|
||||||
print("Cannot specify both --auction-id and --deployer", file=sys.stderr)
|
|
||||||
sys.exit(2)
|
|
||||||
|
|
||||||
if not auction_id and not deployer:
|
|
||||||
print("Must specify either --auction-id or --deployer", file=sys.stderr)
|
|
||||||
sys.exit(2)
|
|
||||||
|
|
||||||
if auction_id and (make_payment or use_payment):
|
|
||||||
print("Cannot specify --auction-id with --make-payment or --use-payment", file=sys.stderr)
|
|
||||||
sys.exit(2)
|
|
||||||
|
|
||||||
if env_file and config_ref:
|
|
||||||
fatal("Cannot use --env-file and --config-ref at the same time.")
|
|
||||||
|
|
||||||
laconic = LaconicRegistryClient(laconic_config)
|
|
||||||
|
|
||||||
app_record = laconic.get_record(app)
|
|
||||||
if not app_record:
|
|
||||||
fatal(f"Unable to locate app: {app}")
|
|
||||||
|
|
||||||
# Deployers to send requests to
|
|
||||||
deployer_records = []
|
|
||||||
|
|
||||||
auction = None
|
|
||||||
auction_winners = None
|
|
||||||
if auction_id:
|
|
||||||
# Fetch auction record for given auction
|
|
||||||
auction_records_by_id = laconic.app_deployment_auctions({"auction": auction_id})
|
|
||||||
if len(auction_records_by_id) == 0:
|
|
||||||
fatal(f"Unable to locate record for auction: {auction_id}")
|
|
||||||
|
|
||||||
# Cross check app against application in the auction record
|
|
||||||
auction_app = auction_records_by_id[0].attributes.application
|
|
||||||
if auction_app != app:
|
|
||||||
fatal(f"Requested application {app} does not match application from auction record {auction_app}")
|
|
||||||
|
|
||||||
# Fetch auction details
|
|
||||||
auction = laconic.get_auction(auction_id)
|
|
||||||
if not auction:
|
|
||||||
fatal(f"Unable to locate auction: {auction_id}")
|
|
||||||
|
|
||||||
# Check auction owner
|
|
||||||
if auction.ownerAddress != laconic.whoami().address:
|
|
||||||
fatal(f"Auction {auction_id} owner mismatch")
|
|
||||||
|
|
||||||
# Check auction kind
|
|
||||||
if auction.kind != AUCTION_KIND_PROVIDER:
|
|
||||||
fatal(f"Auction kind needs to be ${AUCTION_KIND_PROVIDER}, got {auction.kind}")
|
|
||||||
|
|
||||||
# Check auction status
|
|
||||||
if auction.status != AuctionStatus.COMPLETED:
|
|
||||||
fatal(f"Auction {auction_id} not completed yet, status {auction.status}")
|
|
||||||
|
|
||||||
# Check that winner list is not empty
|
|
||||||
if len(auction.winnerAddresses) == 0:
|
|
||||||
fatal(f"Auction {auction_id} has no winners")
|
|
||||||
|
|
||||||
auction_winners = auction.winnerAddresses
|
|
||||||
|
|
||||||
# Get deployer record for all the auction winners
|
|
||||||
for auction_winner in auction_winners:
|
|
||||||
# TODO: Match auction winner address with provider address?
|
|
||||||
deployer_records_by_owner = laconic.webapp_deployers({"paymentAddress": auction_winner})
|
|
||||||
if len(deployer_records_by_owner) == 0:
|
|
||||||
print(f"WARNING: Unable to locate deployer for auction winner {auction_winner}")
|
|
||||||
|
|
||||||
# Take first record with name set
|
|
||||||
target_deployer_record = deployer_records_by_owner[0]
|
|
||||||
for r in deployer_records_by_owner:
|
|
||||||
if len(r.names) > 0:
|
|
||||||
target_deployer_record = r
|
|
||||||
break
|
|
||||||
deployer_records.append(target_deployer_record)
|
|
||||||
else:
|
|
||||||
deployer_record = laconic.get_record(deployer)
|
|
||||||
if not deployer_record:
|
|
||||||
fatal(f"Unable to locate deployer: {deployer}")
|
|
||||||
|
|
||||||
deployer_records.append(deployer_record)
|
|
||||||
|
|
||||||
# Create and send request to each deployer
|
|
||||||
deployment_requests = []
|
|
||||||
for deployer_record in deployer_records:
|
|
||||||
# Upload config to deployers if env_file is passed
|
|
||||||
if env_file:
|
|
||||||
tempdir = tempfile.mkdtemp()
|
|
||||||
try:
|
|
||||||
gpg = gnupg.GPG(gnupghome=tempdir)
|
|
||||||
|
|
||||||
# Import the deployer's public key
|
|
||||||
result = gpg.import_keys(
|
|
||||||
base64.b64decode(deployer_record.attributes.publicKey)
|
|
||||||
)
|
|
||||||
if 1 != result.imported:
|
|
||||||
fatal("Failed to import deployer's public key.")
|
|
||||||
|
|
||||||
recip = gpg.list_keys()[0]["uids"][0]
|
|
||||||
|
|
||||||
# Wrap the config
|
|
||||||
config = {
|
|
||||||
# Include account (and payment?) details
|
|
||||||
"authorized": [laconic.whoami().address],
|
|
||||||
"config": {"env": dict(dotenv_values(env_file))},
|
|
||||||
}
|
|
||||||
serialized = yaml.dump(config)
|
|
||||||
|
|
||||||
# Encrypt
|
|
||||||
result = gpg.encrypt(serialized, recip, always_trust=True, armor=False)
|
|
||||||
if not result.ok:
|
|
||||||
fatal("Failed to encrypt config.")
|
|
||||||
|
|
||||||
# Upload it to the deployer's API
|
|
||||||
response = requests.post(
|
|
||||||
f"{deployer_record.attributes.apiUrl}/upload/config",
|
|
||||||
data=result.data,
|
|
||||||
headers={"Content-Type": "application/octet-stream"},
|
|
||||||
)
|
|
||||||
if not response.ok:
|
|
||||||
response.raise_for_status()
|
|
||||||
|
|
||||||
config_ref = response.json()["id"]
|
|
||||||
finally:
|
|
||||||
shutil.rmtree(tempdir, ignore_errors=True)
|
|
||||||
|
|
||||||
target_deployer = deployer
|
|
||||||
if (not deployer) and len(deployer_record.names):
|
|
||||||
target_deployer = deployer_record.names[0]
|
|
||||||
|
|
||||||
deployment_request = {
|
|
||||||
"record": {
|
|
||||||
"type": "ApplicationDeploymentRequest",
|
|
||||||
"application": app,
|
|
||||||
"version": "1.0.0",
|
|
||||||
"name": f"{app_record.attributes.name}@{app_record.attributes.version}",
|
|
||||||
"deployer": target_deployer,
|
|
||||||
"meta": {"when": str(datetime.utcnow())},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if auction_id:
|
|
||||||
deployment_request["record"]["auction"] = auction_id
|
|
||||||
|
|
||||||
if config_ref:
|
|
||||||
deployment_request["record"]["config"] = {"ref": config_ref}
|
|
||||||
|
|
||||||
if dns:
|
|
||||||
deployment_request["record"]["dns"] = dns.lower()
|
|
||||||
|
|
||||||
if make_payment:
|
|
||||||
amount = 0
|
|
||||||
if dry_run:
|
|
||||||
deployment_request["record"]["payment"] = "DRY_RUN"
|
|
||||||
elif "auto" == make_payment:
|
|
||||||
if "minimumPayment" in deployer_record.attributes:
|
|
||||||
amount = int(
|
|
||||||
deployer_record.attributes.minimumPayment.replace("alnt", "")
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
amount = make_payment
|
|
||||||
if amount:
|
|
||||||
receipt = laconic.send_tokens(
|
|
||||||
deployer_record.attributes.paymentAddress, amount
|
|
||||||
)
|
|
||||||
deployment_request["record"]["payment"] = receipt.tx.hash
|
|
||||||
print("Payment TX:", receipt.tx.hash)
|
|
||||||
elif use_payment:
|
|
||||||
deployment_request["record"]["payment"] = use_payment
|
|
||||||
|
|
||||||
deployment_requests.append(deployment_request)
|
|
||||||
|
|
||||||
# Send all requests
|
|
||||||
for deployment_request in deployment_requests:
|
|
||||||
if dry_run:
|
|
||||||
print(yaml.dump(deployment_request))
|
|
||||||
continue
|
|
||||||
|
|
||||||
laconic.publish(deployment_request)
|
|
@ -1,106 +0,0 @@
|
|||||||
# Copyright ©2023 Vulcanize
|
|
||||||
# This program is free software: you can redistribute it and/or modify
|
|
||||||
# it under the terms of the GNU Affero General Public License as published by
|
|
||||||
# the Free Software Foundation, either version 3 of the License, or
|
|
||||||
# (at your option) any later version.
|
|
||||||
|
|
||||||
# This program is distributed in the hope that it will be useful,
|
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
# GNU Affero General Public License for more details.
|
|
||||||
|
|
||||||
# You should have received a copy of the GNU Affero General Public License
|
|
||||||
# along with this program. If not, see <http:#www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
import sys
|
|
||||||
|
|
||||||
import click
|
|
||||||
import yaml
|
|
||||||
|
|
||||||
from stack_orchestrator.deploy.webapp.util import (LaconicRegistryClient)
|
|
||||||
|
|
||||||
|
|
||||||
def fatal(msg: str):
|
|
||||||
print(msg, file=sys.stderr)
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
|
|
||||||
@click.command()
|
|
||||||
@click.option(
|
|
||||||
"--laconic-config", help="Provide a config file for laconicd", required=True
|
|
||||||
)
|
|
||||||
@click.option(
|
|
||||||
"--deployer",
|
|
||||||
help="The LRN of the deployer to process this request.",
|
|
||||||
required=True
|
|
||||||
)
|
|
||||||
@click.option(
|
|
||||||
"--deployment",
|
|
||||||
help="Deployment record (ApplicationDeploymentRecord) id of the deployment to remove.",
|
|
||||||
required=True,
|
|
||||||
)
|
|
||||||
@click.option(
|
|
||||||
"--make-payment",
|
|
||||||
help="The payment to make (in alnt). The value should be a number or 'auto' to use the deployer's minimum required payment.",
|
|
||||||
)
|
|
||||||
@click.option(
|
|
||||||
"--use-payment", help="The TX id of an existing, unused payment", default=None
|
|
||||||
)
|
|
||||||
@click.option(
|
|
||||||
"--dry-run",
|
|
||||||
help="Don't publish anything, just report what would be done.",
|
|
||||||
is_flag=True,
|
|
||||||
)
|
|
||||||
@click.pass_context
|
|
||||||
def command(
|
|
||||||
ctx,
|
|
||||||
laconic_config,
|
|
||||||
deployer,
|
|
||||||
deployment,
|
|
||||||
make_payment,
|
|
||||||
use_payment,
|
|
||||||
dry_run,
|
|
||||||
):
|
|
||||||
if make_payment and use_payment:
|
|
||||||
fatal("Cannot use --make-payment and --use-payment at the same time.")
|
|
||||||
|
|
||||||
laconic = LaconicRegistryClient(laconic_config)
|
|
||||||
|
|
||||||
deployer_record = laconic.get_record(deployer)
|
|
||||||
if not deployer_record:
|
|
||||||
fatal(f"Unable to locate deployer: {deployer}")
|
|
||||||
|
|
||||||
undeployment_request = {
|
|
||||||
"record": {
|
|
||||||
"type": "ApplicationDeploymentRemovalRequest",
|
|
||||||
"version": "1.0.0",
|
|
||||||
"deployer": deployer,
|
|
||||||
"deployment": deployment,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if make_payment:
|
|
||||||
amount = 0
|
|
||||||
if dry_run:
|
|
||||||
undeployment_request["record"]["payment"] = "DRY_RUN"
|
|
||||||
elif "auto" == make_payment:
|
|
||||||
if "minimumPayment" in deployer_record.attributes:
|
|
||||||
amount = int(
|
|
||||||
deployer_record.attributes.minimumPayment.replace("alnt", "")
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
amount = make_payment
|
|
||||||
if amount:
|
|
||||||
receipt = laconic.send_tokens(
|
|
||||||
deployer_record.attributes.paymentAddress, amount
|
|
||||||
)
|
|
||||||
undeployment_request["record"]["payment"] = receipt.tx.hash
|
|
||||||
print("Payment TX:", receipt.tx.hash)
|
|
||||||
elif use_payment:
|
|
||||||
undeployment_request["record"]["payment"] = use_payment
|
|
||||||
|
|
||||||
if dry_run:
|
|
||||||
print(yaml.dump(undeployment_request))
|
|
||||||
return
|
|
||||||
|
|
||||||
laconic.publish(undeployment_request)
|
|
@ -38,7 +38,7 @@ def process_app_removal_request(
|
|||||||
deployment_parent_dir,
|
deployment_parent_dir,
|
||||||
delete_volumes,
|
delete_volumes,
|
||||||
delete_names,
|
delete_names,
|
||||||
webapp_deployer_record,
|
payment_address,
|
||||||
):
|
):
|
||||||
deployment_record = laconic.get_record(
|
deployment_record = laconic.get_record(
|
||||||
app_removal_request.attributes.deployment, require=True
|
app_removal_request.attributes.deployment, require=True
|
||||||
@ -84,7 +84,7 @@ def process_app_removal_request(
|
|||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"request": app_removal_request.id,
|
"request": app_removal_request.id,
|
||||||
"deployment": deployment_record.id,
|
"deployment": deployment_record.id,
|
||||||
"deployer": webapp_deployer_record.names[0],
|
"by": payment_address,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -168,19 +168,21 @@ def dump_known_requests(filename, requests):
|
|||||||
)
|
)
|
||||||
@click.option(
|
@click.option(
|
||||||
"--min-required-payment",
|
"--min-required-payment",
|
||||||
help="Requests must have a minimum payment to be processed (in alnt)",
|
help="Requests must have a minimum payment to be processed",
|
||||||
default=0,
|
default=0,
|
||||||
)
|
)
|
||||||
@click.option("--lrn", help="The LRN of this deployer.", required=True)
|
@click.option(
|
||||||
|
"--payment-address",
|
||||||
|
help="The address to which payments should be made. "
|
||||||
|
"Default is the current laconic account.",
|
||||||
|
default=None,
|
||||||
|
)
|
||||||
@click.option(
|
@click.option(
|
||||||
"--all-requests",
|
"--all-requests",
|
||||||
help="Handle requests addressed to anyone (by default only requests to"
|
help="Handle requests addressed to anyone (by default only requests to"
|
||||||
"my payment address are examined).",
|
"my payment address are examined).",
|
||||||
is_flag=True,
|
is_flag=True,
|
||||||
)
|
)
|
||||||
@click.option(
|
|
||||||
"--registry-lock-file", help="File path to use for registry mutex lock", default=None
|
|
||||||
)
|
|
||||||
@click.pass_context
|
@click.pass_context
|
||||||
def command( # noqa: C901
|
def command( # noqa: C901
|
||||||
ctx,
|
ctx,
|
||||||
@ -196,9 +198,8 @@ def command( # noqa: C901
|
|||||||
include_tags,
|
include_tags,
|
||||||
exclude_tags,
|
exclude_tags,
|
||||||
min_required_payment,
|
min_required_payment,
|
||||||
lrn,
|
payment_address,
|
||||||
all_requests,
|
all_requests,
|
||||||
registry_lock_file,
|
|
||||||
):
|
):
|
||||||
if request_id and discover:
|
if request_id and discover:
|
||||||
print("Cannot specify both --request-id and --discover", file=sys.stderr)
|
print("Cannot specify both --request-id and --discover", file=sys.stderr)
|
||||||
@ -216,17 +217,9 @@ def command( # noqa: C901
|
|||||||
include_tags = [tag.strip() for tag in include_tags.split(",") if tag]
|
include_tags = [tag.strip() for tag in include_tags.split(",") if tag]
|
||||||
exclude_tags = [tag.strip() for tag in exclude_tags.split(",") if tag]
|
exclude_tags = [tag.strip() for tag in exclude_tags.split(",") if tag]
|
||||||
|
|
||||||
laconic = LaconicRegistryClient(laconic_config, log_file=sys.stderr, mutex_lock_file=registry_lock_file)
|
laconic = LaconicRegistryClient(laconic_config, log_file=sys.stderr)
|
||||||
deployer_record = laconic.get_record(lrn, require=True)
|
if not payment_address:
|
||||||
payment_address = deployer_record.attributes.paymentAddress
|
payment_address = laconic.whoami().address
|
||||||
main_logger.log(f"Payment address: {payment_address}")
|
|
||||||
|
|
||||||
if min_required_payment and not payment_address:
|
|
||||||
print(
|
|
||||||
f"Minimum payment required, but no payment address listed for deployer: {lrn}.",
|
|
||||||
file=sys.stderr,
|
|
||||||
)
|
|
||||||
sys.exit(2)
|
|
||||||
|
|
||||||
# Find deployment removal requests.
|
# Find deployment removal requests.
|
||||||
# single request
|
# single request
|
||||||
@ -240,7 +233,7 @@ def command( # noqa: C901
|
|||||||
if all_requests:
|
if all_requests:
|
||||||
requests = laconic.app_deployment_removal_requests()
|
requests = laconic.app_deployment_removal_requests()
|
||||||
else:
|
else:
|
||||||
requests = laconic.app_deployment_removal_requests({"deployer": lrn})
|
requests = laconic.app_deployment_removal_requests({"to": payment_address})
|
||||||
|
|
||||||
if only_update_state:
|
if only_update_state:
|
||||||
if not dry_run:
|
if not dry_run:
|
||||||
@ -315,16 +308,11 @@ def command( # noqa: C901
|
|||||||
main_logger.log(f"ERROR examining {r.id}: {e}")
|
main_logger.log(f"ERROR examining {r.id}: {e}")
|
||||||
|
|
||||||
requests_to_execute = []
|
requests_to_execute = []
|
||||||
# TODO: Handle requests with auction
|
|
||||||
if min_required_payment:
|
if min_required_payment:
|
||||||
for r in requests_to_check_for_payment:
|
for r in requests_to_check_for_payment:
|
||||||
main_logger.log(f"{r.id}: Confirming payment...")
|
main_logger.log(f"{r.id}: Confirming payment...")
|
||||||
if confirm_payment(
|
if confirm_payment(
|
||||||
laconic,
|
laconic, r, payment_address, min_required_payment, main_logger
|
||||||
r,
|
|
||||||
payment_address,
|
|
||||||
min_required_payment,
|
|
||||||
main_logger,
|
|
||||||
):
|
):
|
||||||
main_logger.log(f"{r.id}: Payment confirmed.")
|
main_logger.log(f"{r.id}: Payment confirmed.")
|
||||||
requests_to_execute.append(r)
|
requests_to_execute.append(r)
|
||||||
@ -348,7 +336,7 @@ def command( # noqa: C901
|
|||||||
os.path.abspath(deployment_parent_dir),
|
os.path.abspath(deployment_parent_dir),
|
||||||
delete_volumes,
|
delete_volumes,
|
||||||
delete_names,
|
delete_names,
|
||||||
deployer_record,
|
payment_address,
|
||||||
)
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
main_logger.log(f"ERROR processing removal request {r.id}: {e}")
|
main_logger.log(f"ERROR processing removal request {r.id}: {e}")
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
# = str(min_required_payment) Copyright © 2023 Vulcanize
|
# Copyright © 2023 Vulcanize
|
||||||
|
|
||||||
# This program is free software: you can redistribute it and/or modify
|
# This program is free software: you can redistribute it and/or modify
|
||||||
# it under the terms of the GNU Affero General Public License as published by
|
# it under the terms of the GNU Affero General Public License as published by
|
||||||
@ -24,21 +24,6 @@ import tempfile
|
|||||||
import uuid
|
import uuid
|
||||||
import yaml
|
import yaml
|
||||||
|
|
||||||
from enum import Enum
|
|
||||||
|
|
||||||
from stack_orchestrator.deploy.webapp.registry_mutex import registry_mutex
|
|
||||||
|
|
||||||
|
|
||||||
class AuctionStatus(str, Enum):
|
|
||||||
COMMIT = "commit"
|
|
||||||
REVEAL = "reveal"
|
|
||||||
COMPLETED = "completed"
|
|
||||||
EXPIRED = "expired"
|
|
||||||
|
|
||||||
|
|
||||||
TOKEN_DENOM = "alnt"
|
|
||||||
AUCTION_KIND_PROVIDER = "provider"
|
|
||||||
|
|
||||||
|
|
||||||
class AttrDict(dict):
|
class AttrDict(dict):
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
@ -73,12 +58,6 @@ class TimedLogger:
|
|||||||
self.last = datetime.datetime.now()
|
self.last = datetime.datetime.now()
|
||||||
|
|
||||||
|
|
||||||
def load_known_requests(filename):
|
|
||||||
if filename and os.path.exists(filename):
|
|
||||||
return json.load(open(filename, "r"))
|
|
||||||
return {}
|
|
||||||
|
|
||||||
|
|
||||||
def logged_cmd(log_file, *vargs):
|
def logged_cmd(log_file, *vargs):
|
||||||
result = None
|
result = None
|
||||||
try:
|
try:
|
||||||
@ -113,8 +92,76 @@ def is_id(name_or_id: str):
|
|||||||
return not is_lrn(name_or_id)
|
return not is_lrn(name_or_id)
|
||||||
|
|
||||||
|
|
||||||
|
def confirm_payment(laconic, record, payment_address, min_amount, logger):
|
||||||
|
req_owner = laconic.get_owner(record)
|
||||||
|
if req_owner == payment_address:
|
||||||
|
# No need to confirm payment if the sender and recipient are the same account.
|
||||||
|
return True
|
||||||
|
|
||||||
|
if not record.attributes.payment:
|
||||||
|
logger.log(f"{record.id}: no payment tx info")
|
||||||
|
return False
|
||||||
|
|
||||||
|
tx = laconic.get_tx(record.attributes.payment)
|
||||||
|
if not tx:
|
||||||
|
logger.log(f"{record.id}: cannot locate payment tx")
|
||||||
|
return False
|
||||||
|
|
||||||
|
if tx.code != 0:
|
||||||
|
logger.log(
|
||||||
|
f"{record.id}: payment tx {tx.hash} was not successful - code: {tx.code}, log: {tx.log}"
|
||||||
|
)
|
||||||
|
return False
|
||||||
|
|
||||||
|
if tx.sender != req_owner:
|
||||||
|
logger.log(
|
||||||
|
f"{record.id}: payment sender {tx.sender} in tx {tx.hash} does not match deployment "
|
||||||
|
f"request owner {req_owner}"
|
||||||
|
)
|
||||||
|
return False
|
||||||
|
|
||||||
|
if tx.recipient != payment_address:
|
||||||
|
logger.log(
|
||||||
|
f"{record.id}: payment recipient {tx.recipient} in tx {tx.hash} does not match {payment_address}"
|
||||||
|
)
|
||||||
|
return False
|
||||||
|
|
||||||
|
pay_denom = "".join([i for i in tx.amount if not i.isdigit()])
|
||||||
|
if pay_denom != "alnt":
|
||||||
|
logger.log(
|
||||||
|
f"{record.id}: {pay_denom} in tx {tx.hash} is not an expected payment denomination"
|
||||||
|
)
|
||||||
|
return False
|
||||||
|
|
||||||
|
pay_amount = int("".join([i for i in tx.amount if i.isdigit()]))
|
||||||
|
if pay_amount < min_amount:
|
||||||
|
logger.log(
|
||||||
|
f"{record.id}: payment amount {tx.amount} is less than minimum {min_amount}"
|
||||||
|
)
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Check if the payment was already used on a
|
||||||
|
used = laconic.app_deployments(
|
||||||
|
{"by": payment_address, "payment": tx.hash}, all=True
|
||||||
|
)
|
||||||
|
if len(used):
|
||||||
|
logger.log(f"{record.id}: payment {tx.hash} already used on deployment {used}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
used = laconic.app_deployment_removals(
|
||||||
|
{"by": payment_address, "payment": tx.hash}, all=True
|
||||||
|
)
|
||||||
|
if len(used):
|
||||||
|
logger.log(
|
||||||
|
f"{record.id}: payment {tx.hash} already used on deployment removal {used}"
|
||||||
|
)
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
class LaconicRegistryClient:
|
class LaconicRegistryClient:
|
||||||
def __init__(self, config_file, log_file=None, mutex_lock_file=None):
|
def __init__(self, config_file, log_file=None):
|
||||||
self.config_file = config_file
|
self.config_file = config_file
|
||||||
self.log_file = log_file
|
self.log_file = log_file
|
||||||
self.cache = AttrDict(
|
self.cache = AttrDict(
|
||||||
@ -125,9 +172,6 @@ class LaconicRegistryClient:
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
self.mutex_lock_file = mutex_lock_file
|
|
||||||
self.mutex_lock_acquired = False
|
|
||||||
|
|
||||||
def whoami(self, refresh=False):
|
def whoami(self, refresh=False):
|
||||||
if not refresh and "whoami" in self.cache:
|
if not refresh and "whoami" in self.cache:
|
||||||
return self.cache["whoami"]
|
return self.cache["whoami"]
|
||||||
@ -326,34 +370,6 @@ class LaconicRegistryClient:
|
|||||||
if require:
|
if require:
|
||||||
raise Exception("Cannot locate tx:", hash)
|
raise Exception("Cannot locate tx:", hash)
|
||||||
|
|
||||||
def get_auction(self, auction_id, require=False):
|
|
||||||
args = [
|
|
||||||
"laconic",
|
|
||||||
"-c",
|
|
||||||
self.config_file,
|
|
||||||
"registry",
|
|
||||||
"auction",
|
|
||||||
"get",
|
|
||||||
"--id",
|
|
||||||
auction_id,
|
|
||||||
]
|
|
||||||
|
|
||||||
results = None
|
|
||||||
try:
|
|
||||||
results = [
|
|
||||||
AttrDict(r) for r in json.loads(logged_cmd(self.log_file, *args)) if r
|
|
||||||
]
|
|
||||||
except: # noqa: E722
|
|
||||||
pass
|
|
||||||
|
|
||||||
if results and len(results):
|
|
||||||
return results[0]
|
|
||||||
|
|
||||||
if require:
|
|
||||||
raise Exception("Cannot locate auction:", auction_id)
|
|
||||||
|
|
||||||
return None
|
|
||||||
|
|
||||||
def app_deployment_requests(self, criteria=None, all=True):
|
def app_deployment_requests(self, criteria=None, all=True):
|
||||||
if criteria is None:
|
if criteria is None:
|
||||||
criteria = {}
|
criteria = {}
|
||||||
@ -382,21 +398,6 @@ class LaconicRegistryClient:
|
|||||||
criteria["type"] = "ApplicationDeploymentRemovalRecord"
|
criteria["type"] = "ApplicationDeploymentRemovalRecord"
|
||||||
return self.list_records(criteria, all)
|
return self.list_records(criteria, all)
|
||||||
|
|
||||||
def webapp_deployers(self, criteria=None, all=True):
|
|
||||||
if criteria is None:
|
|
||||||
criteria = {}
|
|
||||||
criteria = criteria.copy()
|
|
||||||
criteria["type"] = "WebappDeployer"
|
|
||||||
return self.list_records(criteria, all)
|
|
||||||
|
|
||||||
def app_deployment_auctions(self, criteria=None, all=True):
|
|
||||||
if criteria is None:
|
|
||||||
criteria = {}
|
|
||||||
criteria = criteria.copy()
|
|
||||||
criteria["type"] = "ApplicationDeploymentAuction"
|
|
||||||
return self.list_records(criteria, all)
|
|
||||||
|
|
||||||
@registry_mutex()
|
|
||||||
def publish(self, record, names=None):
|
def publish(self, record, names=None):
|
||||||
if names is None:
|
if names is None:
|
||||||
names = []
|
names = []
|
||||||
@ -427,7 +428,6 @@ class LaconicRegistryClient:
|
|||||||
finally:
|
finally:
|
||||||
logged_cmd(self.log_file, "rm", "-rf", tmpdir)
|
logged_cmd(self.log_file, "rm", "-rf", tmpdir)
|
||||||
|
|
||||||
@registry_mutex()
|
|
||||||
def set_name(self, name, record_id):
|
def set_name(self, name, record_id):
|
||||||
logged_cmd(
|
logged_cmd(
|
||||||
self.log_file,
|
self.log_file,
|
||||||
@ -441,7 +441,6 @@ class LaconicRegistryClient:
|
|||||||
record_id,
|
record_id,
|
||||||
)
|
)
|
||||||
|
|
||||||
@registry_mutex()
|
|
||||||
def delete_name(self, name):
|
def delete_name(self, name):
|
||||||
logged_cmd(
|
logged_cmd(
|
||||||
self.log_file,
|
self.log_file,
|
||||||
@ -454,86 +453,6 @@ class LaconicRegistryClient:
|
|||||||
name,
|
name,
|
||||||
)
|
)
|
||||||
|
|
||||||
@registry_mutex()
|
|
||||||
def send_tokens(self, address, amount, type="alnt"):
|
|
||||||
args = [
|
|
||||||
"laconic",
|
|
||||||
"-c",
|
|
||||||
self.config_file,
|
|
||||||
"registry",
|
|
||||||
"tokens",
|
|
||||||
"send",
|
|
||||||
"--address",
|
|
||||||
address,
|
|
||||||
"--quantity",
|
|
||||||
str(amount),
|
|
||||||
"--type",
|
|
||||||
type,
|
|
||||||
]
|
|
||||||
|
|
||||||
return AttrDict(json.loads(logged_cmd(self.log_file, *args)))
|
|
||||||
|
|
||||||
@registry_mutex()
|
|
||||||
def create_deployment_auction(self, auction):
|
|
||||||
args = [
|
|
||||||
"laconic",
|
|
||||||
"-c",
|
|
||||||
self.config_file,
|
|
||||||
"registry",
|
|
||||||
"auction",
|
|
||||||
"create",
|
|
||||||
"--kind",
|
|
||||||
auction["kind"],
|
|
||||||
"--commits-duration",
|
|
||||||
str(auction["commits_duration"]),
|
|
||||||
"--reveals-duration",
|
|
||||||
str(auction["reveals_duration"]),
|
|
||||||
"--denom",
|
|
||||||
auction["denom"],
|
|
||||||
"--commit-fee",
|
|
||||||
str(auction["commit_fee"]),
|
|
||||||
"--reveal-fee",
|
|
||||||
str(auction["reveal_fee"]),
|
|
||||||
"--max-price",
|
|
||||||
str(auction["max_price"]),
|
|
||||||
"--num-providers",
|
|
||||||
str(auction["num_providers"])
|
|
||||||
]
|
|
||||||
|
|
||||||
return json.loads(logged_cmd(self.log_file, *args))["auctionId"]
|
|
||||||
|
|
||||||
@registry_mutex()
|
|
||||||
def commit_bid(self, auction_id, amount, type="alnt"):
|
|
||||||
args = [
|
|
||||||
"laconic",
|
|
||||||
"-c",
|
|
||||||
self.config_file,
|
|
||||||
"registry",
|
|
||||||
"auction",
|
|
||||||
"bid",
|
|
||||||
"commit",
|
|
||||||
auction_id,
|
|
||||||
str(amount),
|
|
||||||
type,
|
|
||||||
]
|
|
||||||
|
|
||||||
return json.loads(logged_cmd(self.log_file, *args))["reveal_file"]
|
|
||||||
|
|
||||||
@registry_mutex()
|
|
||||||
def reveal_bid(self, auction_id, reveal_file_path):
|
|
||||||
logged_cmd(
|
|
||||||
self.log_file,
|
|
||||||
"laconic",
|
|
||||||
"-c",
|
|
||||||
self.config_file,
|
|
||||||
"registry",
|
|
||||||
"auction",
|
|
||||||
"bid",
|
|
||||||
"reveal",
|
|
||||||
auction_id,
|
|
||||||
reveal_file_path,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def file_hash(filename):
|
def file_hash(filename):
|
||||||
return hashlib.sha1(open(filename).read().encode()).hexdigest()
|
return hashlib.sha1(open(filename).read().encode()).hexdigest()
|
||||||
@ -690,7 +609,7 @@ def publish_deployment(
|
|||||||
dns_lrn,
|
dns_lrn,
|
||||||
deployment_dir,
|
deployment_dir,
|
||||||
app_deployment_request=None,
|
app_deployment_request=None,
|
||||||
webapp_deployer_record=None,
|
payment_address=None,
|
||||||
logger=None,
|
logger=None,
|
||||||
):
|
):
|
||||||
if not deploy_record:
|
if not deploy_record:
|
||||||
@ -740,18 +659,15 @@ def publish_deployment(
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if app_deployment_request:
|
if app_deployment_request:
|
||||||
new_deployment_record["record"]["request"] = app_deployment_request.id
|
new_deployment_record["record"]["request"] = app_deployment_request.id
|
||||||
|
if app_deployment_request.attributes.payment:
|
||||||
|
new_deployment_record["record"][
|
||||||
|
"payment"
|
||||||
|
] = app_deployment_request.attributes.payment
|
||||||
|
|
||||||
# Set auction or payment id from request
|
if payment_address:
|
||||||
if app_deployment_request.attributes.auction:
|
new_deployment_record["record"]["by"] = payment_address
|
||||||
new_deployment_record["record"]["auction"] = app_deployment_request.attributes.auction
|
|
||||||
elif app_deployment_request.attributes.payment:
|
|
||||||
new_deployment_record["record"]["payment"] = app_deployment_request.attributes.payment
|
|
||||||
|
|
||||||
if webapp_deployer_record:
|
|
||||||
new_deployment_record["record"]["deployer"] = webapp_deployer_record.names[0]
|
|
||||||
|
|
||||||
if logger:
|
if logger:
|
||||||
logger.log("Publishing ApplicationDeploymentRecord.")
|
logger.log("Publishing ApplicationDeploymentRecord.")
|
||||||
@ -796,108 +712,3 @@ def skip_by_tag(r, include_tags, exclude_tags):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def confirm_payment(laconic: LaconicRegistryClient, record, payment_address, min_amount, logger):
|
|
||||||
req_owner = laconic.get_owner(record)
|
|
||||||
if req_owner == payment_address:
|
|
||||||
# No need to confirm payment if the sender and recipient are the same account.
|
|
||||||
return True
|
|
||||||
|
|
||||||
if not record.attributes.payment:
|
|
||||||
logger.log(f"{record.id}: no payment tx info")
|
|
||||||
return False
|
|
||||||
|
|
||||||
tx = laconic.get_tx(record.attributes.payment)
|
|
||||||
if not tx:
|
|
||||||
logger.log(f"{record.id}: cannot locate payment tx")
|
|
||||||
return False
|
|
||||||
|
|
||||||
if tx.code != 0:
|
|
||||||
logger.log(
|
|
||||||
f"{record.id}: payment tx {tx.hash} was not successful - code: {tx.code}, log: {tx.log}"
|
|
||||||
)
|
|
||||||
return False
|
|
||||||
|
|
||||||
if tx.sender != req_owner:
|
|
||||||
logger.log(
|
|
||||||
f"{record.id}: payment sender {tx.sender} in tx {tx.hash} does not match deployment "
|
|
||||||
f"request owner {req_owner}"
|
|
||||||
)
|
|
||||||
return False
|
|
||||||
|
|
||||||
if tx.recipient != payment_address:
|
|
||||||
logger.log(
|
|
||||||
f"{record.id}: payment recipient {tx.recipient} in tx {tx.hash} does not match {payment_address}"
|
|
||||||
)
|
|
||||||
return False
|
|
||||||
|
|
||||||
pay_denom = "".join([i for i in tx.amount if not i.isdigit()])
|
|
||||||
if pay_denom != "alnt":
|
|
||||||
logger.log(
|
|
||||||
f"{record.id}: {pay_denom} in tx {tx.hash} is not an expected payment denomination"
|
|
||||||
)
|
|
||||||
return False
|
|
||||||
|
|
||||||
pay_amount = int("".join([i for i in tx.amount if i.isdigit()]))
|
|
||||||
if pay_amount < min_amount:
|
|
||||||
logger.log(
|
|
||||||
f"{record.id}: payment amount {tx.amount} is less than minimum {min_amount}"
|
|
||||||
)
|
|
||||||
return False
|
|
||||||
|
|
||||||
# Check if the payment was already used on a deployment
|
|
||||||
used = laconic.app_deployments(
|
|
||||||
{"deployer": record.attributes.deployer, "payment": tx.hash}, all=True
|
|
||||||
)
|
|
||||||
if len(used):
|
|
||||||
# Fetch the app name from request record
|
|
||||||
used_request = laconic.get_record(used[0].attributes.request, require=True)
|
|
||||||
|
|
||||||
# Check that payment was used for deployment of same application
|
|
||||||
if record.attributes.application != used_request.attributes.application:
|
|
||||||
logger.log(f"{record.id}: payment {tx.hash} already used on a different application deployment {used}")
|
|
||||||
return False
|
|
||||||
|
|
||||||
used = laconic.app_deployment_removals(
|
|
||||||
{"deployer": record.attributes.deployer, "payment": tx.hash}, all=True
|
|
||||||
)
|
|
||||||
if len(used):
|
|
||||||
logger.log(
|
|
||||||
f"{record.id}: payment {tx.hash} already used on deployment removal {used}"
|
|
||||||
)
|
|
||||||
return False
|
|
||||||
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
def confirm_auction(laconic: LaconicRegistryClient, record, deployer_lrn, payment_address, logger):
|
|
||||||
auction_id = record.attributes.auction
|
|
||||||
auction = laconic.get_auction(auction_id)
|
|
||||||
|
|
||||||
# Fetch auction record for given auction
|
|
||||||
auction_records_by_id = laconic.app_deployment_auctions({"auction": auction_id})
|
|
||||||
if len(auction_records_by_id) == 0:
|
|
||||||
logger.log(f"{record.id}: unable to locate record for auction {auction_id}")
|
|
||||||
return False
|
|
||||||
|
|
||||||
# Cross check app against application in the auction record
|
|
||||||
requested_app = laconic.get_record(record.attributes.application, require=True)
|
|
||||||
auction_app = laconic.get_record(auction_records_by_id[0].attributes.application, require=True)
|
|
||||||
if requested_app.id != auction_app.id:
|
|
||||||
logger.log(
|
|
||||||
f"{record.id}: requested application {record.attributes.application} does not match application from "
|
|
||||||
f"auction record {auction_records_by_id[0].attributes.application}"
|
|
||||||
)
|
|
||||||
return False
|
|
||||||
|
|
||||||
if not auction:
|
|
||||||
logger.log(f"{record.id}: unable to locate auction {auction_id}")
|
|
||||||
return False
|
|
||||||
|
|
||||||
# Check if the deployer payment address is in auction winners list
|
|
||||||
if payment_address not in auction.winnerAddresses:
|
|
||||||
logger.log(f"{record.id}: deployer payment address not in auction winners.")
|
|
||||||
return False
|
|
||||||
|
|
||||||
return True
|
|
||||||
|
@ -24,12 +24,7 @@ from stack_orchestrator.build import build_webapp
|
|||||||
from stack_orchestrator.deploy.webapp import (run_webapp,
|
from stack_orchestrator.deploy.webapp import (run_webapp,
|
||||||
deploy_webapp,
|
deploy_webapp,
|
||||||
deploy_webapp_from_registry,
|
deploy_webapp_from_registry,
|
||||||
undeploy_webapp_from_registry,
|
undeploy_webapp_from_registry)
|
||||||
publish_webapp_deployer,
|
|
||||||
publish_deployment_auction,
|
|
||||||
handle_deployment_auction,
|
|
||||||
request_webapp_deployment,
|
|
||||||
request_webapp_undeployment)
|
|
||||||
from stack_orchestrator.deploy import deploy
|
from stack_orchestrator.deploy import deploy
|
||||||
from stack_orchestrator import version
|
from stack_orchestrator import version
|
||||||
from stack_orchestrator.deploy import deployment
|
from stack_orchestrator.deploy import deployment
|
||||||
@ -66,11 +61,6 @@ cli.add_command(run_webapp.command, "run-webapp")
|
|||||||
cli.add_command(deploy_webapp.command, "deploy-webapp")
|
cli.add_command(deploy_webapp.command, "deploy-webapp")
|
||||||
cli.add_command(deploy_webapp_from_registry.command, "deploy-webapp-from-registry")
|
cli.add_command(deploy_webapp_from_registry.command, "deploy-webapp-from-registry")
|
||||||
cli.add_command(undeploy_webapp_from_registry.command, "undeploy-webapp-from-registry")
|
cli.add_command(undeploy_webapp_from_registry.command, "undeploy-webapp-from-registry")
|
||||||
cli.add_command(publish_webapp_deployer.command, "publish-deployer-to-registry")
|
|
||||||
cli.add_command(publish_deployment_auction.command, "publish-deployment-auction")
|
|
||||||
cli.add_command(handle_deployment_auction.command, "handle-deployment-auction")
|
|
||||||
cli.add_command(request_webapp_deployment.command, "request-webapp-deployment")
|
|
||||||
cli.add_command(request_webapp_undeployment.command, "request-webapp-undeployment")
|
|
||||||
cli.add_command(deploy.command, "deploy") # deploy is an alias for deploy-system
|
cli.add_command(deploy.command, "deploy") # deploy is an alias for deploy-system
|
||||||
cli.add_command(deploy.command, "deploy-system")
|
cli.add_command(deploy.command, "deploy-system")
|
||||||
cli.add_command(deployment.command, "deployment")
|
cli.add_command(deployment.command, "deployment")
|
||||||
|
Loading…
Reference in New Issue
Block a user