orchestrator-lso 2.4.0__py3-none-any.whl → 2.4.2__py3-none-any.whl

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.
lso/__init__.py CHANGED
@@ -13,7 +13,7 @@
13
13
 
14
14
  """LSO, an API for remotely running Ansible playbooks."""
15
15
 
16
- __version__ = "2.4.0"
16
+ __version__ = "2.4.2"
17
17
 
18
18
  import logging
19
19
 
lso/playbook.py CHANGED
@@ -13,22 +13,15 @@
13
13
 
14
14
  """Module that gathers common API responses and data models."""
15
15
 
16
- import logging
17
- from collections.abc import Callable
18
16
  from pathlib import Path
19
17
  from typing import Any
20
18
  from uuid import UUID, uuid4
21
19
 
22
- import requests
23
- from ansible_runner import Runner
24
20
  from pydantic import HttpUrl
25
- from starlette import status
26
21
 
27
22
  from lso.config import ExecutorType, settings
28
23
  from lso.tasks import run_playbook_proc_task
29
- from lso.utils import CallbackFailedError, get_thread_pool
30
-
31
- logger = logging.getLogger(__name__)
24
+ from lso.utils import get_thread_pool
32
25
 
33
26
 
34
27
  def get_playbook_path(playbook_name: Path) -> Path:
@@ -36,57 +29,6 @@ def get_playbook_path(playbook_name: Path) -> Path:
36
29
  return Path(settings.ANSIBLE_PLAYBOOKS_ROOT_DIR) / playbook_name
37
30
 
38
31
 
39
- def playbook_event_handler_factory(progress: str, *, progress_is_incremental: bool) -> Callable[[dict], bool]:
40
- """Create an event handler for Ansible playbook runs.
41
-
42
- This is used to send incremental progress updates to the external system that called for this playbook to be run.
43
-
44
- :param str progress: The progress URL where the external system expects to receive updates.
45
- :param bool progress_is_incremental: Whether progress updates are sent incrementally, or only contain the latest
46
- event data.
47
- :return Callable[[dict], bool]]: A handler method that processes every Ansible playbook event.
48
- """
49
- events_stdout = []
50
-
51
- def _playbook_event_handler(event: dict) -> bool:
52
- if progress_is_incremental:
53
- emit_body = event["stdout"].strip()
54
- else:
55
- events_stdout.append(event["stdout"].strip())
56
- emit_body = events_stdout
57
-
58
- requests.post(str(progress), json={"progress": emit_body}, timeout=settings.REQUEST_TIMEOUT_SEC)
59
- return True
60
-
61
- return _playbook_event_handler
62
-
63
-
64
- def playbook_finished_handler_factory(callback: str, job_id: UUID) -> Callable[[Runner], None]:
65
- """Create an event handler for finished Ansible playbook runs.
66
-
67
- Once Ansible runner is finished, it will call the handler method created by this factory before teardown.
68
-
69
- :param str callback: The callback URL that ansible runner should report to.
70
- :param UUID job_id: The job ID of this playbook run, used for reporting.
71
- :return Callable[[Runner], None]: A handler method that sends one request to the callback URL.
72
- """
73
-
74
- def _playbook_finished_handler(runner: Runner) -> None:
75
- payload = {
76
- "status": runner.status,
77
- "job_id": str(job_id),
78
- "output": runner.stdout.readlines(),
79
- "return_code": int(runner.rc),
80
- }
81
-
82
- response = requests.post(str(callback), json=payload, timeout=settings.REQUEST_TIMEOUT_SEC)
83
- if not (status.HTTP_200_OK <= response.status_code < status.HTTP_300_MULTIPLE_CHOICES):
84
- msg = f"Callback failed: {response.text}, url: {callback}"
85
- raise CallbackFailedError(msg)
86
-
87
- return _playbook_finished_handler
88
-
89
-
90
32
  def run_playbook(
91
33
  playbook_path: Path,
92
34
  extra_vars: dict[str, Any],
@@ -105,39 +47,38 @@ def run_playbook(
105
47
  This is used for workflow-orchestrator to continue with the next step in a workflow.
106
48
  :return UUID: Job ID of the launched playbook.
107
49
  """
108
- msg = f"playbook_path: {playbook_path}"
109
50
  job_id = uuid4()
110
51
  callback_str = None
111
52
  progress_str = None
112
- event_handler = None
113
- finished_callback = None
114
-
115
53
  if callback:
116
54
  callback_str = str(callback)
117
- msg += f", callback URL: {callback_str}"
118
- finished_callback = playbook_finished_handler_factory(callback_str, job_id)
119
55
  if progress:
120
56
  progress_str = str(progress)
121
- msg += f", progress URL: {progress_str}"
122
- event_handler = playbook_event_handler_factory(progress_str, progress_is_incremental=progress_is_incremental)
123
-
124
- logger.info(msg)
125
57
 
126
58
  if settings.EXECUTOR == ExecutorType.THREADPOOL:
127
59
  executor = get_thread_pool()
128
60
  executor_handle = executor.submit(
129
- run_playbook_proc_task, str(playbook_path), extra_vars, inventory, event_handler, finished_callback
61
+ run_playbook_proc_task,
62
+ str(job_id),
63
+ str(playbook_path),
64
+ extra_vars,
65
+ inventory,
66
+ callback_str,
67
+ progress_str,
68
+ progress_is_incremental=progress_is_incremental,
130
69
  )
131
70
  if settings.TESTING:
132
71
  executor_handle.result()
133
72
 
134
73
  elif settings.EXECUTOR == ExecutorType.WORKER:
135
74
  run_playbook_proc_task.delay(
75
+ str(job_id),
136
76
  str(playbook_path),
137
77
  extra_vars,
138
78
  inventory,
139
- event_handler,
140
- finished_callback,
79
+ callback_str,
80
+ progress_str,
81
+ progress_is_incremental=progress_is_incremental,
141
82
  )
142
83
 
143
84
  return job_id
lso/tasks.py CHANGED
@@ -28,37 +28,106 @@ from starlette import status
28
28
 
29
29
  from lso.config import settings
30
30
  from lso.schema import ExecutableRunResponse
31
- from lso.utils import CallbackFailedError
32
31
  from lso.worker import RUN_EXECUTABLE, RUN_PLAYBOOK, celery
33
32
 
34
33
  logger = logging.getLogger(__name__)
35
34
 
36
35
 
36
+ class CallbackFailedError(Exception):
37
+ """Exception raised when a callback url can't be reached."""
38
+
39
+
40
+ def playbook_event_handler_factory(
41
+ progress: str | None, *, progress_is_incremental: bool
42
+ ) -> Callable[[dict], bool] | None:
43
+ """Create an event handler for Ansible playbook runs.
44
+
45
+ This is used to send incremental progress updates to the external system that called for this playbook to be run.
46
+
47
+ :param str progress: The progress URL where the external system expects to receive updates.
48
+ :param bool progress_is_incremental: Whether progress updates are sent incrementally, or contain the whole history
49
+ of event data.
50
+ """
51
+ events_stdout = []
52
+
53
+ def _playbook_event_handler(event: dict) -> bool:
54
+ event_data = event["stdout"].strip()
55
+ if not event_data:
56
+ return False
57
+
58
+ event_data_lines = event_data.split("\r\n")
59
+ if progress_is_incremental:
60
+ emit_body = event_data_lines
61
+ else:
62
+ events_stdout.extend(event_data_lines)
63
+ emit_body = events_stdout
64
+
65
+ requests.post(str(progress), json={"progress": emit_body}, timeout=settings.REQUEST_TIMEOUT_SEC)
66
+ return True
67
+
68
+ if progress:
69
+ return _playbook_event_handler
70
+ return None
71
+
72
+
73
+ def playbook_finished_handler_factory(callback: str | None, job_id: str) -> Callable[[Runner], None] | None:
74
+ """Create an event handler for finished Ansible playbook runs.
75
+
76
+ Once Ansible runner is finished, it will call the handler method created by this factory before teardown.
77
+
78
+ :param str callback: The callback URL that ansible runner should report to.
79
+ :param str job_id: The job ID of this playbook run, used for reporting.
80
+ :return Callable: A handler method that sends one request to the callback URL.
81
+ """
82
+
83
+ def _playbook_finished_handler(runner: Runner) -> None:
84
+ payload = {
85
+ "status": runner.status,
86
+ "job_id": job_id,
87
+ "output": runner.stdout.readlines(),
88
+ "return_code": int(runner.rc),
89
+ }
90
+
91
+ response = requests.post(str(callback), json=payload, timeout=settings.REQUEST_TIMEOUT_SEC)
92
+ if not (status.HTTP_200_OK <= response.status_code < status.HTTP_300_MULTIPLE_CHOICES):
93
+ msg = f"Callback failed: {response.text}, url: {callback}"
94
+ raise CallbackFailedError(msg)
95
+
96
+ if callback:
97
+ return _playbook_finished_handler
98
+ return None
99
+
100
+
37
101
  @celery.task(name=RUN_PLAYBOOK) # type: ignore[misc]
38
102
  def run_playbook_proc_task(
103
+ job_id: str,
39
104
  playbook_path: str,
40
105
  extra_vars: dict[str, Any],
41
106
  inventory: dict[str, Any] | str,
42
- event_handler: Callable[[dict], bool] | None = None,
43
- finished_callback: Callable[[Runner], None] | None = None,
107
+ callback: str | None,
108
+ progress: str | None,
109
+ *,
110
+ progress_is_incremental: bool,
44
111
  ) -> None:
45
112
  """Celery task to run a playbook.
46
113
 
114
+ :param str job_id: Identifier of the job being executed.
47
115
  :param str playbook_path: Path to the playbook to be executed.
48
116
  :param dict[str, Any] extra_vars: Extra variables to pass to the playbook.
49
117
  :param dict[str, Any] | str inventory: Inventory to run the playbook against.
50
- :param Callable[[dict], bool] event_handler: Event handler method that is executed on every event while the playbook
51
- runs.
52
- :param Callable[[Runner], None] finished_callback: Callback handler method that is executed once the playbook run is
53
- completed.
118
+ :param str callback: Callback URL for status updates.
119
+ :param str progress: URL for sending progress updates.
120
+ :param bool progress_is_incremental: Whether progress updates include all past progress.
54
121
  :return: None
55
122
  """
123
+ msg = f"playbook_path: {playbook_path}, callback: {callback}"
124
+ logger.info(msg)
56
125
  run(
57
126
  playbook=playbook_path,
58
127
  inventory=inventory,
59
128
  extravars=extra_vars,
60
- event_handler=event_handler,
61
- finished_callback=finished_callback,
129
+ event_handler=playbook_event_handler_factory(progress, progress_is_incremental=progress_is_incremental),
130
+ finished_callback=playbook_finished_handler_factory(callback, job_id),
62
131
  )
63
132
 
64
133
 
lso/utils.py CHANGED
@@ -20,10 +20,6 @@ from lso.config import settings
20
20
  _executor = None
21
21
 
22
22
 
23
- class CallbackFailedError(Exception):
24
- """Exception raised when a callback url can't be reached."""
25
-
26
-
27
23
  def get_thread_pool() -> ThreadPoolExecutor:
28
24
  """Initialize or return a cached ThreadPoolExecutor for local asynchronous execution."""
29
25
  global _executor # noqa: PLW0603
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: orchestrator-lso
3
- Version: 2.4.0
3
+ Version: 2.4.2
4
4
  Summary: LSO, an API for remotely running Ansible playbooks.
5
5
  Author: GÉANT Orchestration and Automation Team
6
6
  Author-email: GÉANT Orchestration and Automation Team <goat@geant.org>
@@ -1,17 +1,17 @@
1
- lso/__init__.py,sha256=uZ6rBsNmtFc11EZl1eag4GsGhtFpLCDEzdzO4GH-H5o,1589
1
+ lso/__init__.py,sha256=-1xhYhBAtzdi8TaFrK-xyzqp29irj-3lhitkNlZefyU,1589
2
2
  lso/app.py,sha256=PCdL4hY5i1fho_pMSiNAHuaIKWPgfQNyePsZ-LdasCg,775
3
3
  lso/config.py,sha256=tyxjq67EJLEMLmpIlkxrtwJ3arm8ZUBNGkj9NTsBJxw,1639
4
4
  lso/environment.py,sha256=4k8a8cSLwgLjBIU-XUa7Y6Clns2VjmsBkiKvkzK2Pp4,1771
5
5
  lso/execute.py,sha256=T-HL9Q86vGU5VunjP7mjokRYvaVs0EnkSOFXZUT_GJE,2731
6
- lso/playbook.py,sha256=-WcL-kFXaBqSnvA4XlS7r0Rga8b05g5Ynk9VewTa3kQ,5500
6
+ lso/playbook.py,sha256=Hh2k3hpBcm5lVeEnmsPguYfJw6uQhaCUUjDprjWDLBQ,2866
7
7
  lso/routes/__init__.py,sha256=l2hfxU8DF_bhbZ4GQ24VzcLjlZMfVSjUNBWSk1beEPw,639
8
8
  lso/routes/default.py,sha256=ScSrDFjbGqjcjeOodMTKZUMzZqXZ0zC6L-I9hrP0dqk,1438
9
9
  lso/routes/execute.py,sha256=nDmilV2THCnPzAIHGUjYBuETrL0QeOQSUC7z5M3EaMA,2713
10
10
  lso/routes/playbook.py,sha256=9ughP7zqnqRpBV31KQbTMoJdfgzF0qyjxiXw1l3TlUE,5270
11
11
  lso/schema.py,sha256=Xp4D7FRc21Mhh-1xlp0EbjTvdu5kGEHRpQvQp6ic6ro,1518
12
- lso/tasks.py,sha256=Ddj5smLkbafpdBbS3_fbz-stX72Zy3-jqODuyV5QhGc,3847
13
- lso/utils.py,sha256=Ml2JCHdy9BPJ4ItjYhx5z3lHwKQiJ9JzMySktrRzo_Q,1148
12
+ lso/tasks.py,sha256=F-Wbis-ylL7Vwy6qMu2fS-S9WRzIUvkkIcD8LFCDMUw,6377
13
+ lso/utils.py,sha256=eVKyYRtdu_yPkbUQqDJlCKlCPAgc0dFxG1E1kY2Qsao,1043
14
14
  lso/worker.py,sha256=ZAXii3EctILrpnHInvVMTyjOs_40gqR_VybVLBzGRw4,1727
15
- orchestrator_lso-2.4.0.dist-info/WHEEL,sha256=5w2T7AS2mz1-rW9CNagNYWRCaB0iQqBMYLwKdlgiR4Q,78
16
- orchestrator_lso-2.4.0.dist-info/METADATA,sha256=xPixjYkEuCEDtHbOpFsGm5TuzCLSuLKRewrcL8rKGDQ,5621
17
- orchestrator_lso-2.4.0.dist-info/RECORD,,
15
+ orchestrator_lso-2.4.2.dist-info/WHEEL,sha256=5w2T7AS2mz1-rW9CNagNYWRCaB0iQqBMYLwKdlgiR4Q,78
16
+ orchestrator_lso-2.4.2.dist-info/METADATA,sha256=uQS0-YaAJ6xo-8_PtrT65XdKjlZtdmsl-ZCvw0sLtFk,5621
17
+ orchestrator_lso-2.4.2.dist-info/RECORD,,