forked from cerc-io/stack-orchestrator
Merge pull request #180 from cerc-io/dboreham/npm-build-with-package-stack
npm build with package-registry stack
This commit is contained in:
commit
9f07a0ed4b
57
app/base.py
57
app/base.py
@ -13,22 +13,65 @@
|
|||||||
# You should have received a copy of the GNU Affero General Public License
|
# 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/>.
|
# along with this program. If not, see <http:#www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import os
|
||||||
|
from abc import ABC, abstractmethod
|
||||||
|
from .deploy_system import get_stack_status
|
||||||
|
|
||||||
|
|
||||||
def get_stack(config, stack):
|
def get_stack(config, stack):
|
||||||
return base_stack(config, stack)
|
if stack == "package-registry":
|
||||||
|
return package_registry_stack(config, stack)
|
||||||
|
else:
|
||||||
|
return base_stack(config, stack)
|
||||||
|
|
||||||
|
|
||||||
class base_stack():
|
class base_stack(ABC):
|
||||||
|
|
||||||
def __init__(self, config, stack):
|
def __init__(self, config, stack):
|
||||||
self.config = config
|
self.config = config
|
||||||
self.stack = stack
|
self.stack = stack
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
def ensure_available(self):
|
def ensure_available(self):
|
||||||
if self.config.verbose:
|
pass
|
||||||
print(f"Checking that base stack {self.stack} is available")
|
|
||||||
return 1
|
@abstractmethod
|
||||||
|
def get_url(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class package_registry_stack(base_stack):
|
||||||
|
|
||||||
|
def ensure_available(self):
|
||||||
|
self.url = "<no registry url set>"
|
||||||
|
# Check if we were given an external registry URL
|
||||||
|
url_from_environment = os.environ.get("CERC_NPM_REGISTRY_URL")
|
||||||
|
if url_from_environment:
|
||||||
|
if self.config.verbose:
|
||||||
|
print(f"Using package registry url from CERC_NPM_REGISTRY_URL: {url_from_environment}")
|
||||||
|
self.url = url_from_environment
|
||||||
|
else:
|
||||||
|
# Otherwise we expect to use the local package-registry stack
|
||||||
|
# First check if the stack is up
|
||||||
|
registry_running = get_stack_status(self.config, "package-registry")
|
||||||
|
if registry_running:
|
||||||
|
# If it is available, get its mapped port and construct its URL
|
||||||
|
if self.config.debug:
|
||||||
|
print("Found local package registry stack is up")
|
||||||
|
# TODO: get url from deploy-stack
|
||||||
|
self.url = "http://gitea.local:3000/api/packages/cerc-io/npm/"
|
||||||
|
else:
|
||||||
|
# If not, print a message about how to start it and return fail to the caller
|
||||||
|
print("ERROR: The package-registry stack is not running, and no external registry specified with CERC_NPM_REGISTRY_URL")
|
||||||
|
print("ERROR: Start the local package registry with: laconic-so --stack package-registry deploy-system up")
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
def get_url(self):
|
def get_url(self):
|
||||||
return "http://gitea.local:3000/api/packages/cerc-io/npm/"
|
return self.url
|
||||||
|
|
||||||
# TODO: finish this implementation for the npm package registry
|
# Temporary helper functions while we figure out a good interface to the stack deploy code
|
||||||
|
|
||||||
|
|
||||||
|
def _get_stack_mapped_port(stack, service, exposed_port):
|
||||||
|
return 3000
|
||||||
|
@ -44,9 +44,16 @@ def command(ctx, include, exclude):
|
|||||||
|
|
||||||
# build-npms depends on having access to a writable package registry
|
# build-npms depends on having access to a writable package registry
|
||||||
# so we check here that it is available
|
# so we check here that it is available
|
||||||
package_registry_stack = get_stack(ctx.obj, 'package-registry')
|
package_registry_stack = get_stack(ctx.obj, "package-registry")
|
||||||
package_registry_stack.ensure_available()
|
registry_available = package_registry_stack.ensure_available()
|
||||||
npm_registry_url = package_registry_stack.get_url('package-registry')
|
if not registry_available:
|
||||||
|
print("FATAL: no npm registry available for build-npms command")
|
||||||
|
sys.exit(1)
|
||||||
|
npm_registry_url = package_registry_stack.get_url()
|
||||||
|
npm_registry_url_token = config("CERC_NPM_AUTH_TOKEN", default=None)
|
||||||
|
if not npm_registry_url_token:
|
||||||
|
print("FATAL: CERC_NPM_AUTH_TOKEN is not defined")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
if local_stack:
|
if local_stack:
|
||||||
dev_root_path = os.getcwd()[0:os.getcwd().rindex("stack-orchestrator")]
|
dev_root_path = os.getcwd()[0:os.getcwd().rindex("stack-orchestrator")]
|
||||||
@ -86,7 +93,7 @@ def command(ctx, include, exclude):
|
|||||||
if not dry_run:
|
if not dry_run:
|
||||||
if verbose:
|
if verbose:
|
||||||
print(f"Executing: {build_command}")
|
print(f"Executing: {build_command}")
|
||||||
envs = {"CERC_NPM_AUTH_TOKEN": os.environ["CERC_NPM_AUTH_TOKEN"]} | ({"CERC_SCRIPT_DEBUG": "true"} if debug else {})
|
envs = {"CERC_NPM_AUTH_TOKEN": npm_registry_url_token} | ({"CERC_SCRIPT_DEBUG": "true"} if debug else {})
|
||||||
try:
|
try:
|
||||||
docker.run("cerc/builder-js",
|
docker.run("cerc/builder-js",
|
||||||
remove=True,
|
remove=True,
|
||||||
|
6
app/data/stacks/build-support/stack.yml
Normal file
6
app/data/stacks/build-support/stack.yml
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
version: "1.1"
|
||||||
|
name: build-support
|
||||||
|
decription: "Build Support Components"
|
||||||
|
containers:
|
||||||
|
- cerc/builder-js
|
||||||
|
- cerc/builder-gerbil
|
@ -16,6 +16,7 @@
|
|||||||
# Deploys the system components using docker-compose
|
# Deploys the system components using docker-compose
|
||||||
|
|
||||||
import hashlib
|
import hashlib
|
||||||
|
import copy
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
from decouple import config
|
from decouple import config
|
||||||
@ -46,74 +47,10 @@ def command(ctx, include, exclude, cluster, command, extra_args):
|
|||||||
dry_run = ctx.obj.dry_run
|
dry_run = ctx.obj.dry_run
|
||||||
stack = ctx.obj.stack
|
stack = ctx.obj.stack
|
||||||
|
|
||||||
if local_stack:
|
cluster_context = _make_cluster_context(ctx.obj, include, exclude, cluster)
|
||||||
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"))
|
|
||||||
|
|
||||||
# See: https://stackoverflow.com/questions/25389095/python-get-path-of-root-project-structure
|
|
||||||
compose_dir = Path(__file__).absolute().parent.joinpath("data", "compose")
|
|
||||||
|
|
||||||
if cluster is None:
|
|
||||||
# Create default unique, stable cluster name from confile file path
|
|
||||||
# TODO: change this to the config file path
|
|
||||||
path = os.path.realpath(sys.argv[0])
|
|
||||||
hash = hashlib.md5(path.encode()).hexdigest()
|
|
||||||
cluster = f"laconic-{hash}"
|
|
||||||
if verbose:
|
|
||||||
print(f"Using cluster name: {cluster}")
|
|
||||||
|
|
||||||
# See: https://stackoverflow.com/a/20885799/1701505
|
|
||||||
from . import data
|
|
||||||
with importlib.resources.open_text(data, "pod-list.txt") as pod_list_file:
|
|
||||||
all_pods = pod_list_file.read().splitlines()
|
|
||||||
|
|
||||||
pods_in_scope = []
|
|
||||||
if stack:
|
|
||||||
stack_config = get_parsed_stack_config(stack)
|
|
||||||
# TODO: syntax check the input here
|
|
||||||
pods_in_scope = stack_config['pods']
|
|
||||||
else:
|
|
||||||
pods_in_scope = all_pods
|
|
||||||
|
|
||||||
# Convert all pod definitions to v1.1 format
|
|
||||||
pods_in_scope = _convert_to_new_format(pods_in_scope)
|
|
||||||
|
|
||||||
if verbose:
|
|
||||||
print(f"Pods: {pods_in_scope}")
|
|
||||||
|
|
||||||
# Construct a docker compose command suitable for our purpose
|
|
||||||
|
|
||||||
compose_files = []
|
|
||||||
pre_start_commands = []
|
|
||||||
post_start_commands = []
|
|
||||||
for pod in pods_in_scope:
|
|
||||||
pod_name = pod["name"]
|
|
||||||
pod_repository = pod["repository"]
|
|
||||||
pod_path = pod["path"]
|
|
||||||
if include_exclude_check(pod_name, include, exclude):
|
|
||||||
if pod_repository is None or pod_repository == "internal":
|
|
||||||
compose_file_name = os.path.join(compose_dir, f"docker-compose-{pod_path}.yml")
|
|
||||||
else:
|
|
||||||
pod_root_dir = os.path.join(dev_root_path, pod_repository.split("/")[-1], pod["path"])
|
|
||||||
compose_file_name = os.path.join(pod_root_dir, "docker-compose.yml")
|
|
||||||
pod_pre_start_command = pod["pre_start_command"]
|
|
||||||
pod_post_start_command = pod["post_start_command"]
|
|
||||||
if pod_pre_start_command is not None:
|
|
||||||
pre_start_commands.append(os.path.join(pod_root_dir, pod_pre_start_command))
|
|
||||||
if pod_post_start_command is not None:
|
|
||||||
post_start_commands.append(os.path.join(pod_root_dir, pod_post_start_command))
|
|
||||||
compose_files.append(compose_file_name)
|
|
||||||
else:
|
|
||||||
if verbose:
|
|
||||||
print(f"Excluding: {pod_name}")
|
|
||||||
|
|
||||||
if verbose:
|
|
||||||
print(f"files: {compose_files}")
|
|
||||||
|
|
||||||
# See: https://gabrieldemarmiesse.github.io/python-on-whales/sub-commands/compose/
|
# See: https://gabrieldemarmiesse.github.io/python-on-whales/sub-commands/compose/
|
||||||
docker = DockerClient(compose_files=compose_files, compose_project_name=cluster)
|
docker = DockerClient(compose_files=cluster_context.compose_files, compose_project_name=cluster_context.cluster)
|
||||||
|
|
||||||
extra_args_list = list(extra_args) or None
|
extra_args_list = list(extra_args) or None
|
||||||
|
|
||||||
@ -123,11 +60,11 @@ def command(ctx, include, exclude, cluster, command, extra_args):
|
|||||||
os.environ["CERC_SCRIPT_DEBUG"] = "true"
|
os.environ["CERC_SCRIPT_DEBUG"] = "true"
|
||||||
if verbose:
|
if verbose:
|
||||||
print(f"Running compose up for extra_args: {extra_args_list}")
|
print(f"Running compose up for extra_args: {extra_args_list}")
|
||||||
for pre_start_command in pre_start_commands:
|
for pre_start_command in cluster_context.pre_start_commands:
|
||||||
_run_command(ctx.obj, cluster, pre_start_command)
|
_run_command(ctx.obj, cluster_context.cluster, pre_start_command)
|
||||||
docker.compose.up(detach=True, services=extra_args_list)
|
docker.compose.up(detach=True, services=extra_args_list)
|
||||||
for post_start_command in post_start_commands:
|
for post_start_command in cluster_context.post_start_commands:
|
||||||
_run_command(ctx.obj, cluster, post_start_command)
|
_run_command(ctx.obj, cluster_context.cluster, post_start_command)
|
||||||
elif command == "down":
|
elif command == "down":
|
||||||
if verbose:
|
if verbose:
|
||||||
print("Running compose down")
|
print("Running compose down")
|
||||||
@ -181,6 +118,106 @@ def command(ctx, include, exclude, cluster, command, extra_args):
|
|||||||
docker.compose.logs()
|
docker.compose.logs()
|
||||||
|
|
||||||
|
|
||||||
|
def get_stack_status(ctx, stack):
|
||||||
|
|
||||||
|
ctx_copy = copy.copy(ctx)
|
||||||
|
ctx_copy.stack = stack
|
||||||
|
|
||||||
|
cluster_context = _make_cluster_context(ctx_copy, None, None, None)
|
||||||
|
docker = DockerClient(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()
|
||||||
|
if len(container_list) > 0:
|
||||||
|
if ctx.debug:
|
||||||
|
print(f"Container list from compose ps: {container_list}")
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
if ctx.debug:
|
||||||
|
print("No containers found from compose ps")
|
||||||
|
False
|
||||||
|
|
||||||
|
|
||||||
|
def _make_cluster_context(ctx, include, exclude, cluster):
|
||||||
|
|
||||||
|
if ctx.local_stack:
|
||||||
|
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"))
|
||||||
|
|
||||||
|
# See: https://stackoverflow.com/questions/25389095/python-get-path-of-root-project-structure
|
||||||
|
compose_dir = Path(__file__).absolute().parent.joinpath("data", "compose")
|
||||||
|
|
||||||
|
if cluster is None:
|
||||||
|
# Create default unique, stable cluster name from confile file path
|
||||||
|
# TODO: change this to the config file path
|
||||||
|
path = os.path.realpath(sys.argv[0])
|
||||||
|
hash = hashlib.md5(path.encode()).hexdigest()
|
||||||
|
cluster = f"laconic-{hash}"
|
||||||
|
if ctx.verbose:
|
||||||
|
print(f"Using cluster name: {cluster}")
|
||||||
|
|
||||||
|
# See: https://stackoverflow.com/a/20885799/1701505
|
||||||
|
from . import data
|
||||||
|
with importlib.resources.open_text(data, "pod-list.txt") as pod_list_file:
|
||||||
|
all_pods = pod_list_file.read().splitlines()
|
||||||
|
|
||||||
|
pods_in_scope = []
|
||||||
|
if ctx.stack:
|
||||||
|
stack_config = get_parsed_stack_config(ctx.stack)
|
||||||
|
# TODO: syntax check the input here
|
||||||
|
pods_in_scope = stack_config['pods']
|
||||||
|
else:
|
||||||
|
pods_in_scope = all_pods
|
||||||
|
|
||||||
|
# Convert all pod definitions to v1.1 format
|
||||||
|
pods_in_scope = _convert_to_new_format(pods_in_scope)
|
||||||
|
|
||||||
|
if ctx.verbose:
|
||||||
|
print(f"Pods: {pods_in_scope}")
|
||||||
|
|
||||||
|
# Construct a docker compose command suitable for our purpose
|
||||||
|
|
||||||
|
compose_files = []
|
||||||
|
pre_start_commands = []
|
||||||
|
post_start_commands = []
|
||||||
|
for pod in pods_in_scope:
|
||||||
|
pod_name = pod["name"]
|
||||||
|
pod_repository = pod["repository"]
|
||||||
|
pod_path = pod["path"]
|
||||||
|
if include_exclude_check(pod_name, include, exclude):
|
||||||
|
if pod_repository is None or pod_repository == "internal":
|
||||||
|
compose_file_name = os.path.join(compose_dir, f"docker-compose-{pod_path}.yml")
|
||||||
|
else:
|
||||||
|
pod_root_dir = os.path.join(dev_root_path, pod_repository.split("/")[-1], pod["path"])
|
||||||
|
compose_file_name = os.path.join(pod_root_dir, "docker-compose.yml")
|
||||||
|
pod_pre_start_command = pod["pre_start_command"]
|
||||||
|
pod_post_start_command = pod["post_start_command"]
|
||||||
|
if pod_pre_start_command is not None:
|
||||||
|
pre_start_commands.append(os.path.join(pod_root_dir, pod_pre_start_command))
|
||||||
|
if pod_post_start_command is not None:
|
||||||
|
post_start_commands.append(os.path.join(pod_root_dir, pod_post_start_command))
|
||||||
|
compose_files.append(compose_file_name)
|
||||||
|
else:
|
||||||
|
if ctx.verbose:
|
||||||
|
print(f"Excluding: {pod_name}")
|
||||||
|
|
||||||
|
if ctx.verbose:
|
||||||
|
print(f"files: {compose_files}")
|
||||||
|
|
||||||
|
return cluster_context(cluster, compose_files, pre_start_commands, post_start_commands)
|
||||||
|
|
||||||
|
|
||||||
|
class cluster_context:
|
||||||
|
def __init__(self, cluster, compose_files, pre_start_commands, post_start_commands) -> None:
|
||||||
|
self.cluster = cluster
|
||||||
|
self.compose_files = compose_files
|
||||||
|
self.pre_start_commands = pre_start_commands
|
||||||
|
self.post_start_commands = post_start_commands
|
||||||
|
|
||||||
|
|
||||||
def _convert_to_new_format(old_pod_array):
|
def _convert_to_new_format(old_pod_array):
|
||||||
new_pod_array = []
|
new_pod_array = []
|
||||||
for old_pod in old_pod_array:
|
for old_pod in old_pod_array:
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
python-decouple>=3.6
|
python-decouple>=3.6
|
||||||
GitPython>=3.1.27
|
GitPython>=3.1.27
|
||||||
tqdm>=4.64.0
|
tqdm>=4.64.0
|
||||||
python-on-whales>=0.52.0
|
python-on-whales>=0.58.0
|
||||||
click>=8.1.3
|
click>=8.1.3
|
||||||
pyyaml>=6.0
|
pyyaml>=6.0
|
||||||
|
Loading…
Reference in New Issue
Block a user