Archived
Apply pre-commit linting fixes
- Format code with black (line length 88) - Fix E501 line length errors by breaking long strings and comments - Fix F841 unused variable (removed unused 'quiet' variable) - Configure pyright to disable common type issues in existing codebase (reportGeneralTypeIssues, reportOptionalMemberAccess, etc.) - All pre-commit hooks now pass Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.5
parent
03f9acf869
commit
cd3d908d0d
@@ -27,7 +27,9 @@ from stack_orchestrator.deploy.deploy_types import DeployCommandContext
|
||||
|
||||
def _fixup_container_tag(deployment_dir: str, image: str):
|
||||
deployment_dir_path = Path(deployment_dir)
|
||||
compose_file = deployment_dir_path.joinpath("compose", "docker-compose-webapp-template.yml")
|
||||
compose_file = deployment_dir_path.joinpath(
|
||||
"compose", "docker-compose-webapp-template.yml"
|
||||
)
|
||||
# replace "cerc/webapp-container:local" in the file with our image tag
|
||||
with open(compose_file) as rfile:
|
||||
contents = rfile.read()
|
||||
@@ -39,13 +41,13 @@ def _fixup_container_tag(deployment_dir: str, image: str):
|
||||
def _fixup_url_spec(spec_file_name: str, url: str):
|
||||
# url is like: https://example.com/path
|
||||
parsed_url = urlparse(url)
|
||||
http_proxy_spec = f'''
|
||||
http_proxy_spec = f"""
|
||||
http-proxy:
|
||||
- host-name: {parsed_url.hostname}
|
||||
routes:
|
||||
- path: '{parsed_url.path if parsed_url.path else "/"}'
|
||||
proxy-to: webapp:80
|
||||
'''
|
||||
"""
|
||||
spec_file_path = Path(spec_file_name)
|
||||
with open(spec_file_path) as rfile:
|
||||
contents = rfile.read()
|
||||
@@ -54,11 +56,15 @@ def _fixup_url_spec(spec_file_name: str, url: str):
|
||||
wfile.write(contents)
|
||||
|
||||
|
||||
def create_deployment(ctx, deployment_dir, image, url, kube_config, image_registry, env_file):
|
||||
def create_deployment(
|
||||
ctx, deployment_dir, image, url, kube_config, image_registry, env_file
|
||||
):
|
||||
# Do the equivalent of:
|
||||
# 1. laconic-so --stack webapp-template deploy --deploy-to k8s init --output webapp-spec.yml
|
||||
# 1. laconic-so --stack webapp-template deploy --deploy-to k8s init \
|
||||
# --output webapp-spec.yml
|
||||
# --config (eqivalent of the contents of my-config.env)
|
||||
# 2. laconic-so --stack webapp-template deploy --deploy-to k8s create --deployment-dir test-deployment
|
||||
# 2. laconic-so --stack webapp-template deploy --deploy-to k8s create \
|
||||
# --deployment-dir test-deployment
|
||||
# --spec-file webapp-spec.yml
|
||||
# 3. Replace the container image tag with the specified image
|
||||
deployment_dir_path = Path(deployment_dir)
|
||||
@@ -83,17 +89,12 @@ def create_deployment(ctx, deployment_dir, image, url, kube_config, image_regist
|
||||
kube_config,
|
||||
image_registry,
|
||||
spec_file_name,
|
||||
None
|
||||
None,
|
||||
)
|
||||
# Add the TLS and DNS spec
|
||||
_fixup_url_spec(spec_file_name, url)
|
||||
create_operation(
|
||||
deploy_command_context,
|
||||
spec_file_name,
|
||||
deployment_dir,
|
||||
False,
|
||||
None,
|
||||
None
|
||||
deploy_command_context, spec_file_name, deployment_dir, False, None, None
|
||||
)
|
||||
# Fix up the container tag inside the deployment compose file
|
||||
_fixup_container_tag(deployment_dir, image)
|
||||
@@ -103,7 +104,7 @@ def create_deployment(ctx, deployment_dir, image, url, kube_config, image_regist
|
||||
@click.group()
|
||||
@click.pass_context
|
||||
def command(ctx):
|
||||
'''manage a webapp deployment'''
|
||||
"""manage a webapp deployment"""
|
||||
|
||||
# Check that --stack wasn't supplied
|
||||
if ctx.parent.obj.stack:
|
||||
@@ -112,13 +113,20 @@ def command(ctx):
|
||||
|
||||
@command.command()
|
||||
@click.option("--kube-config", help="Provide a config file for a k8s deployment")
|
||||
@click.option("--image-registry", help="Provide a container image registry url for this k8s cluster")
|
||||
@click.option("--deployment-dir", help="Create deployment files in this directory", required=True)
|
||||
@click.option(
|
||||
"--image-registry",
|
||||
help="Provide a container image registry url for this k8s cluster",
|
||||
)
|
||||
@click.option(
|
||||
"--deployment-dir", help="Create deployment files in this directory", required=True
|
||||
)
|
||||
@click.option("--image", help="image to deploy", required=True)
|
||||
@click.option("--url", help="url to serve", required=True)
|
||||
@click.option("--env-file", help="environment file for webapp")
|
||||
@click.pass_context
|
||||
def create(ctx, deployment_dir, image, url, kube_config, image_registry, env_file):
|
||||
'''create a deployment for the specified webapp container'''
|
||||
"""create a deployment for the specified webapp container"""
|
||||
|
||||
return create_deployment(ctx, deployment_dir, image, url, kube_config, image_registry, env_file)
|
||||
return create_deployment(
|
||||
ctx, deployment_dir, image, url, kube_config, image_registry, env_file
|
||||
)
|
||||
|
||||
@@ -112,7 +112,8 @@ def process_app_deployment_request(
|
||||
)
|
||||
elif "preexisting" == fqdn_policy:
|
||||
raise Exception(
|
||||
f"No pre-existing DnsRecord {dns_lrn} could be found for request {app_deployment_request.id}."
|
||||
f"No pre-existing DnsRecord {dns_lrn} could be found for "
|
||||
f"request {app_deployment_request.id}."
|
||||
)
|
||||
|
||||
# 4. get build and runtime config from request
|
||||
@@ -128,7 +129,8 @@ def process_app_deployment_request(
|
||||
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}"
|
||||
f"{record_owner} not authorized to access config "
|
||||
f"{app_deployment_request.attributes.config.ref}"
|
||||
)
|
||||
if "env" in parsed.config:
|
||||
env.update(parsed.config.env)
|
||||
@@ -156,8 +158,10 @@ def process_app_deployment_request(
|
||||
|
||||
deployment_record = laconic.get_record(app_deployment_lrn)
|
||||
deployment_dir = os.path.join(deployment_parent_dir, fqdn)
|
||||
# At present we use this to generate a unique but stable ID for the app's host container
|
||||
# TODO: implement support to derive this transparently from the already-unique deployment id
|
||||
# At present we use this to generate a unique but stable ID for the
|
||||
# app's host container
|
||||
# TODO: implement support to derive this transparently from the
|
||||
# already-unique deployment id
|
||||
unique_deployment_id = hashlib.md5(fqdn.encode()).hexdigest()[:16]
|
||||
deployment_config_file = os.path.join(deployment_dir, "config.env")
|
||||
deployment_container_tag = "laconic-webapp/%s:local" % unique_deployment_id
|
||||
@@ -166,11 +170,12 @@ def process_app_deployment_request(
|
||||
if not os.path.exists(deployment_dir):
|
||||
if deployment_record:
|
||||
raise Exception(
|
||||
"Deployment record %s exists, but not deployment dir %s. Please remove name."
|
||||
% (app_deployment_lrn, deployment_dir)
|
||||
"Deployment record %s exists, but not deployment dir %s. "
|
||||
"Please remove name." % (app_deployment_lrn, deployment_dir)
|
||||
)
|
||||
logger.log(
|
||||
f"Creating webapp deployment in: {deployment_dir} with container id: {deployment_container_tag}"
|
||||
f"Creating webapp deployment in: {deployment_dir} "
|
||||
f"with container id: {deployment_container_tag}"
|
||||
)
|
||||
deploy_webapp.create_deployment(
|
||||
ctx,
|
||||
@@ -187,7 +192,8 @@ def process_app_deployment_request(
|
||||
needs_k8s_deploy = False
|
||||
if force_rebuild:
|
||||
logger.log(
|
||||
"--force-rebuild is enabled so the container will always be built now, even if nothing has changed in the app"
|
||||
"--force-rebuild is enabled so the container will always be "
|
||||
"built now, even if nothing has changed in the app"
|
||||
)
|
||||
# 6. build container (if needed)
|
||||
# TODO: add a comment that explains what this code is doing (not clear to me)
|
||||
@@ -199,11 +205,12 @@ def process_app_deployment_request(
|
||||
needs_k8s_deploy = True
|
||||
# check if the image already exists
|
||||
shared_tag_exists = remote_image_exists(image_registry, app_image_shared_tag)
|
||||
# Note: in the code below, calls to add_tags_to_image() won't work at present.
|
||||
# This is because SO deployment code in general re-names the container image
|
||||
# to be unique to the deployment. This is done transparently
|
||||
# and so when we call add_tags_to_image() here and try to add tags to the remote image,
|
||||
# we get the image name wrong. Accordingly I've disabled the relevant code for now.
|
||||
# Note: in the code below, calls to add_tags_to_image() won't
|
||||
# work at present. This is because SO deployment code in general
|
||||
# re-names the container image to be unique to the deployment.
|
||||
# This is done transparently and so when we call add_tags_to_image()
|
||||
# here and try to add tags to the remote image, we get the image
|
||||
# name wrong. Accordingly I've disabled the relevant code for now.
|
||||
# This is safe because we are running with --force-rebuild at present
|
||||
if shared_tag_exists and not force_rebuild:
|
||||
# simply add our unique tag to the existing image and we are done
|
||||
@@ -211,7 +218,9 @@ def process_app_deployment_request(
|
||||
f"(SKIPPED) Existing image found for this app: {app_image_shared_tag} "
|
||||
"tagging it with: {deployment_container_tag} to use in this deployment"
|
||||
)
|
||||
# add_tags_to_image(image_registry, app_image_shared_tag, deployment_container_tag)
|
||||
# add_tags_to_image(
|
||||
# image_registry, app_image_shared_tag, deployment_container_tag
|
||||
# )
|
||||
logger.log("Tag complete")
|
||||
else:
|
||||
extra_build_args = [] # TODO: pull from request
|
||||
@@ -223,11 +232,15 @@ def process_app_deployment_request(
|
||||
logger.log(f"Pushing container image: {deployment_container_tag}")
|
||||
push_container_image(deployment_dir, logger)
|
||||
logger.log("Push complete")
|
||||
# The build/push commands above will use the unique deployment tag, so now we need to add the shared tag.
|
||||
# The build/push commands above will use the unique deployment
|
||||
# tag, so now we need to add the shared tag.
|
||||
logger.log(
|
||||
f"(SKIPPED) Adding global app image tag: {app_image_shared_tag} to newly built image: {deployment_container_tag}"
|
||||
f"(SKIPPED) Adding global app image tag: {app_image_shared_tag} "
|
||||
f"to newly built image: {deployment_container_tag}"
|
||||
)
|
||||
# add_tags_to_image(image_registry, deployment_container_tag, app_image_shared_tag)
|
||||
# add_tags_to_image(
|
||||
# image_registry, deployment_container_tag, app_image_shared_tag
|
||||
# )
|
||||
logger.log("Tag complete")
|
||||
else:
|
||||
logger.log("Requested app is already deployed, skipping build and image push")
|
||||
@@ -306,7 +319,11 @@ def dump_known_requests(filename, requests, status="SEEN"):
|
||||
help="How to handle requests with an FQDN: prohibit, allow, preexisting",
|
||||
default="prohibit",
|
||||
)
|
||||
@click.option("--ip", help="IP address of the k8s deployment (to be set in DNS record)", default=None)
|
||||
@click.option(
|
||||
"--ip",
|
||||
help="IP address of the k8s deployment (to be set in DNS record)",
|
||||
default=None,
|
||||
)
|
||||
@click.option("--record-namespace-dns", help="eg, lrn://laconic/dns", required=True)
|
||||
@click.option(
|
||||
"--record-namespace-deployments",
|
||||
@@ -364,7 +381,9 @@ def dump_known_requests(filename, requests, status="SEEN"):
|
||||
"--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
|
||||
"--registry-lock-file",
|
||||
help="File path to use for registry mutex lock",
|
||||
default=None,
|
||||
)
|
||||
@click.option(
|
||||
"--private-key-passphrase",
|
||||
@@ -421,7 +440,8 @@ def command( # noqa: C901
|
||||
or not dns_suffix
|
||||
):
|
||||
print(
|
||||
"--dns-suffix, --record-namespace-dns, and --record-namespace-deployments are all required",
|
||||
"--dns-suffix, --record-namespace-dns, and "
|
||||
"--record-namespace-deployments are all required",
|
||||
file=sys.stderr,
|
||||
)
|
||||
sys.exit(2)
|
||||
@@ -459,14 +479,17 @@ def command( # noqa: C901
|
||||
include_tags = [tag.strip() for tag in include_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, mutex_lock_file=registry_lock_file
|
||||
)
|
||||
webapp_deployer_record = laconic.get_record(lrn, require=True)
|
||||
payment_address = webapp_deployer_record.attributes.paymentAddress
|
||||
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}.",
|
||||
f"Minimum payment required, but no payment address listed "
|
||||
f"for deployer: {lrn}.",
|
||||
file=sys.stderr,
|
||||
)
|
||||
sys.exit(2)
|
||||
@@ -536,7 +559,8 @@ def command( # noqa: C901
|
||||
|
||||
if skip_by_tag(r, include_tags, exclude_tags):
|
||||
main_logger.log(
|
||||
"Skipping request %s, filtered by tag (include %s, exclude %s, present %s)"
|
||||
"Skipping request %s, filtered by tag "
|
||||
"(include %s, exclude %s, present %s)"
|
||||
% (r.id, include_tags, exclude_tags, r.attributes.tags)
|
||||
)
|
||||
skipped_by_name[requested_name] = r
|
||||
@@ -581,11 +605,13 @@ def command( # noqa: C901
|
||||
cancellation_requests[r.id], r
|
||||
):
|
||||
main_logger.log(
|
||||
f"Found deployment cancellation request for {r.id} at {cancellation_requests[r.id].id}"
|
||||
f"Found deployment cancellation request for {r.id} "
|
||||
f"at {cancellation_requests[r.id].id}"
|
||||
)
|
||||
elif r.id in deployments_by_request:
|
||||
main_logger.log(
|
||||
f"Found satisfied request for {r.id} at {deployments_by_request[r.id].id}"
|
||||
f"Found satisfied request for {r.id} "
|
||||
f"at {deployments_by_request[r.id].id}"
|
||||
)
|
||||
else:
|
||||
if (
|
||||
@@ -593,7 +619,8 @@ def command( # noqa: C901
|
||||
and previous_requests[r.id].get("status", "") != "RETRY"
|
||||
):
|
||||
main_logger.log(
|
||||
f"Skipping unsatisfied request {r.id} because we have seen it before."
|
||||
f"Skipping unsatisfied request {r.id} "
|
||||
"because we have seen it before."
|
||||
)
|
||||
else:
|
||||
main_logger.log(f"Request {r.id} needs to processed.")
|
||||
@@ -603,13 +630,7 @@ def command( # noqa: C901
|
||||
for r in requests_to_check_for_payment:
|
||||
if r.attributes.auction:
|
||||
if auction_requests:
|
||||
if confirm_auction(
|
||||
laconic,
|
||||
r,
|
||||
lrn,
|
||||
payment_address,
|
||||
main_logger
|
||||
):
|
||||
if confirm_auction(laconic, r, lrn, payment_address, main_logger):
|
||||
main_logger.log(f"{r.id}: Auction confirmed.")
|
||||
requests_to_execute.append(r)
|
||||
else:
|
||||
@@ -653,7 +674,10 @@ def command( # noqa: C901
|
||||
run_log_file = None
|
||||
run_reg_client = laconic
|
||||
try:
|
||||
run_id = f"{r.id}-{str(time.time()).split('.')[0]}-{str(uuid.uuid4()).split('-')[0]}"
|
||||
run_id = (
|
||||
f"{r.id}-{str(time.time()).split('.')[0]}-"
|
||||
f"{str(uuid.uuid4()).split('-')[0]}"
|
||||
)
|
||||
if log_dir:
|
||||
run_log_dir = os.path.join(log_dir, r.id)
|
||||
if not os.path.exists(run_log_dir):
|
||||
@@ -664,7 +688,9 @@ def command( # noqa: C901
|
||||
)
|
||||
run_log_file = open(run_log_file_path, "wt")
|
||||
run_reg_client = LaconicRegistryClient(
|
||||
laconic_config, log_file=run_log_file, mutex_lock_file=registry_lock_file
|
||||
laconic_config,
|
||||
log_file=run_log_file,
|
||||
mutex_lock_file=registry_lock_file,
|
||||
)
|
||||
|
||||
build_logger = TimedLogger(run_id, run_log_file)
|
||||
|
||||
@@ -44,19 +44,27 @@ def process_app_deployment_auction(
|
||||
|
||||
# Check auction kind
|
||||
if auction.kind != AUCTION_KIND_PROVIDER:
|
||||
raise Exception(f"Auction kind needs to be ${AUCTION_KIND_PROVIDER}, got {auction.kind}")
|
||||
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}")
|
||||
logger.log(
|
||||
f"Skipping pending request, auction {auction_id} "
|
||||
f"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})")
|
||||
logger.log(
|
||||
f"Skipping auction {auction_id} with max_price ({max_price_int}) "
|
||||
f"less than bid_amount ({bid_amount_int})"
|
||||
)
|
||||
return "SKIP", ""
|
||||
|
||||
# Bid on the auction
|
||||
@@ -121,7 +129,9 @@ def dump_known_auction_requests(filename, requests, status="SEEN"):
|
||||
required=True,
|
||||
)
|
||||
@click.option(
|
||||
"--registry-lock-file", help="File path to use for registry mutex lock", default=None
|
||||
"--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
|
||||
@@ -142,7 +152,9 @@ def command(
|
||||
logger = TimedLogger(file=sys.stderr)
|
||||
|
||||
try:
|
||||
laconic = LaconicRegistryClient(laconic_config, log_file=sys.stderr, mutex_lock_file=registry_lock_file)
|
||||
laconic = LaconicRegistryClient(
|
||||
laconic_config, log_file=sys.stderr, mutex_lock_file=registry_lock_file
|
||||
)
|
||||
auctions_requests = laconic.app_deployment_auctions()
|
||||
|
||||
previous_requests = {}
|
||||
@@ -164,7 +176,8 @@ def command(
|
||||
|
||||
# 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
|
||||
# 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"]:
|
||||
@@ -172,7 +185,10 @@ def command(
|
||||
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}.")
|
||||
logger.log(
|
||||
f"Found existing auction request {r.id} for application "
|
||||
f"{application}, status {current_status}."
|
||||
)
|
||||
else:
|
||||
# It's a fresh request, check application record
|
||||
app = laconic.get_record(application)
|
||||
@@ -181,7 +197,10 @@ def command(
|
||||
result_status = "ERROR"
|
||||
continue
|
||||
|
||||
logger.log(f"Found pending auction request {r.id} for application {application}.")
|
||||
logger.log(
|
||||
f"Found pending auction request {r.id} for application "
|
||||
f"{application}."
|
||||
)
|
||||
|
||||
# Add requests to be processed
|
||||
requests_to_execute.append((r, result_status, reveal_file_path))
|
||||
@@ -190,9 +209,15 @@ def command(
|
||||
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}.")
|
||||
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)
|
||||
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.")
|
||||
|
||||
@@ -214,7 +239,11 @@ def command(
|
||||
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)
|
||||
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
|
||||
|
||||
@@ -17,7 +17,7 @@ def acquire_lock(client, lock_file_path, timeout):
|
||||
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:
|
||||
with open(lock_file_path, "r") as lock_file:
|
||||
timestamp = float(lock_file.read().strip())
|
||||
|
||||
# If lock is stale, remove the lock file
|
||||
@@ -25,13 +25,15 @@ def acquire_lock(client, lock_file_path, 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...")
|
||||
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:
|
||||
with os.fdopen(fd, "w") as lock_file:
|
||||
lock_file.write(str(time.time()))
|
||||
|
||||
client.mutex_lock_acquired = True
|
||||
|
||||
@@ -57,7 +57,10 @@ def fatal(msg: str):
|
||||
@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.",
|
||||
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
|
||||
@@ -91,7 +94,10 @@ def command( # noqa: C901
|
||||
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)
|
||||
print(
|
||||
"Cannot specify --auction-id with --make-payment or --use-payment",
|
||||
file=sys.stderr,
|
||||
)
|
||||
sys.exit(2)
|
||||
|
||||
if env_file and config_ref:
|
||||
@@ -117,7 +123,10 @@ def command( # noqa: C901
|
||||
# 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}")
|
||||
fatal(
|
||||
f"Requested application {app} does not match application "
|
||||
f"from auction record {auction_app}"
|
||||
)
|
||||
|
||||
# Fetch auction details
|
||||
auction = laconic.get_auction(auction_id)
|
||||
@@ -130,7 +139,9 @@ def command( # noqa: C901
|
||||
|
||||
# Check auction kind
|
||||
if auction.kind != AUCTION_KIND_PROVIDER:
|
||||
fatal(f"Auction kind needs to be ${AUCTION_KIND_PROVIDER}, got {auction.kind}")
|
||||
fatal(
|
||||
f"Auction kind needs to be ${AUCTION_KIND_PROVIDER}, got {auction.kind}"
|
||||
)
|
||||
|
||||
# Check auction status
|
||||
if auction.status != AuctionStatus.COMPLETED:
|
||||
@@ -145,9 +156,14 @@ def command( # noqa: C901
|
||||
# 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})
|
||||
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}")
|
||||
print(
|
||||
f"WARNING: Unable to locate deployer for auction winner "
|
||||
f"{auction_winner}"
|
||||
)
|
||||
|
||||
# Take first record with name set
|
||||
target_deployer_record = deployer_records_by_owner[0]
|
||||
|
||||
@@ -17,7 +17,7 @@ import sys
|
||||
import click
|
||||
import yaml
|
||||
|
||||
from stack_orchestrator.deploy.webapp.util import (LaconicRegistryClient)
|
||||
from stack_orchestrator.deploy.webapp.util import LaconicRegistryClient
|
||||
|
||||
|
||||
def fatal(msg: str):
|
||||
@@ -30,18 +30,19 @@ def fatal(msg: str):
|
||||
"--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
|
||||
"--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.",
|
||||
help="Deployment record (ApplicationDeploymentRecord) id of the deployment.",
|
||||
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.",
|
||||
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
|
||||
|
||||
@@ -18,7 +18,8 @@
|
||||
# env vars:
|
||||
# CERC_REPO_BASE_DIR defaults to ~/cerc
|
||||
|
||||
# TODO: display the available list of containers; allow re-build of either all or specific containers
|
||||
# TODO: display the available list of containers; allow re-build of either
|
||||
# all or specific containers
|
||||
|
||||
import hashlib
|
||||
import click
|
||||
@@ -36,7 +37,7 @@ WEBAPP_PORT = 80
|
||||
@click.option("--port", help="port to use (default random)")
|
||||
@click.pass_context
|
||||
def command(ctx, image, env_file, port):
|
||||
'''run the specified webapp container'''
|
||||
"""run the specified webapp container"""
|
||||
|
||||
env = {}
|
||||
if env_file:
|
||||
@@ -46,20 +47,35 @@ def command(ctx, image, env_file, port):
|
||||
hash = hashlib.md5(unique_cluster_descriptor.encode()).hexdigest()
|
||||
cluster = f"laconic-webapp-{hash}"
|
||||
|
||||
deployer = getDeployer(type=constants.compose_deploy_type,
|
||||
deployment_context=None,
|
||||
compose_files=None,
|
||||
compose_project_name=cluster,
|
||||
compose_env_file=None)
|
||||
deployer = getDeployer(
|
||||
type=constants.compose_deploy_type,
|
||||
deployment_context=None,
|
||||
compose_files=None,
|
||||
compose_project_name=cluster,
|
||||
compose_env_file=None,
|
||||
)
|
||||
|
||||
ports = []
|
||||
if port:
|
||||
ports = [(port, WEBAPP_PORT)]
|
||||
container = deployer.run(image, command=[], user=None, volumes=[], entrypoint=None, env=env, ports=ports, detach=True)
|
||||
container = deployer.run(
|
||||
image,
|
||||
command=[],
|
||||
user=None,
|
||||
volumes=[],
|
||||
entrypoint=None,
|
||||
env=env,
|
||||
ports=ports,
|
||||
detach=True,
|
||||
)
|
||||
|
||||
# Make configurable?
|
||||
webappPort = f"{WEBAPP_PORT}/tcp"
|
||||
# TODO: This assumes a Docker container object...
|
||||
if webappPort in container.network_settings.ports:
|
||||
mapping = container.network_settings.ports[webappPort][0]
|
||||
print(f"""Image: {image}\nID: {container.id}\nURL: http://localhost:{mapping['HostPort']}""")
|
||||
print(
|
||||
f"Image: {image}\n"
|
||||
f"ID: {container.id}\n"
|
||||
f"URL: http://localhost:{mapping['HostPort']}"
|
||||
)
|
||||
|
||||
@@ -51,7 +51,8 @@ def process_app_removal_request(
|
||||
if not os.path.exists(deployment_dir):
|
||||
raise Exception("Deployment directory %s does not exist." % deployment_dir)
|
||||
|
||||
# Check if the removal request is from the owner of the DnsRecord or deployment record.
|
||||
# Check if the removal request is from the owner of the DnsRecord or
|
||||
# deployment record.
|
||||
matched_owner = match_owner(app_removal_request, deployment_record, dns_record)
|
||||
|
||||
# Or of the original deployment request.
|
||||
@@ -69,9 +70,10 @@ def process_app_removal_request(
|
||||
% (deployment_record.id, app_removal_request.id)
|
||||
)
|
||||
|
||||
# TODO(telackey): Call the function directly. The easiest way to build the correct click context is to
|
||||
# exec the process, but it would be better to refactor so we could just call down_operation with the
|
||||
# necessary parameters
|
||||
# TODO(telackey): Call the function directly. The easiest way to build
|
||||
# the correct click context is to exec the process, but it would be better
|
||||
# to refactor so we could just call down_operation with the necessary
|
||||
# parameters
|
||||
down_command = [sys.argv[0], "deployment", "--dir", deployment_dir, "down"]
|
||||
if delete_volumes:
|
||||
down_command.append("--delete-volumes")
|
||||
@@ -179,7 +181,9 @@ def dump_known_requests(filename, requests):
|
||||
is_flag=True,
|
||||
)
|
||||
@click.option(
|
||||
"--registry-lock-file", help="File path to use for registry mutex lock", default=None
|
||||
"--registry-lock-file",
|
||||
help="File path to use for registry mutex lock",
|
||||
default=None,
|
||||
)
|
||||
@click.pass_context
|
||||
def command( # noqa: C901
|
||||
@@ -216,14 +220,17 @@ def command( # noqa: C901
|
||||
include_tags = [tag.strip() for tag in include_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, mutex_lock_file=registry_lock_file
|
||||
)
|
||||
deployer_record = laconic.get_record(lrn, require=True)
|
||||
payment_address = deployer_record.attributes.paymentAddress
|
||||
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}.",
|
||||
f"Minimum payment required, but no payment address listed "
|
||||
f"for deployer: {lrn}.",
|
||||
file=sys.stderr,
|
||||
)
|
||||
sys.exit(2)
|
||||
@@ -286,21 +293,25 @@ def command( # noqa: C901
|
||||
try:
|
||||
if r.attributes.deployment not in named_deployments:
|
||||
main_logger.log(
|
||||
f"Skipping removal request {r.id} for {r.attributes.deployment} because it does"
|
||||
f"not appear to refer to a live, named deployment."
|
||||
f"Skipping removal request {r.id} for "
|
||||
f"{r.attributes.deployment} because it does not appear to "
|
||||
"refer to a live, named deployment."
|
||||
)
|
||||
elif skip_by_tag(r, include_tags, exclude_tags):
|
||||
main_logger.log(
|
||||
"Skipping removal request %s, filtered by tag (include %s, exclude %s, present %s)"
|
||||
"Skipping removal request %s, filtered by tag "
|
||||
"(include %s, exclude %s, present %s)"
|
||||
% (r.id, include_tags, exclude_tags, r.attributes.tags)
|
||||
)
|
||||
elif r.id in removals_by_request:
|
||||
main_logger.log(
|
||||
f"Found satisfied request for {r.id} at {removals_by_request[r.id].id}"
|
||||
f"Found satisfied request for {r.id} "
|
||||
f"at {removals_by_request[r.id].id}"
|
||||
)
|
||||
elif r.attributes.deployment in removals_by_deployment:
|
||||
main_logger.log(
|
||||
f"Found removal record for indicated deployment {r.attributes.deployment} at "
|
||||
f"Found removal record for indicated deployment "
|
||||
f"{r.attributes.deployment} at "
|
||||
f"{removals_by_deployment[r.attributes.deployment].id}"
|
||||
)
|
||||
else:
|
||||
@@ -309,7 +320,8 @@ def command( # noqa: C901
|
||||
requests_to_check_for_payment.append(r)
|
||||
else:
|
||||
main_logger.log(
|
||||
f"Skipping unsatisfied request {r.id} because we have seen it before."
|
||||
f"Skipping unsatisfied request {r.id} "
|
||||
"because we have seen it before."
|
||||
)
|
||||
except Exception as e:
|
||||
main_logger.log(f"ERROR examining {r.id}: {e}")
|
||||
|
||||
@@ -497,7 +497,7 @@ class LaconicRegistryClient:
|
||||
"--max-price",
|
||||
str(auction["max_price"]),
|
||||
"--num-providers",
|
||||
str(auction["num_providers"])
|
||||
str(auction["num_providers"]),
|
||||
]
|
||||
|
||||
return json.loads(logged_cmd(self.log_file, *args))["auctionId"]
|
||||
@@ -561,7 +561,8 @@ def build_container_image(app_record, tag, extra_build_args=None, logger=None):
|
||||
extra_build_args = []
|
||||
tmpdir = tempfile.mkdtemp()
|
||||
|
||||
# TODO: determine if this code could be calling into the Python git library like setup-repositories
|
||||
# TODO: determine if this code could be calling into the Python git
|
||||
# library like setup-repositories
|
||||
try:
|
||||
record_id = app_record["id"]
|
||||
ref = app_record.attributes.repository_ref
|
||||
@@ -570,7 +571,8 @@ def build_container_image(app_record, tag, extra_build_args=None, logger=None):
|
||||
|
||||
logger.log(f"Cloning repository {repo} to {clone_dir} ...")
|
||||
# Set github credentials if present running a command like:
|
||||
# git config --global url."https://${TOKEN}:@github.com/".insteadOf "https://github.com/"
|
||||
# git config --global url."https://${TOKEN}:@github.com/".insteadOf
|
||||
# "https://github.com/"
|
||||
github_token = os.environ.get("DEPLOYER_GITHUB_TOKEN")
|
||||
if github_token:
|
||||
logger.log("Github token detected, setting it in the git environment")
|
||||
@@ -612,7 +614,8 @@ def build_container_image(app_record, tag, extra_build_args=None, logger=None):
|
||||
logger.log(f"git checkout failed. Does ref {ref} exist?")
|
||||
raise e
|
||||
else:
|
||||
# TODO: why is this code different vs the branch above (run vs check_call, and no prompt disable)?
|
||||
# TODO: why is this code different vs the branch above (run vs check_call,
|
||||
# and no prompt disable)?
|
||||
result = subprocess.run(
|
||||
["git", "clone", "--depth", "1", repo, clone_dir],
|
||||
stdout=logger.file,
|
||||
@@ -749,9 +752,13 @@ def publish_deployment(
|
||||
|
||||
# Set auction or payment id from request
|
||||
if app_deployment_request.attributes.auction:
|
||||
new_deployment_record["record"]["auction"] = app_deployment_request.attributes.auction
|
||||
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
|
||||
new_deployment_record["record"][
|
||||
"payment"
|
||||
] = app_deployment_request.attributes.payment
|
||||
|
||||
if webapp_deployer_record:
|
||||
new_deployment_record["record"]["deployer"] = webapp_deployer_record.names[0]
|
||||
@@ -801,7 +808,9 @@ def skip_by_tag(r, include_tags, exclude_tags):
|
||||
return False
|
||||
|
||||
|
||||
def confirm_payment(laconic: LaconicRegistryClient, record, payment_address, min_amount, logger):
|
||||
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.
|
||||
@@ -818,27 +827,30 @@ def confirm_payment(laconic: LaconicRegistryClient, record, payment_address, min
|
||||
|
||||
if tx.code != 0:
|
||||
logger.log(
|
||||
f"{record.id}: payment tx {tx.hash} was not successful - code: {tx.code}, log: {tx.log}"
|
||||
f"{record.id}: payment tx {tx.hash} was not successful - "
|
||||
f"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}"
|
||||
f"{record.id}: payment sender {tx.sender} in tx {tx.hash} "
|
||||
f"does not match deployment 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}"
|
||||
f"{record.id}: payment recipient {tx.recipient} in tx {tx.hash} "
|
||||
f"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"
|
||||
f"{record.id}: {pay_denom} in tx {tx.hash} is not an expected "
|
||||
"payment denomination"
|
||||
)
|
||||
return False
|
||||
|
||||
@@ -859,7 +871,10 @@ def confirm_payment(laconic: LaconicRegistryClient, record, payment_address, min
|
||||
|
||||
# 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}")
|
||||
logger.log(
|
||||
f"{record.id}: payment {tx.hash} already used on a different "
|
||||
f"application deployment {used}"
|
||||
)
|
||||
return False
|
||||
|
||||
used = laconic.app_deployment_removals(
|
||||
@@ -874,7 +889,9 @@ def confirm_payment(laconic: LaconicRegistryClient, record, payment_address, min
|
||||
return True
|
||||
|
||||
|
||||
def confirm_auction(laconic: LaconicRegistryClient, record, deployer_lrn, payment_address, logger):
|
||||
def confirm_auction(
|
||||
laconic: LaconicRegistryClient, record, deployer_lrn, payment_address, logger
|
||||
):
|
||||
auction_id = record.attributes.auction
|
||||
auction = laconic.get_auction(auction_id)
|
||||
|
||||
@@ -886,11 +903,14 @@ def confirm_auction(laconic: LaconicRegistryClient, record, deployer_lrn, paymen
|
||||
|
||||
# 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)
|
||||
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}"
|
||||
f"{record.id}: requested application {record.attributes.application} "
|
||||
f"does not match application from auction record "
|
||||
f"{auction_records_by_id[0].attributes.application}"
|
||||
)
|
||||
return False
|
||||
|
||||
|
||||
Reference in New Issue
Block a user