Skip to content

Commit 2bfdc08

Browse files
authored
Merge pull request #37 from epics-containers/dev
Add support for local docker deployment
2 parents b4af0d5 + 7dedc38 commit 2bfdc08

File tree

17 files changed

+606
-178
lines changed

17 files changed

+606
-178
lines changed

environment.sh

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
#!/bin/bash
2+
3+
# This is an example environment.sh for local docker deployments.
4+
# It is used for testing epics-contianers-cli. It sets up a number of
5+
# environment varaibles used to direct 'ec' commands to the beamline you
6+
# are working on. Typically each beamline repository will have its own.
7+
8+
# check we are sourced
9+
if [ "$0" = "$BASH_SOURCE" ]; then
10+
echo "ERROR: Please source this script"
11+
exit 1
12+
fi
13+
14+
echo "Loading IOC environment for BL45P ..."
15+
16+
# a mapping between genenric IOC repo roots and the related container registry
17+
export EC_REGISTRY_MAPPING='github.com=ghcr.io gitlab.diamond.ac.uk=gcr.io/diamond-privreg/controls/ioc'
18+
19+
# the git organisation used for beamline repositories
20+
export EC_GIT_ORG=https://github.com/epics-containers
21+
22+
# the git rempos used for the current beamline
23+
export EC_DOMAIN_REPO=git@github.com:epics-containers/bl45p.git
24+
25+
# to use kubernetes specify the namespace - otherwise docker local deployments are used
26+
# export EC_K8S_NAMESPACE=bl45p
27+
28+
# enforce a specific container cli (docker or podman)
29+
# export EC_CONTAINER_CLI=podman
30+
31+
# enable debug output in all 'ec' commands
32+
# export EC_DEBUG=1
33+
34+
# declare your centralised log server Web UI
35+
# export EC_LOG_URL='https://graylog2.diamond.ac.uk/search?rangetype=relative&fields=message%2Csource&width=1489&highlightMessage=&relative=172800&q=pod_name%3A{ioc_name}*'
36+
37+
# if you are using kubernetes then this script must enable access to the cluster.
38+
# An example for how this is done at DLS is below.
39+
40+
# # the following configures kubernetes inside DLS.
41+
# if module --version &> /dev/null; then
42+
# if module avail pollux > /dev/null; then
43+
# module unload pollux > /dev/null
44+
# module load pollux > /dev/null
45+
# fi
46+
# fi
47+
48+
# check if epics-containers-cli (ec command) is installed and install if not
49+
if ! ec --version &> /dev/null; then
50+
# must be in a venv and this is the reliable check
51+
if python3 -c 'import sys; sys.exit(0 if sys.base_prefix==sys.prefix else 1)'; then
52+
echo "ERROR: Please activate a virtualenv and re-run"
53+
return
54+
elif ! ec --version &> /dev/null; then
55+
pip install epics-containers-cli
56+
fi
57+
fi
58+
59+
# enable shell completion for ec commands
60+
source <(ec --show-completion ${SHELL})

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ dev = [
3838
"sphinx-design",
3939
"tox-direct",
4040
"types-mock",
41+
"types-requests",
4142
]
4243

4344
[project.scripts]

src/epics_containers_cli/__main__.py

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import os
12
from typing import Optional
23

34
import typer
@@ -9,13 +10,7 @@
910
from .k8s.k8s_cli import cluster
1011
from .k8s.kubectl import fmt_deploys, fmt_pods, fmt_pods_wide
1112
from .logging import init_logging
12-
from .shell import (
13-
EC_DOMAIN_REPO,
14-
EC_GIT_ORG,
15-
EC_K8S_NAMESPACE,
16-
check_domain,
17-
run_command,
18-
)
13+
from .shell import run_command
1914

2015
__all__ = ["main"]
2116

@@ -55,19 +50,19 @@ def main(
5550
help="Log the version of ec and exit",
5651
),
5752
org: str = typer.Option(
58-
EC_GIT_ORG,
53+
"",
5954
"-o",
6055
"--org",
6156
help="git remote organisation of domain repos",
6257
),
6358
repo: str = typer.Option(
64-
EC_DOMAIN_REPO,
59+
"",
6560
"-r",
6661
"--repo",
6762
help="beamline or accelerator domain repository of ioc instances",
6863
),
6964
namespace: str = typer.Option(
70-
EC_K8S_NAMESPACE, "-n", "--namespace", help="kubernetes namespace to use"
65+
"", "-n", "--namespace", help="kubernetes namespace to use"
7166
),
7267
log_level: str = typer.Option(
7368
"WARN", help="Log level (DEBUG, INFO, WARNING, ERROR, CRITICAL)"
@@ -81,6 +76,9 @@ def main(
8176
init_logging(log_level.upper(), debug)
8277

8378
# create a context dictionary to pass to all sub commands
79+
org = org or os.environ.get("EC_GIT_ORG", "")
80+
repo = repo or os.environ.get("EC_DOMAIN_REPO", "")
81+
namespace = namespace or os.environ.get("EC_K8S_NAMESPACE", "")
8482
ctx.ensure_object(Context)
8583
context = Context(namespace, repo, org)
8684
ctx.obj = context
@@ -98,7 +96,6 @@ def ps(
9896
):
9997
"""List the IOCs running in the current domain"""
10098
domain = ctx.obj.namespace
101-
check_domain(domain)
10299

103100
if all:
104101
run_command(f"kubectl -n {domain} get deploy -l is_ioc==True -o {fmt_deploys}")

src/epics_containers_cli/dev/dev_commands.py

Lines changed: 8 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,18 @@
66

77
import typer
88

9+
from epics_containers_cli.git import get_git_name, get_image_name
10+
from epics_containers_cli.logging import log
11+
from epics_containers_cli.shell import EC_CONTAINER_CLI, run_command
12+
from epics_containers_cli.utils import get_instance_image_name
13+
914
from ..globals import (
1015
CONFIG_FOLDER,
1116
IOC_CONFIG_FOLDER,
1217
IOC_START,
1318
Architecture,
1419
Targets,
1520
)
16-
from ..logging import log
17-
from ..shell import EC_CONTAINER_CLI, get_git_name, get_image_name, run_command
1821

1922
dev = typer.Typer()
2023

@@ -185,25 +188,11 @@ def launch(
185188
f" execute={execute} target={target} args={args}"
186189
)
187190

188-
mounts = []
191+
mounts = [f"-v {ioc_instance}/{CONFIG_FOLDER}:{IOC_CONFIG_FOLDER}"]
189192

190-
ioc_instance = ioc_instance.resolve()
191-
values = ioc_instance / "values.yaml"
192-
if not values.exists():
193-
log.error(f"values.yaml not found in {ioc_instance}")
194-
raise typer.Exit(1)
195-
mounts.append(f"-v {ioc_instance}/{CONFIG_FOLDER}:{IOC_CONFIG_FOLDER}")
193+
image_name = image or get_instance_image_name(ioc_instance, tag)
196194

197-
values_text = values.read_text()
198-
matches = re.findall(r"image: (.*):(.*)", values_text)
199-
if len(matches) == 1:
200-
tag = tag or matches[0][1]
201-
image = matches[0][0] + f":{tag}"
202-
else:
203-
log.error(f"image tag definition not found in {values}")
204-
raise typer.Exit(1)
205-
206-
self._do_launch(ioc_name, target, image, execute, args, mounts)
195+
self._do_launch(ioc_name, target, image_name, execute, args, mounts)
207196

208197
def debug_last(self, generic_ioc: Path, mount_repos: bool):
209198
"""

src/epics_containers_cli/git.py

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
"""
2+
Utility functions for working with git
3+
"""
4+
5+
import os
6+
import re
7+
from pathlib import Path
8+
from typing import Tuple
9+
10+
import typer
11+
12+
from epics_containers_cli.globals import Architecture
13+
from epics_containers_cli.logging import log
14+
from epics_containers_cli.shell import (
15+
EC_REGISTRY_MAPPING,
16+
check_beamline_repo,
17+
run_command,
18+
)
19+
20+
21+
def get_image_name(
22+
repo: str, arch: Architecture = Architecture.linux, target: str = "developer"
23+
) -> str:
24+
registry = repo2registry(repo).lower().removesuffix(".git")
25+
26+
image = f"{registry}-{arch}-{target}"
27+
log.info("repo = %s image = %s", repo, image)
28+
return image
29+
30+
31+
def get_git_name(folder: Path = Path(".")) -> Tuple[str, Path]:
32+
"""
33+
work out the git repo name and top level folder for a local clone
34+
"""
35+
os.chdir(folder)
36+
path = str(run_command("git rev-parse --show-toplevel", interactive=False))
37+
git_root = Path(path.strip())
38+
39+
remotes = str(run_command("git remote -v", interactive=False))
40+
log.debug(f"remotes = {remotes}")
41+
42+
matches = re.findall(r"((?:(?:git@)|(?:http[s]+:\/\/)).*) (?:.fetch.)", remotes)
43+
44+
if len(matches) > 0:
45+
repo_name = str(matches[0])
46+
else:
47+
log.error(f"folder {folder.absolute()} cannot parse repo name {remotes}")
48+
raise typer.Exit(1)
49+
50+
log.debug(f"repo_name = {repo_name}, git_root = {git_root}")
51+
return repo_name, git_root
52+
53+
54+
# work out what the registry name is for a given repo remote e.g.
55+
def repo2registry(repo_name: str) -> str:
56+
"""convert a repo name to the related a container registry name"""
57+
58+
log.debug("extracting fields from repo name %s", repo_name)
59+
60+
match_git = re.match(r"git@([^:]*):(.*)\/(.*)(?:.git)", repo_name)
61+
match_http = re.match(r"https:\/\/([^\/]*)\/([^\/]*)\/([^\/]*)", repo_name)
62+
for match in [match_git, match_http]:
63+
if match is not None:
64+
source_reg, org, repo = match.groups()
65+
break
66+
else:
67+
log.error(f"repo {repo_name} is not a valid git remote")
68+
raise typer.Exit(1)
69+
70+
log.debug("source_reg = %s org = %s repo = %s", source_reg, org, repo)
71+
72+
if not EC_REGISTRY_MAPPING:
73+
log.error("environment variable EC_REGISTRY_MAPPING not set")
74+
raise typer.Exit(1)
75+
76+
for mapping in EC_REGISTRY_MAPPING.split():
77+
if mapping.split("=")[0] == source_reg:
78+
registry = mapping.split("=")[1]
79+
registry = f"{registry}/{org}/{repo}"
80+
break
81+
else:
82+
log.error(f"repo {repo_name} does not match any registry mapping")
83+
log.error("please update the environment variable EC_REGISTRY_MAPPING")
84+
raise typer.Exit(1)
85+
86+
return registry
87+
88+
89+
def versions(beamline_repo: str, ioc_name: str, folder: Path):
90+
"""
91+
determine the versions of an IOC instance by discovering the tags in the
92+
beamline repo at which changes to the instance were made since the last
93+
tag
94+
"""
95+
check_beamline_repo(beamline_repo)
96+
typer.echo(f"Available instance versions for {ioc_name}:")
97+
98+
run_command(f"git clone {beamline_repo} {folder}", interactive=False)
99+
100+
ioc_name = Path(ioc_name).name
101+
os.chdir(folder)
102+
result = str(run_command("git tag", interactive=False))
103+
log.debug(f"checking these tags for changes in the instance: {result}")
104+
105+
tags = result.split("\n")
106+
for tag in tags:
107+
if tag == "":
108+
continue
109+
cmd = f"git diff --name-only {tag} {tag}^"
110+
result = str(run_command(cmd, interactive=False))
111+
112+
if ioc_name in result:
113+
typer.echo(f" {tag}")

src/epics_containers_cli/ioc/__init__.py

Whitespace-only changes.

src/epics_containers_cli/ioc/helm.py

Lines changed: 4 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import os
21
import shutil
32
from datetime import datetime
43
from pathlib import Path
@@ -11,6 +10,7 @@
1110
from epics_containers_cli.globals import BEAMLINE_CHART_FOLDER, CONFIG_FOLDER
1211
from epics_containers_cli.logging import log
1312
from epics_containers_cli.shell import run_command
13+
from epics_containers_cli.utils import check_ioc_instance_path
1414

1515

1616
class Helm:
@@ -31,7 +31,7 @@ def __init__(
3131
Create a helm chart from a local or a remote repo
3232
"""
3333
self.ioc_name = ioc_name
34-
self.repo = repo
34+
self.beamline_repo = repo
3535
self.namespace = domain
3636
self.args = args
3737
self.version = version or datetime.strftime(
@@ -59,14 +59,7 @@ def deploy_local(self, ioc_path: Path, yes: bool = False):
5959
Deploy a local IOC helm chart directly to the cluster with dated beta version
6060
"""
6161

62-
ioc_path = ioc_path.absolute()
63-
ioc_name = ioc_path.name.lower()
64-
if (
65-
not (ioc_path / "values.yaml").exists()
66-
or not (ioc_path / CONFIG_FOLDER).is_dir()
67-
):
68-
log.error("ERROR: IOC instance requires values.yaml and config")
69-
raise typer.Exit(1)
62+
ioc_name, ioc_path = check_ioc_instance_path(ioc_path)
7063

7164
if not yes and not self.template:
7265
typer.echo(
@@ -91,7 +84,7 @@ def deploy(self):
9184
raise typer.Exit("ERROR: version is required")
9285

9386
run_command(
94-
f"git clone {self.repo} {self.tmp} --depth=1 "
87+
f"git clone {self.beamline_repo} {self.tmp} --depth=1 "
9588
f"--single-branch --branch={self.version}",
9689
interactive=False,
9790
)
@@ -139,24 +132,3 @@ def _install(
139132

140133
output = run_command(cmd, interactive=False)
141134
typer.echo(output)
142-
143-
def versions(self):
144-
typer.echo(f"Available instance versions for {self.ioc_name}:")
145-
146-
run_command(f"git clone {self.repo} {self.tmp}", interactive=False)
147-
148-
ioc_name = Path(self.ioc_name).name
149-
cmd = "git tag"
150-
os.chdir(self.tmp)
151-
result = run_command(cmd, interactive=False)
152-
log.debug(f"checking these tags for changes in the instance: {result}")
153-
154-
tags = result.split("\n")
155-
for tag in tags:
156-
if tag == "":
157-
continue
158-
cmd = f"git diff --name-only {tag} {tag}^"
159-
result = run_command(cmd, interactive=False)
160-
161-
if ioc_name in result:
162-
typer.echo(f" {tag}")

0 commit comments

Comments
 (0)