Skip to content

Commit 3dc7a5f

Browse files
authored
Merge pull request #47 from epics-containers/dev
Add local docker deployment. Factor out docker calls to Docker class.
2 parents 6177451 + 9cae3c2 commit 3dc7a5f

File tree

16 files changed

+241
-70
lines changed

16 files changed

+241
-70
lines changed

docs/conf.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,7 @@
153153
logo=dict(
154154
text=project,
155155
),
156+
navigation_with_keys=True,
156157
use_edit_page_button=True,
157158
github_url=f"https://github.com/{github_user}/{github_repo}",
158159
icon_links=[

src/epics_containers_cli/__main__.py

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

66
import epics_containers_cli.globals as glob_vars
7+
from epics_containers_cli.ioc.k8s_commands import IocK8sCommands
8+
from epics_containers_cli.ioc.local_commands import IocLocalCommands
79

810
from . import __version__
911
from .dev.dev_cli import dev
1012
from .ioc.ioc_cli import ioc
1113
from .k8s.k8s_cli import cluster
12-
from .k8s.kubectl import fmt_deploys, fmt_pods, fmt_pods_wide
1314
from .logging import init_logging
14-
from .shell import run_command
1515

1616
__all__ = ["main"]
1717

@@ -93,14 +93,11 @@ def ps(
9393
False, "--wide", "-w", help="use a wide format with additional fields"
9494
),
9595
):
96-
"""List the IOCs running in the current domain"""
97-
domain = ctx.obj.namespace
98-
99-
if all:
100-
run_command(f"kubectl -n {domain} get deploy -l is_ioc==True -o {fmt_deploys}")
96+
"""List the IOCs running in the current namespace"""
97+
if ctx.obj.namespace == "":
98+
IocLocalCommands(ctx.obj).ps(all, wide)
10199
else:
102-
format = fmt_pods_wide if wide else fmt_pods
103-
run_command(f"kubectl -n {domain} get pod -l is_ioc==True -o {format}")
100+
IocK8sCommands(ctx.obj).ps(all, wide)
104101

105102

106103
# test with:

src/epics_containers_cli/dev/dev_cli.py

Lines changed: 43 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,13 @@ def launch_local(
5757
IOC project folder.
5858
"""
5959
DevCommands().launch_local(
60-
ioc_instance, generic_ioc, execute, target, tag, args, ioc_name
60+
ioc_instance=ioc_instance,
61+
generic_ioc=generic_ioc,
62+
execute=execute,
63+
target=target,
64+
tag=tag,
65+
args=args,
66+
ioc_name=ioc_name,
6167
)
6268

6369

@@ -67,9 +73,9 @@ def launch(
6773
ioc_instance: Path = typer.Argument(
6874
...,
6975
help="local IOC definition folder from domain repo",
70-
dir_okay=True,
7176
file_okay=False,
7277
exists=True,
78+
resolve_path=True,
7379
),
7480
execute: str = typer.Option(
7581
f"{IOC_START}; bash",
@@ -93,12 +99,22 @@ def launch(
9399
instances. You may find the devcontainer a more convenient way to
94100
do this.
95101
"""
96-
DevCommands().launch(ioc_instance, execute, target, image, tag, args, ioc_name)
102+
DevCommands().launch(
103+
ioc_instance=ioc_instance,
104+
execute=execute,
105+
target=target,
106+
image=image,
107+
tag=tag,
108+
args=args,
109+
ioc_name=ioc_name,
110+
)
97111

98112

99113
@dev.command()
100114
def debug_last(
101-
generic_ioc: Path = typer.Argument(Path("."), help="Container project folder"),
115+
generic_ioc: Path = typer.Argument(
116+
Path("."), help="Container project folder", exists=True, file_okay=False
117+
),
102118
mount_repos: bool = typer.Option(
103119
True, help="Mount generic IOC repo folder into the container"
104120
),
@@ -108,13 +124,15 @@ def debug_last(
108124
Useful for debugging failed builds - if the last build failed it will
109125
start the container after the most recent successful build step.
110126
"""
111-
DevCommands().debug_last(generic_ioc, mount_repos)
127+
DevCommands().debug_last(generic_ioc=generic_ioc, mount_repos=mount_repos)
112128

113129

114130
@dev.command()
115131
def versions(
116132
ctx: typer.Context,
117-
generic_ioc: Path = typer.Argument(Path("."), help="Generic IOC project folder"),
133+
generic_ioc: Path = typer.Argument(
134+
Path("."), help="Generic IOC project folder", exists=True, file_okay=False
135+
),
118136
arch: Architecture = typer.Option(
119137
Architecture.linux, help="choose target architecture"
120138
),
@@ -129,7 +147,7 @@ def versions(
129147
or the local project folder (defaults to .) e.g.
130148
ec dev versions ../ioc-template
131149
"""
132-
DevCommands().versions(generic_ioc, arch, image)
150+
DevCommands().versions(generic_ioc=generic_ioc, arch=arch, image=image)
133151

134152

135153
@dev.command()
@@ -142,24 +160,24 @@ def stop(
142160
"""
143161
Stop a running local IOC container
144162
"""
145-
DevCommands().stop(ioc_name)
163+
DevCommands().stop(ioc_name=ioc_name)
146164

147165

148166
@dev.command()
149167
def exec(
150168
ctx: typer.Context,
151-
ioc_name: str = typer.Option(
152-
IOC_NAME, help="container name override. Use to run multiple instances"
153-
),
154169
command: str = typer.Argument(
155170
"bash", help="command to execute inside the container must be 'single quoted'"
156171
),
172+
ioc_name: str = typer.Option(
173+
IOC_NAME, help="container name override. Use to run multiple instances"
174+
),
157175
args: str = typer.Option("", help="Additional args for exec, 'must be quoted'"),
158176
):
159177
"""
160178
Execute a command inside a running local IOC container
161179
"""
162-
DevCommands().exec(ioc_name, command, args)
180+
DevCommands().exec(ioc_name=ioc_name, command=command, args=args)
163181

164182

165183
@dev.command()
@@ -176,13 +194,15 @@ def wait_pv(
176194
"""
177195
Execute a command inside a running local IOC container
178196
"""
179-
DevCommands().wait_pv(pv_name, ioc_name, attempts)
197+
DevCommands().wait_pv(pv_name=pv_name, ioc_name=ioc_name, attempts=attempts)
180198

181199

182200
@dev.command()
183201
def build(
184202
ctx: typer.Context,
185-
generic_ioc: Path = typer.Option(Path("."), help="Generic IOC project folder"),
203+
generic_ioc: Path = typer.Option(
204+
Path("."), help="Generic IOC project folder", exists=True, file_okay=False
205+
),
186206
tag: str = typer.Option(IMAGE_TAG, help="version tag for the image"),
187207
arch: Architecture = typer.Option(
188208
Architecture.linux, help="choose target architecture"
@@ -200,13 +220,13 @@ def build(
200220
Builds both developer and runtime targets.
201221
"""
202222
DevCommands().build(
203-
generic_ioc,
204-
tag,
205-
arch,
206-
platform,
207-
cache,
208-
cache_to,
209-
cache_from,
210-
push,
211-
rebuild,
223+
generic_ioc=generic_ioc,
224+
tag=tag,
225+
arch=arch,
226+
platform=platform,
227+
cache=cache,
228+
cache_from=cache_from,
229+
cache_to=cache_to,
230+
push=push,
231+
rebuild=rebuild,
212232
)

src/epics_containers_cli/dev/dev_commands.py

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import time
22
from pathlib import Path
3-
from subprocess import CalledProcessError
43
from typing import Optional
54

65
import typer
@@ -158,23 +157,21 @@ def stop(self, ioc_name: str):
158157
"""
159158
self.docker.stop(ioc_name)
160159

161-
def exec(self, command: str, ioc_name: str, args: str = ""):
160+
def exec(self, ioc_name: str, command: str, args: str = ""):
162161
"""
163162
execute a command in a locally running container
164163
"""
165-
self.docker.exec(command, ioc_name, args)
164+
self.docker.exec(container=ioc_name, command=command, args=args)
166165

167166
def wait_pv(self, pv_name: str, ioc_name: str, attempts: int):
168167
"""
169168
wait for a local IOC instance to start by monitoring for a PV
170169
"""
171170
for i in range(attempts):
172-
try:
173-
cmd = f"caget {pv_name}"
174-
result = self.docker.exec(ioc_name, cmd, interactive=False)
175-
except CalledProcessError:
176-
pass # assume the IOC is not running yet
177-
171+
cmd = f"caget {pv_name}"
172+
result = self.docker.exec(
173+
container=ioc_name, command=cmd, interactive=False, errorOK=True
174+
)
178175
if str(result).startswith(pv_name):
179176
break
180177
time.sleep(1)

src/epics_containers_cli/docker.py

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,11 @@
44
import re
55
import sys
66
from pathlib import Path
7+
from time import sleep
78
from typing import List, Optional
89

10+
import typer
11+
912
import epics_containers_cli.globals as glob_vars
1013
from epics_containers_cli.globals import Architecture
1114
from epics_containers_cli.logging import log
@@ -130,15 +133,22 @@ def build(
130133
run_command(f"{cmd} build --target {target}{t_arch} {args} -t {name} {context}")
131134

132135
def exec(
133-
self, container: str, command: str, args: str = "", interactive: bool = True
136+
self,
137+
container: str,
138+
command: str,
139+
args: str = "",
140+
interactive: bool = True,
141+
errorOK: bool = False,
134142
):
135143
"""
136144
execute a command in a local IOC instance
137145
"""
146+
self.is_running(container, error=True)
138147
args = f"{args} " if args else ""
139148
result = run_command(
140149
f'{self.docker} exec {args}{container} bash -c "{command}"',
141150
interactive=interactive,
151+
error_OK=errorOK,
142152
)
143153
return result
144154

@@ -163,13 +173,33 @@ def attach(self, container: str):
163173
"""
164174
attach to a container
165175
"""
176+
self.is_running(container, error=True)
166177
run_command(f"{self.docker} attach {container}")
167178

168179
def logs(self, container: str, previous: bool = False, follow: bool = False):
169180
"""
170181
show logs from a container
171182
"""
183+
self.is_running(container, error=True)
172184
prev = " -p" if previous else ""
173185
fol = " -f" if follow else ""
174186

175-
run_command(f"docker logs{prev}{fol} {container}")
187+
run_command(f"{self.docker} logs{prev}{fol} {container}")
188+
189+
def is_running(self, container: str, retry=1, error=False):
190+
"""
191+
verify that a given container is up and running
192+
"""
193+
for i in range(retry):
194+
result = run_command(
195+
f"{self.docker} ps -f name={container} --format '{{{{.Names}}}}'",
196+
interactive=False,
197+
)
198+
if container in str(result):
199+
return True
200+
sleep(0.5)
201+
else:
202+
if error:
203+
log.error(f"{container} is not running")
204+
raise typer.Exit(1)
205+
return False

src/epics_containers_cli/git.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@ def versions(beamline_repo: str, ioc_name: str, folder: Path):
102102
result = str(run_command("git tag", interactive=False))
103103
log.debug(f"checking these tags for changes in the instance: {result}")
104104

105+
count = 0
105106
tags = result.split("\n")
106107
for tag in tags:
107108
if tag == "":
@@ -111,3 +112,11 @@ def versions(beamline_repo: str, ioc_name: str, folder: Path):
111112

112113
if ioc_name in result:
113114
typer.echo(f" {tag}")
115+
count += 1
116+
117+
if count == 0:
118+
# also look to see if the first tag was when the instance was created
119+
cmd = f"git diff --name-only {tags[0]} $(git hash-object -t tree /dev/null)"
120+
result = str(run_command(cmd, interactive=False))
121+
if ioc_name in result:
122+
typer.echo(f" {tags[0]}")

src/epics_containers_cli/ioc/ioc_cli.py

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,13 @@ def delete(
4242
@ioc.command()
4343
def template(
4444
ctx: typer.Context,
45-
ioc_instance: Path = typer.Argument(..., help="folder of local ioc definition"),
45+
ioc_instance: Path = typer.Argument(
46+
...,
47+
help="folder of local ioc definition",
48+
exists=True,
49+
file_okay=False,
50+
resolve_path=True,
51+
),
4652
args: str = typer.Option("", help="Additional args for helm, 'must be quoted'"),
4753
):
4854
"""
@@ -57,7 +63,13 @@ def template(
5763
@ioc.command()
5864
def deploy_local(
5965
ctx: typer.Context,
60-
ioc_instance: Path = typer.Argument(..., help="folder of local ioc definition"),
66+
ioc_instance: Path = typer.Argument(
67+
...,
68+
help="folder of local ioc definition",
69+
exists=True,
70+
file_okay=False,
71+
resolve_path=True,
72+
),
6173
yes: bool = typer.Option(False, "-y", "--yes", help="Skip confirmation prompt"),
6274
args: str = typer.Option("", help="Additional args for helm, 'must be quoted'"),
6375
):

0 commit comments

Comments
 (0)