edge-containers-cli 4.0.0__tar.gz → 4.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (101) hide show
  1. {edge_containers_cli-4.0.0 → edge_containers_cli-4.1.0}/PKG-INFO +1 -1
  2. {edge_containers_cli-4.0.0 → edge_containers_cli-4.1.0}/src/edge_containers_cli/__main__.py +9 -2
  3. {edge_containers_cli-4.0.0 → edge_containers_cli-4.1.0}/src/edge_containers_cli/_version.py +2 -2
  4. {edge_containers_cli-4.0.0 → edge_containers_cli-4.1.0}/src/edge_containers_cli/backend.py +11 -1
  5. {edge_containers_cli-4.0.0 → edge_containers_cli-4.1.0}/src/edge_containers_cli/cli.py +12 -2
  6. {edge_containers_cli-4.0.0 → edge_containers_cli-4.1.0}/src/edge_containers_cli/cmds/argo_commands.py +42 -8
  7. {edge_containers_cli-4.0.0 → edge_containers_cli-4.1.0}/src/edge_containers_cli/cmds/commands.py +20 -18
  8. {edge_containers_cli-4.0.0 → edge_containers_cli-4.1.0}/src/edge_containers_cli/cmds/k8s_commands.py +7 -2
  9. {edge_containers_cli-4.0.0 → edge_containers_cli-4.1.0}/src/edge_containers_cli/cmds/monitor.py +2 -2
  10. {edge_containers_cli-4.0.0 → edge_containers_cli-4.1.0}/src/edge_containers_cli/git.py +28 -2
  11. {edge_containers_cli-4.0.0 → edge_containers_cli-4.1.0}/src/edge_containers_cli/utils.py +45 -0
  12. {edge_containers_cli-4.0.0 → edge_containers_cli-4.1.0}/src/edge_containers_cli.egg-info/PKG-INFO +1 -1
  13. {edge_containers_cli-4.0.0 → edge_containers_cli-4.1.0}/src/edge_containers_cli.egg-info/SOURCES.txt +16 -13
  14. {edge_containers_cli-4.0.0 → edge_containers_cli-4.1.0}/tests/conftest.py +1 -1
  15. edge_containers_cli-4.1.0/tests/data/argocd.yaml +83 -0
  16. edge_containers_cli-4.1.0/tests/data/bl01t-deployment/apps/values.yaml +3 -0
  17. {edge_containers_cli-4.0.0 → edge_containers_cli-4.1.0}/tests/data/k8s.yaml +3 -3
  18. edge_containers_cli-4.1.0/tests/data/yaml.yaml +12 -0
  19. {edge_containers_cli-4.0.0 → edge_containers_cli-4.1.0}/tests/test_argocd.py +22 -2
  20. {edge_containers_cli-4.0.0 → edge_containers_cli-4.1.0}/tests/test_cli.py +2 -2
  21. {edge_containers_cli-4.0.0 → edge_containers_cli-4.1.0}/tests/test_k8s.py +5 -3
  22. edge_containers_cli-4.1.0/tests/test_unit.py +30 -0
  23. edge_containers_cli-4.0.0/tests/data/argocd.yaml +0 -45
  24. {edge_containers_cli-4.0.0 → edge_containers_cli-4.1.0}/.copier-answers.yml +0 -0
  25. {edge_containers_cli-4.0.0 → edge_containers_cli-4.1.0}/.devcontainer/devcontainer.json +0 -0
  26. {edge_containers_cli-4.0.0 → edge_containers_cli-4.1.0}/.github/CONTRIBUTING.md +0 -0
  27. {edge_containers_cli-4.0.0 → edge_containers_cli-4.1.0}/.github/actions/install_requirements/action.yml +0 -0
  28. {edge_containers_cli-4.0.0 → edge_containers_cli-4.1.0}/.github/dependabot.yml +0 -0
  29. {edge_containers_cli-4.0.0 → edge_containers_cli-4.1.0}/.github/pages/index.html +0 -0
  30. {edge_containers_cli-4.0.0 → edge_containers_cli-4.1.0}/.github/pages/make_switcher.py +0 -0
  31. {edge_containers_cli-4.0.0 → edge_containers_cli-4.1.0}/.github/workflows/_check.yml +0 -0
  32. {edge_containers_cli-4.0.0 → edge_containers_cli-4.1.0}/.github/workflows/_dist.yml +0 -0
  33. {edge_containers_cli-4.0.0 → edge_containers_cli-4.1.0}/.github/workflows/_pypi.yml +0 -0
  34. {edge_containers_cli-4.0.0 → edge_containers_cli-4.1.0}/.github/workflows/_release.yml +0 -0
  35. {edge_containers_cli-4.0.0 → edge_containers_cli-4.1.0}/.github/workflows/_test.yml +0 -0
  36. {edge_containers_cli-4.0.0 → edge_containers_cli-4.1.0}/.github/workflows/_tox.yml +0 -0
  37. {edge_containers_cli-4.0.0 → edge_containers_cli-4.1.0}/.github/workflows/ci.yml +0 -0
  38. {edge_containers_cli-4.0.0 → edge_containers_cli-4.1.0}/.gitignore +0 -0
  39. {edge_containers_cli-4.0.0 → edge_containers_cli-4.1.0}/.pre-commit-config.yaml +0 -0
  40. {edge_containers_cli-4.0.0 → edge_containers_cli-4.1.0}/.vscode/extensions.json +0 -0
  41. {edge_containers_cli-4.0.0 → edge_containers_cli-4.1.0}/.vscode/launch.json +0 -0
  42. {edge_containers_cli-4.0.0 → edge_containers_cli-4.1.0}/.vscode/settings.json +0 -0
  43. {edge_containers_cli-4.0.0 → edge_containers_cli-4.1.0}/.vscode/tasks.json +0 -0
  44. {edge_containers_cli-4.0.0 → edge_containers_cli-4.1.0}/Dockerfile +0 -0
  45. {edge_containers_cli-4.0.0 → edge_containers_cli-4.1.0}/LICENSE +0 -0
  46. {edge_containers_cli-4.0.0 → edge_containers_cli-4.1.0}/README.md +0 -0
  47. {edge_containers_cli-4.0.0 → edge_containers_cli-4.1.0}/catalog-info.yaml +0 -0
  48. {edge_containers_cli-4.0.0 → edge_containers_cli-4.1.0}/docs/conf.py +0 -0
  49. {edge_containers_cli-4.0.0 → edge_containers_cli-4.1.0}/docs/developer/explanations/decisions/0001-record-architecture-decisions.rst +0 -0
  50. {edge_containers_cli-4.0.0 → edge_containers_cli-4.1.0}/docs/developer/explanations/decisions/0002-switched-to-pip-skeleton.rst +0 -0
  51. {edge_containers_cli-4.0.0 → edge_containers_cli-4.1.0}/docs/developer/explanations/decisions.rst +0 -0
  52. {edge_containers_cli-4.0.0 → edge_containers_cli-4.1.0}/docs/developer/how-to/build-docs.rst +0 -0
  53. {edge_containers_cli-4.0.0 → edge_containers_cli-4.1.0}/docs/developer/how-to/contribute.rst +0 -0
  54. {edge_containers_cli-4.0.0 → edge_containers_cli-4.1.0}/docs/developer/how-to/lint.rst +0 -0
  55. {edge_containers_cli-4.0.0 → edge_containers_cli-4.1.0}/docs/developer/how-to/make-release.rst +0 -0
  56. {edge_containers_cli-4.0.0 → edge_containers_cli-4.1.0}/docs/developer/how-to/pin-requirements.rst +0 -0
  57. {edge_containers_cli-4.0.0 → edge_containers_cli-4.1.0}/docs/developer/how-to/run-tests.rst +0 -0
  58. {edge_containers_cli-4.0.0 → edge_containers_cli-4.1.0}/docs/developer/how-to/static-analysis.rst +0 -0
  59. {edge_containers_cli-4.0.0 → edge_containers_cli-4.1.0}/docs/developer/how-to/test-container.rst +0 -0
  60. {edge_containers_cli-4.0.0 → edge_containers_cli-4.1.0}/docs/developer/how-to/update-tools.rst +0 -0
  61. {edge_containers_cli-4.0.0 → edge_containers_cli-4.1.0}/docs/developer/index.rst +0 -0
  62. {edge_containers_cli-4.0.0 → edge_containers_cli-4.1.0}/docs/developer/reference/standards.rst +0 -0
  63. {edge_containers_cli-4.0.0 → edge_containers_cli-4.1.0}/docs/developer/tutorials/dev-install.rst +0 -0
  64. {edge_containers_cli-4.0.0 → edge_containers_cli-4.1.0}/docs/genindex.rst +0 -0
  65. {edge_containers_cli-4.0.0 → edge_containers_cli-4.1.0}/docs/images/dls-favicon.ico +0 -0
  66. {edge_containers_cli-4.0.0 → edge_containers_cli-4.1.0}/docs/images/dls-logo.svg +0 -0
  67. {edge_containers_cli-4.0.0 → edge_containers_cli-4.1.0}/docs/index.rst +0 -0
  68. {edge_containers_cli-4.0.0 → edge_containers_cli-4.1.0}/docs/user/explanations/docs-structure.rst +0 -0
  69. {edge_containers_cli-4.0.0 → edge_containers_cli-4.1.0}/docs/user/how-to/run-container.rst +0 -0
  70. {edge_containers_cli-4.0.0 → edge_containers_cli-4.1.0}/docs/user/index.rst +0 -0
  71. {edge_containers_cli-4.0.0 → edge_containers_cli-4.1.0}/docs/user/reference/api.rst +0 -0
  72. {edge_containers_cli-4.0.0 → edge_containers_cli-4.1.0}/docs/user/tutorials/installation.rst +0 -0
  73. {edge_containers_cli-4.0.0 → edge_containers_cli-4.1.0}/pyproject.toml +0 -0
  74. {edge_containers_cli-4.0.0 → edge_containers_cli-4.1.0}/setup.cfg +0 -0
  75. {edge_containers_cli-4.0.0 → edge_containers_cli-4.1.0}/src/edge_containers_cli/__init__.py +0 -0
  76. {edge_containers_cli-4.0.0 → edge_containers_cli-4.1.0}/src/edge_containers_cli/autocomplete.py +0 -0
  77. {edge_containers_cli-4.0.0 → edge_containers_cli-4.1.0}/src/edge_containers_cli/cmds/__init__.py +0 -0
  78. {edge_containers_cli-4.0.0 → edge_containers_cli-4.1.0}/src/edge_containers_cli/cmds/helm.py +0 -0
  79. {edge_containers_cli-4.0.0 → edge_containers_cli-4.1.0}/src/edge_containers_cli/cmds/monitor.tcss +0 -0
  80. {edge_containers_cli-4.0.0 → edge_containers_cli-4.1.0}/src/edge_containers_cli/definitions.py +0 -0
  81. {edge_containers_cli-4.0.0 → edge_containers_cli-4.1.0}/src/edge_containers_cli/globals.py +0 -0
  82. {edge_containers_cli-4.0.0 → edge_containers_cli-4.1.0}/src/edge_containers_cli/logging.py +0 -0
  83. {edge_containers_cli-4.0.0 → edge_containers_cli-4.1.0}/src/edge_containers_cli/shell.py +0 -0
  84. {edge_containers_cli-4.0.0 → edge_containers_cli-4.1.0}/src/edge_containers_cli.egg-info/dependency_links.txt +0 -0
  85. {edge_containers_cli-4.0.0 → edge_containers_cli-4.1.0}/src/edge_containers_cli.egg-info/entry_points.txt +0 -0
  86. {edge_containers_cli-4.0.0 → edge_containers_cli-4.1.0}/src/edge_containers_cli.egg-info/requires.txt +0 -0
  87. {edge_containers_cli-4.0.0 → edge_containers_cli-4.1.0}/src/edge_containers_cli.egg-info/top_level.txt +0 -0
  88. {edge_containers_cli-4.0.0 → edge_containers_cli-4.1.0}/tests/__init__.py +0 -0
  89. {edge_containers_cli-4.0.0/tests/data/bl01t → edge_containers_cli-4.1.0/tests/data/bl01t-services}/services/bl01t-ea-test-01/Chart.yaml +0 -0
  90. {edge_containers_cli-4.0.0/tests/data/bl01t → edge_containers_cli-4.1.0/tests/data/bl01t-services}/services/bl01t-ea-test-01/config/ioc.db +0 -0
  91. {edge_containers_cli-4.0.0/tests/data/bl01t → edge_containers_cli-4.1.0/tests/data/bl01t-services}/services/bl01t-ea-test-01/config/ioc.yaml +0 -0
  92. {edge_containers_cli-4.0.0/tests/data/bl01t → edge_containers_cli-4.1.0/tests/data/bl01t-services}/services/bl01t-ea-test-01/templates/configmap.yaml +0 -0
  93. {edge_containers_cli-4.0.0/tests/data/bl01t → edge_containers_cli-4.1.0/tests/data/bl01t-services}/services/bl01t-ea-test-01/values.yaml +0 -0
  94. {edge_containers_cli-4.0.0/tests/data/bl01t → edge_containers_cli-4.1.0/tests/data/bl01t-services}/services/bl01t-ea-test-02/Chart.yaml +0 -0
  95. {edge_containers_cli-4.0.0/tests/data/bl01t → edge_containers_cli-4.1.0/tests/data/bl01t-services}/services/bl01t-ea-test-02/values.yaml +0 -0
  96. {edge_containers_cli-4.0.0/tests/data/bl01t → edge_containers_cli-4.1.0/tests/data/bl01t-services}/services/dls-aravis/Chart.yaml +0 -0
  97. {edge_containers_cli-4.0.0/tests/data/bl01t → edge_containers_cli-4.1.0/tests/data/bl01t-services}/services/dls-aravis/config/ioc.yaml +0 -0
  98. {edge_containers_cli-4.0.0/tests/data/bl01t → edge_containers_cli-4.1.0/tests/data/bl01t-services}/services/dls-aravis/templates/configmap.yaml +0 -0
  99. {edge_containers_cli-4.0.0/tests/data/bl01t → edge_containers_cli-4.1.0/tests/data/bl01t-services}/services/dls-aravis/values.yaml +0 -0
  100. {edge_containers_cli-4.0.0/tests/data/bl01t → edge_containers_cli-4.1.0/tests/data/bl01t-services}/services/values.yaml +0 -0
  101. {edge_containers_cli-4.0.0 → edge_containers_cli-4.1.0}/tests/data/cli.yaml +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: edge-containers-cli
3
- Version: 4.0.0
3
+ Version: 4.1.0
4
4
  Summary: CLI for deploying and managing epics containers IOCs and services
5
5
  Author-email: Giles Knap <giles.knap@gmail.com>
6
6
  License: Apache License
@@ -23,15 +23,22 @@ def version_callback(value: bool):
23
23
 
24
24
  def backend_callback(ctx: typer.Context, backend: ECBackends):
25
25
  init_backend(backend)
26
+
26
27
  # Dynamically drop any method not implemented
27
28
  not_implemented = [
28
- mthd.replace("_", "-") for mthd in ec_backend.get_notimplemented()
29
+ mthd.replace("_", "-") for mthd in ec_backend.get_notimplemented_cmds()
29
30
  ]
31
+ typer_commands = ctx.command.commands # type: ignore
30
32
  for command in not_implemented:
31
- typer_commands = ctx.command.commands # type: ignore
32
33
  if command in typer_commands:
33
34
  typer_commands.pop(command)
34
35
 
36
+ # Dynamically drop any cli options as specified
37
+ for cmd_name, drop_params in ec_backend.get_notimplemented_params().items():
38
+ for index, param in enumerate(typer_commands[cmd_name].params):
39
+ if param.name in drop_params:
40
+ typer_commands[cmd_name].params.pop(index)
41
+
35
42
  return backend.value
36
43
 
37
44
 
@@ -12,5 +12,5 @@ __version__: str
12
12
  __version_tuple__: VERSION_TUPLE
13
13
  version_tuple: VERSION_TUPLE
14
14
 
15
- __version__ = version = '4.0.0'
16
- __version_tuple__ = version_tuple = (4, 0, 0)
15
+ __version__ = version = '4.1.0'
16
+ __version_tuple__ = version_tuple = (4, 1, 0)
@@ -20,6 +20,13 @@ class Backend:
20
20
  self._Commands: type | None = None
21
21
  self._commands: Commands | None = None
22
22
 
23
+ @property
24
+ def Commands(self):
25
+ if self._Commands is None:
26
+ raise BackendError("Backend commands not set")
27
+ else:
28
+ return self._Commands
29
+
23
30
  @property
24
31
  def commands(self):
25
32
  if self._commands is None:
@@ -44,7 +51,7 @@ class Backend:
44
51
  self._cxt = context
45
52
  self._commands = self._Commands(context)
46
53
 
47
- def get_notimplemented(self) -> list[str]:
54
+ def get_notimplemented_cmds(self) -> list[str]:
48
55
  notimplemented = []
49
56
  if self._Commands is None:
50
57
  return []
@@ -54,6 +61,9 @@ class Backend:
54
61
  notimplemented.append(command)
55
62
  return notimplemented
56
63
 
64
+ def get_notimplemented_params(self) -> dict[str, list[str]]:
65
+ return self.Commands.params_opt_out
66
+
57
67
 
58
68
  backend = Backend()
59
69
 
@@ -234,9 +234,14 @@ def start(
234
234
  service_name: str = typer.Argument(
235
235
  ..., help="Name of the service container to start", autocompletion=all_svc
236
236
  ),
237
+ temp: bool = typer.Option(False, help="Directly overrides the controller values"),
237
238
  ):
238
239
  """Start a service"""
239
- backend.commands.start(service_name)
240
+ try:
241
+ backend.commands.start(service_name, temp)
242
+ except GitError as e:
243
+ msg = f"{str(e)} - Try 'ec start <service> --temp' to bypass the git server"
244
+ raise GitError(msg) from e
240
245
 
241
246
 
242
247
  @cli.command()
@@ -246,9 +251,14 @@ def stop(
246
251
  help="Name of the service container to stop",
247
252
  autocompletion=running_svc,
248
253
  ),
254
+ temp: bool = typer.Option(False, help="Directly overrides the controller values"),
249
255
  ):
250
256
  """Stop a service"""
251
- backend.commands.stop(service_name)
257
+ try:
258
+ backend.commands.stop(service_name, temp)
259
+ except GitError as e:
260
+ msg = f"{str(e)} - Try ec stop <service> --temp to bypass the git server"
261
+ raise GitError(msg) from e
252
262
 
253
263
 
254
264
  @cli.command()
@@ -6,12 +6,14 @@ Relies on the Helm class for deployment aspects.
6
6
 
7
7
  import webbrowser
8
8
  from datetime import datetime
9
+ from pathlib import Path
9
10
 
10
11
  import polars
11
12
  from ruamel.yaml import YAML
12
13
 
13
14
  from edge_containers_cli.cmds.commands import CommandError, Commands, ServicesDataFrame
14
15
  from edge_containers_cli.definitions import ECContext
16
+ from edge_containers_cli.git import set_values
15
17
  from edge_containers_cli.globals import TIME_FORMAT
16
18
  from edge_containers_cli.shell import ShellError, shell
17
19
 
@@ -21,6 +23,36 @@ def extract_ns_app(target: str) -> tuple[str, str]:
21
23
  return namespace, app
22
24
 
23
25
 
26
+ def patch_value(target: str, key: str, value: str | bool | int):
27
+ cmd_temp_ = f"argocd app set {target} -p {key}={value}"
28
+ shell.run_command(cmd_temp_, skip_on_dryrun=True)
29
+ cmd_sync = f"argocd app sync {target} --apply-out-of-sync-only"
30
+ shell.run_command(cmd_sync, skip_on_dryrun=True)
31
+
32
+
33
+ def push_value(target: str, key: str, value: str | bool | int):
34
+ # Get source details
35
+ app_resp = shell.run_command(
36
+ f"argocd app get {target} -o yaml",
37
+ )
38
+ app_dicts = YAML(typ="safe").load(app_resp)
39
+ repo_url = app_dicts["spec"]["source"]["repoURL"]
40
+ path = Path(app_dicts["spec"]["source"]["path"])
41
+
42
+ set_values(
43
+ repo_url,
44
+ path / "values.yaml",
45
+ key,
46
+ value,
47
+ )
48
+
49
+ # Sync & release a possible patched value
50
+ cmd_sync = f"argocd app sync {target} --apply-out-of-sync-only"
51
+ shell.run_command(cmd_sync, skip_on_dryrun=True)
52
+ cmd_unset = f"argocd app unset {target} -p {key}"
53
+ shell.run_command(cmd_unset, skip_on_dryrun=True)
54
+
55
+
24
56
  class ArgoCommands(Commands):
25
57
  """
26
58
  A class for implementing the Kubernetes based commands
@@ -52,17 +84,19 @@ class ArgoCommands(Commands):
52
84
  )
53
85
  shell.run_command(cmd, skip_on_dryrun=True)
54
86
 
55
- def start(self, service_name):
87
+ def start(self, service_name, temp):
56
88
  self._check_service(service_name)
57
- namespace, app = extract_ns_app(self.target)
58
- cmd = f"argocd app set {namespace}/{service_name} -p global.enabled=true"
59
- shell.run_command(cmd, skip_on_dryrun=True)
89
+ if temp:
90
+ patch_value(self.target, f"ec_services.{service_name}.enabled", True)
91
+ else:
92
+ push_value(self.target, f"ec_services.{service_name}.enabled", True)
60
93
 
61
- def stop(self, service_name):
94
+ def stop(self, service_name, temp):
62
95
  self._check_service(service_name)
63
- namespace, app = extract_ns_app(self.target)
64
- cmd = f"argocd app set {namespace}/{service_name} -p global.enabled=false"
65
- shell.run_command(cmd, skip_on_dryrun=True)
96
+ if temp:
97
+ patch_value(self.target, f"ec_services.{service_name}.enabled", False)
98
+ else:
99
+ push_value(self.target, f"ec_services.{service_name}.enabled", False)
66
100
 
67
101
  def _get_logs(self, service_name, prev) -> str:
68
102
  namespace, app = extract_ns_app(self.target)
@@ -39,6 +39,8 @@ class Commands(ABC):
39
39
  Methods not exposed to the CLI should be private
40
40
  """
41
41
 
42
+ params_opt_out: dict[str, list[str]] = {}
43
+
42
44
  def __init__(self, ctx: ECContext):
43
45
  self._target = ctx.target
44
46
  self._target_valid = False
@@ -72,50 +74,50 @@ class Commands(ABC):
72
74
  log.debug("log_url = %s", self._log_url)
73
75
  return self._log_url
74
76
 
75
- def attach(self, service_name: str):
77
+ def attach(self, service_name: str) -> None:
76
78
  raise NotImplementedError
77
79
 
78
- def delete(self, service_name: str):
80
+ def delete(self, service_name: str) -> None:
79
81
  raise NotImplementedError
80
82
 
81
- def deploy(self, service_name: str, version: str, args: str):
83
+ def deploy(self, service_name: str, version: str, args: str) -> None:
82
84
  raise NotImplementedError
83
85
 
84
- def deploy_local(self, svc_instance: Path, args: str):
86
+ def deploy_local(self, svc_instance: Path, args: str) -> None:
85
87
  raise NotImplementedError
86
88
 
87
- def exec(self, service_name: str):
89
+ def exec(self, service_name: str) -> None:
88
90
  raise NotImplementedError
89
91
 
90
- def logs(self, service_name: str, prev: bool):
92
+ def logs(self, service_name: str, prev: bool) -> None:
91
93
  raise NotImplementedError
92
94
 
93
- def log_history(self, service_name: str):
95
+ def log_history(self, service_name: str) -> None:
94
96
  raise NotImplementedError
95
97
 
96
- def ps(self, running_only: bool):
98
+ def ps(self, running_only: bool) -> None:
97
99
  raise NotImplementedError
98
100
 
99
101
  @abstractmethod
100
- def restart(self, service_name: str):
102
+ def restart(self, service_name: str) -> None:
101
103
  raise NotImplementedError
102
104
 
103
105
  @abstractmethod
104
- def start(self, service_name: str):
106
+ def start(self, service_name: str, temp: bool) -> None:
105
107
  raise NotImplementedError
106
108
 
107
109
  @abstractmethod
108
- def stop(self, service_name: str):
110
+ def stop(self, service_name: str, temp: bool) -> None:
109
111
  raise NotImplementedError
110
112
 
111
- def template(self, svc_instance: Path, args: str):
113
+ def template(self, svc_instance: Path, args: str) -> None:
112
114
  raise NotImplementedError
113
115
 
114
116
  @abstractmethod
115
117
  def _get_services(self, running_only: bool) -> ServicesDataFrame:
116
118
  raise NotImplementedError
117
119
 
118
- def _ps(self, running_only: bool):
120
+ def _ps(self, running_only: bool) -> None:
119
121
  services_df = self._get_services(running_only)
120
122
  print(services_df)
121
123
 
@@ -123,19 +125,19 @@ class Commands(ABC):
123
125
  def _get_logs(self, service_name: str, prev: bool) -> str:
124
126
  raise NotImplementedError
125
127
 
126
- def _logs(self, service_name: str, prev: bool):
128
+ def _logs(self, service_name: str, prev: bool) -> None:
127
129
  print(self._get_logs(service_name, prev))
128
130
 
129
- def _validate_target(self):
131
+ def _validate_target(self) -> None:
130
132
  raise NotImplementedError
131
133
 
132
- def _running_services(self):
134
+ def _running_services(self) -> list[str]:
133
135
  return self._get_services(running_only=True)["name"].to_list()
134
136
 
135
- def _all_services(self):
137
+ def _all_services(self) -> list[str]:
136
138
  return self._get_services(running_only=False)["name"].to_list()
137
139
 
138
- def _check_service(self, service_name: str):
140
+ def _check_service(self, service_name: str) -> None:
139
141
  services_list = self._get_services(running_only=False)["name"]
140
142
  if service_name in services_list:
141
143
  pass
@@ -23,6 +23,11 @@ class K8sCommands(Commands):
23
23
  A class for implementing the Kubernetes based commands
24
24
  """
25
25
 
26
+ params_opt_out = {
27
+ "stop": ["temp"],
28
+ "start": ["temp"],
29
+ }
30
+
26
31
  def __init__(
27
32
  self,
28
33
  ctx: ECContext,
@@ -84,14 +89,14 @@ class K8sCommands(Commands):
84
89
  f"kubectl delete -n {self.target} {pod_name}", skip_on_dryrun=True
85
90
  )
86
91
 
87
- def start(self, service_name):
92
+ def start(self, service_name, temp):
88
93
  self._check_service(service_name)
89
94
  shell.run_command(
90
95
  f"kubectl scale -n {self.target} statefulset {service_name} --replicas=1",
91
96
  skip_on_dryrun=True,
92
97
  )
93
98
 
94
- def stop(self, service_name):
99
+ def stop(self, service_name, temp):
95
100
  self._check_service(service_name)
96
101
  shell.run_command(
97
102
  f"kubectl scale -n {self.target} statefulset {service_name} --replicas=0 ",
@@ -446,7 +446,7 @@ class MonitorApp(App):
446
446
  def check_start(start: bool | None) -> None:
447
447
  """Called when StartScreen is dismissed."""
448
448
  if start:
449
- self.commands.start(service_name)
449
+ self.commands.start(service_name, False)
450
450
 
451
451
  self.push_screen(StartScreen(service_name), check_start)
452
452
 
@@ -457,7 +457,7 @@ class MonitorApp(App):
457
457
  def check_stop(stop: bool | None) -> None:
458
458
  """Called when StopScreen is dismissed."""
459
459
  if stop:
460
- self.commands.stop(service_name)
460
+ self.commands.stop(service_name, False)
461
461
 
462
462
  self.push_screen(StopScreen(service_name), check_stop)
463
463
 
@@ -9,14 +9,40 @@ import polars
9
9
  from natsort import natsorted
10
10
 
11
11
  from edge_containers_cli.logging import log
12
- from edge_containers_cli.shell import shell
13
- from edge_containers_cli.utils import chdir, new_workdir
12
+ from edge_containers_cli.shell import ShellError, shell
13
+ from edge_containers_cli.utils import YamlFile, chdir, new_workdir
14
14
 
15
15
 
16
16
  class GitError(Exception):
17
17
  pass
18
18
 
19
19
 
20
+ def set_values(repo_url: str, file: Path, key: str, value: str | bool | int) -> None:
21
+ """
22
+ sets an existing key value pair in a yaml file and push the changes
23
+ """
24
+ with new_workdir() as path:
25
+ try:
26
+ shell.run_command(f"git clone --depth=1 {repo_url} {path}")
27
+ with chdir(path): # From python 3.11 can use contextlib.chdir(working_dir)
28
+ file_data = YamlFile(file)
29
+
30
+ value_repo = file_data.get_key(key)
31
+ if value_repo == value:
32
+ log.debug(f"{key} already set as {value}")
33
+ else:
34
+ file_data.set_key(key, value)
35
+ file_data.dump_file()
36
+
37
+ commit_msg = f"Set {key}={value} in {file}"
38
+ shell.run_command("git add .")
39
+ shell.run_command(f'git commit -m "{commit_msg}"')
40
+ shell.run_command("git push", skip_on_dryrun=True)
41
+
42
+ except (FileNotFoundError, ShellError) as e:
43
+ raise GitError(str(e)) from e
44
+
45
+
20
46
  def create_version_map(
21
47
  repo: str, root_dir: Path, working_dir: Path, shared: list[str] | None = None
22
48
  ) -> dict[str, list[str]]:
@@ -12,6 +12,8 @@ from datetime import datetime
12
12
  from pathlib import Path
13
13
  from typing import TypeVar
14
14
 
15
+ from ruamel.yaml import YAML
16
+
15
17
  import edge_containers_cli.globals as globals
16
18
  from edge_containers_cli.logging import log
17
19
 
@@ -112,3 +114,46 @@ def read_cached_dict(cache_folder: Path, cache_file: str) -> dict:
112
114
  read_dict = json.load(f)
113
115
 
114
116
  return read_dict
117
+
118
+
119
+ class YamlFile:
120
+ def __init__(self, file: Path) -> None:
121
+ self.file = file
122
+ self._processor = YAML(typ="rt") # 'rt' slower but preserves comments
123
+ with open(file) as fp:
124
+ self._yaml_data = self._processor.load(fp)
125
+
126
+ def dump_file(self, output_path: Path | None = None):
127
+ with open(output_path if output_path else self.file, "wb") as file_w:
128
+ self._processor.dump(self._yaml_data, file_w)
129
+
130
+ def get_key(self, key_path: str) -> str | bool | int | None:
131
+ curser = self._yaml_data
132
+ for key in key_path.split("."):
133
+ try:
134
+ curser = curser[key]
135
+ except KeyError:
136
+ log.debug(f"Entry '{key}' in '{key_path}' not found")
137
+ return None
138
+ return curser
139
+
140
+ def set_key(self, key_path: str, value: str | bool | int):
141
+ curser = self._yaml_data
142
+ keys = key_path.split(".")
143
+ element = keys[-1]
144
+
145
+ # Iterate through mappings to element - Entries must exist
146
+ for key in keys:
147
+ if key is element:
148
+ break # Keep dict as pointer
149
+ curser = curser[key]
150
+
151
+ # Set element if exists or create it
152
+ try:
153
+ curser[element] = type(curser[element])(value) # Preserve type
154
+ except KeyError:
155
+ # Create element if does not exist
156
+ log.debug(f"Entry '{element}' in '{key_path}' not found - Creating")
157
+ curser[element] = value
158
+
159
+ log.debug(f"Set '{element}' in '{key_path}' to {value}")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: edge-containers-cli
3
- Version: 4.0.0
3
+ Version: 4.1.0
4
4
  Summary: CLI for deploying and managing epics containers IOCs and services
5
5
  Author-email: Giles Knap <giles.knap@gmail.com>
6
6
  License: Apache License
@@ -78,19 +78,22 @@ tests/conftest.py
78
78
  tests/test_argocd.py
79
79
  tests/test_cli.py
80
80
  tests/test_k8s.py
81
+ tests/test_unit.py
81
82
  tests/data/argocd.yaml
82
83
  tests/data/cli.yaml
83
84
  tests/data/k8s.yaml
84
- tests/data/bl01t/services/values.yaml
85
- tests/data/bl01t/services/bl01t-ea-test-01/Chart.yaml
86
- tests/data/bl01t/services/bl01t-ea-test-01/values.yaml
87
- tests/data/bl01t/services/bl01t-ea-test-01/config/ioc.db
88
- tests/data/bl01t/services/bl01t-ea-test-01/config/ioc.yaml
89
- tests/data/bl01t/services/bl01t-ea-test-01/templates/configmap.yaml
90
- tests/data/bl01t/services/bl01t-ea-test-02/Chart.yaml
91
- tests/data/bl01t/services/bl01t-ea-test-02/values.yaml
92
- tests/data/bl01t/services/bl01t-ea-test-02/charts/dls-aravis
93
- tests/data/bl01t/services/dls-aravis/Chart.yaml
94
- tests/data/bl01t/services/dls-aravis/values.yaml
95
- tests/data/bl01t/services/dls-aravis/config/ioc.yaml
96
- tests/data/bl01t/services/dls-aravis/templates/configmap.yaml
85
+ tests/data/yaml.yaml
86
+ tests/data/bl01t-deployment/apps/values.yaml
87
+ tests/data/bl01t-services/services/values.yaml
88
+ tests/data/bl01t-services/services/bl01t-ea-test-01/Chart.yaml
89
+ tests/data/bl01t-services/services/bl01t-ea-test-01/values.yaml
90
+ tests/data/bl01t-services/services/bl01t-ea-test-01/config/ioc.db
91
+ tests/data/bl01t-services/services/bl01t-ea-test-01/config/ioc.yaml
92
+ tests/data/bl01t-services/services/bl01t-ea-test-01/templates/configmap.yaml
93
+ tests/data/bl01t-services/services/bl01t-ea-test-02/Chart.yaml
94
+ tests/data/bl01t-services/services/bl01t-ea-test-02/values.yaml
95
+ tests/data/bl01t-services/services/bl01t-ea-test-02/charts/dls-aravis
96
+ tests/data/bl01t-services/services/dls-aravis/Chart.yaml
97
+ tests/data/bl01t-services/services/dls-aravis/values.yaml
98
+ tests/data/bl01t-services/services/dls-aravis/config/ioc.yaml
99
+ tests/data/bl01t-services/services/dls-aravis/templates/configmap.yaml
@@ -179,7 +179,7 @@ def K8S(mocker, data):
179
179
  "EC_LOG_URL": "https://graylog2.diamond.ac.uk/{service_name}*",
180
180
  "EC_TARGET": "bl01t",
181
181
  "EC_CLI_BACKEND": "K8S",
182
- "EC_SERVICES_REPO": "https://github.com/epics-containers/bl01t",
182
+ "EC_SERVICES_REPO": "https://github.com/epics-containers/bl01t-services",
183
183
  },
184
184
  )
185
185
  file = Path(__file__).parent / "data" / "k8s.yaml"
@@ -0,0 +1,83 @@
1
+ checks:
2
+ - cmd: argocd app get namespace/bl01t
3
+ rsp: ""
4
+ - cmd: argocd app list -l "ec_service=true" --app-namespace namespace -o yaml
5
+ rsp: |
6
+ - metadata:
7
+ creationTimestamp: "2024-07-12T13:42:50Z"
8
+ name: bl01t-ea-test-01
9
+ spec:
10
+ source:
11
+ targetRevision: main
12
+ status:
13
+ resources:
14
+ - kind: StatefulSet
15
+ name: bl01t-ea-test-01
16
+ - cmd: argocd app manifests namespace/bl01t-ea-test-01 --source live
17
+ rsp: |
18
+ ---
19
+ apiVersion: apps/v1
20
+ kind: StatefulSet
21
+ metadata:
22
+ name: bl01t-ea-test-01
23
+ creationTimestamp: "2024-07-12T13:52:35Z"
24
+ status:
25
+ readyReplicas: 1
26
+
27
+ logs:
28
+ - cmd: argocd app logs namespace/bl01t-ea-test-01
29
+ rsp: ""
30
+
31
+ log_history:
32
+ - cmd: https://graylog2.diamond.ac.uk/bl01t-ea-test-01*
33
+ rsp: True
34
+
35
+ restart:
36
+ - cmd: argocd app delete-resource namespace/bl01t-ea-test-01 --kind StatefulSet
37
+ rsp: ""
38
+
39
+ start:
40
+ - cmd: argocd app get namespace/bl01t -o yaml
41
+ rsp: |
42
+ spec:
43
+ source:
44
+ repoURL: https://github.com/test/example-deployment.git
45
+ path: apps
46
+ - cmd: git clone --depth=1 https://github.com/test/example-deployment.git /tmp/ec_tests
47
+ rsp: ""
48
+ - cmd: git add .
49
+ rsp: ""
50
+ - cmd: git commit -m "Set ec_services.bl01t-ea-test-01.enabled=True in apps/values.yaml"
51
+ rsp: ""
52
+ - cmd: git push
53
+ rsp: ""
54
+ - cmd: argocd app sync namespace/bl01t --apply-out-of-sync-only
55
+ rsp: ""
56
+ - cmd: argocd app unset namespace/bl01t -p ec_services.bl01t-ea-test-01.enabled
57
+ rsp: ""
58
+
59
+ start_temp:
60
+ - cmd: argocd app set namespace/bl01t -p ec_services.bl01t-ea-test-01.enabled=True
61
+ rsp: ""
62
+ - cmd: argocd app sync namespace/bl01t --apply-out-of-sync-only
63
+ rsp: ""
64
+
65
+ stop:
66
+ - cmd: argocd app get namespace/bl01t -o yaml
67
+ rsp: |
68
+ spec:
69
+ source:
70
+ repoURL: https://github.com/test/example-deployment.git
71
+ path: apps
72
+ - cmd: git clone --depth=1 https://github.com/test/example-deployment.git /tmp/ec_tests
73
+ rsp: ""
74
+ - cmd: argocd app sync namespace/bl01t --apply-out-of-sync-only
75
+ rsp: ""
76
+ - cmd: argocd app unset namespace/bl01t -p ec_services.bl01t-ea-test-01.enabled
77
+ rsp: ""
78
+
79
+ stop_temp:
80
+ - cmd: argocd app set namespace/bl01t -p ec_services.bl01t-ea-test-01.enabled=False
81
+ rsp: ""
82
+ - cmd: argocd app sync namespace/bl01t --apply-out-of-sync-only
83
+ rsp: ""
@@ -0,0 +1,3 @@
1
+ ec_services:
2
+ bl01t-ea-test-01:
3
+ enabled: false
@@ -28,7 +28,7 @@ delete:
28
28
  rsp: ""
29
29
 
30
30
  template:
31
- - cmd: 'helm package .*\/tests\/data\/bl01t\/services\/bl01t-ea-test-01 -u --app-version .*'
31
+ - cmd: 'helm package .*\/tests\/data\/bl01t-services\/services\/bl01t-ea-test-01 -u --app-version .*'
32
32
  rsp: ""
33
33
  - cmd: 'helm template bl01t-ea-test-01 .*\.tgz --values .*values.yaml --values .*values.yaml --debug *'
34
34
  rsp: |
@@ -37,7 +37,7 @@ template:
37
37
  ...
38
38
 
39
39
  deploy_local:
40
- - cmd: 'helm package .*\/tests\/data\/bl01t\/services\/bl01t-ea-test-01 -u --app-version .*'
40
+ - cmd: 'helm package .*\/tests\/data\/bl01t-services\/services\/bl01t-ea-test-01 -u --app-version .*'
41
41
  rsp: ""
42
42
  - cmd: 'helm upgrade --install bl01t-ea-test-01 .*\.tgz --values .*values.yaml --values .*values.yaml --namespace bl01t *'
43
43
  rsp: ""
@@ -45,7 +45,7 @@ deploy_local:
45
45
  deploy:
46
46
  - cmd: kubectl get namespace bl01t
47
47
  rsp: ""
48
- - cmd: git clone https://github.com/epics-containers/bl01t /tmp/ec_tests --depth=1 --single-branch --branch=2.0
48
+ - cmd: git clone https://github.com/epics-containers/bl01t-services /tmp/ec_tests --depth=1 --single-branch --branch=2.0
49
49
  rsp: ""
50
50
  - cmd: helm package /tmp/ec_tests/services/bl01t-ea-test-01 -u --app-version 2.0
51
51
  rsp: ""
@@ -0,0 +1,12 @@
1
+ # Trunk comment
2
+ trunk_A:
3
+ # branch comment
4
+ branch_A:
5
+ leaf_A: 0 # Leaf comment
6
+ leaf_B: zero
7
+
8
+ trunk_B:
9
+ leaf_B: true # Out of order
10
+ leaf_A: false # Comment before space
11
+
12
+ leaf_A: "False"
@@ -1,3 +1,9 @@
1
+ import shutil
2
+ from pathlib import Path
3
+
4
+ from tests.conftest import TMPDIR
5
+
6
+
1
7
  def test_logs(mock_run, ARGOCD):
2
8
  mock_run.set_seq(ARGOCD.checks + ARGOCD.logs)
3
9
  mock_run.run_cli("logs bl01t-ea-test-01")
@@ -13,16 +19,30 @@ def test_restart(mock_run, ARGOCD):
13
19
  mock_run.run_cli("restart bl01t-ea-test-01")
14
20
 
15
21
 
16
- def test_start(mock_run, ARGOCD):
22
+ def test_start(mock_run, ARGOCD, data: Path):
17
23
  mock_run.set_seq(ARGOCD.checks + ARGOCD.start)
24
+ TMPDIR.mkdir()
25
+ shutil.copytree(data / "bl01t-deployment/apps", TMPDIR / "apps")
18
26
  mock_run.run_cli("start bl01t-ea-test-01")
19
27
 
20
28
 
21
- def test_stop(mock_run, ARGOCD):
29
+ def test_start_temp(mock_run, ARGOCD):
30
+ mock_run.set_seq(ARGOCD.checks + ARGOCD.start_temp)
31
+ mock_run.run_cli("start bl01t-ea-test-01 --temp")
32
+
33
+
34
+ def test_stop(mock_run, ARGOCD, data: Path):
22
35
  mock_run.set_seq(ARGOCD.checks + ARGOCD.stop)
36
+ TMPDIR.mkdir()
37
+ shutil.copytree(data / "bl01t-deployment/apps", TMPDIR / "apps")
23
38
  mock_run.run_cli("stop bl01t-ea-test-01")
24
39
 
25
40
 
41
+ def test_stop_temp(mock_run, ARGOCD):
42
+ mock_run.set_seq(ARGOCD.checks + ARGOCD.stop_temp)
43
+ mock_run.run_cli("stop bl01t-ea-test-01 --temp")
44
+
45
+
26
46
  def test_ps(mock_run, ARGOCD):
27
47
  expect = (
28
48
  "| name | version | ready | deployed |\n"
@@ -23,7 +23,7 @@ def test_list(mock_run, CLI, data: Path):
23
23
  mock_run.set_seq(CLI.instances)
24
24
  # prep what instances expects to find after it cloned bl01t repo
25
25
  TMPDIR.mkdir()
26
- shutil.copytree(data / "bl01t/services", TMPDIR / "services")
26
+ shutil.copytree(data / "bl01t-services/services", TMPDIR / "services")
27
27
  res = mock_run.run_cli("list")
28
28
 
29
29
  assert res == expect
@@ -39,6 +39,6 @@ def test_instances(mock_run, CLI, data: Path):
39
39
  mock_run.set_seq(CLI.instances)
40
40
  # prep what instances expects to find after it cloned bl01t repo
41
41
  TMPDIR.mkdir()
42
- shutil.copytree(data / "bl01t/services", TMPDIR / "services")
42
+ shutil.copytree(data / "bl01t-services/services", TMPDIR / "services")
43
43
  res = mock_run.run_cli("instances bl01t-ea-test-01")
44
44
  assert res == expect
@@ -16,18 +16,20 @@ def test_delete(mock_run, K8S):
16
16
 
17
17
  def test_template(mock_run, data, K8S):
18
18
  mock_run.set_seq(K8S.template)
19
- mock_run.run_cli(f"template {data / 'bl01t/services/bl01t-ea-test-01'}")
19
+ mock_run.run_cli(f"template {data / 'bl01t-services/services/bl01t-ea-test-01'}")
20
20
 
21
21
 
22
22
  def test_deploy_local(mock_run, data, K8S):
23
23
  mock_run.set_seq(K8S.checks[:1] + K8S.deploy_local)
24
- mock_run.run_cli(f"deploy-local {data / 'bl01t/services/bl01t-ea-test-01'}")
24
+ mock_run.run_cli(
25
+ f"deploy-local {data / 'bl01t-services/services/bl01t-ea-test-01'}"
26
+ )
25
27
 
26
28
 
27
29
  def test_deploy(mock_run, K8S, data: Path):
28
30
  mock_run.set_seq(K8S.deploy)
29
31
  # prep what deploy expects to find after it cloned bl01t repo
30
- shutil.copytree(data / "bl01t/services", TMPDIR / "services")
32
+ shutil.copytree(data / "bl01t-services/services", TMPDIR / "services")
31
33
  mock_run.run_cli("deploy bl01t-ea-test-01 2.0")
32
34
 
33
35
 
@@ -0,0 +1,30 @@
1
+ from edge_containers_cli.utils import YamlFile
2
+
3
+
4
+ def test_yaml_processor_get(data):
5
+ processor = YamlFile(data / "yaml.yaml")
6
+ expect_0 = {
7
+ "trunk_A.branch_A.leaf_A": 0,
8
+ "trunk_A.branch_A.leaf_B": "zero",
9
+ "trunk_B.leaf_A": False,
10
+ "leaf_A": "False",
11
+ }
12
+ for key_0 in expect_0:
13
+ assert processor.get_key(key_0) == expect_0[key_0]
14
+
15
+
16
+ def test_yaml_processor_set(data):
17
+ processor = YamlFile(data / "yaml.yaml")
18
+ expect_1 = {
19
+ "trunk_A.branch_A.leaf_A": 1,
20
+ "trunk_A.branch_A.leaf_B": "one",
21
+ "trunk_B.leaf_A": True,
22
+ "trunk_B.leaf_C": True, # insertion
23
+ "leaf_A": "True",
24
+ }
25
+ for key_1 in expect_1:
26
+ processor.set_key(key_1, expect_1[key_1])
27
+ get = processor.get_key(key_1)
28
+ expect = expect_1[key_1]
29
+ assert get == expect
30
+ assert type(get) is type(expect)
@@ -1,45 +0,0 @@
1
- checks:
2
- - cmd: argocd app get namespace/bl01t
3
- rsp: ""
4
- - cmd: argocd app list -l "ec_service=true" --app-namespace namespace -o yaml
5
- rsp: |
6
- - metadata:
7
- creationTimestamp: "2024-07-12T13:42:50Z"
8
- name: bl01t-ea-test-01
9
- spec:
10
- source:
11
- targetRevision: main
12
- status:
13
- resources:
14
- - kind: StatefulSet
15
- name: bl01t-ea-test-01
16
- - cmd: argocd app manifests namespace/bl01t-ea-test-01 --source live
17
- rsp: |
18
- ---
19
- apiVersion: apps/v1
20
- kind: StatefulSet
21
- metadata:
22
- name: bl01t-ea-test-01
23
- creationTimestamp: "2024-07-12T13:52:35Z"
24
- status:
25
- readyReplicas: 1
26
-
27
- logs:
28
- - cmd: argocd app logs namespace/bl01t-ea-test-01
29
- rsp: ""
30
-
31
- log_history:
32
- - cmd: https://graylog2.diamond.ac.uk/bl01t-ea-test-01*
33
- rsp: True
34
-
35
- restart:
36
- - cmd: argocd app delete-resource namespace/bl01t-ea-test-01 --kind StatefulSet
37
- rsp: ""
38
-
39
- start:
40
- - cmd: argocd app set namespace/bl01t-ea-test-01 -p global.enabled=true
41
- rsp: ""
42
-
43
- stop:
44
- - cmd: argocd app set namespace/bl01t-ea-test-01 -p global.enabled=false
45
- rsp: ""