Compare commits

..

15 Commits

6 changed files with 317 additions and 183 deletions

View File

@ -114,22 +114,27 @@ class ClusterInfo:
nodeports.append(service) nodeports.append(service)
return nodeports 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 # No ingress for a deployment that has no http-proxy defined, for now
http_proxy_info_list = self.spec.get_http_proxy() http_proxy_info_list = self.spec.get_http_proxy()
ingress = None if not http_proxy_info_list:
if http_proxy_info_list: return None
# TODO: handle multiple definitions
http_proxy_info = http_proxy_info_list[0] tls = [] if use_tls else None
rules = []
for http_proxy_info in http_proxy_info_list:
if opts.o.debug: if opts.o.debug:
print(f"http-proxy: {http_proxy_info}") print(f"http-proxy: {http_proxy_info}")
# TODO: good enough parsing for webapp deployment for now # TODO: good enough parsing for webapp deployment for now
host_name = http_proxy_info["host-name"] host_name = http_proxy_info["host-name"]
rules = [] certificate = certificate_by_host[host_name] if host_name in certificate_by_host else None
tls = [client.V1IngressTLS(
if use_tls:
tls.append(client.V1IngressTLS(
hosts=certificate["spec"]["dnsNames"] if certificate else [host_name], hosts=certificate["spec"]["dnsNames"] if certificate else [host_name],
secret_name=certificate["spec"]["secretName"] if certificate else f"{self.app_name}-tls" secret_name=certificate["spec"]["secretName"] if certificate else f"{self.app_name}-{host_name}-tls"
)] if use_tls else None ))
paths = [] paths = []
for route in http_proxy_info["routes"]: for route in http_proxy_info["routes"]:
path = route["path"] path = route["path"]

View File

@ -227,15 +227,18 @@ class K8sDeployer(Deployer):
self._create_volume_data() self._create_volume_data()
self._create_deployment() 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) # 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() use_tls = http_proxy_info_list and not self.is_kind()
certificate = self._find_certificate_for_host_name(http_proxy_info[0]["host-name"]) if use_tls else None certificate_by_host = {}
if opts.o.debug: if use_tls:
if certificate: 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}") 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, certificate_by_host=certificate_by_host)
if ingress: if ingress:
if opts.o.debug: if opts.o.debug:
print(f"Sending this ingress: {ingress}") print(f"Sending this ingress: {ingress}")
@ -381,36 +384,46 @@ class K8sDeployer(Deployer):
if not pods: if not pods:
return return
hostname = "?" tls_by_host = {}
ip = "?"
tls = "?"
try: try:
ingress = self.networking_api.read_namespaced_ingress(namespace=self.k8s_namespace, ingress = self.networking_api.read_namespaced_ingress(namespace=self.k8s_namespace,
name=self.cluster_info.get_ingress().metadata.name) name=self.cluster_info.get_ingress().metadata.name)
ip = ingress.status.load_balancer.ingress[0].ip
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( cert = self.custom_obj_api.get_namespaced_custom_object(
group="cert-manager.io", group="cert-manager.io",
version="v1", version="v1",
namespace=self.k8s_namespace, namespace=self.k8s_namespace,
plural="certificates", plural="certificates",
name=ingress.spec.tls[0].secret_name name=tls_spec.secret_name
) )
hostname = ingress.spec.rules[0].host
ip = ingress.status.load_balancer.ingress[0].ip
tls = "notBefore: %s; notAfter: %s; names: %s" % ( tls = "notBefore: %s; notAfter: %s; names: %s" % (
cert["status"]["notBefore"], cert["status"]["notAfter"], ingress.spec.tls[0].hosts cert["status"]["notBefore"], cert["status"]["notAfter"], tls_spec.hosts
) )
tls_by_host[hostname] = tls
else:
tls_by_host[hostname] = None
except: # noqa: E722 except: # noqa: E722
pass pass
print("Ingress:") print("Ingress:")
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("\tHostname:", hostname)
print("\tIP:", ip) print("\tIP:", ip)
print("\tTLS:", tls) print("\tTLS:", tls)
print("") print("")
print("Pods:")
print("Pods:")
for p in pods: for p in pods:
if p.metadata.deletion_timestamp: if p.metadata.deletion_timestamp:
print(f"\t{p.metadata.namespace}/{p.metadata.name}: Terminating ({p.metadata.deletion_timestamp})") print(f"\t{p.metadata.namespace}/{p.metadata.name}: Terminating ({p.metadata.deletion_timestamp})")

View File

@ -13,12 +13,16 @@
# You should have received a copy of the GNU Affero General Public License # You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http:#www.gnu.org/licenses/>. # along with this program. If not, see <http:#www.gnu.org/licenses/>.
import shutil
from typing import List
import click import click
import os import os
import yaml
from pathlib import Path from pathlib import Path
from urllib.parse import urlparse from urllib.parse import urlparse
from tempfile import NamedTemporaryFile from tempfile import NamedTemporaryFile
from stack_orchestrator import constants
from stack_orchestrator.util import error_exit, global_options2 from stack_orchestrator.util import error_exit, global_options2
from stack_orchestrator.deploy.deployment_create import init_operation, create_operation 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 import create_deploy_context
@ -36,25 +40,38 @@ def _fixup_container_tag(deployment_dir: str, image: str):
wfile.write(contents) wfile.write(contents)
def _fixup_url_spec(spec_file_name: str, url: str): def _fixup_url_spec(spec_file_name: str, urls: List[str]):
# url is like: https://example.com/path
parsed_url = urlparse(url)
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) spec_file_path = Path(spec_file_name)
with open(spec_file_path) as rfile:
contents = rfile.read() # Load existing spec
contents = contents + http_proxy_spec with open(spec_file_path, "r") as file:
with open(spec_file_path, "w") as wfile: spec_data = yaml.safe_load(file) or {}
wfile.write(contents)
# Build new http-proxy entries
http_proxy_entries = []
for url in urls:
parsed_url = urlparse(url)
http_proxy_entries.append({
"host-name": parsed_url.hostname,
"routes": [
{
"path": parsed_url.path if parsed_url.path else "/",
"proxy-to": "webapp:80"
}
]
})
# Update the spec
if "network" not in spec_data:
spec_data["network"] = {}
spec_data["network"]["http-proxy"] = http_proxy_entries
# 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, 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: # 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) # --config (eqivalent of the contents of my-config.env)
@ -86,7 +103,7 @@ def create_deployment(ctx, deployment_dir, image, url, kube_config, image_regist
None None
) )
# Add the TLS and DNS spec # Add the TLS and DNS spec
_fixup_url_spec(spec_file_name, url) _fixup_url_spec(spec_file_name, urls)
create_operation( create_operation(
deploy_command_context, deploy_command_context,
spec_file_name, spec_file_name,
@ -99,6 +116,20 @@ def create_deployment(ctx, deployment_dir, image, url, kube_config, image_regist
os.remove(spec_file_name) 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.group()
@click.pass_context @click.pass_context
def command(ctx): def command(ctx):
@ -120,4 +151,4 @@ def command(ctx):
def create(ctx, deployment_dir, image, url, kube_config, image_registry, env_file): 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)

View File

@ -20,6 +20,7 @@ import shutil
import sys import sys
import tempfile import tempfile
import time import time
from typing import List
import uuid import uuid
import yaml import yaml
@ -38,7 +39,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,
@ -47,7 +49,7 @@ from stack_orchestrator.deploy.webapp.util import (
) )
def process_app_deployment_request( def process_app_deployment_request( # noqa
ctx, ctx,
laconic: LaconicRegistryClient, laconic: LaconicRegistryClient,
app_deployment_request, app_deployment_request,
@ -76,25 +78,34 @@ 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)}")
fqdns = []
for requested_name in requested_names:
if "." in requested_name: if "." in requested_name:
if "allow" == fqdn_policy or "preexisting" == fqdn_policy: if "allow" == fqdn_policy or "preexisting" == fqdn_policy:
fqdn = requested_name fqdns.append(requested_name)
else: else:
raise Exception( raise Exception(
f"{requested_name} is invalid: only unqualified hostnames are allowed." f"{requested_name} is invalid: only unqualified hostnames are allowed."
) )
else: else:
fqdn = f"{requested_name}.{default_dns_suffix}" fqdns.append(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_lrns = []
existing_dns_records_by_lrns = {}
for fqdn in fqdns:
dns_lrn = f"{dns_record_namespace}/{fqdn}" dns_lrn = f"{dns_record_namespace}/{fqdn}"
dns_lrns.append(dns_lrn)
dns_record = laconic.get_record(dns_lrn) dns_record = laconic.get_record(dns_lrn)
existing_dns_records_by_lrns[dns_lrn] = dns_record
if dns_record: if dns_record:
matched_owner = match_owner(app_deployment_request, dns_record) matched_owner = match_owner(app_deployment_request, dns_record)
if not matched_owner and dns_record.attributes.request: if not matched_owner and dns_record.attributes.request:
@ -104,7 +115,7 @@ def process_app_deployment_request(
) )
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( raise Exception(
"Unable to confirm ownership of DnsRecord %s for request %s" "Unable to confirm ownership of DnsRecord %s for request %s"
@ -145,30 +156,49 @@ 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
) )
# Target deployment dir
deployment_dir = os.path.join(deployment_parent_dir, fqdns[-1])
# 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) deployment_record = laconic.get_record(app_deployment_lrn)
deployment_dir = os.path.join(deployment_parent_dir, fqdn) 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)
)
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
# 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_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:
raise Exception(
"Deployment record %s exists, but not deployment dir %s. Please remove name."
% (app_deployment_lrn, deployment_dir)
)
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}"
) )
@ -176,13 +206,23 @@ def process_app_deployment_request(
ctx, ctx,
deployment_dir, deployment_dir,
deployment_container_tag, deployment_container_tag,
f"https://{fqdn}", [f"https://{fqdn}" for fqdn in fqdns],
kube_config, kube_config,
image_registry, image_registry,
env_filename, env_filename,
) )
elif env_filename: else:
shutil.copyfile(env_filename, deployment_config_file) # Rename deployment dir according to new request (last fqdn from given dns)
os.rename(existing_deployment_dir, deployment_dir)
# 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:
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 needs_k8s_deploy = False
if force_rebuild: if force_rebuild:
@ -232,10 +272,11 @@ def process_app_deployment_request(
else: else:
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. restart deployment on config or url spec change
if ( if (
not deployment_record 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 needs_k8s_deploy = True
@ -248,9 +289,10 @@ def process_app_deployment_request(
laconic, laconic,
app, app,
deployment_record, deployment_record,
app_deployment_lrn, app_deployment_lrns,
dns_record, existing_dns_records_by_lrns,
dns_lrn, dns_lrns,
dns_record_namespace,
deployment_dir, deployment_dir,
dns_value, dns_value,
app_deployment_request, app_deployment_request,
@ -258,6 +300,17 @@ def process_app_deployment_request(
logger, logger,
) )
logger.log("Publication complete.") 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") logger.log("END - process_app_deployment_request")
@ -517,13 +570,15 @@ 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)
) )
# Skip request if any of the names is superseded
for requested_name in requested_names:
if ( if (
requested_name in skipped_by_name requested_name in skipped_by_name
or requested_name in requests_by_name or requested_name in requests_by_name
@ -532,6 +587,9 @@ def command( # noqa: C901
"Ignoring request %s, it has been superseded." % r.id "Ignoring request %s, it has been superseded." % r.id
) )
result = "SKIP" 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 +597,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)
) )
for requested_name in requested_names:
skipped_by_name[requested_name] = r 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))

View File

@ -76,6 +76,7 @@ def command( # noqa: C901
"name": hostname, "name": hostname,
"publicKey": pub_key, "publicKey": pub_key,
"paymentAddress": payment_address, "paymentAddress": payment_address,
"deployerVersion": "1.0.0",
} }
} }

View File

@ -21,6 +21,7 @@ import random
import subprocess import subprocess
import sys import sys
import tempfile import tempfile
from typing import List
import uuid import uuid
import yaml import yaml
@ -685,9 +686,10 @@ def publish_deployment(
laconic: LaconicRegistryClient, laconic: LaconicRegistryClient,
app_record, app_record,
deploy_record, deploy_record,
deployment_lrn, deployment_lrns,
dns_record, existing_dns_records_by_lrns,
dns_lrn, dns_lrns: List[str],
dns_record_namespace,
deployment_dir, deployment_dir,
dns_value=None, dns_value=None,
app_deployment_request=None, app_deployment_request=None,
@ -701,14 +703,15 @@ def publish_deployment(
int(deploy_record.attributes.version.split(".")[-1]) + 1 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]
if not dns_record: if not dns_record:
dns_ver = "0.0.1" dns_ver = "0.0.1"
else: else:
dns_ver = "0.0.%d" % (int(dns_record.attributes.version.split(".")[-1]) + 1) dns_ver = "0.0.%d" % (int(dns_record.attributes.version.split(".")[-1]) + 1)
spec = yaml.full_load(open(os.path.join(deployment_dir, "spec.yml"))) fqdn = dns_lrn.removeprefix(f"{dns_record_namespace}/")
fqdn = spec["network"]["http-proxy"][0]["host-name"]
uniq = uuid.uuid4() uniq = uuid.uuid4()
new_dns_record = { new_dns_record = {
@ -728,15 +731,20 @@ def publish_deployment(
if logger: if logger:
logger.log("Publishing DnsRecord.") logger.log("Publishing DnsRecord.")
dns_id = laconic.publish(new_dns_record, [dns_lrn]) 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")))
last_fqdn = spec["network"]["http-proxy"][-1]["host-name"]
last_dns_id = dns_ids[-1]
new_deployment_record = { new_deployment_record = {
"record": { "record": {
"type": "ApplicationDeploymentRecord", "type": "ApplicationDeploymentRecord",
"version": deploy_ver, "version": deploy_ver,
"url": f"https://{fqdn}", "url": f"https://{last_fqdn}",
"name": app_record.attributes.name, "name": app_record.attributes.name,
"application": app_record.id, "application": app_record.id,
"dns": dns_id, "dns": last_dns_id,
"meta": { "meta": {
"config": file_hash(os.path.join(deployment_dir, "config.env")), "config": file_hash(os.path.join(deployment_dir, "config.env")),
"so": uniq.hex, "so": uniq.hex,
@ -758,21 +766,34 @@ def publish_deployment(
if logger: if logger:
logger.log("Publishing ApplicationDeploymentRecord.") logger.log("Publishing ApplicationDeploymentRecord.")
deployment_id = laconic.publish(new_deployment_record, [deployment_lrn]) deployment_id = laconic.publish(new_deployment_record, deployment_lrns)
return {"dns": dns_id, "deployment": deployment_id} return {"dns": dns_ids, "deployment": deployment_id}
def hostname_for_deployment_request(app_deployment_request, laconic): def get_requested_names(app_deployment_request):
dns_name = app_deployment_request.attributes.dns request_dns = app_deployment_request.attributes.dns
if not dns_name: 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)
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://"):
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) record = laconic.get_record(dns_name, require=True)
dns_name = record.attributes.name dns_name = record.attributes.name
return dns_name
dns_names.append(dns_name)
return dns_names
def generate_hostname_for_app(app): def generate_hostname_for_app(app):