forked from cerc-io/stack-orchestrator
194 lines
6.6 KiB
Python
194 lines
6.6 KiB
Python
# 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 <http:#www.gnu.org/licenses/>.
|
|
|
|
from decouple import config
|
|
import os.path
|
|
import sys
|
|
import ruamel.yaml
|
|
from pathlib import Path
|
|
from dotenv import dotenv_values
|
|
from typing import Mapping, Set, List
|
|
|
|
|
|
def include_exclude_check(s, include, exclude):
|
|
if include is None and exclude is None:
|
|
return True
|
|
if include is not None:
|
|
include_list = include.split(",")
|
|
return s in include_list
|
|
if exclude is not None:
|
|
exclude_list = exclude.split(",")
|
|
return s not in exclude_list
|
|
|
|
|
|
def get_stack_file_path(stack):
|
|
# In order to be compatible with Python 3.8 we need to use this hack to get the path:
|
|
# See: https://stackoverflow.com/questions/25389095/python-get-path-of-root-project-structure
|
|
stack_file_path = Path(__file__).absolute().parent.joinpath("data", "stacks", stack, "stack.yml")
|
|
return stack_file_path
|
|
|
|
|
|
def get_dev_root_path(ctx):
|
|
if ctx and ctx.local_stack:
|
|
# TODO: This code probably doesn't work
|
|
dev_root_path = os.getcwd()[0:os.getcwd().rindex("stack-orchestrator")]
|
|
print(f'Local stack dev_root_path (CERC_REPO_BASE_DIR) overridden to: {dev_root_path}')
|
|
else:
|
|
dev_root_path = os.path.expanduser(config("CERC_REPO_BASE_DIR", default="~/cerc"))
|
|
return dev_root_path
|
|
|
|
|
|
# Caller can pass either the name of a stack, or a path to a stack file
|
|
def get_parsed_stack_config(stack):
|
|
stack_file_path = stack if isinstance(stack, os.PathLike) else get_stack_file_path(stack)
|
|
try:
|
|
with stack_file_path:
|
|
stack_config = get_yaml().load(open(stack_file_path, "r"))
|
|
return stack_config
|
|
except FileNotFoundError as error:
|
|
# We try here to generate a useful diagnostic error
|
|
# First check if the stack directory is present
|
|
stack_directory = stack_file_path.parent
|
|
if os.path.exists(stack_directory):
|
|
print(f"Error: stack.yml file is missing from stack: {stack}")
|
|
else:
|
|
print(f"Error: stack: {stack} does not exist")
|
|
print(f"Exiting, error: {error}")
|
|
sys.exit(1)
|
|
|
|
|
|
def get_pod_list(parsed_stack):
|
|
# Handle both old and new format
|
|
pods = parsed_stack["pods"]
|
|
if type(pods[0]) is str:
|
|
result = pods
|
|
else:
|
|
result = []
|
|
for pod in pods:
|
|
result.append(pod["name"])
|
|
return result
|
|
|
|
|
|
def get_plugin_code_paths(stack) -> List[Path]:
|
|
parsed_stack = get_parsed_stack_config(stack)
|
|
pods = parsed_stack["pods"]
|
|
result: Set[Path] = set()
|
|
for pod in pods:
|
|
if type(pod) is str:
|
|
result.add(get_stack_file_path(stack).parent)
|
|
else:
|
|
pod_root_dir = os.path.join(get_dev_root_path(None), pod["repository"].split("/")[-1], pod["path"])
|
|
result.add(Path(os.path.join(pod_root_dir, "stack")))
|
|
return list(result)
|
|
|
|
|
|
def get_pod_file_path(parsed_stack, pod_name: str):
|
|
pods = parsed_stack["pods"]
|
|
if type(pods[0]) is str:
|
|
result = os.path.join(get_compose_file_dir(), f"docker-compose-{pod_name}.yml")
|
|
else:
|
|
for pod in pods:
|
|
if pod["name"] == pod_name:
|
|
pod_root_dir = os.path.join(get_dev_root_path(None), pod["repository"].split("/")[-1], pod["path"])
|
|
result = os.path.join(pod_root_dir, "docker-compose.yml")
|
|
return result
|
|
|
|
|
|
def get_pod_script_paths(parsed_stack, pod_name: str):
|
|
pods = parsed_stack["pods"]
|
|
result = []
|
|
if not type(pods[0]) is str:
|
|
for pod in pods:
|
|
if pod["name"] == pod_name:
|
|
pod_root_dir = os.path.join(get_dev_root_path(None), pod["repository"].split("/")[-1], pod["path"])
|
|
if "pre_start_command" in pod:
|
|
result.append(os.path.join(pod_root_dir, pod["pre_start_command"]))
|
|
if "post_start_command" in pod:
|
|
result.append(os.path.join(pod_root_dir, pod["post_start_command"]))
|
|
return result
|
|
|
|
|
|
def pod_has_scripts(parsed_stack, pod_name: str):
|
|
pods = parsed_stack["pods"]
|
|
if type(pods[0]) is str:
|
|
result = False
|
|
else:
|
|
for pod in pods:
|
|
if pod["name"] == pod_name:
|
|
result = "pre_start_command" in pod or "post_start_command" in pod
|
|
return result
|
|
|
|
|
|
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_config_file_dir():
|
|
# TODO: refactor to use common code with deploy command
|
|
data_dir = Path(__file__).absolute().parent.joinpath("data")
|
|
source_config_dir = data_dir.joinpath("config")
|
|
return source_config_dir
|
|
|
|
|
|
def get_parsed_deployment_spec(spec_file):
|
|
spec_file_path = Path(spec_file)
|
|
try:
|
|
with spec_file_path:
|
|
deploy_spec = get_yaml().load(open(spec_file_path, "r"))
|
|
return deploy_spec
|
|
except FileNotFoundError as error:
|
|
# We try here to generate a useful diagnostic error
|
|
print(f"Error: spec file: {spec_file_path} does not exist")
|
|
print(f"Exiting, error: {error}")
|
|
sys.exit(1)
|
|
|
|
|
|
def stack_is_external(stack: str):
|
|
# Bit of a hack: if the supplied stack string represents
|
|
# a path that exists then we assume it must be external
|
|
return Path(stack).exists() if stack is not None else False
|
|
|
|
|
|
def get_yaml():
|
|
# See: https://stackoverflow.com/a/45701840/1701505
|
|
yaml = ruamel.yaml.YAML()
|
|
yaml.preserve_quotes = True
|
|
yaml.indent(sequence=3, offset=1)
|
|
return yaml
|
|
|
|
|
|
# TODO: this is fragile wrt to the subcommand depth
|
|
# See also: https://github.com/pallets/click/issues/108
|
|
def global_options(ctx):
|
|
return ctx.parent.parent.obj
|
|
|
|
|
|
# TODO: hack
|
|
def global_options2(ctx):
|
|
return ctx.parent.obj
|
|
|
|
|
|
def error_exit(s):
|
|
print(f"ERROR: {s}")
|
|
sys.exit(1)
|
|
|
|
|
|
def env_var_map_from_file(file: Path) -> Mapping[str, str]:
|
|
return dotenv_values(file)
|