From 0dc1f3ce0ac49d401b65d608239e29ecd7553647 Mon Sep 17 00:00:00 2001
From: Karel van Klink <karel.vanklink@geant.org>
Date: Thu, 30 Nov 2023 15:34:45 +0100
Subject: [PATCH] add new dynamic endpoint for executing playbooks

---
 lso/__init__.py        |  7 ++++---
 lso/playbook.py        | 11 +++++++---
 lso/routes/default.py  |  2 +-
 lso/routes/playbook.py | 46 ++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 59 insertions(+), 7 deletions(-)
 create mode 100644 lso/routes/playbook.py

diff --git a/lso/__init__.py b/lso/__init__.py
index 746cd00..6c3d936 100644
--- a/lso/__init__.py
+++ b/lso/__init__.py
@@ -6,7 +6,7 @@ from fastapi import FastAPI
 from fastapi.middleware.cors import CORSMiddleware
 
 from lso import config, environment
-from lso.routes import default, ip_trunk, router
+from lso.routes import default, ip_trunk, playbook, router
 
 
 def create_app() -> FastAPI:
@@ -25,14 +25,15 @@ def create_app() -> FastAPI:
     )
 
     app.include_router(default.router, prefix="/api")
+    app.include_router(playbook.router, prefix="/api/playbook")
     app.include_router(router.router, prefix="/api/router")
     app.include_router(ip_trunk.router, prefix="/api/ip_trunk")
 
     # test that config params are loaded and available
     config.load()
 
-    logging.info("FastAPI app initialized")
-
     environment.setup_logging()
 
+    logging.info("FastAPI app initialized")
+
     return app
diff --git a/lso/playbook.py b/lso/playbook.py
index 51320f3..a5e24c8 100644
--- a/lso/playbook.py
+++ b/lso/playbook.py
@@ -176,12 +176,17 @@ def _run_playbook_proc(
         logger.error(msg)
 
 
-def run_playbook(playbook_path: Path, extra_vars: dict, inventory: str, callback: HttpUrl) -> PlaybookLaunchResponse:
+def run_playbook(
+    playbook_path: Path,
+    extra_vars: dict[str, Any],
+    inventory: dict[str, Any] | str,
+    callback: HttpUrl,
+) -> PlaybookLaunchResponse:
     """Run an Ansible playbook against a specified inventory.
 
     :param Path playbook_path: playbook to be executed.
-    :param dict extra_vars: Any extra vars needed for the playbook to run.
-    :param [str] inventory: The inventory that the playbook is executed against.
+    :param dict[str, Any] extra_vars: Any extra vars needed for the playbook to run.
+    :param dict[str, Any] | str inventory: The inventory that the playbook is executed against.
     :param :class:`HttpUrl` callback: Callback URL where the playbook should send a status update when execution is
         completed. This is used for workflow-orchestrator to continue with the next step in a workflow.
     :return: Result of playbook launch, this could either be successful or unsuccessful.
diff --git a/lso/routes/default.py b/lso/routes/default.py
index d6f7292..25c74ee 100644
--- a/lso/routes/default.py
+++ b/lso/routes/default.py
@@ -8,7 +8,7 @@ from importlib import metadata
 from fastapi import APIRouter
 from pydantic import BaseModel, constr
 
-API_VERSION = "0.1"
+API_VERSION = "0.2"
 VersionString = constr(pattern=r"\d+\.\d+")
 
 router = APIRouter()
diff --git a/lso/routes/playbook.py b/lso/routes/playbook.py
new file mode 100644
index 0000000..670848e
--- /dev/null
+++ b/lso/routes/playbook.py
@@ -0,0 +1,46 @@
+"""The API endpoint from which Ansible playbooks can be executed."""
+
+from typing import Any
+
+from fastapi import APIRouter
+from pydantic import BaseModel, HttpUrl
+
+from lso.playbook import PlaybookLaunchResponse, get_playbook_path, run_playbook
+
+router = APIRouter()
+
+
+class PlaybookRunParams(BaseModel):
+    """Parameters for executing an Ansible playbook."""
+
+    #: The filename of a playbook that is executed. It should be present inside the directory defined in the
+    #: configuration option ``ansible_playbooks_root_dir``.
+    playbook_name: str
+    #: The address where LSO should call back to upon completion.
+    callback: HttpUrl
+    #: The inventory to run the playbook against. This inventory can also include any host vars, if needed. When
+    #: including host vars, it should be a dictionary. Can be a simple string containing hostnames when no host vars are
+    #: needed. In the latter case, multiple hosts should be separated with a newline character.
+    inventory: dict[str, Any] | str
+    #: Extra variables that should get passed to the playbook. This includes any required configuration objects
+    #: from the workflow orchestrator, commit comments, whether this execution should be a dry run, a trouble ticket
+    #: number, etc. Which extra vars are required solely depends on what inputs the playbook requires.
+    extra_vars: dict
+
+
+@router.post("/")
+def run_playbook_endpoint(params: PlaybookRunParams) -> PlaybookLaunchResponse:
+    """Launch an Ansible playbook to modify or deploy a subscription instance.
+
+    The response will contain either a job ID, or error information.
+
+    :param params :class:`PlaybookRunParams`: Parameters for executing a playbook.
+    :return: Response from the Ansible runner, including a run ID.
+    :rtype: :class:`lso.playbook.PlaybookLaunchResponse`
+    """
+    return run_playbook(
+        playbook_path=get_playbook_path(params.playbook_name),
+        extra_vars=params.extra_vars,
+        inventory=params.inventory,
+        callback=params.callback,
+    )
-- 
GitLab