orchestrator-lso 2.0.1__py3-none-any.whl → 2.1.0__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 +3 -1
- lso/config.py +2 -2
- lso/execute.py +31 -0
- lso/playbook.py +2 -18
- lso/routes/execute.py +51 -0
- lso/routes/playbook.py +1 -1
- lso/tasks.py +61 -4
- lso/utils.py +16 -0
- lso/worker.py +3 -5
- {orchestrator_lso-2.0.1.dist-info → orchestrator_lso-2.1.0.dist-info}/METADATA +9 -8
- orchestrator_lso-2.1.0.dist-info/RECORD +17 -0
- {orchestrator_lso-2.0.1.dist-info → orchestrator_lso-2.1.0.dist-info}/WHEEL +1 -1
- orchestrator_lso-2.0.1.dist-info/RECORD +0 -14
- {orchestrator_lso-2.0.1.dist-info → orchestrator_lso-2.1.0.dist-info/licenses}/LICENSE +0 -0
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.0
|
|
16
|
+
__version__ = "2.1.0"
|
|
17
17
|
|
|
18
18
|
import logging
|
|
19
19
|
|
|
@@ -22,6 +22,7 @@ from fastapi.middleware.cors import CORSMiddleware
|
|
|
22
22
|
|
|
23
23
|
from lso import environment
|
|
24
24
|
from lso.routes.default import router as default_router
|
|
25
|
+
from lso.routes.execute import router as executable_router
|
|
25
26
|
from lso.routes.playbook import router as playbook_router
|
|
26
27
|
|
|
27
28
|
logger = logging.getLogger(__name__)
|
|
@@ -37,6 +38,7 @@ def create_app() -> FastAPI:
|
|
|
37
38
|
|
|
38
39
|
app.include_router(default_router, prefix="/api")
|
|
39
40
|
app.include_router(playbook_router, prefix="/api/playbook")
|
|
41
|
+
app.include_router(executable_router, prefix="/api/execute")
|
|
40
42
|
|
|
41
43
|
environment.setup_logging()
|
|
42
44
|
|
lso/config.py
CHANGED
|
@@ -34,15 +34,15 @@ class Config(BaseSettings):
|
|
|
34
34
|
|
|
35
35
|
TESTING: bool = False
|
|
36
36
|
ANSIBLE_PLAYBOOKS_ROOT_DIR: str = "/path/to/ansible/playbooks"
|
|
37
|
+
EXECUTABLES_ROOT_DIR: str = "/path/to/executables"
|
|
37
38
|
EXECUTOR: ExecutorType = ExecutorType.THREADPOOL
|
|
38
39
|
MAX_THREAD_POOL_WORKERS: int = min(32, (os.cpu_count() or 1) + 4)
|
|
39
40
|
REQUEST_TIMEOUT_SEC: int = 10
|
|
40
41
|
CELERY_BROKER_URL: str = "redis://localhost:6379/0"
|
|
41
42
|
CELERY_RESULT_BACKEND: str = "redis://localhost:6379/0"
|
|
42
|
-
CELERY_TIMEZONE: str = "Europe/Amsterdam"
|
|
43
|
-
CELERY_ENABLE_UTC: bool = True
|
|
44
43
|
CELERY_RESULT_EXPIRES: int = 3600
|
|
45
44
|
WORKER_QUEUE_NAME: str | None = None
|
|
45
|
+
EXECUTABLE_TIMEOUT_SEC: int = 300
|
|
46
46
|
|
|
47
47
|
|
|
48
48
|
settings = Config()
|
lso/execute.py
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"""Module for handling the execution of arbitrary executables."""
|
|
2
|
+
|
|
3
|
+
import uuid
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
from pydantic import HttpUrl
|
|
7
|
+
|
|
8
|
+
from lso.config import ExecutorType, settings
|
|
9
|
+
from lso.tasks import run_executable_proc_task
|
|
10
|
+
from lso.utils import get_thread_pool
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def get_executable_path(executable_name: Path) -> Path:
|
|
14
|
+
"""Return the full path of an executable, based on the configured EXECUTABLES_ROOT_DIR."""
|
|
15
|
+
return Path(settings.EXECUTABLES_ROOT_DIR) / executable_name
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def run_executable(executable_path: Path, args: list[str], callback: HttpUrl) -> uuid.UUID:
|
|
19
|
+
"""Dispatch the task for executing an arbitrary executable remotely.
|
|
20
|
+
|
|
21
|
+
Uses a ThreadPoolExecutor (for local execution) or a Celery worker (for distributed tasks).
|
|
22
|
+
"""
|
|
23
|
+
job_id = uuid.uuid4()
|
|
24
|
+
if settings.EXECUTOR == ExecutorType.THREADPOOL:
|
|
25
|
+
executor = get_thread_pool()
|
|
26
|
+
future = executor.submit(run_executable_proc_task, str(job_id), str(executable_path), args, str(callback))
|
|
27
|
+
if settings.TESTING:
|
|
28
|
+
future.result()
|
|
29
|
+
elif settings.EXECUTOR == ExecutorType.WORKER:
|
|
30
|
+
run_executable_proc_task.delay(str(job_id), str(executable_path), args, str(callback))
|
|
31
|
+
return job_id
|
lso/playbook.py
CHANGED
|
@@ -14,7 +14,6 @@
|
|
|
14
14
|
"""Module that gathers common API responses and data models."""
|
|
15
15
|
|
|
16
16
|
import uuid
|
|
17
|
-
from concurrent.futures import ThreadPoolExecutor
|
|
18
17
|
from pathlib import Path
|
|
19
18
|
from typing import Any
|
|
20
19
|
|
|
@@ -22,22 +21,7 @@ from pydantic import HttpUrl
|
|
|
22
21
|
|
|
23
22
|
from lso.config import ExecutorType, settings
|
|
24
23
|
from lso.tasks import run_playbook_proc_task
|
|
25
|
-
|
|
26
|
-
_executor = None
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
def get_thread_pool() -> ThreadPoolExecutor:
|
|
30
|
-
"""Get and optionally initialise a ThreadPoolExecutor.
|
|
31
|
-
|
|
32
|
-
Returns:
|
|
33
|
-
ThreadPoolExecutor
|
|
34
|
-
|
|
35
|
-
"""
|
|
36
|
-
global _executor # noqa: PLW0603
|
|
37
|
-
if _executor is None:
|
|
38
|
-
_executor = ThreadPoolExecutor(max_workers=settings.MAX_THREAD_POOL_WORKERS)
|
|
39
|
-
|
|
40
|
-
return _executor
|
|
24
|
+
from lso.utils import get_thread_pool
|
|
41
25
|
|
|
42
26
|
|
|
43
27
|
def get_playbook_path(playbook_name: Path) -> Path:
|
|
@@ -53,7 +37,7 @@ def run_playbook(
|
|
|
53
37
|
) -> uuid.UUID:
|
|
54
38
|
"""Run an Ansible playbook against a specified inventory.
|
|
55
39
|
|
|
56
|
-
:param Path playbook_path:
|
|
40
|
+
:param Path playbook_path: Playbook to be executed.
|
|
57
41
|
:param dict[str, Any] extra_vars: Any extra vars needed for the playbook to run.
|
|
58
42
|
:param dict[str, Any] | str inventory: The inventory that the playbook is executed against.
|
|
59
43
|
:param HttpUrl callback: Callback URL where the playbook should send a status update when execution is completed.
|
lso/routes/execute.py
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"""FastAPI route for running arbitrary executables."""
|
|
2
|
+
|
|
3
|
+
import uuid
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Annotated
|
|
6
|
+
|
|
7
|
+
from fastapi import APIRouter, HTTPException, status
|
|
8
|
+
from pydantic import AfterValidator, BaseModel, HttpUrl
|
|
9
|
+
|
|
10
|
+
from lso.execute import get_executable_path, run_executable
|
|
11
|
+
|
|
12
|
+
router = APIRouter()
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _executable_path_validator(executable_name: Path) -> Path:
|
|
16
|
+
"""Validate that the executable exists, is a file, and is marked as executable."""
|
|
17
|
+
executable_path = get_executable_path(executable_name)
|
|
18
|
+
if not executable_path.exists():
|
|
19
|
+
msg = f"Executable '{executable_path}' does not exist."
|
|
20
|
+
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=msg)
|
|
21
|
+
if not executable_path.is_file():
|
|
22
|
+
msg = f"Executable '{executable_name}' is not a valid file."
|
|
23
|
+
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=msg)
|
|
24
|
+
if not executable_path.stat().st_mode & 0o111:
|
|
25
|
+
msg = f"Executable '{executable_name}' is not marked as executable."
|
|
26
|
+
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail=msg)
|
|
27
|
+
return executable_path
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
ExecutableName = Annotated[Path, AfterValidator(_executable_path_validator)]
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class ExecutableRunParams(BaseModel):
|
|
34
|
+
"""Request parameters for running an arbitrary executable."""
|
|
35
|
+
|
|
36
|
+
executable_name: ExecutableName
|
|
37
|
+
args: list[str] = []
|
|
38
|
+
callback: HttpUrl
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class ExecutableRunResponse(BaseModel):
|
|
42
|
+
"""Response for running an arbitrary executable."""
|
|
43
|
+
|
|
44
|
+
job_id: uuid.UUID
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
@router.post("/", response_model=ExecutableRunResponse, status_code=status.HTTP_201_CREATED)
|
|
48
|
+
def run_executable_endpoint(params: ExecutableRunParams) -> ExecutableRunResponse:
|
|
49
|
+
"""Dispatch an asynchronous task to run an arbitrary executable."""
|
|
50
|
+
job_id = run_executable(params.executable_name, params.args, params.callback)
|
|
51
|
+
return ExecutableRunResponse(job_id=job_id)
|
lso/routes/playbook.py
CHANGED
|
@@ -36,7 +36,7 @@ def _inventory_validator(inventory: dict[str, Any] | str) -> dict[str, Any] | st
|
|
|
36
36
|
"""Validate the provided inventory format.
|
|
37
37
|
|
|
38
38
|
Attempts to parse the inventory to verify its validity. If the inventory cannot be parsed or the inventory
|
|
39
|
-
format is incorrect an HTTP 422 error is raised.
|
|
39
|
+
format is incorrect, an HTTP 422 error is raised.
|
|
40
40
|
|
|
41
41
|
:param inventory: The inventory to validate, can be a dictionary or a string.
|
|
42
42
|
:return: The validated inventory if no errors are found.
|
lso/tasks.py
CHANGED
|
@@ -18,6 +18,8 @@ the results to a specified callback URL.
|
|
|
18
18
|
"""
|
|
19
19
|
|
|
20
20
|
import logging
|
|
21
|
+
import subprocess # noqa: S404
|
|
22
|
+
from enum import StrEnum
|
|
21
23
|
from typing import Any
|
|
22
24
|
|
|
23
25
|
import ansible_runner
|
|
@@ -25,7 +27,7 @@ import requests
|
|
|
25
27
|
from starlette import status
|
|
26
28
|
|
|
27
29
|
from lso.config import settings
|
|
28
|
-
from lso.worker import RUN_PLAYBOOK, celery
|
|
30
|
+
from lso.worker import RUN_EXECUTABLE, RUN_PLAYBOOK, celery
|
|
29
31
|
|
|
30
32
|
logger = logging.getLogger(__name__)
|
|
31
33
|
|
|
@@ -34,6 +36,13 @@ class CallbackFailedError(Exception):
|
|
|
34
36
|
"""Exception raised when a callback url can't be reached."""
|
|
35
37
|
|
|
36
38
|
|
|
39
|
+
class JobStatus(StrEnum):
|
|
40
|
+
"""Enumeration of possible job statuses."""
|
|
41
|
+
|
|
42
|
+
SUCCESSFUL = "successful"
|
|
43
|
+
FAILED = "failed"
|
|
44
|
+
|
|
45
|
+
|
|
37
46
|
@celery.task(name=RUN_PLAYBOOK) # type: ignore[misc]
|
|
38
47
|
def run_playbook_proc_task(
|
|
39
48
|
job_id: str, playbook_path: str, extra_vars: dict[str, Any], inventory: dict[str, Any] | str, callback: str
|
|
@@ -58,7 +67,55 @@ def run_playbook_proc_task(
|
|
|
58
67
|
"return_code": int(ansible_playbook_run.rc),
|
|
59
68
|
}
|
|
60
69
|
|
|
61
|
-
|
|
62
|
-
if not status.HTTP_200_OK <=
|
|
63
|
-
msg = f"Callback failed: {
|
|
70
|
+
response = requests.post(str(callback), json=payload, timeout=settings.REQUEST_TIMEOUT_SEC)
|
|
71
|
+
if not (status.HTTP_200_OK <= response.status_code < status.HTTP_300_MULTIPLE_CHOICES):
|
|
72
|
+
msg = f"Callback failed: {response.text}, url: {callback}"
|
|
64
73
|
raise CallbackFailedError(msg)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
@celery.task(name=RUN_EXECUTABLE) # type: ignore[misc]
|
|
77
|
+
def run_executable_proc_task(job_id: str, executable_path: str, args: list[str], callback: str) -> None:
|
|
78
|
+
"""Celery task to run an arbitrary executable and notify via callback.
|
|
79
|
+
|
|
80
|
+
Executes the executable with the provided arguments and posts back the output and status.
|
|
81
|
+
"""
|
|
82
|
+
msg = f"Executing executable: {executable_path} with args: {args}, callback: {callback}"
|
|
83
|
+
logger.info(msg)
|
|
84
|
+
try:
|
|
85
|
+
result = subprocess.run( # noqa: S603
|
|
86
|
+
[executable_path, *args],
|
|
87
|
+
text=True,
|
|
88
|
+
capture_output=True,
|
|
89
|
+
timeout=settings.EXECUTABLE_TIMEOUT_SEC,
|
|
90
|
+
check=False,
|
|
91
|
+
)
|
|
92
|
+
output = result.stdout + result.stderr
|
|
93
|
+
return_code = result.returncode
|
|
94
|
+
except subprocess.TimeoutExpired:
|
|
95
|
+
output = "Execution timed out."
|
|
96
|
+
return_code = -1
|
|
97
|
+
except Exception as e: # noqa: BLE001
|
|
98
|
+
output = str(e)
|
|
99
|
+
return_code = -1
|
|
100
|
+
|
|
101
|
+
payload = {
|
|
102
|
+
"job_id": job_id,
|
|
103
|
+
"output": output,
|
|
104
|
+
"return_code": return_code,
|
|
105
|
+
"status": JobStatus.SUCCESSFUL if return_code == 0 else JobStatus.FAILED,
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
def _raise_callback_error(message: str, error: Exception | None = None) -> None:
|
|
109
|
+
if error:
|
|
110
|
+
raise CallbackFailedError(message) from error
|
|
111
|
+
raise CallbackFailedError(message)
|
|
112
|
+
|
|
113
|
+
try:
|
|
114
|
+
response = requests.post(str(callback), json=payload, timeout=settings.REQUEST_TIMEOUT_SEC)
|
|
115
|
+
if not (status.HTTP_200_OK <= response.status_code < status.HTTP_300_MULTIPLE_CHOICES):
|
|
116
|
+
msg = f"Callback failed: {response.text}, url: {callback}"
|
|
117
|
+
_raise_callback_error(msg)
|
|
118
|
+
except Exception as e:
|
|
119
|
+
error_msg = f"Callback error: {e}"
|
|
120
|
+
logger.exception(error_msg)
|
|
121
|
+
_raise_callback_error(error_msg, e)
|
lso/utils.py
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"""Utility functions for the LSO package."""
|
|
2
|
+
|
|
3
|
+
from concurrent.futures import ThreadPoolExecutor
|
|
4
|
+
|
|
5
|
+
from lso.config import settings
|
|
6
|
+
|
|
7
|
+
_executor = None
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def get_thread_pool() -> ThreadPoolExecutor:
|
|
11
|
+
"""Initialize or return a cached ThreadPoolExecutor for local asynchronous execution."""
|
|
12
|
+
global _executor # noqa: PLW0603
|
|
13
|
+
if _executor is None:
|
|
14
|
+
_executor = ThreadPoolExecutor(max_workers=settings.MAX_THREAD_POOL_WORKERS)
|
|
15
|
+
|
|
16
|
+
return _executor
|
lso/worker.py
CHANGED
|
@@ -19,6 +19,7 @@ from celery.signals import worker_shutting_down
|
|
|
19
19
|
from lso.config import settings
|
|
20
20
|
|
|
21
21
|
RUN_PLAYBOOK = "lso.tasks.run_playbook_proc_task"
|
|
22
|
+
RUN_EXECUTABLE = "lso.tasks.run_executable_proc_task"
|
|
22
23
|
|
|
23
24
|
celery = Celery(
|
|
24
25
|
"lso-worker",
|
|
@@ -26,11 +27,6 @@ celery = Celery(
|
|
|
26
27
|
backend=settings.CELERY_RESULT_BACKEND,
|
|
27
28
|
)
|
|
28
29
|
|
|
29
|
-
if settings.TESTING:
|
|
30
|
-
celery.conf.update(backend=settings.CELERY_RESULT_BACKEND, task_ignore_result=False)
|
|
31
|
-
else:
|
|
32
|
-
celery.conf.update(task_ignore_result=True)
|
|
33
|
-
|
|
34
30
|
celery.conf.update(
|
|
35
31
|
result_expires=settings.CELERY_RESULT_EXPIRES,
|
|
36
32
|
worker_prefetch_multiplier=1,
|
|
@@ -38,11 +34,13 @@ celery.conf.update(
|
|
|
38
34
|
task_send_sent_event=True,
|
|
39
35
|
redbeat_redis_url=settings.CELERY_BROKER_URL,
|
|
40
36
|
broker_connection_retry_on_startup=True,
|
|
37
|
+
task_ignore_result=not settings.TESTING,
|
|
41
38
|
)
|
|
42
39
|
|
|
43
40
|
if settings.WORKER_QUEUE_NAME:
|
|
44
41
|
celery.conf.task_routes = {
|
|
45
42
|
RUN_PLAYBOOK: {"queue": settings.WORKER_QUEUE_NAME},
|
|
43
|
+
RUN_EXECUTABLE: {"queue": settings.WORKER_QUEUE_NAME},
|
|
46
44
|
}
|
|
47
45
|
|
|
48
46
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: orchestrator-lso
|
|
3
|
-
Version: 2.0
|
|
3
|
+
Version: 2.1.0
|
|
4
4
|
Summary: LSO, an API for remotely running Ansible playbooks.
|
|
5
5
|
Author-email: GÉANT Orchestration and Automation Team <goat@geant.org>
|
|
6
6
|
Requires-Python: >=3.11,<3.13
|
|
@@ -25,15 +25,16 @@ Classifier: License :: OSI Approved :: Apache Software License
|
|
|
25
25
|
Classifier: Programming Language :: Python :: 3 :: Only
|
|
26
26
|
Classifier: Programming Language :: Python :: 3.11
|
|
27
27
|
Classifier: Programming Language :: Python :: 3.12
|
|
28
|
+
License-File: LICENSE
|
|
28
29
|
Requires-Dist: ansible-runner==2.4.0
|
|
29
|
-
Requires-Dist: ansible==10.
|
|
30
|
-
Requires-Dist: fastapi==0.115.
|
|
31
|
-
Requires-Dist: httpx==0.28.
|
|
32
|
-
Requires-Dist: uvicorn[standard]==0.
|
|
30
|
+
Requires-Dist: ansible==10.7.0
|
|
31
|
+
Requires-Dist: fastapi==0.115.8
|
|
32
|
+
Requires-Dist: httpx==0.28.1
|
|
33
|
+
Requires-Dist: uvicorn[standard]==0.34.0
|
|
33
34
|
Requires-Dist: requests==2.32.3
|
|
34
|
-
Requires-Dist: pydantic-settings==2.
|
|
35
|
+
Requires-Dist: pydantic-settings==2.7.1
|
|
35
36
|
Requires-Dist: celery==5.4.0
|
|
36
|
-
Requires-Dist: redis==5.2.
|
|
37
|
+
Requires-Dist: redis==5.2.1
|
|
37
38
|
Requires-Dist: types-setuptools ; extra == "dev"
|
|
38
39
|
Requires-Dist: types-requests ; extra == "dev"
|
|
39
40
|
Requires-Dist: toml ; extra == "dev"
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
lso/__init__.py,sha256=rvLpPFoeQhGsSfP4vjcdFBmHwJJhPVOzjwaMt-iNUfQ,1589
|
|
2
|
+
lso/app.py,sha256=WDtlmjELIeFA437j-WPfqBQf6QT_l35LEABbEosHlqY,775
|
|
3
|
+
lso/config.py,sha256=fceYs85UGN76GRyGs4x6TiKhktv-ykjc6-WocPRmFGY,1639
|
|
4
|
+
lso/environment.py,sha256=iZ3DmsSKAC5a7VNL-HfJOJZ0sQwUMf7ZzNGC34B2CG0,1771
|
|
5
|
+
lso/execute.py,sha256=BZeR0NYLZaLE28K7IcQ6JA7opdJOZrVK7rsSZyArsBo,1208
|
|
6
|
+
lso/playbook.py,sha256=NHCeVttY5u1xGAdty954SZEYLcZEGBbBfjPuvBCgUwI,2409
|
|
7
|
+
lso/tasks.py,sha256=ToHEDEqGipMUYk7sY8qZzrYV5dWwM8nekl2OGiEVVko,4509
|
|
8
|
+
lso/utils.py,sha256=8B3_p5nliD0KQcU2dLRdbEa7LvDqtNH0ZViMJ4yB4gE,458
|
|
9
|
+
lso/worker.py,sha256=61TXUefv8mGMq9LsD419r0O9Qxpa-WAtgSfu7SPEg44,1727
|
|
10
|
+
lso/routes/__init__.py,sha256=1kRrth9zkFgmj6LChujieYJq5cjIETeTGXa1G70pduk,639
|
|
11
|
+
lso/routes/default.py,sha256=a7STN1BJyFVizXUzmqKuADO0fpE1SHun-PzaZ-jx1wU,1438
|
|
12
|
+
lso/routes/execute.py,sha256=8WACuI8QN8piI4pkHNtxARB8l5QcBKWSZ3qE_LWqBfU,1896
|
|
13
|
+
lso/routes/playbook.py,sha256=YNPZGuJXh5JkDWJ-sVRVE-cTg9eEm6BPjNThFLDqxeM,4904
|
|
14
|
+
orchestrator_lso-2.1.0.dist-info/licenses/LICENSE,sha256=CgFXf7XbZXJADozQIw2uUmmvU-zwAwXo4u7cgDfx3rE,10744
|
|
15
|
+
orchestrator_lso-2.1.0.dist-info/WHEEL,sha256=_2ozNFCLWc93bK4WKHCO-eDUENDlo-dgc9cU3qokYO4,82
|
|
16
|
+
orchestrator_lso-2.1.0.dist-info/METADATA,sha256=TlUqzxd8VwnU0I7RgS4xFxi2XfqAM2yabT3a0Zo2Hcs,6351
|
|
17
|
+
orchestrator_lso-2.1.0.dist-info/RECORD,,
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
lso/__init__.py,sha256=Q6PrVmj3JOVZ4MJ9MYMVEI8L8w0GZy0EQZb4baSbzwA,1465
|
|
2
|
-
lso/app.py,sha256=WDtlmjELIeFA437j-WPfqBQf6QT_l35LEABbEosHlqY,775
|
|
3
|
-
lso/config.py,sha256=BXUl8SgYdaXbrmmqBRgxobGboCn02qfp_wdvZHZr_Gk,1627
|
|
4
|
-
lso/environment.py,sha256=iZ3DmsSKAC5a7VNL-HfJOJZ0sQwUMf7ZzNGC34B2CG0,1771
|
|
5
|
-
lso/playbook.py,sha256=PRnZMa93FZ-3s1pn2B4p6PBii-VN-Ti8DyhuMd8AI0I,2766
|
|
6
|
-
lso/tasks.py,sha256=d_JRESlfs2dw-KZMLu2F21VHOz7l-5FhrhTDX58Pn38,2484
|
|
7
|
-
lso/worker.py,sha256=3Y32F2DknsVK3JY4M6hDk35lvn7xLHqJhAIIbvxYIFE,1730
|
|
8
|
-
lso/routes/__init__.py,sha256=1kRrth9zkFgmj6LChujieYJq5cjIETeTGXa1G70pduk,639
|
|
9
|
-
lso/routes/default.py,sha256=a7STN1BJyFVizXUzmqKuADO0fpE1SHun-PzaZ-jx1wU,1438
|
|
10
|
-
lso/routes/playbook.py,sha256=Q0Q-9fLOYpahgC7WJ5SEFt9P2NJnAKdQg3onjVnd6js,4903
|
|
11
|
-
orchestrator_lso-2.0.1.dist-info/LICENSE,sha256=CgFXf7XbZXJADozQIw2uUmmvU-zwAwXo4u7cgDfx3rE,10744
|
|
12
|
-
orchestrator_lso-2.0.1.dist-info/WHEEL,sha256=CpUCUxeHQbRN5UGRQHYRJorO5Af-Qy_fHMctcQ8DSGI,82
|
|
13
|
-
orchestrator_lso-2.0.1.dist-info/METADATA,sha256=Ex0bEBcEdeOPYt-mu1BbbkF2_Rqct3iww2u4nlQEGJk,6329
|
|
14
|
-
orchestrator_lso-2.0.1.dist-info/RECORD,,
|
|
File without changes
|