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 +1 -1
- lso/playbook.py +13 -72
- lso/tasks.py +78 -9
- lso/utils.py +0 -4
- {orchestrator_lso-2.4.0.dist-info → orchestrator_lso-2.4.2.dist-info}/METADATA +1 -1
- {orchestrator_lso-2.4.0.dist-info → orchestrator_lso-2.4.2.dist-info}/RECORD +7 -7
- {orchestrator_lso-2.4.0.dist-info → orchestrator_lso-2.4.2.dist-info}/WHEEL +0 -0
lso/__init__.py
CHANGED
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
|
|
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,
|
|
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
|
-
|
|
140
|
-
|
|
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
|
-
|
|
43
|
-
|
|
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
|
|
51
|
-
|
|
52
|
-
:param
|
|
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=
|
|
61
|
-
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,17 +1,17 @@
|
|
|
1
|
-
lso/__init__.py,sha256
|
|
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
|
|
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=
|
|
13
|
-
lso/utils.py,sha256=
|
|
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.
|
|
16
|
-
orchestrator_lso-2.4.
|
|
17
|
-
orchestrator_lso-2.4.
|
|
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,,
|
|
File without changes
|