diff --git a/stack_orchestrator/deploy/compose/deploy_docker.py b/stack_orchestrator/deploy/compose/deploy_docker.py index 04f24df5..fe4340b4 100644 --- a/stack_orchestrator/deploy/compose/deploy_docker.py +++ b/stack_orchestrator/deploy/compose/deploy_docker.py @@ -40,6 +40,12 @@ class DockerDeployer(Deployer): except DockerException as e: raise DeployerException(e) + def update(self): + try: + return self.docker.compose.restart() + except DockerException as e: + raise DeployerException(e) + def ps(self): try: return self.docker.compose.ps() diff --git a/stack_orchestrator/deploy/deploy.py b/stack_orchestrator/deploy/deploy.py index 424d112f..7e18d2d8 100644 --- a/stack_orchestrator/deploy/deploy.py +++ b/stack_orchestrator/deploy/deploy.py @@ -107,6 +107,14 @@ def down_operation(ctx, delete_volumes, extra_args_list): ctx.obj.deployer.down(timeout=timeout_arg, volumes=delete_volumes) +def update_operation(ctx): + global_context = ctx.parent.parent.obj + if not global_context.dry_run: + if global_context.verbose: + print("Running compose update") + ctx.obj.deployer.update() + + def ps_operation(ctx): global_context = ctx.parent.parent.obj if not global_context.dry_run: diff --git a/stack_orchestrator/deploy/deployer.py b/stack_orchestrator/deploy/deployer.py index 984945ed..fa65ca56 100644 --- a/stack_orchestrator/deploy/deployer.py +++ b/stack_orchestrator/deploy/deployer.py @@ -27,6 +27,10 @@ class Deployer(ABC): def down(self, timeout, volumes): pass + @abstractmethod + def update(self): + pass + @abstractmethod def ps(self): pass diff --git a/stack_orchestrator/deploy/deployment.py b/stack_orchestrator/deploy/deployment.py index 988810b9..67f3a494 100644 --- a/stack_orchestrator/deploy/deployment.py +++ b/stack_orchestrator/deploy/deployment.py @@ -19,7 +19,7 @@ import sys from stack_orchestrator import constants from stack_orchestrator.deploy.images import push_images_operation from stack_orchestrator.deploy.deploy import up_operation, down_operation, ps_operation, port_operation -from stack_orchestrator.deploy.deploy import exec_operation, logs_operation, create_deploy_context +from stack_orchestrator.deploy.deploy import exec_operation, logs_operation, create_deploy_context, update_operation from stack_orchestrator.deploy.deploy_types import DeployCommandContext from stack_orchestrator.deploy.deployment_context import DeploymentContext from stack_orchestrator.deploy.webapp import update_from_registry as webapp_update @@ -161,3 +161,10 @@ def status(ctx): def update_from_registry(ctx, laconic_config, app_crn, deployment_crn, force): ctx.obj = make_deploy_context(ctx) webapp_update.update(ctx, str(ctx.obj.stack.parent), laconic_config, app_crn, deployment_crn, force) + + +@command.command() +@click.pass_context +def update(ctx): + ctx.obj = make_deploy_context(ctx) + update_operation(ctx) diff --git a/stack_orchestrator/deploy/k8s/cluster_info.py b/stack_orchestrator/deploy/k8s/cluster_info.py index 0aa74189..24fe15a0 100644 --- a/stack_orchestrator/deploy/k8s/cluster_info.py +++ b/stack_orchestrator/deploy/k8s/cluster_info.py @@ -189,6 +189,7 @@ class ClusterInfo: container = client.V1Container( name=container_name, image=image_to_use, + image_pull_policy="Always", env=envs_from_environment_variables_map(self.environment_variables.map), ports=[client.V1ContainerPort(container_port=port)], volume_mounts=volume_mounts, diff --git a/stack_orchestrator/deploy/k8s/deploy_k8s.py b/stack_orchestrator/deploy/k8s/deploy_k8s.py index c84aa34a..b9eeff9e 100644 --- a/stack_orchestrator/deploy/k8s/deploy_k8s.py +++ b/stack_orchestrator/deploy/k8s/deploy_k8s.py @@ -13,6 +13,8 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . +from datetime import datetime, timezone + from pathlib import Path from kubernetes import client, config @@ -42,7 +44,7 @@ class K8sDeployer(Deployer): networking_api: client.NetworkingV1Api k8s_namespace: str = "default" kind_cluster_name: str - cluster_info : ClusterInfo + cluster_info: ClusterInfo deployment_dir: Path deployment_context: DeploymentContext @@ -230,6 +232,33 @@ class K8sDeployer(Deployer): log_data = self.core_api.read_namespaced_pod_log(k8s_pod_name, namespace="default", container="test") return log_stream_from_string(log_data) + def update(self): + self.connect_api() + ref_deployment = self.cluster_info.get_deployment() + + deployment = self.apps_api.read_namespaced_deployment( + name=ref_deployment.metadata.name, + namespace=self.k8s_namespace + ) + + new_env = ref_deployment.spec.template.spec.containers[0].env + for container in deployment.spec.template.spec.containers: + old_env = container.env + if old_env != new_env: + container.env = new_env + + deployment.spec.template.metadata.annotations = { + "kubectl.kubernetes.io/restartedAt": datetime.utcnow() + .replace(tzinfo=timezone.utc) + .isoformat() + } + + self.apps_api.patch_namespaced_deployment( + name=ref_deployment.metadata.name, + namespace=self.k8s_namespace, + body=deployment + ) + def run(self, image: str, command=None, user=None, volumes=None, entrypoint=None, env={}, ports=[], detach=False): # We need to figure out how to do this -- check why we're being called first pass diff --git a/stack_orchestrator/deploy/webapp/update_from_registry.py b/stack_orchestrator/deploy/webapp/update_from_registry.py index 335c5f53..ee8a38f7 100644 --- a/stack_orchestrator/deploy/webapp/update_from_registry.py +++ b/stack_orchestrator/deploy/webapp/update_from_registry.py @@ -74,12 +74,8 @@ def config_changed(deploy_record, deployment_dir): def redeploy(laconic_config, app_record, deploy_record, deploy_crn, deployment_dir): - print("Stopping deployment ...") - result = subprocess.run(["laconic-so", "deployment", "--dir", deployment_dir, "stop"]) - result.check_returncode() - - print("Starting deployment ...") - result = subprocess.run(["laconic-so", "deployment", "--dir", deployment_dir, "start"]) + print("Updating deployment ...") + result = subprocess.run(["laconic-so", "deployment", "--dir", deployment_dir, "update"]) result.check_returncode() spec = yaml.full_load(open(os.path.join(deployment_dir, "spec.yml"))) @@ -143,19 +139,20 @@ def update(ctx, deployment_dir, laconic_config, app_crn, deploy_crn, force=False if app_record["id"] == deploy_record.get("attributes", {}).get("application"): print("Deployment %s has latest application: %s" % (deploy_crn, app_record["id"])) else: + needs_update = True print("Found updated application record eligible for deployment %s (old: %s, new: %s)" % ( deploy_crn, deploy_record.get("id"), app_record["id"])) build_image(app_record, deployment_dir) - needs_update = True # check config if config_changed(deploy_record, deployment_dir): + needs_update = True old = None if deploy_record: - old = json.loads(deploy_record["attributes"]["meta"]["config"]) + print(deploy_record) + old = json.loads(deploy_record["attributes"]["meta"])["config"] print("Deployment %s has changed config: (old: %s, new: %s)" % ( deploy_crn, old, config_hash(deployment_dir))) - needs_update = True else: print("Deployment %s has latest config: %s" % ( deploy_crn, json.loads(deploy_record["attributes"]["meta"])["config"]))