Skip to content

Commit 36d4969

Browse files
committed
Fixes for external stack deployment (#851)
Fixes - stack path resolution for `build` - external stack path resolution for deployments - "extra" config detection - `deployment ports` command - `version` command in dist or source install (without build_tag.txt) - `setup-repos`, so it won't die when an existing repo is not at a branch or exact tag Used in https://git.vdb.to/cerc-io/fixturenet-eth-stacks/pulls/14 Reviewed-on: https://git.vdb.to/cerc-io/stack-orchestrator/pulls/851 Reviewed-by: David Boreham <[email protected]>
1 parent a2d6201 commit 36d4969

File tree

8 files changed

+80
-74
lines changed

8 files changed

+80
-74
lines changed

setup.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,11 @@
44
long_description = fh.read()
55
with open("requirements.txt", "r", encoding="utf-8") as fh:
66
requirements = fh.read()
7+
with open("stack_orchestrator/data/version.txt", "r", encoding="utf-8") as fh:
8+
version = fh.readlines()[-1].strip(" \n")
79
setup(
810
name='laconic-stack-orchestrator',
9-
version='1.0.12',
11+
version=version,
1012
author='Cerc',
1113
author_email='[email protected]',
1214
license='GNU Affero General Public License',

stack_orchestrator/build/build_util.py

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,23 +21,21 @@
2121

2222
def get_containers_in_scope(stack: str):
2323

24-
# See: https://stackoverflow.com/a/20885799/1701505
25-
from stack_orchestrator import data
26-
with importlib.resources.open_text(data, "container-image-list.txt") as container_list_file:
27-
all_containers = container_list_file.read().splitlines()
28-
2924
containers_in_scope = []
3025
if stack:
3126
stack_config = get_parsed_stack_config(stack)
3227
if "containers" not in stack_config or stack_config["containers"] is None:
3328
warn_exit(f"stack {stack} does not define any containers")
3429
containers_in_scope = stack_config['containers']
3530
else:
36-
containers_in_scope = all_containers
31+
# See: https://stackoverflow.com/a/20885799/1701505
32+
from stack_orchestrator import data
33+
with importlib.resources.open_text(data, "container-image-list.txt") as container_list_file:
34+
containers_in_scope = container_list_file.read().splitlines()
3735

3836
if opts.o.verbose:
3937
print(f'Containers: {containers_in_scope}')
4038
if stack:
4139
print(f"Stack: {stack}")
4240

43-
return containers_in_scope
41+
return containers_in_scope

stack_orchestrator/deploy/deploy.py

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,15 @@
2626
from pathlib import Path
2727
from stack_orchestrator import constants
2828
from stack_orchestrator.opts import opts
29-
from stack_orchestrator.util import include_exclude_check, get_parsed_stack_config, global_options2, get_dev_root_path
30-
from stack_orchestrator.util import resolve_compose_file
29+
from stack_orchestrator.util import (
30+
get_stack_path,
31+
include_exclude_check,
32+
get_parsed_stack_config,
33+
global_options2,
34+
get_dev_root_path,
35+
stack_is_in_deployment,
36+
resolve_compose_file,
37+
)
3138
from stack_orchestrator.deploy.deployer import Deployer, DeployerException
3239
from stack_orchestrator.deploy.deployer_factory import getDeployer
3340
from stack_orchestrator.deploy.deploy_types import ClusterContext, DeployCommandContext
@@ -60,6 +67,7 @@ def command(ctx, include, exclude, env_file, cluster, deploy_to):
6067
if deploy_to is None:
6168
deploy_to = "compose"
6269

70+
stack = get_stack_path(stack)
6371
ctx.obj = create_deploy_context(global_options2(ctx), None, stack, include, exclude, cluster, env_file, deploy_to)
6472
# Subcommand is executed now, by the magic of click
6573

@@ -274,16 +282,12 @@ def _make_default_cluster_name(deployment, compose_dir, stack, include, exclude)
274282

275283
# stack has to be either PathLike pointing to a stack yml file, or a string with the name of a known stack
276284
def _make_cluster_context(ctx, stack, include, exclude, cluster, env_file):
277-
278285
dev_root_path = get_dev_root_path(ctx)
279286

280-
# TODO: huge hack, fix this
281-
# If the caller passed a path for the stack file, then we know that we can get the compose files
282-
# from the same directory
283-
deployment = False
284-
if isinstance(stack, os.PathLike):
285-
compose_dir = stack.parent.joinpath("compose")
286-
deployment = True
287+
# TODO: hack, this should be encapsulated by the deployment context.
288+
deployment = stack_is_in_deployment(stack)
289+
if deployment:
290+
compose_dir = stack.joinpath("compose")
287291
else:
288292
# See: https://stackoverflow.com/questions/25389095/python-get-path-of-root-project-structure
289293
compose_dir = Path(__file__).absolute().parent.parent.joinpath("data", "compose")

stack_orchestrator/deploy/deployment.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,15 +50,15 @@ def command(ctx, dir):
5050

5151
def make_deploy_context(ctx) -> DeployCommandContext:
5252
context: DeploymentContext = ctx.obj
53-
stack_file_path = context.get_stack_file()
5453
env_file = context.get_env_file()
5554
cluster_name = context.get_cluster_id()
5655
if constants.deploy_to_key in context.spec.obj:
5756
deployment_type = context.spec.obj[constants.deploy_to_key]
5857
else:
5958
deployment_type = constants.compose_deploy_type
60-
return create_deploy_context(ctx.parent.parent.obj, context, stack_file_path, None, None, cluster_name, env_file,
61-
deployment_type)
59+
stack = context.deployment_dir
60+
return create_deploy_context(ctx.parent.parent.obj, context, stack, None, None,
61+
cluster_name, env_file, deployment_type)
6262

6363

6464
@command.command()
@@ -123,6 +123,7 @@ def push_images(ctx):
123123
@click.argument('extra_args', nargs=-1) # help: command: port <service1> <service2>
124124
@click.pass_context
125125
def port(ctx, extra_args):
126+
ctx.obj = make_deploy_context(ctx)
126127
port_operation(ctx, extra_args)
127128

128129

stack_orchestrator/deploy/deployment_create.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
import sys
2525
from stack_orchestrator import constants
2626
from stack_orchestrator.opts import opts
27-
from stack_orchestrator.util import (get_stack_file_path, get_parsed_deployment_spec, get_parsed_stack_config,
27+
from stack_orchestrator.util import (get_stack_path, get_parsed_deployment_spec, get_parsed_stack_config,
2828
global_options, get_yaml, get_pod_list, get_pod_file_path, pod_has_scripts,
2929
get_pod_script_paths, get_plugin_code_paths, error_exit, env_var_map_from_file,
3030
resolve_config_dir)
@@ -238,6 +238,11 @@ def _find_extra_config_dirs(parsed_pod_file, pod):
238238
config_dir = host_path.split("/")[2]
239239
if config_dir != pod:
240240
config_dirs.add(config_dir)
241+
for env_file in service_info.get("env_file", []):
242+
if env_file.startswith("../config"):
243+
config_dir = env_file.split("/")[2]
244+
if config_dir != pod:
245+
config_dirs.add(config_dir)
241246
return config_dirs
242247

243248

@@ -454,7 +459,7 @@ def create_operation(deployment_command_context, spec_file, deployment_dir, netw
454459
_check_volume_definitions(parsed_spec)
455460
stack_name = parsed_spec["stack"]
456461
deployment_type = parsed_spec[constants.deploy_to_key]
457-
stack_file = get_stack_file_path(stack_name)
462+
stack_file = get_stack_path(stack_name).joinpath(constants.stack_file_name)
458463
parsed_stack = get_parsed_stack_config(stack_name)
459464
if opts.o.debug:
460465
print(f"parsed spec: {parsed_spec}")
@@ -467,7 +472,7 @@ def create_operation(deployment_command_context, spec_file, deployment_dir, netw
467472
os.mkdir(deployment_dir_path)
468473
# Copy spec file and the stack file into the deployment dir
469474
copyfile(spec_file, deployment_dir_path.joinpath(constants.spec_file_name))
470-
copyfile(stack_file, deployment_dir_path.joinpath(os.path.basename(stack_file)))
475+
copyfile(stack_file, deployment_dir_path.joinpath(constants.stack_file_name))
471476
_create_deployment_file(deployment_dir_path)
472477
# Copy any config varibles from the spec file into an env file suitable for compose
473478
_write_config_file(spec_file, deployment_dir_path.joinpath(constants.config_file_name))

stack_orchestrator/repos/setup_repositories.py

Lines changed: 15 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,12 @@
2020
import sys
2121
from decouple import config
2222
import git
23+
from git.exc import GitCommandError
2324
from tqdm import tqdm
2425
import click
2526
import importlib.resources
26-
from pathlib import Path
27-
import yaml
28-
from stack_orchestrator.constants import stack_file_name
2927
from stack_orchestrator.opts import opts
30-
from stack_orchestrator.util import include_exclude_check, stack_is_external, error_exit, warn_exit
28+
from stack_orchestrator.util import get_parsed_stack_config, include_exclude_check, error_exit, warn_exit
3129

3230

3331
class GitProgress(git.RemoteProgress):
@@ -81,9 +79,13 @@ def _get_repo_current_branch_or_tag(full_filesystem_repo_path):
8179
except TypeError:
8280
# This means that the current ref is not a branch, so possibly a tag
8381
# Let's try to get the tag
84-
current_repo_branch_or_tag = git.Repo(full_filesystem_repo_path).git.describe("--tags", "--exact-match")
85-
# Note that git is assymetric -- the tag you told it to check out may not be the one
86-
# you get back here (if there are multiple tags associated with the same commit)
82+
try:
83+
current_repo_branch_or_tag = git.Repo(full_filesystem_repo_path).git.describe("--tags", "--exact-match")
84+
# Note that git is asymmetric -- the tag you told it to check out may not be the one
85+
# you get back here (if there are multiple tags associated with the same commit)
86+
except GitCommandError:
87+
# If there is no matching branch or tag checked out, just use the current SHA
88+
current_repo_branch_or_tag = git.Repo(full_filesystem_repo_path).commit("HEAD").hexsha
8789
return current_repo_branch_or_tag, is_branch
8890

8991

@@ -102,7 +104,7 @@ def process_repo(pull, check_only, git_ssh, dev_root_path, branches_array, fully
102104
full_filesystem_repo_path
103105
) if is_present else (None, None)
104106
if not opts.o.quiet:
105-
present_text = f"already exists active {'branch' if is_branch else 'tag'}: {current_repo_branch_or_tag}" if is_present \
107+
present_text = f"already exists active {'branch' if is_branch else 'ref'}: {current_repo_branch_or_tag}" if is_present \
106108
else 'Needs to be fetched'
107109
print(f"Checking: {full_filesystem_repo_path}: {present_text}")
108110
# Quick check that it's actually a repo
@@ -120,7 +122,7 @@ def process_repo(pull, check_only, git_ssh, dev_root_path, branches_array, fully
120122
origin = git_repo.remotes.origin
121123
origin.pull(progress=None if opts.o.quiet else GitProgress())
122124
else:
123-
print("skipping pull because this repo checked out a tag")
125+
print("skipping pull because this repo is not on a branch")
124126
else:
125127
print("(git pull skipped)")
126128
if not is_present:
@@ -222,20 +224,10 @@ def command(ctx, include, exclude, git_ssh, check_only, pull, branches):
222224

223225
repos_in_scope = []
224226
if stack:
225-
if stack_is_external(stack):
226-
stack_file_path = Path(stack).joinpath(stack_file_name)
227-
else:
228-
# In order to be compatible with Python 3.8 we need to use this hack to get the path:
229-
# See: https://stackoverflow.com/questions/25389095/python-get-path-of-root-project-structure
230-
stack_file_path = Path(__file__).absolute().parent.parent.joinpath("data", "stacks", stack, stack_file_name)
231-
if not stack_file_path.exists():
232-
error_exit(f"stack {stack} does not exist")
233-
with stack_file_path:
234-
stack_config = yaml.safe_load(open(stack_file_path, "r"))
235-
if "repos" not in stack_config or stack_config["repos"] is None:
236-
warn_exit(f"stack {stack} does not define any repositories")
237-
else:
238-
repos_in_scope = stack_config["repos"]
227+
stack_config = get_parsed_stack_config(stack)
228+
if "repos" not in stack_config or stack_config["repos"] is None:
229+
warn_exit(f"stack {stack} does not define any repositories")
230+
repos_in_scope = stack_config["repos"]
239231
else:
240232
repos_in_scope = all_repos
241233

stack_orchestrator/util.py

Lines changed: 22 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
from pathlib import Path
2121
from dotenv import dotenv_values
2222
from typing import Mapping, Set, List
23+
from stack_orchestrator.constants import stack_file_name, deployment_file_name
2324

2425

2526
def include_exclude_check(s, include, exclude):
@@ -33,11 +34,14 @@ def include_exclude_check(s, include, exclude):
3334
return s not in exclude_list
3435

3536

36-
def get_stack_file_path(stack):
37-
# In order to be compatible with Python 3.8 we need to use this hack to get the path:
38-
# See: https://stackoverflow.com/questions/25389095/python-get-path-of-root-project-structure
39-
stack_file_path = Path(__file__).absolute().parent.joinpath("data", "stacks", stack, "stack.yml")
40-
return stack_file_path
37+
def get_stack_path(stack):
38+
if stack_is_external(stack):
39+
stack_path = Path(stack)
40+
else:
41+
# In order to be compatible with Python 3.8 we need to use this hack to get the path:
42+
# See: https://stackoverflow.com/questions/25389095/python-get-path-of-root-project-structure
43+
stack_path = Path(__file__).absolute().parent.joinpath("data", "stacks", stack)
44+
return stack_path
4145

4246

4347
def get_dev_root_path(ctx):
@@ -52,21 +56,14 @@ def get_dev_root_path(ctx):
5256

5357
# Caller can pass either the name of a stack, or a path to a stack file
5458
def get_parsed_stack_config(stack):
55-
stack_file_path = stack if isinstance(stack, os.PathLike) else get_stack_file_path(stack)
56-
try:
57-
with stack_file_path:
58-
stack_config = get_yaml().load(open(stack_file_path, "r"))
59-
return stack_config
60-
except FileNotFoundError as error:
61-
# We try here to generate a useful diagnostic error
62-
# First check if the stack directory is present
63-
stack_directory = stack_file_path.parent
64-
if os.path.exists(stack_directory):
65-
print(f"Error: stack.yml file is missing from stack: {stack}")
66-
else:
67-
print(f"Error: stack: {stack} does not exist")
68-
print(f"Exiting, error: {error}")
69-
sys.exit(1)
59+
stack_file_path = get_stack_path(stack).joinpath(stack_file_name)
60+
if stack_file_path.exists():
61+
return get_yaml().load(open(stack_file_path, "r"))
62+
# We try here to generate a useful diagnostic error
63+
# First check if the stack directory is present
64+
if stack_file_path.parent.exists():
65+
error_exit(f"stack.yml file is missing from stack: {stack}")
66+
error_exit(f"stack {stack} does not exist")
7067

7168

7269
def get_pod_list(parsed_stack):
@@ -87,7 +84,7 @@ def get_plugin_code_paths(stack) -> List[Path]:
8784
result: Set[Path] = set()
8885
for pod in pods:
8986
if type(pod) is str:
90-
result.add(get_stack_file_path(stack).parent)
87+
result.add(get_stack_path(stack))
9188
else:
9289
pod_root_dir = os.path.join(get_dev_root_path(None), pod["repository"].split("/")[-1], pod["path"])
9390
result.add(Path(os.path.join(pod_root_dir, "stack")))
@@ -199,6 +196,10 @@ def stack_is_external(stack: str):
199196
return Path(stack).exists() if stack is not None else False
200197

201198

199+
def stack_is_in_deployment(stack: Path):
200+
return stack.joinpath(deployment_file_name).exists()
201+
202+
202203
def get_yaml():
203204
# See: https://stackoverflow.com/a/45701840/1701505
204205
yaml = ruamel.yaml.YAML()

stack_orchestrator/version.py

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
# along with this program. If not, see <http:#www.gnu.org/licenses/>.
1515

1616
import click
17-
import importlib.resources
17+
from importlib import resources, metadata
1818

1919

2020
@click.command()
@@ -24,8 +24,11 @@ def command(ctx):
2424

2525
# See: https://stackoverflow.com/a/20885799/1701505
2626
from stack_orchestrator import data
27-
with importlib.resources.open_text(data, "build_tag.txt") as version_file:
28-
# TODO: code better version that skips comment lines
29-
version_string = version_file.read().splitlines()[1]
30-
31-
print(f"Version: {version_string}")
27+
if resources.is_resource(data, "build_tag.txt"):
28+
with resources.open_text(data, "build_tag.txt") as version_file:
29+
# TODO: code better version that skips comment lines
30+
version_string = version_file.read().splitlines()[1]
31+
else:
32+
version_string = metadata.version("laconic-stack-orchestrator") + "-unknown"
33+
34+
print(version_string)

0 commit comments

Comments
 (0)