From a8e7235267de04621a08667211813ea4cff8811a Mon Sep 17 00:00:00 2001 From: Prathamesh Musale Date: Wed, 5 Feb 2025 17:57:18 +0530 Subject: [PATCH 01/15] Handle multiple domains in webapp deployment request --- .../webapp/deploy_webapp_from_registry.py | 166 +++++++++++------- .../deploy/webapp/publish_webapp_deployer.py | 1 + stack_orchestrator/deploy/webapp/util.py | 27 ++- 3 files changed, 126 insertions(+), 68 deletions(-) diff --git a/stack_orchestrator/deploy/webapp/deploy_webapp_from_registry.py b/stack_orchestrator/deploy/webapp/deploy_webapp_from_registry.py index 24a529c2..69407580 100644 --- a/stack_orchestrator/deploy/webapp/deploy_webapp_from_registry.py +++ b/stack_orchestrator/deploy/webapp/deploy_webapp_from_registry.py @@ -38,7 +38,8 @@ from stack_orchestrator.deploy.webapp.util import ( file_hash, deploy_to_k8s, publish_deployment, - hostname_for_deployment_request, + get_requested_names, + hostnames_for_deployment_request, generate_hostname_for_app, match_owner, skip_by_tag, @@ -76,44 +77,47 @@ def process_app_deployment_request( logger.log(f"Retrieved app record {app_deployment_request.attributes.application}") # 2. determine dns - requested_name = hostname_for_deployment_request(app_deployment_request, laconic) - logger.log(f"Determined requested name: {requested_name}") + requested_names = hostnames_for_deployment_request(app_deployment_request, laconic) + logger.log(f"Determined requested name(s): {','.join(str(x) for x in requested_names)}") - if "." in requested_name: - if "allow" == fqdn_policy or "preexisting" == fqdn_policy: - fqdn = requested_name + fqdns = [] + for requested_name in requested_names: + if "." in requested_name: + if "allow" == fqdn_policy or "preexisting" == fqdn_policy: + fqdns.append(requested_name) + else: + raise Exception( + f"{requested_name} is invalid: only unqualified hostnames are allowed." + ) else: - raise Exception( - f"{requested_name} is invalid: only unqualified hostnames are allowed." - ) - else: - fqdn = f"{requested_name}.{default_dns_suffix}" + fqdns.append(f"{requested_name}.{default_dns_suffix}") # Normalize case (just in case) - fqdn = fqdn.lower() + fqdns = [fqdn.lower() for fqdn in fqdns] - # 3. check ownership of existing dnsrecord vs this request - dns_lrn = f"{dns_record_namespace}/{fqdn}" - dns_record = laconic.get_record(dns_lrn) - if dns_record: - matched_owner = match_owner(app_deployment_request, dns_record) - if not matched_owner and dns_record.attributes.request: - matched_owner = match_owner( - app_deployment_request, - laconic.get_record(dns_record.attributes.request, require=True), - ) + # 3. check ownership of existing dnsrecord(s) vs this request + for fqdn in fqdns: + dns_lrn = f"{dns_record_namespace}/{fqdn}" + dns_record = laconic.get_record(dns_lrn) + if dns_record: + matched_owner = match_owner(app_deployment_request, dns_record) + if not matched_owner and dns_record.attributes.request: + matched_owner = match_owner( + app_deployment_request, + laconic.get_record(dns_record.attributes.request, require=True), + ) - if matched_owner: - logger.log(f"Matched DnsRecord ownership: {matched_owner}") - else: + if matched_owner: + logger.log(f"Matched DnsRecord ownership for {fqdn}: {matched_owner}") + else: + raise Exception( + "Unable to confirm ownership of DnsRecord %s for request %s" + % (dns_lrn, app_deployment_request.id) + ) + elif "preexisting" == fqdn_policy: raise Exception( - "Unable to confirm ownership of DnsRecord %s for request %s" - % (dns_lrn, app_deployment_request.id) + f"No pre-existing DnsRecord {dns_lrn} could be found for request {app_deployment_request.id}." ) - elif "preexisting" == fqdn_policy: - raise Exception( - f"No pre-existing DnsRecord {dns_lrn} could be found for request {app_deployment_request.id}." - ) # 4. get build and runtime config from request env = {} @@ -145,25 +149,39 @@ def process_app_deployment_request( # 5. determine new or existing deployment # a. check for deployment lrn - app_deployment_lrn = f"{deployment_record_namespace}/{fqdn}" + app_deployment_lrns = [f"{deployment_record_namespace}/{fqdn}" for fqdn in fqdns] if app_deployment_request.attributes.deployment: - app_deployment_lrn = app_deployment_request.attributes.deployment - if not app_deployment_lrn.startswith(deployment_record_namespace): - raise Exception( - "Deployment LRN %s is not in a supported namespace" - % app_deployment_request.attributes.deployment - ) + app_deployment_lrns = [app_deployment_request.attributes.deployment] + if not app_deployment_lrns[0].startswith(deployment_record_namespace): + raise Exception( + "Deployment LRN %s is not in a supported namespace" + % app_deployment_request.attributes.deployment + ) - deployment_record = laconic.get_record(app_deployment_lrn) - deployment_dir = os.path.join(deployment_parent_dir, fqdn) + # Target deployment dir and existing deployment dir + deployment_dir = os.path.join(deployment_parent_dir, fqdns[-1]) + existing_deployment_dir = deployment_dir + + # Existing deployment record: take the first lrn that resolves + deployment_record = None + for app_deployment_lrn in app_deployment_lrns: + deployment_record = laconic.get_record(app_deployment_lrn) + if deployment_record is not None: + # TODO: Determine the deployment dir for existing deployment + # prev_request = laconic.get_record(deployment_record.attributes.request, True) + # existing_deployment_dir = ... + + break + + # Use the last fqdn for unique deployment container tag # 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") + unique_deployment_id = hashlib.md5(fqdns[-1].encode()).hexdigest()[:16] + deployment_config_file = os.path.join(existing_deployment_dir, "config.env") deployment_container_tag = "laconic-webapp/%s:local" % unique_deployment_id app_image_shared_tag = f"laconic-webapp/{app.id}:local" # b. check for deployment directory (create if necessary) - if not os.path.exists(deployment_dir): + if not os.path.exists(existing_deployment_dir): if deployment_record: raise Exception( "Deployment record %s exists, but not deployment dir %s. Please remove name." @@ -172,11 +190,12 @@ def process_app_deployment_request( logger.log( f"Creating webapp deployment in: {deployment_dir} with container id: {deployment_container_tag}" ) + # TODO: Pass URLs for all fqdns deploy_webapp.create_deployment( ctx, deployment_dir, deployment_container_tag, - f"https://{fqdn}", + f"https://{fqdns[-1]}", kube_config, image_registry, env_filename, @@ -184,6 +203,16 @@ def process_app_deployment_request( elif env_filename: shutil.copyfile(env_filename, deployment_config_file) + # TODO: Update spec with new urls + + # TODO: Update with deployment_container_tag if it's not a redeployment + # as for redeployment, deployment_container_tag won't get built + # if deployment_record.attributes.application != app.id: + # ... + + # Rename deployment dir according to new request (last fqdn from given dns) + os.rename(existing_deployment_dir, deployment_dir) + needs_k8s_deploy = False if force_rebuild: logger.log( @@ -233,17 +262,24 @@ def process_app_deployment_request( logger.log("Requested app is already deployed, skipping build and image push") # 7. update config (if needed) + # TODO: Also check if domains set has changed if ( not deployment_record or file_hash(deployment_config_file) != deployment_record.attributes.meta.config ): needs_k8s_deploy = True - # 8. update k8s deployment + # TODO: 8. delete unused names + # if deployment_record: + # ... + + # 9. update k8s deployment if needs_k8s_deploy: deploy_to_k8s(deployment_record, deployment_dir, recreate_on_deploy, logger) logger.log("Publishing deployment to registry.") + # TODO: Publish multiple DNS records + # TODO: Point all app_deployment_lrns publish_deployment( laconic, app, @@ -517,21 +553,26 @@ def command( # noqa: C901 result = "ERROR" continue - requested_name = r.attributes.dns - if not requested_name: - requested_name = generate_hostname_for_app(app) + requested_names = get_requested_names(r) + if len(requested_names) == 0: + requested_names = [generate_hostname_for_app(app)] main_logger.log( - "Generating name %s for request %s." % (requested_name, r.id) + "Generating name %s for request %s." % (requested_names[0], r.id) ) - if ( - requested_name in skipped_by_name - or requested_name in requests_by_name - ): - main_logger.log( - "Ignoring request %s, it has been superseded." % r.id - ) - result = "SKIP" + # Skip request if any of the names is superseded + for requested_name in requested_names: + if ( + requested_name in skipped_by_name + or requested_name in requests_by_name + ): + main_logger.log( + "Ignoring request %s, it has been superseded." % r.id + ) + result = "SKIP" + break + + if result == "SKIP": continue if skip_by_tag(r, include_tags, exclude_tags): @@ -539,15 +580,20 @@ def command( # noqa: C901 "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 + + for requested_name in requested_names: + skipped_by_name[requested_name] = r + result = "SKIP" continue main_logger.log( "Found pending request %s to run application %s on %s." - % (r.id, r.attributes.application, requested_name) + % (r.id, r.attributes.application, ','.join(str(x) for x in requested_names)) ) - requests_by_name[requested_name] = r + + # Set request for on of the names + requests_by_name[requested_names[-1]] = r except Exception as e: result = "ERROR" main_logger.log(f"ERROR examining request {r.id}: " + str(e)) diff --git a/stack_orchestrator/deploy/webapp/publish_webapp_deployer.py b/stack_orchestrator/deploy/webapp/publish_webapp_deployer.py index 851e90e1..dbbe7d6f 100644 --- a/stack_orchestrator/deploy/webapp/publish_webapp_deployer.py +++ b/stack_orchestrator/deploy/webapp/publish_webapp_deployer.py @@ -78,6 +78,7 @@ def command( # noqa: C901 "paymentAddress": payment_address, } } + # TODO: Add deployerVersion 1.0.0 if min_required_payment: webapp_deployer_record["record"][ diff --git a/stack_orchestrator/deploy/webapp/util.py b/stack_orchestrator/deploy/webapp/util.py index 991dd249..ebcd2526 100644 --- a/stack_orchestrator/deploy/webapp/util.py +++ b/stack_orchestrator/deploy/webapp/util.py @@ -761,18 +761,29 @@ def publish_deployment( deployment_id = laconic.publish(new_deployment_record, [deployment_lrn]) return {"dns": dns_id, "deployment": deployment_id} +def get_requested_names(app_deployment_request): + request_dns = app_deployment_request.attributes.dns + return request_dns.split(",") if request_dns else [] -def hostname_for_deployment_request(app_deployment_request, laconic): - dns_name = app_deployment_request.attributes.dns - if not dns_name: +def hostnames_for_deployment_request(app_deployment_request, laconic): + requested_names = get_requested_names(app_deployment_request) + + if len(requested_names) == 0: app = laconic.get_record( app_deployment_request.attributes.application, require=True ) - dns_name = generate_hostname_for_app(app) - elif dns_name.startswith("lrn://"): - record = laconic.get_record(dns_name, require=True) - dns_name = record.attributes.name - return dns_name + return [generate_hostname_for_app(app)] + + dns_names = [] + for requested_name in requested_names: + dns_name = requested_name + if dns_name.startswith("lrn://"): + record = laconic.get_record(dns_name, require=True) + dns_name = record.attributes.name + + dns_names.append(dns_name) + + return dns_names def generate_hostname_for_app(app): -- 2.45.2 From f5947a8a6503ecd519c936859a87382485bb46e7 Mon Sep 17 00:00:00 2001 From: Prathamesh Musale Date: Wed, 5 Feb 2025 18:39:18 +0530 Subject: [PATCH 02/15] Check for existing deployment --- .../webapp/deploy_webapp_from_registry.py | 36 ++++++++++--------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/stack_orchestrator/deploy/webapp/deploy_webapp_from_registry.py b/stack_orchestrator/deploy/webapp/deploy_webapp_from_registry.py index 69407580..e9559ea5 100644 --- a/stack_orchestrator/deploy/webapp/deploy_webapp_from_registry.py +++ b/stack_orchestrator/deploy/webapp/deploy_webapp_from_registry.py @@ -158,18 +158,23 @@ def process_app_deployment_request( % app_deployment_request.attributes.deployment ) - # Target deployment dir and existing deployment dir + # Target deployment dir deployment_dir = os.path.join(deployment_parent_dir, fqdns[-1]) - existing_deployment_dir = deployment_dir # Existing deployment record: take the first lrn that resolves deployment_record = None + existing_deployment_dir = deployment_dir # Default to target dir in case the app had been undeployed for app_deployment_lrn in app_deployment_lrns: deployment_record = laconic.get_record(app_deployment_lrn) - if deployment_record is not None: - # TODO: Determine the deployment dir for existing deployment - # prev_request = laconic.get_record(deployment_record.attributes.request, True) - # existing_deployment_dir = ... + if deployment_record: + # Determine the deployment dir for existing deployment + dir_name = deployment_record.attributes.url.replace("https://", "") + existing_deployment_dir = os.path.join(deployment_parent_dir, dir_name) + if not os.path.exists(existing_deployment_dir): + raise Exception( + "Deployment record %s exists, but not deployment dir %s. Please remove name." + % (app_deployment_lrn, existing_deployment_dir) + ) break @@ -177,16 +182,10 @@ def process_app_deployment_request( # 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(fqdns[-1].encode()).hexdigest()[:16] - deployment_config_file = os.path.join(existing_deployment_dir, "config.env") deployment_container_tag = "laconic-webapp/%s:local" % unique_deployment_id app_image_shared_tag = f"laconic-webapp/{app.id}:local" # b. check for deployment directory (create if necessary) if not os.path.exists(existing_deployment_dir): - if deployment_record: - raise Exception( - "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}" ) @@ -200,8 +199,14 @@ def process_app_deployment_request( image_registry, env_filename, ) - elif env_filename: - shutil.copyfile(env_filename, deployment_config_file) + else: + # Rename deployment dir according to new request (last fqdn from given dns) + os.rename(existing_deployment_dir, deployment_dir) + + # Update config if required + if env_filename: + deployment_config_file = os.path.join(deployment_dir, "config.env") + shutil.copyfile(env_filename, deployment_config_file) # TODO: Update spec with new urls @@ -210,8 +215,6 @@ def process_app_deployment_request( # if deployment_record.attributes.application != app.id: # ... - # Rename deployment dir according to new request (last fqdn from given dns) - os.rename(existing_deployment_dir, deployment_dir) needs_k8s_deploy = False if force_rebuild: @@ -646,6 +649,7 @@ def command( # noqa: C901 requests_to_check_for_payment.append(r) requests_to_execute = [] + # TODO: Charge more for multiple dns? for r in requests_to_check_for_payment: if r.attributes.auction: if auction_requests: -- 2.45.2 From 3d1a455344614e28488f256a5a527ce0ae0dc521 Mon Sep 17 00:00:00 2001 From: Prathamesh Musale Date: Wed, 5 Feb 2025 18:45:46 +0530 Subject: [PATCH 03/15] Configure spec with all given URLs --- .../deploy/webapp/deploy_webapp.py | 26 +++++++++++-------- .../webapp/deploy_webapp_from_registry.py | 4 +-- 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/stack_orchestrator/deploy/webapp/deploy_webapp.py b/stack_orchestrator/deploy/webapp/deploy_webapp.py index 4c91dec3..54f833aa 100644 --- a/stack_orchestrator/deploy/webapp/deploy_webapp.py +++ b/stack_orchestrator/deploy/webapp/deploy_webapp.py @@ -36,25 +36,29 @@ def _fixup_container_tag(deployment_dir: str, image: str): wfile.write(contents) -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: +def _fixup_url_spec(spec_file_name: str, urls: list[str]): + http_proxy_entries = [] + for url in urls: + parsed_url = urlparse(url) + http_proxy_entries.append(f''' - host-name: {parsed_url.hostname} routes: - path: '{parsed_url.path if parsed_url.path else "/"}' - proxy-to: webapp:80 + proxy-to: webapp:80''') + + http_proxy_spec = f''' + http-proxy:{''.join(http_proxy_entries)} ''' + spec_file_path = Path(spec_file_name) - with open(spec_file_path) as rfile: + with open(spec_file_path, "r") as rfile: contents = rfile.read() - contents = contents + http_proxy_spec + contents += http_proxy_spec with open(spec_file_path, "w") as wfile: wfile.write(contents) -def create_deployment(ctx, deployment_dir, image, url, kube_config, image_registry, env_file): +def create_deployment(ctx, deployment_dir, image, urls, 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 # --config (eqivalent of the contents of my-config.env) @@ -86,7 +90,7 @@ def create_deployment(ctx, deployment_dir, image, url, kube_config, image_regist None ) # Add the TLS and DNS spec - _fixup_url_spec(spec_file_name, url) + _fixup_url_spec(spec_file_name, urls) create_operation( deploy_command_context, spec_file_name, @@ -120,4 +124,4 @@ def command(ctx): def create(ctx, deployment_dir, image, url, kube_config, image_registry, env_file): '''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) diff --git a/stack_orchestrator/deploy/webapp/deploy_webapp_from_registry.py b/stack_orchestrator/deploy/webapp/deploy_webapp_from_registry.py index e9559ea5..92c16bce 100644 --- a/stack_orchestrator/deploy/webapp/deploy_webapp_from_registry.py +++ b/stack_orchestrator/deploy/webapp/deploy_webapp_from_registry.py @@ -189,12 +189,11 @@ def process_app_deployment_request( logger.log( f"Creating webapp deployment in: {deployment_dir} with container id: {deployment_container_tag}" ) - # TODO: Pass URLs for all fqdns deploy_webapp.create_deployment( ctx, deployment_dir, deployment_container_tag, - f"https://{fqdns[-1]}", + [f"https://{fqdn}" for fqdn in fqdns], kube_config, image_registry, env_filename, @@ -649,7 +648,6 @@ def command( # noqa: C901 requests_to_check_for_payment.append(r) requests_to_execute = [] - # TODO: Charge more for multiple dns? for r in requests_to_check_for_payment: if r.attributes.auction: if auction_requests: -- 2.45.2 From cdd431d8a52123aa8b37d72113e0c8e777eb3c63 Mon Sep 17 00:00:00 2001 From: Prathamesh Musale Date: Wed, 5 Feb 2025 19:09:23 +0530 Subject: [PATCH 04/15] Update existing deployment spec with new urls --- .../deploy/webapp/deploy_webapp.py | 42 ++++++++++++------- .../webapp/deploy_webapp_from_registry.py | 4 +- 2 files changed, 29 insertions(+), 17 deletions(-) diff --git a/stack_orchestrator/deploy/webapp/deploy_webapp.py b/stack_orchestrator/deploy/webapp/deploy_webapp.py index 54f833aa..5e77a8a4 100644 --- a/stack_orchestrator/deploy/webapp/deploy_webapp.py +++ b/stack_orchestrator/deploy/webapp/deploy_webapp.py @@ -15,6 +15,7 @@ import click import os +import yaml from pathlib import Path from urllib.parse import urlparse from tempfile import NamedTemporaryFile @@ -36,26 +37,35 @@ def _fixup_container_tag(deployment_dir: str, image: str): wfile.write(contents) -def _fixup_url_spec(spec_file_name: str, urls: list[str]): +def fixup_url_spec(spec_file_name: str, urls: list[str]): + spec_file_path = Path(spec_file_name) + + # Load existing spec + with open(spec_file_path, "r") as file: + spec_data = yaml.safe_load(file) or {} + + # Build new http-proxy entries http_proxy_entries = [] for url in urls: parsed_url = urlparse(url) - http_proxy_entries.append(f''' - - host-name: {parsed_url.hostname} - routes: - - path: '{parsed_url.path if parsed_url.path else "/"}' - proxy-to: webapp:80''') + http_proxy_entries.append({ + "host-name": parsed_url.hostname, + "routes": [ + { + "path": parsed_url.path if parsed_url.path else "/", + "proxy-to": "webapp:80" + } + ] + }) - http_proxy_spec = f''' - http-proxy:{''.join(http_proxy_entries)} - ''' + # Update the spec + if "network" not in spec_data: + spec_data["network"] = {} + spec_data["network"]["http-proxy"] = http_proxy_entries - spec_file_path = Path(spec_file_name) - with open(spec_file_path, "r") as rfile: - contents = rfile.read() - contents += http_proxy_spec - with open(spec_file_path, "w") as wfile: - wfile.write(contents) + # Write back the updated YAML + with open(spec_file_path, "w") as file: + yaml.dump(spec_data, file, default_flow_style=False, sort_keys=False) def create_deployment(ctx, deployment_dir, image, urls, kube_config, image_registry, env_file): @@ -90,7 +100,7 @@ def create_deployment(ctx, deployment_dir, image, urls, kube_config, image_regis None ) # Add the TLS and DNS spec - _fixup_url_spec(spec_file_name, urls) + fixup_url_spec(spec_file_name, urls) create_operation( deploy_command_context, spec_file_name, diff --git a/stack_orchestrator/deploy/webapp/deploy_webapp_from_registry.py b/stack_orchestrator/deploy/webapp/deploy_webapp_from_registry.py index 92c16bce..fa5500b2 100644 --- a/stack_orchestrator/deploy/webapp/deploy_webapp_from_registry.py +++ b/stack_orchestrator/deploy/webapp/deploy_webapp_from_registry.py @@ -26,6 +26,7 @@ import yaml import click import gnupg +from stack_orchestrator import constants from stack_orchestrator.deploy.images import remote_image_exists from stack_orchestrator.deploy.webapp import deploy_webapp from stack_orchestrator.deploy.webapp.util import ( @@ -207,7 +208,8 @@ def process_app_deployment_request( deployment_config_file = os.path.join(deployment_dir, "config.env") shutil.copyfile(env_filename, deployment_config_file) - # TODO: Update spec with new urls + # Update existing deployment spec with new urls + deploy_webapp.fixup_url_spec(os.path.join(deployment_dir, constants.spec_file_name)) # TODO: Update with deployment_container_tag if it's not a redeployment # as for redeployment, deployment_container_tag won't get built -- 2.45.2 From ef6f5db743a8ed6f1e473ae5e1e882e07dd011cc Mon Sep 17 00:00:00 2001 From: Prathamesh Musale Date: Wed, 5 Feb 2025 19:18:49 +0530 Subject: [PATCH 05/15] Update image tag for subsequent deployments of same app --- stack_orchestrator/deploy/webapp/deploy_webapp.py | 4 ++-- .../deploy/webapp/deploy_webapp_from_registry.py | 9 ++++----- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/stack_orchestrator/deploy/webapp/deploy_webapp.py b/stack_orchestrator/deploy/webapp/deploy_webapp.py index 5e77a8a4..092bf028 100644 --- a/stack_orchestrator/deploy/webapp/deploy_webapp.py +++ b/stack_orchestrator/deploy/webapp/deploy_webapp.py @@ -26,7 +26,7 @@ from stack_orchestrator.deploy.deploy import create_deploy_context from stack_orchestrator.deploy.deploy_types import DeployCommandContext -def _fixup_container_tag(deployment_dir: str, image: str): +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") # replace "cerc/webapp-container:local" in the file with our image tag @@ -109,7 +109,7 @@ def create_deployment(ctx, deployment_dir, image, urls, kube_config, image_regis None ) # Fix up the container tag inside the deployment compose file - _fixup_container_tag(deployment_dir, image) + fixup_container_tag(deployment_dir, image) os.remove(spec_file_name) diff --git a/stack_orchestrator/deploy/webapp/deploy_webapp_from_registry.py b/stack_orchestrator/deploy/webapp/deploy_webapp_from_registry.py index fa5500b2..a62f24f7 100644 --- a/stack_orchestrator/deploy/webapp/deploy_webapp_from_registry.py +++ b/stack_orchestrator/deploy/webapp/deploy_webapp_from_registry.py @@ -211,11 +211,10 @@ def process_app_deployment_request( # Update existing deployment spec with new urls deploy_webapp.fixup_url_spec(os.path.join(deployment_dir, constants.spec_file_name)) - # TODO: Update with deployment_container_tag if it's not a redeployment - # as for redeployment, deployment_container_tag won't get built - # if deployment_record.attributes.application != app.id: - # ... - + # Update the image name deployment_container_tag + # Skip for redeployment as deployment_container_tag won't get built + if deployment_record.attributes.application != app.id: + deploy_webapp.fixup_container_tag(deployment_dir, deployment_container_tag) needs_k8s_deploy = False if force_rebuild: -- 2.45.2 From 42103ef55181d0cd34ff95703201a25acce55cf3 Mon Sep 17 00:00:00 2001 From: Prathamesh Musale Date: Thu, 6 Feb 2025 15:14:12 +0530 Subject: [PATCH 06/15] Publish multiple DNS records and set all deployment names --- .../webapp/deploy_webapp_from_registry.py | 15 ++-- stack_orchestrator/deploy/webapp/util.py | 72 ++++++++++--------- 2 files changed, 50 insertions(+), 37 deletions(-) diff --git a/stack_orchestrator/deploy/webapp/deploy_webapp_from_registry.py b/stack_orchestrator/deploy/webapp/deploy_webapp_from_registry.py index a62f24f7..c9c990a4 100644 --- a/stack_orchestrator/deploy/webapp/deploy_webapp_from_registry.py +++ b/stack_orchestrator/deploy/webapp/deploy_webapp_from_registry.py @@ -97,9 +97,15 @@ def process_app_deployment_request( fqdns = [fqdn.lower() for fqdn in fqdns] # 3. check ownership of existing dnsrecord(s) vs this request + dns_lrns = [] + existing_dns_records_by_lrns = {} for fqdn in fqdns: dns_lrn = f"{dns_record_namespace}/{fqdn}" + dns_lrns.append(dns_lrn) + dns_record = laconic.get_record(dns_lrn) + existing_dns_records_by_lrns[dns_lrn] = dns_record + if dns_record: matched_owner = match_owner(app_deployment_request, dns_record) if not matched_owner and dns_record.attributes.request: @@ -281,15 +287,14 @@ def process_app_deployment_request( deploy_to_k8s(deployment_record, deployment_dir, recreate_on_deploy, logger) logger.log("Publishing deployment to registry.") - # TODO: Publish multiple DNS records - # TODO: Point all app_deployment_lrns publish_deployment( laconic, app, deployment_record, - app_deployment_lrn, - dns_record, - dns_lrn, + app_deployment_lrns, + existing_dns_records_by_lrns, + dns_lrns, + dns_record_namespace, deployment_dir, dns_value, app_deployment_request, diff --git a/stack_orchestrator/deploy/webapp/util.py b/stack_orchestrator/deploy/webapp/util.py index ebcd2526..0c8e6f2f 100644 --- a/stack_orchestrator/deploy/webapp/util.py +++ b/stack_orchestrator/deploy/webapp/util.py @@ -685,9 +685,10 @@ def publish_deployment( laconic: LaconicRegistryClient, app_record, deploy_record, - deployment_lrn, - dns_record, - dns_lrn, + deployment_lrns, + existing_dns_records_by_lrns, + dns_lrns: list[str], + dns_record_namespace, deployment_dir, dns_value=None, app_deployment_request=None, @@ -701,42 +702,49 @@ def publish_deployment( int(deploy_record.attributes.version.split(".")[-1]) + 1 ) - if not dns_record: - dns_ver = "0.0.1" - else: - dns_ver = "0.0.%d" % (int(dns_record.attributes.version.split(".")[-1]) + 1) + + dns_ids = [] + for dns_lrn in dns_lrns: + dns_record = existing_dns_records_by_lrns[dns_lrn] + if not dns_record: + dns_ver = "0.0.1" + else: + dns_ver = "0.0.%d" % (int(dns_record.attributes.version.split(".")[-1]) + 1) + + fqdn = dns_lrn.removeprefix(f"${dns_record_namespace}/") + uniq = uuid.uuid4() + + new_dns_record = { + "record": { + "type": "DnsRecord", + "version": dns_ver, + "name": fqdn, + "resource_type": "A", + "meta": {"so": uniq.hex}, + } + } + if app_deployment_request: + new_dns_record["record"]["request"] = app_deployment_request.id + if dns_value: + new_dns_record["record"]["value"] = dns_value + + if logger: + logger.log("Publishing DnsRecord.") + dns_id = laconic.publish(new_dns_record, [dns_lrn]) + dns_ids.append(dns_id) spec = yaml.full_load(open(os.path.join(deployment_dir, "spec.yml"))) - fqdn = spec["network"]["http-proxy"][0]["host-name"] - - uniq = uuid.uuid4() - - new_dns_record = { - "record": { - "type": "DnsRecord", - "version": dns_ver, - "name": fqdn, - "resource_type": "A", - "meta": {"so": uniq.hex}, - } - } - if app_deployment_request: - new_dns_record["record"]["request"] = app_deployment_request.id - if dns_value: - new_dns_record["record"]["value"] = dns_value - - if logger: - logger.log("Publishing DnsRecord.") - dns_id = laconic.publish(new_dns_record, [dns_lrn]) + last_fqdn = spec["network"]["http-proxy"][-1]["host-name"] + last_dns_id = dns_ids[-1] new_deployment_record = { "record": { "type": "ApplicationDeploymentRecord", "version": deploy_ver, - "url": f"https://{fqdn}", + "url": f"https://{last_fqdn}", "name": app_record.attributes.name, "application": app_record.id, - "dns": dns_id, + "dns": last_dns_id, "meta": { "config": file_hash(os.path.join(deployment_dir, "config.env")), "so": uniq.hex, @@ -758,8 +766,8 @@ def publish_deployment( if logger: logger.log("Publishing ApplicationDeploymentRecord.") - deployment_id = laconic.publish(new_deployment_record, [deployment_lrn]) - return {"dns": dns_id, "deployment": deployment_id} + deployment_id = laconic.publish(new_deployment_record, [deployment_lrns]) + return {"dns": dns_ids, "deployment": deployment_id} def get_requested_names(app_deployment_request): request_dns = app_deployment_request.attributes.dns -- 2.45.2 From 50508644bed6ddd93f8786465529f755e475b370 Mon Sep 17 00:00:00 2001 From: Prathamesh Musale Date: Thu, 6 Feb 2025 16:12:11 +0530 Subject: [PATCH 07/15] Delete deployment and DNS names for removed domains --- .../deploy/webapp/deploy_webapp.py | 24 +++++++++-- .../webapp/deploy_webapp_from_registry.py | 43 +++++++++++-------- stack_orchestrator/deploy/webapp/util.py | 2 +- 3 files changed, 47 insertions(+), 22 deletions(-) diff --git a/stack_orchestrator/deploy/webapp/deploy_webapp.py b/stack_orchestrator/deploy/webapp/deploy_webapp.py index 092bf028..fd51f055 100644 --- a/stack_orchestrator/deploy/webapp/deploy_webapp.py +++ b/stack_orchestrator/deploy/webapp/deploy_webapp.py @@ -13,6 +13,7 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . +import shutil import click import os import yaml @@ -20,13 +21,14 @@ from pathlib import Path from urllib.parse import urlparse from tempfile import NamedTemporaryFile +from stack_orchestrator import constants from stack_orchestrator.util import error_exit, global_options2 from stack_orchestrator.deploy.deployment_create import init_operation, create_operation from stack_orchestrator.deploy.deploy import create_deploy_context from stack_orchestrator.deploy.deploy_types import DeployCommandContext -def fixup_container_tag(deployment_dir: str, image: str): +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") # replace "cerc/webapp-container:local" in the file with our image tag @@ -37,7 +39,7 @@ def fixup_container_tag(deployment_dir: str, image: str): wfile.write(contents) -def fixup_url_spec(spec_file_name: str, urls: list[str]): +def _fixup_url_spec(spec_file_name: str, urls: list[str]): spec_file_path = Path(spec_file_name) # Load existing spec @@ -100,7 +102,7 @@ def create_deployment(ctx, deployment_dir, image, urls, kube_config, image_regis None ) # Add the TLS and DNS spec - fixup_url_spec(spec_file_name, urls) + _fixup_url_spec(spec_file_name, urls) create_operation( deploy_command_context, spec_file_name, @@ -109,10 +111,24 @@ def create_deployment(ctx, deployment_dir, image, urls, kube_config, image_regis None ) # Fix up the container tag inside the deployment compose file - fixup_container_tag(deployment_dir, image) + _fixup_container_tag(deployment_dir, image) os.remove(spec_file_name) +def update_deployment(deployment_dir, image, urls, env_file): + # Update config if required + if env_file: + deployment_config_file = os.path.join(deployment_dir, "config.env") + shutil.copyfile(env_file, deployment_config_file) + + # Update existing deployment spec with new urls + _fixup_url_spec(os.path.join(deployment_dir, constants.spec_file_name), urls) + + # Update the image name if required + if image: + _fixup_container_tag(deployment_dir, image) + + @click.group() @click.pass_context def command(ctx): diff --git a/stack_orchestrator/deploy/webapp/deploy_webapp_from_registry.py b/stack_orchestrator/deploy/webapp/deploy_webapp_from_registry.py index c9c990a4..95b82383 100644 --- a/stack_orchestrator/deploy/webapp/deploy_webapp_from_registry.py +++ b/stack_orchestrator/deploy/webapp/deploy_webapp_from_registry.py @@ -170,7 +170,9 @@ def process_app_deployment_request( # Existing deployment record: take the first lrn that resolves deployment_record = None + fqdns_to_release = [] existing_deployment_dir = deployment_dir # Default to target dir in case the app had been undeployed + for app_deployment_lrn in app_deployment_lrns: deployment_record = laconic.get_record(app_deployment_lrn) if deployment_record: @@ -183,6 +185,10 @@ def process_app_deployment_request( % (app_deployment_lrn, existing_deployment_dir) ) + previous_app_deployment_lrns: list[str] = deployment_record.names + previous_fqdns = [lrn.removeprefix(f"{deployment_record_namespace}/") for lrn in previous_app_deployment_lrns] + fqdns_to_release = list(set(previous_fqdns) - set(fqdns)) + break # Use the last fqdn for unique deployment container tag @@ -209,18 +215,14 @@ def process_app_deployment_request( # Rename deployment dir according to new request (last fqdn from given dns) os.rename(existing_deployment_dir, deployment_dir) - # Update config if required - if env_filename: - deployment_config_file = os.path.join(deployment_dir, "config.env") - shutil.copyfile(env_filename, deployment_config_file) - - # Update existing deployment spec with new urls - deploy_webapp.fixup_url_spec(os.path.join(deployment_dir, constants.spec_file_name)) - # Update the image name deployment_container_tag # Skip for redeployment as deployment_container_tag won't get built + updated_image = None if deployment_record.attributes.application != app.id: - deploy_webapp.fixup_container_tag(deployment_dir, deployment_container_tag) + updated_image = deployment_container_tag + + # Update the existing deployment + deploy_webapp.update_deployment(deployment_dir, updated_image, [f"https://{fqdn}" for fqdn in fqdns], env_filename) needs_k8s_deploy = False if force_rebuild: @@ -270,19 +272,15 @@ def process_app_deployment_request( else: logger.log("Requested app is already deployed, skipping build and image push") - # 7. update config (if needed) - # TODO: Also check if domains set has changed + # 7. restart deployment on config or url spec change if ( not deployment_record - or file_hash(deployment_config_file) != deployment_record.attributes.meta.config + or file_hash(os.path.join(deployment_dir, "config.env")) != deployment_record.attributes.meta.config + or len(fqdns_to_release) != 0 ): needs_k8s_deploy = True - # TODO: 8. delete unused names - # if deployment_record: - # ... - - # 9. update k8s deployment + # 8. update k8s deployment if needs_k8s_deploy: deploy_to_k8s(deployment_record, deployment_dir, recreate_on_deploy, logger) @@ -302,6 +300,17 @@ def process_app_deployment_request( logger, ) logger.log("Publication complete.") + + # 9. delete unused names from previous deployment (app and dns) + for fqdn in fqdns_to_release: + # Delete app deployment name and DNS name + deployment_name = "{deployment_record_namespace}/{fqdn}" + dns_name = "{deployment_record_namespace}/{fqdn}" + + logger.log(f"Removing names {deployment_name} and {dns_name}") + laconic.delete_name(deployment_name) + laconic.delete_name(dns_name) + logger.log("END - process_app_deployment_request") diff --git a/stack_orchestrator/deploy/webapp/util.py b/stack_orchestrator/deploy/webapp/util.py index 0c8e6f2f..6c2bab05 100644 --- a/stack_orchestrator/deploy/webapp/util.py +++ b/stack_orchestrator/deploy/webapp/util.py @@ -711,7 +711,7 @@ def publish_deployment( else: dns_ver = "0.0.%d" % (int(dns_record.attributes.version.split(".")[-1]) + 1) - fqdn = dns_lrn.removeprefix(f"${dns_record_namespace}/") + fqdn = dns_lrn.removeprefix(f"{dns_record_namespace}/") uniq = uuid.uuid4() new_dns_record = { -- 2.45.2 From af5ec54b8d075d3f464a82a1ed2aac85c887c233 Mon Sep 17 00:00:00 2001 From: Prathamesh Musale Date: Thu, 6 Feb 2025 16:13:43 +0530 Subject: [PATCH 08/15] Add deployer version to deployer record --- stack_orchestrator/deploy/webapp/publish_webapp_deployer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stack_orchestrator/deploy/webapp/publish_webapp_deployer.py b/stack_orchestrator/deploy/webapp/publish_webapp_deployer.py index dbbe7d6f..2ba79149 100644 --- a/stack_orchestrator/deploy/webapp/publish_webapp_deployer.py +++ b/stack_orchestrator/deploy/webapp/publish_webapp_deployer.py @@ -76,9 +76,9 @@ def command( # noqa: C901 "name": hostname, "publicKey": pub_key, "paymentAddress": payment_address, + "deployerVersion": "1.0.0", } } - # TODO: Add deployerVersion 1.0.0 if min_required_payment: webapp_deployer_record["record"][ -- 2.45.2 From 831e4a14ff53ede3d0eb43da155df42602e096e0 Mon Sep 17 00:00:00 2001 From: Prathamesh Musale Date: Thu, 6 Feb 2025 16:56:21 +0530 Subject: [PATCH 09/15] Handle lint errors --- .../deploy/webapp/deploy_webapp_from_registry.py | 5 ++--- stack_orchestrator/deploy/webapp/util.py | 3 ++- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/stack_orchestrator/deploy/webapp/deploy_webapp_from_registry.py b/stack_orchestrator/deploy/webapp/deploy_webapp_from_registry.py index 95b82383..a31467f6 100644 --- a/stack_orchestrator/deploy/webapp/deploy_webapp_from_registry.py +++ b/stack_orchestrator/deploy/webapp/deploy_webapp_from_registry.py @@ -26,7 +26,6 @@ import yaml import click import gnupg -from stack_orchestrator import constants from stack_orchestrator.deploy.images import remote_image_exists from stack_orchestrator.deploy.webapp import deploy_webapp from stack_orchestrator.deploy.webapp.util import ( @@ -49,7 +48,7 @@ from stack_orchestrator.deploy.webapp.util import ( ) -def process_app_deployment_request( +def process_app_deployment_request( # noqa ctx, laconic: LaconicRegistryClient, app_deployment_request, @@ -171,7 +170,7 @@ def process_app_deployment_request( # Existing deployment record: take the first lrn that resolves deployment_record = None fqdns_to_release = [] - existing_deployment_dir = deployment_dir # Default to target dir in case the app had been undeployed + existing_deployment_dir = deployment_dir # Default to target dir in case the app had been undeployed for app_deployment_lrn in app_deployment_lrns: deployment_record = laconic.get_record(app_deployment_lrn) diff --git a/stack_orchestrator/deploy/webapp/util.py b/stack_orchestrator/deploy/webapp/util.py index 6c2bab05..5949693e 100644 --- a/stack_orchestrator/deploy/webapp/util.py +++ b/stack_orchestrator/deploy/webapp/util.py @@ -702,7 +702,6 @@ def publish_deployment( int(deploy_record.attributes.version.split(".")[-1]) + 1 ) - dns_ids = [] for dns_lrn in dns_lrns: dns_record = existing_dns_records_by_lrns[dns_lrn] @@ -769,10 +768,12 @@ def publish_deployment( deployment_id = laconic.publish(new_deployment_record, [deployment_lrns]) return {"dns": dns_ids, "deployment": deployment_id} + def get_requested_names(app_deployment_request): request_dns = app_deployment_request.attributes.dns return request_dns.split(",") if request_dns else [] + def hostnames_for_deployment_request(app_deployment_request, laconic): requested_names = get_requested_names(app_deployment_request) -- 2.45.2 From 4ca96bbf8c98879c4efc976bfe528f8412b1c2a9 Mon Sep 17 00:00:00 2001 From: Prathamesh Musale Date: Thu, 6 Feb 2025 19:13:58 +0530 Subject: [PATCH 10/15] Fix types usage --- stack_orchestrator/deploy/webapp/deploy_webapp.py | 3 ++- .../deploy/webapp/deploy_webapp_from_registry.py | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/stack_orchestrator/deploy/webapp/deploy_webapp.py b/stack_orchestrator/deploy/webapp/deploy_webapp.py index fd51f055..871f227e 100644 --- a/stack_orchestrator/deploy/webapp/deploy_webapp.py +++ b/stack_orchestrator/deploy/webapp/deploy_webapp.py @@ -14,6 +14,7 @@ # along with this program. If not, see . import shutil +from typing import List import click import os import yaml @@ -39,7 +40,7 @@ def _fixup_container_tag(deployment_dir: str, image: str): wfile.write(contents) -def _fixup_url_spec(spec_file_name: str, urls: list[str]): +def _fixup_url_spec(spec_file_name: str, urls: List[str]): spec_file_path = Path(spec_file_name) # Load existing spec diff --git a/stack_orchestrator/deploy/webapp/deploy_webapp_from_registry.py b/stack_orchestrator/deploy/webapp/deploy_webapp_from_registry.py index a31467f6..c8cf0ced 100644 --- a/stack_orchestrator/deploy/webapp/deploy_webapp_from_registry.py +++ b/stack_orchestrator/deploy/webapp/deploy_webapp_from_registry.py @@ -20,6 +20,7 @@ import shutil import sys import tempfile import time +from typing import List import uuid import yaml @@ -184,7 +185,7 @@ def process_app_deployment_request( # noqa % (app_deployment_lrn, existing_deployment_dir) ) - previous_app_deployment_lrns: list[str] = deployment_record.names + previous_app_deployment_lrns: List[str] = deployment_record.names previous_fqdns = [lrn.removeprefix(f"{deployment_record_namespace}/") for lrn in previous_app_deployment_lrns] fqdns_to_release = list(set(previous_fqdns) - set(fqdns)) -- 2.45.2 From 8c77af0fecc86a151fa3418e9cd22825d2e703b6 Mon Sep 17 00:00:00 2001 From: Prathamesh Musale Date: Thu, 6 Feb 2025 20:30:45 +0530 Subject: [PATCH 11/15] Fix types usage --- stack_orchestrator/deploy/webapp/util.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/stack_orchestrator/deploy/webapp/util.py b/stack_orchestrator/deploy/webapp/util.py index 5949693e..d5098099 100644 --- a/stack_orchestrator/deploy/webapp/util.py +++ b/stack_orchestrator/deploy/webapp/util.py @@ -21,6 +21,7 @@ import random import subprocess import sys import tempfile +from typing import List import uuid import yaml @@ -687,7 +688,7 @@ def publish_deployment( deploy_record, deployment_lrns, existing_dns_records_by_lrns, - dns_lrns: list[str], + dns_lrns: List[str], dns_record_namespace, deployment_dir, dns_value=None, -- 2.45.2 From f98e75c81f06ec0113ab592ba43f149ab828b103 Mon Sep 17 00:00:00 2001 From: Prathamesh Musale Date: Fri, 7 Feb 2025 11:30:07 +0530 Subject: [PATCH 12/15] Fix deployment record publication --- stack_orchestrator/deploy/webapp/util.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stack_orchestrator/deploy/webapp/util.py b/stack_orchestrator/deploy/webapp/util.py index d5098099..ff5131a6 100644 --- a/stack_orchestrator/deploy/webapp/util.py +++ b/stack_orchestrator/deploy/webapp/util.py @@ -766,7 +766,7 @@ def publish_deployment( if logger: logger.log("Publishing ApplicationDeploymentRecord.") - deployment_id = laconic.publish(new_deployment_record, [deployment_lrns]) + deployment_id = laconic.publish(new_deployment_record, deployment_lrns) return {"dns": dns_ids, "deployment": deployment_id} -- 2.45.2 From 9bc8ce4866a5fbee4161aff6b472f20a9f5cf09d Mon Sep 17 00:00:00 2001 From: Prathamesh Musale Date: Mon, 10 Feb 2025 10:56:00 +0530 Subject: [PATCH 13/15] Update ingress creation for multiple host names --- stack_orchestrator/deploy/k8s/cluster_info.py | 54 ++++++++++--------- stack_orchestrator/deploy/k8s/deploy_k8s.py | 2 + 2 files changed, 31 insertions(+), 25 deletions(-) diff --git a/stack_orchestrator/deploy/k8s/cluster_info.py b/stack_orchestrator/deploy/k8s/cluster_info.py index be1b2e3d..04bfeaed 100644 --- a/stack_orchestrator/deploy/k8s/cluster_info.py +++ b/stack_orchestrator/deploy/k8s/cluster_info.py @@ -117,19 +117,23 @@ class ClusterInfo: def get_ingress(self, use_tls=False, certificate=None, cluster_issuer="letsencrypt-prod"): # No ingress for a deployment that has no http-proxy defined, for now http_proxy_info_list = self.spec.get_http_proxy() - ingress = None - if http_proxy_info_list: - # TODO: handle multiple definitions - http_proxy_info = http_proxy_info_list[0] + if not http_proxy_info_list: + return None + + tls = [] if use_tls else None + rules = [] + for http_proxy_info in http_proxy_info_list: if opts.o.debug: print(f"http-proxy: {http_proxy_info}") # TODO: good enough parsing for webapp deployment for now host_name = http_proxy_info["host-name"] - rules = [] - tls = [client.V1IngressTLS( - hosts=certificate["spec"]["dnsNames"] if certificate else [host_name], - secret_name=certificate["spec"]["secretName"] if certificate else f"{self.app_name}-tls" - )] if use_tls else None + + if use_tls: + tls.append(client.V1IngressTLS( + hosts=certificate["spec"]["dnsNames"] if certificate else [host_name], + secret_name=certificate["spec"]["secretName"] if certificate else f"{self.app_name}-{host_name}-tls" + )) + paths = [] for route in http_proxy_info["routes"]: path = route["path"] @@ -156,24 +160,24 @@ class ClusterInfo: paths=paths ) )) - spec = client.V1IngressSpec( - tls=tls, - rules=rules - ) + spec = client.V1IngressSpec( + tls=tls, + rules=rules + ) - ingress_annotations = { - "kubernetes.io/ingress.class": "nginx", - } - if not certificate: - ingress_annotations["cert-manager.io/cluster-issuer"] = cluster_issuer + ingress_annotations = { + "kubernetes.io/ingress.class": "nginx", + } + if not certificate: + ingress_annotations["cert-manager.io/cluster-issuer"] = cluster_issuer - ingress = client.V1Ingress( - metadata=client.V1ObjectMeta( - name=f"{self.app_name}-ingress", - annotations=ingress_annotations - ), - spec=spec - ) + ingress = client.V1Ingress( + metadata=client.V1ObjectMeta( + name=f"{self.app_name}-ingress", + annotations=ingress_annotations + ), + spec=spec + ) return ingress # TODO: suppoprt multiple services diff --git a/stack_orchestrator/deploy/k8s/deploy_k8s.py b/stack_orchestrator/deploy/k8s/deploy_k8s.py index b254fd4c..e9519db8 100644 --- a/stack_orchestrator/deploy/k8s/deploy_k8s.py +++ b/stack_orchestrator/deploy/k8s/deploy_k8s.py @@ -230,6 +230,7 @@ class K8sDeployer(Deployer): http_proxy_info = self.cluster_info.spec.get_http_proxy() # Note: at present we don't support tls for kind (and enabling tls causes errors) use_tls = http_proxy_info and not self.is_kind() + # TODO Handle for multiple http_proxy_info certificate = self._find_certificate_for_host_name(http_proxy_info[0]["host-name"]) if use_tls else None if opts.o.debug: if certificate: @@ -367,6 +368,7 @@ class K8sDeployer(Deployer): # Destroy the kind cluster destroy_cluster(self.kind_cluster_name) + # TODO: Update for multiple host-names def status(self): self.connect_api() # Call whatever API we need to get the running container list -- 2.45.2 From e491545354e3cc70bf753761770c13b31e1a4c33 Mon Sep 17 00:00:00 2001 From: Prathamesh Musale Date: Mon, 10 Feb 2025 11:44:28 +0530 Subject: [PATCH 14/15] Use existing certificates if available and update status command --- stack_orchestrator/deploy/k8s/cluster_info.py | 3 +- stack_orchestrator/deploy/k8s/deploy_k8s.py | 69 +++++++++++-------- 2 files changed, 42 insertions(+), 30 deletions(-) diff --git a/stack_orchestrator/deploy/k8s/cluster_info.py b/stack_orchestrator/deploy/k8s/cluster_info.py index 04bfeaed..838fac08 100644 --- a/stack_orchestrator/deploy/k8s/cluster_info.py +++ b/stack_orchestrator/deploy/k8s/cluster_info.py @@ -114,7 +114,7 @@ class ClusterInfo: nodeports.append(service) return nodeports - def get_ingress(self, use_tls=False, certificate=None, cluster_issuer="letsencrypt-prod"): + def get_ingress(self, use_tls=False, certificate_by_host={}, cluster_issuer="letsencrypt-prod"): # No ingress for a deployment that has no http-proxy defined, for now http_proxy_info_list = self.spec.get_http_proxy() if not http_proxy_info_list: @@ -127,6 +127,7 @@ class ClusterInfo: print(f"http-proxy: {http_proxy_info}") # TODO: good enough parsing for webapp deployment for now host_name = http_proxy_info["host-name"] + certificate = certificate_by_host[host_name] if host_name in certificate_by_host else None if use_tls: tls.append(client.V1IngressTLS( diff --git a/stack_orchestrator/deploy/k8s/deploy_k8s.py b/stack_orchestrator/deploy/k8s/deploy_k8s.py index e9519db8..3d2c6614 100644 --- a/stack_orchestrator/deploy/k8s/deploy_k8s.py +++ b/stack_orchestrator/deploy/k8s/deploy_k8s.py @@ -227,16 +227,18 @@ class K8sDeployer(Deployer): self._create_volume_data() self._create_deployment() - http_proxy_info = self.cluster_info.spec.get_http_proxy() + http_proxy_info_list = self.cluster_info.spec.get_http_proxy() # Note: at present we don't support tls for kind (and enabling tls causes errors) - use_tls = http_proxy_info and not self.is_kind() - # TODO Handle for multiple http_proxy_info - certificate = self._find_certificate_for_host_name(http_proxy_info[0]["host-name"]) if use_tls else None - if opts.o.debug: - if certificate: - print(f"Using existing certificate: {certificate}") + use_tls = http_proxy_info_list and not self.is_kind() + certificate_by_host = {} + if use_tls: + for http_proxy_info in http_proxy_info_list: + certificate = self._find_certificate_for_host_name(http_proxy_info["host-name"]) + if opts.o.debug and certificate: + print(f"Using existing certificate: {certificate}") + certificate_by_host[http_proxy_info["host-name"]] = certificate - ingress: client.V1Ingress = self.cluster_info.get_ingress(use_tls=use_tls, certificate=certificate) + ingress: client.V1Ingress = self.cluster_info.get_ingress(use_tls=use_tls, certificates_by_host_name=certificate_by_host) if ingress: if opts.o.debug: print(f"Sending this ingress: {ingress}") @@ -368,7 +370,6 @@ class K8sDeployer(Deployer): # Destroy the kind cluster destroy_cluster(self.kind_cluster_name) - # TODO: Update for multiple host-names def status(self): self.connect_api() # Call whatever API we need to get the running container list @@ -383,36 +384,46 @@ class K8sDeployer(Deployer): if not pods: return - hostname = "?" - ip = "?" - tls = "?" + tls_by_host = {} try: ingress = self.networking_api.read_namespaced_ingress(namespace=self.k8s_namespace, name=self.cluster_info.get_ingress().metadata.name) - cert = self.custom_obj_api.get_namespaced_custom_object( - group="cert-manager.io", - version="v1", - namespace=self.k8s_namespace, - plural="certificates", - name=ingress.spec.tls[0].secret_name - ) - - hostname = ingress.spec.rules[0].host ip = ingress.status.load_balancer.ingress[0].ip - tls = "notBefore: %s; notAfter: %s; names: %s" % ( - cert["status"]["notBefore"], cert["status"]["notAfter"], ingress.spec.tls[0].hosts - ) + for rule in ingress.spec.rules: + hostname = rule.host + tls_spec = next((tls for tls in ingress.spec.tls if hostname in tls.hosts), None) + if tls_spec: + cert = self.custom_obj_api.get_namespaced_custom_object( + group="cert-manager.io", + version="v1", + namespace=self.k8s_namespace, + plural="certificates", + name=tls_spec.secret_name + ) + tls = "notBefore: %s; notAfter: %s; names: %s" % ( + cert["status"]["notBefore"], cert["status"]["notAfter"], tls_spec.hosts + ) + tls_by_host[hostname] = tls + else: + tls_by_host[hostname] = None except: # noqa: E722 pass print("Ingress:") - print("\tHostname:", hostname) - print("\tIP:", ip) - print("\tTLS:", tls) - print("") - print("Pods:") + if len(tls_by_host) == 0: + print("\tHostname:", "?") + print("\tIP:", "?") + print("\tTLS:", "?") + print("") + for hostname, tls in tls_by_host.items(): + print("\tHostname:", hostname) + print("\tIP:", ip) + print("\tTLS:", tls) + print("") + + print("Pods:") for p in pods: if p.metadata.deletion_timestamp: print(f"\t{p.metadata.namespace}/{p.metadata.name}: Terminating ({p.metadata.deletion_timestamp})") -- 2.45.2 From e1e7075bf3e1f5a6492dbc487508205a9aa68e0b Mon Sep 17 00:00:00 2001 From: Prathamesh Musale Date: Mon, 10 Feb 2025 14:10:16 +0530 Subject: [PATCH 15/15] Fix arg --- stack_orchestrator/deploy/k8s/deploy_k8s.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stack_orchestrator/deploy/k8s/deploy_k8s.py b/stack_orchestrator/deploy/k8s/deploy_k8s.py index 3d2c6614..c2df1bd1 100644 --- a/stack_orchestrator/deploy/k8s/deploy_k8s.py +++ b/stack_orchestrator/deploy/k8s/deploy_k8s.py @@ -238,7 +238,7 @@ class K8sDeployer(Deployer): print(f"Using existing certificate: {certificate}") certificate_by_host[http_proxy_info["host-name"]] = certificate - ingress: client.V1Ingress = self.cluster_info.get_ingress(use_tls=use_tls, certificates_by_host_name=certificate_by_host) + ingress: client.V1Ingress = self.cluster_info.get_ingress(use_tls=use_tls, certificate_by_host=certificate_by_host) if ingress: if opts.o.debug: print(f"Sending this ingress: {ingress}") -- 2.45.2