diff --git a/.circleci/config.yml b/.circleci/config.yml index ed39131..5707d24 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,7 +1,7 @@ version: 2.1 orbs: - collection-testing: maxhoesel-ansible/ansible-collection-testing@0.4.0 + collection-testing: maxhoesel-ansible/ansible-collection-testing@0.5.3 filters: &semver-tagged tags: @@ -10,28 +10,44 @@ filters: &semver-tagged branches: ignore: /.*/ +executors: + pytest: + machine: + image: ubuntu-2204:current + resource_class: large + +jobs: + test: + parameters: + parallelism: + description: Number of parallel runners + type: integer + ansible-version: + description: Version of Ansible to use for testing + type: string + node-python-version: + description: Version of python to use for module tests + type: string + executor: pytest + parallelism: << parameters.parallelism >> + steps: + - collection-testing/pytest: + pytest-args: > + --ansible-version << parameters.ansible-version >> + --node-python-version << parameters.node-python-version >> + workflows: ci: jobs: + - test: + name: Test (ansible-<< matrix.ansible-version >>) + parallelism: 3 + matrix: + parameters: + ansible-version: ["2.15", "2.14"] + node-python-version: ["3.6"] - collection-testing/pre-commit-lint: name: Lint - #- collection-testing/antsibull-docs: - # name: Generate Docs - #- collection-testing/run-tox-environments: - # name: Test Modules - # match-environments: \-test\- - # parallelism: 3 - # resource-class: medium - # retries: 1 - # retry-delay: 60 - - collection-testing/run-tox-environments: - name: Test Roles - match-environments: roles - # number of scenarios * ansible versions to test - parallelism: 12 - resource-class: large - retries: 1 - retry-delay: 60 - collection-testing/publish-github: name: Publish Release to GitHub context: collection-publishing diff --git a/.config/molecule/config.yml b/.config/molecule/config.yml index f637938..c1908ff 100644 --- a/.config/molecule/config.yml +++ b/.config/molecule/config.yml @@ -3,17 +3,10 @@ dependency: name: galaxy driver: - name: podman + name: docker provisioner: name: ansible - env: {} - # Cannot enable pipelining for podman at this time: - # https://github.com/ansible-community/molecule-podman/issues/2 - #ANSIBLE_PIPELINING: false - inventory: - group_vars: - all: {} scenario: test_sequence: diff --git a/.gitignore b/.gitignore index 409c940..5141035 100644 --- a/.gitignore +++ b/.gitignore @@ -145,5 +145,12 @@ dmypy.json # Ansible collection archives *.tar.gz -# Adding this to gitignore breaks ansible-test. so let's not do that -#ansible_collections/ +docs/ansible_collections/ + +# Don't commit ephermal integration config +tests/integration/integration_config.yml +collections + +# CI files +results +split-tests diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2c961e8..3b25ea3 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -9,9 +9,8 @@ Note that by contributing to this collection, you agree with the code of conduct Prerequisites: -- A recent version of Python supported by `ansible-core` (see [here](https://docs.ansible.com/ansible/latest/installation_guide/intro_installation.html#control-node-requirements)) -- For role tests: `podman` 4 or newer set up as shown [below](#setting-up-podman) (note that Docker will *not* work for role tests!) -- For plugin tests (TBD): A recent version of Docker +- A recent version of Python supported by the current release of `ansible-core` (see [here](https://docs.ansible.com/ansible/latest/installation_guide/intro_installation.html#control-node-requirements)) +- Docker (for running Tests) Steps: @@ -27,7 +26,7 @@ Steps: ### Plugins (and Modules) There are currently no plugins in this collection. -In the future, we may look into creating plugins for accessing the Pterodactyl API. +In the future, we may add modules to wrap around the pterodactyl API. ### Roles @@ -41,74 +40,53 @@ Some general guidelines: - Keep the configuration for the user simple and try to provide sensible defaults where possible - Try to avoid using complex data structures as role variables/parameters, use simple values that can be composed easily instead. - Make sure to document any role variables in both the `README.md` and in the `meta/argument_specs.yml` file. - Te latter is used to generate role documentation programmatically. + The latter is used to generate role documentation programmatically. ## Testing Changes -We aim to test all of the components in this collection as thoroughly as possible. -We currently test all components using the following testing matrix: +We aim to test every part of this collection as thoroughly as reasonable to ensure correct behavior. +We use `pytest` to run all of our tests, both for plugins and roles. +If you set up the test environment as described in [the Getting Started guide](#getting-started), you should be able to see all available tests: -- Ansible version: The three most recent major releases (such as 6,7,8), with a compatible host Python -- Node Python: The minimum supported Python version (see `tox.ini`, used for plugi tests) -- Pterodactyl Version: The most recently released version of Panel+Wings -- For each entry in this matrix, we test all roles on all of their supported platforms, as well as all modules/plugins +`pytest --co` -To make testing all these permutations easier, we use `tox`to manage test scenarios. -If you set up the test environment as described in [the Getting Started guide](#getting-started), you should be able to run `tox -l` from the collection root: +You can run these tests using `pytest` and limit execution to specific test with `pytest -k 'test_pattern'` (or just use your editors testing plugin). +Please note that running the full test suite executes all molecule scenarios and may take **up to an hour** to complete. -This should print a list of test environments (scenarios) that you can run. -To run a specific environment, just use `tox -e {environment}`. -You can also use substitution syntax to run multiple environments at once: +### Testing different App Versions -```bash -# Will run the py3-ansible7-roles-pterodactyl_panel_selfsign environment -tox -e py3-ansible7-roles-pterodactyl_panel_selfsign +When you run the collection tests using `ptytest`, they are executed with the current stable Ansible version in `requirements.txt`. +To ensure that this collection remains backwards-compatible, we also test against older versions of Ansible. +Our testing Matrix currently looks like this: -# Will run XXX-roles-pterodactyl_panel_selfsign environment with ansible versions 6,7 and 8 -# Note the single quotes around the string! -tox -e 'py3-ansible{6,7,8}-roles-pterodactyl_panel_selfsign' -``` - -### Plugins (and Modules) - -TBD - -### Roles - -We use the `tox-ansible` plugin (v1) to integrate molecule scenarios into tox. -You can run these scenarios using `tox -e`, just like for module tests. - -Molecule itself runs the subject role against several containers to verify its functionality across target systems. -Since some roles involve the management of systemd services, we need a container runtime with good systemd support, -something which `docker` sadly doesn't offer on [modern linux distros](https://gist.github.com/pinkeen/bba0a6790fec96d6c8de84bd824ad933). +| Component | Module Tests | Role Tests | Versions | +|-----------|--------------|------------|----------| +| `ansible-core` | ✅ | ✅ | Three most recent releases (e.g. `2.13`, `2.14`, `2.15`) | +| Node Python Version | ✅ | ❌ | Collection-supported Python version (see [README](./README.md)) -Instead, we use `podman`, a daemonless, rootless container runtime developed by RedHat to be (mostly) compatible to docker, but with better support for certain features such as systemd. -See below for setup instructions. -Once podman is installed and running, you can use `tox` to run the molecule scenarios. +All possible permutations are automatically tested in CI. +You can change the tested versions locally by supplying additional arguments to `pytest`: -#### Setting up Podman - -1. Ensure that you have the following packages installed: - - `podman` version 4+ (as it comes with the new netvark networking stack) - - `aardvark-dns`, a plugin for netvark which provides DNS between containers in the same network - - **NOTE:** If you previously used an older (`<=3.x`) version of `podman`, you will have to migrate to the new networking stack fist. This can be done with `podman system reset` -2. Ensure that your user has a subuid/subguid configuration associated with them so that you can run rootless containers - - Check the `/etc/subuid` and `/etc/subgid` files for entries corresponding to your username. - - If there is nothing, you can add them like so: `sudo usermod --add-subuids 100000-165535 --add-subgids 100000-165535 ` (make sure that the range is not already taken by another user in `/etc/subuid`/`/etc/subgid`). -3. Once you have applied your changes, run `podman system migrate` to force `podman` to pick up the new configuration. - -That's it! Podman should now be working! To test it, you can run a container just like with docker: `podman run --rm -it ubuntu bash` +``` +$ pytest --help +# truncated output +Custom options: + --ansible-version=ANSIBLE_VERSION + Version of ansible to use for tests, in the format '2.xx'. Default: see requirements.txt + --node-python-version=NODE_PYTHON_VERSION + Python version to test Ansible modules with, in the format '3.x'. Default: '3.6' +``` ## Writing Tests Any new new component or change to an existing one should be covered by tests to ensure that the code works, and that it keeps working into the future. This section will help you in adding your own tests to this collection. -#### Plugins +### Plugins TBD -#### Roles +### Roles There are tons of good guides online for how to write tests using molecule. Alternatively, you can always look at the existing molecule scenarios in this collection @@ -124,7 +102,6 @@ some_role/ converge.yml molecule.yml prepare.yml - requirements.txt # --> symlink to /tests/roles/requirements.txt verify.yml another-scenario/ ... @@ -132,10 +109,7 @@ some_role/ ... ``` -The `requirements.txt` symlink is used by `tox-ansible` when running tests via `tox` to install a specific, known-good version of `molecule` and the `molecule-podman` driver. -That way, all roles and scenarios in this collection can use the same version of `molecule`. - -The [root molecule config](./.config/molecule/config.yml) contains the basic settings for molecule, such as driver setup and the step utility versions. +The [root molecule config](./.config/molecule/config.yml) contains the basic settings for molecule, such as driver setup. In addition, your roles molecule scenario must define a set of platforms to test on, as well as any inventory configuration that you may need. To get started you can copy the `molecule.yml` configuration from an existing role, then adjust it to suit your needs. @@ -145,17 +119,19 @@ TBD ## Maintainer information -### Updating Tested Versions +### Bumping supported ansible-core versions + +1. Update the versions in the [CI config](./.circleci/config.yml) + +### Bumping node python version -- Pterodactyl and ansible-core (for plugins): Add the new core version to the following places in `tox.ini`: - - The `envlist` in `tox.ini` - - each `testenv:xxx` section header that deals with plugin tests - - Set the correct environment variable in the main `testenv` section -- ansible (for roles): Change the version string in the `ansible` section in `tox.ini` +1. Update the version in [`tests/conftest.py`](./tests/conftest.py) +2. Update the version in the [CI config](./.circleci/config.yml) ### Versioning and Releases +- Releases are automatically drafted by `release-drafter`, with a changelog generated from PR labels - When merging a pull request, make sure to select an appropriate label (pr-bugfix, pr-feature, etc.). - Release-drafter will automatically update the draft release changelog and the galaxy.yml version will be bumped if needed. -- Once a draft release is actually published, collection packages will be added to the release and ansible-galaxy automatically. -- If you need to manually bump the collection version, run the `update-version` script and adjust the test versions if required. + Release-drafter will automatically update the draft release changelog and a PR will be opened with bumped collection versions. +- Once a draft release is actually published, collection packages will be published to the release and ansible-galaxy automatically. +- If you need to manually bump the collection version, run the `update-version` script diff --git a/README.md b/README.md index 32a9879..efd2869 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ for v1.0 becomes available. ### Dependencies -- A recent version of ansible. We test against the current and the 3 previous major releases +- A recent version of ansible. We test against the current and the previous major release - Python 3.6 or newer on remote hosts and the controller ### Install via ansible-galaxy diff --git a/galaxy.yml b/galaxy.yml index 2acf635..9f23ce9 100644 --- a/galaxy.yml +++ b/galaxy.yml @@ -18,7 +18,6 @@ build_ignore: - .readthedocs.yaml - pyproject.toml - requirements.txt -- tox.ini - '**/requirements.txt' dependencies: community.crypto: '>=1.0.0' diff --git a/pyproject.toml b/pyproject.toml index 7e33c32..144abf1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,20 +4,23 @@ ignore = "E402" # Pylint settings [tool.pylint.'NESSAGES CONTROL'] -disable=[ +disable = [ "logging-fstring-interpolation", # See https://github.com/PyCQA/pylint/issues/2354 "missing-module-docstring", "missing-class-docstring", - "missing-function-docstring", # also triggers on abstract functions - "consider-using-enumerate", # not for simple loops + "missing-function-docstring", # also triggers on abstract functions + "consider-using-enumerate", # not for simple loops "consider-using-dict-items", "no-else-return", "invalid-name", "too-many-boolean-expressions", "too-few-public-methods", - 'wrong-import-position', # Ansible module best practices + 'wrong-import-position', # Ansible module best practices "I", ] [tool.pylint.'FORMAT'] -max-line-length=120 +max-line-length = 120 + +[tool.pytest.ini_options] +testpaths = ["tests"] diff --git a/requirements.txt b/requirements.txt index 8cc5381..0152ebe 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,19 +2,20 @@ # Includes utilities, CLI helpers and so on # Linting & Formatting -ansible-lint==6.21.1 -pylint==3.0.2 +ansible-lint==6.20.3 +pylint==3.0.1 autopep8==2.0.4 -pre-commit==3.5.0 +pre-commit==3.4.0 -# Test scenario runner -tox==3.28.0 +# Testing libraries +pytest==7.4.2 +pytest-virtualenv==1.7.0 +docker==6.1.3 # Utility packages used in scripts pyyaml==6.0.1 packaging==23.2 -# Also include a version of ansible-core for IDE hints and such. -# Note that this version is independent of the one used in tests, see tox.ini -# and /tests +# Also include a version of ansible-core for IDE hints +# and as the default version used in tests ansible-core==2.15.5 diff --git a/roles/pterodactyl_panel/README.md b/roles/pterodactyl_panel/README.md index 7614378..c2664fc 100644 --- a/roles/pterodactyl_panel/README.md +++ b/roles/pterodactyl_panel/README.md @@ -89,7 +89,6 @@ Other versions are supported on a best-effort basis. ### Panel Settings Prefix for all variables: `pterodactyl_panel_` -[Title](../../../ansible-collection-smallstep/.config) | Name | Description | Required | Default | |------|-------------|:--------:|---------| | `app_key` | base64 encoded app key for the panel, as generated by `php artisan key:generate`. You can generate a key with this command: `echo "base64:$(openssl rand -base64 32)"` | X | undefined | diff --git a/roles/pterodactyl_panel/molecule/default/molecule.yml b/roles/pterodactyl_panel/molecule/default/molecule.yml index 683f74f..2c76a2b 100644 --- a/roles/pterodactyl_panel/molecule/default/molecule.yml +++ b/roles/pterodactyl_panel/molecule/default/molecule.yml @@ -4,10 +4,14 @@ platforms: image: "docker.io/geerlingguy/docker-ubuntu2204-ansible" groups: - panel - systemd: always + volumes: + - /sys/fs/cgroup:/sys/fs/cgroup:rw + cgroupns_mode: host + privileged: true override_command: false pre_build_image: true - network: molecule-pterodactyl-panel + networks: + - name: molecule-pterodactyl-panel - name: pterodactyl-panel-ubuntu-22-db image: docker.io/mariadb pre_build_image: true @@ -17,17 +21,22 @@ platforms: MYSQL_DATABASE: panel_molecule MYSQL_USER: panel_molecule MYSQL_PASSWORD: panel_molecule - network: molecule-pterodactyl-panel + networks: + - name: molecule-pterodactyl-panel - name: pterodactyl-panel-ubuntu-20 image: "docker.io/geerlingguy/docker-ubuntu2004-ansible" groups: - panel - php7 - systemd: always + volumes: + - /sys/fs/cgroup:/sys/fs/cgroup:rw + cgroupns_mode: host + privileged: true override_command: false pre_build_image: true - network: molecule-pterodactyl-panel + networks: + - name: molecule-pterodactyl-panel - name: pterodactyl-panel-ubuntu-20-db image: docker.io/mariadb pre_build_image: true @@ -37,16 +46,21 @@ platforms: MYSQL_DATABASE: panel_molecule MYSQL_USER: panel_molecule MYSQL_PASSWORD: panel_molecule - network: molecule-pterodactyl-panel + networks: + - name: molecule-pterodactyl-panel - name: pterodactyl-panel-debian-12 image: "docker.io/geerlingguy/docker-debian12-ansible" groups: - panel - systemd: always + volumes: + - /sys/fs/cgroup:/sys/fs/cgroup:rw + cgroupns_mode: host + privileged: true override_command: false pre_build_image: true - network: molecule-pterodactyl-panel + networks: + - name: molecule-pterodactyl-panel - name: pterodactyl-panel-debian-12-db image: docker.io/mariadb pre_build_image: true @@ -56,17 +70,22 @@ platforms: MYSQL_DATABASE: panel_molecule MYSQL_USER: panel_molecule MYSQL_PASSWORD: panel_molecule - network: molecule-pterodactyl-panel + networks: + - name: molecule-pterodactyl-panel - name: pterodactyl-panel-debian-11 image: "docker.io/geerlingguy/docker-debian11-ansible" groups: - panel - php7 - systemd: always + volumes: + - /sys/fs/cgroup:/sys/fs/cgroup:rw + cgroupns_mode: host + privileged: true override_command: false pre_build_image: true - network: molecule-pterodactyl-panel + networks: + - name: molecule-pterodactyl-panel - name: pterodactyl-panel-debian-11-db image: docker.io/mariadb pre_build_image: true @@ -76,7 +95,8 @@ platforms: MYSQL_DATABASE: panel_molecule MYSQL_USER: panel_molecule MYSQL_PASSWORD: panel_molecule - network: molecule-pterodactyl-panel + networks: + - name: molecule-pterodactyl-panel provisioner: inventory: diff --git a/roles/pterodactyl_panel/molecule/default/requirements.txt b/roles/pterodactyl_panel/molecule/default/requirements.txt deleted file mode 120000 index 0bd8d01..0000000 --- a/roles/pterodactyl_panel/molecule/default/requirements.txt +++ /dev/null @@ -1 +0,0 @@ -../../../../tests/roles/requirements.txt \ No newline at end of file diff --git a/roles/pterodactyl_wings/molecule/default/molecule.yml b/roles/pterodactyl_wings/molecule/default/molecule.yml index 345c171..657046f 100644 --- a/roles/pterodactyl_wings/molecule/default/molecule.yml +++ b/roles/pterodactyl_wings/molecule/default/molecule.yml @@ -3,56 +3,66 @@ platforms: - name: pterodactyl-wings-ubuntu-22 image: "docker.io/geerlingguy/docker-ubuntu2204-ansible" volumes: - # The wings daemon needs to connect to a docker/podman daemon. We use a sibling setup + # The wings daemon needs to connect to a docker daemon. We use a sibling setup # to allow the wings containers to access the host docker daemon by passing through the docker socket - - /var/run/podman/podman.sock:/tmp/podman.sock + - /var/run/docker.sock:/tmp/docker.sock + - /sys/fs/cgroup:/sys/fs/cgroup:rw env: # The /tmp/sock workaround is required due to this issue: https://github.com/ansible-community/molecule/issues/1568 - DOCKER_HOST: /tmp/podman.sock - systemd: always + DOCKER_HOST: /tmp/docker.sock + cgroupns_mode: host + privileged: true override_command: false pre_build_image: true - name: pterodactyl-wings-ubuntu-20 image: "docker.io/geerlingguy/docker-ubuntu2004-ansible" volumes: - # The wings daemon needs to connect to a docker/podman daemon. We use a sibling setup + # The wings daemon needs to connect to a docker daemon. We use a sibling setup # to allow the wings containers to access the host docker daemon by passing through the docker socket - - /var/run/podman/podman.sock:/tmp/podman.sock + - /var/run/docker.sock:/tmp/docker.sock + - /sys/fs/cgroup:/sys/fs/cgroup:rw env: # The /tmp/sock workaround is required due to this issue: https://github.com/ansible-community/molecule/issues/1568 - DOCKER_HOST: /tmp/podman.sock - systemd: always + DOCKER_HOST: /tmp/docker.sock + cgroupns_mode: host + privileged: true override_command: false pre_build_image: true - name: pterodactyl-wings-debian-12 image: "docker.io/geerlingguy/docker-debian12-ansible" volumes: - - /var/run/podman/podman.sock:/tmp/podman.sock + - /var/run/docker.sock:/tmp/docker.sock + - /sys/fs/cgroup:/sys/fs/cgroup:rw env: - DOCKER_HOST: /tmp/podman.sock - systemd: always + DOCKER_HOST: /tmp/docker.sock + cgroupns_mode: host + privileged: true override_command: false pre_build_image: true - name: pterodactyl-wings-debian-11 image: "docker.io/geerlingguy/docker-debian11-ansible" volumes: - - /var/run/podman/podman.sock:/tmp/podman.sock + - /var/run/docker.sock:/tmp/docker.sock + - /sys/fs/cgroup:/sys/fs/cgroup:rw env: - DOCKER_HOST: /tmp/podman.sock - systemd: always + DOCKER_HOST: /tmp/docker.sock + cgroupns_mode: host + privileged: true override_command: false pre_build_image: true - name: pterodactyl-wings-debian-10 image: "docker.io/geerlingguy/docker-debian10-ansible" volumes: - - /var/run/podman/podman.sock:/tmp/podman.sock + - /var/run/docker.sock:/tmp/docker.sock + - /sys/fs/cgroup:/sys/fs/cgroup:rw env: - DOCKER_HOST: /tmp/podman.sock - systemd: always + DOCKER_HOST: /tmp/docker.sock + cgroupns_mode: host + privileged: true override_command: false pre_build_image: true diff --git a/roles/pterodactyl_wings/molecule/default/requirements.txt b/roles/pterodactyl_wings/molecule/default/requirements.txt deleted file mode 120000 index 0bd8d01..0000000 --- a/roles/pterodactyl_wings/molecule/default/requirements.txt +++ /dev/null @@ -1 +0,0 @@ -../../../../tests/roles/requirements.txt \ No newline at end of file diff --git a/roles/pterodactyl_wings/templates/wings.service.j2 b/roles/pterodactyl_wings/templates/wings.service.j2 index 971d565..3f15917 100644 --- a/roles/pterodactyl_wings/templates/wings.service.j2 +++ b/roles/pterodactyl_wings/templates/wings.service.j2 @@ -1,6 +1,6 @@ [Unit] Description=Pterodactyl Wings Daemon -{# Hidden switch to disable binding to the docker service for testing purposes. This service may not exist with podman. Not intended for public use #} +{# Hidden switch to disable binding to the docker service for testing purposes. This service may not exist in a container. Not intended for public use #} {% if _pterodactyl_wings_systemd_dockerbind | default(true) %} After=docker.service Requires=docker.service diff --git a/scripts/setup.sh b/scripts/setup.sh index 8235582..bc12a9d 100755 --- a/scripts/setup.sh +++ b/scripts/setup.sh @@ -11,6 +11,7 @@ source .venv/bin/activate printf "Installing development requirements..." python3 -m pip install --upgrade pip --quiet python3 -m pip install --quiet -r requirements.txt --upgrade +# Also install the scenario requirements so we can run them directly python3 -m pip install --quiet -r tests/roles/requirements.txt --upgrade printf "OK\n" @@ -18,10 +19,6 @@ printf "Installing pre-commit hook..." pre-commit install > /dev/null printf "OK\n" -printf "Initializing tox..." -tox -l > /dev/null -printf "OK\n" - printf "Development venv initialized!\n" printf "To activate it, run:\n\n" printf "source .venv/bin/activate\n" diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000..e493937 --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,78 @@ +from dataclasses import dataclass +import os +from pathlib import Path +import subprocess + +from packaging import version +import pkg_resources +import pytest +from pytest_virtualenv import VirtualEnv +import yaml + +NODE_PYTHON_DEFAULT_VERSION = "3.7" + +with open("galaxy.yml", encoding="utf-8") as f: + GALAXY_YML = yaml.safe_load(f) + + +class TestEnv(): + def __init__(self, virtualenv: VirtualEnv) -> None: + self.virtualenv = virtualenv + + def run(self, *args, **kwargs): + # Combine any passed in env with the virtualenv to ensure proper PATH + if "env" in kwargs: + kwargs["env"] = {**kwargs["env"], **self.virtualenv.env} + self.virtualenv.run(*args, **kwargs) + + +def get_ansible_version(): + base_version = version.parse(pkg_resources.get_distribution("ansible-core").version) + return f"{base_version.major}.{base_version.minor}" + + +def pytest_addoption(parser): + parser.addoption("--ansible-version", action="store", default=get_ansible_version(), + help="Version of ansible to use for tests, in the format '2.xx'. Default: see requirements.txt") + parser.addoption("--node-python-version", action="store", default=NODE_PYTHON_DEFAULT_VERSION, + help="Python version to test Ansible modules with, " + f"in the format '3.x'. Default: '{NODE_PYTHON_DEFAULT_VERSION}'") + + +@pytest.fixture(scope="session") +def collection_path(tmp_path_factory) -> Path: + build_path: Path = tmp_path_factory.mktemp("build") + collection_file = build_path / f"{GALAXY_YML['namespace']}-{GALAXY_YML['name']}-{GALAXY_YML['version']}.tar.gz" + subprocess.run( + ["ansible-galaxy", "collection", "build", "--output-path", build_path], + check=True, + ) + + install_path: Path = tmp_path_factory.mktemp("collections") + env = os.environ.copy() + env["ANSIBLE_COLLECTIONS_PATH"] = install_path.resolve().as_posix() + subprocess.run( + ["ansible-galaxy", "collection", "install", collection_file], + env=env, check=True, + ) + return install_path + + +@dataclass +class TestVersions: + ansible_version: str + node_python_version: str + + @property + def ansible_version_pip(self): + major, minor = self.ansible_version.split(".") + next_minor = int(minor) + 1 + return f"ansible-core>={self.ansible_version},<{major}.{next_minor}" + + +@pytest.fixture(scope="session") +def test_versions(request) -> TestVersions: + return TestVersions( + request.config.getoption("--ansible-version"), + request.config.getoption("--node-python-version") + ) diff --git a/tests/roles/conftest.py b/tests/roles/conftest.py new file mode 100644 index 0000000..1b9bdeb --- /dev/null +++ b/tests/roles/conftest.py @@ -0,0 +1,43 @@ +import os +from pathlib import Path +from typing import Optional + +import pytest + +from tests.conftest import TestEnv + +MOLECULE_REQUIREMENTS_PIP = Path("tests/roles/requirements.txt").resolve() +MOLECULE_REQUIREMENTS_ANSIBLE = Path("tests/roles/requirements.yml").resolve() + + +class MoleculeTestEnv(TestEnv): + # pylint: disable=redefined-outer-name + def __init__(self, virtualenv, test_versions, collection_path) -> None: + self.env = {**os.environ.copy(), **{ + "ANSIBLE_COLLECTIONS_PATH": collection_path, + }} + super().__init__(virtualenv) + + self.run(["pip", "install", test_versions.ansible_version_pip]) + self.run(["pip", "install", "-r", MOLECULE_REQUIREMENTS_PIP]) + self.run(["ansible-galaxy", "collection", "install", "-r", MOLECULE_REQUIREMENTS_ANSIBLE]) + + def run(self, *args, **kwargs): + kwargs["env"] = self.env + return super().run(*args, **kwargs) + + +MOLECULE_ENV: Optional[MoleculeTestEnv] = None + + +@pytest.fixture() +# This fixture should be session-scoped, but cannot be since it requires the function-scoped virtualenv fixture. +# Use memoization for now. +# pylint: disable=redefined-outer-name +def molecule_env(virtualenv, test_versions, collection_path) -> MoleculeTestEnv: + global MOLECULE_ENV # pylint: disable=global-statement + if MOLECULE_ENV is not None: + return MOLECULE_ENV + + MOLECULE_ENV = MoleculeTestEnv(virtualenv, test_versions, collection_path) + return MOLECULE_ENV diff --git a/tests/roles/requirements.txt b/tests/roles/requirements.txt index a7e1c0b..578fa5b 100644 --- a/tests/roles/requirements.txt +++ b/tests/roles/requirements.txt @@ -1,4 +1,3 @@ # Dependencies for executing the role scenarios. -# We pin molecule to prevent issues with breaking changes molecule==6.0.2 -molecule-plugins[podman]==23.4.1 +molecule-plugins[docker]==23.4.1 diff --git a/tests/roles/requirements.yml b/tests/roles/requirements.yml new file mode 100644 index 0000000..f93a9fe --- /dev/null +++ b/tests/roles/requirements.yml @@ -0,0 +1,5 @@ +# Requirements for running molecule scenarios +# These are not the collection runtime requirements, see galaxy.yml for those +collections: + - name: community.docker + version: "==3.4.8" diff --git a/tests/roles/test_molecule.py b/tests/roles/test_molecule.py new file mode 100644 index 0000000..c4d5111 --- /dev/null +++ b/tests/roles/test_molecule.py @@ -0,0 +1,23 @@ +from pathlib import Path + +import pytest + +# List of molecule scenario directories +MOLECULE_SCENARIOS = [ + subdir + for role in Path("./roles").iterdir() + for subdir in Path(role, 'molecule').glob('*') + if subdir.is_dir() and (subdir / "molecule.yml").exists() +] + + +def scenario_id(path: Path) -> str: + return f"{path.parent.parent.name}-{path.name}" + + +@pytest.mark.parametrize("scenario", MOLECULE_SCENARIOS, ids=scenario_id) +def test_scenario(scenario: Path, molecule_env) -> None: + molecule_env.run( + ["molecule", "test", "-s", scenario.name], + cwd=scenario.parent.parent.resolve() + ) diff --git a/tox.ini b/tox.ini deleted file mode 100644 index 70faf73..0000000 --- a/tox.ini +++ /dev/null @@ -1,65 +0,0 @@ -[tox] -# plugin tests: run plugin tests with these ansible versions -# Uncomment when we add plugins -#envlist = py3-ansible{213,214,215}-test-{sanity,integration} -requires = - tox-ansible>=1.8,<2 - tox<4 -skipsdist = True - -[ansible] -# Role tests config: we test roles with these ansible/py versions -ansible = 6,7,8 -python = 3 -scenario_format = $path-$parent-$nondefault_name - -[testenv] -passenv = - HOME -setenv = - # Set the ansible version for plugin tests - ansible213: ANSIBLE_VERSION = 2.13 - ansible214: ANSIBLE_VERSION = 2.14 - ansible215: ANSIBLE_VERSION = 2.15 - # Visualization - PY_COLORS = 1 - ANSIBLE_FORCE_COLOR = 1 - -#[testenv:py3-ansible{213,214,215}-test-sanity] -#whitelist_externals = -# docker -#changedir = tests/sanity -#commands_pre = -# docker compose down -#commands = -# docker compose run --no-TTY --rm test-runner -#commands_post = -# # cleanup networks -# docker compose down --remove-orphans -# -#[testenv:py3-ansible{213,214,215}-test-units] -#whitelist_externals = -# docker -#changedir = tests/unit -#commands_pre = -# docker compose down -#commands = -# docker compose run --no-TTY --rm test-runner -#commands_post = -# # cleanup networks -# docker compose down --remove-orphans -#[testenv:py3-ansible{213,214,215}-test-integration] -#whitelist_externals = -# docker -#changedir = tests/integration -#commands_pre = -# docker compose -f docker-compose-local.yml down -# docker compose -f docker-compose-remote.yml down -#commands = -# docker compose -f docker-compose-local.yml build -# docker compose -f docker-compose-local.yml run --no-TTY --rm test-runner-local -# docker compose -f docker-compose-remote.yml run --no-TTY --rm test-runner-remote -#commands_post = -# # cleanup networks -# docker compose -f docker-compose-local.yml down --remove-orphans -# docker compose -f docker-compose-remote.yml down --remove-orphans