From 6031bada9748ead4c7e7bfcd173a6089987e345e Mon Sep 17 00:00:00 2001 From: Karel van Klink <karel.vanklink@geant.org> Date: Thu, 7 Mar 2024 11:44:17 +0100 Subject: [PATCH] move project to github --- .github/workflows/sphinx.yaml | 30 ----- .gitignore | 6 - .gitlab-ci.yml | 49 -------- Dockerfile | 12 +- build-docs.sh | 12 -- docs/.gitlab-ci.yml | 19 --- docs/Makefile | 20 ---- docs/source/_static/custom.css | 13 --- docs/source/_static/geant_logo_white.svg | 16 --- docs/source/conf.py | 78 ------------- docs/source/index.rst | 11 -- docs/source/module/config.rst | 6 - docs/source/module/playbook.rst | 7 -- docs/source/module/routes/default.rst | 6 - docs/source/module/routes/index.rst | 16 --- docs/source/module/routes/playbook.rst | 6 - docs/source/modules.rst | 31 ----- docs/source/quickstart.rst | 86 -------------- docs/vale/.vale.ini | 26 ----- .../config/vocabularies/Sphinx/accept.txt | 1 - .../vocabularies/geant-jargon/accept.txt | 4 - lso/__init__.py | 34 ------ lso/app.py | 10 -- lso/config.py | 57 --------- lso/environment.py | 36 ------ lso/playbook.py | 109 ------------------ lso/routes/__init__.py | 1 - lso/routes/default.py | 30 ----- lso/routes/playbook.py | 78 ------------- pyproject.toml | 105 ----------------- requirements.txt | 19 ++- setup.py | 37 +----- sonar.properties | 6 - test/__init__.py | 0 test/conftest.py | 62 ---------- test/routes/__init__.py | 0 test/routes/test_default.py | 20 ---- test/routes/test_playbook.py | 107 ----------------- test/test_config.py | 31 ----- tox.ini | 20 ---- 40 files changed, 16 insertions(+), 1201 deletions(-) delete mode 100644 .github/workflows/sphinx.yaml delete mode 100755 build-docs.sh delete mode 100644 docs/.gitlab-ci.yml delete mode 100644 docs/Makefile delete mode 100644 docs/source/_static/custom.css delete mode 100644 docs/source/_static/geant_logo_white.svg delete mode 100644 docs/source/conf.py delete mode 100644 docs/source/index.rst delete mode 100644 docs/source/module/config.rst delete mode 100644 docs/source/module/playbook.rst delete mode 100644 docs/source/module/routes/default.rst delete mode 100644 docs/source/module/routes/index.rst delete mode 100644 docs/source/module/routes/playbook.rst delete mode 100644 docs/source/modules.rst delete mode 100644 docs/source/quickstart.rst delete mode 100644 docs/vale/.vale.ini delete mode 100644 docs/vale/styles/config/vocabularies/Sphinx/accept.txt delete mode 100644 docs/vale/styles/config/vocabularies/geant-jargon/accept.txt delete mode 100644 lso/__init__.py delete mode 100644 lso/app.py delete mode 100644 lso/config.py delete mode 100644 lso/environment.py delete mode 100644 lso/playbook.py delete mode 100644 lso/routes/__init__.py delete mode 100644 lso/routes/default.py delete mode 100644 lso/routes/playbook.py delete mode 100644 pyproject.toml delete mode 100644 sonar.properties delete mode 100644 test/__init__.py delete mode 100644 test/conftest.py delete mode 100644 test/routes/__init__.py delete mode 100644 test/routes/test_default.py delete mode 100644 test/routes/test_playbook.py delete mode 100644 test/test_config.py delete mode 100644 tox.ini diff --git a/.github/workflows/sphinx.yaml b/.github/workflows/sphinx.yaml deleted file mode 100644 index 60011ac..0000000 --- a/.github/workflows/sphinx.yaml +++ /dev/null @@ -1,30 +0,0 @@ -name: "Sphinx: render docs" - -on: - push: - branches: - - develop - -jobs: - docs: - runs-on: ubuntu-latest - permissions: - contents: write - steps: - - uses: actions/checkout@v4 - - name: Set up Python environment - uses: actions/setup-python@v4 - with: - python-version: 3.11 - - name: Install requirements - run: | - python3 -m pip install -r requirements.txt - python3 -m pip install -e . - - name: Build documentation - run: TZ=UTC sphinx-build -b html docs/source docs/build - - uses: peaceiris/actions-gh-pages@v3 - with: - publish_branch: gh-pages - github_token: ${{ secrets.GITHUB_TOKEN }} - publish_dir: docs/build/ - enable_jekyll: false diff --git a/.gitignore b/.gitignore index bce88d2..8fb4cb7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,15 +1,9 @@ __pycache__ -.coverage .idea .vscode .tox -coverage.xml *.egg-info -docs/build -docs/source/_static/openapi.json -docs/vale/styles/* -!docs/vale/styles/config/ venv/ .venv/ diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 3bd2f9e..8c0541a 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,56 +1,7 @@ --- stages: - - tox - - documentation - - sonarqube - trigger_jenkins_build -include: - - docs/.gitlab-ci.yml - -#################################### tox - Testing and linting -run-tox-pipeline: - stage: tox - tags: - - docker-executor - image: python:3.11 - - # Change pip's cache directory to be inside the project directory since we can - # only cache local items. - variables: - PIP_CACHE_DIR: "$CI_PROJECT_DIR/.cache/pip" - - # Pip's cache doesn't store the python packages - # https://pip.pypa.io/en/stable/topics/caching/ - # - # If you want to also cache the installed packages, you have to install - # them in a virtualenv and cache it as well. - cache: - paths: - - .cache/pip - - before_script: - - pip install virtualenv - - virtualenv venv - - . venv/bin/activate - - pip install tox - - script: - - tox - - artifacts: - paths: - - htmlcov - - docs/source/_static/openapi.json - -sonarqube: - stage: sonarqube - image: sonarsource/sonar-scanner-cli - script: - - sonar-scanner -Dsonar.login=$SONAR_TOKEN -Dproject.settings=./sonar.properties - tags: - - docker-executor - trigger_jenkins_build: stage: trigger_jenkins_build image: alpine:latest diff --git a/Dockerfile b/Dockerfile index c2abcb9..5eac59f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,17 +1,12 @@ -FROM python:3.11-alpine - -ARG ARTIFACT_VERSION +FROM ghcr.io/workfloworchestrator/lso WORKDIR /app COPY ./ansible-galaxy-requirements.yaml ./ansible-galaxy-requirements.yaml +COPY ./requirements.txt ./requirements.txt RUN apk add --update --no-cache gcc libc-dev libffi-dev curl vim bash openssh -RUN pip install \ - --pre \ - --trusted-host 150.254.211.2 \ - --extra-index-url https://150.254.211.2/artifactory/api/pypi/geant-swd-pypi/simple \ - goat-lso==${ARTIFACT_VERSION} +RUN pip install -r requirements.txt RUN ansible-galaxy install \ -r ansible-galaxy-requirements.yaml \ -p /app/gap/ansible @@ -19,5 +14,4 @@ RUN ansible-galaxy collection install \ -r ansible-galaxy-requirements.yaml \ -p /app/gap/ansible EXPOSE 8000 -ENTRYPOINT [] CMD ["python", "-m", "uvicorn", "lso.app:app", "--host", "0.0.0.0", "--port", "8000"] diff --git a/build-docs.sh b/build-docs.sh deleted file mode 100755 index 9ac7997..0000000 --- a/build-docs.sh +++ /dev/null @@ -1,12 +0,0 @@ -#!/bin/sh -set -o errexit -set -o nounset - -pip install -r requirements.txt -pip install -e . - -rm -r ./docs/build/* -vale --config=docs/vale/.vale.ini sync -vale --config=docs/vale/.vale.ini docs/source/*.rst lso/*.py - -sphinx-build -b html docs/source docs/build diff --git a/docs/.gitlab-ci.yml b/docs/.gitlab-ci.yml deleted file mode 100644 index 17e0252..0000000 --- a/docs/.gitlab-ci.yml +++ /dev/null @@ -1,19 +0,0 @@ ---- -##### Vale - Documentation linter -lint-documentation: - stage: documentation - image: - name: jdkato/vale:latest - entrypoint: [""] - - tags: - - docker-executor - needs: - - job: run-tox-pipeline # Only run when tox has finished - - before_script: - - cd $CI_PROJECT_DIR/docs/vale - - vale sync - - script: - - vale $CI_PROJECT_DIR/docs/source/*.rst $CI_PROJECT_DIR/lso/*.py diff --git a/docs/Makefile b/docs/Makefile deleted file mode 100644 index d0c3cbf..0000000 --- a/docs/Makefile +++ /dev/null @@ -1,20 +0,0 @@ -# Minimal makefile for Sphinx documentation -# - -# You can set these variables from the command line, and also -# from the environment for the first two. -SPHINXOPTS ?= -SPHINXBUILD ?= sphinx-build -SOURCEDIR = source -BUILDDIR = build - -# Put it first so that "make" without argument is like "make help". -help: - @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) - -.PHONY: help Makefile - -# Catch-all target: route all unknown targets to Sphinx using the new -# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). -%: Makefile - @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/docs/source/_static/custom.css b/docs/source/_static/custom.css deleted file mode 100644 index af96111..0000000 --- a/docs/source/_static/custom.css +++ /dev/null @@ -1,13 +0,0 @@ -.wy-menu > p > span { - color: rgb(237 21 86); -} - -section > dl > .sig-object { - background: #e5e8e8 !important; - color: rgb(237 21 86) !important; - border-top: 3px solid rgb(167 179 180) !important; -} - -.code.literal { - color: rgb(226 67 1) !important; -} diff --git a/docs/source/_static/geant_logo_white.svg b/docs/source/_static/geant_logo_white.svg deleted file mode 100644 index 31bfead..0000000 --- a/docs/source/_static/geant_logo_white.svg +++ /dev/null @@ -1,16 +0,0 @@ -<svg width="79" height="35" viewBox="0 0 79 35" fill="none" xmlns="http://www.w3.org/2000/svg"> -<g clip-path="url(#clip0_101_15)"> -<path d="M15.9 17.8C16.5 17.3 17 17.1 17.4 17.1C18.4 17.1 18.7 17.8 18.7 18.2C18.5 18.3 14.2 19.7 14 19.8C13.9 19.7 13.9 19.6 13.8 19.6C14 19.4 15.9 17.8 15.9 17.8Z" fill="white" fill-opacity="0.85"/> -<path d="M0 27C0 31.8 2.1 34.3 6.3 34.3C9.1 34.3 10.8 33.1 10.8 33L10.9 32.9V26.2H5.2V28.1C5.2 28.1 8.1 28.1 8.6 28.1C8.6 28.6 8.6 31.6 8.6 31.9C8.3 32.1 7.4 32.4 6.2 32.4C3.7 32.4 2.5 30.6 2.5 27C2.5 24.9 3.1 22.4 5.9 22.4C7.8 22.4 8.5 23.5 8.5 24.5V24.8H11.1V24.5C11.1 22.1 9 20.5 5.9 20.5C2.2 20.5 0 22.9 0 27Z" fill="white" fill-opacity="0.85"/> -<path d="M20.2 20.7H12.6V34.1H20.7V32.2C20.7 32.2 15.5 32.2 14.9 32.2C14.9 31.7 14.9 28.5 14.9 28C15.5 28 20.2 28 20.2 28V26.1C20.2 26.1 15.5 26.1 14.9 26.1C14.9 25.6 14.9 23 14.9 22.5C15.5 22.5 20.5 22.5 20.5 22.5V20.6H20.2V20.7Z" fill="white" fill-opacity="0.85"/> -<path d="M54.5 20.7H42.9C42.9 20.7 42.9 28.7 42.9 30.6C42 29 37.2 20.7 37.2 20.7H34.5V34.1H36.8C36.8 34.1 36.8 26.1 36.8 24.2C37.7 25.8 42.5 34.1 42.5 34.1H45.2C45.2 34.1 45.2 23.2 45.2 22.6C45.7 22.6 48.4 22.6 48.9 22.6C48.9 23.2 48.9 34.1 48.9 34.1H51.3C51.3 34.1 51.3 23.2 51.3 22.6C51.8 22.6 54.9 22.6 54.9 22.6V20.7H54.5V20.7Z" fill="white" fill-opacity="0.85"/> -<path d="M28.9 20.7H28.7H26.4L21.4 34.1H23.8C23.8 34.1 25.1 30.6 25.3 30.2C25.7 30.2 29.8 30.2 30.2 30.2C30.3 30.6 31.7 34.1 31.7 34.1H34L28.9 20.7ZM25.9 28.3C26.1 27.6 27.3 24.4 27.7 23.3C28.1 24.4 29.2 27.6 29.5 28.3C28.7 28.3 26.6 28.3 25.9 28.3Z" fill="white" fill-opacity="0.85"/> -<path d="M77 8C68.2 -2.9 32.6 12.5 23 16.5C22.3 16.8 21.4 16.7 20.9 15.7C21.3 16.7 22.1 17.1 23.1 16.7C35.8 11.6 66.5 0.600002 74.2 10.7C77.7 15.3 76.7 20.9 72.9 28.8C72.7 29.1 72.6 29.4 72.6 29.4C72.6 29.4 72.6 29.4 72.6 29.5C72.6 29.5 72.6 29.5 72.6 29.6C72.3 30.1 71.9 30.3 71.6 30.4C72 30.4 72.5 30.2 72.9 29.6C73 29.5 73.1 29.3 73.3 29C78.7 19.5 80.7 12.5 77 8Z" fill="white" fill-opacity="0.85"/> -<path d="M70.3 29.9C70.2 29.8 68.6 28.4 67 26.9C58.7 19 33.4 -5.3 22.4 1.1C19.3 2.9 18.8 8.2 20.7 15.2C20.7 15.3 20.8 15.4 20.8 15.5C21 16.2 21.5 16.7 22.2 16.7C21.7 16.6 21.3 16.2 21.1 15.7C21.1 15.6 21 15.5 21 15.5C21 15.4 20.9 15.3 20.9 15.1C20.9 15 20.9 14.9 20.8 14.9C19.8 9 21 5.1 23.4 3.5C32.3 -2.5 53.5 15.8 64.2 25C66.6 27.1 69.4 29.5 70.2 30.1C71.4 31 72.4 30 72.7 29.5C72.3 30.1 71.3 30.7 70.3 29.9Z" fill="white" fill-opacity="0.85"/> -</g> -<defs> -<clipPath id="clip0_101_15"> -<rect width="78.9" height="34.3" fill="white"/> -</clipPath> -</defs> -</svg> diff --git a/docs/source/conf.py b/docs/source/conf.py deleted file mode 100644 index c937dec..0000000 --- a/docs/source/conf.py +++ /dev/null @@ -1,78 +0,0 @@ -# Configuration file for the Sphinx documentation builder. -# -# For the full list of built-in configuration values, see the documentation: -# https://www.sphinx-doc.org/en/master/usage/configuration.html - -# -- Path setup -------------------------------------------------------------- - -# If extensions (or modules to document with autodoc) are in another directory, -# add these directories to sys.path here. If the directory is relative to the -# documentation root, use os.path.abspath to make it absolute, like shown here. -from importlib import import_module -from docutils.parsers.rst import Directive -from docutils import nodes -from sphinx import addnodes -import json -import os -import sys - -sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", "lso"))) - - -class RenderAsJSON(Directive): - # cf. https://stackoverflow.com/a/59883833 - - required_arguments = 1 - - def run(self): - module_path, member_name = self.arguments[0].rsplit(".", 1) - - member_data = getattr(import_module(module_path), member_name) - code = json.dumps(member_data, indent=2) - - literal = nodes.literal_block(code, code) - literal["language"] = "json" - - return [ - addnodes.desc_name(text=member_name), - addnodes.desc_content("", literal) - ] - - -def setup(app): - app.add_directive("asjson", RenderAsJSON) - - -# -- Project information ----------------------------------------------------- - -project = "Lightweight Service Orchestrator" -copyright = "2023, GÉANT Vereniging" -author = "GÉANT Orchestration & Automation Team" - -# -- General configuration --------------------------------------------------- - -extensions = [ - "sphinx_rtd_theme", - "sphinx.ext.autodoc", - "sphinx.ext.coverage", - "sphinx.ext.todo" -] - -templates_path = ["templates"] -exclude_patterns = [] - -# -- Options for HTML output ------------------------------------------------- - -html_theme = "sphinx_rtd_theme" -html_static_path = ["_static"] -html_theme_options = {"style_nav_header_background": "rgb(0 63 95)"} -html_css_files = ["custom.css"] -html_logo = "_static/geant_logo_white.svg" - - -# Both the class' and the ``__init__`` method's docstring are concatenated and inserted. -autoclass_content = "both" -autodoc_typehints = "none" - -# Display todos by setting to True -todo_include_todos = True diff --git a/docs/source/index.rst b/docs/source/index.rst deleted file mode 100644 index 9e5c505..0000000 --- a/docs/source/index.rst +++ /dev/null @@ -1,11 +0,0 @@ -Lightweight Service Orchestrator -================================ - -Documentation for LSO (Lightweight Service Orchestrator). - -.. toctree:: - :maxdepth: 1 - :caption: Contents: - - quickstart - modules diff --git a/docs/source/module/config.rst b/docs/source/module/config.rst deleted file mode 100644 index feabe09..0000000 --- a/docs/source/module/config.rst +++ /dev/null @@ -1,6 +0,0 @@ -``lso.config`` -============== - -.. automodule:: lso.config - :members: - :show-inheritance: diff --git a/docs/source/module/playbook.rst b/docs/source/module/playbook.rst deleted file mode 100644 index 7a9bd43..0000000 --- a/docs/source/module/playbook.rst +++ /dev/null @@ -1,7 +0,0 @@ -``lso.playbook`` -================ - -.. automodule:: lso.playbook - :members: - :private-members: - :show-inheritance: diff --git a/docs/source/module/routes/default.rst b/docs/source/module/routes/default.rst deleted file mode 100644 index af64afb..0000000 --- a/docs/source/module/routes/default.rst +++ /dev/null @@ -1,6 +0,0 @@ -``lso.routes.default`` -====================== - -.. automodule:: lso.routes.default - :members: - :show-inheritance: diff --git a/docs/source/module/routes/index.rst b/docs/source/module/routes/index.rst deleted file mode 100644 index 1255547..0000000 --- a/docs/source/module/routes/index.rst +++ /dev/null @@ -1,16 +0,0 @@ -``lso.routes`` -============== - -.. automodule:: lso.routes - :members: - :show-inheritance: - -Submodules ----------- - -.. toctree:: - :maxdepth: 2 - :titlesonly: - - default - playbook diff --git a/docs/source/module/routes/playbook.rst b/docs/source/module/routes/playbook.rst deleted file mode 100644 index 7a3da3c..0000000 --- a/docs/source/module/routes/playbook.rst +++ /dev/null @@ -1,6 +0,0 @@ -``lso.routes.playbook`` -======================= - -.. automodule:: lso.routes.playbook - :members: - :show-inheritance: diff --git a/docs/source/modules.rst b/docs/source/modules.rst deleted file mode 100644 index 1455bfc..0000000 --- a/docs/source/modules.rst +++ /dev/null @@ -1,31 +0,0 @@ -========================= -Sub-packages and -modules -========================= - -This page lists references to the documentation of all sub-packages and -modules. - -Subpackages ------------ - -.. toctree:: - :maxdepth: 2 - :titlesonly: - - module/routes/index - -Submodules ----------- - -.. toctree:: - :maxdepth: 2 - :titlesonly: - - module/config - module/playbook - - -========== - -.. automodule:: lso - :members: - :show-inheritance: diff --git a/docs/source/quickstart.rst b/docs/source/quickstart.rst deleted file mode 100644 index f9ae6b7..0000000 --- a/docs/source/quickstart.rst +++ /dev/null @@ -1,86 +0,0 @@ -Quick start -=========== - -This is a quick setup guide for running standalone on your local machine. - -As a Docker container ---------------------- -To run LSO as a Docker container, build an image using the ``Dockerfile`` as an example. Be sure to update -``ansible-galaxy-requirements.yaml`` accordingly, depending on your specific Ansible collection and -role needs. - -An example Docker compose file is presented below: - -.. code-block:: yaml - - version: "3.5" - services: - lso: - image: goat-lso:$LSO_VERSION_TAG - environment: - SETTINGS_FILENAME: /app/config.json - ANSIBLE_ROLES_PATH: /app/lso/ansible_roles - volumes: - - "/home/user/config.json:/app/config.json:ro" - - "/home/user/ansible_inventory:/opt/ansible_inventory:ro" - - "~/.ssh/id_ed25519.pub:/root/.ssh/id_rsa.pub:ro" - - "~/.ssh/id_ed25519:/root/.ssh/id_rsa:ro" - restart: unless-stopped - -This will expose the API on port 8080. The container requires some more files to be mounted: - -* A ``config.json`` that references to the location where the Ansible playbooks are stored **inside the container**. -* An Ansible inventory for all host and group variables that are used in the playbooks -* A public/private key pair for SSH authentication on external machines that are targeted by Ansible playbooks. -* Any Ansible-specific configuration (such as ``collections_path``, ``roles_path``, etc.) should be set using - environment variables. ``ANSIBLE_ROLES_PATH`` is given as an example in the Docker compose snippet above. - -Install the module ------------------- - -As an alternative, below are a set of instructions for installing and running LSO directly on a machine. - -*One of these should be what you're looking for:* - -* Install the latest module snapshot - -.. code-block:: bash - - python3 -m venv my-venv-directory - . my-venv-directory/bin/activate - - pip install --pre --extra-index-url https://artifactory.software.geant.org/artifactory/api/pypi/geant-swd-pypi/simple goat-lso - -* Install the source code - -.. code-block:: bash - - python3 -m venv my-venv-directory - . my-venv-directory/bin/activate - - git clone https://gitlab.software.geant.org/goat/gap/lso.git - cd lso - pip install -e . - - # for a full dev environment - pip install -r requirements.txt - -Running the app ---------------- - -* Create a settings file, see ``config.json.example`` for an example. -* If necessary, set the environment variable ``ANSIBLE_HOME`` to a custom path. -* Run the app like this (``app.py`` starts the server on port 44444): - - .. code-block:: bash - - SETTINGS_FILENAME=/absolute/path/to/config.json python -m lso.app - -Examples - -* Get the version - - .. code-block:: bash - - curl http://localhost:44444/api/version - -* View the docs by loading http://localhost:44444/docs in your browser diff --git a/docs/vale/.vale.ini b/docs/vale/.vale.ini deleted file mode 100644 index 36aa7e6..0000000 --- a/docs/vale/.vale.ini +++ /dev/null @@ -1,26 +0,0 @@ -StylesPath = styles - -MinAlertLevel = suggestion - -Vocab = geant-jargon, Sphinx - -Packages = proselint, Microsoft - -[*] -BasedOnStyles = Vale, proselint, Microsoft -; Found to be too intrusive -Microsoft.Passive = NO -; We are not a general audience -Microsoft.GeneralURL = NO -; It's okay to leave TODOs in the code, that's what they're for -proselint.Annotations = NO -; Replacing a ... with … shouldn't be holding back the entire CI pipeline -proselint.Typography = warning -; Same applies for not using contractions -Microsoft.Contractions = warning -Microsoft.Headings = NO - -TokenIgnores = (:class:`\S+`) - -[formats] -py = rst diff --git a/docs/vale/styles/config/vocabularies/Sphinx/accept.txt b/docs/vale/styles/config/vocabularies/Sphinx/accept.txt deleted file mode 100644 index 0a57a0f..0000000 --- a/docs/vale/styles/config/vocabularies/Sphinx/accept.txt +++ /dev/null @@ -1 +0,0 @@ -param diff --git a/docs/vale/styles/config/vocabularies/geant-jargon/accept.txt b/docs/vale/styles/config/vocabularies/geant-jargon/accept.txt deleted file mode 100644 index db8478e..0000000 --- a/docs/vale/styles/config/vocabularies/geant-jargon/accept.txt +++ /dev/null @@ -1,4 +0,0 @@ -[LSO|lso] -[Ss]ubpackages -Vereniging -against diff --git a/lso/__init__.py b/lso/__init__.py deleted file mode 100644 index 2d752ef..0000000 --- a/lso/__init__.py +++ /dev/null @@ -1,34 +0,0 @@ -"""Automatically invoked app factory.""" - -import logging - -from fastapi import FastAPI -from fastapi.middleware.cors import CORSMiddleware - -from lso import config, environment -from lso.routes.default import router as default_router -from lso.routes.playbook import router as playbook_router - - -def create_app() -> FastAPI: - """Override default settings with those found in the file read from environment variable `SETTINGS_FILENAME`. - - :return: a new flask app instance - """ - app = FastAPI() - - app.add_middleware( - CORSMiddleware, allow_origins=["*"], allow_credentials=True, allow_methods=["*"], allow_headers=["*"] - ) - - app.include_router(default_router, prefix="/api") - app.include_router(playbook_router, prefix="/api/playbook") - - # test that config params are loaded and available - config.load() - - environment.setup_logging() - - logging.info("FastAPI app initialized") - - return app diff --git a/lso/app.py b/lso/app.py deleted file mode 100644 index 8ca6862..0000000 --- a/lso/app.py +++ /dev/null @@ -1,10 +0,0 @@ -"""Default app creation.""" - -import lso - -app = lso.create_app() - -if __name__ == "__main__": - import uvicorn - - uvicorn.run("lso.app:app", host="0.0.0.0", port=44444, log_level="debug") diff --git a/lso/config.py b/lso/config.py deleted file mode 100644 index ddc1781..0000000 --- a/lso/config.py +++ /dev/null @@ -1,57 +0,0 @@ -"""A module for loading configuration data, including a config schema that data is validated against. - -Data is loaded from a file, the location of which may be specified when using :func:`load_from_file`. -Config file location can also be loaded from environment variable ``$SETTINGS_FILENAME``, which is default behaviour in -:func:`load`. -""" - -import json -import os -from pathlib import Path - -import jsonschema -from pydantic import BaseModel - -CONFIG_SCHEMA = { - "$schema": "http://json-schema.org/draft-07/schema#", - "type": "object", - "properties": {"ansible_playbooks_root_dir": {"type": "string"}}, - "required": ["ansible_playbooks_root_dir"], - "additionalProperties": False, -} -DEFAULT_REQUEST_TIMEOUT = 10 - - -class Config(BaseModel): - """Simple Config class. - - Contains the root directory at which Ansible playbooks are present. - """ - - ansible_playbooks_root_dir: str - - -def load_from_file(file: Path) -> Config: - """Load, validate and return configuration parameters. - - Input is validated against this jsonschema: - - .. asjson:: lso.config.CONFIG_SCHEMA - - :param file: :class:`Path` object that produces the config file. - :return: a dict containing the parsed configuration parameters. - """ - config = json.loads(file.read_text()) - jsonschema.validate(config, CONFIG_SCHEMA) - return Config(**config) - - -def load() -> Config: - """Load a config file, located at the path specified in the environment variable ``$SETTINGS_FILENAME``. - - Loading and validating the file is performed by :func:`load_from_file`. - - :return: a dict containing the parsed configuration parameters - """ - assert "SETTINGS_FILENAME" in os.environ, "Environment variable SETTINGS_FILENAME not set" # noqa: S101 - return load_from_file(Path(os.environ["SETTINGS_FILENAME"])) diff --git a/lso/environment.py b/lso/environment.py deleted file mode 100644 index f50d645..0000000 --- a/lso/environment.py +++ /dev/null @@ -1,36 +0,0 @@ -"""Environment module for setting up logging.""" - -import json -import logging.config -import os -from pathlib import Path - -LOGGING_DEFAULT_CONFIG = { - "version": 1, - "disable_existing_loggers": False, - "formatters": {"simple": {"format": "%(asctime)s - %(name)s (%(lineno)d) - %(levelname)s - %(message)s"}}, - "handlers": { - "console": { - "class": "logging.StreamHandler", - "level": "DEBUG", - "formatter": "simple", - "stream": "ext://sys.stdout", - } - }, - "loggers": {"resource_management": {"level": "DEBUG", "handlers": ["console"], "propagate": False}}, - "root": {"level": "INFO", "handlers": ["console"]}, -} - - -def setup_logging() -> None: - """Set up logging using the configured filename. - - If LOGGING_CONFIG is defined in the environment, use this for the filename, otherwise use LOGGING_DEFAULT_CONFIG. - """ - logging_config = LOGGING_DEFAULT_CONFIG - if "LOGGING_CONFIG" in os.environ: - filename = os.environ["LOGGING_CONFIG"] - config_file = Path(filename).read_text() - logging_config = json.loads(config_file) - - logging.config.dictConfig(logging_config) diff --git a/lso/playbook.py b/lso/playbook.py deleted file mode 100644 index 32155c5..0000000 --- a/lso/playbook.py +++ /dev/null @@ -1,109 +0,0 @@ -"""Module that gathers common API responses and data models.""" - -import logging -import threading -import uuid -from pathlib import Path -from typing import Any - -import ansible_runner -import requests -from fastapi import status -from fastapi.responses import JSONResponse -from pydantic import HttpUrl - -from lso import config -from lso.config import DEFAULT_REQUEST_TIMEOUT - -logger = logging.getLogger(__name__) - - -def get_playbook_path(playbook_name: str) -> Path: - """Get the path of a playbook on the local filesystem.""" - config_params = config.load() - return Path(config_params.ansible_playbooks_root_dir) / playbook_name - - -def playbook_launch_success(job_id: str) -> JSONResponse: - """Return a :class:`PlaybookLaunchResponse` for the successful start of a playbook execution. - - :return JSONResponse: A playbook launch response that's successful. - """ - return JSONResponse(content={"job_id": job_id}, status_code=status.HTTP_201_CREATED) - - -def playbook_launch_error(reason: str, status_code: int = status.HTTP_400_BAD_REQUEST) -> JSONResponse: - """Return a :class:`PlaybookLaunchResponse` for the erroneous start of a playbook execution. - - :param str reason: The reason why a request has failed. - :param status status_code: The HTTP status code that should be associated with this request. Defaults to HTTP 400: - Bad request. - :return JSONResponse: A playbook launch response that's unsuccessful. - """ - return JSONResponse(content={"error": reason}, status_code=status_code) - - -def _run_playbook_proc( - job_id: str, playbook_path: str, extra_vars: dict, inventory: dict[str, Any] | str, callback: str -) -> None: - """Run a playbook, internal function. - - :param str job_id: Identifier of the job that's executed. - :param str playbook_path: Ansible playbook to be executed. - :param dict extra_vars: Extra variables passed to the Ansible playbook. - :param str callback: Callback URL to return output to when execution is completed. - :param dict[str, Any] | str inventory: Ansible inventory to run the playbook against. - """ - ansible_playbook_run = ansible_runner.run(playbook=playbook_path, inventory=inventory, extravars=extra_vars) - - payload = { - "status": ansible_playbook_run.status, - "job_id": job_id, - "output": ansible_playbook_run.stdout.readlines(), - "return_code": int(ansible_playbook_run.rc), - } - - request_result = requests.post(callback, json=payload, timeout=DEFAULT_REQUEST_TIMEOUT) - if not status.HTTP_200_OK <= request_result.status_code < status.HTTP_300_MULTIPLE_CHOICES: - msg = f"Callback failed: {request_result.text}" - logger.error(msg) - - -def run_playbook( - playbook_path: Path, - extra_vars: dict[str, Any], - inventory: dict[str, Any] | str, - callback: HttpUrl, -) -> JSONResponse: - """Run an Ansible playbook against a specified inventory. - - :param Path playbook_path: playbook to be executed. - :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 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. - :rtype: :class:`fastapi.responses.JSONResponse` - """ - if not Path.exists(playbook_path): - msg = f"Filename '{playbook_path}' does not exist." - return playbook_launch_error(reason=msg, status_code=status.HTTP_404_NOT_FOUND) - - if not ansible_runner.utils.isinventory(inventory): - msg = "Invalid inventory provided. Should be a string, or JSON object." - return playbook_launch_error(reason=msg, status_code=status.HTTP_400_BAD_REQUEST) - - job_id = str(uuid.uuid4()) - thread = threading.Thread( - target=_run_playbook_proc, - kwargs={ - "job_id": job_id, - "playbook_path": str(playbook_path), - "inventory": inventory, - "extra_vars": extra_vars, - "callback": callback, - }, - ) - thread.start() - - return playbook_launch_success(job_id=job_id) diff --git a/lso/routes/__init__.py b/lso/routes/__init__.py deleted file mode 100644 index 6df1dee..0000000 --- a/lso/routes/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""Module of all routes that are available in LSO.""" diff --git a/lso/routes/default.py b/lso/routes/default.py deleted file mode 100644 index d23f5a1..0000000 --- a/lso/routes/default.py +++ /dev/null @@ -1,30 +0,0 @@ -"""Default route located at the root URL /. - -For now only includes a single endpoint that responds with the current version of the API and LSO. -""" - -from importlib import metadata - -from fastapi import APIRouter -from pydantic import BaseModel, constr - -API_VERSION = "1.0" -VersionString = constr(pattern=r"\d+\.\d+") - -router = APIRouter() - - -class Version(BaseModel): - """Simple model for returning a version number of both the API and the `goat-lso` module.""" - - api: VersionString # type: ignore[valid-type] - module: VersionString # type: ignore[valid-type] - - -@router.get("/version") -def version() -> Version: - """Return the version numbers of the API version, and the module version. - - :return: Version object with both API and `goat-lso` versions numbers. - """ - return Version(api=API_VERSION, module=metadata.version("goat-lso")) diff --git a/lso/routes/playbook.py b/lso/routes/playbook.py deleted file mode 100644 index 4380887..0000000 --- a/lso/routes/playbook.py +++ /dev/null @@ -1,78 +0,0 @@ -"""The API endpoint from which Ansible playbooks can be executed.""" - -import json -import tempfile -from contextlib import redirect_stderr -from io import StringIO -from typing import Annotated, Any - -from ansible.inventory.manager import InventoryManager -from ansible.parsing.dataloader import DataLoader -from fastapi import APIRouter, HTTPException, status -from fastapi.responses import JSONResponse -from pydantic import AfterValidator, BaseModel, HttpUrl - -from lso.playbook import get_playbook_path, run_playbook - -router = APIRouter() - - -def _inventory_validator(inventory: dict[str, Any] | str) -> dict[str, Any] | str: - """Validate the format of the provided inventory by trying to parse it. - - If an inventory cannot be parsed without warnings or errors, these are returned to the user by means of an HTTP - status 422 for 'unprocessable entity'. - """ - loader = DataLoader() - output = StringIO() - with tempfile.NamedTemporaryFile(mode="w+") as temp_inv, redirect_stderr(output): - json.dump(inventory, temp_inv, ensure_ascii=False) - temp_inv.flush() - - inventory_manager = InventoryManager(loader=loader, sources=[temp_inv.name], parse=True) - inventory_manager.parse_source(temp_inv.name) - - output.seek(0) - error_messages = output.readlines() - if error_messages: - raise HTTPException(status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail=error_messages) - - return inventory - - -PlaybookInventory = Annotated[dict[str, Any] | str, AfterValidator(_inventory_validator)] - - -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 ``\n`` newline character only. - inventory: PlaybookInventory - #: 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[str, Any] = {} - - -@router.post("/") -def run_playbook_endpoint(params: PlaybookRunParams) -> JSONResponse: - """Launch an Ansible playbook to modify or deploy a subscription instance. - - The response will contain either a job ID, or error information. - - :param PlaybookRunParams params: Parameters for executing a playbook. - :return JSONResponse: Response from the Ansible runner, including a run ID. - """ - return run_playbook( - playbook_path=get_playbook_path(params.playbook_name), - extra_vars=params.extra_vars, - inventory=params.inventory, - callback=params.callback, - ) diff --git a/pyproject.toml b/pyproject.toml deleted file mode 100644 index 2871c77..0000000 --- a/pyproject.toml +++ /dev/null @@ -1,105 +0,0 @@ -[tool.mypy] -exclude = [ - "venv", - "test/*", - "docs" -] -ignore_missing_imports = true -disallow_untyped_calls = true -disallow_untyped_defs = true -disallow_incomplete_defs = true -disallow_untyped_decorators = true -no_implicit_optional = true -strict_optional = true -namespace_packages = true -warn_unused_ignores = true -warn_redundant_casts = true -warn_no_return = true -warn_unreachable = true -implicit_reexport = false -strict_equality = true -show_error_codes = true -show_column_numbers = true -# Suppress "note: By default the bodies of untyped functions are not checked" -disable_error_code = "annotation-unchecked" -# Forbid the use of a generic "type: ignore" without specifying the exact error that is ignored -enable_error_code = "ignore-without-code" - -[tool.ruff] -extend-exclude = [ - "htmlcov", - "docs", -] -ignore = [ - "COM812", - "D203", - "D213", - "N805", - "PLR0913", - "PLR0904", - "PLW1514", - "S104" -] -line-length = 120 -select = [ - "A", - "ARG", - "B", - "BLE", - "C", - "COM", - "C4", - "C90", - "D", - "DTZ", - "E", - "EM", - "ERA", - "F", - "FA", - "FBT", - "FLY", - "FURB", - "G", - "I", - "ICN", - "INP", - "ISC", - "LOG", - "N", - "PERF", - "PGH", - "PIE", - "PL", - "PT", - "PTH", - "PYI", - "Q", - "RET", - "R", - "RET", - "RSE", - "RUF", - "S", - "SIM", - "SLF", - "T", - "T20", - "TID", - "TRY", - "UP", - "W", - "YTT" -] -target-version = "py311" - -[tool.ruff.flake8-tidy-imports] -ban-relative-imports = "all" - -[tool.ruff.per-file-ignores] -"test/*" = ["D", "S101"] -"setup.py" = ["D100"] - -[tool.ruff.isort] -known-third-party = ["pydantic", "migrations"] -known-first-party = ["test", "docs"] diff --git a/requirements.txt b/requirements.txt index aa526ec..9b6bf6e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,10 +1,9 @@ -pytest~=7.4.3 -Faker~=20.0.3 -responses~=0.24.1 -sphinx~=7.2.6 -sphinx-rtd-theme~=1.3.0 -docutils~=0.18.1 -mypy~=1.7.0 -ruff~=0.1.6 -types-setuptools~=68.2.0.1 -types-requests~=2.31.0.10 +ansible_merge_vars~=5.0.0 +jinja2==3.1.2 +jmespath~=1.0.1 +junos-eznc~=2.6.8 +jxmlease~=1.0.3 +ncclient~=0.6.13 +netaddr~=0.8.0 +requests~=2.31.0 +ruamel.yaml~=0.18.5 diff --git a/setup.py b/setup.py index f3c9f8c..bac1abc 100644 --- a/setup.py +++ b/setup.py @@ -1,4 +1,4 @@ -from setuptools import find_packages, setup +from setuptools import setup setup( name="goat-lso", @@ -7,38 +7,9 @@ setup( author_email="goat@geant.org", description="Lightweight Service Orchestrator", url="https://gitlab.software.geant.org/goat/gap/lso", - packages=find_packages(), - install_requires=[ - "ansible_merge_vars~=5.0.0", - "ansible-runner~=2.3.4", - "ansible~=8.6.1", - "dictdiffer~=0.9.0", - "fastapi~=0.104.1", - "GitPython~=3.1.40", - "httpx~=0.25.1", - "jinja2==3.1.2", - "jmespath~=1.0.1", - "jsonschema~=4.20.0", - "junos-eznc~=2.6.8", - "jxmlease~=1.0.3", - "ncclient~=0.6.13", - "netaddr~=0.8.0", - "pydantic~=2.0.3", - "requests~=2.31.0", - "ruamel.yaml~=0.18.5", - "uvicorn[standard]~=0.22.0", - "xmltodict~=0.13.0", - ], + packages=[], + install_requires=[], license="MIT", license_files=("LICENSE.txt",), - classifiers=[ - "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.11", - "License :: OSI Approved :: MIT License", - "Operating System :: OS Independent", - "Development Status :: 3 - Alpha", - "Framework :: FastAPI", - "Intended Audience :: System Administrators", - "Intended Audience :: Telecommunications Industry", - ], + classifiers=[], ) diff --git a/sonar.properties b/sonar.properties deleted file mode 100644 index 340ffdf..0000000 --- a/sonar.properties +++ /dev/null @@ -1,6 +0,0 @@ -sonar.projectKey=lso -sonar.projectName='Lightweight Service Orchestrator' -sonar.projectVersion=1.0 -sonar.sources=lso -sonar.python.coverage.reportPaths=coverage.xml -sonar.host.url=https://sonarqube.software.geant.org/ diff --git a/test/__init__.py b/test/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/test/conftest.py b/test/conftest.py deleted file mode 100644 index 09c0b82..0000000 --- a/test/conftest.py +++ /dev/null @@ -1,62 +0,0 @@ -import json -import os -import tempfile -from collections.abc import Callable, Generator -from io import StringIO -from pathlib import Path -from typing import Any - -import pytest -from faker import Faker -from fastapi.testclient import TestClient - -import lso - - -@pytest.fixture() -def mocked_ansible_runner_run() -> Callable: - class Runner: - def __init__(self) -> None: - self.status = "success" - self.rc = 0 - self.stdout = StringIO("[{'step one': 'results'}, {'step two': 2}]") - - def run(*args: Any, **kwargs: Any) -> Runner: # noqa: ARG001 - return Runner() - - return run - - -@pytest.fixture(scope="session") -def configuration_data() -> dict[str, str]: - """Start the server with valid configuration data.""" - with tempfile.TemporaryDirectory() as tempdir: - # Create required YAML files for the unit tests - (Path(tempdir) / "placeholder.yaml").touch() - - yield {"ansible_playbooks_root_dir": tempdir} - - -@pytest.fixture(scope="session") -def data_config_filename(configuration_data: dict[str, str]) -> Generator[str, Any, None]: - """Fixture that will yield a filename that contains a valid configuration. - - :return: Path to valid configuration file - """ - with tempfile.NamedTemporaryFile(mode="w") as file: - file.write(json.dumps(configuration_data)) - file.flush() - yield file.name - - -@pytest.fixture(scope="session") -def client(data_config_filename: str) -> TestClient: - """Return a client that can be used to test the server.""" - os.environ["SETTINGS_FILENAME"] = data_config_filename - app = lso.create_app() - return TestClient(app) # wait here until calling context ends - - -@pytest.fixture(scope="session") -def faker() -> Faker: - return Faker(locale="en_GB") diff --git a/test/routes/__init__.py b/test/routes/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/test/routes/test_default.py b/test/routes/test_default.py deleted file mode 100644 index 3d88dfb..0000000 --- a/test/routes/test_default.py +++ /dev/null @@ -1,20 +0,0 @@ -from importlib import metadata - -import jsonschema -import responses -from fastapi import status -from starlette.testclient import TestClient - -from lso.routes.default import API_VERSION, Version - - -@responses.activate -def test_ip_trunk_modification(client: TestClient) -> None: - rv = client.get("/api/version/") - assert rv.status_code == status.HTTP_200_OK, rv.text - response = rv.json() - - jsonschema.validate(response, Version.model_json_schema()) - - assert response["api"] == API_VERSION, response["api"] - assert response["module"] == metadata.version("goat-lso"), response["module"] diff --git a/test/routes/test_playbook.py b/test/routes/test_playbook.py deleted file mode 100644 index 8439182..0000000 --- a/test/routes/test_playbook.py +++ /dev/null @@ -1,107 +0,0 @@ -import re -import time -from collections.abc import Callable -from unittest.mock import patch - -import responses -from fastapi import status -from fastapi.testclient import TestClient - -TEST_CALLBACK_URL = "https://fqdn.abc.xyz/api/resume" - - -@responses.activate -def test_playbook_endpoint_dict_inventory_success(client: TestClient, mocked_ansible_runner_run: Callable) -> None: - responses.post(url=TEST_CALLBACK_URL, status=status.HTTP_200_OK) - - params = { - "playbook_name": "placeholder.yaml", - "callback": TEST_CALLBACK_URL, - "inventory": { - "_meta": {"vars": {"host1.local": {"foo": "bar"}, "host2.local": {"hello": "world"}}}, - "all": {"hosts": {"host1.local": None, "host2.local": None}}, - }, - "extra_vars": {"dry_run": True, "commit_comment": "I am a robot!"}, - } - - with patch("lso.playbook.ansible_runner.run", new=mocked_ansible_runner_run) as _: - rv = client.post("/api/playbook/", json=params) - assert rv.status_code == status.HTTP_201_CREATED - response = rv.json() - # wait one second for the run thread to finish - time.sleep(1) - - assert isinstance(response, dict) - assert isinstance(response["job_id"], str) - responses.assert_call_count(TEST_CALLBACK_URL, 1) - - -@responses.activate -def test_playbook_endpoint_str_inventory_success(client: TestClient, mocked_ansible_runner_run: Callable) -> None: - responses.post(url=TEST_CALLBACK_URL, status=status.HTTP_200_OK) - - params = { - "playbook_name": "placeholder.yaml", - "callback": TEST_CALLBACK_URL, - "inventory": {"all": {"hosts": "host1.local\nhost2.local\nhost3.local"}}, - } - - with patch("lso.playbook.ansible_runner.run", new=mocked_ansible_runner_run) as _: - rv = client.post("/api/playbook/", json=params) - assert rv.status_code == status.HTTP_201_CREATED - response = rv.json() - # wait one second for the run thread to finish - time.sleep(1) - - assert isinstance(response, dict) - assert isinstance(response["job_id"], str) - responses.assert_call_count(TEST_CALLBACK_URL, 1) - - -@responses.activate -def test_playbook_endpoint_invalid_host_vars(client: TestClient, mocked_ansible_runner_run: Callable) -> None: - params = { - "playbook_name": "placeholder.yaml", - "callback": TEST_CALLBACK_URL, - "inventory": { - "_meta": {"host_vars": {"host1.local": {"foo": "bar"}, "host2.local": {"hello": "world"}}}, - "all": {"hosts": "host1.local\nhost2.local\nhost3.local"}, - }, - } - - with patch("lso.playbook.ansible_runner.run", new=mocked_ansible_runner_run) as _: - rv = client.post("/api/playbook/", json=params) - assert rv.status_code == status.HTTP_422_UNPROCESSABLE_ENTITY - response = rv.json() - # wait one second for the run thread to finish - time.sleep(1) - - assert isinstance(response, dict) - assert response["detail"] == [ - '[WARNING]: Skipping unexpected key (host_vars) in group (_meta), only "vars",\n', - '"children" and "hosts" are valid\n', - ] - responses.assert_call_count(TEST_CALLBACK_URL, 0) - - -@responses.activate -def test_playbook_endpoint_invalid_hosts(client: TestClient, mocked_ansible_runner_run: Callable) -> None: - params = { - "playbook_name": "placeholder.yaml", - "callback": TEST_CALLBACK_URL, - "inventory": { - "_meta": {"vars": {"host1.local": {"foo": "bar"}}}, - "all": {"hosts": ["host1.local", "host2.local", "host3.local"]}, - }, - } - - with patch("lso.playbook.ansible_runner.run", new=mocked_ansible_runner_run) as _: - rv = client.post("/api/playbook/", json=params) - assert rv.status_code == status.HTTP_422_UNPROCESSABLE_ENTITY - response = rv.json() - # wait one second for the run thread to finish - time.sleep(1) - - assert isinstance(response, dict) - assert 'Invalid "hosts" entry for "all" group' in re.sub("\n", " ", "".join(response["detail"])) - responses.assert_call_count(TEST_CALLBACK_URL, 0) diff --git a/test/test_config.py b/test/test_config.py deleted file mode 100644 index 25a41fa..0000000 --- a/test/test_config.py +++ /dev/null @@ -1,31 +0,0 @@ -"""Set of tests that verify correct config is accepted and incorrect config is not.""" - -import json -import os -import tempfile -from pathlib import Path - -import jsonschema -import pytest - -from lso import config - - -def test_validate_testenv_config(data_config_filename: str) -> None: - """Load a configuration from a file. - - :param data_config_filename: Configuration file pytest fixture - """ - os.environ["SETTINGS_FILENAME"] = data_config_filename - params = config.load() - assert params - - -@pytest.mark.parametrize( - "bad_config", [{"name": "bad version", "version": 123}, {"name": "missing version"}, {"version": "missing name"}] -) -def test_bad_config(bad_config: dict) -> None: - with tempfile.NamedTemporaryFile(mode="w") as file: - Path(file.name).write_text(json.dumps(bad_config)) - with pytest.raises(jsonschema.ValidationError): - config.load_from_file(Path(file.name)) diff --git a/tox.ini b/tox.ini deleted file mode 100644 index 3468ba6..0000000 --- a/tox.ini +++ /dev/null @@ -1,20 +0,0 @@ -[tox] -envlist = py311 - -[testenv] -passenv = XDG_CACHE_HOME,USE_COMPOSE -setenv = - SETTINGS_FILENAME = dummy.json -deps = - coverage - -r requirements.txt - -commands = - ruff --respect-gitignore --preview . - ruff format --respect-gitignore --preview --check . - mypy . - coverage erase - coverage run --source lso -m pytest - coverage xml - coverage html - coverage report --fail-under 80 -- GitLab