forked from cerc-io/stack-orchestrator
Handle multiple domains in webapp deployment request
This commit is contained in:
parent
873a6d472c
commit
a8e7235267
@ -38,7 +38,8 @@ from stack_orchestrator.deploy.webapp.util import (
|
|||||||
file_hash,
|
file_hash,
|
||||||
deploy_to_k8s,
|
deploy_to_k8s,
|
||||||
publish_deployment,
|
publish_deployment,
|
||||||
hostname_for_deployment_request,
|
get_requested_names,
|
||||||
|
hostnames_for_deployment_request,
|
||||||
generate_hostname_for_app,
|
generate_hostname_for_app,
|
||||||
match_owner,
|
match_owner,
|
||||||
skip_by_tag,
|
skip_by_tag,
|
||||||
@ -76,44 +77,47 @@ def process_app_deployment_request(
|
|||||||
logger.log(f"Retrieved app record {app_deployment_request.attributes.application}")
|
logger.log(f"Retrieved app record {app_deployment_request.attributes.application}")
|
||||||
|
|
||||||
# 2. determine dns
|
# 2. determine dns
|
||||||
requested_name = hostname_for_deployment_request(app_deployment_request, laconic)
|
requested_names = hostnames_for_deployment_request(app_deployment_request, laconic)
|
||||||
logger.log(f"Determined requested name: {requested_name}")
|
logger.log(f"Determined requested name(s): {','.join(str(x) for x in requested_names)}")
|
||||||
|
|
||||||
if "." in requested_name:
|
fqdns = []
|
||||||
if "allow" == fqdn_policy or "preexisting" == fqdn_policy:
|
for requested_name in requested_names:
|
||||||
fqdn = requested_name
|
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:
|
else:
|
||||||
raise Exception(
|
fqdns.append(f"{requested_name}.{default_dns_suffix}")
|
||||||
f"{requested_name} is invalid: only unqualified hostnames are allowed."
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
fqdn = f"{requested_name}.{default_dns_suffix}"
|
|
||||||
|
|
||||||
# Normalize case (just in case)
|
# Normalize case (just in case)
|
||||||
fqdn = fqdn.lower()
|
fqdns = [fqdn.lower() for fqdn in fqdns]
|
||||||
|
|
||||||
# 3. check ownership of existing dnsrecord vs this request
|
# 3. check ownership of existing dnsrecord(s) vs this request
|
||||||
dns_lrn = f"{dns_record_namespace}/{fqdn}"
|
for fqdn in fqdns:
|
||||||
dns_record = laconic.get_record(dns_lrn)
|
dns_lrn = f"{dns_record_namespace}/{fqdn}"
|
||||||
if dns_record:
|
dns_record = laconic.get_record(dns_lrn)
|
||||||
matched_owner = match_owner(app_deployment_request, dns_record)
|
if dns_record:
|
||||||
if not matched_owner and dns_record.attributes.request:
|
matched_owner = match_owner(app_deployment_request, dns_record)
|
||||||
matched_owner = match_owner(
|
if not matched_owner and dns_record.attributes.request:
|
||||||
app_deployment_request,
|
matched_owner = match_owner(
|
||||||
laconic.get_record(dns_record.attributes.request, require=True),
|
app_deployment_request,
|
||||||
)
|
laconic.get_record(dns_record.attributes.request, require=True),
|
||||||
|
)
|
||||||
|
|
||||||
if matched_owner:
|
if matched_owner:
|
||||||
logger.log(f"Matched DnsRecord ownership: {matched_owner}")
|
logger.log(f"Matched DnsRecord ownership for {fqdn}: {matched_owner}")
|
||||||
else:
|
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(
|
raise Exception(
|
||||||
"Unable to confirm ownership of DnsRecord %s for request %s"
|
f"No pre-existing DnsRecord {dns_lrn} could be found for request {app_deployment_request.id}."
|
||||||
% (dns_lrn, 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
|
# 4. get build and runtime config from request
|
||||||
env = {}
|
env = {}
|
||||||
@ -145,25 +149,39 @@ def process_app_deployment_request(
|
|||||||
|
|
||||||
# 5. determine new or existing deployment
|
# 5. determine new or existing deployment
|
||||||
# a. check for deployment lrn
|
# 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:
|
if app_deployment_request.attributes.deployment:
|
||||||
app_deployment_lrn = app_deployment_request.attributes.deployment
|
app_deployment_lrns = [app_deployment_request.attributes.deployment]
|
||||||
if not app_deployment_lrn.startswith(deployment_record_namespace):
|
if not app_deployment_lrns[0].startswith(deployment_record_namespace):
|
||||||
raise Exception(
|
raise Exception(
|
||||||
"Deployment LRN %s is not in a supported namespace"
|
"Deployment LRN %s is not in a supported namespace"
|
||||||
% app_deployment_request.attributes.deployment
|
% app_deployment_request.attributes.deployment
|
||||||
)
|
)
|
||||||
|
|
||||||
deployment_record = laconic.get_record(app_deployment_lrn)
|
# Target deployment dir and existing deployment dir
|
||||||
deployment_dir = os.path.join(deployment_parent_dir, fqdn)
|
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
|
# 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
|
# TODO: implement support to derive this transparently from the already-unique deployment id
|
||||||
unique_deployment_id = hashlib.md5(fqdn.encode()).hexdigest()[:16]
|
unique_deployment_id = hashlib.md5(fqdns[-1].encode()).hexdigest()[:16]
|
||||||
deployment_config_file = os.path.join(deployment_dir, "config.env")
|
deployment_config_file = os.path.join(existing_deployment_dir, "config.env")
|
||||||
deployment_container_tag = "laconic-webapp/%s:local" % unique_deployment_id
|
deployment_container_tag = "laconic-webapp/%s:local" % unique_deployment_id
|
||||||
app_image_shared_tag = f"laconic-webapp/{app.id}:local"
|
app_image_shared_tag = f"laconic-webapp/{app.id}:local"
|
||||||
# b. check for deployment directory (create if necessary)
|
# 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:
|
if deployment_record:
|
||||||
raise Exception(
|
raise Exception(
|
||||||
"Deployment record %s exists, but not deployment dir %s. Please remove name."
|
"Deployment record %s exists, but not deployment dir %s. Please remove name."
|
||||||
@ -172,11 +190,12 @@ def process_app_deployment_request(
|
|||||||
logger.log(
|
logger.log(
|
||||||
f"Creating webapp deployment in: {deployment_dir} with container id: {deployment_container_tag}"
|
f"Creating webapp deployment in: {deployment_dir} with container id: {deployment_container_tag}"
|
||||||
)
|
)
|
||||||
|
# TODO: Pass URLs for all fqdns
|
||||||
deploy_webapp.create_deployment(
|
deploy_webapp.create_deployment(
|
||||||
ctx,
|
ctx,
|
||||||
deployment_dir,
|
deployment_dir,
|
||||||
deployment_container_tag,
|
deployment_container_tag,
|
||||||
f"https://{fqdn}",
|
f"https://{fqdns[-1]}",
|
||||||
kube_config,
|
kube_config,
|
||||||
image_registry,
|
image_registry,
|
||||||
env_filename,
|
env_filename,
|
||||||
@ -184,6 +203,16 @@ def process_app_deployment_request(
|
|||||||
elif env_filename:
|
elif env_filename:
|
||||||
shutil.copyfile(env_filename, deployment_config_file)
|
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
|
needs_k8s_deploy = False
|
||||||
if force_rebuild:
|
if force_rebuild:
|
||||||
logger.log(
|
logger.log(
|
||||||
@ -233,17 +262,24 @@ def process_app_deployment_request(
|
|||||||
logger.log("Requested app is already deployed, skipping build and image push")
|
logger.log("Requested app is already deployed, skipping build and image push")
|
||||||
|
|
||||||
# 7. update config (if needed)
|
# 7. update config (if needed)
|
||||||
|
# TODO: Also check if domains set has changed
|
||||||
if (
|
if (
|
||||||
not deployment_record
|
not deployment_record
|
||||||
or file_hash(deployment_config_file) != deployment_record.attributes.meta.config
|
or file_hash(deployment_config_file) != deployment_record.attributes.meta.config
|
||||||
):
|
):
|
||||||
needs_k8s_deploy = True
|
needs_k8s_deploy = True
|
||||||
|
|
||||||
# 8. update k8s deployment
|
# TODO: 8. delete unused names
|
||||||
|
# if deployment_record:
|
||||||
|
# ...
|
||||||
|
|
||||||
|
# 9. update k8s deployment
|
||||||
if needs_k8s_deploy:
|
if needs_k8s_deploy:
|
||||||
deploy_to_k8s(deployment_record, deployment_dir, recreate_on_deploy, logger)
|
deploy_to_k8s(deployment_record, deployment_dir, recreate_on_deploy, logger)
|
||||||
|
|
||||||
logger.log("Publishing deployment to registry.")
|
logger.log("Publishing deployment to registry.")
|
||||||
|
# TODO: Publish multiple DNS records
|
||||||
|
# TODO: Point all app_deployment_lrns
|
||||||
publish_deployment(
|
publish_deployment(
|
||||||
laconic,
|
laconic,
|
||||||
app,
|
app,
|
||||||
@ -517,21 +553,26 @@ def command( # noqa: C901
|
|||||||
result = "ERROR"
|
result = "ERROR"
|
||||||
continue
|
continue
|
||||||
|
|
||||||
requested_name = r.attributes.dns
|
requested_names = get_requested_names(r)
|
||||||
if not requested_name:
|
if len(requested_names) == 0:
|
||||||
requested_name = generate_hostname_for_app(app)
|
requested_names = [generate_hostname_for_app(app)]
|
||||||
main_logger.log(
|
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 (
|
# Skip request if any of the names is superseded
|
||||||
requested_name in skipped_by_name
|
for requested_name in requested_names:
|
||||||
or requested_name in requests_by_name
|
if (
|
||||||
):
|
requested_name in skipped_by_name
|
||||||
main_logger.log(
|
or requested_name in requests_by_name
|
||||||
"Ignoring request %s, it has been superseded." % r.id
|
):
|
||||||
)
|
main_logger.log(
|
||||||
result = "SKIP"
|
"Ignoring request %s, it has been superseded." % r.id
|
||||||
|
)
|
||||||
|
result = "SKIP"
|
||||||
|
break
|
||||||
|
|
||||||
|
if result == "SKIP":
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if skip_by_tag(r, include_tags, exclude_tags):
|
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)"
|
"Skipping request %s, filtered by tag (include %s, exclude %s, present %s)"
|
||||||
% (r.id, include_tags, exclude_tags, r.attributes.tags)
|
% (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"
|
result = "SKIP"
|
||||||
continue
|
continue
|
||||||
|
|
||||||
main_logger.log(
|
main_logger.log(
|
||||||
"Found pending request %s to run application %s on %s."
|
"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:
|
except Exception as e:
|
||||||
result = "ERROR"
|
result = "ERROR"
|
||||||
main_logger.log(f"ERROR examining request {r.id}: " + str(e))
|
main_logger.log(f"ERROR examining request {r.id}: " + str(e))
|
||||||
|
@ -78,6 +78,7 @@ def command( # noqa: C901
|
|||||||
"paymentAddress": payment_address,
|
"paymentAddress": payment_address,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
# TODO: Add deployerVersion 1.0.0
|
||||||
|
|
||||||
if min_required_payment:
|
if min_required_payment:
|
||||||
webapp_deployer_record["record"][
|
webapp_deployer_record["record"][
|
||||||
|
@ -761,18 +761,29 @@ def publish_deployment(
|
|||||||
deployment_id = laconic.publish(new_deployment_record, [deployment_lrn])
|
deployment_id = laconic.publish(new_deployment_record, [deployment_lrn])
|
||||||
return {"dns": dns_id, "deployment": deployment_id}
|
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):
|
def hostnames_for_deployment_request(app_deployment_request, laconic):
|
||||||
dns_name = app_deployment_request.attributes.dns
|
requested_names = get_requested_names(app_deployment_request)
|
||||||
if not dns_name:
|
|
||||||
|
if len(requested_names) == 0:
|
||||||
app = laconic.get_record(
|
app = laconic.get_record(
|
||||||
app_deployment_request.attributes.application, require=True
|
app_deployment_request.attributes.application, require=True
|
||||||
)
|
)
|
||||||
dns_name = generate_hostname_for_app(app)
|
return [generate_hostname_for_app(app)]
|
||||||
elif dns_name.startswith("lrn://"):
|
|
||||||
record = laconic.get_record(dns_name, require=True)
|
dns_names = []
|
||||||
dns_name = record.attributes.name
|
for requested_name in requested_names:
|
||||||
return dns_name
|
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):
|
def generate_hostname_for_app(app):
|
||||||
|
Loading…
Reference in New Issue
Block a user