diff --git a/app/data/stacks/mainnet-laconic/deploy/commands.py b/app/data/stacks/mainnet-laconic/deploy/commands.py
index a8a62bd7..0d4f5b8b 100644
--- a/app/data/stacks/mainnet-laconic/deploy/commands.py
+++ b/app/data/stacks/mainnet-laconic/deploy/commands.py
@@ -13,9 +13,10 @@
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see .
-from dataclasses import dataclass
from app.util import get_yaml
+from app.deploy_types import DeployCommandContext, DeploymentContext
from app.stack_state import State
+from app.deploy_util import VolumeMapping, run_container_command
default_spec_file_content = """config:
node_moniker: my-node-name
@@ -25,38 +26,26 @@ default_spec_file_content = """config:
init_help_text = """Add helpful text here on setting config variables.
"""
-@dataclass
-class VolumeMapping:
- host_path: str
- container_path: str
-
-# In order to make this, we need the ability to run the stack
-# In theory we can make this same way as we would run deploy up
-def run_container_command(ctx, ontainer, command, mounts):
- deploy_context = ctx.obj
- pass
-
-
-def setup(ctx):
+def setup(command_context: DeployCommandContext):
node_moniker = "dbdb-node"
chain_id = "laconic_81337-1"
mounts = [
VolumeMapping("./path", "~/.laconicd")
]
- output, status = run_container_command(ctx, "laconicd", f"laconicd init {node_moniker} --chain-id {chain_id}", mounts)
+ output, status = run_container_command(command_context.cluster_context, "laconicd", f"laconicd init {node_moniker} --chain-id {chain_id}", mounts)
-def init(command_context):
+def init(command_context: DeployCommandContext):
print(init_help_text)
yaml = get_yaml()
return yaml.load(default_spec_file_content)
-def get_state(command_context):
+def get_state(command_context: DeployCommandContext):
print("Here we get state")
return State.CONFIGURED
-def change_state(command_context):
+def change_state(command_context: DeployCommandContext):
pass
diff --git a/app/data/stacks/test/deploy/commands.py b/app/data/stacks/test/deploy/commands.py
new file mode 100644
index 00000000..4947f976
--- /dev/null
+++ b/app/data/stacks/test/deploy/commands.py
@@ -0,0 +1,54 @@
+# Copyright © 2022, 2023 Cerc
+
+# 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.util import get_yaml
+from app.deploy_types import DeployCommandContext, DeploymentContext
+from app.stack_state import State
+from app.deploy_util import VolumeMapping, run_container_command
+import os
+from pathlib import Path
+
+default_spec_file_content = """config:
+ config_variable: test-value
+"""
+
+init_help_text = """Add helpful text here on setting config variables.
+"""
+
+# Output a known string to a know file in the bind mounted directory ./container-output-dir
+# for test purposes -- test checks that the file was written.
+def setup(command_context: DeployCommandContext, extra_args):
+ host_directory = "./container-output-dir"
+ host_directory_absolute = Path(extra_args[0]).absolute().joinpath(host_directory)
+ host_directory_absolute.mkdir(parents=True, exist_ok=True)
+ mounts = [
+ VolumeMapping(host_directory_absolute, "/data")
+ ]
+ output, status = run_container_command(command_context, "test", "echo output-data > /data/output-file && echo success", mounts)
+
+
+def init(command_context: DeployCommandContext):
+ print(init_help_text)
+ yaml = get_yaml()
+ return yaml.load(default_spec_file_content)
+
+
+def get_state(command_context: DeployCommandContext):
+ print("Here we get state")
+ return State.CONFIGURED
+
+
+def change_state(command_context: DeployCommandContext):
+ pass
diff --git a/app/deploy.py b/app/deploy.py
index 4ddd81a7..64b3f11c 100644
--- a/app/deploy.py
+++ b/app/deploy.py
@@ -27,17 +27,12 @@ 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
+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
from app.deployment_create import setup as deployment_setup
-class DeployCommandContext(object):
- def __init__(self, cluster_context, docker):
- self.cluster_context = cluster_context
- self.docker = docker
-
-
@click.group()
@click.option("--include", help="only start these components")
@click.option("--exclude", help="don\'t start these components")
@@ -58,7 +53,7 @@ def create_deploy_context(global_context, stack, include, exclude, cluster, env_
# 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(cluster_context, docker)
+ return DeployCommandContext(stack, cluster_context, docker)
def up_operation(ctx, services_list, stay_attached=False):
@@ -313,17 +308,7 @@ def _make_cluster_context(ctx, stack, include, exclude, cluster, env_file):
if ctx.verbose:
print(f"files: {compose_files}")
- return cluster_context(cluster, compose_files, pre_start_commands, post_start_commands, cluster_config, env_file)
-
-
-class cluster_context:
- def __init__(self, cluster, compose_files, pre_start_commands, post_start_commands, config, env_file) -> None:
- self.cluster = cluster
- self.compose_files = compose_files
- self.pre_start_commands = pre_start_commands
- self.post_start_commands = post_start_commands
- self.config = config
- self.env_file = env_file
+ return ClusterContext(cluster, compose_files, pre_start_commands, post_start_commands, cluster_config, env_file)
def _convert_to_new_format(old_pod_array):
diff --git a/app/deploy_types.py b/app/deploy_types.py
new file mode 100644
index 00000000..c6df5784
--- /dev/null
+++ b/app/deploy_types.py
@@ -0,0 +1,47 @@
+# Copyright © 2023 Cerc
+
+# 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 typing import List
+from dataclasses import dataclass
+from pathlib import Path
+from python_on_whales import DockerClient
+
+@dataclass
+class ClusterContext:
+ cluster: str
+ compose_files: List[str]
+ pre_start_commands: List[str]
+ post_start_commands: List[str]
+ config: str
+ env_file: str
+
+
+@dataclass
+class DeployCommandContext:
+ stack: str
+ cluster_context: ClusterContext
+ docker: DockerClient
+
+
+@dataclass
+class DeploymentContext:
+ deployment_dir: Path
+ command_context: DeployCommandContext
+
+
+@dataclass
+class VolumeMapping:
+ host_path: str
+ container_path: str
diff --git a/app/deploy_util.py b/app/deploy_util.py
new file mode 100644
index 00000000..814f8001
--- /dev/null
+++ b/app/deploy_util.py
@@ -0,0 +1,56 @@
+# Copyright © 2022, 2023 Cerc
+
+# 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 .
+
+import os
+from typing import List
+from dataclasses import dataclass
+from app.deploy_types import DeployCommandContext, VolumeMapping
+from app.util import get_parsed_stack_config, get_yaml, get_compose_file_dir
+
+
+def _container_image_from_service(stack:str, service: str):
+ # Parse the compose files looking for the image name of the specified service
+ image_name = None
+ parsed_stack = get_parsed_stack_config(stack)
+ pods = parsed_stack["pods"]
+ yaml = get_yaml()
+ for pod in pods:
+ pod_file_path = os.path.join(get_compose_file_dir(), f"docker-compose-{pod}.yml")
+ parsed_pod_file = yaml.load(open(pod_file_path, "r"))
+ if "services" in parsed_pod_file:
+ services = parsed_pod_file["services"]
+ if service in services:
+ service_definition = services[service]
+ if "image" in service_definition:
+ image_name = service_definition["image"]
+ return image_name
+
+
+def _volumes_to_docker(mounts: List[VolumeMapping]):
+# Example from doc: [("/", "/host"), ("/etc/hosts", "/etc/hosts", "rw")]
+ result = []
+ for mount in mounts:
+ docker_volume = (mount.host_path, mount.container_path)
+ result.append(docker_volume)
+ return result
+
+
+def run_container_command(ctx: DeployCommandContext, service: str, command: str, mounts: List[VolumeMapping]):
+ docker = ctx.docker
+ container_image = _container_image_from_service(ctx.stack, service)
+ docker_volumes = _volumes_to_docker(mounts)
+ docker_output = docker.run(container_image, ["-c", command], entrypoint="bash", volumes=docker_volumes)
+ # There doesn't seem to be a way to get an exit code from docker.run()
+ return (docker_output, 0)
diff --git a/app/deployment_create.py b/app/deployment_create.py
index 652bbdba..47f96e34 100644
--- a/app/deployment_create.py
+++ b/app/deployment_create.py
@@ -20,26 +20,14 @@ import os
from pathlib import Path
from shutil import copyfile, copytree
import sys
-from app.util import get_stack_file_path, get_parsed_deployment_spec, get_parsed_stack_config, global_options, get_yaml
-
-@dataclass
-class DeploymentContext:
- stack: str
- deployment_dir: Path
+from app.util import get_stack_file_path, get_parsed_deployment_spec, get_parsed_stack_config, global_options, get_yaml, get_compose_file_dir
+from app.deploy_types import DeploymentContext, DeployCommandContext
def _make_default_deployment_dir():
return "deployment-001"
-def _get_compose_file_dir():
- # TODO: refactor to use common code with deploy command
- # See: https://stackoverflow.com/questions/25389095/python-get-path-of-root-project-structure
- data_dir = Path(__file__).absolute().parent.joinpath("data")
- source_compose_dir = data_dir.joinpath("compose")
- return source_compose_dir
-
-
def _get_named_volumes(stack):
# Parse the compose files looking for named volumes
named_volumes = []
@@ -47,7 +35,7 @@ def _get_named_volumes(stack):
pods = parsed_stack["pods"]
yaml = get_yaml()
for pod in pods:
- pod_file_path = os.path.join(_get_compose_file_dir(), f"docker-compose-{pod}.yml")
+ pod_file_path = os.path.join(get_compose_file_dir(), f"docker-compose-{pod}.yml")
parsed_pod_file = yaml.load(open(pod_file_path, "r"))
if "volumes" in parsed_pod_file:
volumes = parsed_pod_file["volumes"]
@@ -94,27 +82,27 @@ def _fixup_pod_file(pod, spec, compose_dir):
pod["volumes"][volume] = new_volume_spec
-def call_stack_deploy_init(stack):
+def call_stack_deploy_init(deploy_command_context):
# Link with the python file in the stack
# Call a function in it
# If no function found, return None
- python_file_path = get_stack_file_path(stack).parent.joinpath("deploy", "commands.py")
+ python_file_path = get_stack_file_path(deploy_command_context.stack).parent.joinpath("deploy", "commands.py")
spec = util.spec_from_file_location("commands", python_file_path)
imported_stack = util.module_from_spec(spec)
spec.loader.exec_module(imported_stack)
- return imported_stack.init(None)
+ return imported_stack.init(deploy_command_context)
# TODO: fold this with function above
-def call_stack_deploy_setup(stack):
+def call_stack_deploy_setup(deploy_command_context, extra_args):
# Link with the python file in the stack
# Call a function in it
# If no function found, return None
- python_file_path = get_stack_file_path(stack).parent.joinpath("deploy", "commands.py")
+ python_file_path = get_stack_file_path(deploy_command_context.stack).parent.joinpath("deploy", "commands.py")
spec = util.spec_from_file_location("commands", python_file_path)
imported_stack = util.module_from_spec(spec)
spec.loader.exec_module(imported_stack)
- return imported_stack.setup(None)
+ return imported_stack.setup(deploy_command_context, extra_args)
# TODO: fold this with function above
@@ -154,7 +142,7 @@ def init(ctx, output):
yaml = get_yaml()
stack = global_options(ctx).stack
verbose = global_options(ctx).verbose
- default_spec_file_content = call_stack_deploy_init(stack)
+ default_spec_file_content = call_stack_deploy_init(ctx.obj)
spec_file_content = {"stack": stack}
if default_spec_file_content:
spec_file_content.update(default_spec_file_content)
@@ -217,7 +205,7 @@ def create(ctx, spec_file, deployment_dir):
if not os.path.exists(destination_config_dir):
copytree(source_config_dir, destination_config_dir)
# Delegate to the stack's Python code
- deployment_context = DeploymentContext(stack_name, Path(deployment_dir))
+ deployment_context = DeploymentContext(Path(deployment_dir), ctx.obj)
call_stack_deploy_create(deployment_context)
@@ -227,7 +215,7 @@ def create(ctx, spec_file, deployment_dir):
@click.option("--initialize-network", is_flag=True, default=False, help="Help goes here")
@click.option("--join-network", is_flag=True, default=False, help="Help goes here")
@click.option("--create-network", is_flag=True, default=False, help="Help goes here")
+@click.argument('extra_args', nargs=-1)
@click.pass_context
-def setup(ctx, node_moniker, key_name, initialize_network, join_network, create_network):
- stack = global_options(ctx).stack
- call_stack_deploy_setup(stack)
+def setup(ctx, node_moniker, key_name, initialize_network, join_network, create_network, extra_args):
+ call_stack_deploy_setup(ctx.obj, extra_args)
diff --git a/app/util.py b/app/util.py
index 2b12cfbc..42a4673e 100644
--- a/app/util.py
+++ b/app/util.py
@@ -56,6 +56,14 @@ def get_parsed_stack_config(stack):
sys.exit(1)
+def get_compose_file_dir():
+ # TODO: refactor to use common code with deploy command
+ # See: https://stackoverflow.com/questions/25389095/python-get-path-of-root-project-structure
+ data_dir = Path(__file__).absolute().parent.joinpath("data")
+ source_compose_dir = data_dir.joinpath("compose")
+ return source_compose_dir
+
+
def get_parsed_deployment_spec(spec_file):
spec_file_path = Path(spec_file)
try:
diff --git a/tests/deploy/run-deploy-test.sh b/tests/deploy/run-deploy-test.sh
index 8e84a6b5..70eb0e18 100755
--- a/tests/deploy/run-deploy-test.sh
+++ b/tests/deploy/run-deploy-test.sh
@@ -23,6 +23,26 @@ mkdir -p $CERC_REPO_BASE_DIR
# with and without volume removal
$TEST_TARGET_SO --stack test setup-repositories
$TEST_TARGET_SO --stack test build-containers
+# Test deploy command execution
+$TEST_TARGET_SO --stack test deploy setup $CERC_REPO_BASE_DIR
+# Check that we now have the expected output directory
+if [ ! -d "$CERC_REPO_BASE_DIR/container-output-dir" ]; then
+ echo "deploy setup test: output directory not present"
+ echo "deploy setup test: FAILED"
+ exit 1
+fi
+if [ ! -f "$CERC_REPO_BASE_DIR/container-output-dir/output-file" ]; then
+ echo "deploy setup test: output file not present"
+ echo "deploy setup test: FAILED"
+ exit 1
+fi
+output_file_content=$(<$CERC_REPO_BASE_DIR/container-output-dir/output-file)
+if [ ! "$output_file_content" == "output-data" ]; then
+ echo "deploy setup test: output file contents not correct"
+ echo "deploy setup test: FAILED"
+ exit 1
+fi
+# Check that we now have the expected output file
$TEST_TARGET_SO --stack test deploy up
# Test deploy port command
deploy_port_output=$( $TEST_TARGET_SO --stack test deploy port test 80 )