From f55ca7ff5b9216b19eadecf0126ac7540153bd98 Mon Sep 17 00:00:00 2001 From: David Boreham Date: Thu, 19 Oct 2023 15:22:12 -0600 Subject: [PATCH 01/10] Initial refactor --- app/deploy.py | 38 +++++++++++++++++++------------------- app/deploy_docker.py | 17 +++++++++++++++++ app/deploy_k8s.py | 17 +++++++++++++++++ app/deploy_types.py | 4 ++-- app/deployer.py | 27 +++++++++++++++++++++++++++ requirements.txt | 1 + 6 files changed, 83 insertions(+), 21 deletions(-) create mode 100644 app/deploy_docker.py create mode 100644 app/deploy_k8s.py create mode 100644 app/deployer.py diff --git a/app/deploy.py b/app/deploy.py index 0ebcfe18..bb45520a 100644 --- a/app/deploy.py +++ b/app/deploy.py @@ -13,7 +13,7 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . -# Deploys the system components using docker-compose +# Deploys the system components using a deployer (either docker-compose or k8s) import hashlib import copy @@ -22,10 +22,10 @@ import sys from dataclasses import dataclass from importlib import resources import subprocess -from python_on_whales import DockerClient, DockerException import click from pathlib import Path from app.util import include_exclude_check, get_parsed_stack_config, global_options2, get_dev_root_path +from app.deployer import Deployer, DeployerException from app.deploy_types import ClusterContext, DeployCommandContext from app.deployment_create import create as deployment_create from app.deployment_create import init as deployment_init @@ -57,9 +57,9 @@ def command(ctx, include, exclude, env_file, cluster): def create_deploy_context(global_context, stack, include, exclude, cluster, env_file): cluster_context = _make_cluster_context(global_context, stack, include, exclude, cluster, env_file) # See: https://gabrieldemarmiesse.github.io/python-on-whales/sub-commands/compose/ - docker = DockerClient(compose_files=cluster_context.compose_files, compose_project_name=cluster_context.cluster, - compose_env_file=cluster_context.env_file) - return DeployCommandContext(stack, cluster_context, docker) + deployer = Deployer(compose_files=cluster_context.compose_files, compose_project_name=cluster_context.cluster, + compose_env_file=cluster_context.env_file) + return DeployCommandContext(stack, cluster_context, deployer) def up_operation(ctx, services_list, stay_attached=False): @@ -74,10 +74,10 @@ def up_operation(ctx, services_list, stay_attached=False): print(f"Running compose up with container_exec_env: {container_exec_env}, extra_args: {services_list}") for pre_start_command in cluster_context.pre_start_commands: _run_command(global_context, cluster_context.cluster, pre_start_command) - deploy_context.docker.compose.up(detach=not stay_attached, services=services_list) + deploy_context.deployer.compose.up(detach=not stay_attached, services=services_list) for post_start_command in cluster_context.post_start_commands: _run_command(global_context, cluster_context.cluster, post_start_command) - _orchestrate_cluster_config(global_context, cluster_context.config, deploy_context.docker, container_exec_env) + _orchestrate_cluster_config(global_context, cluster_context.config, deploy_context.deployer, container_exec_env) def down_operation(ctx, delete_volumes, extra_args_list): @@ -89,7 +89,7 @@ def down_operation(ctx, delete_volumes, extra_args_list): if extra_args_list: timeout_arg = extra_args_list[0] # Specify shutdown timeout (default 10s) to give services enough time to shutdown gracefully - ctx.obj.docker.compose.down(timeout=timeout_arg, volumes=delete_volumes) + ctx.obj.deployer.compose.down(timeout=timeout_arg, volumes=delete_volumes) def ps_operation(ctx): @@ -97,7 +97,7 @@ def ps_operation(ctx): if not global_context.dry_run: if global_context.verbose: print("Running compose ps") - container_list = ctx.obj.docker.compose.ps() + container_list = ctx.obj.deployer.compose.ps() if len(container_list) > 0: print("Running containers:") for container in container_list: @@ -128,7 +128,7 @@ def port_operation(ctx, extra_args): exposed_port = extra_args_list[1] if global_context.verbose: print(f"Running compose port {service_name} {exposed_port}") - mapped_port_data = ctx.obj.docker.compose.port(service_name, exposed_port) + mapped_port_data = ctx.obj.deployer.compose.port(service_name, exposed_port) print(f"{mapped_port_data[0]}:{mapped_port_data[1]}") @@ -145,8 +145,8 @@ def exec_operation(ctx, extra_args): if global_context.verbose: print(f"Running compose exec {service_name} {command_to_exec}") try: - ctx.obj.docker.compose.execute(service_name, command_to_exec, envs=container_exec_env) - except DockerException: + ctx.obj.deployer.compose.execute(service_name, command_to_exec, envs=container_exec_env) + except DeployerException: print("container command returned error exit status") @@ -157,7 +157,7 @@ def logs_operation(ctx, tail: int, follow: bool, extra_args: str): if global_context.verbose: print("Running compose logs") services_list = extra_args_list if extra_args_list is not None else [] - logs_stream = ctx.obj.docker.compose.logs(services=services_list, tail=tail, follow=follow, stream=True) + logs_stream = ctx.obj.deployer.compose.logs(services=services_list, tail=tail, follow=follow, stream=True) for stream_type, stream_content in logs_stream: print(stream_content.decode("utf-8"), end="") @@ -214,11 +214,11 @@ def get_stack_status(ctx, stack): ctx_copy.stack = stack cluster_context = _make_cluster_context(ctx_copy, stack, None, None, None, None) - docker = DockerClient(compose_files=cluster_context.compose_files, compose_project_name=cluster_context.cluster) + deployer = Deployer(compose_files=cluster_context.compose_files, compose_project_name=cluster_context.cluster) # TODO: refactor to avoid duplicating this code above if ctx.verbose: print("Running compose ps") - container_list = docker.compose.ps() + container_list = deployer.compose.ps() if len(container_list) > 0: if ctx.debug: print(f"Container list from compose ps: {container_list}") @@ -359,7 +359,7 @@ def _run_command(ctx, cluster_name, command): sys.exit(1) -def _orchestrate_cluster_config(ctx, cluster_config, docker, container_exec_env): +def _orchestrate_cluster_config(ctx, cluster_config, deployer, container_exec_env): @dataclass class ConfigDirective: @@ -390,13 +390,13 @@ def _orchestrate_cluster_config(ctx, cluster_config, docker, container_exec_env) # TODO: fix the script paths so they're consistent between containers source_value = None try: - source_value = docker.compose.execute(pd.source_container, + source_value = deployer.compose.execute(pd.source_container, ["sh", "-c", "sh /docker-entrypoint-scripts.d/export-" f"{pd.source_variable}.sh"], tty=False, envs=container_exec_env) - except DockerException as error: + except DeployerException as error: if ctx.debug: print(f"Docker exception reading config source: {error}") # If the script executed failed for some reason, we get: @@ -411,7 +411,7 @@ def _orchestrate_cluster_config(ctx, cluster_config, docker, container_exec_env) if source_value: if ctx.debug: print(f"fetched source value: {source_value}") - destination_output = docker.compose.execute(pd.destination_container, + destination_output = deployer.compose.execute(pd.destination_container, ["sh", "-c", f"sh /scripts/import-{pd.destination_variable}.sh" f" {source_value}"], diff --git a/app/deploy_docker.py b/app/deploy_docker.py new file mode 100644 index 00000000..8db3fa54 --- /dev/null +++ b/app/deploy_docker.py @@ -0,0 +1,17 @@ +# Copyright © 2022, 2023 Vulcanize + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. + +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +from python_on_whales import DockerClient, DockerException + diff --git a/app/deploy_k8s.py b/app/deploy_k8s.py new file mode 100644 index 00000000..48562508 --- /dev/null +++ b/app/deploy_k8s.py @@ -0,0 +1,17 @@ +# Copyright © 2023 Vulcanize + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. + +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +from kubernetes import client, config + diff --git a/app/deploy_types.py b/app/deploy_types.py index 63f32762..ecc0d5a3 100644 --- a/app/deploy_types.py +++ b/app/deploy_types.py @@ -16,8 +16,8 @@ from typing import List from dataclasses import dataclass from pathlib import Path -from python_on_whales import DockerClient from app.command_types import CommandOptions +from app.deployer import Deployer @dataclass @@ -35,7 +35,7 @@ class ClusterContext: class DeployCommandContext: stack: str cluster_context: ClusterContext - docker: DockerClient + deployer: Deployer @dataclass diff --git a/app/deployer.py b/app/deployer.py new file mode 100644 index 00000000..76416125 --- /dev/null +++ b/app/deployer.py @@ -0,0 +1,27 @@ +# Copyright © 2023 Vulcanize + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. + +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +from abc import ABC, abstractmethod + +class Deployer(ABC): + + @abstractmethod + def (self, purchase): + pass + + +class DeployerException(Exception) { + +} \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 7f60f9dd..bf4845a1 100644 --- a/requirements.txt +++ b/requirements.txt @@ -8,3 +8,4 @@ ruamel.yaml>=0.17.32 pydantic==1.10.9 tomli==2.0.1 validators==0.22.0 +kubernetes>=28.1.0 -- 2.45.2 From e1cdbe76aaec8b224eacd62f9ff2351b053df0ef Mon Sep 17 00:00:00 2001 From: David Boreham Date: Fri, 20 Oct 2023 06:24:13 -0600 Subject: [PATCH 02/10] Basic refactor --- app/deploy.py | 38 +++++++++++++++++++------------------- app/deploy_docker.py | 24 ++++++++++++++++++++++++ app/deploy_k8s.py | 5 +++++ app/deployer.py | 12 +++++++++--- 4 files changed, 57 insertions(+), 22 deletions(-) diff --git a/app/deploy.py b/app/deploy.py index bb45520a..911b96db 100644 --- a/app/deploy.py +++ b/app/deploy.py @@ -74,7 +74,7 @@ def up_operation(ctx, services_list, stay_attached=False): print(f"Running compose up with container_exec_env: {container_exec_env}, extra_args: {services_list}") for pre_start_command in cluster_context.pre_start_commands: _run_command(global_context, cluster_context.cluster, pre_start_command) - deploy_context.deployer.compose.up(detach=not stay_attached, services=services_list) + deploy_context.deployer.compose_up(detach=not stay_attached, services=services_list) for post_start_command in cluster_context.post_start_commands: _run_command(global_context, cluster_context.cluster, post_start_command) _orchestrate_cluster_config(global_context, cluster_context.config, deploy_context.deployer, container_exec_env) @@ -89,7 +89,7 @@ def down_operation(ctx, delete_volumes, extra_args_list): if extra_args_list: timeout_arg = extra_args_list[0] # Specify shutdown timeout (default 10s) to give services enough time to shutdown gracefully - ctx.obj.deployer.compose.down(timeout=timeout_arg, volumes=delete_volumes) + ctx.obj.deployer.compose_down(timeout=timeout_arg, volumes=delete_volumes) def ps_operation(ctx): @@ -97,7 +97,7 @@ def ps_operation(ctx): if not global_context.dry_run: if global_context.verbose: print("Running compose ps") - container_list = ctx.obj.deployer.compose.ps() + container_list = ctx.obj.deployer.compose_ps() if len(container_list) > 0: print("Running containers:") for container in container_list: @@ -128,7 +128,7 @@ def port_operation(ctx, extra_args): exposed_port = extra_args_list[1] if global_context.verbose: print(f"Running compose port {service_name} {exposed_port}") - mapped_port_data = ctx.obj.deployer.compose.port(service_name, exposed_port) + mapped_port_data = ctx.obj.deployer.compose_port(service_name, exposed_port) print(f"{mapped_port_data[0]}:{mapped_port_data[1]}") @@ -145,7 +145,7 @@ def exec_operation(ctx, extra_args): if global_context.verbose: print(f"Running compose exec {service_name} {command_to_exec}") try: - ctx.obj.deployer.compose.execute(service_name, command_to_exec, envs=container_exec_env) + ctx.obj.deployer.compose_execute(service_name, command_to_exec, envs=container_exec_env) except DeployerException: print("container command returned error exit status") @@ -157,7 +157,7 @@ def logs_operation(ctx, tail: int, follow: bool, extra_args: str): if global_context.verbose: print("Running compose logs") services_list = extra_args_list if extra_args_list is not None else [] - logs_stream = ctx.obj.deployer.compose.logs(services=services_list, tail=tail, follow=follow, stream=True) + logs_stream = ctx.obj.deployer.compose_logs(services=services_list, tail=tail, follow=follow, stream=True) for stream_type, stream_content in logs_stream: print(stream_content.decode("utf-8"), end="") @@ -218,7 +218,7 @@ def get_stack_status(ctx, stack): # TODO: refactor to avoid duplicating this code above if ctx.verbose: print("Running compose ps") - container_list = deployer.compose.ps() + container_list = deployer.compose_ps() if len(container_list) > 0: if ctx.debug: print(f"Container list from compose ps: {container_list}") @@ -390,12 +390,12 @@ def _orchestrate_cluster_config(ctx, cluster_config, deployer, container_exec_en # TODO: fix the script paths so they're consistent between containers source_value = None try: - source_value = deployer.compose.execute(pd.source_container, - ["sh", "-c", - "sh /docker-entrypoint-scripts.d/export-" - f"{pd.source_variable}.sh"], - tty=False, - envs=container_exec_env) + source_value = deployer.compose_execute(pd.source_container, + ["sh", "-c", + "sh /docker-entrypoint-scripts.d/export-" + f"{pd.source_variable}.sh"], + tty=False, + envs=container_exec_env) except DeployerException as error: if ctx.debug: print(f"Docker exception reading config source: {error}") @@ -411,12 +411,12 @@ def _orchestrate_cluster_config(ctx, cluster_config, deployer, container_exec_en if source_value: if ctx.debug: print(f"fetched source value: {source_value}") - destination_output = deployer.compose.execute(pd.destination_container, - ["sh", "-c", - f"sh /scripts/import-{pd.destination_variable}.sh" - f" {source_value}"], - tty=False, - envs=container_exec_env) + destination_output = deployer.compose_execute(pd.destination_container, + ["sh", "-c", + f"sh /scripts/import-{pd.destination_variable}.sh" + f" {source_value}"], + tty=False, + envs=container_exec_env) waiting_for_data = False if ctx.debug: print(f"destination output: {destination_output}") diff --git a/app/deploy_docker.py b/app/deploy_docker.py index 8db3fa54..16781def 100644 --- a/app/deploy_docker.py +++ b/app/deploy_docker.py @@ -14,4 +14,28 @@ # along with this program. If not, see . from python_on_whales import DockerClient, DockerException +from app.deployer import Deployer + +class DockerDeployer(Deployer): + def __init__(self, compose_files, compose_project_name, compose_env_file) -> None: + self.docker = DockerClient(compose_files=compose_files, compose_project_name=compose_project_name, + compose_env_file=compose_env_file) + + def compose_up(self, detach, services): + return self.docker.compose.up(detach=detach, services=services) + + def compose_down(self, timeout, volumes): + return self.docker.compose.down(timeout=timeout, volumes=volumes) + + def compose_ps(self): + return self.docker.compose.ps() + + def compose_port(self, service, private_port): + return self.docker.compose.port(service=service, private_port=private_port) + + def compose_execute(self, service_name, command, envs): + return self.docker.compose.execute(service_name=service_name, command=command, envs=envs) + + def compose_logs(self, services, tail, follow, stream): + return self.docker.compose.logs(services=services, tail=tail, follow=follow, stream=stream) diff --git a/app/deploy_k8s.py b/app/deploy_k8s.py index 48562508..34d8ab2c 100644 --- a/app/deploy_k8s.py +++ b/app/deploy_k8s.py @@ -14,4 +14,9 @@ # along with this program. If not, see . from kubernetes import client, config +from app.deployer import Deployer + +class K8sDeployer(Deployer): + def __init__(self) -> None: + pass diff --git a/app/deployer.py b/app/deployer.py index 76416125..d41b6b60 100644 --- a/app/deployer.py +++ b/app/deployer.py @@ -14,14 +14,20 @@ # along with this program. If not, see . from abc import ABC, abstractmethod +from app.deploy_k8s import K8sDeployer +from app.deploy_docker import DockerDeployer + class Deployer(ABC): @abstractmethod - def (self, purchase): + def method(self, purchase): pass -class DeployerException(Exception) { +def getDeployer(compose_files, compose_project_name, compose_env_file): + return DockerDeployer(compose_files, compose_project_name, compose_env_file) -} \ No newline at end of file + +class DeployerException(Exception): + pass -- 2.45.2 From 3291c164668e33e5cdf138f2a0c1638c91e06ead Mon Sep 17 00:00:00 2001 From: Thomas E Lackey Date: Fri, 20 Oct 2023 11:59:06 -0500 Subject: [PATCH 03/10] Update stacks for migrated laconic repos. (#594) --- app/data/stacks/fixturenet-laconic-loaded/stack.yml | 8 ++++---- app/data/stacks/fixturenet-laconicd/stack.yml | 6 +++--- app/data/stacks/test/stack.yml | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/app/data/stacks/fixturenet-laconic-loaded/stack.yml b/app/data/stacks/fixturenet-laconic-loaded/stack.yml index 523a7091..6c8aae4a 100644 --- a/app/data/stacks/fixturenet-laconic-loaded/stack.yml +++ b/app/data/stacks/fixturenet-laconic-loaded/stack.yml @@ -2,14 +2,14 @@ version: "1.1" name: fixturenet-laconic-loaded description: "A full featured laconic fixturenet" repos: - - github.com/cerc-io/laconicd + - git.vdb.to/cerc-io/laconicd - github.com/lirewine/debug - github.com/lirewine/crypto - github.com/lirewine/gem - github.com/lirewine/sdk - - github.com/cerc-io/laconic-sdk - - github.com/cerc-io/laconic-registry-cli - - github.com/cerc-io/laconic-console + - git.vdb.to/cerc-io/laconic-sdk + - git.vdb.to/cerc-io/laconic-registry-cli + - git.vdb.to/cerc-io/laconic-console npms: - laconic-sdk - laconic-registry-cli diff --git a/app/data/stacks/fixturenet-laconicd/stack.yml b/app/data/stacks/fixturenet-laconicd/stack.yml index ce0b1946..adab9e3e 100644 --- a/app/data/stacks/fixturenet-laconicd/stack.yml +++ b/app/data/stacks/fixturenet-laconicd/stack.yml @@ -2,9 +2,9 @@ version: "1.0" name: fixturenet-laconicd description: "A laconicd fixturenet" repos: - - github.com/cerc-io/laconicd - - github.com/cerc-io/laconic-sdk - - github.com/cerc-io/laconic-registry-cli + - git.vdb.to/cerc-io/laconicd + - git.vdb.to/cerc-io/laconic-sdk + - git.vdb.to/cerc-io/laconic-registry-cli npms: - laconic-sdk - laconic-registry-cli diff --git a/app/data/stacks/test/stack.yml b/app/data/stacks/test/stack.yml index a62da193..ac724c89 100644 --- a/app/data/stacks/test/stack.yml +++ b/app/data/stacks/test/stack.yml @@ -2,7 +2,7 @@ version: "1.0" name: test description: "A test stack" repos: - - github.com/cerc-io/laconicd + - git.vdb.to/cerc-io/laconicd - git.vdb.to/cerc-io/test-project@test-branch containers: - cerc/test-container -- 2.45.2 From f60717fbc3cbeeae8fe71532413cb9bf7d7c8b63 Mon Sep 17 00:00:00 2001 From: David Boreham Date: Fri, 20 Oct 2023 14:20:54 -0600 Subject: [PATCH 04/10] Passes tests --- app/deploy.py | 5 +++-- app/deploy_docker.py | 38 +++++++++++++++++++++++++++++++------- app/deploy_util.py | 4 ++-- app/deployer.py | 35 +++++++++++++++++++++++++++-------- app/deployer_factory.py | 21 +++++++++++++++++++++ 5 files changed, 84 insertions(+), 19 deletions(-) create mode 100644 app/deployer_factory.py diff --git a/app/deploy.py b/app/deploy.py index 911b96db..ba06bd5f 100644 --- a/app/deploy.py +++ b/app/deploy.py @@ -26,6 +26,7 @@ import click from pathlib import Path from app.util import include_exclude_check, get_parsed_stack_config, global_options2, get_dev_root_path from app.deployer import Deployer, DeployerException +from app.deployer_factory import getDeployer from app.deploy_types import ClusterContext, DeployCommandContext from app.deployment_create import create as deployment_create from app.deployment_create import init as deployment_init @@ -57,8 +58,8 @@ def command(ctx, include, exclude, env_file, cluster): def create_deploy_context(global_context, stack, include, exclude, cluster, env_file): cluster_context = _make_cluster_context(global_context, stack, include, exclude, cluster, env_file) # See: https://gabrieldemarmiesse.github.io/python-on-whales/sub-commands/compose/ - deployer = Deployer(compose_files=cluster_context.compose_files, compose_project_name=cluster_context.cluster, - compose_env_file=cluster_context.env_file) + deployer = getDeployer(compose_files=cluster_context.compose_files, compose_project_name=cluster_context.cluster, + compose_env_file=cluster_context.env_file) return DeployCommandContext(stack, cluster_context, deployer) diff --git a/app/deploy_docker.py b/app/deploy_docker.py index 16781def..0e75ee73 100644 --- a/app/deploy_docker.py +++ b/app/deploy_docker.py @@ -14,7 +14,7 @@ # along with this program. If not, see . from python_on_whales import DockerClient, DockerException -from app.deployer import Deployer +from app.deployer import Deployer, DeployerException class DockerDeployer(Deployer): @@ -23,19 +23,43 @@ class DockerDeployer(Deployer): compose_env_file=compose_env_file) def compose_up(self, detach, services): - return self.docker.compose.up(detach=detach, services=services) + try: + return self.docker.compose.up(detach=detach, services=services) + except DockerException as e: + raise DeployerException(e) def compose_down(self, timeout, volumes): - return self.docker.compose.down(timeout=timeout, volumes=volumes) + try: + return self.docker.compose.down(timeout=timeout, volumes=volumes) + except DockerException as e: + raise DeployerException(e) def compose_ps(self): - return self.docker.compose.ps() + try: + return self.docker.compose.ps() + except DockerException as e: + raise DeployerException(e) def compose_port(self, service, private_port): - return self.docker.compose.port(service=service, private_port=private_port) + try: + return self.docker.compose.port(service=service, private_port=private_port) + except DockerException as e: + raise DeployerException(e) def compose_execute(self, service_name, command, envs): - return self.docker.compose.execute(service_name=service_name, command=command, envs=envs) + try: + return self.docker.compose.execute(service_name=service_name, command=command, envs=envs) + except DockerException as e: + raise DeployerException(e) def compose_logs(self, services, tail, follow, stream): - return self.docker.compose.logs(services=services, tail=tail, follow=follow, stream=stream) + try: + return self.docker.compose.logs(services=services, tail=tail, follow=follow, stream=stream) + except DockerException as e: + raise DeployerException(e) + + def run(self, image, command, user, volumes, entrypoint=None): + try: + return self.docker.run(image=image, command=command, user=user, volumes=volumes, entrypoint=entrypoint) + except DockerException as e: + raise DeployerException(e) diff --git a/app/deploy_util.py b/app/deploy_util.py index 498e3dfd..b492d6e1 100644 --- a/app/deploy_util.py +++ b/app/deploy_util.py @@ -47,12 +47,12 @@ def _volumes_to_docker(mounts: List[VolumeMapping]): def run_container_command(ctx: DeployCommandContext, service: str, command: str, mounts: List[VolumeMapping]): - docker = ctx.docker + deployer = ctx.deployer container_image = _container_image_from_service(ctx.stack, service) docker_volumes = _volumes_to_docker(mounts) if ctx.cluster_context.options.debug: print(f"Running this command in {service} container: {command}") - docker_output = docker.run( + docker_output = deployer.run( container_image, ["-c", command], entrypoint="sh", user=f"{os.getuid()}:{os.getgid()}", diff --git a/app/deployer.py b/app/deployer.py index d41b6b60..97595db7 100644 --- a/app/deployer.py +++ b/app/deployer.py @@ -14,20 +14,39 @@ # along with this program. If not, see . from abc import ABC, abstractmethod -from app.deploy_k8s import K8sDeployer -from app.deploy_docker import DockerDeployer class Deployer(ABC): @abstractmethod - def method(self, purchase): + def compose_up(self, detach, services): + pass + + @abstractmethod + def compose_down(self, timeout, volumes): + pass + + @abstractmethod + def compose_ps(self): + pass + + @abstractmethod + def compose_port(self, service, private_port): + pass + + @abstractmethod + def compose_execute(self, service_name, command, envs): + pass + + @abstractmethod + def compose_logs(self, services, tail, follow, stream): + pass + + @abstractmethod + def run(self, image, command, user, volumes, entrypoint): pass -def getDeployer(compose_files, compose_project_name, compose_env_file): - return DockerDeployer(compose_files, compose_project_name, compose_env_file) - - class DeployerException(Exception): - pass + def __init__(self, *args: object) -> None: + super().__init__(*args) diff --git a/app/deployer_factory.py b/app/deployer_factory.py new file mode 100644 index 00000000..fd68d587 --- /dev/null +++ b/app/deployer_factory.py @@ -0,0 +1,21 @@ +# Copyright © 2023 Vulcanize + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. + +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +from app.deploy_k8s import K8sDeployer +from app.deploy_docker import DockerDeployer + + +def getDeployer(compose_files, compose_project_name, compose_env_file): + return DockerDeployer(compose_files, compose_project_name, compose_env_file) -- 2.45.2 From bfc668d64db5a28dd7c157af8a1e31f340f1eadc Mon Sep 17 00:00:00 2001 From: David Boreham Date: Fri, 20 Oct 2023 14:32:12 -0600 Subject: [PATCH 05/10] Fix lint errors --- app/deploy.py | 2 +- app/deploy_k8s.py | 24 +++++++++++++++++++++++- app/deployer_factory.py | 7 +++++-- 3 files changed, 29 insertions(+), 4 deletions(-) diff --git a/app/deploy.py b/app/deploy.py index ba06bd5f..18be4559 100644 --- a/app/deploy.py +++ b/app/deploy.py @@ -58,7 +58,7 @@ def command(ctx, include, exclude, env_file, cluster): def create_deploy_context(global_context, stack, include, exclude, cluster, env_file): cluster_context = _make_cluster_context(global_context, stack, include, exclude, cluster, env_file) # See: https://gabrieldemarmiesse.github.io/python-on-whales/sub-commands/compose/ - deployer = getDeployer(compose_files=cluster_context.compose_files, compose_project_name=cluster_context.cluster, + deployer = getDeployer("docker", compose_files=cluster_context.compose_files, compose_project_name=cluster_context.cluster, compose_env_file=cluster_context.env_file) return DeployCommandContext(stack, cluster_context, deployer) diff --git a/app/deploy_k8s.py b/app/deploy_k8s.py index 34d8ab2c..de26addc 100644 --- a/app/deploy_k8s.py +++ b/app/deploy_k8s.py @@ -18,5 +18,27 @@ from app.deployer import Deployer class K8sDeployer(Deployer): - def __init__(self) -> None: + def __init__(self, compose_files, compose_project_name, compose_env_file) -> None: + config.load_kube_config() + self.client = client.CoreV1Api() + + def compose_up(self, detach, services): + pass + + def compose_down(self, timeout, volumes): + pass + + def compose_ps(self): + pass + + def compose_port(self, service, private_port): + pass + + def compose_execute(self, service_name, command, envs): + pass + + def compose_logs(self, services, tail, follow, stream): + pass + + def run(self, image, command, user, volumes, entrypoint=None): pass diff --git a/app/deployer_factory.py b/app/deployer_factory.py index fd68d587..7a1bca2a 100644 --- a/app/deployer_factory.py +++ b/app/deployer_factory.py @@ -17,5 +17,8 @@ from app.deploy_k8s import K8sDeployer from app.deploy_docker import DockerDeployer -def getDeployer(compose_files, compose_project_name, compose_env_file): - return DockerDeployer(compose_files, compose_project_name, compose_env_file) +def getDeployer(type, compose_files, compose_project_name, compose_env_file): + if type == "docker": + return DockerDeployer(compose_files, compose_project_name, compose_env_file) + else: + return K8sDeployer(compose_files, compose_project_name, compose_env_file) -- 2.45.2 From ada1eff241c6d439aff506d5952bcc0299734985 Mon Sep 17 00:00:00 2001 From: David Boreham Date: Mon, 23 Oct 2023 21:01:38 -0600 Subject: [PATCH 06/10] Add deployer switch --- app/deploy.py | 9 +++++---- app/deployer_factory.py | 6 ++++-- app/deployment.py | 2 +- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/app/deploy.py b/app/deploy.py index 18be4559..59fbf41c 100644 --- a/app/deploy.py +++ b/app/deploy.py @@ -38,8 +38,9 @@ from app.deployment_create import setup as deployment_setup @click.option("--exclude", help="don\'t start these components") @click.option("--env-file", help="env file to be used") @click.option("--cluster", help="specify a non-default cluster name") +@click.option("--deploy-to", help="cluster system to deploy to (compose or k8s)") @click.pass_context -def command(ctx, include, exclude, env_file, cluster): +def command(ctx, include, exclude, env_file, cluster, deploy_to): '''deploy a stack''' # Although in theory for some subcommands (e.g. deploy create) the stack can be inferred, @@ -51,14 +52,14 @@ def command(ctx, include, exclude, env_file, cluster): if ctx.parent.obj.debug: print(f"ctx.parent.obj: {ctx.parent.obj}") - ctx.obj = create_deploy_context(global_options2(ctx), stack, include, exclude, cluster, env_file) + ctx.obj = create_deploy_context(global_options2(ctx), stack, include, exclude, cluster, env_file, deploy_to) # Subcommand is executed now, by the magic of click -def create_deploy_context(global_context, stack, include, exclude, cluster, env_file): +def create_deploy_context(global_context, stack, include, exclude, cluster, env_file, deploy_to): cluster_context = _make_cluster_context(global_context, stack, include, exclude, cluster, env_file) # See: https://gabrieldemarmiesse.github.io/python-on-whales/sub-commands/compose/ - deployer = getDeployer("docker", compose_files=cluster_context.compose_files, compose_project_name=cluster_context.cluster, + deployer = getDeployer(deploy_to, compose_files=cluster_context.compose_files, compose_project_name=cluster_context.cluster, compose_env_file=cluster_context.env_file) return DeployCommandContext(stack, cluster_context, deployer) diff --git a/app/deployer_factory.py b/app/deployer_factory.py index 7a1bca2a..45c54ecd 100644 --- a/app/deployer_factory.py +++ b/app/deployer_factory.py @@ -18,7 +18,9 @@ from app.deploy_docker import DockerDeployer def getDeployer(type, compose_files, compose_project_name, compose_env_file): - if type == "docker": + if type == "compose" or type is None: return DockerDeployer(compose_files, compose_project_name, compose_env_file) - else: + elif type == "k8s": return K8sDeployer(compose_files, compose_project_name, compose_env_file) + else: + print(f"ERROR: deploy-to {type} is not valid") diff --git a/app/deployment.py b/app/deployment.py index aeabf61c..c9c2bb98 100644 --- a/app/deployment.py +++ b/app/deployment.py @@ -62,7 +62,7 @@ def make_deploy_context(ctx): stack_file_path = ctx.obj.get_stack_file() env_file = ctx.obj.get_env_file() cluster_name = ctx.obj.get_cluster_name() - return create_deploy_context(ctx.parent.parent.obj, stack_file_path, None, None, cluster_name, env_file) + return create_deploy_context(ctx.parent.parent.obj, stack_file_path, None, None, cluster_name, env_file, deploy_to) @command.command() -- 2.45.2 From ec5318cfc7aadef93e235eab1cd9897f37618c3b Mon Sep 17 00:00:00 2001 From: David Boreham Date: Mon, 23 Oct 2023 21:13:14 -0600 Subject: [PATCH 07/10] Add spec and stack to deployment --- app/deploy.py | 4 ++-- app/deployment.py | 33 +++++++++++++++++++++++++-------- app/deployment_create.py | 4 ++-- app/spec.py | 30 ++++++++++++++++++++++++++++++ app/stack.py | 30 ++++++++++++++++++++++++++++++ 5 files changed, 89 insertions(+), 12 deletions(-) create mode 100644 app/spec.py create mode 100644 app/stack.py diff --git a/app/deploy.py b/app/deploy.py index 59fbf41c..b8173d2a 100644 --- a/app/deploy.py +++ b/app/deploy.py @@ -56,10 +56,10 @@ def command(ctx, include, exclude, env_file, cluster, deploy_to): # Subcommand is executed now, by the magic of click -def create_deploy_context(global_context, stack, include, exclude, cluster, env_file, deploy_to): +def create_deploy_context(global_context, stack, include, exclude, cluster, env_file, deployer): cluster_context = _make_cluster_context(global_context, stack, include, exclude, cluster, env_file) # See: https://gabrieldemarmiesse.github.io/python-on-whales/sub-commands/compose/ - deployer = getDeployer(deploy_to, compose_files=cluster_context.compose_files, compose_project_name=cluster_context.cluster, + deployer = getDeployer(deployer, compose_files=cluster_context.compose_files, compose_project_name=cluster_context.cluster, compose_env_file=cluster_context.env_file) return DeployCommandContext(stack, cluster_context, deployer) diff --git a/app/deployment.py b/app/deployment.py index c9c2bb98..3de8ac7a 100644 --- a/app/deployment.py +++ b/app/deployment.py @@ -14,20 +14,25 @@ # along with this program. If not, see . import click -from dataclasses import dataclass from pathlib import Path import sys from app.deploy import up_operation, down_operation, ps_operation, port_operation from app.deploy import exec_operation, logs_operation, create_deploy_context +from app.stack import Stack +from app.spec import Spec -@dataclass class DeploymentContext: dir: Path + spec: Spec + stack: Stack def get_stack_file(self): return self.dir.joinpath("stack.yml") + def get_spec_file(self): + return self.dir.joinpath("spec.yml") + def get_env_file(self): return self.dir.joinpath("config.env") @@ -35,12 +40,19 @@ class DeploymentContext: def get_cluster_name(self): return None + def init(self, dir): + self.dir = dir + self.stack = Stack() + self.stack.init_from_file(self.get_stack_file()) + self.spec = Spec() + self.spec.init_from_file(self.get_spec_file()) + @click.group() @click.option("--dir", required=True, help="path to deployment directory") @click.pass_context def command(ctx, dir): - '''create a deployment''' + '''manage a deployment''' # Check that --stack wasn't supplied if ctx.parent.obj.stack: @@ -55,14 +67,19 @@ def command(ctx, dir): print(f"Error: supplied deployment directory path {dir} exists but is a file not a directory") sys.exit(1) # Store the deployment context for subcommands - ctx.obj = DeploymentContext(dir_path) + deployment_context = DeploymentContext() + deployment_context.init(dir_path) + print(f"DEBUG dc: {deployment_context.spec.obj}") + ctx.obj = deployment_context def make_deploy_context(ctx): - stack_file_path = ctx.obj.get_stack_file() - env_file = ctx.obj.get_env_file() - cluster_name = ctx.obj.get_cluster_name() - return create_deploy_context(ctx.parent.parent.obj, stack_file_path, None, None, cluster_name, env_file, deploy_to) + context: DeploymentContext = ctx.obj + stack_file_path = context.get_stack_file() + env_file = context.get_env_file() + cluster_name = context.get_cluster_name() + return create_deploy_context(ctx.parent.parent.obj, stack_file_path, None, None, cluster_name, env_file, + context.spec.obj["deploy-to"]) @command.command() diff --git a/app/deployment_create.py b/app/deployment_create.py index 7f416300..85835343 100644 --- a/app/deployment_create.py +++ b/app/deployment_create.py @@ -249,7 +249,7 @@ def init(ctx, config, output, map_ports_to_host): stack = global_options(ctx).stack debug = global_options(ctx).debug default_spec_file_content = call_stack_deploy_init(ctx.obj) - spec_file_content = {"stack": stack} + spec_file_content = {"stack": stack, "deploy-to": "compose"} if default_spec_file_content: spec_file_content.update(default_spec_file_content) config_variables = _parse_config_variables(config) @@ -315,7 +315,7 @@ def create(ctx, spec_file, deployment_dir, network_dir, initial_peers): sys.exit(1) os.mkdir(deployment_dir) # Copy spec file and the stack file into the deployment dir - copyfile(spec_file, os.path.join(deployment_dir, os.path.basename(spec_file))) + copyfile(spec_file, os.path.join(deployment_dir, "spec.yml")) copyfile(stack_file, os.path.join(deployment_dir, os.path.basename(stack_file))) # Copy any config varibles from the spec file into an env file suitable for compose _write_config_file(spec_file, os.path.join(deployment_dir, "config.env")) diff --git a/app/spec.py b/app/spec.py new file mode 100644 index 00000000..a23bc167 --- /dev/null +++ b/app/spec.py @@ -0,0 +1,30 @@ +# Copyright © 2022, 2023 Vulcanize + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. + +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +from pathlib import Path +import typing +from app.util import get_yaml + + +class Spec: + + obj: typing.Any + + def __init__(self) -> None: + pass + + def init_from_file(self, file_path: Path): + with file_path: + self.obj = get_yaml().load(open(file_path, "r")) diff --git a/app/stack.py b/app/stack.py new file mode 100644 index 00000000..1f94acdf --- /dev/null +++ b/app/stack.py @@ -0,0 +1,30 @@ +# Copyright © 2023 Vulcanize + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. + +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +from pathlib import Path +import typing +from app.util import get_yaml + + +class Stack: + + obj: typing.Any + + def __init__(self) -> None: + pass + + def init_from_file(self, file_path: Path): + with file_path: + self.obj = get_yaml().load(open(file_path, "r")) -- 2.45.2 From 7285bea43f032b2b07f6f0bc33f9701eaa997445 Mon Sep 17 00:00:00 2001 From: David Boreham Date: Mon, 23 Oct 2023 21:17:59 -0600 Subject: [PATCH 08/10] Remove debug code --- app/deployment.py | 1 - 1 file changed, 1 deletion(-) diff --git a/app/deployment.py b/app/deployment.py index 3de8ac7a..36cdecac 100644 --- a/app/deployment.py +++ b/app/deployment.py @@ -69,7 +69,6 @@ def command(ctx, dir): # Store the deployment context for subcommands deployment_context = DeploymentContext() deployment_context.init(dir_path) - print(f"DEBUG dc: {deployment_context.spec.obj}") ctx.obj = deployment_context -- 2.45.2 From 5847cab1c078b6c945556ce0f4cc53bcb1dc3fae Mon Sep 17 00:00:00 2001 From: David Boreham Date: Mon, 23 Oct 2023 21:33:33 -0600 Subject: [PATCH 09/10] Plumb in deployer --- app/deploy.py | 4 ++++ app/deploy_docker.py | 2 ++ app/deploy_k8s.py | 2 ++ app/deployment_create.py | 3 ++- 4 files changed, 10 insertions(+), 1 deletion(-) diff --git a/app/deploy.py b/app/deploy.py index b8173d2a..d11efa00 100644 --- a/app/deploy.py +++ b/app/deploy.py @@ -52,6 +52,10 @@ def command(ctx, include, exclude, env_file, cluster, deploy_to): if ctx.parent.obj.debug: print(f"ctx.parent.obj: {ctx.parent.obj}") + + if deploy_to is None: + deploy_to = "compose" + ctx.obj = create_deploy_context(global_options2(ctx), stack, include, exclude, cluster, env_file, deploy_to) # Subcommand is executed now, by the magic of click diff --git a/app/deploy_docker.py b/app/deploy_docker.py index 0e75ee73..a40c4754 100644 --- a/app/deploy_docker.py +++ b/app/deploy_docker.py @@ -18,6 +18,8 @@ from app.deployer import Deployer, DeployerException class DockerDeployer(Deployer): + name: str = "compose" + def __init__(self, compose_files, compose_project_name, compose_env_file) -> None: self.docker = DockerClient(compose_files=compose_files, compose_project_name=compose_project_name, compose_env_file=compose_env_file) diff --git a/app/deploy_k8s.py b/app/deploy_k8s.py index de26addc..2c3a3a9d 100644 --- a/app/deploy_k8s.py +++ b/app/deploy_k8s.py @@ -18,6 +18,8 @@ from app.deployer import Deployer class K8sDeployer(Deployer): + name: str = "k8s" + def __init__(self, compose_files, compose_project_name, compose_env_file) -> None: config.load_kube_config() self.client = client.CoreV1Api() diff --git a/app/deployment_create.py b/app/deployment_create.py index 85835343..ac16ad46 100644 --- a/app/deployment_create.py +++ b/app/deployment_create.py @@ -249,7 +249,7 @@ def init(ctx, config, output, map_ports_to_host): stack = global_options(ctx).stack debug = global_options(ctx).debug default_spec_file_content = call_stack_deploy_init(ctx.obj) - spec_file_content = {"stack": stack, "deploy-to": "compose"} + spec_file_content = {"stack": stack, "deploy-to": ctx.obj.deployer.name} if default_spec_file_content: spec_file_content.update(default_spec_file_content) config_variables = _parse_config_variables(config) @@ -271,6 +271,7 @@ def init(ctx, config, output, map_ports_to_host): for named_volume in named_volumes: volume_descriptors[named_volume] = f"./data/{named_volume}" spec_file_content["volumes"] = volume_descriptors + print(f"DEBUG spec: {spec_file_content}") with open(output, "w") as output_file: yaml.dump(spec_file_content, output_file) -- 2.45.2 From a52cd73cdb8982381f7505df0df63120d8e6d9be Mon Sep 17 00:00:00 2001 From: David Boreham Date: Mon, 23 Oct 2023 21:37:17 -0600 Subject: [PATCH 10/10] Small fixes --- app/deploy_k8s.py | 2 +- app/deployment_create.py | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/app/deploy_k8s.py b/app/deploy_k8s.py index 2c3a3a9d..2614f725 100644 --- a/app/deploy_k8s.py +++ b/app/deploy_k8s.py @@ -19,7 +19,7 @@ from app.deployer import Deployer class K8sDeployer(Deployer): name: str = "k8s" - + def __init__(self, compose_files, compose_project_name, compose_env_file) -> None: config.load_kube_config() self.client = client.CoreV1Api() diff --git a/app/deployment_create.py b/app/deployment_create.py index ac16ad46..81259795 100644 --- a/app/deployment_create.py +++ b/app/deployment_create.py @@ -271,7 +271,6 @@ def init(ctx, config, output, map_ports_to_host): for named_volume in named_volumes: volume_descriptors[named_volume] = f"./data/{named_volume}" spec_file_content["volumes"] = volume_descriptors - print(f"DEBUG spec: {spec_file_content}") with open(output, "w") as output_file: yaml.dump(spec_file_content, output_file) -- 2.45.2