Skip to content

Commit 99905bb

Browse files
authored
Merge pull request #52 from epics-containers/dev
use container version of yajsv
2 parents d87344b + 87e5513 commit 99905bb

File tree

12 files changed

+122
-38
lines changed

12 files changed

+122
-38
lines changed

src/epics_containers_cli/__main__.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,12 +94,22 @@ def ps(
9494
),
9595
):
9696
"""List the IOCs running in the current namespace"""
97-
if ctx.obj.namespace == "":
97+
if ctx.obj.namespace == glob_vars.LOCAL_NAMESPACE:
9898
IocLocalCommands(ctx.obj).ps(all, wide)
9999
else:
100100
IocK8sCommands(ctx.obj).ps(all, wide)
101101

102102

103+
@cli.command()
104+
def env(
105+
ctx: typer.Context,
106+
verbose: bool = typer.Option(
107+
False, "-v", "--verbose", help="show all relevant environment variables"
108+
),
109+
):
110+
IocLocalCommands(ctx.obj).environment(verbose == verbose)
111+
112+
103113
# test with:
104114
# python -m epics_containers_cli
105115
if __name__ == "__main__":

src/epics_containers_cli/docker.py

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ def _check_docker(self):
6161
result = run_command(
6262
f"{self.docker} buildx version", interactive=False, error_OK=True
6363
)
64-
self.is_buildx = bool(result) and "buildah" not in result
64+
self.is_buildx = "docker/buildx" in result
6565

6666
log.debug(f"buildx={self.is_buildx} ({result})")
6767

@@ -204,3 +204,19 @@ def is_running(self, container: str, retry=1, error=False):
204204
log.error(f"{container} is not running")
205205
raise typer.Exit(1)
206206
return False
207+
208+
def run_tool(
209+
self, image: str, args: str = "", entrypoint: str = "", interactive=False
210+
):
211+
"""
212+
run a command in a container - mount the current directory
213+
so that the command can see files passed on the CLI
214+
"""
215+
if entrypoint:
216+
entrypoint = f" --entrypoint {entrypoint}"
217+
cwd = Path.cwd().resolve()
218+
mount = f"-w {cwd} -v {cwd}:{cwd} -v /tmp:/tmp"
219+
run_command(
220+
f"{self.docker} run{entrypoint} --rm {mount} {image} {args}",
221+
interactive=True,
222+
)

src/epics_containers_cli/globals.py

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,14 +31,25 @@ def __str__(self):
3131

3232

3333
# common stings used in paths
34+
# folder containing the beamline IOC Instance Helm Chart
3435
BEAMLINE_CHART_FOLDER = "beamline-chart"
36+
# location of IOC Instance configuration in a beamline ioc Instance folder
3537
CONFIG_FOLDER = "config"
36-
CONFIG_FILE = "ioc.yaml"
38+
# location of IOC Instance configuration inside a Generic IOC container
3739
IOC_CONFIG_FOLDER = "/epics/ioc/config/"
40+
# file name of IOC Instance ibek configuration inside a Generic IOC container
41+
CONFIG_FILE = "ioc.yaml"
42+
# location of default IOC start script inside Generic IOC containers
3843
IOC_START = "/epics/ioc/start.sh"
44+
# default container name for local testing
3945
IOC_NAME = "test-ioc"
46+
# namespace name for deploying IOC instances into the local podman/docker
47+
LOCAL_NAMESPACE = "local"
48+
4049
# these should be set to 0 or 1 in the environment - blank is treated as false
50+
# Enable debug logging in all ec commands
4151
EC_DEBUG = bool(os.environ.get("EC_DEBUG", 0))
52+
# Enable printing of all shell commands run by ec
4253
EC_VERBOSE = bool(os.environ.get("EC_VERBOSE", 0))
4354

4455
"""
@@ -60,7 +71,7 @@ def __str__(self):
6071
break and regex and replacement are separated by space
6172
"""
6273
EC_REGISTRY_MAPPING_REGEX = os.environ.get(
63-
"EC_REGISTRY_MAPPING",
74+
"EC_REGISTRY_MAPPING_REGEX",
6475
r"""
6576
.*github.com:(.*)\/(.*)\.git ghcr.io/\1/\2
6677
.*gitlab.diamond.ac.uk.*\/(.*).git gcr.io/diamond-privreg/controls/prod/ioc/\1

src/epics_containers_cli/ioc/helm.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
import epics_containers_cli.globals as glob_vars
1010
from epics_containers_cli.globals import BEAMLINE_CHART_FOLDER, CONFIG_FOLDER
1111
from epics_containers_cli.shell import run_command
12-
from epics_containers_cli.utils import check_ioc_instance_path
12+
from epics_containers_cli.utils import check_ioc_instance_path, log
1313

1414

1515
class Helm:
@@ -86,6 +86,12 @@ def deploy(self):
8686
f"--single-branch --branch={self.version}",
8787
interactive=False,
8888
)
89+
if not self.ioc_config_folder.exists():
90+
log.error(
91+
f"{self.ioc_name} does not exist in {self.beamline_repo} version {self.version}"
92+
)
93+
raise typer.Exit(1)
94+
8995
self._do_deploy(self.ioc_config_folder)
9096

9197
def _do_deploy(self, config_folder: Path):

src/epics_containers_cli/ioc/ioc_cli.py

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import typer
55

66
from epics_containers_cli.git import versions
7+
from epics_containers_cli.globals import LOCAL_NAMESPACE
78
from epics_containers_cli.ioc.k8s_commands import IocK8sCommands
89
from epics_containers_cli.ioc.local_commands import IocLocalCommands
910
from epics_containers_cli.logging import log
@@ -19,7 +20,7 @@ def attach(
1920
"""
2021
Attach to the IOC shell of a live IOC
2122
"""
22-
if ctx.obj.namespace == "":
23+
if ctx.obj.namespace == LOCAL_NAMESPACE:
2324
IocLocalCommands(ctx.obj, ioc_name).attach()
2425
else:
2526
IocK8sCommands(ctx.obj, ioc_name).attach()
@@ -33,7 +34,7 @@ def delete(
3334
"""
3435
Remove an IOC helm deployment from the cluster
3536
"""
36-
if ctx.obj.namespace == "":
37+
if ctx.obj.namespace == LOCAL_NAMESPACE:
3738
IocLocalCommands(ctx.obj, ioc_name).delete()
3839
else:
3940
IocK8sCommands(ctx.obj, ioc_name).delete()
@@ -54,7 +55,7 @@ def template(
5455
"""
5556
print out the helm template generated from a local ioc instance
5657
"""
57-
if ctx.obj.namespace == "":
58+
if ctx.obj.namespace == LOCAL_NAMESPACE:
5859
typer.echo("Not applicable to local deployments")
5960
else:
6061
IocK8sCommands(ctx.obj).template(ioc_instance, args)
@@ -76,7 +77,7 @@ def deploy_local(
7677
"""
7778
Deploy a local IOC helm chart directly to the cluster with dated beta version
7879
"""
79-
if ctx.obj.namespace == "":
80+
if ctx.obj.namespace == LOCAL_NAMESPACE:
8081
IocLocalCommands(ctx.obj).deploy_local(ioc_instance, yes, args)
8182
else:
8283
IocK8sCommands(ctx.obj).deploy_local(ioc_instance, yes, args)
@@ -94,7 +95,7 @@ def deploy(
9495
"""
9596
Pull an IOC helm chart version from the domain repo and deploy it to the cluster
9697
"""
97-
if ctx.obj.namespace == "":
98+
if ctx.obj.namespace == LOCAL_NAMESPACE:
9899
IocLocalCommands(ctx.obj, ioc_name).deploy(ioc_name, version, args)
99100
else:
100101
IocK8sCommands(ctx.obj, ioc_name).deploy(ioc_name, version, args)
@@ -116,7 +117,7 @@ def exec(
116117
ioc_name: str = typer.Argument(..., help="Name of the IOC container to run in"),
117118
):
118119
"""Execute a bash prompt in a live IOC's container"""
119-
if ctx.obj.namespace == "":
120+
if ctx.obj.namespace == LOCAL_NAMESPACE:
120121
IocLocalCommands(ctx.obj, ioc_name).exec()
121122
else:
122123
IocK8sCommands(ctx.obj, ioc_name).exec()
@@ -143,7 +144,7 @@ def logs(
143144
follow: bool = typer.Option(False, "--follow", "-f", help="Follow the log stream"),
144145
):
145146
"""Show logs for current and previous instances of an IOC"""
146-
if ctx.obj.namespace == "":
147+
if ctx.obj.namespace == LOCAL_NAMESPACE:
147148
IocLocalCommands(ctx.obj, ioc_name).logs(prev, follow)
148149
else:
149150
IocK8sCommands(ctx.obj, ioc_name).logs(prev, follow)
@@ -155,7 +156,7 @@ def restart(
155156
ioc_name: str = typer.Argument(..., help="Name of the IOC container to restart"),
156157
):
157158
"""Restart an IOC"""
158-
if ctx.obj.namespace == "":
159+
if ctx.obj.namespace == LOCAL_NAMESPACE:
159160
IocLocalCommands(ctx.obj, ioc_name).restart()
160161
else:
161162
IocK8sCommands(ctx.obj, ioc_name).restart()
@@ -168,7 +169,7 @@ def start(
168169
):
169170
"""Start an IOC"""
170171
log.debug("Starting IOC with LOCAL={ctx.obj.namespace == " "}")
171-
if ctx.obj.namespace == "":
172+
if ctx.obj.namespace == LOCAL_NAMESPACE:
172173
IocLocalCommands(ctx.obj, ioc_name).start()
173174
else:
174175
IocK8sCommands(ctx.obj, ioc_name).start()
@@ -180,7 +181,7 @@ def stop(
180181
ioc_name: str = typer.Argument(..., help="Name of the IOC container to stop"),
181182
):
182183
"""Stop an IOC"""
183-
if ctx.obj.namespace == "":
184+
if ctx.obj.namespace == LOCAL_NAMESPACE:
184185
IocLocalCommands(ctx.obj, ioc_name).stop()
185186
else:
186187
IocK8sCommands(ctx.obj, ioc_name).stop()

src/epics_containers_cli/ioc/k8s_commands.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,14 +29,17 @@ def check_namespace(namespace: Optional[str]):
2929
"""
3030
Verify we have a good namespace that exists in the cluster
3131
"""
32-
if namespace is None:
32+
if not namespace:
3333
log.error("Please set EC_K8S_NAMESPACE or pass --namespace")
3434
raise typer.Exit(1)
3535

3636
cmd = f"kubectl get namespace {namespace} -o name"
3737
result = run_command(cmd, interactive=False, error_OK=True)
3838
if "NotFound" in str(result):
39-
log.error(f"namespace {namespace} not found - please check your environment")
39+
log.error(
40+
f"namespace {namespace} not found - please check "
41+
f"~/.kube/config or change EC_K8S_NAMESPACE"
42+
)
4043
raise typer.Exit(1)
4144

4245
log.info("domain = %s", namespace)

src/epics_containers_cli/ioc/local_commands.py

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,10 @@
2424
CONFIG_FILE,
2525
CONFIG_FOLDER,
2626
IOC_CONFIG_FOLDER,
27+
LOCAL_NAMESPACE,
2728
Context,
2829
)
30+
from epics_containers_cli.ioc.k8s_commands import check_namespace
2931
from epics_containers_cli.logging import log
3032
from epics_containers_cli.shell import check_beamline_repo, run_command
3133
from epics_containers_cli.utils import (
@@ -43,9 +45,11 @@ class IocLocalCommands:
4345
def __init__(
4446
self, ctx: Optional[Context], ioc_name: str = "", with_docker: bool = True
4547
):
48+
self.namespace = ""
4649
self.beamline_repo: str = ""
4750
if ctx is not None:
4851
self.beamline_repo = ctx.beamline_repo
52+
self.namespace = ctx.namespace
4953

5054
self.ioc_name: str = ioc_name
5155

@@ -197,14 +201,10 @@ def validate_instance(self, ioc_instance: Path):
197201
with requests.get(schema_url, allow_redirects=True) as r:
198202
schema_file.write_text(r.content.decode())
199203

200-
if not run_command("yajsv -v", interactive=False, error_OK=True):
201-
typer.echo(
202-
"yajsv, used for schema validation of ioc.yaml, is not installed. "
203-
"Please install from https://github.com/neilpa/yajsv"
204-
)
205-
raise typer.Exit(1)
206-
207-
run_command(f"yajsv -s {schema_file} {ioc_config_file}", interactive=False)
204+
self.docker.run_tool(
205+
image="ghcr.io/epics-containers/yajsv",
206+
args=f"-s {schema_file} {ioc_config_file}",
207+
)
208208

209209
# check that the image name and the schema are from the same generic IOC
210210
if image_tag not in schema_url:
@@ -225,3 +225,18 @@ def validate_instance(self, ioc_instance: Path):
225225
shutil.rmtree(tmp, ignore_errors=True)
226226

227227
typer.echo(f"{ioc_instance} validated successfully")
228+
229+
def environment(self, verbose: bool):
230+
"""
231+
declare the environment settings for ec
232+
"""
233+
ns = self.namespace
234+
235+
if ns == LOCAL_NAMESPACE:
236+
typer.echo("ioc commands deploy to the local docker/podman instance")
237+
else:
238+
check_namespace(ns)
239+
typer.echo(f"ioc commands deploy to the {ns} namespace the K8S cluster")
240+
241+
typer.echo("\nEC environment variables:")
242+
run_command("env | grep '^EC_'", interactive=False, show=True)

src/epics_containers_cli/shell.py

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,21 @@
1414
from .logging import log
1515

1616

17-
def run_command(command: str, interactive=True, error_OK=False) -> Union[str, bool]:
17+
def run_command(
18+
command: str, interactive=True, error_OK=False, show=False
19+
) -> Union[str, bool]:
1820
"""
1921
Run a command and return the output
2022
2123
if interactive is true then allow stdin and stdout, return the return code,
2224
otherwise return True for success and False for failure
25+
26+
args:
27+
28+
command: the command to run
29+
interactive: if True then allow stdin and stdout
30+
error_OK: if True then do not raise an exception on failure
31+
show: print the command output to the console
2332
"""
2433
console = Console(highlight=False, soft_wrap=True)
2534

@@ -28,20 +37,31 @@ def run_command(command: str, interactive=True, error_OK=False) -> Union[str, bo
2837

2938
p_result = subprocess.run(command, capture_output=not interactive, shell=True)
3039

31-
output = "" if interactive else p_result.stdout.decode() + p_result.stderr.decode()
40+
if interactive:
41+
output = error_out = ""
42+
else:
43+
output = p_result.stdout.decode()
44+
error_out = p_result.stderr.decode()
3245

3346
if interactive:
3447
result: Union[str, bool] = p_result.returncode == 0
3548
else:
36-
result = p_result.stdout.decode() + p_result.stderr.decode()
49+
result = output + error_out
3750

3851
if p_result.returncode != 0 and not error_OK:
3952
console.print("\nCommand Failed:", style=Style(color="red", bold=True))
40-
console.print(f"{command}", style=Style(color="pale_turquoise4"))
41-
console.print(output, style=Style(color="red", bold=True))
53+
if not glob_vars.EC_VERBOSE:
54+
console.print(f"{command}", style=Style(color="pale_turquoise4"))
55+
console.print(output, style=Style(color="deep_sky_blue3", bold=True))
56+
console.print(error_out, style=Style(color="red", bold=True))
4257
raise typer.Exit(1)
4358

59+
if show:
60+
console.print(output, style=Style(color="deep_sky_blue3", bold=True))
61+
console.print(error_out, style=Style(color="red", bold=True))
62+
4463
log.debug(f"returning: {result}")
64+
4565
return result
4666

4767

tests/conftest.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,7 @@ def ioc(data):
157157
@fixture()
158158
def local(data):
159159
file = Path(__file__).parent / "data" / "local.yaml"
160-
os.environ["EC_K8S_NAMESPACE"] = ""
160+
os.environ["EC_K8S_NAMESPACE"] = "local"
161161
yaml = YAML(typ="safe").load(file)
162162
return SimpleNamespace(**yaml)
163163

tests/data/local.yaml

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -105,10 +105,7 @@ ps:
105105
rsp: bl45p-ea-ioc-01%2023.10.9%Up%an image
106106

107107
validate:
108-
- cmd: yajsv -v
109-
rsp: "1.4.0"
110-
- cmd: yajsv -s \/tmp\/ec_tests\/schema.json .*\/data\/iocs\/bl45p-ea-ioc-01\/config\/ioc.yaml
111-
rsp: "OK"
112-
- cmd: docker manifest inspect ghcr.io/epics-containers/ioc-adaravis-linux-runtime:23.9.4
108+
- cmd: docker run --rm -w .* -v .*:.* -v \/tmp:\/tmp ghcr.io\/epics-containers\/yajsv -s \/tmp\/ec_tests\/schema.json .*/bl45p-ea-ioc-01\/config\/ioc.yaml
109+
rsp: True
110+
- cmd: docker manifest inspect ghcr.io\/epics-containers\/ioc-adaravis-linux-runtime:23.9.4
113111
rsp: "OK"
114-

0 commit comments

Comments
 (0)