WIP: telackey/laconicdv1 #912
@ -34,5 +34,6 @@ volumes_key = "volumes"
|
||||
security_key = "security"
|
||||
annotations_key = "annotations"
|
||||
labels_key = "labels"
|
||||
replicas_key = "replicas"
|
||||
kind_config_filename = "kind-config.yml"
|
||||
kube_config_filename = "kubeconfig.yml"
|
||||
|
@ -78,28 +78,38 @@ class ClusterInfo:
|
||||
if (opts.o.debug):
|
||||
print(f"Env vars: {self.environment_variables.map}")
|
||||
|
||||
def get_nodeport(self):
|
||||
def get_nodeports(self):
|
||||
nodeports = []
|
||||
for pod_name in self.parsed_pod_yaml_map:
|
||||
pod = self.parsed_pod_yaml_map[pod_name]
|
||||
services = pod["services"]
|
||||
for service_name in services:
|
||||
service_info = services[service_name]
|
||||
if "ports" in service_info:
|
||||
port = int(service_info["ports"][0])
|
||||
if opts.o.debug:
|
||||
print(f"service port: {port}")
|
||||
service = client.V1Service(
|
||||
metadata=client.V1ObjectMeta(name=f"{self.app_name}-nodeport"),
|
||||
spec=client.V1ServiceSpec(
|
||||
type="NodePort",
|
||||
ports=[client.V1ServicePort(
|
||||
port=port,
|
||||
target_port=port
|
||||
)],
|
||||
selector={"app": self.app_name}
|
||||
)
|
||||
)
|
||||
return service
|
||||
for raw_port in service_info["ports"]:
|
||||
if opts.o.debug:
|
||||
print(f"service port: {raw_port}")
|
||||
if ":" in raw_port:
|
||||
parts = raw_port.split(":", 2)
|
||||
node_port = int(parts[0])
|
||||
pod_port = int(parts[1])
|
||||
else:
|
||||
node_port = None
|
||||
pod_port = int(raw_port)
|
||||
service = client.V1Service(
|
||||
metadata=client.V1ObjectMeta(name=f"{self.app_name}-nodeport-{pod_port}"),
|
||||
spec=client.V1ServiceSpec(
|
||||
type="NodePort",
|
||||
ports=[client.V1ServicePort(
|
||||
port=pod_port,
|
||||
target_port=pod_port,
|
||||
node_port=node_port
|
||||
)],
|
||||
selector={"app": self.app_name}
|
||||
)
|
||||
)
|
||||
nodeports.append(service)
|
||||
return nodeports
|
||||
|
||||
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
|
||||
@ -373,9 +383,12 @@ class ClusterInfo:
|
||||
spec=client.V1PodSpec(containers=containers, image_pull_secrets=image_pull_secrets, volumes=volumes),
|
||||
)
|
||||
spec = client.V1DeploymentSpec(
|
||||
replicas=1, template=template, selector={
|
||||
replicas=self.spec.get_replicas(),
|
||||
template=template, selector={
|
||||
"matchLabels":
|
||||
{"app": self.app_name}})
|
||||
{"app": self.app_name}
|
||||
}
|
||||
)
|
||||
|
||||
deployment = client.V1Deployment(
|
||||
api_version="apps/v1",
|
||||
|
@ -246,8 +246,8 @@ class K8sDeployer(Deployer):
|
||||
if opts.o.debug:
|
||||
print("No ingress configured")
|
||||
|
||||
nodeport: client.V1Service = self.cluster_info.get_nodeport()
|
||||
if nodeport:
|
||||
nodeports: List[client.V1Service] = self.cluster_info.get_nodeports()
|
||||
for nodeport in nodeports:
|
||||
if opts.o.debug:
|
||||
print(f"Sending this nodeport: {nodeport}")
|
||||
if not opts.o.dry_run:
|
||||
@ -342,10 +342,10 @@ class K8sDeployer(Deployer):
|
||||
if opts.o.debug:
|
||||
print("No ingress to delete")
|
||||
|
||||
nodeport: client.V1Service = self.cluster_info.get_nodeport()
|
||||
if nodeport:
|
||||
nodeports: List[client.V1Service] = self.cluster_info.get_nodeports()
|
||||
for nodeport in nodeports:
|
||||
if opts.o.debug:
|
||||
print(f"Deleting this nodeport: {ingress}")
|
||||
print(f"Deleting this nodeport: {nodeport}")
|
||||
try:
|
||||
self.core_api.delete_namespaced_service(
|
||||
namespace=self.k8s_namespace,
|
||||
|
@ -117,6 +117,9 @@ class Spec:
|
||||
def get_annotations(self):
|
||||
return self.obj.get(constants.annotations_key, {})
|
||||
|
||||
def get_replicas(self):
|
||||
return self.obj.get(constants.replicas_key, 1)
|
||||
|
||||
def get_labels(self):
|
||||
return self.obj.get(constants.labels_key, {})
|
||||
|
||||
|
@ -45,6 +45,7 @@ def process_app_deployment_request(
|
||||
image_registry,
|
||||
force_rebuild,
|
||||
fqdn_policy,
|
||||
recreate_on_deploy,
|
||||
logger
|
||||
):
|
||||
logger.log("BEGIN - process_app_deployment_request")
|
||||
@ -66,8 +67,8 @@ def process_app_deployment_request(
|
||||
fqdn = f"{requested_name}.{default_dns_suffix}"
|
||||
|
||||
# 3. check ownership of existing dnsrecord vs this request
|
||||
dns_lrn = f"{dns_record_namespace}/{fqdn}"
|
||||
dns_record = laconic.get_record(dns_lrn)
|
||||
dns_crn = f"{dns_record_namespace}/{fqdn}"
|
||||
dns_record = laconic.get_record(dns_crn)
|
||||
if dns_record:
|
||||
matched_owner = match_owner(app_deployment_request, dns_record)
|
||||
if not matched_owner and dns_record.attributes.request:
|
||||
@ -77,9 +78,9 @@ def process_app_deployment_request(
|
||||
logger.log(f"Matched DnsRecord ownership: {matched_owner}")
|
||||
else:
|
||||
raise Exception("Unable to confirm ownership of DnsRecord %s for request %s" %
|
||||
(dns_lrn, app_deployment_request.id))
|
||||
(dns_crn, 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}.")
|
||||
raise Exception(f"No pre-existing DnsRecord {dns_crn} could be found for request {app_deployment_request.id}.")
|
||||
|
||||
# 4. get build and runtime config from request
|
||||
env_filename = None
|
||||
@ -90,14 +91,14 @@ def process_app_deployment_request(
|
||||
file.write("%s=%s\n" % (k, shlex.quote(str(v))))
|
||||
|
||||
# 5. determine new or existing deployment
|
||||
# a. check for deployment lrn
|
||||
app_deployment_lrn = f"{deployment_record_namespace}/{fqdn}"
|
||||
# a. check for deployment crn
|
||||
app_deployment_crn = f"{deployment_record_namespace}/{fqdn}"
|
||||
if app_deployment_request.attributes.deployment:
|
||||
app_deployment_lrn = app_deployment_request.attributes.deployment
|
||||
if not app_deployment_lrn.startswith(deployment_record_namespace):
|
||||
app_deployment_crn = app_deployment_request.attributes.deployment
|
||||
if not app_deployment_crn.startswith(deployment_record_namespace):
|
||||
raise Exception("Deployment CRN %s is not in a supported namespace" % app_deployment_request.attributes.deployment)
|
||||
|
||||
deployment_record = laconic.get_record(app_deployment_lrn)
|
||||
deployment_record = laconic.get_record(app_deployment_crn)
|
||||
deployment_dir = os.path.join(deployment_parent_dir, fqdn)
|
||||
# At present we use this to generate a unique but stable ID for the app's host container
|
||||
# TODO: implement support to derive this transparently from the already-unique deployment id
|
||||
@ -109,7 +110,7 @@ def process_app_deployment_request(
|
||||
if not os.path.exists(deployment_dir):
|
||||
if deployment_record:
|
||||
raise Exception("Deployment record %s exists, but not deployment dir %s. Please remove name." %
|
||||
(app_deployment_lrn, deployment_dir))
|
||||
(app_deployment_crn, deployment_dir))
|
||||
logger.log(f"Creating webapp deployment in: {deployment_dir} with container id: {deployment_container_tag}")
|
||||
deploy_webapp.create_deployment(ctx, deployment_dir, deployment_container_tag,
|
||||
f"https://{fqdn}", kube_config, image_registry, env_filename)
|
||||
@ -165,6 +166,7 @@ def process_app_deployment_request(
|
||||
deploy_to_k8s(
|
||||
deployment_record,
|
||||
deployment_dir,
|
||||
recreate_on_deploy,
|
||||
logger
|
||||
)
|
||||
|
||||
@ -173,9 +175,9 @@ def process_app_deployment_request(
|
||||
laconic,
|
||||
app,
|
||||
deployment_record,
|
||||
app_deployment_lrn,
|
||||
app_deployment_crn,
|
||||
dns_record,
|
||||
dns_lrn,
|
||||
dns_crn,
|
||||
deployment_dir,
|
||||
app_deployment_request,
|
||||
logger
|
||||
@ -214,18 +216,19 @@ def dump_known_requests(filename, requests, status="SEEN"):
|
||||
@click.option("--only-update-state", help="Only update the state file, don't process any requests anything.", is_flag=True)
|
||||
@click.option("--dns-suffix", help="DNS domain to use eg, laconic.servesthe.world")
|
||||
@click.option("--fqdn-policy", help="How to handle requests with an FQDN: prohibit, allow, preexisting", default="prohibit")
|
||||
@click.option("--record-namespace-dns", help="eg, lrn://laconic/dns")
|
||||
@click.option("--record-namespace-deployments", help="eg, lrn://laconic/deployments")
|
||||
@click.option("--record-namespace-dns", help="eg, crn://laconic/dns")
|
||||
@click.option("--record-namespace-deployments", help="eg, crn://laconic/deployments")
|
||||
@click.option("--dry-run", help="Don't do anything, just report what would be done.", is_flag=True)
|
||||
@click.option("--include-tags", help="Only include requests with matching tags (comma-separated).", default="")
|
||||
@click.option("--exclude-tags", help="Exclude requests with matching tags (comma-separated).", default="")
|
||||
@click.option("--force-rebuild", help="Rebuild even if the image already exists.", is_flag=True)
|
||||
@click.option("--recreate-on-deploy", help="Remove and recreate deployments instead of updating them.", is_flag=True)
|
||||
@click.option("--log-dir", help="Output build/deployment logs to directory.", default=None)
|
||||
@click.pass_context
|
||||
def command(ctx, kube_config, laconic_config, image_registry, deployment_parent_dir, # noqa: C901
|
||||
request_id, discover, state_file, only_update_state,
|
||||
dns_suffix, fqdn_policy, record_namespace_dns, record_namespace_deployments, dry_run,
|
||||
include_tags, exclude_tags, force_rebuild, log_dir):
|
||||
include_tags, exclude_tags, force_rebuild, recreate_on_deploy, log_dir):
|
||||
if request_id and discover:
|
||||
print("Cannot specify both --request-id and --discover", file=sys.stderr)
|
||||
sys.exit(2)
|
||||
@ -365,6 +368,7 @@ def command(ctx, kube_config, laconic_config, image_registry, deployment_parent_
|
||||
image_registry,
|
||||
force_rebuild,
|
||||
fqdn_policy,
|
||||
recreate_on_deploy,
|
||||
logger
|
||||
)
|
||||
status = "DEPLOYED"
|
||||
|
@ -94,7 +94,7 @@ class LaconicRegistryClient:
|
||||
)
|
||||
|
||||
def list_records(self, criteria={}, all=False):
|
||||
args = ["laconic", "-c", self.config_file, "registry", "record", "list"]
|
||||
args = ["laconic", "-c", self.config_file, "cns", "record", "list"]
|
||||
|
||||
if all:
|
||||
args.append("--all")
|
||||
@ -112,13 +112,13 @@ class LaconicRegistryClient:
|
||||
|
||||
return results
|
||||
|
||||
def is_lrn(self, name_or_id: str):
|
||||
def is_crn(self, name_or_id: str):
|
||||
if name_or_id:
|
||||
return str(name_or_id).startswith("lrn://")
|
||||
return str(name_or_id).startswith("crn://")
|
||||
return False
|
||||
|
||||
def is_id(self, name_or_id: str):
|
||||
return not self.is_lrn(name_or_id)
|
||||
return not self.is_crn(name_or_id)
|
||||
|
||||
def _add_to_cache(self, records):
|
||||
if not records:
|
||||
@ -127,8 +127,8 @@ class LaconicRegistryClient:
|
||||
for p in records:
|
||||
self.cache["name_or_id"][p.id] = p
|
||||
if p.names:
|
||||
for lrn in p.names:
|
||||
self.cache["name_or_id"][lrn] = p
|
||||
for crn in p.names:
|
||||
self.cache["name_or_id"][crn] = p
|
||||
if p.attributes.type not in self.cache:
|
||||
self.cache[p.attributes.type] = []
|
||||
self.cache[p.attributes.type].append(p)
|
||||
@ -140,7 +140,7 @@ class LaconicRegistryClient:
|
||||
if name in self.cache.name_or_id:
|
||||
return self.cache.name_or_id[name]
|
||||
|
||||
args = ["laconic", "-c", self.config_file, "registry", "name", "resolve", name]
|
||||
args = ["laconic", "-c", self.config_file, "cns", "name", "resolve", name]
|
||||
|
||||
parsed = [AttrDict(r) for r in json.loads(logged_cmd(self.log_file, *args))]
|
||||
if parsed:
|
||||
@ -158,14 +158,14 @@ class LaconicRegistryClient:
|
||||
if name_or_id in self.cache.name_or_id:
|
||||
return self.cache.name_or_id[name_or_id]
|
||||
|
||||
if self.is_lrn(name_or_id):
|
||||
if self.is_crn(name_or_id):
|
||||
return self.resolve(name_or_id)
|
||||
|
||||
args = [
|
||||
"laconic",
|
||||
"-c",
|
||||
self.config_file,
|
||||
"registry",
|
||||
"cns",
|
||||
"record",
|
||||
"get",
|
||||
"--id",
|
||||
@ -203,16 +203,7 @@ class LaconicRegistryClient:
|
||||
print(open(record_fname, 'r').read(), file=self.log_file)
|
||||
|
||||
new_record_id = json.loads(
|
||||
logged_cmd(
|
||||
self.log_file,
|
||||
"laconic", "-c",
|
||||
self.config_file,
|
||||
"registry",
|
||||
"record",
|
||||
"publish",
|
||||
"--filename",
|
||||
record_fname
|
||||
)
|
||||
logged_cmd(self.log_file, "laconic", "-c", self.config_file, "cns", "record", "publish", "--filename", record_fname)
|
||||
)["id"]
|
||||
for name in names:
|
||||
self.set_name(name, new_record_id)
|
||||
@ -221,10 +212,10 @@ class LaconicRegistryClient:
|
||||
logged_cmd(self.log_file, "rm", "-rf", tmpdir)
|
||||
|
||||
def set_name(self, name, record_id):
|
||||
logged_cmd(self.log_file, "laconic", "-c", self.config_file, "registry", "name", "set", name, record_id)
|
||||
logged_cmd(self.log_file, "laconic", "-c", self.config_file, "cns", "name", "set", name, record_id)
|
||||
|
||||
def delete_name(self, name):
|
||||
logged_cmd(self.log_file, "laconic", "-c", self.config_file, "registry", "name", "delete", name)
|
||||
logged_cmd(self.log_file, "laconic", "-c", self.config_file, "cns", "name", "delete", name)
|
||||
|
||||
|
||||
def file_hash(filename):
|
||||
@ -318,26 +309,33 @@ def push_container_image(deployment_dir, logger):
|
||||
logger.log("Finished pushing images.")
|
||||
|
||||
|
||||
def deploy_to_k8s(deploy_record, deployment_dir, logger):
|
||||
if not deploy_record:
|
||||
command = "start"
|
||||
else:
|
||||
command = "update"
|
||||
|
||||
def deploy_to_k8s(deploy_record, deployment_dir, recreate, logger):
|
||||
logger.log("Deploying to k8s ...")
|
||||
logger.log(f"Running {command} command on deployment dir: {deployment_dir}")
|
||||
result = subprocess.run([sys.argv[0], "deployment", "--dir", deployment_dir, command],
|
||||
stdout=logger.file, stderr=logger.file)
|
||||
result.check_returncode()
|
||||
|
||||
if recreate:
|
||||
commands_to_run = ["stop", "start"]
|
||||
else:
|
||||
if not deploy_record:
|
||||
commands_to_run = ["start"]
|
||||
else:
|
||||
commands_to_run = ["update"]
|
||||
|
||||
for command in commands_to_run:
|
||||
logger.log(f"Running {command} command on deployment dir: {deployment_dir}")
|
||||
result = subprocess.run([sys.argv[0], "deployment", "--dir", deployment_dir, command],
|
||||
stdout=logger.file, stderr=logger.file)
|
||||
result.check_returncode()
|
||||
logger.log(f"Finished {command} command on deployment dir: {deployment_dir}")
|
||||
|
||||
logger.log("Finished deploying to k8s.")
|
||||
|
||||
|
||||
def publish_deployment(laconic: LaconicRegistryClient,
|
||||
app_record,
|
||||
deploy_record,
|
||||
deployment_lrn,
|
||||
deployment_crn,
|
||||
dns_record,
|
||||
dns_lrn,
|
||||
dns_crn,
|
||||
deployment_dir,
|
||||
app_deployment_request=None,
|
||||
logger=None):
|
||||
@ -372,7 +370,7 @@ def publish_deployment(laconic: LaconicRegistryClient,
|
||||
|
||||
if logger:
|
||||
logger.log("Publishing DnsRecord.")
|
||||
dns_id = laconic.publish(new_dns_record, [dns_lrn])
|
||||
dns_id = laconic.publish(new_dns_record, [dns_crn])
|
||||
|
||||
new_deployment_record = {
|
||||
"record": {
|
||||
@ -393,7 +391,7 @@ def publish_deployment(laconic: LaconicRegistryClient,
|
||||
|
||||
if logger:
|
||||
logger.log("Publishing ApplicationDeploymentRecord.")
|
||||
deployment_id = laconic.publish(new_deployment_record, [deployment_lrn])
|
||||
deployment_id = laconic.publish(new_deployment_record, [deployment_crn])
|
||||
return {"dns": dns_id, "deployment": deployment_id}
|
||||
|
||||
|
||||
@ -402,7 +400,7 @@ def hostname_for_deployment_request(app_deployment_request, laconic):
|
||||
if not dns_name:
|
||||
app = laconic.get_record(app_deployment_request.attributes.application, require=True)
|
||||
dns_name = generate_hostname_for_app(app)
|
||||
elif dns_name.startswith("lrn://"):
|
||||
elif dns_name.startswith("crn://"):
|
||||
record = laconic.get_record(dns_name, require=True)
|
||||
dns_name = record.attributes.name
|
||||
return dns_name
|
||||
|
Loading…
Reference in New Issue
Block a user