cli: add ioc commands
Deploy and restart modified services / deploy (push) Successful in 18s

This commit is contained in:
Benjamin Labrecque
2026-07-15 08:25:03 +02:00
parent 44335bf30d
commit 2e8b5b6f51
14 changed files with 240 additions and 12 deletions
+4 -1
View File
@@ -1,5 +1,8 @@
[defaults]
remote_tmp = /tmp/ansible-$USER
# some responses of 'ioc ...' cli are non-utf8
module_strict_utf8_response = False
# ./ refers to dir in which this file is located
collections_path = ./installed-collections
collections_path = ./installed-collections
+5 -2
View File
@@ -1,9 +1,12 @@
---
- name: Install an IOC
# TODO: dev/prod
hosts: sls-vserv-bd-01-dev
hosts: sls-lc
gather_facts: false
vars:
workspace_dir: "/tmp/hla_framework_bd"
tasks:
- name: Run ioc install tasks
ansible.builtin.include_tasks: ../tasks/ioc/ioc-install.yml
ansible.builtin.import_tasks: ../tasks/ioc/ioc-install.yml
+4 -1
View File
@@ -1,9 +1,12 @@
---
- name: Restart an IOC
# TODO: dev/prod
hosts: sls-vserv-bd-01-dev
hosts: sls-lc
gather_facts: false
vars:
workspace_dir: "/tmp/hla_framework_bd"
tasks:
- name: Run ioc restart tasks
ansible.builtin.include_tasks: ../tasks/ioc/ioc-restart.yml
+1 -1
View File
@@ -6,4 +6,4 @@
tasks:
- name: Run ioc start tasks
ansible.builtin.include_tasks: ../tasks/ioc/ioc-start.yml
ansible.builtin.import_tasks: ../tasks/ioc/ioc-start.yml
+23
View File
@@ -12,6 +12,29 @@
# Uses preferred_service_name_lower if defined in vars, otherwise falls back to the CLI extra-vars
target_service_name_lower: "{{ preferred_service_name_lower | default(service_name_lower) }}"
- name: Initialize service name
ansible.builtin.set_fact:
# Uses preferred_service_name_lower if defined in vars, otherwise falls back to the CLI extra-vars
target_service_name_lower: "{{ preferred_service_name_lower | default(service_name_lower) }}"
- name: Read the service registry file
ansible.builtin.slurp:
src: "{{ workspace_dir }}/config/services_registry.yml"
register: remote_registry_file
- name: Parse the service registry YAML content into a fact
ansible.builtin.set_fact:
services_registry: "{{ remote_registry_file.content | b64decode | from_yaml }}"
- name: Extract the port from the services registry and save it as a persistent fact
ansible.builtin.set_fact:
ioc_port: "{{ services_registry.services | selectattr('name', 'equalto', target_service_name_lower) | map(attribute='ioc_port') | first | default(none) }}"
- name: Fail if the service or port wasn't found in the registry
ansible.builtin.fail:
msg: "Service '{{ target_service_name_lower }}' not found in services registry or port not set"
when: ioc_port | trim == "" or ioc_port is none
- name: "[{{ target_service_name_lower }}] Establish environment naming conventions"
ansible.builtin.set_fact:
service_name_upper: "{{ target_service_name_lower | upper }}"
+1 -6
View File
@@ -1,10 +1,5 @@
---
- name: Initialize service name
ansible.builtin.set_fact:
# Uses preferred_service_name_lower if defined in vars, otherwise falls back to the CLI extra-vars
target_service_name_lower: "{{ preferred_service_name_lower | default(service_name_lower) }}"
- name: "[{{ target_service_name_lower }}] Start IOC"
- name: "[{{ ioc_port }}] Start IOC"
ansible.builtin.shell: |
sudo shellbox start {{ ioc_port }}
changed_when: true
+31
View File
@@ -0,0 +1,31 @@
---
- name: Clone this repository
ansible.builtin.git:
repo: "git@gitea.psi.ch:sls/hla_framework_bd.git"
dest: "{{ workspace_dir }}"
accept_hostkey: true
version: "{{ branch_name }}"
force: true
- name: Initialize service name
ansible.builtin.set_fact:
# Uses preferred_service_name_lower if defined in vars, otherwise falls back to the CLI extra-vars
target_service_name_lower: "{{ preferred_service_name_lower | default(service_name_lower) }}"
- name: Read the service registry file
ansible.builtin.slurp:
src: "{{ workspace_dir }}/config/services_registry.yml"
register: remote_registry_file
- name: Parse the service registry YAML content into a fact
ansible.builtin.set_fact:
services_registry: "{{ remote_registry_file.content | b64decode | from_yaml }}"
- name: Extract the port from the services registry and save it as a persistent fact
ansible.builtin.set_fact:
ioc_port: "{{ services_registry.services | selectattr('name', 'equalto', target_service_name_lower) | map(attribute='ioc_port') | first | default(none) }}"
- name: Fail if the service or port wasn't found in the registry
ansible.builtin.fail:
msg: "Service '{{ target_service_name_lower }}' not found in services registry or port not set"
when: ioc_port | trim == "" or ioc_port is none
+1
View File
@@ -9,6 +9,7 @@ authors = [
dependencies = [
"ansible>=8.7.0",
"ansible-runner>=2.4.3",
"copier>=9.10.3",
"gitpython>=3.1.50",
"pydantic>=2.13.4",
+35
View File
@@ -0,0 +1,35 @@
import os
import sys
import ansible_runner
import typer
from core.utils import get_git_root
ioc = typer.Typer(no_args_is_help=True)
REPO_ROOT = get_git_root(__file__)
def run_playbook(playbook: str, vars: list[str]):
current_env = os.environ.copy()
current_env["ANSIBLE_CONFIG"] = f"{REPO_ROOT}/ansible/ansible.cfg"
extra_vars = [f"--extra-vars={var}" for var in vars]
out, err, rc = ansible_runner.run_command(
executable_cmd=f"ansible-playbook",
cmdline_args=[
playbook,
"-i",
f"{REPO_ROOT}/ansible/hosts.yml",
*extra_vars,
"-v",
],
envvars=current_env,
input_fd=sys.stdin,
output_fd=sys.stdout,
error_fd=sys.stderr,
)
print("rc: {}".format(rc))
print("out: {}".format(out))
print("err: {}".format(err))
+49
View File
@@ -0,0 +1,49 @@
import sys
import ansible_runner
import typer
from core.ansible import run_playbook
from core.utils import get_git_root
ioc = typer.Typer(no_args_is_help=True)
REPO_ROOT = get_git_root(__file__)
ANSIBLE_PLAYBOOKS_DIR = REPO_ROOT / "ansible" / "playbooks"
IOC_PLAYBOOK_INSTALL = ANSIBLE_PLAYBOOKS_DIR / "ioc-install.yml"
IOC_PLAYBOOK_RESTART = ANSIBLE_PLAYBOOKS_DIR / "ioc-restart.yml"
IOC_PLAYBOOK_START = ANSIBLE_PLAYBOOKS_DIR / "ioc-start.yml"
@ioc.command(no_args_is_help=True)
def install(
env: str = typer.Option(..., "--env"),
service_name_lower: str = typer.Option(..., "--service-name", "-s"),
):
env_var = f"agebd_env={env}"
branch_var = f"branch_name=feature/add-service-{service_name_lower}"
service_name_var = f"service_name_lower={service_name_lower}"
run_playbook(playbook=str(IOC_PLAYBOOK_INSTALL), vars=[env_var, branch_var, service_name_var])
@ioc.command(no_args_is_help=True)
def restart(
env: str = typer.Option(..., "--env"),
service_name_lower: str = typer.Option(..., "--service-name", "-s"),
):
env_var = f"agebd_env={env}"
branch_var = f"branch_name=feature/add-service-{service_name_lower}"
service_name_var = f"service_name_lower={service_name_lower}"
run_playbook(playbook=str(IOC_PLAYBOOK_RESTART), vars=[env_var, branch_var, service_name_var])
@ioc.command(no_args_is_help=True)
def start(
ioc_port: int = typer.Option(..., "--port", "-p"),
):
ioc_port_var = f"ioc_port={ioc_port}"
run_playbook(playbook=str(IOC_PLAYBOOK_START), vars=[ioc_port_var])
+2
View File
@@ -2,6 +2,7 @@ import logging
import typer
import ioc
import service
logger = logging.getLogger(__name__)
@@ -9,6 +10,7 @@ logger = logging.getLogger(__name__)
cli = typer.Typer(no_args_is_help=True, add_completion=False)
cli.add_typer(service.service, name="service")
cli.add_typer(ioc.ioc, name="ioc")
if __name__ == "__main__":
Generated
+81
View File
@@ -25,6 +25,7 @@ dependencies = [
{ name = "ansible", version = "10.7.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" },
{ name = "ansible", version = "12.3.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*'" },
{ name = "ansible", version = "14.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" },
{ name = "ansible-runner" },
{ name = "copier", version = "9.10.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
{ name = "copier", version = "9.16.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
{ name = "gitpython" },
@@ -45,6 +46,7 @@ dev = [
[package.metadata]
requires-dist = [
{ name = "ansible", specifier = ">=8.7.0" },
{ name = "ansible-runner", specifier = ">=2.4.3" },
{ name = "copier", specifier = ">=9.10.3" },
{ name = "gitpython", specifier = ">=3.1.50" },
{ name = "pydantic", specifier = ">=2.13.4" },
@@ -231,6 +233,22 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/90/55/894f2cad10ef650bf33a0e64e242805de2d1a8835f539d04d52053fe08a6/ansible_core-2.21.1-py3-none-any.whl", hash = "sha256:1403ff56bb69484ef90788126d06c65b4e28e8d2dcdcfb7f027d4c1495052d7e", size = 2454057, upload-time = "2026-06-18T19:35:00.741Z" },
]
[[package]]
name = "ansible-runner"
version = "2.4.3"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "importlib-metadata", marker = "python_full_version < '3.10'" },
{ name = "packaging" },
{ name = "pexpect" },
{ name = "python-daemon" },
{ name = "pyyaml" },
]
sdist = { url = "https://files.pythonhosted.org/packages/68/4f/62222b42b52cc771db51ccc77ffac19cc5f0196ff61de7c93585845a819b/ansible_runner-2.4.3.tar.gz", hash = "sha256:5f3025529bb968fdc3b627457dd8d418dbf9d53bc73735d50e69c28544d53031", size = 154148, upload-time = "2026-03-16T18:42:14.861Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/e3/89/fa7f81db6134caa455c9351373ee934b5fdeed212eb939d6c42d80d683d7/ansible_runner-2.4.3-py3-none-any.whl", hash = "sha256:cdac6daa151a50084ffda710e769db23fa975fc0507796191d7708831b286e37", size = 80257, upload-time = "2026-03-16T18:42:13.72Z" },
]
[[package]]
name = "cffi"
version = "2.0.0"
@@ -740,6 +758,18 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/20/7a/1c6e3562dfd8950adbb11ffbc65d21e7c89d01a6e4f137fa981056de25c5/gitpython-3.1.50-py3-none-any.whl", hash = "sha256:d352abe2908d07355014abdd21ddf798c2a961469239afec4962e9da884858f9", size = 212507, upload-time = "2026-05-06T04:01:23.799Z" },
]
[[package]]
name = "importlib-metadata"
version = "6.2.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "zipp", marker = "python_full_version < '3.10'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/4b/fd/ea3ca048abb8534a847c3e5f59f6701a4eb1fd124556b14edeb506ec8986/importlib_metadata-6.2.1.tar.gz", hash = "sha256:5a66966b39ff1c14ef5b2d60c1d842b0141fefff0f4cc6365b4bc9446c652807", size = 50493, upload-time = "2023-04-09T15:29:45.766Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/63/bb/1622fce2a3efb66ec1c223f7bdba6eec49a443d436da092668787d7f19b3/importlib_metadata-6.2.1-py3-none-any.whl", hash = "sha256:f65e478a7c2177bd19517a3a15dac094d253446d8690c5f3e71e735a04312374", size = 21957, upload-time = "2023-04-09T15:29:43.94Z" },
]
[[package]]
name = "importlib-resources"
version = "5.0.7"
@@ -808,6 +838,15 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/72/b9/313e8f2f2e9517ae050a692ae7b3e4b3f17cc5e6dfea0db51fe14e586580/jinja2_ansible_filters-1.3.2-py3-none-any.whl", hash = "sha256:e1082f5564917649c76fed239117820610516ec10f87735d0338688800a55b34", size = 18975, upload-time = "2022-06-30T14:08:49.571Z" },
]
[[package]]
name = "lockfile"
version = "0.12.2"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/17/47/72cb04a58a35ec495f96984dddb48232b551aafb95bde614605b754fe6f7/lockfile-0.12.2.tar.gz", hash = "sha256:6aed02de03cba24efabcd600b30540140634fc06cfa603822d508d5361e9f799", size = 20874, upload-time = "2015-11-25T18:29:58.279Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/c8/22/9460e311f340cb62d26a38c419b1381b8593b0bb6b5d1f056938b086d362/lockfile-0.12.2-py2.py3-none-any.whl", hash = "sha256:6c3cb24f344923d30b2785d5ad75182c8ea7ac1b6171b08657258ec7429d50fa", size = 13564, upload-time = "2015-11-25T18:29:51.462Z" },
]
[[package]]
name = "markdown-it-py"
version = "3.0.0"
@@ -980,6 +1019,18 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/f1/d9/7fb5aa316bc299258e68c73ba3bddbc499654a07f151cba08f6153988714/pathspec-1.1.1-py3-none-any.whl", hash = "sha256:a00ce642f577bf7f473932318056212bc4f8bfdf53128c78bbd5af0b9b20b189", size = 57328, upload-time = "2026-04-27T01:46:07.06Z" },
]
[[package]]
name = "pexpect"
version = "4.9.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "ptyprocess" },
]
sdist = { url = "https://files.pythonhosted.org/packages/42/92/cc564bf6381ff43ce1f4d06852fc19a2f11d180f23dc32d9588bee2f149d/pexpect-4.9.0.tar.gz", hash = "sha256:ee7d41123f3c9911050ea2c2dac107568dc43b2d3b0c7557a33212c398ead30f", size = 166450, upload-time = "2023-11-25T09:07:26.339Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/9e/c3/059298687310d527a58bb01f3b1965787ee3b40dce76752eda8b44e9a2c5/pexpect-4.9.0-py2.py3-none-any.whl", hash = "sha256:7236d1e080e4936be2dc3e326cec0af72acf9212a7e1d060210e70a47e253523", size = 63772, upload-time = "2023-11-25T06:56:14.81Z" },
]
[[package]]
name = "platformdirs"
version = "4.4.0"
@@ -1047,6 +1098,15 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/84/03/0d3ce49e2505ae70cf43bc5bb3033955d2fc9f932163e84dc0779cc47f48/prompt_toolkit-3.0.52-py3-none-any.whl", hash = "sha256:9aac639a3bbd33284347de5ad8d68ecc044b91a762dc39b7c21095fcd6a19955", size = 391431, upload-time = "2025-08-27T15:23:59.498Z" },
]
[[package]]
name = "ptyprocess"
version = "0.7.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/20/e5/16ff212c1e452235a90aeb09066144d0c5a6a8c0834397e03f5224495c4e/ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220", size = 70762, upload-time = "2020-12-28T15:15:30.155Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/22/a6/858897256d0deac81a172289110f31629fc4cee19b6f01283303e18c8db3/ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35", size = 13993, upload-time = "2020-12-28T15:15:28.35Z" },
]
[[package]]
name = "pycparser"
version = "2.23"
@@ -1300,6 +1360,18 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/24/25/1de2678b631f5a49215c6c96fff41ba892b0a34df68d6d80292b1b48aa7f/pytest-9.1.1-py3-none-any.whl", hash = "sha256:37a86b45efb9a47a61a36449063e8e18d0cab3161329fc099eb21783169c4f0c", size = 386536, upload-time = "2026-06-19T10:58:31.347Z" },
]
[[package]]
name = "python-daemon"
version = "3.1.2"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "lockfile" },
]
sdist = { url = "https://files.pythonhosted.org/packages/3d/37/4f10e37bdabc058a32989da2daf29e57dc59dbc5395497f3d36d5f5e2694/python_daemon-3.1.2.tar.gz", hash = "sha256:f7b04335adc473de877f5117e26d5f1142f4c9f7cd765408f0877757be5afbf4", size = 71576, upload-time = "2024-12-03T08:41:07.843Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/45/3c/b88167e2d6785c0e781ee5d498b07472aeb9b6765da3b19e7cc9e0813841/python_daemon-3.1.2-py3-none-any.whl", hash = "sha256:b906833cef63502994ad48e2eab213259ed9bb18d54fa8774dcba2ff7864cec6", size = 30872, upload-time = "2024-12-03T08:41:03.322Z" },
]
[[package]]
name = "pyyaml"
version = "6.0.3"
@@ -1605,3 +1677,12 @@ sdist = { url = "https://files.pythonhosted.org/packages/34/74/c6428f875774288be
wheels = [
{ url = "https://files.pythonhosted.org/packages/96/42/3e5985a0a7e57de470b320c6d6a1a67c844f6737a587f3d44dd13d1819e7/wcwidth-0.8.2-py3-none-any.whl", hash = "sha256:d63947694a0539a1d51e01eda7caf800c291020e6cdd7e28ad7b14dd33ad4f85", size = 323166, upload-time = "2026-06-29T18:11:09.888Z" },
]
[[package]]
name = "zipp"
version = "3.23.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/30/21/093488dfc7cc8964ded15ab726fad40f25fd3d788fd741cc1c5a17d78ee8/zipp-3.23.1.tar.gz", hash = "sha256:32120e378d32cd9714ad503c1d024619063ec28aad2248dc6672ad13edfa5110", size = 25965, upload-time = "2026-04-13T23:21:46.6Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/08/8a/0861bec20485572fbddf3dfba2910e38fe249796cb73ecdeb74e07eeb8d3/zipp-3.23.1-py3-none-any.whl", hash = "sha256:0b3596c50a5c700c9cb40ba8d86d9f2cc4807e9bedb06bcdf7fac85633e444dc", size = 10378, upload-time = "2026-04-13T23:21:45.386Z" },
]
+2
View File
@@ -20,6 +20,7 @@ class CallbackRunner:
self._callback_active = False
def start(self):
# TODO: standardize svc.name to lower or make it unavailable
logger.debug(f"Starting CallbackRunner for service: {self.svc.name}")
while not self.pvs.abort.get():
try:
@@ -76,6 +77,7 @@ class PeriodicRunner:
self.pvs = service.pvs
def start(self):
# TODO: standardize svc.name to lower or make it unavailable
logger.debug(f"Starting PeriodicRunner for service: {self.svc.name}")
while not self.pvs.abort.get():
try:
@@ -1,6 +1,6 @@
cpu_architecture: x86_64
epics_version: 7.0.8
ioc_host: sls-vserv-bd-01{{ agebd_env }}
ioc_host: sls-vserv-bd-01{{ agebd_env }}
ioc_port: {{ ioc_port }}
os: RHEL8
os_id: rhel