├── .ansible-lint ├── .ansible-lint-ignore ├── .flake8 ├── .github ├── dependabot.yml ├── release-drafter.yml └── workflows │ ├── ack.yml │ ├── push.yml │ ├── release.yml │ └── tox.yml ├── .gitignore ├── .pre-commit-config.yaml ├── LICENSE ├── MANIFEST.in ├── README.md ├── bindep.txt ├── conftest.py ├── doc ├── ec2 │ ├── README.rst │ └── platforms.rst ├── openstack │ ├── README.rst │ └── platforms.rst └── vagrant │ └── README.rst ├── molecule └── test-podman │ ├── converge.yml │ ├── molecule.yml │ └── verify.yml ├── mypy.ini ├── pyproject.toml ├── pytest.ini ├── requirements.yml ├── src └── molecule_plugins │ ├── __init__.py │ ├── azure │ ├── __init__.py │ ├── cookiecutter │ │ ├── cookiecutter.json │ │ └── {{cookiecutter.molecule_directory}} │ │ │ └── {{cookiecutter.scenario_name}} │ │ │ ├── INSTALL.rst │ │ │ ├── converge.yml │ │ │ ├── create.yml │ │ │ └── destroy.yml │ └── driver.py │ ├── containers │ ├── __init__.py │ ├── cookiecutter │ │ ├── cookiecutter.json │ │ └── {{cookiecutter.molecule_directory}} │ │ │ └── {{cookiecutter.scenario_name}} │ │ │ └── converge.yml │ └── driver.py │ ├── docker │ ├── __init__.py │ ├── cookiecutter │ │ ├── cookiecutter.json │ │ └── {{cookiecutter.molecule_directory}} │ │ │ └── {{cookiecutter.scenario_name}} │ │ │ └── converge.yml │ ├── driver.py │ └── playbooks │ │ ├── Dockerfile.j2 │ │ ├── create.yml │ │ ├── destroy.yml │ │ ├── filter_plugins │ │ └── get_docker_networks.py │ │ ├── tasks │ │ ├── create_network.yml │ │ └── delete_network.yml │ │ └── validate-dockerfile.yml │ ├── ec2 │ ├── __init__.py │ ├── cookiecutter │ │ ├── cookiecutter.json │ │ └── {{cookiecutter.molecule_directory}} │ │ │ └── {{cookiecutter.scenario_name}} │ │ │ ├── INSTALL.rst │ │ │ ├── converge.yml │ │ │ ├── create.yml │ │ │ ├── destroy.yml │ │ │ └── prepare.yml │ └── driver.py │ ├── gce │ ├── __init__.py │ ├── cookiecutter │ │ ├── cookiecutter.json │ │ └── {{cookiecutter.molecule_directory}} │ │ │ └── {{cookiecutter.scenario_name}} │ │ │ └── converge.yml │ ├── driver.py │ └── playbooks │ │ ├── create.yml │ │ ├── destroy.yml │ │ ├── files │ │ └── windows_auth.py │ │ ├── handlers │ │ └── main.yml │ │ └── tasks │ │ ├── create_linux_instance.yml │ │ └── create_windows_instance.yml │ ├── openstack │ ├── __init__.py │ ├── cookiecutter │ │ ├── cookiecutter.json │ │ └── {{cookiecutter.molecule_directory}} │ │ │ └── {{cookiecutter.scenario_name}} │ │ │ └── converge.yml │ ├── driver.py │ └── playbooks │ │ ├── create.yml │ │ ├── destroy.yml │ │ ├── prepare.yml │ │ └── tasks │ │ ├── server_addr.yml │ │ └── vars.yml │ ├── podman │ ├── __init__.py │ ├── cookiecutter │ │ ├── cookiecutter.json │ │ └── {{cookiecutter.molecule_directory}} │ │ │ └── {{cookiecutter.scenario_name}} │ │ │ └── converge.yml │ ├── driver.py │ └── playbooks │ │ ├── Dockerfile.j2 │ │ ├── create.yml │ │ ├── destroy.yml │ │ └── validate-dockerfile.yml │ ├── py.typed │ └── vagrant │ ├── __init__.py │ ├── cookiecutter │ ├── cookiecutter.json │ └── {{cookiecutter.molecule_directory}} │ │ └── {{cookiecutter.scenario_name}} │ │ ├── INSTALL.rst │ │ └── converge.yml │ ├── driver.py │ ├── modules │ ├── __init__.py │ └── vagrant.py │ └── playbooks │ ├── create.yml │ ├── destroy.yml │ └── prepare.yml ├── test ├── azure │ ├── __init__.py │ ├── functional │ │ ├── __init__.py │ │ ├── conftest.py │ │ └── test_azure.py │ ├── scenarios │ │ └── driver │ │ │ └── azure │ │ │ └── molecule │ │ │ ├── default │ │ │ ├── converge.yml │ │ │ ├── molecule.yml │ │ │ └── tests │ │ │ │ └── test_default.py │ │ │ └── multi-node │ │ │ ├── converge.yml │ │ │ ├── molecule.yml │ │ │ ├── prepare.yml │ │ │ └── tests │ │ │ ├── __init__.py │ │ │ └── test_default.py │ └── test_driver.py ├── containers │ ├── __init__.py │ ├── functional │ │ ├── .ansible-lint │ │ ├── __init__.py │ │ ├── conftest.py │ │ └── test_containers.py │ ├── scenarios │ │ └── driver │ │ │ └── containers │ │ │ └── molecule │ │ │ └── default │ │ │ ├── converge.yml │ │ │ └── molecule.yml │ └── test_driver.py ├── docker │ ├── __init__.py │ ├── conftest.py │ ├── scenarios │ │ ├── env-substitution │ │ │ └── molecule │ │ │ │ └── default │ │ │ │ ├── converge.yml │ │ │ │ └── molecule.yml │ │ └── with-context │ │ │ └── molecule │ │ │ └── default │ │ │ ├── Dockerfile.j2 │ │ │ ├── FOO │ │ │ ├── converge.yml │ │ │ └── molecule.yml │ ├── test_driver.py │ └── test_func.py ├── ec2 │ ├── __init__.py │ ├── functional │ │ ├── .ansible-lint │ │ ├── __init__.py │ │ ├── conftest.py │ │ └── test_ec2.py │ ├── scenarios │ │ └── driver │ │ │ └── ec2 │ │ │ └── molecule │ │ │ ├── default │ │ │ ├── converge.yml │ │ │ ├── molecule.yml │ │ │ ├── prepare.yml │ │ │ └── tests │ │ │ │ └── test_default.py │ │ │ └── multi-node │ │ │ ├── converge.yml │ │ │ ├── molecule.yml │ │ │ ├── prepare.yml │ │ │ └── tests │ │ │ ├── __init__.py │ │ │ └── test_default.py │ └── test_driver.py ├── gce │ ├── __init__.py │ ├── functional │ │ ├── .ansible-lint │ │ ├── __init__.py │ │ ├── conftest.py │ │ └── test_func.py │ ├── scenarios │ │ ├── linux │ │ │ ├── INSTALL.md │ │ │ ├── converge.yml │ │ │ ├── create.yml │ │ │ ├── destroy.yml │ │ │ ├── files │ │ │ │ └── windows_auth.py │ │ │ ├── handlers │ │ │ │ └── main.yml │ │ │ ├── molecule.yml │ │ │ ├── prepare.yml │ │ │ ├── requirements.yml │ │ │ ├── tasks │ │ │ │ ├── create_linux_instance.yml │ │ │ │ └── create_windows_instance.yml │ │ │ └── verify.yml │ │ └── windows │ │ │ ├── INSTALL.md │ │ │ ├── converge.yml │ │ │ ├── create.yml │ │ │ ├── destroy.yml │ │ │ ├── files │ │ │ └── windows_auth.py │ │ │ ├── handlers │ │ │ └── main.yml │ │ │ ├── molecule.yml │ │ │ ├── prepare.yml │ │ │ ├── requirements.yml │ │ │ ├── tasks │ │ │ ├── create_linux_instance.yml │ │ │ └── create_windows_instance.yml │ │ │ └── verify.yml │ └── test_driver.py ├── openstack │ ├── .ansible-lint │ ├── __init__.py │ ├── conftest.py │ ├── scenarios │ │ └── molecule │ │ │ ├── default │ │ │ ├── converge.yml │ │ │ └── molecule.yml │ │ │ ├── multiple │ │ │ ├── converge.yml │ │ │ └── molecule.yml │ │ │ ├── network │ │ │ ├── converge.yml │ │ │ └── molecule.yml │ │ │ ├── security_group │ │ │ ├── converge.yml │ │ │ └── molecule.yml │ │ │ └── volume │ │ │ ├── converge.yml │ │ │ └── molecule.yml │ ├── test_driver.py │ └── test_func.py ├── podman │ ├── __init__.py │ ├── conftest.py │ ├── test_driver.py │ └── test_func.py └── vagrant-plugin │ ├── __init__.py │ ├── functional │ ├── __init__.py │ ├── conftest.py │ └── test_func.py │ ├── scenarios │ └── molecule │ │ ├── box_url │ │ ├── converge.yml │ │ └── molecule.yml │ │ ├── config_options │ │ ├── converge.yml │ │ ├── molecule.yml │ │ └── verify.yml │ │ ├── default-compat │ │ ├── converge.yml │ │ ├── create.yml │ │ ├── destroy.yml │ │ └── molecule.yml │ │ ├── default │ │ ├── converge.yml │ │ └── molecule.yml │ │ ├── hostname │ │ ├── converge.yml │ │ ├── molecule.yml │ │ └── verify.yml │ │ ├── invalid │ │ ├── converge.yml │ │ └── molecule.yml │ │ ├── multi-node │ │ ├── converge.yml │ │ ├── molecule.yml │ │ └── verify.yml │ │ ├── network │ │ ├── converge.yml │ │ ├── molecule.yml │ │ └── verify.yml │ │ ├── provider_config_options │ │ ├── converge.yml │ │ ├── molecule.yml │ │ └── verify.yml │ │ └── vagrant_root │ │ ├── converge.yml │ │ ├── molecule.yml │ │ └── verify.yml │ └── test_driver.py ├── tools ├── Vagrantfile ├── create_testbox.sh ├── extract_plugin_names.py ├── generate-templates.sh └── test-setup.sh └── tox.ini /.ansible-lint: -------------------------------------------------------------------------------- 1 | # .ansible-lint 2 | exclude_paths: 3 | - .cache/ # implicit unless exclude_paths is defined in config 4 | - .github/ 5 | - requirements.yml 6 | - src/**/cookiecutter/{{* 7 | 8 | skip_list: 9 | # Temporary skips made during migration 10 | - args[module] 11 | 12 | mock_modules: 13 | - vagrant # only until we publish community.vagrant collection 14 | -------------------------------------------------------------------------------- /.ansible-lint-ignore: -------------------------------------------------------------------------------- 1 | test/gce/scenarios/linux/molecule.yml yaml[line-length] 2 | test/gce/scenarios/windows/molecule.yml yaml[line-length] 3 | test/roles/ec2plugin/molecule/default/create.yml no-handler 4 | 5 | test/roles/vagrantplugin/molecule/default/destroy.yml yaml[octal-values] 6 | test/roles/vagrantplugin/molecule/default/create.yml yaml[octal-values] 7 | test/roles/podmanplugin/molecule/default/destroy.yml yaml[octal-values] 8 | test/roles/podmanplugin/molecule/default/create.yml yaml[octal-values] 9 | test/roles/gceplugin/molecule/default/destroy.yml yaml[octal-values] 10 | test/roles/gceplugin/molecule/default/create.yml yaml[octal-values] 11 | test/roles/ec2plugin/molecule/default/destroy.yml yaml[octal-values] 12 | test/roles/ec2plugin/molecule/default/create.yml yaml[octal-values] 13 | test/roles/dockerplugin/molecule/default/destroy.yml yaml[octal-values] 14 | test/roles/dockerplugin/molecule/default/create.yml yaml[octal-values] 15 | test/roles/containersplugin/molecule/default/destroy.yml yaml[octal-values] 16 | test/roles/containersplugin/molecule/default/create.yml yaml[octal-values] 17 | test/roles/azureplugin/molecule/default/create.yml yaml[octal-values] 18 | test/roles/azureplugin/molecule/default/destroy.yml yaml[octal-values] 19 | 20 | test/roles/ec2plugin/molecule/default/destroy.yml risky-file-permissions 21 | 22 | test/roles/openstackplugin/molecule/default/create.yml yaml[octal-values] 23 | test/roles/openstackplugin/molecule/default/destroy.yml yaml[octal-values] 24 | -------------------------------------------------------------------------------- /.flake8: -------------------------------------------------------------------------------- 1 | [flake8] 2 | # do not add excludes for files in repo 3 | exclude = .venv/,.tox/,dist/,build/,.eggs/ 4 | format = pylint 5 | # E203: https://github.com/python/black/issues/315 6 | ignore = E741,W503,W504,H,E501,E203 7 | # 88 is official black default: 8 | max-line-length = 88 9 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # https://docs.github.com/en/code-security/supply-chain-security/keeping-your-dependencies-updated-automatically/enabling-and-disabling-dependabot-version-updates 2 | version: 2 3 | updates: 4 | - package-ecosystem: "github-actions" 5 | directory: "/" 6 | schedule: 7 | interval: weekly 8 | open-pull-requests-limit: 3 9 | labels: 10 | - "dependencies" 11 | - "skip-changelog" 12 | -------------------------------------------------------------------------------- /.github/release-drafter.yml: -------------------------------------------------------------------------------- 1 | # see https://github.com/ansible-community/devtools 2 | _extends: ansible-community/devtools 3 | -------------------------------------------------------------------------------- /.github/workflows/ack.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # See https://github.com/ansible/team-devtools/blob/main/.github/workflows/ack.yml 3 | name: ack 4 | 5 | concurrency: 6 | group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} 7 | cancel-in-progress: true 8 | 9 | on: 10 | pull_request_target: 11 | types: [opened, labeled, unlabeled, synchronize] 12 | 13 | jobs: 14 | ack: 15 | uses: ansible/team-devtools/.github/workflows/ack.yml@main 16 | -------------------------------------------------------------------------------- /.github/workflows/push.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # See https://github.com/ansible/team-devtools/blob/main/.github/workflows/push.yml 3 | name: push 4 | on: 5 | push: 6 | branches: 7 | - main 8 | - "releases/**" 9 | - "stable/**" 10 | 11 | jobs: 12 | ack: 13 | uses: ansible/team-devtools/.github/workflows/push.yml@main 14 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: release 3 | 4 | on: 5 | release: 6 | types: [published] 7 | 8 | jobs: 9 | pypi: 10 | name: Publish to PyPI registry 11 | environment: release 12 | runs-on: ubuntu-22.04 13 | permissions: 14 | id-token: write 15 | 16 | env: 17 | FORCE_COLOR: 1 18 | PY_COLORS: 1 19 | TOX_PARALLEL_NO_SPINNER: 1 20 | 21 | steps: 22 | - name: Switch to using Python 3.10 by default 23 | uses: actions/setup-python@v5 24 | with: 25 | python-version: "3.10" 26 | 27 | - name: Install tox 28 | run: >- 29 | python3 -m 30 | pip install 31 | --user 32 | tox 33 | 34 | - name: Check out src from Git 35 | uses: actions/checkout@v4 36 | with: 37 | fetch-depth: 0 # needed by setuptools-scm 38 | 39 | - name: Build dists 40 | run: python -m tox -e pkg 41 | 42 | - name: Publish to pypi.org 43 | if: >- # "create" workflows run separately from "push" & "pull_request" 44 | github.event_name == 'release' 45 | uses: pypa/gh-action-pypi-publish@release/v1 46 | with: 47 | password: ${{ secrets.pypi_password }} 48 | -------------------------------------------------------------------------------- /.github/workflows/tox.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: tox 3 | 4 | # https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#concurrency 5 | concurrency: 6 | group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} 7 | cancel-in-progress: true 8 | 9 | on: 10 | push: # only publishes pushes to the main branch to TestPyPI 11 | branches: # any integration branch but not tag 12 | - "main" 13 | pull_request: 14 | 15 | jobs: 16 | pre: 17 | name: pre 18 | runs-on: ubuntu-22.04 19 | outputs: 20 | matrix: ${{ steps.generate_matrix.outputs.matrix }} 21 | steps: 22 | - name: Determine matrix 23 | id: generate_matrix 24 | uses: coactions/dynamic-matrix@v1 25 | with: 26 | min_python: "3.10" 27 | max_python: "3.12" 28 | default_python: "3.10" # used by jobs in other_names 29 | other_names: | 30 | lint 31 | pkg 32 | devel 33 | build: 34 | name: ${{ matrix.name }} 35 | runs-on: ubuntu-22.04 36 | needs: pre 37 | env: 38 | PYTEST_REQPASS: 14 39 | strategy: 40 | fail-fast: false 41 | matrix: ${{ fromJson(needs.pre.outputs.matrix) }} 42 | 43 | steps: 44 | - name: Checkout repository 45 | uses: actions/checkout@v4 46 | with: 47 | fetch-depth: 0 # needed by setuptools-scm 48 | 49 | - name: Install Python 50 | uses: actions/setup-python@v5 51 | with: 52 | python-version: ${{ matrix.python_version }} 53 | 54 | - name: Install dependencies 55 | run: | 56 | python -m pip install -U pip 57 | pip install 'tox>=4.2.3' 58 | 59 | - name: Run tox run -e ${{ matrix.passed_name }} 60 | run: | 61 | ${{ matrix.PREFIX }} tox run -e ${{ matrix.passed_name }} 62 | 63 | check: # This job does nothing and is only used for the branch protection 64 | if: always() 65 | 66 | needs: 67 | - build 68 | runs-on: ubuntu-22.04 69 | steps: 70 | - name: Decide whether the needed jobs succeeded or failed 71 | uses: re-actors/alls-green@release/v1 72 | with: 73 | jobs: ${{ toJSON(needs) }} 74 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | .python-version 86 | 87 | # pipenv 88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 91 | # install all needed dependencies. 92 | #Pipfile.lock 93 | 94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 95 | __pypackages__/ 96 | 97 | # Celery stuff 98 | celerybeat-schedule 99 | celerybeat.pid 100 | 101 | # SageMath parsed files 102 | *.sage.py 103 | 104 | # Environments 105 | .env 106 | .venv 107 | env/ 108 | venv/ 109 | ENV/ 110 | env.bak/ 111 | venv.bak/ 112 | 113 | # Spyder project settings 114 | .spyderproject 115 | .spyproject 116 | 117 | # Rope project settings 118 | .ropeproject 119 | 120 | # mkdocs documentation 121 | /site 122 | 123 | # mypy 124 | .mypy_cache/ 125 | .dmypy.json 126 | dmypy.json 127 | 128 | # Pyre type checker 129 | .pyre/ 130 | src/molecule_plugins/_version.py 131 | 132 | # Ignore generated files 133 | test/roles 134 | 135 | # Quilt/Patch generated files 136 | .pc 137 | patches 138 | *.patch 139 | *.orig 140 | *.rej 141 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | ci: 3 | skip: 4 | # https://github.com/pre-commit-ci/issues/issues/55 5 | - ansible-lint 6 | exclude: > 7 | (?x)^( 8 | src/molecule_plugins/_version.py 9 | )$ 10 | default_language_version: 11 | python: python3 12 | minimum_pre_commit_version: 1.14.0 13 | repos: 14 | - repo: https://github.com/pre-commit/mirrors-prettier 15 | # keep it before yamllint 16 | rev: "v4.0.0-alpha.8" 17 | hooks: 18 | - id: prettier 19 | # Temporary excludes so we can gradually normalize the formatting 20 | exclude: > 21 | (?x)^( 22 | .*\.md| 23 | .*\{\{.* 24 | )$ 25 | - repo: https://github.com/PyCQA/doc8.git 26 | rev: v1.1.1 27 | hooks: 28 | - id: doc8 29 | - repo: https://github.com/psf/black 30 | rev: 24.1.1 31 | hooks: 32 | - id: black 33 | language_version: python3 34 | - repo: https://github.com/pre-commit/pre-commit-hooks.git 35 | rev: v4.5.0 36 | hooks: 37 | - id: end-of-file-fixer 38 | - id: trailing-whitespace 39 | - id: mixed-line-ending 40 | - id: check-byte-order-marker 41 | - id: check-executables-have-shebangs 42 | - id: check-merge-conflict 43 | - id: debug-statements 44 | - repo: https://github.com/astral-sh/ruff-pre-commit 45 | rev: "v0.2.0" 46 | hooks: 47 | - id: ruff 48 | args: [--fix, --exit-non-zero-on-fix] 49 | - repo: https://github.com/ansible/ansible-lint.git 50 | rev: v6.22.2 51 | hooks: 52 | - id: ansible-lint 53 | entry: env ANSIBLE_ROLES_PATH="$ANSIBLE_ROLES_PATH:test/roles" ansible-lint 54 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Community managed Ansible repositories 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include LICENSE 2 | include README.md 3 | 4 | recursive-exclude * __pycache__ 5 | recursive-exclude * *.py[co] 6 | 7 | recursive-include src/molecule_plugins/*/cookiecutter **/* 8 | recursive-include src/molecule_plugins/*/modules **/* 9 | recursive-include src/molecule_plugins/*/playbooks **/* 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # molecule-plugins 2 | 3 | This repository contains the following molecule plugins: 4 | 5 | - azure 6 | - containers 7 | - docker 8 | - ec2 9 | - gce 10 | - openstack 11 | - podman 12 | - vagrant 13 | 14 | Installing `molecule-plugins` does not install dependencies specific to each, 15 | plugin. To install these you need to install the extras for each plugin, like 16 | `pip3 install 'molecule-plugins[azure]'`. 17 | 18 | Before installing these plugins be sure that you uninstall their old standalone 19 | packages, like `pip3 uninstall molecule-azure`. If you fail to do so, you will 20 | end-up with a broken setup, as multiple plugins will have the same entry points, 21 | registered. 22 | 23 | ## Creating new releases 24 | 25 | The `release.yml` workflow generates the wheel and uploads the release to PyPI. 26 | Here are the steps you need to kick that process off: 27 | 28 | 1. Review the commit logs and decide on the next version. 29 | - Breaking changes should increment to a new major version. 30 | - New features should increment to a new minor version. 31 | - Bug fixes and small changes should increment to a new patch version. 32 | 33 | 2. Create a new tag and push it to the repo. 34 | 35 | ```bash 36 | git tag -s -m "Tag message" 37 | git push --tags upstream 38 | ``` 39 | 40 | > It is possible to create lightweight tags using `git tag ` but signed tags are preferred. 41 | 42 | 3. Publish the release with either the GitHub CLI or in a browser. 43 | See the [GitHub documentation about managing releases](https://docs.github.com/en/repositories/releasing-projects-on-github/managing-releases-in-a-repository). 44 | 4. Check the [release workflow](https://github.com/ansible-community/molecule-plugins/actions/workflows/release.yml) runs successfully. 45 | 5. Verify the new version is available from the [molecule-plugins](https://pypi.org/project/molecule-plugins/) page on PyPI. 46 | -------------------------------------------------------------------------------- /bindep.txt: -------------------------------------------------------------------------------- 1 | # This is a cross-platform list tracking distribution packages needed by tests; 2 | # see https://docs.openstack.org/infra/bindep/ for additional information. 3 | 4 | build-essential [platform:dpkg] 5 | dnsmasq-base [platform:dpkg] 6 | ebtables [platform:dpkg] 7 | gcc [test platform:rpm] 8 | gcc-c++ [test platform:rpm] 9 | libselinux-python [platform:centos-7] 10 | libvirt [platform:rpm] 11 | libvirt-clients [platform:dpkg] 12 | libvirt-daemon [platform:dpkg] 13 | libvirt-daemon-kvm [platform:rpm] 14 | libvirt-daemon-system [platform:dpkg] 15 | libvirt-dev [platform:dpkg] 16 | libvirt-devel [platform:rpm] 17 | libguestfs-tools [platform:dpkg platform:rpm] 18 | libxml2-dev [platform:dpkg] 19 | libxslt-dev [platform:dpkg] 20 | make [platform:centos-7 platform:centos-8] 21 | pkg-config [platform:dpkg] 22 | python3 [test platform:dpkg platform:rpm !platform:centos-7] 23 | python3-devel [test platform:rpm !platform:centos-7] 24 | python3-dev [test platform:dpkg] 25 | python3-libselinux [test platform:rpm !platform:centos-7] 26 | python3-libvirt [test platform:rpm !platform:centos-7 platform:dpkg] 27 | python3-netifaces [test !platform:centos-7 platform:rpm] 28 | python36 [test !platform:dpkg !platform:centos-7 !platform:fedora-28] 29 | qemu-system [platform:dpkg] 30 | qemu-utils [platform:dpkg] 31 | qemu-kvm [platform:rpm] 32 | ruby-dev [platform:dpkg] 33 | ruby-devel [!platform:centos-7 !platform:centos-8 platform:rpm] 34 | ruby-libvirt [platform:dpkg] 35 | vagrant [platform:dpkg] 36 | zlib1g-dev [platform:dpkg] 37 | -------------------------------------------------------------------------------- /conftest.py: -------------------------------------------------------------------------------- 1 | import contextlib 2 | import os 3 | import random 4 | import shutil 5 | import string 6 | from pathlib import Path 7 | 8 | import pytest 9 | 10 | from molecule import config, logger 11 | from molecule.app import get_app 12 | from molecule.scenario import ephemeral_directory 13 | 14 | LOG = logger.get_logger(__name__) 15 | 16 | 17 | @pytest.helpers.register 18 | def run_command(cmd, env=os.environ, log=True): 19 | if cmd.__class__.__name__ == "Command": 20 | if log: 21 | cmd = _rebake_command(cmd, env) 22 | cmd = cmd.bake(_truncate_exc=False) 23 | return get_app(Path()).run_command(cmd, env=env) 24 | 25 | 26 | def _rebake_command(cmd, env, out=LOG.info, err=LOG.error): 27 | return cmd.bake(_env=env, _out=out, _err=err) 28 | 29 | 30 | @pytest.fixture() 31 | def random_string(length=5): 32 | return "".join(random.choice(string.ascii_uppercase) for _ in range(length)) 33 | 34 | 35 | @contextlib.contextmanager 36 | def change_dir_to(dir_name): 37 | cwd = os.getcwd() 38 | os.chdir(dir_name) 39 | yield 40 | os.chdir(cwd) 41 | 42 | 43 | @pytest.fixture() 44 | def temp_dir(tmpdir, random_string, request): 45 | directory = tmpdir.mkdir(random_string) 46 | 47 | with change_dir_to(directory.strpath): 48 | yield directory 49 | 50 | 51 | @pytest.fixture() 52 | def resources_folder_path(): 53 | resources_folder_path = os.path.join(os.path.dirname(__file__), "resources") 54 | return resources_folder_path 55 | 56 | 57 | @pytest.helpers.register 58 | def molecule_project_directory(): 59 | return os.getcwd() 60 | 61 | 62 | @pytest.helpers.register 63 | def molecule_directory(): 64 | return config.molecule_directory(molecule_project_directory()) 65 | 66 | 67 | @pytest.helpers.register 68 | def molecule_scenario_directory(): 69 | return os.path.join(molecule_directory(), "default") 70 | 71 | 72 | @pytest.helpers.register 73 | def molecule_file(): 74 | return get_molecule_file(molecule_scenario_directory()) 75 | 76 | 77 | @pytest.helpers.register 78 | def get_molecule_file(path): 79 | return config.molecule_file(path) 80 | 81 | 82 | @pytest.helpers.register 83 | def molecule_ephemeral_directory(_fixture_uuid): 84 | project_directory = f"test-project-{_fixture_uuid}" 85 | scenario_name = "test-instance" 86 | 87 | return ephemeral_directory( 88 | os.path.join("molecule_test", project_directory, scenario_name), 89 | ) 90 | 91 | 92 | def metadata_lint_update(role_directory: str) -> None: 93 | # By default, ansible-lint will fail on newly-created roles because the 94 | # fields in this file have not been changed from their defaults. This is 95 | # good because molecule should create this file using the defaults, and 96 | # users should receive feedback to change these defaults. However, this 97 | # blocks the testing of 'molecule init' itself, so ansible-lint should 98 | # be configured to ignore these metadata lint errors. 99 | dirname = os.path.dirname(os.path.abspath(__file__)) 100 | ansible_lint_src = os.path.join(dirname, ".ansible-lint") 101 | shutil.copy(ansible_lint_src, role_directory) 102 | 103 | # Explicitly lint here to catch any unexpected lint errors before 104 | # continuining functional testing. Ansible lint is run at the root 105 | # of the role directory and pointed at the role directory to ensure 106 | # the customize ansible-lint config is used. 107 | with change_dir_to(role_directory): 108 | cmd = ["ansible-lint", "."] 109 | result = run_command(cmd) 110 | assert result.returncode == 0 111 | -------------------------------------------------------------------------------- /doc/ec2/README.rst: -------------------------------------------------------------------------------- 1 | ******************* 2 | Molecule EC2 Plugin 3 | ******************* 4 | 5 | .. image:: https://badge.fury.io/py/molecule-ec2.svg 6 | :target: https://badge.fury.io/py/molecule-ec2 7 | :alt: PyPI Package 8 | 9 | .. image:: https://zuul-ci.org/gated.svg 10 | :target: https://dashboard.zuul.ansible.com/t/ansible/builds?project=ansible-community/molecule-ec2 11 | 12 | .. image:: https://img.shields.io/badge/code%20style-black-000000.svg 13 | :target: https://github.com/python/black 14 | :alt: Python Black Code Style 15 | 16 | .. image:: https://img.shields.io/badge/Code%20of%20Conduct-silver.svg 17 | :target: https://docs.ansible.com/ansible/latest/community/code_of_conduct.html 18 | :alt: Ansible Code of Conduct 19 | 20 | .. image:: https://img.shields.io/badge/Mailing%20lists-silver.svg 21 | :target: https://docs.ansible.com/ansible/latest/community/communication.html#mailing-list-information 22 | :alt: Ansible mailing lists 23 | 24 | .. image:: https://img.shields.io/badge/license-MIT-brightgreen.svg 25 | :target: LICENSE 26 | :alt: Repository License 27 | 28 | Molecule EC2 is designed to allow use of AWS EC2 for provisioning of test 29 | resources. 30 | 31 | .. _quickstart: 32 | 33 | Quickstart 34 | ========== 35 | 36 | Installation 37 | ------------ 38 | .. code-block:: bash 39 | 40 | pip install molecule-ec2 41 | 42 | Create a scenario 43 | ----------------- 44 | 45 | With a new role 46 | ^^^^^^^^^^^^^^^ 47 | .. code-block:: bash 48 | 49 | molecule init role -d ec2 my-role 50 | 51 | This will create a new folder *my-role* containing a bare-bone generated 52 | role like you would do with ``ansible-galaxy init`` command. 53 | It will also contain a molecule folder with a default scenario 54 | using the ec2 driver (using ansible amazon.aws.ec2_instance collection). 55 | Install the collection using 56 | `ansible-galaxy install -r test_requirements.yml`. 57 | 58 | In a pre-existing role 59 | ^^^^^^^^^^^^^^^^^^^^^^ 60 | .. code-block:: bash 61 | 62 | molecule init scenario -d ec2 63 | 64 | This will create a default scenario with the ec2 driver in a molecule folder, 65 | located in the current working directory. 66 | 67 | Example 68 | ------- 69 | This is a molecule.yml example file 70 | 71 | .. code-block:: yaml 72 | 73 | dependency: 74 | name: galaxy 75 | driver: 76 | name: ec2 77 | platforms: 78 | - name: instance 79 | image_owner: "099720109477" 80 | image_name: ubuntu/images/hvm-ssd/ubuntu-bionic-18.04-amd64-server-* 81 | instance_type: t2.micro 82 | vpc_subnet_id: 83 | tags: 84 | Name: molecule_instance 85 | provisioner: 86 | name: ansible 87 | verifier: 88 | name: ansible 89 | 90 | All you need to do is fill in the subnet-id you want 91 | to create your test instance into. 92 | Then run 93 | 94 | .. code-block:: bash 95 | 96 | molecule test 97 | 98 | .. note:: 99 | To make this work, you need to export your AWS credentials, as well as the AWS region you want to use, in your environment. 100 | 101 | .. code-block:: bash 102 | 103 | export AWS_ACCESS_KEY_ID=ACCESS_API_KEY 104 | export AWS_SECRET_KEY=SECRET_API_KEY 105 | export AWS_REGION=us-east-1 106 | 107 | You can read more about managing AWS credentials with Ansible modules 108 | in the official documentation of the `Ansible AWS modules `_ 109 | 110 | Documentation 111 | ============= 112 | 113 | Details on the parameters for the platforms section are detailed in 114 | ``__. 115 | 116 | Read the molecule documentation and more at https://molecule.readthedocs.io/. 117 | 118 | .. _get-involved: 119 | 120 | Get Involved 121 | ============ 122 | 123 | * Join us in the ``#ansible-molecule`` channel on `Freenode`_. 124 | * Join the discussion in `molecule-users Forum`_. 125 | * Join the community working group by checking the `wiki`_. 126 | * Want to know about releases, subscribe to `ansible-announce list`_. 127 | * For the full list of Ansible email Lists, IRC channels see the 128 | `communication page`_. 129 | 130 | .. _`Freenode`: https://freenode.net 131 | .. _`molecule-users Forum`: https://groups.google.com/forum/#!forum/molecule-users 132 | .. _`wiki`: https://github.com/ansible/community/wiki/Molecule 133 | .. _`ansible-announce list`: https://groups.google.com/group/ansible-announce 134 | .. _`communication page`: https://docs.ansible.com/ansible/latest/community/communication.html 135 | 136 | .. _authors: 137 | 138 | Authors 139 | ======= 140 | 141 | Molecule EC2 Plugin was created by Sorin Sbarnea based on code from 142 | Molecule. 143 | 144 | .. _license: 145 | 146 | License 147 | ======= 148 | 149 | The `MIT`_ License. 150 | 151 | .. _`MIT`: https://github.com/ansible/molecule/blob/master/LICENSE 152 | 153 | The logo is licensed under the `Creative Commons NoDerivatives 4.0 License`_. 154 | 155 | If you have some other use in mind, contact us. 156 | 157 | .. _`Creative Commons NoDerivatives 4.0 License`: https://creativecommons.org/licenses/by-nd/4.0/ 158 | -------------------------------------------------------------------------------- /doc/openstack/README.rst: -------------------------------------------------------------------------------- 1 | ************************* 2 | Molecule Openstack Plugin 3 | ************************* 4 | 5 | Molecule Openstack is designed to allow use of Openstack 6 | for provisioning of test resources. 7 | 8 | .. _quickstart: 9 | 10 | Quickstart 11 | ========== 12 | 13 | Installation 14 | ------------ 15 | 16 | .. code-block:: bash 17 | 18 | pip install molecule-plugins 19 | 20 | Create a scenario 21 | ----------------- 22 | 23 | In a pre-existing role 24 | ^^^^^^^^^^^^^^^^^^^^^^ 25 | .. code-block:: bash 26 | 27 | molecule init scenario -d openstack 28 | 29 | This will create a default scenario with the openstack driver 30 | in a molecule folder, located in the current working directory. 31 | 32 | Example 33 | ------- 34 | This is a molecule.yml example file 35 | 36 | .. code-block:: yaml 37 | 38 | dependency: 39 | name: galaxy 40 | driver: 41 | name: openstack 42 | platforms: 43 | - name: ubuntu2004 44 | flavor: m1.small 45 | image: Ubuntu_22.04 46 | user: ubuntu 47 | provisioner: 48 | name: ansible 49 | 50 | Then run 51 | 52 | .. code-block:: bash 53 | 54 | molecule test 55 | 56 | .. note:: 57 | You need to configure `openstack authentication ` using config file or environment variables. 58 | 59 | Documentation 60 | ============= 61 | 62 | Details on the parameters for the platforms section are detailed in 63 | ``__. 64 | 65 | .. _license: 66 | 67 | License 68 | ======= 69 | 70 | The `MIT`_ License. 71 | 72 | .. _`MIT`: https://github.com/ansible/molecule/blob/master/LICENSE 73 | 74 | The logo is licensed under the `Creative Commons NoDerivatives 4.0 License`_. 75 | 76 | If you have some other use in mind, contact us. 77 | 78 | .. _`Creative Commons NoDerivatives 4.0 License`: https://creativecommons.org/licenses/by-nd/4.0/ 79 | -------------------------------------------------------------------------------- /molecule/test-podman/converge.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Converge 3 | hosts: all 4 | tasks: 5 | - name: Test 6 | ansible.builtin.debug: 7 | msg: "It worked!" 8 | -------------------------------------------------------------------------------- /molecule/test-podman/molecule.yml: -------------------------------------------------------------------------------- 1 | --- 2 | dependency: 3 | name: galaxy 4 | driver: 5 | name: podman 6 | platforms: 7 | - name: instance 8 | image: quay.io/centos/centos:stream9 9 | pre_build_image: true 10 | published_ports: 11 | - 127.0.0.1:2080:80 12 | - 127.0.0.1:2443:443 13 | provisioner: 14 | name: ansible 15 | verifier: 16 | name: ansible 17 | -------------------------------------------------------------------------------- /molecule/test-podman/verify.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # This is an example playbook to execute Ansible tests. 3 | 4 | - name: Verify 5 | hosts: all 6 | tasks: 7 | - name: Example assertion 8 | ansible.builtin.assert: 9 | that: true 10 | -------------------------------------------------------------------------------- /mypy.ini: -------------------------------------------------------------------------------- 1 | [mypy] 2 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = [ 3 | "setuptools >= 64.0.0", # required by pyproject+setuptools_scm integration 4 | "setuptools_scm[toml] >= 7.0.5", # required for "no-local-version" scheme 5 | 6 | ] 7 | build-backend = "setuptools.build_meta" 8 | 9 | [project] 10 | # https://peps.python.org/pep-0621/#readme 11 | requires-python = ">=3.10" 12 | dynamic = ["version"] 13 | name = "molecule-plugins" 14 | description = "Molecule Plugins" 15 | readme = "README.md" 16 | authors = [{ "name" = "Sorin Sbarnea", "email" = "sorin.sbarnea@gmail.com" }] 17 | maintainers = [{ "name" = "Sorin Sbarnea", "email" = "sorin.sbarnea@gmail.com" }] 18 | license = { text = "MIT" } 19 | classifiers = [ 20 | "Development Status :: 5 - Production/Stable", 21 | "Environment :: Console", 22 | "Intended Audience :: Developers", 23 | "Intended Audience :: Information Technology", 24 | "Intended Audience :: System Administrators", 25 | "License :: OSI Approved :: MIT License", 26 | "Operating System :: MacOS", 27 | "Operating System :: POSIX", 28 | "Programming Language :: Python :: 3", 29 | "Programming Language :: Python :: 3.10", 30 | "Programming Language :: Python :: 3.11", 31 | "Programming Language :: Python :: 3.12", 32 | "Programming Language :: Python :: 3 :: Only", 33 | "Programming Language :: Python", 34 | "Topic :: System :: Systems Administration", 35 | "Topic :: Software Development :: Quality Assurance", 36 | "Topic :: Software Development :: Testing", 37 | "Topic :: Utilities", 38 | ] 39 | keywords = ["ansible", "testing", "molecule", "plugin"] 40 | dependencies = [ 41 | # molecule plugins are not allowed to mention Ansible as a direct dependency 42 | "molecule >= 25.1.0", 43 | ] 44 | 45 | [project.urls] 46 | homepage = "https://github.com/ansible-community/molecule-plugins" 47 | documentation = "https://molecule.readthedocs.io/" 48 | repository = "https://github.com/ansible-community/molecule-plugins" 49 | changelog = "https://github.com/ansible-community/molecule-plugins/releases" 50 | 51 | [project.optional-dependencies] 52 | test = [ 53 | "pytest-helpers-namespace >= 2019.1.8", 54 | "molecule[test] >= 25.1.0" 55 | ] 56 | azure = [] 57 | docker = [ 58 | # selinux python module is needed as least by ansible-docker modules 59 | # and allows use of isolated (default) virtualenvs. It does not avoid need 60 | # to install the system selinux libraries but it will provide a clear 61 | # message when user has to do that. 62 | 'selinux; sys_platform=="linux2"', 63 | 'selinux; sys_platform=="linux"', 64 | "docker >= 4.3.1", 65 | "requests" # also required by docker 66 | ] 67 | ec2 = [] 68 | gce = [ 69 | "requests >= 2.31.0", 70 | "google-auth >= 2.28.2", 71 | ] 72 | podman = [ 73 | ] 74 | selinux = [ 75 | # selinux python module is needed as least by podman and docker on systems 76 | # that do have selinux enabled and where code is running inside of an 77 | # isolated (default) virtualenv. It does not avoid need to install the 78 | # system selinux libraries but it will provide a clear message when user 79 | # has to do that. 80 | 'selinux; sys_platform=="linux2"', 81 | 'selinux; sys_platform=="linux"', 82 | ] 83 | vagrant = [ 84 | "python-vagrant", 85 | ] 86 | openstack = [ 87 | "openstacksdk >= 1.1.0" 88 | ] 89 | 90 | [tool.ruff] 91 | lint.ignore = [ 92 | "E501", # we use black 93 | # we deliberately ignore these: 94 | "EM102", 95 | # temporary disabled until we either fix them or decide to ignore them: 96 | "A001", 97 | "ANN", 98 | "ARG", 99 | "B006", 100 | "B028", 101 | "BLE", 102 | "C901", 103 | "COM812", 104 | "D", 105 | "DTZ", 106 | "FBT", 107 | "INP", 108 | "ISC", 109 | "N", 110 | "PERF203", 111 | "PGH", 112 | "PLR", 113 | "PT", 114 | "PTH", 115 | "RET", 116 | "S", 117 | # not sure we'll open a bug for every TODO. 118 | "TD003", 119 | "FIX002", 120 | "TRY", 121 | ] 122 | lint.select = ["ALL"] 123 | target-version = "py39" 124 | # Same as Black. 125 | line-length = 88 126 | 127 | [tool.ruff.lint.flake8-pytest-style] 128 | parametrize-values-type = "tuple" 129 | 130 | [tool.ruff.lint.isort] 131 | known-first-party = ["molecule_plugins"] 132 | 133 | [project.entry-points."molecule.driver"] 134 | azure = "molecule_plugins.azure.driver:Azure" 135 | containers = "molecule_plugins.containers.driver:Container" 136 | docker = "molecule_plugins.docker.driver:Docker" 137 | ec2 = "molecule_plugins.ec2.driver:EC2" 138 | gce = "molecule_plugins.gce.driver:GCE" 139 | podman = "molecule_plugins.podman.driver:Podman" 140 | vagrant = "molecule_plugins.vagrant.driver:Vagrant" 141 | openstack = "molecule_plugins.openstack.driver:Openstack" 142 | 143 | [tool.setuptools_scm] 144 | local_scheme = "no-local-version" 145 | write_to = "src/molecule_plugins/_version.py" 146 | -------------------------------------------------------------------------------- /pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | addopts = -v -rxXs --doctest-modules --durations 10 3 | doctest_optionflags = ALLOW_UNICODE ELLIPSIS 4 | junit_suite_name = molecule_test_suite 5 | norecursedirs = dist doc build .tox .eggs src test/*/scenarios test/*/resources 6 | -------------------------------------------------------------------------------- /requirements.yml: -------------------------------------------------------------------------------- 1 | collections: 2 | - name: ansible.posix # docker 3 | version: ">=1.4.0" 4 | - name: google.cloud 5 | - name: amazon.aws 6 | - name: community.docker 7 | version: ">=3.10.2" 8 | - name: containers.podman 9 | - name: azure.azcollection 10 | version: ">=1,<2" 11 | - name: community.crypto 12 | version: ">=1.8.0" 13 | - name: community.vagrant 14 | - name: openstack.cloud 15 | -------------------------------------------------------------------------------- /src/molecule_plugins/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ansible-community/molecule-plugins/980db38a12043e1685dd0e91e9845f1ae684cab1/src/molecule_plugins/__init__.py -------------------------------------------------------------------------------- /src/molecule_plugins/azure/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ansible-community/molecule-plugins/980db38a12043e1685dd0e91e9845f1ae684cab1/src/molecule_plugins/azure/__init__.py -------------------------------------------------------------------------------- /src/molecule_plugins/azure/cookiecutter/cookiecutter.json: -------------------------------------------------------------------------------- 1 | { 2 | "molecule_directory": "molecule", 3 | "role_name": "OVERRIDDEN", 4 | "scenario_name": "OVERRIDDEN" 5 | } 6 | -------------------------------------------------------------------------------- /src/molecule_plugins/azure/cookiecutter/{{cookiecutter.molecule_directory}}/{{cookiecutter.scenario_name}}/INSTALL.rst: -------------------------------------------------------------------------------- 1 | ******************************* 2 | Azure driver installation guide 3 | ******************************* 4 | 5 | Requirements 6 | ============ 7 | 8 | * An Azure credentials rc file 9 | 10 | Install 11 | ======= 12 | 13 | Please refer to the `Virtual environment`_ documentation for installation best 14 | practices. If not using a virtual environment, please consider passing the 15 | widely recommended `'--user' flag`_ when invoking ``pip``. 16 | 17 | .. _Virtual environment: https://virtualenv.pypa.io/en/latest/ 18 | .. _'--user' flag: https://packaging.python.org/tutorials/installing-packages/#installing-to-the-user-site 19 | 20 | .. code-block:: bash 21 | 22 | $ pip install 'molecule[azure]' 23 | -------------------------------------------------------------------------------- /src/molecule_plugins/azure/cookiecutter/{{cookiecutter.molecule_directory}}/{{cookiecutter.scenario_name}}/converge.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Converge 3 | hosts: all 4 | tasks: 5 | - name: Include tested role 6 | ansible.builtin.include_role: 7 | name: "{{ cookiecutter.role_name }}" 8 | -------------------------------------------------------------------------------- /src/molecule_plugins/azure/cookiecutter/{{cookiecutter.molecule_directory}}/{{cookiecutter.scenario_name}}/create.yml: -------------------------------------------------------------------------------- 1 | --- 2 | {% raw -%} 3 | - name: Create 4 | hosts: localhost 5 | connection: local 6 | gather_facts: false 7 | no_log: "{{ molecule_no_log }}" 8 | vars: 9 | resource_group_name: molecule 10 | location: "{{ lookup('env', 'AZURE_REGION') or 'westus' }}" 11 | ssh_user: molecule 12 | ssh_port: 22 13 | virtual_network_name: molecule_vnet 14 | subnet_name: molecule_subnet 15 | keypair_path: "{{ lookup('env', 'MOLECULE_EPHEMERAL_DIRECTORY') }}/ssh_key" 16 | tasks: 17 | - name: Create resource group 18 | azure.azcollection.azure_rm_resourcegroup: 19 | name: "{{ resource_group_name }}" 20 | location: "{{ location }}" 21 | 22 | - name: Create virtual network 23 | azure.azcollection.azure_rm_virtualnetwork: 24 | resource_group: "{{ resource_group_name }}" 25 | name: "{{ virtual_network_name }}" 26 | address_prefixes: "10.10.0.0/16" 27 | 28 | - name: Create subnet 29 | azure.azcollection.azure_rm_subnet: 30 | resource_group: "{{ resource_group_name }}" 31 | name: "{{ subnet_name }}" 32 | address_prefix_cidr: 10.10.1.0/24 33 | virtual_network_name: "{{ virtual_network_name }}" 34 | 35 | - name: Create key pair 36 | ansible.builtin.user: 37 | name: "{{ lookup('env', 'USER') }}" 38 | generate_ssh_key: true 39 | ssh_key_file: "{{ keypair_path }}" 40 | register: key_pair 41 | 42 | - name: Create molecule instance(s) 43 | azure.azcollection.azure_rm_virtualmachine: 44 | resource_group: "{{ resource_group_name }}" 45 | name: "{{ item.name }}" 46 | vm_size: Standard_B1ms 47 | admin_username: "{{ ssh_user }}" 48 | public_ip_allocation_method: Dynamic 49 | ssh_password_enabled: false 50 | ssh_public_keys: 51 | - path: "/home/{{ ssh_user }}/.ssh/authorized_keys" 52 | key_data: "{{ key_pair.ssh_public_key }}" 53 | image: 54 | offer: CentOS 55 | publisher: OpenLogic 56 | sku: '8_5-gen2' 57 | version: latest 58 | register: server 59 | with_items: "{{ molecule_yml.platforms }}" 60 | async: 7200 61 | poll: 0 62 | 63 | - name: Wait for instance(s) creation to complete 64 | ansible.builtin.async_status: 65 | jid: "{{ item.ansible_job_id }}" 66 | register: azure_jobs 67 | until: azure_jobs.finished 68 | retries: 300 69 | with_items: "{{ server.results }}" 70 | notify: 71 | - Populate instance config dict 72 | - Convert instance config dict to a list 73 | - Dump instance config 74 | - Wait for SSH 75 | 76 | handlers: 77 | - name: Populate instance config dict 78 | ansible.builtin.set_fact: 79 | instance_conf_dict: # noqa: jinja[spacing] 80 | { 81 | "instance": "{{ item.ansible_facts.azure_vm.name }}", 82 | "address": "{{ item.ansible_facts.azure_vm 83 | .properties.networkProfile.networkInterfaces[0] 84 | .properties.ipConfigurations[0] 85 | .properties.publicIPAddress 86 | .properties.ipAddress }}", 87 | "user": "{{ ssh_user }}", 88 | "port": "{{ ssh_port }}", 89 | "identity_file": "{{ keypair_path }}", 90 | } 91 | with_items: "{{ azure_jobs.results }}" 92 | register: instance_config_dict 93 | 94 | - name: Convert instance config dict to a list 95 | ansible.builtin.set_fact: 96 | instance_conf: "{{ instance_config_dict.results | map(attribute='ansible_facts.instance_conf_dict') | list }}" 97 | 98 | - name: Dump instance config 99 | ansible.builtin.copy: 100 | content: "{{ instance_conf | to_json | from_json | to_yaml }}" 101 | dest: "{{ molecule_instance_config }}" 102 | mode: "0664" 103 | 104 | - name: Wait for SSH 105 | ansible.builtin.wait_for: 106 | port: "{{ ssh_port }}" 107 | host: "{{ item.address }}" 108 | search_regex: SSH 109 | delay: 10 110 | with_items: "{{ instance_conf }}" 111 | {%- endraw %} 112 | -------------------------------------------------------------------------------- /src/molecule_plugins/azure/cookiecutter/{{cookiecutter.molecule_directory}}/{{cookiecutter.scenario_name}}/destroy.yml: -------------------------------------------------------------------------------- 1 | --- 2 | {% raw -%} 3 | - name: Destroy 4 | hosts: localhost 5 | connection: local 6 | gather_facts: false 7 | no_log: "{{ molecule_no_log }}" 8 | vars: 9 | resource_group_name: molecule 10 | virtual_network_name: molecule_vnet 11 | subnet_name: molecule_subnet 12 | tasks: 13 | - name: Destroy resource group and all associated resources 14 | azure.azcollection.azure_rm_resourcegroup: 15 | name: "{{ resource_group_name }}" 16 | state: absent 17 | force_delete_nonempty: true 18 | register: rg 19 | notify: 20 | - Dump instance config 21 | 22 | - name: Populate instance config 23 | ansible.builtin.set_fact: 24 | instance_conf: {} 25 | 26 | handlers: 27 | - name: Dump instance config 28 | ansible.builtin.copy: 29 | content: "{{ instance_conf | to_json | from_json | to_yaml }}" 30 | dest: "{{ molecule_instance_config }}" 31 | mode: "0644" 32 | {%- endraw %} 33 | -------------------------------------------------------------------------------- /src/molecule_plugins/azure/driver.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015-2018 Cisco Systems, Inc. 2 | # 3 | # Permission is hereby granted, free of charge, to any person obtaining a copy 4 | # of this software and associated documentation files (the "Software"), to 5 | # deal in the Software without restriction, including without limitation the 6 | # rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 7 | # sell copies of the Software, and to permit persons to whom the Software is 8 | # furnished to do so, subject to the following conditions: 9 | # 10 | # The above copyright notice and this permission notice shall be included in 11 | # all copies or substantial portions of the Software. 12 | # 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 18 | # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 19 | # DEALINGS IN THE SOFTWARE. 20 | import os 21 | 22 | from molecule import logger, util 23 | from molecule.api import Driver 24 | 25 | LOG = logger.get_logger(__name__) 26 | 27 | 28 | class Azure(Driver): 29 | """ 30 | The class responsible for managing `Azure`_ instances. `Azure`_ 31 | is ``not`` the default driver used in Molecule. 32 | 33 | Molecule leverages Ansible's `azure_module`_, by mapping variables 34 | from ``molecule.yml`` into ``create.yml`` and ``destroy.yml``. 35 | 36 | .. _`azure_module`: https://docs.ansible.com/ansible/latest/guide_azure.html 37 | 38 | .. code-block:: yaml 39 | 40 | driver: 41 | name: azure 42 | platforms: 43 | - name: instance 44 | 45 | .. code-block:: bash 46 | 47 | $ pip install 'molecule-azure' 48 | 49 | Change the options passed to the ssh client. 50 | 51 | .. code-block:: yaml 52 | 53 | driver: 54 | name: azure 55 | ssh_connection_options: 56 | - '-o ControlPath=~/.ansible/cp/%r@%h-%p' 57 | 58 | .. important:: 59 | 60 | Molecule does not merge lists, when overriding the developer must 61 | provide all options. 62 | 63 | Provide a list of files Molecule will preserve, relative to the scenario 64 | ephemeral directory, after any ``destroy`` subcommand execution. 65 | 66 | .. code-block:: yaml 67 | 68 | driver: 69 | name: azure 70 | safe_files: 71 | - foo 72 | 73 | .. _`Azure`: https://azure.microsoft.com 74 | """ 75 | 76 | def __init__(self, config=None) -> None: 77 | super().__init__(config) 78 | self._name = "azure" 79 | 80 | @property 81 | def name(self): 82 | return self._name 83 | 84 | @name.setter 85 | def name(self, value): 86 | self._name = value 87 | 88 | @property 89 | def login_cmd_template(self): 90 | connection_options = " ".join(self.ssh_connection_options) 91 | 92 | return ( 93 | "ssh {address} " 94 | "-l {user} " 95 | "-p {port} " 96 | "-i {identity_file} " 97 | f"{connection_options}" 98 | ) 99 | 100 | @property 101 | def default_safe_files(self): 102 | return [self.instance_config] 103 | 104 | @property 105 | def default_ssh_connection_options(self): 106 | return self._get_ssh_connection_options() 107 | 108 | def login_options(self, instance_name): 109 | d = {"instance": instance_name} 110 | 111 | return util.merge_dicts(d, self._get_instance_config(instance_name)) 112 | 113 | def ansible_connection_options(self, instance_name): 114 | try: 115 | d = self._get_instance_config(instance_name) 116 | 117 | return { 118 | "ansible_user": d["user"], 119 | "ansible_host": d["address"], 120 | "ansible_port": d["port"], 121 | "ansible_private_key_file": d["identity_file"], 122 | "connection": "ssh", 123 | "ansible_ssh_common_args": " ".join(self.ssh_connection_options), 124 | } 125 | except StopIteration: 126 | return {} 127 | except OSError: 128 | # Instance has yet to be provisioned , therefore the 129 | # instance_config is not on disk. 130 | return {} 131 | 132 | def _get_instance_config(self, instance_name): 133 | instance_config_dict = util.safe_load_file(self._config.driver.instance_config) 134 | 135 | return next( 136 | item for item in instance_config_dict if item["instance"] == instance_name 137 | ) 138 | 139 | def sanity_checks(self): 140 | # TODO(decentral1se): Implement sanity checks 141 | pass 142 | 143 | def template_dir(self): 144 | """Return path to its own cookiecutterm templates. It is used by init 145 | command in order to figure out where to load the templates from. 146 | """ 147 | return os.path.join(os.path.dirname(__file__), "cookiecutter") 148 | -------------------------------------------------------------------------------- /src/molecule_plugins/containers/__init__.py: -------------------------------------------------------------------------------- 1 | """Molecule Containers Drivers.""" 2 | -------------------------------------------------------------------------------- /src/molecule_plugins/containers/cookiecutter/cookiecutter.json: -------------------------------------------------------------------------------- 1 | { 2 | "molecule_directory": "molecule", 3 | "role_name": "OVERRIDDEN", 4 | "scenario_name": "OVERRIDDEN" 5 | } 6 | -------------------------------------------------------------------------------- /src/molecule_plugins/containers/cookiecutter/{{cookiecutter.molecule_directory}}/{{cookiecutter.scenario_name}}/converge.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Converge 3 | hosts: all 4 | # Disabled gather as it seems broken on podman 5 | # https://github.com/ansible-community/molecule-podman/issues/2 6 | gather_facts: false 7 | tasks: 8 | - name: Test implementation 9 | ansible.builtin.debug: 10 | msg: Add test implementation here 11 | -------------------------------------------------------------------------------- /src/molecule_plugins/containers/driver.py: -------------------------------------------------------------------------------- 1 | """Containers Driver Module.""" 2 | 3 | import inspect 4 | import os 5 | import shutil 6 | 7 | from molecule import logger 8 | 9 | _logger = logger.get_logger(__name__) 10 | 11 | # Preference logic for picking backend driver to be used. 12 | drivers = os.environ.get("MOLECULE_CONTAINERS_BACKEND", "podman,docker").split(",") 13 | for driver in drivers: 14 | if shutil.which(driver): 15 | break 16 | else: 17 | driver = drivers[0] 18 | 19 | # Logic for picking backend is subject to change. 20 | if driver == "docker": 21 | from molecule_plugins.docker.driver import Docker as DriverBackend 22 | elif driver == "podman": 23 | from molecule_plugins.podman.driver import Podman as DriverBackend 24 | else: 25 | raise NotImplementedError(f"Driver {driver} is not supported.") 26 | _logger.debug("Containers driver will use %s backend", driver) 27 | 28 | 29 | class Container(DriverBackend): 30 | """ 31 | Container Driver Class. 32 | 33 | This class aims to provide an agnostic container enginer implementation, 34 | which should allow users to consume whichever enginer they have available. 35 | """ 36 | 37 | def __init__(self, config=None) -> None: 38 | """Construct Container.""" 39 | super().__init__(config) 40 | self._name = "containers" 41 | # Assure that _path points to the driver we would be using, or 42 | # molecule will fail to find the embedded playbooks. 43 | self._path = os.path.abspath(os.path.dirname(inspect.getfile(DriverBackend))) 44 | 45 | @property 46 | def required_collections(self) -> dict[str, str]: 47 | """Return collections dict containing names and versions required.""" 48 | return { 49 | "ansible.posix": "1.3.0", 50 | "community.docker": "1.9.1", 51 | "containers.podman": "1.8.1", 52 | } 53 | -------------------------------------------------------------------------------- /src/molecule_plugins/docker/__init__.py: -------------------------------------------------------------------------------- 1 | """Molecule Docker Driver.""" 2 | -------------------------------------------------------------------------------- /src/molecule_plugins/docker/cookiecutter/cookiecutter.json: -------------------------------------------------------------------------------- 1 | { 2 | "molecule_directory": "molecule", 3 | "role_name": "OVERRIDDEN", 4 | "scenario_name": "OVERRIDDEN" 5 | } 6 | -------------------------------------------------------------------------------- /src/molecule_plugins/docker/cookiecutter/{{cookiecutter.molecule_directory}}/{{cookiecutter.scenario_name}}/converge.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Converge 3 | hosts: all 4 | tasks: 5 | - name: "Include {{ cookiecutter.role_name }}" 6 | ansible.builtin.include_role: 7 | name: "{{ cookiecutter.role_name }}" 8 | -------------------------------------------------------------------------------- /src/molecule_plugins/docker/playbooks/Dockerfile.j2: -------------------------------------------------------------------------------- 1 | # Molecule managed 2 | 3 | {% if item.registry is defined %} 4 | FROM {{ item.registry.url }}/{{ item.image }} 5 | {% else %} 6 | FROM {{ item.image }} 7 | {% endif %} 8 | 9 | {% if item.env is defined %} 10 | {% for var, value in item.env.items() %} 11 | {% if value %} 12 | ENV {{ var }} {{ value }} 13 | {% endif %} 14 | {% endfor %} 15 | {% endif %} 16 | 17 | RUN if [ $(command -v apt-get) ]; then export DEBIAN_FRONTEND=noninteractive && apt-get update && apt-get install -y python3 sudo bash ca-certificates iproute2 python3-apt aptitude rsync && apt-get clean && rm -rf /var/lib/apt/lists/*; \ 18 | elif [ $(command -v dnf) ]; then dnf makecache && dnf --assumeyes install /usr/bin/python3 /usr/bin/python3-config /usr/bin/dnf-3 sudo bash iproute rsync && dnf clean all; \ 19 | elif [ $(command -v yum) ]; then yum makecache fast && yum install -y /usr/bin/python /usr/bin/python2-config sudo yum-plugin-ovl bash iproute rsync && sed -i 's/plugins=0/plugins=1/g' /etc/yum.conf && yum clean all; \ 20 | elif [ $(command -v zypper) ]; then zypper refresh && zypper install -y python3 sudo bash iproute2 rsync && zypper clean -a; \ 21 | elif [ $(command -v apk) ]; then apk update && apk add --no-cache python3 sudo bash ca-certificates rsync; \ 22 | elif [ $(command -v xbps-install) ]; then xbps-install -Syu && xbps-install -y python3 sudo bash ca-certificates iproute2 rsync && xbps-remove -O; fi 23 | -------------------------------------------------------------------------------- /src/molecule_plugins/docker/playbooks/destroy.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Destroy 3 | hosts: localhost 4 | connection: local 5 | gather_facts: false 6 | no_log: "{{ molecule_no_log }}" 7 | tags: 8 | - always 9 | tasks: 10 | - name: Set async_dir for HOME env 11 | ansible.builtin.set_fact: 12 | ansible_async_dir: "{{ lookup('env', 'HOME') }}/.ansible_async/" 13 | when: (lookup('env', 'HOME')) 14 | 15 | - name: Destroy molecule instance(s) 16 | community.docker.docker_container: 17 | name: "{{ item.name }}" 18 | docker_host: "{{ item.docker_host | default(lookup('env', 'DOCKER_HOST') or 'unix://var/run/docker.sock') }}" 19 | cacert_path: "{{ item.cacert_path | default((lookup('env', 'DOCKER_CERT_PATH') + '/ca.pem') if lookup('env', 'DOCKER_CERT_PATH') else omit) }}" 20 | cert_path: "{{ item.cert_path | default((lookup('env', 'DOCKER_CERT_PATH') + '/cert.pem') if lookup('env', 'DOCKER_CERT_PATH') else omit) }}" 21 | key_path: "{{ item.key_path | default((lookup('env', 'DOCKER_CERT_PATH') + '/key.pem') if lookup('env', 'DOCKER_CERT_PATH') else omit) }}" 22 | tls_verify: "{{ item.tls_verify | default(lookup('env', 'DOCKER_TLS_VERIFY')) or false }}" 23 | state: absent 24 | force_kill: "{{ item.force_kill | default(true) }}" 25 | keep_volumes: "{{ item.keep_volumes | default(true) }}" 26 | container_default_behavior: >- 27 | {{ item.container_default_behavior 28 | | default('compatibility' if ansible_version.full is version_compare('2.10', '>=') else omit) }} 29 | register: server 30 | loop: "{{ molecule_yml.platforms }}" 31 | loop_control: 32 | label: "{{ item.name }}" 33 | no_log: false 34 | async: 7200 35 | poll: 0 36 | 37 | - name: Wait for instance(s) deletion to complete 38 | ansible.builtin.async_status: 39 | jid: "{{ item.ansible_job_id }}" 40 | register: docker_jobs 41 | until: docker_jobs.finished 42 | retries: 300 43 | loop: "{{ server.results }}" 44 | loop_control: 45 | label: "{{ item.item.name }}" 46 | 47 | - name: Delete docker networks(s) 48 | ansible.builtin.include_tasks: tasks/delete_network.yml 49 | loop: "{{ molecule_yml.platforms | molecule_get_docker_networks() }}" 50 | loop_control: 51 | label: "{{ item.name }}" 52 | no_log: false 53 | -------------------------------------------------------------------------------- /src/molecule_plugins/docker/playbooks/filter_plugins/get_docker_networks.py: -------------------------------------------------------------------------------- 1 | """Embedded ansible filter used by Molecule Docker driver create playbook.""" 2 | 3 | 4 | def get_docker_networks(data, labels={}): 5 | """Get list of docker networks.""" 6 | network_list = [] 7 | network_names = [] 8 | for platform in data: 9 | if "docker_networks" in platform: 10 | for docker_network in platform["docker_networks"]: 11 | if "labels" not in docker_network: 12 | docker_network["labels"] = {} 13 | for key in labels: 14 | docker_network["labels"][key] = labels[key] 15 | 16 | if "name" in docker_network: 17 | network_list.append(docker_network) 18 | network_names.append(docker_network["name"]) 19 | 20 | # If a network name is defined for a platform but is not defined in 21 | # docker_networks, add it to the network list. 22 | if "networks" in platform: 23 | for network in platform["networks"]: 24 | if "name" in network: 25 | name = network["name"] 26 | if name not in network_names: 27 | network_list.append({"name": name, "labels": labels}) 28 | return network_list 29 | 30 | 31 | class FilterModule: 32 | """Core Molecule filter plugins.""" 33 | 34 | def filters(self): 35 | return { 36 | "molecule_get_docker_networks": get_docker_networks, 37 | } 38 | -------------------------------------------------------------------------------- /src/molecule_plugins/docker/playbooks/tasks/create_network.yml: -------------------------------------------------------------------------------- 1 | - name: Check if network exist 2 | community.docker.docker_network_info: 3 | name: "{{ item.name }}" 4 | register: docker_netname 5 | 6 | - name: Create docker network(s) 7 | community.docker.docker_network: 8 | api_version: "{{ item.api_version | default(omit) }}" 9 | appends: "{{ item.appends | default(omit) }}" 10 | attachable: "{{ item.attachable | default(omit) }}" 11 | ca_cert: "{{ item.ca_cert | default(omit) }}" 12 | client_cert: "{{ item.client_cert | default(omit) }}" 13 | client_key: "{{ item.client_key | default(omit) }}" 14 | connected: "{{ item.connected | default(omit) }}" 15 | debug: "{{ item.debug | default(omit) }}" 16 | docker_host: "{{ item.docker_host | default(omit) }}" 17 | driver: "{{ item.driver | default(omit) }}" 18 | driver_options: "{{ item.driver_options | default(omit) }}" 19 | enable_ipv6: "{{ item.enable_ipv6 | default(omit) }}" 20 | force: "{{ item.force | default(omit) }}" 21 | internal: "{{ item.internal | default(omit) }}" 22 | ipam_config: "{{ item.ipam_config | default(omit) }}" 23 | ipam_driver: "{{ item.ipam_driver | default(omit) }}" 24 | ipam_driver_options: "{{ item.ipam_driver_options | default(omit) }}" 25 | key_path: "{{ item.key_path | default(omit) }}" 26 | labels: "{{ item.labels }}" 27 | name: "{{ item.name }}" 28 | scope: "{{ item.scope | default(omit) }}" 29 | ssl_version: "{{ item.ssl_version | default(omit) }}" 30 | state: "present" 31 | timeout: "{{ item.timeout | default(omit) }}" 32 | tls: "{{ item.tls | default(omit) }}" 33 | tls_hostname: "{{ item.tls_hostname | default(omit) }}" 34 | validate_certs: "{{ item.validate_certs | default(omit) }}" 35 | when: not docker_netname.exists 36 | -------------------------------------------------------------------------------- /src/molecule_plugins/docker/playbooks/tasks/delete_network.yml: -------------------------------------------------------------------------------- 1 | - name: Retrieve network info 2 | community.docker.docker_network_info: 3 | name: "{{ item.name }}" 4 | register: docker_netname 5 | 6 | - name: Delete docker network(s) 7 | community.docker.docker_network: 8 | name: "{{ item.name }}" 9 | state: "absent" 10 | when: 11 | - docker_netname.exists 12 | - docker_netname.network.Labels.owner is defined 13 | - docker_netname.network.Labels.owner == 'molecule' 14 | -------------------------------------------------------------------------------- /src/molecule_plugins/docker/playbooks/validate-dockerfile.yml: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ansible-playbook 2 | --- 3 | - name: Validate dockerfile 4 | hosts: localhost 5 | connection: local 6 | gather_facts: false 7 | vars: 8 | platforms: 9 | # platforms supported as being managed by molecule/ansible, this does 10 | # not mean molecule itself can run on them. 11 | - image: alpine:edge 12 | - image: quay.io/centos/centos:stream9 13 | - image: ubuntu:latest 14 | - image: debian:latest 15 | tags: 16 | - always 17 | tasks: 18 | - name: Assure we have docker module installed 19 | ansible.builtin.pip: 20 | name: docker 21 | 22 | - name: Create temporary dockerfiles 23 | ansible.builtin.tempfile: 24 | prefix: "molecule-dockerfile-{{ item.image | replace('/', '-') }}" 25 | suffix: build 26 | register: temp_dockerfiles 27 | with_items: "{{ platforms }}" 28 | loop_control: 29 | label: "{{ item.image }}" 30 | 31 | - name: Expand Dockerfile templates 32 | ansible.builtin.template: 33 | src: Dockerfile.j2 34 | dest: "{{ temp_dockerfiles.results[index].path }}" 35 | mode: "0600" 36 | register: result 37 | with_items: "{{ platforms }}" 38 | loop_control: 39 | index_var: index 40 | label: "{{ item.image }}" 41 | 42 | - name: Test Dockerfile template 43 | community.docker.docker_image: 44 | name: "{{ item.item.image }}" 45 | build: 46 | path: "." 47 | dockerfile: "{{ item.dest }}" 48 | pull: true 49 | nocache: true 50 | source: build 51 | state: present 52 | debug: true 53 | force_source: true 54 | with_items: "{{ result.results }}" 55 | loop_control: 56 | label: "{{ item.item.image }}" 57 | register: result 58 | 59 | - name: Clean up temporary Dockerfile's 60 | ansible.builtin.file: 61 | path: "{{ item }}" 62 | state: absent 63 | mode: "0600" 64 | loop: "{{ temp_dockerfiles.results | map(attribute='path') | list }}" 65 | 66 | - name: Display results 67 | ansible.builtin.debug: 68 | var: result 69 | -------------------------------------------------------------------------------- /src/molecule_plugins/ec2/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ansible-community/molecule-plugins/980db38a12043e1685dd0e91e9845f1ae684cab1/src/molecule_plugins/ec2/__init__.py -------------------------------------------------------------------------------- /src/molecule_plugins/ec2/cookiecutter/cookiecutter.json: -------------------------------------------------------------------------------- 1 | { 2 | "molecule_directory": "molecule", 3 | "role_name": "OVERRIDDEN", 4 | "scenario_name": "OVERRIDDEN" 5 | } 6 | -------------------------------------------------------------------------------- /src/molecule_plugins/ec2/cookiecutter/{{cookiecutter.molecule_directory}}/{{cookiecutter.scenario_name}}/INSTALL.rst: -------------------------------------------------------------------------------- 1 | ********************************************* 2 | Amazon Web Services driver installation guide 3 | ********************************************* 4 | 5 | Requirements 6 | ============ 7 | 8 | * An AWS credentials rc file 9 | 10 | Install 11 | ======= 12 | 13 | Please refer to the `Virtual environment`_ documentation for installation best 14 | practices. If not using a virtual environment, please consider passing the 15 | widely recommended `'--user' flag`_ when invoking ``pip``. 16 | 17 | .. _Virtual environment: https://virtualenv.pypa.io/en/latest/ 18 | .. _'--user' flag: https://packaging.python.org/tutorials/installing-packages/#installing-to-the-user-site 19 | 20 | .. code-block:: bash 21 | 22 | $ pip install 'molecule-ec2' 23 | -------------------------------------------------------------------------------- /src/molecule_plugins/ec2/cookiecutter/{{cookiecutter.molecule_directory}}/{{cookiecutter.scenario_name}}/converge.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Converge 3 | hosts: all 4 | tasks: 5 | - name: "Include {{ cookiecutter.role_name }}" 6 | ansible.builtin.include_role: 7 | name: "{{ cookiecutter.role_name }}" 8 | -------------------------------------------------------------------------------- /src/molecule_plugins/ec2/cookiecutter/{{cookiecutter.molecule_directory}}/{{cookiecutter.scenario_name}}/prepare.yml: -------------------------------------------------------------------------------- 1 | --- 2 | {% raw -%} 3 | - name: Prepare 4 | hosts: all 5 | gather_facts: false 6 | tasks: 7 | - name: Make sure python3 is installed 8 | ansible.builtin.package: 9 | name: python3 10 | state: present 11 | become: true 12 | {%- endraw %} 13 | -------------------------------------------------------------------------------- /src/molecule_plugins/gce/__init__.py: -------------------------------------------------------------------------------- 1 | """Plugin exports.""" 2 | 3 | __name__ = __name__.split("_")[-1] 4 | -------------------------------------------------------------------------------- /src/molecule_plugins/gce/cookiecutter/cookiecutter.json: -------------------------------------------------------------------------------- 1 | { 2 | "molecule_directory": "molecule", 3 | "dependency_name": "OVERRIDDEN", 4 | "driver_name": "OVERRIDDEN", 5 | "provisioner_name": "OVERRIDDEN", 6 | "scenario_name": "OVERRIDDEN", 7 | "role_name": "OVERRIDDEN", 8 | "verifier_name": "OVERRIDDEN" 9 | } 10 | -------------------------------------------------------------------------------- /src/molecule_plugins/gce/cookiecutter/{{cookiecutter.molecule_directory}}/{{cookiecutter.scenario_name}}/converge.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Converge 3 | hosts: all 4 | tasks: 5 | - name: "Include {{ cookiecutter.role_name }}" 6 | ansible.builtin.include_role: 7 | name: "{{ cookiecutter.role_name }}" 8 | -------------------------------------------------------------------------------- /src/molecule_plugins/gce/playbooks/create.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Create 3 | hosts: localhost 4 | connection: local 5 | gather_facts: false 6 | no_log: "{{ molecule_no_log }}" 7 | vars: 8 | ssh_identity_file: "{{ lookup('env', 'MOLECULE_EPHEMERAL_DIRECTORY') }}/ssh_key" 9 | gcp_project_id: "{{ molecule_yml.driver.project_id | default(lookup('env', 'GCE_PROJECT_ID')) }}" 10 | gcp_net_name: "{{ molecule_yml.driver.network_name | default('default') }}" 11 | gcp_project: "{{ molecule_yml.driver.vpc_host_project | default(gcp_project_id) }}" 12 | gcp_net: "https://www.googleapis.com/compute/v1/projects/{{ gcp_project }}/global/networks/{{ gcp_net_name }}" 13 | gcp_snet_name: "{{ molecule_yml.driver.subnetwork_name | default('default') }}" 14 | gcp_snet: "https://compute.googleapis.com/compute/v1/projects/{{ gcp_project }}/regions/{{ molecule_yml.driver.region }}/subnetworks/{{ gcp_snet_name }}" 15 | tasks: 16 | - name: Make sure if linux or windows either specified 17 | ansible.builtin.assert: 18 | that: 19 | - molecule_yml.driver.instance_os_type | lower == "linux" or 20 | molecule_yml.driver.instance_os_type | lower == "windows" 21 | fail_msg: "instance_os_type is possible only to specify linux or windows either" 22 | 23 | - name: Include create_linux_instance tasks 24 | ansible.builtin.include_tasks: tasks/create_linux_instance.yml 25 | when: 26 | - molecule_yml.driver.instance_os_type | lower == "linux" 27 | 28 | - name: Include create_windows_instance tasks 29 | ansible.builtin.include_tasks: tasks/create_windows_instance.yml 30 | when: 31 | - molecule_yml.driver.instance_os_type | lower == "windows" 32 | 33 | handlers: 34 | - name: Import main handler tasks 35 | ansible.builtin.import_tasks: handlers/main.yml 36 | -------------------------------------------------------------------------------- /src/molecule_plugins/gce/playbooks/destroy.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Destroy 3 | hosts: localhost 4 | connection: local 5 | gather_facts: false 6 | no_log: "{{ molecule_no_log }}" 7 | 8 | tasks: 9 | - name: Destroy molecule instance(s) 10 | google.cloud.gcp_compute_instance: 11 | name: "{{ item.name }}" 12 | state: absent 13 | zone: "{{ item.zone | default(molecule_yml.driver.region + '-b') }}" 14 | project: "{{ molecule_yml.driver.project_id | default(lookup('env', 'GCE_PROJECT_ID')) }}" 15 | scopes: "{{ molecule_yml.driver.scopes | default(['https://www.googleapis.com/auth/compute'], True) }}" 16 | service_account_email: "{{ molecule_yml.driver.service_account_email | default(omit, true) }}" 17 | service_account_file: "{{ molecule_yml.driver.service_account_file | default(omit, true) }}" 18 | auth_kind: "{{ molecule_yml.driver.auth_kind | default(omit, true) }}" 19 | register: async_results 20 | loop: "{{ molecule_yml.platforms }}" 21 | async: 7200 22 | poll: 0 23 | notify: 24 | - "Wipe out instance config" 25 | - "Dump instance config" 26 | 27 | - name: Wait for instance(s) deletion to complete 28 | ansible.builtin.async_status: 29 | jid: "{{ item.ansible_job_id }}" 30 | register: server 31 | until: server.finished 32 | retries: 300 33 | delay: 10 34 | loop: "{{ async_results.results }}" 35 | 36 | handlers: 37 | - name: Import main handler tasks 38 | ansible.builtin.import_tasks: handlers/main.yml 39 | -------------------------------------------------------------------------------- /src/molecule_plugins/gce/playbooks/handlers/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Populate instance config dict Linux 3 | ansible.builtin.set_fact: 4 | instance_conf_dict: 5 | { 6 | "instance": "{{ instance_info.name }}", 7 | "address": "{{ instance_info.networkInterfaces.0.accessConfigs.0.natIP 8 | if molecule_yml.driver.external_access 9 | else instance_info.networkInterfaces.0.networkIP }}", 10 | "user": "{{ lookup('env', 'USER') }}", 11 | "port": "22", 12 | "identity_file": "{{ ssh_identity_file }}", 13 | "instance_os_type": "{{ molecule_yml.driver.instance_os_type }}", 14 | } 15 | loop: "{{ server.results }}" 16 | loop_control: 17 | loop_var: instance_info 18 | no_log: true 19 | register: instance_conf_dict 20 | 21 | - name: Populate instance config dict Windows 22 | ansible.builtin.set_fact: 23 | instance_conf_dict: 24 | { 25 | "instance": "{{ instance_info.name }}", 26 | "address": "{{ instance_info.networkInterfaces.0.accessConfigs.0.natIP 27 | if molecule_yml.driver.external_access 28 | else instance_info.networkInterfaces.0.networkIP }}", 29 | "user": "molecule_usr", 30 | "password": "{{ instance_info.password }}", 31 | "port": "{{ instance_info.winrm_port | default(5986) }}", 32 | "winrm_transport": "{{ molecule_yml.driver.winrm_transport | default('ntlm') }}", 33 | "winrm_server_cert_validation": "{{ molecule_yml.driver.winrm_server_cert_validation | default('ignore') }}", 34 | "instance_os_type": "{{ molecule_yml.driver.instance_os_type }}", 35 | } 36 | loop: "{{ win_instances }}" 37 | loop_control: 38 | loop_var: instance_info 39 | no_log: true 40 | register: instance_conf_dict 41 | 42 | - name: Wipe out instance config 43 | ansible.builtin.set_fact: 44 | instance_conf: {} 45 | 46 | - name: Convert instance config dict to a list 47 | ansible.builtin.set_fact: 48 | instance_conf: "{{ instance_conf_dict.results | map(attribute='ansible_facts.instance_conf_dict') | list }}" 49 | 50 | - name: Dump instance config 51 | ansible.builtin.copy: 52 | content: "{{ instance_conf }}" 53 | dest: "{{ molecule_instance_config }}" 54 | mode: "0600" 55 | -------------------------------------------------------------------------------- /src/molecule_plugins/gce/playbooks/tasks/create_linux_instance.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Create ssh keypair 3 | community.crypto.openssh_keypair: 4 | comment: "{{ lookup('env', 'USER') }} user for Molecule" 5 | path: "{{ ssh_identity_file }}" 6 | register: keypair 7 | 8 | - name: "Create molecule Linux instance(s)" 9 | google.cloud.gcp_compute_instance: 10 | state: present 11 | name: "{{ item.name }}" 12 | machine_type: "{{ item.machine_type | default('n1-standard-1') }}" 13 | metadata: 14 | ssh-keys: "{{ lookup('env', 'USER') }}:{{ keypair.public_key }}" 15 | scheduling: 16 | preemptible: "{{ item.preemptible | default(false) }}" 17 | disks: 18 | - auto_delete: true 19 | boot: true 20 | initialize_params: 21 | disk_size_gb: "{{ item.disk_size_gb | default(omit) }}" 22 | source_image: "{{ item.image | default('projects/debian-cloud/global/images/family/debian-10') }}" 23 | source_image_encryption_key: 24 | raw_key: "{{ item.image_encryption_key | default(omit) }}" 25 | network_interfaces: 26 | - network: 27 | selfLink: "{{ gcp_net }}" 28 | subnetwork: 29 | selfLink: "{{ gcp_snet }}" 30 | access_configs: "{{ [{'name': 'instance_ip', 'type': 'ONE_TO_ONE_NAT'}] if molecule_yml.driver.external_access else [] }}" 31 | tags: "{{ item.tags | default(omit) }}" 32 | zone: "{{ item.zone | default(molecule_yml.driver.region + '-b') }}" 33 | project: "{{ gcp_project_id }}" 34 | scopes: "{{ molecule_yml.driver.scopes | default(['https://www.googleapis.com/auth/compute'], True) }}" 35 | service_account_email: "{{ molecule_yml.driver.service_account_email | default(omit, true) }}" 36 | service_account_file: "{{ molecule_yml.driver.service_account_file | default(omit, true) }}" 37 | auth_kind: "{{ molecule_yml.driver.auth_kind | default(omit, true) }}" 38 | register: async_results 39 | loop: "{{ molecule_yml.platforms }}" 40 | loop_control: 41 | pause: 3 42 | async: 7200 43 | poll: 0 44 | 45 | - name: Wait for instance(s) creation to complete 46 | ansible.builtin.async_status: 47 | jid: "{{ item.ansible_job_id }}" 48 | loop: "{{ async_results.results }}" 49 | register: server 50 | until: server.finished 51 | retries: 300 52 | delay: 10 53 | notify: 54 | - "Populate instance config dict Linux" 55 | - "Convert instance config dict to a list" 56 | - "Dump instance config" 57 | 58 | - name: Wait for SSH 59 | ansible.builtin.wait_for: 60 | port: 22 61 | host: "{{ item.networkInterfaces.0.accessConfigs.0.natIP if molecule_yml.driver.external_access else item.networkInterfaces.0.networkIP }}" 62 | search_regex: SSH 63 | delay: 10 64 | loop: "{{ server.results }}" 65 | register: waitfor 66 | until: waitfor.failed == false 67 | retries: 6 68 | delay: 10 69 | -------------------------------------------------------------------------------- /src/molecule_plugins/gce/playbooks/tasks/create_windows_instance.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Create molecule Windows instance(s) 3 | google.cloud.gcp_compute_instance: 4 | state: present 5 | name: "{{ item.name }}" 6 | machine_type: "{{ item.machine_type | default('n1-standard-1') }}" 7 | scheduling: 8 | preemptible: "{{ item.preemptible | default(false) }}" 9 | disks: 10 | - auto_delete: true 11 | boot: true 12 | initialize_params: 13 | disk_size_gb: "{{ item.disk_size_gb | default(omit) }}" 14 | source_image: "{{ item.image | default('projects/windows-cloud/global/images/family/windows-2019') }}" 15 | source_image_encryption_key: 16 | raw_key: "{{ item.image_encryption_key | default(omit) }}" 17 | network_interfaces: 18 | - network: 19 | selfLink: "{{ gcp_net }}" 20 | subnetwork: 21 | selfLink: "{{ gcp_snet }}" 22 | access_configs: "{{ [{'name': 'instance_ip', 'type': 'ONE_TO_ONE_NAT'}] if molecule_yml.driver.external_access else [] }}" 23 | tags: "{{ item.tags | default(omit) }}" 24 | zone: "{{ item.zone | default(molecule_yml.driver.region + '-b') }}" 25 | project: "{{ gcp_project_id }}" 26 | scopes: "{{ molecule_yml.driver.scopes | default(['https://www.googleapis.com/auth/compute'], True) }}" 27 | service_account_email: "{{ molecule_yml.driver.service_account_email | default(omit, true) }}" 28 | service_account_file: "{{ molecule_yml.driver.service_account_file | default(omit, true) }}" 29 | auth_kind: "{{ molecule_yml.driver.auth_kind | default(omit, true) }}" 30 | register: async_results 31 | loop: "{{ molecule_yml.platforms }}" 32 | async: 7200 33 | poll: 0 34 | 35 | - name: Wait for instance(s) creation to complete 36 | ansible.builtin.async_status: 37 | jid: "{{ item.ansible_job_id }}" 38 | loop: "{{ async_results.results }}" 39 | register: server 40 | until: server.finished 41 | retries: 300 42 | delay: 10 43 | notify: 44 | - "Populate instance config dict Windows" 45 | - "Convert instance config dict to a list" 46 | - "Dump instance config" 47 | 48 | - name: Wait for WinRM 49 | ansible.builtin.wait_for: 50 | port: 5986 51 | host: "{{ item.networkInterfaces.0.accessConfigs.0.natIP if molecule_yml.driver.external_access else item.networkInterfaces.0.networkIP }}" 52 | delay: 10 53 | loop: "{{ server.results }}" 54 | register: waitfor 55 | until: waitfor.failed == false 56 | retries: 6 57 | delay: 10 58 | 59 | - name: Prepare Windows User 60 | ansible.builtin.script: > 61 | ./files/windows_auth.py 62 | --instance {{ item.name }} 63 | --zone {{ item.zone | default(molecule_yml.driver.region + '-b') }} 64 | --project {{ gcp_project_id }} 65 | --username molecule_usr 66 | args: 67 | executable: python3 68 | environment: 69 | GOOGLE_APPLICATION_CREDENTIALS: "{{ molecule_yml.driver.service_account_file | default(lookup('env', 'GCP_SERVICE_ACCOUNT_FILE'), true) }}" 70 | loop: "{{ molecule_yml.platforms }}" 71 | changed_when: 72 | - password.rc == 0 73 | - password.stdout 74 | register: password 75 | retries: 10 76 | delay: 10 77 | 78 | - name: Add password for instances in server list 79 | ansible.builtin.set_fact: 80 | win_instances: "{{ win_instances | default([]) + [dict(item[0], password=item[1].stdout_lines | last)] }}" 81 | loop: "{{ server.results | zip(password.results) | list }}" 82 | no_log: true 83 | -------------------------------------------------------------------------------- /src/molecule_plugins/openstack/__init__.py: -------------------------------------------------------------------------------- 1 | """Molecule Openstack Driver.""" 2 | -------------------------------------------------------------------------------- /src/molecule_plugins/openstack/cookiecutter/cookiecutter.json: -------------------------------------------------------------------------------- 1 | { 2 | "molecule_directory": "molecule", 3 | "role_name": "OVERRIDDEN", 4 | "scenario_name": "OVERRIDDEN" 5 | } 6 | -------------------------------------------------------------------------------- /src/molecule_plugins/openstack/cookiecutter/{{cookiecutter.molecule_directory}}/{{cookiecutter.scenario_name}}/converge.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Converge 3 | hosts: all 4 | tasks: 5 | - name: "Include {{ cookiecutter.role_name }}" 6 | ansible.builtin.include_role: 7 | name: "{{ cookiecutter.role_name }}" 8 | -------------------------------------------------------------------------------- /src/molecule_plugins/openstack/playbooks/destroy.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Destroy 3 | hosts: localhost 4 | connection: local 5 | gather_facts: false 6 | no_log: "{{ molecule_no_log }}" 7 | tags: 8 | - always 9 | tasks: 10 | - name: Include molecule Tasks 11 | ansible.builtin.include_tasks: 12 | file: tasks/vars.yml 13 | 14 | - name: Delete SSH Keys 15 | openstack.cloud.keypair: 16 | name: "{{ key_name }}" 17 | state: absent 18 | 19 | - name: Delete local SSH Key 20 | ansible.builtin.file: 21 | name: "{{ identity_file }}" 22 | state: absent 23 | 24 | - name: Destroy openstack instance 25 | openstack.cloud.server: 26 | state: absent 27 | name: "molecule-test-{{ item.name }}-{{ uuid }}" 28 | delete_fip: true 29 | loop: "{{ molecule_yml.platforms }}" 30 | 31 | - name: Delete security groups 32 | openstack.cloud.security_group: 33 | state: absent 34 | name: "molecule-test-{{ item.security_group.name }}-{{ uuid }}" 35 | when: 36 | - item.security_group is defined 37 | - item.security_group.create | default(true) 38 | - item.security_group.name is defined 39 | loop: "{{ molecule_yml.platforms }}" 40 | 41 | - name: Destroy Router 42 | openstack.cloud.router: 43 | name: "molecule-test-{{ item.network.router.name }}-{{ uuid }}" 44 | state: absent 45 | when: 46 | - item.network is defined 47 | - item.network.name is defined 48 | - item.network.create | default(true) 49 | - item.network.router is defined 50 | - item.network.router.name is defined 51 | - item.network.subnet is defined 52 | - item.network.subnet.name is defined 53 | loop: "{{ molecule_yml.platforms }}" 54 | 55 | - name: Delete network 56 | openstack.cloud.network: 57 | name: "molecule-test-{{ item.network.name }}-{{ uuid }}" 58 | state: absent 59 | when: 60 | - item.network is defined 61 | - item.network.create | default(true) 62 | - item.network.name is defined 63 | loop: "{{ molecule_yml.platforms }}" 64 | -------------------------------------------------------------------------------- /src/molecule_plugins/openstack/playbooks/prepare.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Prepare 3 | hosts: all 4 | gather_facts: false 5 | tasks: 6 | - name: Gather system info 7 | ansible.builtin.raw: uname 8 | register: raw_uname 9 | changed_when: false 10 | failed_when: false 11 | 12 | - name: Bootstrap python for Ansible 13 | ansible.builtin.raw: | 14 | command -v python3 python || ( 15 | command -v apk >/dev/null && sudo apk add --no-progress --update python3 || 16 | (test -e /usr/bin/dnf && sudo dnf install -y python3) || 17 | (test -e /usr/bin/apt && (apt -y update && apt install -y python3-minimal)) || 18 | (test -e /usr/bin/yum && sudo yum -y -qq install python3) || 19 | (test -e /usr/sbin/pkg && sudo env ASSUME_ALWAYS_YES=yes pkg update && sudo env ASSUME_ALWAYS_YES=yes pkg install python3) || 20 | (test -e /usr/sbin/pkg_add && sudo /usr/sbin/pkg_add -U -I -x python%3.9) || 21 | (test -e /usr/bin/pacman && sudo /usr/bin/pacman -Sy python3 --noconfirm --quiet) || 22 | echo "Warning: Python not bootstrapped due to unknown platform." 23 | ) 24 | become: true 25 | changed_when: false 26 | when: raw_uname.rc == 0 and raw_uname.stdout | trim == "Linux" 27 | 28 | - name: Set molecule instances 29 | ansible.builtin.set_fact: 30 | instances: "{{ lookup('file', molecule_instance_config) }}" 31 | when: raw_uname.rc == 0 and raw_uname.stdout | trim == "Linux" 32 | 33 | - name: Save molecule instances 34 | ansible.builtin.copy: 35 | content: "{{ instances | map(attribute='instance') | list }}" 36 | mode: "0644" 37 | dest: /tmp/instances 38 | when: raw_uname.rc == 0 and raw_uname.stdout | trim == "Linux" 39 | 40 | - name: Setup /etc/hosts 41 | ansible.builtin.lineinfile: 42 | line: "{{ item.address }} {{ item.instance }}" 43 | path: /etc/hosts 44 | become: true 45 | loop: "{{ instances }}" 46 | when: raw_uname.rc == 0 and raw_uname.stdout | trim == "Linux" 47 | -------------------------------------------------------------------------------- /src/molecule_plugins/openstack/playbooks/tasks/server_addr.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Extract address 3 | ansible.builtin.set_fact: 4 | address: >- 5 | {%- if item.access_ipv4 -%} 6 | {{ item.access_ipv4 }} 7 | {%- elif item.access_ipv6 -%} 8 | {{ item.access_ipv6 }} 9 | {%- else -%} 10 | {%- for int in item.addresses[item.metadata.get('network', 'public')] -%} 11 | {%- if int['OS-EXT-IPS:type'] == 'floating' -%} 12 | {{ int['addr'] }} 13 | {%- endif -%} 14 | {%- endfor -%} 15 | {%- endif -%} 16 | 17 | - name: Set to first addr if no floating 18 | ansible.builtin.set_fact: 19 | address: "{{ item.addresses[item.metadata.get('network', 'public')][0].addr }}" 20 | when: address == "" 21 | 22 | - name: Populate instance config dict 23 | ansible.builtin.set_fact: 24 | instance_conf_dict: 25 | { 26 | "instance": "{{ item.metadata.molecule_instance }}", 27 | "address": "{{ address }}", 28 | "user": "{{ item.metadata.user }}", 29 | "port": 22, 30 | "identity_file": "{{ identity_file }}", 31 | } 32 | register: instance_conf_dict 33 | 34 | - name: Add instance to all instances list 35 | ansible.builtin.set_fact: 36 | all_instances: "{{ all_instances + [instance_conf_dict] }}" 37 | -------------------------------------------------------------------------------- /src/molecule_plugins/openstack/playbooks/tasks/vars.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Load molecule state file 3 | ansible.builtin.include_vars: 4 | file: "{{ lookup('env', 'MOLECULE_STATE_FILE') }}" 5 | name: molecule_state 6 | 7 | - name: Set Molecule run UUID 8 | ansible.builtin.set_fact: 9 | uuid: "{{ molecule_state.run_uuid }}" 10 | 11 | - name: Set ssh key name 12 | ansible.builtin.set_fact: 13 | key_name: "molecule-test-{{ uuid }}" 14 | 15 | - name: Set local identity file 16 | ansible.builtin.set_fact: 17 | identity_file: "{{ lookup('env', 'HOME') }}/.ansible/tmp/{{ key_name }}" 18 | -------------------------------------------------------------------------------- /src/molecule_plugins/podman/__init__.py: -------------------------------------------------------------------------------- 1 | """Molecule Podman Driver.""" 2 | -------------------------------------------------------------------------------- /src/molecule_plugins/podman/cookiecutter/cookiecutter.json: -------------------------------------------------------------------------------- 1 | { 2 | "molecule_directory": "molecule", 3 | "role_name": "OVERRIDDEN", 4 | "scenario_name": "OVERRIDDEN" 5 | } 6 | -------------------------------------------------------------------------------- /src/molecule_plugins/podman/cookiecutter/{{cookiecutter.molecule_directory}}/{{cookiecutter.scenario_name}}/converge.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Converge 3 | hosts: all 4 | tasks: 5 | # replace these tasks with whatever you find suitable to test 6 | - name: Copy something to test use of synchronize module 7 | ansible.builtin.copy: 8 | src: /etc/hosts 9 | dest: /tmp/hosts-from-controller 10 | mode: "0644" 11 | - name: "Include {{ cookiecutter.role_name }}" 12 | ansible.builtin.include_role: 13 | name: "{{ cookiecutter.role_name }}" 14 | -------------------------------------------------------------------------------- /src/molecule_plugins/podman/playbooks/Dockerfile.j2: -------------------------------------------------------------------------------- 1 | # Molecule managed 2 | 3 | {% if item.registry is defined %} 4 | FROM {{ item.registry.url }}/{{ item.image }} 5 | {% else %} 6 | FROM {{ item.image }} 7 | {% endif %} 8 | 9 | {% if item.env is defined %} 10 | {% for var, value in item.env.items() %} 11 | {% if value %} 12 | ENV {{ var }} {{ value }} 13 | {% endif %} 14 | {% endfor %} 15 | {% endif %} 16 | 17 | RUN if [ $(command -v apt-get) ]; then export DEBIAN_FRONTEND=noninteractive && apt-get update && apt-get install -y python3 sudo bash ca-certificates iproute2 python3-apt aptitude && apt-get clean && rm -rf /var/lib/apt/lists/*; \ 18 | elif [ $(command -v dnf) ]; then dnf makecache && dnf --assumeyes install /usr/bin/python3 /usr/bin/python3-config /usr/bin/dnf-3 sudo bash iproute && dnf clean all; \ 19 | elif [ $(command -v yum) ]; then yum makecache fast && yum install -y /usr/bin/python /usr/bin/python2-config sudo yum-plugin-ovl bash iproute && sed -i 's/plugins=0/plugins=1/g' /etc/yum.conf && yum clean all; \ 20 | elif [ $(command -v zypper) ]; then zypper refresh && zypper install -y python3 sudo bash iproute2 && zypper clean -a; \ 21 | elif [ $(command -v apk) ]; then apk update && apk add --no-cache python3 sudo bash ca-certificates; \ 22 | elif [ $(command -v xbps-install) ]; then xbps-install -Syu && xbps-install -y python3 sudo bash ca-certificates iproute2 && xbps-remove -O; fi 23 | -------------------------------------------------------------------------------- /src/molecule_plugins/podman/playbooks/destroy.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Destroy 3 | hosts: localhost 4 | connection: local 5 | gather_facts: false 6 | no_log: "{{ molecule_no_log }}" 7 | become: "{{ not (item.rootless|default(true)) }}" 8 | vars: 9 | podman_exec: "{{ lookup('env','MOLECULE_PODMAN_EXECUTABLE')|default('podman',true) }}" 10 | tasks: 11 | - name: Destroy molecule instance(s) 12 | ansible.builtin.shell: "{{ podman_exec }} container exists {{ item.name }} && {{ podman_exec }} rm -f {{ item.name }} || true" 13 | register: server 14 | with_items: "{{ molecule_yml.platforms }}" 15 | async: 7200 16 | poll: 0 17 | changed_when: true 18 | 19 | - name: Wait for instance(s) deletion to complete 20 | become: "{{ not item.item.rootless | default(omit) }}" 21 | ansible.builtin.async_status: 22 | jid: "{{ item.ansible_job_id }}" 23 | register: podman_jobs 24 | until: podman_jobs.finished 25 | retries: 300 26 | with_items: "{{ server.results }}" 27 | 28 | - name: Delete podman network dedicated to this scenario 29 | containers.podman.podman_network: 30 | name: "{{ item.network }}" 31 | executable: "{{ podman_exec }}" 32 | state: absent 33 | when: 34 | - item.network is defined 35 | loop: "{{ molecule_yml.platforms | flatten(levels=1) }}" 36 | loop_control: 37 | extended: true 38 | label: "{{ item.name }}: {{ item.network | default('None specified') }}" 39 | -------------------------------------------------------------------------------- /src/molecule_plugins/podman/playbooks/validate-dockerfile.yml: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ansible-playbook 2 | --- 3 | - name: Validate dockerfile 4 | hosts: localhost 5 | connection: local 6 | gather_facts: false 7 | vars: 8 | platforms: 9 | # platforms supported as being managed by molecule/ansible, this does 10 | # not mean molecule itself can run on them. 11 | - image: alpine:edge 12 | - image: quay.io/centos/centos:stream9 13 | - image: ubuntu:latest 14 | - image: debian:latest 15 | tasks: 16 | - name: Create isolated build directories for each image 17 | ansible.builtin.tempfile: 18 | prefix: "molecule-dockerfile-{{ item.image | replace('/', '-') }}" 19 | state: directory 20 | register: temp_image_dirs 21 | with_items: "{{ platforms }}" 22 | loop_control: 23 | label: "{{ item.image }}" 24 | 25 | - name: Expand Dockerfile templates 26 | ansible.builtin.template: 27 | src: Dockerfile.j2 28 | dest: "{{ temp_image_dirs.results[index].path }}/Dockerfile" 29 | mode: "0600" 30 | register: result 31 | with_items: "{{ platforms }}" 32 | loop_control: 33 | index_var: index 34 | label: "{{ item.image }}" 35 | 36 | - name: Build Dockerfile(s) 37 | containers.podman.podman_image: 38 | name: "{{ item.item.image }}" 39 | path: "{{ temp_image_dirs.results[index].path }}" 40 | state: build 41 | with_items: "{{ result.results }}" 42 | loop_control: 43 | index_var: index 44 | label: "{{ item.item.image }}" 45 | register: result 46 | 47 | - name: Clean up temporary Dockerfile's 48 | ansible.builtin.file: 49 | path: "{{ item }}" 50 | state: absent 51 | loop: "{{ temp_image_dirs.results | map(attribute='path') | list }}" 52 | 53 | - name: Display result 54 | ansible.builtin.debug: 55 | var: result 56 | -------------------------------------------------------------------------------- /src/molecule_plugins/py.typed: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ansible-community/molecule-plugins/980db38a12043e1685dd0e91e9845f1ae684cab1/src/molecule_plugins/py.typed -------------------------------------------------------------------------------- /src/molecule_plugins/vagrant/__init__.py: -------------------------------------------------------------------------------- 1 | """Plugin exports.""" 2 | 3 | __name__ = __name__.split("_")[-1] 4 | -------------------------------------------------------------------------------- /src/molecule_plugins/vagrant/cookiecutter/cookiecutter.json: -------------------------------------------------------------------------------- 1 | { 2 | "molecule_directory": "molecule", 3 | "role_name": "OVERRIDDEN", 4 | "scenario_name": "OVERRIDDEN" 5 | } 6 | -------------------------------------------------------------------------------- /src/molecule_plugins/vagrant/cookiecutter/{{cookiecutter.molecule_directory}}/{{cookiecutter.scenario_name}}/INSTALL.rst: -------------------------------------------------------------------------------- 1 | ********************************* 2 | Vagrant driver installation guide 3 | ********************************* 4 | 5 | Requirements 6 | ============ 7 | 8 | * Vagrant 9 | * Virtualbox, Parallels, VMware Fusion, VMware Workstation or VMware Desktop 10 | 11 | Install 12 | ======= 13 | 14 | Please refer to the `Virtual environment`_ documentation for installation best 15 | practices. If not using a virtual environment, please consider passing the 16 | widely recommended `'--user' flag`_ when invoking ``pip``. 17 | 18 | .. _Virtual environment: https://virtualenv.pypa.io/en/latest/ 19 | .. _'--user' flag: https://packaging.python.org/tutorials/installing-packages/#installing-to-the-user-site 20 | 21 | .. code-block:: bash 22 | 23 | $ pip install 'molecule-plugins[vagrant]' 24 | -------------------------------------------------------------------------------- /src/molecule_plugins/vagrant/cookiecutter/{{cookiecutter.molecule_directory}}/{{cookiecutter.scenario_name}}/converge.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Converge 3 | hosts: all 4 | tasks: 5 | - name: "Include {{ cookiecutter.role_name }}" 6 | ansible.builtin.include_role: 7 | name: "{{ cookiecutter.role_name }}" 8 | -------------------------------------------------------------------------------- /src/molecule_plugins/vagrant/modules/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ansible-community/molecule-plugins/980db38a12043e1685dd0e91e9845f1ae684cab1/src/molecule_plugins/vagrant/modules/__init__.py -------------------------------------------------------------------------------- /src/molecule_plugins/vagrant/playbooks/create.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Create 3 | hosts: localhost 4 | connection: local 5 | gather_facts: false 6 | no_log: "{{ molecule_no_log }}" 7 | tasks: 8 | - name: Create molecule instance(s) # noqa fqcn[action] 9 | vagrant: 10 | instances: "{{ molecule_yml.platforms }}" 11 | default_box: "{{ molecule_yml.driver.default_box | default('generic/alpine316') }}" 12 | provider_name: "{{ molecule_yml.driver.provider.name | default(omit, true) }}" 13 | provision: "{{ molecule_yml.driver.provision | default(omit) }}" 14 | cachier: "{{ molecule_yml.driver.cachier | default(omit) }}" 15 | parallel: "{{ molecule_yml.driver.parallel | default(omit) }}" 16 | state: up 17 | register: server 18 | no_log: false 19 | 20 | # NOTE(retr0h): Vagrant/VirtualBox sucks and parallelizing instance creation 21 | # causes issues. 22 | 23 | # Mandatory configuration for Molecule to function. 24 | 25 | - name: Create molecule instances configuration 26 | when: server is changed # noqa no-handler 27 | block: 28 | - name: Populate instance config dict 29 | ansible.builtin.set_fact: 30 | instance_conf_dict: 31 | { 32 | "instance": "{{ item.Host }}", 33 | "address": "{{ item.HostName }}", 34 | "user": "{{ item.User }}", 35 | "port": "{{ item.Port }}", 36 | "identity_file": "{{ item.IdentityFile }}", 37 | } 38 | with_items: "{{ server.results }}" 39 | register: instance_config_dict 40 | 41 | - name: Convert instance config dict to a list 42 | ansible.builtin.set_fact: 43 | instance_conf: "{{ instance_config_dict.results | map(attribute='ansible_facts.instance_conf_dict') | list }}" 44 | 45 | - name: Dump instance config 46 | ansible.builtin.copy: 47 | content: "{{ instance_conf | to_json | from_json | to_yaml }}" 48 | dest: "{{ molecule_instance_config }}" 49 | mode: "0600" 50 | -------------------------------------------------------------------------------- /src/molecule_plugins/vagrant/playbooks/destroy.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Destroy 3 | hosts: localhost 4 | connection: local 5 | gather_facts: false 6 | no_log: "{{ molecule_no_log }}" 7 | tasks: 8 | - name: Destroy molecule instance(s) # noqa fqcn[action] 9 | vagrant: 10 | instances: "{{ molecule_yml.platforms }}" 11 | default_box: "{{ molecule_yml.driver.default_box | default('generic/alpine316') }}" 12 | provider_name: "{{ molecule_yml.driver.provider.name | default(omit, true) }}" 13 | cachier: "{{ molecule_yml.driver.cachier | default(omit) }}" 14 | force_stop: "{{ item.force_stop | default(true) }}" 15 | state: destroy 16 | register: server 17 | no_log: false 18 | 19 | # NOTE(retr0h): Vagrant/VirtualBox sucks and parallelizing instance deletion 20 | # causes issues. 21 | 22 | # Mandatory configuration for Molecule to function. 23 | 24 | - name: Populate instance config 25 | ansible.builtin.set_fact: 26 | instance_conf: {} 27 | 28 | - name: Dump instance config # noqa no-handler 29 | ansible.builtin.copy: 30 | content: | 31 | # Molecule managed 32 | {{ instance_conf | to_json | from_json | to_yaml }} 33 | dest: "{{ molecule_instance_config }}" 34 | mode: "0600" 35 | when: server.changed | bool 36 | -------------------------------------------------------------------------------- /src/molecule_plugins/vagrant/playbooks/prepare.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Prepare 3 | hosts: all 4 | gather_facts: false 5 | tasks: 6 | - name: Gather system info 7 | ansible.builtin.raw: uname 8 | register: raw_uname 9 | changed_when: false 10 | failed_when: false 11 | 12 | - name: Bootstrap python for Ansible 13 | ansible.builtin.raw: | 14 | command -v python3 python || ( 15 | command -v apk >/dev/null && sudo apk add --no-progress --update python3 || 16 | (test -e /usr/bin/dnf && sudo dnf install -y python3) || 17 | (test -e /usr/bin/apt && (apt -y update && apt install -y python3-minimal)) || 18 | (test -e /usr/bin/yum && sudo yum -y -qq install python3) || 19 | (test -e /usr/sbin/pkg && sudo env ASSUME_ALWAYS_YES=yes pkg update && sudo env ASSUME_ALWAYS_YES=yes pkg install python3) || 20 | (test -e /usr/sbin/pkg_add && sudo /usr/sbin/pkg_add -U -I -x python%3.9) || 21 | (test -e /usr/bin/pacman && sudo /usr/bin/pacman -Sy python3 --noconfirm --quiet) || 22 | echo "Warning: Python not bootstrapped due to unknown platform." 23 | ) 24 | become: true 25 | changed_when: false 26 | when: raw_uname.rc == 0 and raw_uname.stdout | trim == "Linux" 27 | -------------------------------------------------------------------------------- /test/azure/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ansible-community/molecule-plugins/980db38a12043e1685dd0e91e9845f1ae684cab1/test/azure/__init__.py -------------------------------------------------------------------------------- /test/azure/functional/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ansible-community/molecule-plugins/980db38a12043e1685dd0e91e9845f1ae684cab1/test/azure/functional/__init__.py -------------------------------------------------------------------------------- /test/azure/functional/conftest.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015-2018 Cisco Systems, Inc. 2 | # Copyright (c) 2018 Red Hat, Inc. 3 | # 4 | # Permission is hereby granted, free of charge, to any person obtaining a copy 5 | # of this software and associated documentation files (the "Software"), to 6 | # deal in the Software without restriction, including without limitation the 7 | # rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 8 | # sell copies of the Software, and to permit persons to whom the Software is 9 | # furnished to do so, subject to the following conditions: 10 | # 11 | # The above copyright notice and this permission notice shall be included in 12 | # all copies or substantial portions of the Software. 13 | # 14 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 20 | # DEALINGS IN THE SOFTWARE. 21 | 22 | 23 | from conftest import * # noqa 24 | -------------------------------------------------------------------------------- /test/azure/functional/test_azure.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015-2018 Cisco Systems, Inc. 2 | # Copyright (c) 2018 Red Hat, Inc. 3 | # 4 | # Permission is hereby granted, free of charge, to any person obtaining a copy 5 | # of this software and associated documentation files (the "Software"), to 6 | # deal in the Software without restriction, including without limitation the 7 | # rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 8 | # sell copies of the Software, and to permit persons to whom the Software is 9 | # furnished to do so, subject to the following conditions: 10 | # 11 | # The above copyright notice and this permission notice shall be included in 12 | # all copies or substantial portions of the Software. 13 | # 14 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 20 | # DEALINGS IN THE SOFTWARE. 21 | 22 | import os 23 | from pathlib import Path 24 | 25 | import pytest 26 | 27 | from conftest import change_dir_to 28 | from molecule import logger 29 | from molecule.app import get_app 30 | 31 | LOG = logger.get_logger(__name__) 32 | 33 | 34 | def test_azure_command_init_scenario(temp_dir): 35 | role_directory = os.path.join(temp_dir.strpath, "test_init") 36 | cmd = ["ansible-galaxy", "role", "init", "test_init"] 37 | result = get_app(Path()).run_command(cmd) 38 | assert result.returncode == 0 39 | 40 | with change_dir_to(role_directory): 41 | # we need to inject namespace info into meta/main.yml 42 | cmd_meta = [ 43 | "ansible", 44 | "localhost", 45 | "-o", # one line output 46 | "-m", 47 | "lineinfile", 48 | "-a", 49 | 'path=meta/main.yml line=" namespace: foo" insertafter=" author: your name"', 50 | ] 51 | get_app(Path()).run_command(cmd_meta, check=True) 52 | 53 | # we need to inject namespace info into tests/test.yml 54 | cmd_tests = [ 55 | "ansible", 56 | "localhost", 57 | "-o", # one line output 58 | "-m", 59 | "lineinfile", 60 | "-a", 61 | 'path=tests/test.yml line=" - foo.test_init" regex="^(.*) - test_init"', 62 | ] 63 | get_app(Path()).run_command(cmd_tests, check=True) 64 | 65 | molecule_directory = pytest.helpers.molecule_directory() 66 | scenario_directory = os.path.join(molecule_directory, "test_scenario") 67 | cmd = [ 68 | "molecule", 69 | "init", 70 | "scenario", 71 | "test_scenario", 72 | "--driver-name", 73 | "azure", 74 | ] 75 | result = get_app(Path()).run_command(cmd) 76 | assert result.returncode == 0 77 | 78 | assert os.path.isdir(scenario_directory) 79 | 80 | os.unlink(os.path.join(scenario_directory, "create.yml")) 81 | os.unlink(os.path.join(scenario_directory, "destroy.yml")) 82 | 83 | # temporary trick to pass on CI/CD 84 | if "AZURE_SECRET" in os.environ: 85 | cmd = ["molecule", "test", "-s", "test-scenario"] 86 | result = get_app(Path()).run_command(cmd) 87 | assert result.returncode == 0 88 | -------------------------------------------------------------------------------- /test/azure/scenarios/driver/azure/molecule/default/converge.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Converge 3 | hosts: all 4 | gather_facts: false 5 | become: true 6 | -------------------------------------------------------------------------------- /test/azure/scenarios/driver/azure/molecule/default/molecule.yml: -------------------------------------------------------------------------------- 1 | --- 2 | dependency: 3 | name: galaxy 4 | driver: 5 | name: azure 6 | platforms: 7 | - name: instance 8 | provisioner: 9 | name: ansible 10 | config_options: 11 | defaults: 12 | callback_whitelist: profile_roles,profile_tasks,timer 13 | playbooks: 14 | create: ../../../../../resources/playbooks/azure/create.yml 15 | destroy: ../../../../../resources/playbooks/azure/destroy.yml 16 | env: 17 | ANSIBLE_ROLES_PATH: ../../../../../resources/roles/ 18 | verifier: 19 | name: testinfra 20 | -------------------------------------------------------------------------------- /test/azure/scenarios/driver/azure/molecule/default/tests/test_default.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | import testinfra.utils.ansible_runner 4 | 5 | testinfra_hosts = testinfra.utils.ansible_runner.AnsibleRunner( 6 | os.environ["MOLECULE_INVENTORY_FILE"], 7 | ).get_hosts("all") 8 | 9 | 10 | def test_hostname(host): 11 | assert host.check_output("hostname -s") == "instance" 12 | 13 | 14 | def test_etc_molecule_directory(host): 15 | f = host.file("/etc/molecule") 16 | 17 | assert f.is_directory 18 | assert f.user == "root" 19 | assert f.group == "root" 20 | assert f.mode == 0o755 21 | 22 | 23 | def test_etc_molecule_ansible_hostname_file(host): 24 | f = host.file("/etc/molecule/instance") 25 | 26 | assert f.is_file 27 | assert f.user == "root" 28 | assert f.group == "root" 29 | assert f.mode == 0o644 30 | -------------------------------------------------------------------------------- /test/azure/scenarios/driver/azure/molecule/multi-node/converge.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Converge 3 | hosts: all 4 | gather_facts: false 5 | become: true 6 | 7 | - name: Converge 8 | hosts: bar 9 | gather_facts: false 10 | become: true 11 | 12 | - name: Converge 13 | hosts: foo 14 | gather_facts: false 15 | become: true 16 | 17 | - name: Converge 18 | hosts: baz 19 | gather_facts: false 20 | become: true 21 | -------------------------------------------------------------------------------- /test/azure/scenarios/driver/azure/molecule/multi-node/molecule.yml: -------------------------------------------------------------------------------- 1 | --- 2 | dependency: 3 | name: galaxy 4 | driver: 5 | name: azure 6 | platforms: 7 | - name: instance-1 8 | groups: 9 | - foo 10 | - bar 11 | - name: instance-2 12 | groups: 13 | - foo 14 | - baz 15 | provisioner: 16 | name: ansible 17 | config_options: 18 | defaults: 19 | callback_whitelist: profile_roles,profile_tasks,timer 20 | playbooks: 21 | create: ../../../../../resources/playbooks/azure/create.yml 22 | destroy: ../../../../../resources/playbooks/azure/destroy.yml 23 | inventory: 24 | group_vars: 25 | all: 26 | resource_group_name: molecule 27 | location: "{{ lookup('env', 'AZURE_REGION') or 'westus' }}" 28 | ssh_user: molecule 29 | ssh_port: 22 30 | virtual_network_name: molecule_vnet 31 | subnet_name: molecule_subnet 32 | keypair_path: "{{ lookup('env', 'MOLECULE_EPHEMERAL_DIRECTORY') }}/ssh_key" 33 | env: 34 | ANSIBLE_ROLES_PATH: ../../../../../resources/roles/ 35 | verifier: 36 | name: testinfra 37 | -------------------------------------------------------------------------------- /test/azure/scenarios/driver/azure/molecule/multi-node/prepare.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Prepare 3 | hosts: all 4 | gather_facts: false 5 | tasks: 6 | - name: Install python for Ansible 7 | ansible.builtin.raw: test -e /usr/bin/python || (apt -y update && apt install -y python-minimal python-zipstream) 8 | become: true 9 | changed_when: false 10 | -------------------------------------------------------------------------------- /test/azure/scenarios/driver/azure/molecule/multi-node/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ansible-community/molecule-plugins/980db38a12043e1685dd0e91e9845f1ae684cab1/test/azure/scenarios/driver/azure/molecule/multi-node/tests/__init__.py -------------------------------------------------------------------------------- /test/azure/scenarios/driver/azure/molecule/multi-node/tests/test_default.py: -------------------------------------------------------------------------------- 1 | import os 2 | import re 3 | 4 | import testinfra.utils.ansible_runner 5 | 6 | testinfra_hosts = testinfra.utils.ansible_runner.AnsibleRunner( 7 | os.environ["MOLECULE_INVENTORY_FILE"], 8 | ).get_hosts("all") 9 | 10 | 11 | def test_hostname(host): 12 | assert re.search(r"instance-[12].*", host.check_output("hostname -s")) 13 | 14 | 15 | def test_etc_molecule_directory(host): 16 | f = host.file("/etc/molecule") 17 | 18 | assert f.is_directory 19 | assert f.user == "root" 20 | assert f.group == "root" 21 | assert f.mode == 0o755 22 | 23 | 24 | def test_etc_molecule_ansible_hostname_file(host): 25 | filename = "/etc/molecule/{}".format(host.check_output("hostname -s")) 26 | f = host.file(filename) 27 | 28 | assert f.is_file 29 | assert f.user == "root" 30 | assert f.group == "root" 31 | assert f.mode == 0o644 32 | -------------------------------------------------------------------------------- /test/azure/test_driver.py: -------------------------------------------------------------------------------- 1 | from molecule import api 2 | 3 | 4 | def test_azure_driver_is_detected(): 5 | assert "azure" in [str(d) for d in api.drivers()] 6 | -------------------------------------------------------------------------------- /test/containers/__init__.py: -------------------------------------------------------------------------------- 1 | """Molecule Containers Driver Tests.""" 2 | -------------------------------------------------------------------------------- /test/containers/functional/.ansible-lint: -------------------------------------------------------------------------------- 1 | # ansible-lint config for functional testing, used to bypass expected metadata 2 | # errors in molecule-generated roles. Loaded via the metadata_lint_update 3 | # pytest helper. For reference, see "E7xx - metadata" in: 4 | # https://docs.ansible.com/ansible-lint/rules/default_rules.html 5 | skip_list: 6 | # metadata/701 - Role info should contain platforms 7 | - '701' 8 | # metadata/703 - Should change default metadata: " 9 | - '703' 10 | -------------------------------------------------------------------------------- /test/containers/functional/__init__.py: -------------------------------------------------------------------------------- 1 | """Functional Tests.""" 2 | -------------------------------------------------------------------------------- /test/containers/functional/conftest.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015-2018 Cisco Systems, Inc. 2 | # Copyright (c) 2018 Red Hat, Inc. 3 | # 4 | # Permission is hereby granted, free of charge, to any person obtaining a copy 5 | # of this software and associated documentation files (the "Software"), to 6 | # deal in the Software without restriction, including without limitation the 7 | # rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 8 | # sell copies of the Software, and to permit persons to whom the Software is 9 | # furnished to do so, subject to the following conditions: 10 | # 11 | # The above copyright notice and this permission notice shall be included in 12 | # all copies or substantial portions of the Software. 13 | # 14 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 20 | # DEALINGS IN THE SOFTWARE. 21 | """PyTest Config.""" 22 | 23 | from conftest import * # noqa pylint: disable=wildcard-import,unused-wildcard-import 24 | -------------------------------------------------------------------------------- /test/containers/functional/test_containers.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015-2018 Cisco Systems, Inc. 2 | # Copyright (c) 2018 Red Hat, Inc. 3 | # 4 | # Permission is hereby granted, free of charge, to any person obtaining a copy 5 | # of this software and associated documentation files (the "Software"), to 6 | # deal in the Software without restriction, including without limitation the 7 | # rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 8 | # sell copies of the Software, and to permit persons to whom the Software is 9 | # furnished to do so, subject to the following conditions: 10 | # 11 | # The above copyright notice and this permission notice shall be included in 12 | # all copies or substantial portions of the Software. 13 | # 14 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 20 | # DEALINGS IN THE SOFTWARE. 21 | """Functional Tests.""" 22 | 23 | import os 24 | from pathlib import Path 25 | 26 | from conftest import change_dir_to, molecule_directory 27 | from molecule import logger 28 | from molecule.app import get_app 29 | 30 | LOG = logger.get_logger(__name__) 31 | 32 | 33 | def test_containers_command_init_scenario(temp_dir): 34 | """Verify that we can initialize a new scenario with this driver.""" 35 | with change_dir_to(temp_dir): 36 | scenario_directory = os.path.join(molecule_directory(), "default") 37 | cmd = [ 38 | "molecule", 39 | "init", 40 | "scenario", 41 | "default", 42 | "--driver-name", 43 | "containers", 44 | ] 45 | result = get_app(Path()).run_command(cmd) 46 | assert result.returncode == 0 47 | 48 | assert os.path.isdir(scenario_directory) 49 | 50 | # we do not run the full "test" sequence because lint will fail, check 51 | # is shorter but comprehensive enough to test the most important 52 | # functionality: destroy, dependency, create, prepare, converge 53 | cmd = ["molecule", "check", "-s", "default"] 54 | result = get_app(Path()).run_command(cmd) 55 | assert result.returncode == 0 56 | -------------------------------------------------------------------------------- /test/containers/scenarios/driver/containers/molecule/default/converge.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Converge 3 | hosts: all 4 | gather_facts: false 5 | become: true 6 | tasks: 7 | - name: Display bogus message 8 | ansible.builtin.debug: 9 | msg: seems ok 10 | -------------------------------------------------------------------------------- /test/containers/scenarios/driver/containers/molecule/default/molecule.yml: -------------------------------------------------------------------------------- 1 | --- 2 | driver: 3 | name: containers 4 | platforms: 5 | - name: instance 6 | provisioner: 7 | name: ansible 8 | -------------------------------------------------------------------------------- /test/containers/test_driver.py: -------------------------------------------------------------------------------- 1 | """Unit Tests.""" 2 | 3 | from molecule import api 4 | 5 | 6 | def test_container_driver_is_detected(): 7 | """Check that driver is recognized.""" 8 | assert "containers" in [str(d) for d in api.drivers()] 9 | -------------------------------------------------------------------------------- /test/docker/__init__.py: -------------------------------------------------------------------------------- 1 | """Driver tests.""" 2 | -------------------------------------------------------------------------------- /test/docker/conftest.py: -------------------------------------------------------------------------------- 1 | """Pytest Fixtures.""" 2 | 3 | from conftest import random_string, temp_dir # noqa 4 | 5 | import pytest 6 | 7 | 8 | @pytest.fixture() 9 | def DRIVER(): 10 | """Return name of the driver to be tested.""" 11 | return "docker" 12 | -------------------------------------------------------------------------------- /test/docker/scenarios/env-substitution/molecule/default/converge.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Converge 3 | hosts: all 4 | gather_facts: false 5 | become: false 6 | -------------------------------------------------------------------------------- /test/docker/scenarios/env-substitution/molecule/default/molecule.yml: -------------------------------------------------------------------------------- 1 | --- 2 | driver: 3 | name: docker 4 | platforms: 5 | - name: instance-${DOES_NOT_EXIST:-local} 6 | image: ${MOLECULE_ROLE_IMAGE:-quay.io/centos/centos:stream9} 7 | pre_build_image: false 8 | provisioner: 9 | name: ansible 10 | -------------------------------------------------------------------------------- /test/docker/scenarios/with-context/molecule/default/Dockerfile.j2: -------------------------------------------------------------------------------- 1 | FROM python:3 2 | COPY FOO FOO 3 | ENTRYPOINT [] 4 | CMD ["cat"] 5 | -------------------------------------------------------------------------------- /test/docker/scenarios/with-context/molecule/default/FOO: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ansible-community/molecule-plugins/980db38a12043e1685dd0e91e9845f1ae684cab1/test/docker/scenarios/with-context/molecule/default/FOO -------------------------------------------------------------------------------- /test/docker/scenarios/with-context/molecule/default/converge.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Converge 3 | hosts: all 4 | gather_facts: false 5 | become: false 6 | -------------------------------------------------------------------------------- /test/docker/scenarios/with-context/molecule/default/molecule.yml: -------------------------------------------------------------------------------- 1 | --- 2 | driver: 3 | name: docker 4 | platforms: 5 | - name: instance 6 | image: with-context 7 | pre_build_image: false 8 | provisioner: 9 | name: ansible 10 | -------------------------------------------------------------------------------- /test/docker/test_driver.py: -------------------------------------------------------------------------------- 1 | """Unit tests.""" 2 | 3 | from molecule import api 4 | 5 | 6 | def test_docker_driver_is_detected(DRIVER): 7 | """Asserts that molecule recognizes the driver.""" 8 | assert DRIVER in [str(d) for d in api.drivers()] 9 | -------------------------------------------------------------------------------- /test/docker/test_func.py: -------------------------------------------------------------------------------- 1 | """Functional tests.""" 2 | 3 | import os 4 | import pathlib 5 | import shutil 6 | import subprocess 7 | from pathlib import Path 8 | 9 | import pytest 10 | 11 | from conftest import change_dir_to 12 | from molecule import logger 13 | from molecule.app import get_app 14 | 15 | LOG = logger.get_logger(__name__) 16 | 17 | 18 | def format_result(result: subprocess.CompletedProcess): 19 | """Return friendly representation of completed process run.""" 20 | return ( 21 | f"RC: {result.returncode}\n" 22 | + f"STDOUT: {result.stdout}\n" 23 | + f"STDERR: {result.stderr}" 24 | ) 25 | 26 | 27 | @pytest.mark.skip(reason="broken, fix welcomed") 28 | def test_command_init_and_test_scenario(tmp_path: pathlib.Path, DRIVER: str) -> None: 29 | """Verify that init scenario works.""" 30 | shutil.rmtree(tmp_path, ignore_errors=True) 31 | tmp_path.mkdir(exist_ok=True) 32 | 33 | scenario_name = "default" 34 | 35 | with change_dir_to(tmp_path): 36 | scenario_directory = tmp_path / "molecule" / scenario_name 37 | cmd = [ 38 | "molecule", 39 | "init", 40 | "scenario", 41 | "--driver-name", 42 | DRIVER, 43 | ] 44 | result = get_app(tmp_path).run_command(cmd) 45 | assert result.returncode == 0 46 | 47 | assert scenario_directory.exists() 48 | 49 | # run molecule reset as this may clean some leftovers from other 50 | # test runs and also ensure that reset works. 51 | result = get_app(tmp_path).run_command( 52 | ["molecule", "reset"] 53 | ) # default scenario 54 | assert result.returncode == 0 55 | 56 | result = get_app(tmp_path).run_command( 57 | ["molecule", "reset", "-s", scenario_name] 58 | ) 59 | assert result.returncode == 0 60 | 61 | cmd = ["molecule", "--debug", "test", "-s", scenario_name] 62 | result = get_app(tmp_path).run_command(cmd) 63 | assert result.returncode == 0 64 | 65 | 66 | @pytest.mark.skip(reason="broken, fix welcomed") 67 | def test_command_static_scenario() -> None: 68 | """Validate that the scenario we included with code still works.""" 69 | cmd = ["molecule", "test"] 70 | 71 | result = get_app(Path()).run_command(cmd) 72 | assert result.returncode == 0 73 | 74 | 75 | @pytest.mark.skip(reason="broken, fix welcomed") 76 | def test_dockerfile_with_context() -> None: 77 | """Verify that Dockerfile.j2 with context works.""" 78 | with change_dir_to("test/docker/scenarios/with-context"): 79 | cmd = ["molecule", "--debug", "test"] 80 | result = get_app(Path()).run_command(cmd) 81 | assert result.returncode == 0 82 | 83 | 84 | @pytest.mark.skip(reason="broken, fix welcomed") 85 | def test_env_substitution() -> None: 86 | """Verify that env variables in molecule.yml are replaced properly.""" 87 | os.environ["MOLECULE_ROLE_IMAGE"] = "debian:bullseye" 88 | with change_dir_to("test/docker/scenarios/env-substitution"): 89 | cmd = ["molecule", "--debug", "test"] 90 | result = get_app(Path()).run_command(cmd) 91 | assert result.returncode == 0 92 | -------------------------------------------------------------------------------- /test/ec2/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ansible-community/molecule-plugins/980db38a12043e1685dd0e91e9845f1ae684cab1/test/ec2/__init__.py -------------------------------------------------------------------------------- /test/ec2/functional/.ansible-lint: -------------------------------------------------------------------------------- 1 | # ansible-lint config for functional testing, used to bypass expected metadata 2 | # errors in molecule-generated roles. Loaded via the metadata_lint_update 3 | # pytest helper. For reference, see "E7xx - metadata" in: 4 | # https://docs.ansible.com/ansible-lint/rules/default_rules.html 5 | skip_list: 6 | # metadata/701 - Role info should contain platforms 7 | - '701' 8 | # metadata/703 - Should change default metadata: " 9 | - '703' 10 | -------------------------------------------------------------------------------- /test/ec2/functional/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ansible-community/molecule-plugins/980db38a12043e1685dd0e91e9845f1ae684cab1/test/ec2/functional/__init__.py -------------------------------------------------------------------------------- /test/ec2/functional/conftest.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015-2018 Cisco Systems, Inc. 2 | # Copyright (c) 2018 Red Hat, Inc. 3 | # 4 | # Permission is hereby granted, free of charge, to any person obtaining a copy 5 | # of this software and associated documentation files (the "Software"), to 6 | # deal in the Software without restriction, including without limitation the 7 | # rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 8 | # sell copies of the Software, and to permit persons to whom the Software is 9 | # furnished to do so, subject to the following conditions: 10 | # 11 | # The above copyright notice and this permission notice shall be included in 12 | # all copies or substantial portions of the Software. 13 | # 14 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 20 | # DEALINGS IN THE SOFTWARE. 21 | 22 | 23 | from conftest import * # noqa 24 | -------------------------------------------------------------------------------- /test/ec2/functional/test_ec2.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015-2018 Cisco Systems, Inc. 2 | # Copyright (c) 2018 Red Hat, Inc. 3 | # 4 | # Permission is hereby granted, free of charge, to any person obtaining a copy 5 | # of this software and associated documentation files (the "Software"), to 6 | # deal in the Software without restriction, including without limitation the 7 | # rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 8 | # sell copies of the Software, and to permit persons to whom the Software is 9 | # furnished to do so, subject to the following conditions: 10 | # 11 | # The above copyright notice and this permission notice shall be included in 12 | # all copies or substantial portions of the Software. 13 | # 14 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 20 | # DEALINGS IN THE SOFTWARE. 21 | 22 | import os 23 | from pathlib import Path 24 | 25 | import pytest 26 | 27 | from conftest import change_dir_to, metadata_lint_update 28 | from molecule import logger 29 | from molecule.app import get_app 30 | 31 | LOG = logger.get_logger(__name__) 32 | 33 | 34 | @pytest.mark.xfail(reason="need to fix template path") 35 | def test_ec2_command_init_scenario(temp_dir): 36 | role_directory = os.path.join(temp_dir.strpath, "test-init") 37 | cmd = ["molecule", "init", "role", "test-init"] 38 | assert get_app(Path()).run_command(cmd).returncode == 0 39 | metadata_lint_update(role_directory) 40 | 41 | with change_dir_to(role_directory): 42 | molecule_directory = pytest.helpers.molecule_directory() 43 | scenario_directory = os.path.join(molecule_directory, "test-scenario") 44 | cmd = [ 45 | "molecule", 46 | "init", 47 | "scenario", 48 | "test-scenario", 49 | "--role_name=test-init", 50 | "--driver-name=ec2", 51 | ] 52 | assert get_app(Path()).run_command(cmd).returncode == 0 53 | 54 | assert os.path.isdir(scenario_directory) 55 | os.unlink(os.path.join(scenario_directory, "create.yml")) 56 | os.unlink(os.path.join(scenario_directory, "destroy.yml")) 57 | 58 | cmd = ["molecule", "test", "-s", "test-scenario"] 59 | assert get_app(Path()).run_command(cmd).returncode == 0 60 | -------------------------------------------------------------------------------- /test/ec2/scenarios/driver/ec2/molecule/default/converge.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Converge 3 | hosts: all 4 | gather_facts: false 5 | become: true 6 | tasks: [] 7 | -------------------------------------------------------------------------------- /test/ec2/scenarios/driver/ec2/molecule/default/molecule.yml: -------------------------------------------------------------------------------- 1 | --- 2 | dependency: 3 | name: galaxy 4 | driver: 5 | name: ec2 6 | platforms: 7 | - name: instance 8 | image: ami-a5b196c0 9 | instance_type: t2.micro 10 | vpc_subnet_id: subnet-6456fd1f 11 | provisioner: 12 | name: ansible 13 | playbooks: 14 | create: ../../../../../resources/playbooks/ec2/create.yml 15 | destroy: ../../../../../resources/playbooks/ec2/destroy.yml 16 | env: 17 | ANSIBLE_ROLES_PATH: ../../../../../resources/roles/ 18 | lint: 19 | name: ansible-lint 20 | scenario: 21 | name: default 22 | verifier: 23 | name: testinfra 24 | -------------------------------------------------------------------------------- /test/ec2/scenarios/driver/ec2/molecule/default/prepare.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Prepare 3 | hosts: all 4 | gather_facts: false 5 | tasks: 6 | - name: Make sure python3 is installed 7 | ansible.builtin.package: 8 | name: python3 9 | state: present 10 | become: true 11 | -------------------------------------------------------------------------------- /test/ec2/scenarios/driver/ec2/molecule/default/tests/test_default.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | import testinfra.utils.ansible_runner 4 | 5 | testinfra_hosts = testinfra.utils.ansible_runner.AnsibleRunner( 6 | os.environ["MOLECULE_INVENTORY_FILE"], 7 | ).get_hosts("all") 8 | 9 | 10 | # EC2 provides unique random hostnames. 11 | def test_hostname(host): 12 | pass 13 | 14 | 15 | def test_etc_molecule_directory(host): 16 | f = host.file("/etc/molecule") 17 | 18 | assert f.is_directory 19 | assert f.user == "root" 20 | assert f.group == "root" 21 | assert f.mode == 0o755 22 | 23 | 24 | def test_etc_molecule_ansible_hostname_file(host): 25 | filename = "/etc/molecule/{}".format(host.check_output("hostname -s")) 26 | f = host.file(filename) 27 | 28 | assert f.is_file 29 | assert f.user == "root" 30 | assert f.group == "root" 31 | assert f.mode == 0o644 32 | -------------------------------------------------------------------------------- /test/ec2/scenarios/driver/ec2/molecule/multi-node/converge.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Converge 3 | hosts: all 4 | gather_facts: false 5 | become: true 6 | tasks: [] 7 | - name: Converge 8 | hosts: bar 9 | gather_facts: false 10 | become: true 11 | tasks: [] 12 | - name: Converge 13 | hosts: foo 14 | gather_facts: false 15 | become: true 16 | tasks: [] 17 | - name: Converge 18 | hosts: baz 19 | gather_facts: false 20 | become: true 21 | tasks: [] 22 | -------------------------------------------------------------------------------- /test/ec2/scenarios/driver/ec2/molecule/multi-node/molecule.yml: -------------------------------------------------------------------------------- 1 | --- 2 | dependency: 3 | name: galaxy 4 | driver: 5 | name: ec2 6 | lint: 7 | name: yamllint 8 | options: 9 | config-file: ../../../resources/.yamllint 10 | platforms: 11 | - name: instance-1 12 | image: ami-a5b196c0 13 | instance_type: t2.micro 14 | vpc_subnet_id: subnet-6456fd1f 15 | groups: 16 | - foo 17 | - bar 18 | - name: instance-2 19 | image: ami-a5b196c0 20 | instance_type: t2.micro 21 | vpc_subnet_id: subnet-6456fd1f 22 | groups: 23 | - foo 24 | - baz 25 | provisioner: 26 | name: ansible 27 | config_options: 28 | defaults: 29 | callback_whitelist: profile_roles,profile_tasks,timer 30 | playbooks: 31 | create: ../../../../../resources/playbooks/ec2/create.yml 32 | destroy: ../../../../../resources/playbooks/ec2/destroy.yml 33 | env: 34 | ANSIBLE_ROLES_PATH: ../../../../../resources/roles/ 35 | lint: 36 | name: ansible-lint 37 | scenario: 38 | name: multi-node 39 | verifier: 40 | name: testinfra 41 | lint: 42 | name: flake8 43 | -------------------------------------------------------------------------------- /test/ec2/scenarios/driver/ec2/molecule/multi-node/prepare.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Prepare 3 | hosts: all 4 | gather_facts: false 5 | tasks: 6 | - name: Make sure python3 is installed 7 | ansible.builtin.package: 8 | name: python3 9 | state: present 10 | become: true 11 | -------------------------------------------------------------------------------- /test/ec2/scenarios/driver/ec2/molecule/multi-node/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ansible-community/molecule-plugins/980db38a12043e1685dd0e91e9845f1ae684cab1/test/ec2/scenarios/driver/ec2/molecule/multi-node/tests/__init__.py -------------------------------------------------------------------------------- /test/ec2/scenarios/driver/ec2/molecule/multi-node/tests/test_default.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | import testinfra.utils.ansible_runner 4 | 5 | testinfra_hosts = testinfra.utils.ansible_runner.AnsibleRunner( 6 | os.environ["MOLECULE_INVENTORY_FILE"], 7 | ).get_hosts("all") 8 | 9 | 10 | # EC2 provides unique random hostnames. 11 | def test_hostname(host): 12 | pass 13 | 14 | 15 | def test_etc_molecule_directory(host): 16 | f = host.file("/etc/molecule") 17 | 18 | assert f.is_directory 19 | assert f.user == "root" 20 | assert f.group == "root" 21 | assert f.mode == 0o755 22 | 23 | 24 | def test_etc_molecule_ansible_hostname_file(host): 25 | filename = "/etc/molecule/{}".format(host.check_output("hostname -s")) 26 | f = host.file(filename) 27 | 28 | assert f.is_file 29 | assert f.user == "root" 30 | assert f.group == "root" 31 | assert f.mode == 0o644 32 | -------------------------------------------------------------------------------- /test/ec2/test_driver.py: -------------------------------------------------------------------------------- 1 | from molecule import api 2 | 3 | 4 | def test_ec2_driver_is_detected(): 5 | assert "ec2" in [str(d) for d in api.drivers()] 6 | -------------------------------------------------------------------------------- /test/gce/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ansible-community/molecule-plugins/980db38a12043e1685dd0e91e9845f1ae684cab1/test/gce/__init__.py -------------------------------------------------------------------------------- /test/gce/functional/.ansible-lint: -------------------------------------------------------------------------------- 1 | # ansible-lint config for functional testing, used to bypass expected metadata 2 | # errors in molecule-generated roles. Loaded via the metadata_lint_update 3 | # pytest helper. For reference, see "E7xx - metadata" in: 4 | # https://docs.ansible.com/ansible-lint/rules/default_rules.html 5 | skip_list: 6 | # metadata/701 - Role info should contain platforms 7 | - '701' 8 | # metadata/703 - Should change default metadata: " 9 | - '703' 10 | -------------------------------------------------------------------------------- /test/gce/functional/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ansible-community/molecule-plugins/980db38a12043e1685dd0e91e9845f1ae684cab1/test/gce/functional/__init__.py -------------------------------------------------------------------------------- /test/gce/functional/conftest.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015-2018 Cisco Systems, Inc. 2 | # Copyright (c) 2018 Red Hat, Inc. 3 | # 4 | # Permission is hereby granted, free of charge, to any person obtaining a copy 5 | # of this software and associated documentation files (the "Software"), to 6 | # deal in the Software without restriction, including without limitation the 7 | # rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 8 | # sell copies of the Software, and to permit persons to whom the Software is 9 | # furnished to do so, subject to the following conditions: 10 | # 11 | # The above copyright notice and this permission notice shall be included in 12 | # all copies or substantial portions of the Software. 13 | # 14 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 20 | # DEALINGS IN THE SOFTWARE. 21 | 22 | 23 | from conftest import * # noqa 24 | -------------------------------------------------------------------------------- /test/gce/functional/test_func.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015-2018 Cisco Systems, Inc. 2 | # Copyright (c) 2018 Red Hat, Inc. 3 | # 4 | # Permission is hereby granted, free of charge, to any person obtaining a copy 5 | # of this software and associated documentation files (the "Software"), to 6 | # deal in the Software without restriction, including without limitation the 7 | # rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 8 | # sell copies of the Software, and to permit persons to whom the Software is 9 | # furnished to do so, subject to the following conditions: 10 | # 11 | # The above copyright notice and this permission notice shall be included in 12 | # all copies or substantial portions of the Software. 13 | # 14 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 20 | # DEALINGS IN THE SOFTWARE. 21 | 22 | import os 23 | from pathlib import Path 24 | 25 | import pytest 26 | 27 | from conftest import change_dir_to, metadata_lint_update 28 | from molecule import logger 29 | from molecule.app import get_app 30 | 31 | LOG = logger.get_logger(__name__) 32 | driver_name = __name__.split(".")[0].split("_")[-1] 33 | 34 | 35 | @pytest.mark.xfail(reason="need to fix template path") 36 | def test_gce_command_init_scenario(temp_dir): 37 | """Test init scenario with driver.""" 38 | role_directory = os.path.join(temp_dir.strpath, "test-init") 39 | cmd = ["molecule", "init", "role", "test-init"] 40 | assert get_app(Path()).run_command(cmd).returncode == 0 41 | metadata_lint_update(role_directory) 42 | 43 | with change_dir_to(role_directory): 44 | molecule_directory = pytest.helpers.molecule_directory() 45 | scenario_directory = os.path.join(molecule_directory, "test-scenario") 46 | cmd = [ 47 | "molecule", 48 | "init", 49 | "scenario", 50 | "test-scenario", 51 | "--role-name", 52 | "test-init", 53 | "--driver-name", 54 | driver_name, 55 | ] 56 | assert get_app(Path()).run_command(cmd).returncode == 0 57 | 58 | assert os.path.isdir(scenario_directory) 59 | os.unlink(os.path.join(scenario_directory, "create.yml")) 60 | os.unlink(os.path.join(scenario_directory, "destroy.yml")) 61 | 62 | cmd = ["molecule", "test", "-s", "test-scenario"] 63 | assert get_app(Path()).run_command(cmd).returncode == 0 64 | -------------------------------------------------------------------------------- /test/gce/scenarios/linux/INSTALL.md: -------------------------------------------------------------------------------- 1 | # Google Cloud Engine driver installation guide 2 | 3 | ## Requirements 4 | 5 | - A GCE credentials rc file 6 | 7 | ## Install 8 | 9 | Please refer to the [Virtual environment][] documentation for 10 | installation best practices. If not using a virtual environment, please 11 | consider passing the widely recommended ['--user' flag][] when invoking 12 | `pip`. 13 | 14 | ```bash 15 | $ pip install 'molecule_gce' 16 | ``` 17 | 18 | [Virtual environment]: https://virtualenv.pypa.io/en/latest/ 19 | ['--user' flag]: https://packaging.python.org/tutorials/installing-packages/#installing-to-the-user-site 20 | -------------------------------------------------------------------------------- /test/gce/scenarios/linux/converge.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Converge 3 | hosts: all 4 | tasks: 5 | - name: Include testrole 6 | ansible.builtin.include_role: 7 | name: testrole 8 | -------------------------------------------------------------------------------- /test/gce/scenarios/linux/create.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Create 3 | hosts: localhost 4 | connection: local 5 | gather_facts: false 6 | no_log: "{{ molecule_no_log }}" 7 | vars: 8 | ssh_identity_file: "{{ lookup('env', 'MOLECULE_EPHEMERAL_DIRECTORY') }}/ssh_key" 9 | gcp_project_id: "{{ molecule_yml.driver.project_id | default(lookup('env', 'GCE_PROJECT_ID')) }}" 10 | 11 | tasks: 12 | - name: Make sure if linux or windows either specified 13 | ansible.builtin.assert: 14 | that: 15 | - molecule_yml.driver.instance_os_type | lower == "linux" or molecule_yml.driver.instance_os_type | lower == "windows" 16 | fail_msg: instance_os_type is possible only to specify linux or windows either 17 | 18 | - name: Get network info 19 | google.cloud.gcp_compute_network_info: 20 | filters: 21 | - name = "{{ molecule_yml.driver.network_name | default('default') }}" 22 | project: "{{ molecule_yml.driver.vpc_host_project | default(gcp_project_id) }}" 23 | service_account_email: "{{ molecule_yml.driver.service_account_email | default(omit, true) }}" 24 | service_account_file: "{{ molecule_yml.driver.service_account_file | default(omit, true) }}" 25 | auth_kind: "{{ molecule_yml.driver.auth_kind | default(omit, true) }}" 26 | register: my_network 27 | 28 | - name: Get subnetwork info 29 | google.cloud.gcp_compute_subnetwork_info: 30 | filters: 31 | - name = "{{ molecule_yml.driver.subnetwork_name | default('default') }}" 32 | project: "{{ molecule_yml.driver.vpc_host_project | default(gcp_project_id) }}" 33 | region: "{{ molecule_yml.driver.region }}" 34 | service_account_email: "{{ molecule_yml.driver.service_account_email | default(omit, true) }}" 35 | service_account_file: "{{ molecule_yml.driver.service_account_file | default(omit, true) }}" 36 | auth_kind: "{{ molecule_yml.driver.auth_kind | default(omit, true) }}" 37 | register: my_subnetwork 38 | 39 | - name: Set external access config 40 | ansible.builtin.set_fact: 41 | external_access_config: 42 | - access_configs: 43 | - name: External NAT 44 | type: ONE_TO_NAT 45 | when: molecule_yml.driver.external_access 46 | 47 | - name: Include create_linux_instance tasks 48 | ansible.builtin.include_tasks: tasks/create_linux_instance.yml 49 | when: 50 | - molecule_yml.driver.instance_os_type | lower == "linux" 51 | 52 | - name: Include create_windows_instance tasks 53 | ansible.builtin.include_tasks: tasks/create_windows_instance.yml 54 | when: 55 | - molecule_yml.driver.instance_os_type | lower == "windows" 56 | 57 | handlers: 58 | - name: Import main handler tasks 59 | ansible.builtin.import_tasks: handlers/main.yml 60 | -------------------------------------------------------------------------------- /test/gce/scenarios/linux/destroy.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Destroy 3 | hosts: localhost 4 | connection: local 5 | gather_facts: false 6 | no_log: "{{ molecule_no_log }}" 7 | 8 | tasks: 9 | - name: Destroy molecule instance(s) 10 | google.cloud.gcp_compute_instance: 11 | name: "{{ item.name }}" 12 | state: absent 13 | zone: "{{ item.zone | default(molecule_yml.driver.region + '-b') }}" 14 | project: "{{ molecule_yml.driver.project_id | default(lookup('env', 'GCE_PROJECT_ID')) }}" 15 | scopes: "{{ molecule_yml.driver.scopes | default(['https://www.googleapis.com/auth/compute'], True) }}" 16 | service_account_email: "{{ molecule_yml.driver.service_account_email | default(omit, true) }}" 17 | service_account_file: "{{ molecule_yml.driver.service_account_file | default(omit, true) }}" 18 | auth_kind: "{{ molecule_yml.driver.auth_kind | default(omit, true) }}" 19 | register: async_results 20 | loop: "{{ molecule_yml.platforms }}" 21 | async: 7200 22 | poll: 0 23 | notify: 24 | - Wipe out instance config 25 | - Dump instance config 26 | 27 | - name: Wait for instance(s) deletion to complete 28 | ansible.builtin.async_status: 29 | jid: "{{ item.ansible_job_id }}" 30 | register: server 31 | until: server.finished 32 | retries: 300 33 | delay: 10 34 | loop: "{{ async_results.results }}" 35 | 36 | handlers: 37 | - name: Import main handler tasks 38 | ansible.builtin.import_tasks: handlers/main.yml 39 | -------------------------------------------------------------------------------- /test/gce/scenarios/linux/handlers/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Populate instance config dict Linux 3 | ansible.builtin.set_fact: 4 | instance_conf_dict: 5 | instance: "{{ instance_info.name }}" 6 | address: 7 | "{{ instance_info.networkInterfaces.0.accessConfigs.0.natIP if molecule_yml.driver.external_access else instance_info.networkInterfaces.0.networkIP 8 | }}" 9 | user: "{{ lookup('env', 'USER') }}" 10 | port: "22" 11 | identity_file: "{{ ssh_identity_file }}" 12 | instance_os_type: "{{ molecule_yml.driver.instance_os_type }}" 13 | loop: "{{ server.results }}" 14 | loop_control: 15 | loop_var: instance_info 16 | no_log: true 17 | register: instance_conf_dict 18 | 19 | - name: Populate instance config dict Windows 20 | ansible.builtin.set_fact: 21 | instance_conf_dict: 22 | instance: "{{ instance_info.name }}" 23 | address: 24 | "{{ instance_info.networkInterfaces.0.accessConfigs.0.natIP if molecule_yml.driver.external_access else instance_info.networkInterfaces.0.networkIP 25 | }}" 26 | user: molecule_usr 27 | password: "{{ instance_info.password }}" 28 | port: "{{ instance_info.winrm_port | default(5986) }}" 29 | winrm_transport: "{{ molecule_yml.driver.winrm_transport | default('ntlm') }}" 30 | winrm_server_cert_validation: "{{ molecule_yml.driver.winrm_server_cert_validation | default('ignore') }}" 31 | instance_os_type: "{{ molecule_yml.driver.instance_os_type }}" 32 | loop: "{{ win_instances }}" 33 | loop_control: 34 | loop_var: instance_info 35 | no_log: true 36 | register: instance_conf_dict 37 | 38 | - name: Wipe out instance config 39 | ansible.builtin.set_fact: 40 | instance_conf: {} 41 | - name: Convert instance config dict to a list 42 | ansible.builtin.set_fact: 43 | instance_conf: "{{ instance_conf_dict.results | map(attribute='ansible_facts.instance_conf_dict') | list }}" 44 | 45 | - name: Dump instance config 46 | ansible.builtin.copy: 47 | content: "{{ instance_conf }}" 48 | dest: "{{ molecule_instance_config }}" 49 | mode: "0600" 50 | -------------------------------------------------------------------------------- /test/gce/scenarios/linux/molecule.yml: -------------------------------------------------------------------------------- 1 | --- 2 | dependency: 3 | name: galaxy 4 | driver: 5 | name: gce 6 | project_id: change-to-id-of-the-gcp-project # if not set, will default to env GCE_PROJECT_ID 7 | auth_kind: # set to machineaccount or serviceaccount or application - if set to null will read env GCP_AUTH_KIND 8 | service_account_email: # set to an email associated with the project - if set to null, will default to GCP_SERVICE_ACCOUNT_EMAIL. Should not be set if using auth_kind serviceaccount. 9 | service_account_file: # set to the path to the JSON credentials file - if set to null, will default to env GCP_SERVICE_ACCOUNT_FILE 10 | region: us-west1 # REQUIRED. example: us-central1 11 | external_access: false # chose whether to create a public IP for the VM or not - default is private IP only 12 | instance_os_type: linux # will be considered linux by default, but can be explicitely set to windows 13 | platforms: 14 | - name: linuxgce-createdbymolecule # is an instance name 15 | machine_type: n1-standard-1 # define your machine type 16 | zone: # example: us-west1-b, will default to zone b of driver.region 17 | provisioner: 18 | name: ansible 19 | verifier: 20 | name: ansible 21 | -------------------------------------------------------------------------------- /test/gce/scenarios/linux/prepare.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Prepare 3 | hosts: all 4 | gather_facts: false 5 | tasks: 6 | - name: Wait 600 seconds for target connection to become reachable/usable 7 | ansible.builtin.wait_for_connection: 8 | -------------------------------------------------------------------------------- /test/gce/scenarios/linux/requirements.yml: -------------------------------------------------------------------------------- 1 | collections: 2 | - name: google.cloud 3 | -------------------------------------------------------------------------------- /test/gce/scenarios/linux/tasks/create_linux_instance.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Create ssh keypair 3 | community.crypto.openssh_keypair: 4 | comment: "{{ lookup('env', 'USER') }} user for Molecule" 5 | path: "{{ ssh_identity_file }}" 6 | register: keypair 7 | 8 | - name: Create molecule Linux instance(s) 9 | google.cloud.gcp_compute_instance: 10 | state: present 11 | name: "{{ item.name }}" 12 | machine_type: "{{ item.machine_type | default('n1-standard-1') }}" 13 | metadata: 14 | ssh-keys: "{{ lookup('env', 'USER') }}:{{ keypair.public_key }}" 15 | disks: 16 | - auto_delete: true 17 | boot: true 18 | initialize_params: 19 | disk_size_gb: "{{ item.disk_size_gb | default(omit) }}" 20 | source_image: "{{ item.image | default('projects/debian-cloud/global/images/family/debian-10') }}" 21 | source_image_encryption_key: 22 | raw_key: "{{ item.image_encryption_key | default(omit) }}" 23 | network_interfaces: 24 | "{{ [ { 'network': my_network.resources.0 | default(omit), 'subnetwork': my_subnetwork.resources.0 | default(omit) } | combine(external_access_config 25 | | default([]) ) ] }}" 26 | zone: "{{ item.zone | default(molecule_yml.driver.region + '-b') }}" 27 | project: "{{ gcp_project_id }}" 28 | scopes: "{{ molecule_yml.driver.scopes | default(['https://www.googleapis.com/auth/compute'], True) }}" 29 | service_account_email: "{{ molecule_yml.driver.service_account_email | default(omit, true) }}" 30 | service_account_file: "{{ molecule_yml.driver.service_account_file | default(omit, true) }}" 31 | auth_kind: "{{ molecule_yml.driver.auth_kind | default(omit, true) }}" 32 | register: async_results 33 | loop: "{{ molecule_yml.platforms }}" 34 | loop_control: 35 | pause: 3 36 | async: 7200 37 | poll: 0 38 | 39 | - name: Wait for instance(s) creation to complete 40 | ansible.builtin.async_status: 41 | jid: "{{ item.ansible_job_id }}" 42 | loop: "{{ async_results.results }}" 43 | register: server 44 | until: server.finished 45 | retries: 300 46 | delay: 10 47 | notify: 48 | - Populate instance config dict Linux 49 | - Convert instance config dict to a list 50 | - Dump instance config 51 | 52 | - name: Wait for SSH 53 | ansible.builtin.wait_for: 54 | port: 22 55 | host: "{{ item.networkInterfaces.0.accessConfigs.0.natIP if molecule_yml.driver.external_access else item.networkInterfaces.0.networkIP }}" 56 | search_regex: SSH 57 | delay: 10 58 | loop: "{{ server.results }}" 59 | -------------------------------------------------------------------------------- /test/gce/scenarios/linux/tasks/create_windows_instance.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Create molecule Windows instance(s) 3 | google.cloud.gcp_compute_instance: 4 | state: present 5 | name: "{{ item.name }}" 6 | machine_type: "{{ item.machine_type | default('n1-standard-1') }}" 7 | disks: 8 | - auto_delete: true 9 | boot: true 10 | initialize_params: 11 | disk_size_gb: "{{ item.disk_size_gb | default(omit) }}" 12 | source_image: "{{ item.image | default('projects/windows-cloud/global/images/family/windows-2019') }}" 13 | source_image_encryption_key: 14 | raw_key: "{{ item.image_encryption_key | default(omit) }}" 15 | network_interfaces: 16 | "{{ [ { 'network': my_network.resources.0 | default(omit), 'subnetwork': my_subnetwork.resources.0 | default(omit) } | combine(external_access_config 17 | | default([])) ] }}" 18 | zone: "{{ item.zone | default(molecule_yml.driver.region + '-b') }}" 19 | project: "{{ gcp_project_id }}" 20 | scopes: "{{ molecule_yml.driver.scopes | default(['https://www.googleapis.com/auth/compute'], True) }}" 21 | service_account_email: "{{ molecule_yml.driver.service_account_email | default(omit, true) }}" 22 | service_account_file: "{{ molecule_yml.driver.service_account_file | default(omit, true) }}" 23 | auth_kind: "{{ molecule_yml.driver.auth_kind | default(omit, true) }}" 24 | register: async_results 25 | loop: "{{ molecule_yml.platforms }}" 26 | async: 7200 27 | poll: 0 28 | 29 | - name: Wait for instance(s) creation to complete 30 | ansible.builtin.async_status: 31 | jid: "{{ item.ansible_job_id }}" 32 | loop: "{{ async_results.results }}" 33 | register: server 34 | until: server.finished 35 | retries: 300 36 | delay: 10 37 | notify: 38 | - Populate instance config dict Windows 39 | - Convert instance config dict to a list 40 | - Dump instance config 41 | 42 | - name: Wait for WinRM 43 | ansible.builtin.wait_for: 44 | port: 5986 45 | host: "{{ item.networkInterfaces.0.accessConfigs.0.natIP if molecule_yml.driver.external_access else item.networkInterfaces.0.networkIP }}" 46 | delay: 10 47 | loop: "{{ server.results }}" 48 | 49 | - name: Prepare Windows User 50 | ansible.builtin.script: 51 | ./files/windows_auth.py --instance {{ item.name }} --zone {{ item.zone | default(molecule_yml.driver.region + '-b') }} --project {{ gcp_project_id 52 | }} --username molecule_usr 53 | args: 54 | executable: python3 55 | loop: "{{ molecule_yml.platforms }}" 56 | changed_when: 57 | - password.rc == 0 58 | - password.stdout 59 | register: password 60 | retries: 10 61 | delay: 10 62 | 63 | - name: Add password for instances in server list 64 | ansible.builtin.set_fact: 65 | win_instances: "{{ win_instances | default([]) + [dict(item[0], password=item[1].stdout_lines | last)] }}" 66 | loop: "{{ server.results | zip(password.results) | list }}" 67 | no_log: true 68 | -------------------------------------------------------------------------------- /test/gce/scenarios/linux/verify.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # This is an example playbook to execute Ansible tests. 3 | 4 | - name: Verify 5 | hosts: all 6 | gather_facts: false 7 | tasks: 8 | - name: Example assertion 9 | ansible.builtin.assert: 10 | that: true 11 | -------------------------------------------------------------------------------- /test/gce/scenarios/windows/INSTALL.md: -------------------------------------------------------------------------------- 1 | # Google Cloud Engine driver installation guide 2 | 3 | ## Requirements 4 | 5 | - A GCE credentials rc file 6 | 7 | ## Install 8 | 9 | Please refer to the [Virtual environment][] documentation for 10 | installation best practices. If not using a virtual environment, please 11 | consider passing the widely recommended ['--user' flag][] when invoking 12 | `pip`. 13 | 14 | ```bash 15 | $ pip install 'molecule_gce' 16 | ``` 17 | 18 | [Virtual environment]: https://virtualenv.pypa.io/en/latest/ 19 | ['--user' flag]: https://packaging.python.org/tutorials/installing-packages/#installing-to-the-user-site 20 | -------------------------------------------------------------------------------- /test/gce/scenarios/windows/converge.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Converge 3 | hosts: all 4 | tasks: 5 | - name: Include testrole 6 | ansible.builtin.include_role: 7 | name: testrole 8 | -------------------------------------------------------------------------------- /test/gce/scenarios/windows/create.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Create 3 | hosts: localhost 4 | connection: local 5 | gather_facts: false 6 | no_log: "{{ molecule_no_log }}" 7 | vars: 8 | ssh_identity_file: "{{ lookup('env', 'MOLECULE_EPHEMERAL_DIRECTORY') }}/ssh_key" 9 | gcp_project_id: "{{ molecule_yml.driver.project_id | default(lookup('env', 'GCE_PROJECT_ID')) }}" 10 | 11 | tasks: 12 | - name: Make sure if linux or windows either specified 13 | ansible.builtin.assert: 14 | that: 15 | - molecule_yml.driver.instance_os_type | lower == "linux" or molecule_yml.driver.instance_os_type | lower == "windows" 16 | fail_msg: instance_os_type is possible only to specify linux or windows either 17 | 18 | - name: Get network info 19 | google.cloud.gcp_compute_network_info: 20 | filters: 21 | - name = "{{ molecule_yml.driver.network_name | default('default') }}" 22 | project: "{{ molecule_yml.driver.vpc_host_project | default(gcp_project_id) }}" 23 | service_account_email: "{{ molecule_yml.driver.service_account_email | default(omit, true) }}" 24 | service_account_file: "{{ molecule_yml.driver.service_account_file | default(omit, true) }}" 25 | auth_kind: "{{ molecule_yml.driver.auth_kind | default(omit, true) }}" 26 | register: my_network 27 | 28 | - name: Get subnetwork info 29 | google.cloud.gcp_compute_subnetwork_info: 30 | filters: 31 | - name = "{{ molecule_yml.driver.subnetwork_name | default('default') }}" 32 | project: "{{ molecule_yml.driver.vpc_host_project | default(gcp_project_id) }}" 33 | region: "{{ molecule_yml.driver.region }}" 34 | service_account_email: "{{ molecule_yml.driver.service_account_email | default(omit, true) }}" 35 | service_account_file: "{{ molecule_yml.driver.service_account_file | default(omit, true) }}" 36 | auth_kind: "{{ molecule_yml.driver.auth_kind | default(omit, true) }}" 37 | register: my_subnetwork 38 | 39 | - name: Set external access config 40 | ansible.builtin.set_fact: 41 | external_access_config: 42 | - access_configs: 43 | - name: External NAT 44 | type: ONE_TO_NAT 45 | when: molecule_yml.driver.external_access 46 | 47 | - name: Include create_linux_instance tasks 48 | ansible.builtin.include_tasks: tasks/create_linux_instance.yml 49 | when: 50 | - molecule_yml.driver.instance_os_type | lower == "linux" 51 | 52 | - name: Include create_windows_instance tasks 53 | ansible.builtin.include_tasks: tasks/create_windows_instance.yml 54 | when: 55 | - molecule_yml.driver.instance_os_type | lower == "windows" 56 | 57 | handlers: 58 | - name: Import main handler tasks 59 | ansible.builtin.import_tasks: handlers/main.yml 60 | -------------------------------------------------------------------------------- /test/gce/scenarios/windows/destroy.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Destroy 3 | hosts: localhost 4 | connection: local 5 | gather_facts: false 6 | no_log: "{{ molecule_no_log }}" 7 | 8 | tasks: 9 | - name: Destroy molecule instance(s) 10 | google.cloud.gcp_compute_instance: 11 | name: "{{ item.name }}" 12 | state: absent 13 | zone: "{{ item.zone | default(molecule_yml.driver.region + '-b') }}" 14 | project: "{{ molecule_yml.driver.project_id | default(lookup('env', 'GCE_PROJECT_ID')) }}" 15 | scopes: "{{ molecule_yml.driver.scopes | default(['https://www.googleapis.com/auth/compute'], True) }}" 16 | service_account_email: "{{ molecule_yml.driver.service_account_email | default(omit, true) }}" 17 | service_account_file: "{{ molecule_yml.driver.service_account_file | default(omit, true) }}" 18 | auth_kind: "{{ molecule_yml.driver.auth_kind | default(omit, true) }}" 19 | register: async_results 20 | loop: "{{ molecule_yml.platforms }}" 21 | async: 7200 22 | poll: 0 23 | notify: 24 | - Wipe out instance config 25 | - Dump instance config 26 | 27 | - name: Wait for instance(s) deletion to complete 28 | ansible.builtin.async_status: 29 | jid: "{{ item.ansible_job_id }}" 30 | register: server 31 | until: server.finished 32 | retries: 300 33 | delay: 10 34 | loop: "{{ async_results.results }}" 35 | 36 | handlers: 37 | - name: Import main handler tasks 38 | ansible.builtin.import_tasks: handlers/main.yml 39 | -------------------------------------------------------------------------------- /test/gce/scenarios/windows/handlers/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Populate instance config dict Linux 3 | ansible.builtin.set_fact: 4 | instance_conf_dict: 5 | instance: "{{ instance_info.name }}" 6 | address: 7 | "{{ instance_info.networkInterfaces.0.accessConfigs.0.natIP if molecule_yml.driver.external_access else instance_info.networkInterfaces.0.networkIP 8 | }}" 9 | user: "{{ lookup('env', 'USER') }}" 10 | port: "22" 11 | identity_file: "{{ ssh_identity_file }}" 12 | instance_os_type: "{{ molecule_yml.driver.instance_os_type }}" 13 | loop: "{{ server.results }}" 14 | loop_control: 15 | loop_var: instance_info 16 | no_log: true 17 | register: instance_conf_dict 18 | 19 | - name: Populate instance config dict Windows 20 | ansible.builtin.set_fact: 21 | instance_conf_dict: 22 | instance: "{{ instance_info.name }}" 23 | address: 24 | "{{ instance_info.networkInterfaces.0.accessConfigs.0.natIP if molecule_yml.driver.external_access else instance_info.networkInterfaces.0.networkIP 25 | }}" 26 | user: molecule_usr 27 | password: "{{ instance_info.password }}" 28 | port: "{{ instance_info.winrm_port | default(5986) }}" 29 | winrm_transport: "{{ molecule_yml.driver.winrm_transport | default('ntlm') }}" 30 | winrm_server_cert_validation: "{{ molecule_yml.driver.winrm_server_cert_validation | default('ignore') }}" 31 | instance_os_type: "{{ molecule_yml.driver.instance_os_type }}" 32 | loop: "{{ win_instances }}" 33 | loop_control: 34 | loop_var: instance_info 35 | no_log: true 36 | register: instance_conf_dict 37 | 38 | - name: Wipe out instance config 39 | ansible.builtin.set_fact: 40 | instance_conf: {} 41 | - name: Convert instance config dict to a list 42 | ansible.builtin.set_fact: 43 | instance_conf: "{{ instance_conf_dict.results | map(attribute='ansible_facts.instance_conf_dict') | list }}" 44 | 45 | - name: Dump instance config 46 | ansible.builtin.copy: 47 | content: "{{ instance_conf }}" 48 | dest: "{{ molecule_instance_config }}" 49 | mode: "0600" 50 | -------------------------------------------------------------------------------- /test/gce/scenarios/windows/molecule.yml: -------------------------------------------------------------------------------- 1 | --- 2 | dependency: 3 | name: galaxy 4 | driver: 5 | name: gce 6 | project_id: change-to-id-of-the-gcp-project # if not set, will default to env GCE_PROJECT_ID 7 | auth_kind: # set to machineaccount or serviceaccount or application - if set to null will read env GCP_AUTH_KIND 8 | service_account_email: # set to an email associated with the project - if set to null, will default to GCP_SERVICE_ACCOUNT_EMAIL. Should not be set if using auth_kind serviceaccount. 9 | service_account_file: # set to the path to the JSON credentials file - if set to null, will default to env GCP_SERVICE_ACCOUNT_FILE 10 | region: us-west1 # REQUIRED. example: us-central1 11 | external_access: false # chose whether to create a public IP for the VM or not - default is private IP only 12 | instance_os_type: windows # will be considered linux by default, but can be explicitely set to windows 13 | platforms: 14 | - name: linuxgce-createdbymolecule # is an instance name 15 | machine_type: n1-standard-1 # define your machine type 16 | zone: # example: us-west1-b, will default to zone b of driver.region 17 | provisioner: 18 | name: ansible 19 | verifier: 20 | name: ansible 21 | -------------------------------------------------------------------------------- /test/gce/scenarios/windows/prepare.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Prepare 3 | hosts: all 4 | gather_facts: false 5 | tasks: 6 | - name: Wait 600 seconds for target connection to become reachable/usable 7 | ansible.builtin.wait_for_connection: 8 | -------------------------------------------------------------------------------- /test/gce/scenarios/windows/requirements.yml: -------------------------------------------------------------------------------- 1 | collections: 2 | - name: google.cloud 3 | source: https://galaxy.ansible.com 4 | -------------------------------------------------------------------------------- /test/gce/scenarios/windows/tasks/create_linux_instance.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Create ssh keypair 3 | community.crypto.openssh_keypair: 4 | comment: "{{ lookup('env', 'USER') }} user for Molecule" 5 | path: "{{ ssh_identity_file }}" 6 | register: keypair 7 | 8 | - name: Create molecule Linux instance(s) 9 | google.cloud.gcp_compute_instance: 10 | state: present 11 | name: "{{ item.name }}" 12 | machine_type: "{{ item.machine_type | default('n1-standard-1') }}" 13 | metadata: 14 | ssh-keys: "{{ lookup('env', 'USER') }}:{{ keypair.public_key }}" 15 | disks: 16 | - auto_delete: true 17 | boot: true 18 | initialize_params: 19 | disk_size_gb: "{{ item.disk_size_gb | default(omit) }}" 20 | source_image: "{{ item.image | default('projects/debian-cloud/global/images/family/debian-10') }}" 21 | source_image_encryption_key: 22 | raw_key: "{{ item.image_encryption_key | default(omit) }}" 23 | network_interfaces: 24 | "{{ [ { 'network': my_network.resources.0 | default(omit), 'subnetwork': my_subnetwork.resources.0 | default(omit) } | combine(external_access_config 25 | | default([]) ) ] }}" 26 | zone: "{{ item.zone | default(molecule_yml.driver.region + '-b') }}" 27 | project: "{{ gcp_project_id }}" 28 | scopes: "{{ molecule_yml.driver.scopes | default(['https://www.googleapis.com/auth/compute'], True) }}" 29 | service_account_email: "{{ molecule_yml.driver.service_account_email | default(omit, true) }}" 30 | service_account_file: "{{ molecule_yml.driver.service_account_file | default(omit, true) }}" 31 | auth_kind: "{{ molecule_yml.driver.auth_kind | default(omit, true) }}" 32 | register: async_results 33 | loop: "{{ molecule_yml.platforms }}" 34 | loop_control: 35 | pause: 3 36 | async: 7200 37 | poll: 0 38 | 39 | - name: Wait for instance(s) creation to complete 40 | ansible.builtin.async_status: 41 | jid: "{{ item.ansible_job_id }}" 42 | loop: "{{ async_results.results }}" 43 | register: server 44 | until: server.finished 45 | retries: 300 46 | delay: 10 47 | notify: 48 | - Populate instance config dict Linux 49 | - Convert instance config dict to a list 50 | - Dump instance config 51 | 52 | - name: Wait for SSH 53 | ansible.builtin.wait_for: 54 | port: 22 55 | host: "{{ item.networkInterfaces.0.accessConfigs.0.natIP if molecule_yml.driver.external_access else item.networkInterfaces.0.networkIP }}" 56 | search_regex: SSH 57 | delay: 10 58 | loop: "{{ server.results }}" 59 | -------------------------------------------------------------------------------- /test/gce/scenarios/windows/tasks/create_windows_instance.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Create molecule Windows instance(s) 3 | google.cloud.gcp_compute_instance: 4 | state: present 5 | name: "{{ item.name }}" 6 | machine_type: "{{ item.machine_type | default('n1-standard-1') }}" 7 | disks: 8 | - auto_delete: true 9 | boot: true 10 | initialize_params: 11 | disk_size_gb: "{{ item.disk_size_gb | default(omit) }}" 12 | source_image: "{{ item.image | default('projects/windows-cloud/global/images/family/windows-2019') }}" 13 | source_image_encryption_key: 14 | raw_key: "{{ item.image_encryption_key | default(omit) }}" 15 | network_interfaces: 16 | "{{ [ { 'network': my_network.resources.0 | default(omit), 'subnetwork': my_subnetwork.resources.0 | default(omit) } | combine(external_access_config 17 | | default([])) ] }}" 18 | zone: "{{ item.zone | default(molecule_yml.driver.region + '-b') }}" 19 | project: "{{ gcp_project_id }}" 20 | scopes: "{{ molecule_yml.driver.scopes | default(['https://www.googleapis.com/auth/compute'], True) }}" 21 | service_account_email: "{{ molecule_yml.driver.service_account_email | default(omit, true) }}" 22 | service_account_file: "{{ molecule_yml.driver.service_account_file | default(omit, true) }}" 23 | auth_kind: "{{ molecule_yml.driver.auth_kind | default(omit, true) }}" 24 | register: async_results 25 | loop: "{{ molecule_yml.platforms }}" 26 | async: 7200 27 | poll: 0 28 | 29 | - name: Wait for instance(s) creation to complete 30 | ansible.builtin.async_status: 31 | jid: "{{ item.ansible_job_id }}" 32 | loop: "{{ async_results.results }}" 33 | register: server 34 | until: server.finished 35 | retries: 300 36 | delay: 10 37 | notify: 38 | - Populate instance config dict Windows 39 | - Convert instance config dict to a list 40 | - Dump instance config 41 | 42 | - name: Wait for WinRM 43 | ansible.builtin.wait_for: 44 | port: 5986 45 | host: "{{ item.networkInterfaces.0.accessConfigs.0.natIP if molecule_yml.driver.external_access else item.networkInterfaces.0.networkIP }}" 46 | delay: 10 47 | loop: "{{ server.results }}" 48 | 49 | - name: Prepare Windows User 50 | ansible.builtin.script: 51 | ./files/windows_auth.py --instance {{ item.name }} --zone {{ item.zone | default(molecule_yml.driver.region + '-b') }} --project {{ gcp_project_id 52 | }} --username molecule_usr 53 | args: 54 | executable: python3 55 | loop: "{{ molecule_yml.platforms }}" 56 | changed_when: 57 | - password.rc == 0 58 | - password.stdout 59 | register: password 60 | retries: 10 61 | delay: 10 62 | 63 | - name: Add password for instances in server list 64 | ansible.builtin.set_fact: 65 | win_instances: "{{ win_instances | default([]) + [dict(item[0], password=item[1].stdout_lines | last)] }}" 66 | loop: "{{ server.results | zip(password.results) | list }}" 67 | no_log: true 68 | -------------------------------------------------------------------------------- /test/gce/scenarios/windows/verify.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # This is an example playbook to execute Ansible tests. 3 | 4 | - name: Verify 5 | hosts: all 6 | gather_facts: false 7 | tasks: 8 | - name: Example assertion 9 | ansible.builtin.assert: 10 | that: true 11 | -------------------------------------------------------------------------------- /test/gce/test_driver.py: -------------------------------------------------------------------------------- 1 | from molecule import api 2 | 3 | 4 | def test_gce_driver_is_detected(): 5 | drivers = [str(d) for d in api.drivers()] 6 | assert "gce" in drivers 7 | -------------------------------------------------------------------------------- /test/openstack/.ansible-lint: -------------------------------------------------------------------------------- 1 | # ansible-lint config for functional testing, used to bypass expected metadata 2 | # errors in molecule-generated roles. Loaded via the metadata_lint_update 3 | # pytest helper. For reference, see "E7xx - metadata" in: 4 | # https://docs.ansible.com/ansible-lint/rules/default_rules.html 5 | skip_list: 6 | # metadata/701 - Role info should contain platforms 7 | - '701' 8 | # metadata/703 - Should change default metadata: " 9 | - '703' 10 | -------------------------------------------------------------------------------- /test/openstack/__init__.py: -------------------------------------------------------------------------------- 1 | """Driver tests.""" 2 | -------------------------------------------------------------------------------- /test/openstack/conftest.py: -------------------------------------------------------------------------------- 1 | """Pytest Fixtures.""" 2 | 3 | from conftest import random_string, temp_dir # noqa 4 | 5 | import pytest 6 | 7 | 8 | @pytest.fixture() 9 | def DRIVER(): 10 | """Return name of the driver to be tested.""" 11 | return "openstack" 12 | -------------------------------------------------------------------------------- /test/openstack/scenarios/molecule/default/converge.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Converge 3 | hosts: all 4 | gather_facts: false 5 | become: true 6 | tasks: 7 | - name: Sample task # noqa command-instead-of-shell 8 | ansible.builtin.shell: 9 | cmd: uname 10 | changed_when: false 11 | -------------------------------------------------------------------------------- /test/openstack/scenarios/molecule/default/molecule.yml: -------------------------------------------------------------------------------- 1 | --- 2 | dependency: 3 | name: galaxy 4 | driver: 5 | name: openstack 6 | platforms: 7 | - name: ubuntu2004 8 | flavor: m1.small 9 | image: Ubuntu_22.04 10 | user: ubuntu 11 | provisioner: 12 | name: ansible 13 | -------------------------------------------------------------------------------- /test/openstack/scenarios/molecule/multiple/converge.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Converge 3 | hosts: all 4 | gather_facts: false 5 | become: true 6 | tasks: 7 | - name: Sample task # noqa command-instead-of-shell 8 | ansible.builtin.shell: 9 | cmd: uname 10 | changed_when: false 11 | -------------------------------------------------------------------------------- /test/openstack/scenarios/molecule/multiple/molecule.yml: -------------------------------------------------------------------------------- 1 | --- 2 | dependency: 3 | name: galaxy 4 | driver: 5 | name: openstack 6 | platforms: 7 | - name: ubuntu2004 8 | flavor: m1.small 9 | image: Ubuntu_22.04 10 | user: ubuntu 11 | - name: debian10 12 | flavor: m1.small 13 | image: Debian_10 14 | user: debian 15 | provisioner: 16 | name: ansible 17 | -------------------------------------------------------------------------------- /test/openstack/scenarios/molecule/network/converge.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Converge 3 | hosts: all 4 | gather_facts: false 5 | become: true 6 | tasks: 7 | - name: Sample task # noqa command-instead-of-shell 8 | ansible.builtin.shell: 9 | cmd: uname 10 | changed_when: false 11 | 12 | - name: Get all instances 13 | ansible.builtin.slurp: 14 | src: /tmp/instances 15 | register: instances_base64 16 | 17 | - name: Ping all # noqa command-instead-of-shell 18 | ansible.builtin.shell: 19 | cmd: "ping -c 2 {{ item }}" 20 | changed_when: false 21 | loop: "{{ instances_base64.content | b64decode }}" 22 | -------------------------------------------------------------------------------- /test/openstack/scenarios/molecule/network/molecule.yml: -------------------------------------------------------------------------------- 1 | --- 2 | dependency: 3 | name: galaxy 4 | driver: 5 | name: openstack 6 | platforms: 7 | - name: debian10 8 | flavor: m1.small 9 | image: Debian_10 10 | user: debian 11 | security_group: 12 | name: molecule 13 | description: Molecule test 14 | rules: 15 | - proto: tcp 16 | port: 22 17 | cidr: 0.0.0.0/0 18 | - proto: icmp 19 | port: -1 20 | cidr: 0.0.0.0/0 21 | network: 22 | name: molecule-test 23 | router: 24 | name: router1 25 | ext_network: public 26 | subnet: subnet1 27 | subnet: 28 | name: subnet1 29 | cidr: 192.168.11.0/24 30 | ipv: 4 31 | dns_nameservers: 32 | - 8.8.8.8 33 | 34 | - name: ubuntu2004 35 | flavor: m1.small 36 | image: Ubuntu_20.04 37 | user: ubuntu 38 | network: 39 | name: molecule 40 | create: false 41 | security_group: 42 | name: molecule-sec 43 | description: Molecule test 2 44 | rules: 45 | - proto: tcp 46 | port_min: 22 47 | port_max: 80 48 | cidr: 0.0.0.0/0 49 | - proto: icmp 50 | port: -1 51 | cidr: 0.0.0.0/0 52 | - proto: tcp 53 | port: 22 54 | type: IPv6 55 | cidr: ::/0 56 | 57 | - name: ubuntu2204 58 | flavor: m1.small 59 | image: Ubuntu_22.04 60 | user: ubuntu 61 | security_group: 62 | name: molecule 63 | network: 64 | name: molecule-test 65 | provisioner: 66 | name: ansible 67 | -------------------------------------------------------------------------------- /test/openstack/scenarios/molecule/security_group/converge.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Converge 3 | hosts: all 4 | gather_facts: false 5 | become: true 6 | tasks: 7 | - name: Sample task # noqa command-instead-of-shell 8 | ansible.builtin.shell: 9 | cmd: uname 10 | changed_when: false 11 | -------------------------------------------------------------------------------- /test/openstack/scenarios/molecule/security_group/molecule.yml: -------------------------------------------------------------------------------- 1 | --- 2 | dependency: 3 | name: galaxy 4 | driver: 5 | name: openstack 6 | platforms: 7 | - name: debian10 8 | flavor: m1.small 9 | image: Debian_10 10 | user: debian 11 | security_group: 12 | name: molecule 13 | description: Molecule test 14 | rules: 15 | - proto: tcp 16 | port: 22 17 | cidr: 0.0.0.0/0 18 | - proto: icmp 19 | port: -1 20 | cidr: 0.0.0.0/0 21 | - name: ubuntu2004 22 | flavor: m1.small 23 | image: Ubuntu_20.04 24 | user: ubuntu 25 | security_group: 26 | name: molecule-sec 27 | description: Molecule test 2 28 | rules: 29 | - proto: tcp 30 | port_min: 22 31 | port_max: 80 32 | cidr: 0.0.0.0/0 33 | - proto: icmp 34 | port: -1 35 | cidr: 0.0.0.0/0 36 | - proto: tcp 37 | port: 22 38 | type: IPv6 39 | cidr: ::/0 40 | - name: ubuntu2204 41 | flavor: m1.small 42 | image: Ubuntu_22.04 43 | user: ubuntu 44 | security_group: 45 | name: test 46 | create: false 47 | provisioner: 48 | name: ansible 49 | -------------------------------------------------------------------------------- /test/openstack/scenarios/molecule/volume/converge.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Converge 3 | hosts: all 4 | gather_facts: false 5 | become: true 6 | tasks: 7 | - name: Sample task # noqa command-instead-of-shell 8 | ansible.builtin.shell: 9 | cmd: uname 10 | changed_when: false 11 | -------------------------------------------------------------------------------- /test/openstack/scenarios/molecule/volume/molecule.yml: -------------------------------------------------------------------------------- 1 | --- 2 | dependency: 3 | name: galaxy 4 | driver: 5 | name: openstack 6 | platforms: 7 | - name: debian10 8 | flavor: m1.tiny 9 | image: Debian_10 10 | user: debian 11 | volume: 12 | size: 10 13 | 14 | - name: ubuntu2004 15 | flavor: m1.tiny 16 | image: Ubuntu_20.04 17 | user: ubuntu 18 | volume: 19 | size: 15 20 | 21 | provisioner: 22 | name: ansible 23 | -------------------------------------------------------------------------------- /test/openstack/test_driver.py: -------------------------------------------------------------------------------- 1 | """Unit tests.""" 2 | 3 | from molecule import api 4 | 5 | 6 | def test_driver_is_detected(DRIVER): 7 | """Asserts that molecule recognizes the driver.""" 8 | assert DRIVER in [str(d) for d in api.drivers()] 9 | -------------------------------------------------------------------------------- /test/openstack/test_func.py: -------------------------------------------------------------------------------- 1 | """Functional tests.""" 2 | 3 | import os 4 | import pathlib 5 | import shutil 6 | import subprocess 7 | from pathlib import Path 8 | 9 | import pytest 10 | 11 | import openstack 12 | from conftest import change_dir_to 13 | from molecule import logger 14 | from molecule.app import get_app 15 | 16 | LOG = logger.get_logger(__name__) 17 | 18 | 19 | def is_openstack_auth() -> bool: 20 | """Is the openstack authentication config in place?""" 21 | 22 | try: 23 | conn = openstack.connect() 24 | list(conn.compute.servers()) 25 | return True 26 | except Exception: 27 | return False 28 | 29 | 30 | def format_result(result: subprocess.CompletedProcess): 31 | """Return friendly representation of completed process run.""" 32 | return ( 33 | f"RC: {result.returncode}\n" 34 | + f"STDOUT: {result.stdout}\n" 35 | + f"STDERR: {result.stderr}" 36 | ) 37 | 38 | 39 | @pytest.mark.skipif(not is_openstack_auth(), reason="Openstack authentication missing") 40 | def test_openstack_init_and_test_scenario(tmp_path: pathlib.Path, DRIVER: str) -> None: 41 | """Verify that init scenario works.""" 42 | shutil.rmtree(tmp_path, ignore_errors=True) 43 | tmp_path.mkdir(exist_ok=True) 44 | 45 | scenario_name = "default" 46 | 47 | with change_dir_to(tmp_path): 48 | scenario_directory = tmp_path / "molecule" / scenario_name 49 | cmd = [ 50 | "molecule", 51 | "init", 52 | "scenario", 53 | scenario_name, 54 | "--driver-name", 55 | DRIVER, 56 | ] 57 | result = get_app(tmp_path).run_command(cmd) 58 | assert result.returncode == 0 59 | 60 | assert scenario_directory.exists() 61 | os.unlink(os.path.join(scenario_directory, "create.yml")) 62 | os.unlink(os.path.join(scenario_directory, "destroy.yml")) 63 | 64 | confpath = os.path.join(scenario_directory, "molecule.yml") 65 | testconf = os.path.join( 66 | os.path.dirname(__file__), 67 | "scenarios/molecule", 68 | scenario_name, 69 | "molecule.yml", 70 | ) 71 | 72 | shutil.copyfile(testconf, confpath) 73 | 74 | cmd = ["molecule", "--debug", "test", "-s", scenario_name] 75 | result = get_app(tmp_path).run_command(cmd) 76 | assert result.returncode == 0 77 | 78 | 79 | @pytest.mark.skipif(not is_openstack_auth(), reason="Openstack authentication missing") 80 | @pytest.mark.parametrize( 81 | "scenario", 82 | [("multiple"), ("security_group"), ("network"), ("volume")], 83 | ) 84 | def test_specific_scenarios(temp_dir, scenario) -> None: 85 | """Verify that specific scenarios work""" 86 | scenario_directory = os.path.join(os.path.dirname(__file__), "scenarios") 87 | 88 | with change_dir_to(scenario_directory): 89 | cmd = ["molecule", "test", "--scenario-name", scenario] 90 | result = get_app(Path()).run_command(cmd) 91 | assert result.returncode == 0 92 | -------------------------------------------------------------------------------- /test/podman/__init__.py: -------------------------------------------------------------------------------- 1 | """Driver tests.""" 2 | -------------------------------------------------------------------------------- /test/podman/conftest.py: -------------------------------------------------------------------------------- 1 | """Pytest Fixtures.""" 2 | 3 | import os 4 | import platform 5 | 6 | import pytest 7 | 8 | 9 | def pytest_collection_finish(session): 10 | """Fail fast if current environment is broken.""" 11 | if "CONTAINER_HOST" in os.environ and platform.system() != "Darwin": 12 | pytest.exit( 13 | msg="CONTAINER_HOST is defined, see https://github.com/containers/podman/issues/8070", 14 | ) 15 | 16 | 17 | @pytest.fixture() 18 | def driver_name() -> str: 19 | """Return name of the driver to be tested.""" 20 | return "podman" 21 | -------------------------------------------------------------------------------- /test/podman/test_driver.py: -------------------------------------------------------------------------------- 1 | """Unit tests.""" 2 | 3 | from molecule import api 4 | from molecule_plugins.podman.driver import Podman 5 | 6 | 7 | def test_podman_driver_is_detected(): 8 | """Asserts that molecule recognizes the driver.""" 9 | assert any(str(d) == "podman" for d in api.drivers()) 10 | 11 | 12 | def test_driver_initializes_without_podman_executable(monkeypatch): 13 | """Make sure we can initiaize driver without having an executable present.""" 14 | monkeypatch.setenv("MOLECULE_PODMAN_EXECUTABLE", "bad-executable") 15 | Podman() 16 | -------------------------------------------------------------------------------- /test/podman/test_func.py: -------------------------------------------------------------------------------- 1 | """Functional tests.""" 2 | 3 | import os 4 | import pathlib 5 | import subprocess 6 | from pathlib import Path 7 | 8 | from conftest import change_dir_to 9 | from molecule import logger 10 | from molecule.app import get_app 11 | from molecule_plugins.podman import __file__ as module_file 12 | 13 | LOG = logger.get_logger(__name__) 14 | 15 | 16 | def format_result(result: subprocess.CompletedProcess): 17 | """Return friendly representation of completed process run.""" 18 | return ( 19 | f"RC: {result.returncode}\n" 20 | + f"STDOUT: {result.stdout}\n" 21 | + f"STDERR: {result.stderr}" 22 | ) 23 | 24 | 25 | def test_podman_command_init_scenario(tmp_path: pathlib.Path): 26 | """Verify that init scenario works.""" 27 | scenario_name = "default" 28 | 29 | with change_dir_to(tmp_path): 30 | scenario_directory = tmp_path / "molecule" / scenario_name 31 | cmd = [ 32 | "molecule", 33 | "init", 34 | "scenario", 35 | scenario_name, 36 | "--driver-name", 37 | "podman", 38 | ] 39 | result = get_app(tmp_path).run_command(cmd) 40 | assert result.returncode == 0 41 | 42 | assert scenario_directory.exists() 43 | 44 | # run molecule reset as this may clean some leftovers from other 45 | # test runs and also ensure that reset works. 46 | result = get_app(tmp_path).run_command( 47 | ["molecule", "reset"] 48 | ) # default sceanario 49 | assert result.returncode == 0 50 | 51 | result = get_app(tmp_path).run_command( 52 | ["molecule", "reset", "-s", scenario_name] 53 | ) 54 | assert result.returncode == 0 55 | 56 | cmd = ["molecule", "--debug", "test", "-s", scenario_name] 57 | result = get_app(tmp_path).run_command(cmd) 58 | assert result.returncode == 0 59 | 60 | 61 | def test_sample() -> None: 62 | """Runs the sample scenario present at the repository root.""" 63 | result = get_app(Path()).run_command( 64 | ["molecule", "test", "-s", "test-podman"] 65 | ) # default sceanario 66 | assert result.returncode == 0 67 | 68 | 69 | def test_dockerfile(): 70 | """Verify that our embedded dockerfile can be build.""" 71 | result = subprocess.run( 72 | ["ansible-playbook", "--version"], 73 | check=False, 74 | capture_output=True, 75 | stdin=subprocess.DEVNULL, 76 | shell=False, 77 | text=True, 78 | ) 79 | assert result.returncode == 0, result 80 | assert "ansible-playbook" in result.stdout 81 | 82 | module_path = os.path.dirname(module_file) 83 | assert os.path.isdir(module_path) 84 | env = os.environ.copy() 85 | env["ANSIBLE_FORCE_COLOR"] = "0" 86 | result = subprocess.run( 87 | ["ansible-playbook", "-i", "localhost,", "playbooks/validate-dockerfile.yml"], 88 | check=False, 89 | capture_output=True, 90 | stdin=subprocess.DEVNULL, 91 | shell=False, 92 | cwd=module_path, 93 | text=True, 94 | env=env, 95 | ) 96 | assert result.returncode == 0, format_result(result) 97 | # , result 98 | -------------------------------------------------------------------------------- /test/vagrant-plugin/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ansible-community/molecule-plugins/980db38a12043e1685dd0e91e9845f1ae684cab1/test/vagrant-plugin/__init__.py -------------------------------------------------------------------------------- /test/vagrant-plugin/functional/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ansible-community/molecule-plugins/980db38a12043e1685dd0e91e9845f1ae684cab1/test/vagrant-plugin/functional/__init__.py -------------------------------------------------------------------------------- /test/vagrant-plugin/functional/conftest.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015-2018 Cisco Systems, Inc. 2 | # Copyright (c) 2018 Red Hat, Inc. 3 | # 4 | # Permission is hereby granted, free of charge, to any person obtaining a copy 5 | # of this software and associated documentation files (the "Software"), to 6 | # deal in the Software without restriction, including without limitation the 7 | # rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 8 | # sell copies of the Software, and to permit persons to whom the Software is 9 | # furnished to do so, subject to the following conditions: 10 | # 11 | # The above copyright notice and this permission notice shall be included in 12 | # all copies or substantial portions of the Software. 13 | # 14 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 20 | # DEALINGS IN THE SOFTWARE. 21 | 22 | 23 | from conftest import * # noqa 24 | -------------------------------------------------------------------------------- /test/vagrant-plugin/functional/test_func.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015-2018 Cisco Systems, Inc. 2 | # Copyright (c) 2018 Red Hat, Inc. 3 | # 4 | # Permission is hereby granted, free of charge, to any person obtaining a copy 5 | # of this software and associated documentation files (the "Software"), to 6 | # deal in the Software without restriction, including without limitation the 7 | # rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 8 | # sell copies of the Software, and to permit persons to whom the Software is 9 | # furnished to do so, subject to the following conditions: 10 | # 11 | # The above copyright notice and this permission notice shall be included in 12 | # all copies or substantial portions of the Software. 13 | # 14 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 20 | # DEALINGS IN THE SOFTWARE. 21 | 22 | import os 23 | import platform 24 | import shutil 25 | from pathlib import Path 26 | 27 | import pytest 28 | import vagrant 29 | 30 | from conftest import change_dir_to 31 | from molecule import logger, util 32 | from molecule.app import get_app 33 | from molecule.scenario import ephemeral_directory 34 | 35 | LOG = logger.get_logger(__name__) 36 | 37 | 38 | def is_vagrant_supported() -> bool: 39 | """Return True if vagrant is installed and current platform is supported.""" 40 | if not shutil.which("vagrant"): 41 | return False 42 | if not (platform.machine() == "arm64" and platform.system() == "Darwin"): 43 | return True 44 | 45 | 46 | @pytest.mark.skipif( 47 | not is_vagrant_supported(), 48 | reason="vagrant not supported on this machine", 49 | ) 50 | def test_vagrant_command_init_scenario(temp_dir): 51 | with change_dir_to(temp_dir): 52 | os.makedirs(os.path.join(temp_dir, "molecule", "default")) 53 | scenario_directory = os.path.join(temp_dir, "molecule", "test-scenario") 54 | cmd = [ 55 | "molecule", 56 | "init", 57 | "scenario", 58 | "test-scenario", 59 | "--driver-name", 60 | "vagrant", 61 | ] 62 | result = get_app(Path()).run_command(cmd) 63 | assert result.returncode == 0 64 | 65 | assert os.path.isdir(scenario_directory) 66 | 67 | # Clean unwanted default create/destroy files from molecule init 68 | os.unlink(os.path.join(scenario_directory, "create.yml")) 69 | os.unlink(os.path.join(scenario_directory, "destroy.yml")) 70 | 71 | confpath = os.path.join(scenario_directory, "molecule.yml") 72 | conf = util.safe_load_file(confpath) 73 | env = os.environ 74 | if "TESTBOX" in env: 75 | conf["platforms"][0]["box"] = env["TESTBOX"] 76 | if "vagrant-libvirt" in [x.name for x in vagrant.Vagrant().plugin_list()]: 77 | conf["driver"]["provider"] = {"name": "libvirt"} 78 | util.write_file(confpath, util.safe_dump(conf)) 79 | cmd = ["molecule", "--debug", "test", "-s", "test-scenario"] 80 | result = get_app(Path()).run_command(cmd) 81 | assert result.returncode == 0 82 | 83 | 84 | @pytest.mark.skipif( 85 | not is_vagrant_supported(), 86 | reason="vagrant not supported on this machine", 87 | ) 88 | def test_invalid_settings(temp_dir): 89 | scenario_directory = os.path.join( 90 | os.path.dirname(util.abs_path(__file__)), 91 | os.path.pardir, 92 | "scenarios", 93 | ) 94 | 95 | with change_dir_to(scenario_directory): 96 | cmd = ["molecule", "create", "--scenario-name", "invalid"] 97 | result = get_app(Path()).run_command(cmd) 98 | assert result.returncode == 2 99 | 100 | assert "Failed to validate generated Vagrantfile" in result.stdout 101 | 102 | 103 | @pytest.mark.skipif( 104 | not is_vagrant_supported(), 105 | reason="vagrant not supported on this machine", 106 | ) 107 | @pytest.mark.parametrize( 108 | "scenario", 109 | [ 110 | ("vagrant_root"), 111 | ("config_options"), 112 | ("provider_config_options"), 113 | ("default"), 114 | ("default-compat"), 115 | ("box_url"), 116 | ("network"), 117 | ("hostname"), 118 | ], 119 | ) 120 | def test_vagrant_root(temp_dir, scenario): 121 | scenario_directory = os.path.join( 122 | os.path.dirname(util.abs_path(__file__)), 123 | os.path.pardir, 124 | "scenarios", 125 | ) 126 | 127 | with change_dir_to(scenario_directory): 128 | cmd = ["molecule", "test", "--scenario-name", scenario] 129 | result = get_app(Path()).run_command(cmd) 130 | assert result.returncode == 0 131 | 132 | 133 | @pytest.mark.skipif( 134 | not is_vagrant_supported(), 135 | reason="vagrant not supported on this machine", 136 | ) 137 | def test_multi_node(temp_dir): 138 | scenario_directory = os.path.join( 139 | os.path.dirname(util.abs_path(__file__)), 140 | os.path.pardir, 141 | "scenarios", 142 | ) 143 | 144 | with change_dir_to(scenario_directory): 145 | cmd = ["molecule", "test", "--scenario-name", "multi-node"] 146 | result = get_app(Path()).run_command(cmd) 147 | assert result.returncode == 0 148 | 149 | molecule_eph_directory = ephemeral_directory() 150 | vagrantfile = os.path.join( 151 | molecule_eph_directory, 152 | "scenarios", 153 | "multi-node", 154 | "Vagrantfile", 155 | ) 156 | with open(vagrantfile) as f: 157 | content = f.read() 158 | assert "instance-1" in content 159 | assert "instance-2" in content 160 | -------------------------------------------------------------------------------- /test/vagrant-plugin/scenarios/molecule/box_url/converge.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Converge 3 | hosts: all 4 | gather_facts: false 5 | become: true 6 | tasks: 7 | - name: Sample task # noqa command-instead-of-shell 8 | ansible.builtin.shell: 9 | cmd: uname 10 | changed_when: false 11 | -------------------------------------------------------------------------------- /test/vagrant-plugin/scenarios/molecule/box_url/molecule.yml: -------------------------------------------------------------------------------- 1 | --- 2 | dependency: 3 | name: galaxy 4 | driver: 5 | name: vagrant 6 | provider: 7 | name: libvirt 8 | platforms: 9 | - name: instance 10 | box: centos/stream9 11 | box_url: "https://cloud.centos.org/centos/9-stream/x86_64/images/CentOS-Stream-Vagrant-9-20240930.0.x86_64.vagrant-libvirt.box" 12 | box_download_checksum: "0dd55e0d6d1686181e3ad2636f942d5295be4c3d01e0e1c824fc02ec58728375" 13 | box_download_checksum_type: "sha256" 14 | provisioner: 15 | name: ansible 16 | -------------------------------------------------------------------------------- /test/vagrant-plugin/scenarios/molecule/config_options/converge.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Converge 3 | hosts: all 4 | gather_facts: false 5 | become: true 6 | tasks: 7 | - name: Sample task # noqa command-instead-of-shell 8 | ansible.builtin.shell: 9 | cmd: uname 10 | changed_when: false 11 | -------------------------------------------------------------------------------- /test/vagrant-plugin/scenarios/molecule/config_options/molecule.yml: -------------------------------------------------------------------------------- 1 | --- 2 | dependency: 3 | name: galaxy 4 | driver: 5 | name: vagrant 6 | provider: 7 | name: libvirt 8 | platforms: 9 | - name: instance 10 | config_options: 11 | synced_folder: true 12 | box: ${TESTBOX:-centos/7} 13 | instance_raw_config_args: 14 | - 'vm.synced_folder ".", "/vagrant", type: "rsync"' 15 | provisioner: 16 | name: ansible 17 | verifier: 18 | name: ansible 19 | -------------------------------------------------------------------------------- /test/vagrant-plugin/scenarios/molecule/config_options/verify.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Verify 3 | hosts: all 4 | tasks: 5 | - name: Look for /vagrant 6 | ansible.builtin.stat: 7 | path: /vagrant 8 | register: vagrantdir 9 | 10 | - name: Make sure there's a /vagrant 11 | ansible.builtin.assert: 12 | that: 13 | - vagrantdir.stat.exists | bool 14 | - vagrantdir.stat.isdir is defined 15 | - vagrantdir.stat.isdir | bool 16 | -------------------------------------------------------------------------------- /test/vagrant-plugin/scenarios/molecule/default-compat/converge.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Converge 3 | hosts: all 4 | gather_facts: false 5 | become: true 6 | tasks: 7 | - name: Sample task # noqa command-instead-of-shell 8 | ansible.builtin.command: 9 | cmd: uname 10 | changed_when: false 11 | -------------------------------------------------------------------------------- /test/vagrant-plugin/scenarios/molecule/default-compat/create.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Create 3 | hosts: localhost 4 | connection: local 5 | gather_facts: false 6 | no_log: "{{ molecule_no_log }}" 7 | tasks: 8 | - name: Create molecule instance(s) # noqa fqcn[action] 9 | vagrant: 10 | instance_name: "{{ item.name }}" 11 | instance_interfaces: "{{ item.interfaces | default(omit) }}" 12 | instance_raw_config_args: "{{ item.instance_raw_config_args | default(omit) }}" 13 | 14 | config_options: "{{ item.config_options | default(omit) }}" 15 | 16 | platform_box: "{{ item.box | default('generic/alpine316') }}" 17 | platform_box_version: "{{ item.box_version | default(omit) }}" 18 | platform_box_url: "{{ item.box_url | default(omit) }}" 19 | 20 | provider_name: "{{ molecule_yml.driver.provider.name | default(omit, true) }}" 21 | provider_memory: "{{ item.memory | default(omit) }}" 22 | provider_cpus: "{{ item.cpus | default(omit) }}" 23 | provider_options: "{{ item.provider_options | default(omit) }}" 24 | provider_raw_config_args: "{{ item.provider_raw_config_args | default(omit) }}" 25 | provider_override_args: "{{ item.provider_override_args | default(omit) }}" 26 | 27 | provision: "{{ item.provision | default(omit) }}" 28 | 29 | state: up 30 | register: server 31 | with_items: "{{ molecule_yml.platforms }}" 32 | loop_control: 33 | label: "{{ item.name }}" 34 | no_log: false 35 | 36 | # NOTE(retr0h): Vagrant/VBox sucks and parallelizing instance creation 37 | # causes issues. 38 | 39 | # Mandatory configuration for Molecule to function. 40 | 41 | - name: Create molecule instances configuration 42 | when: server is changed # noqa no-handler 43 | block: 44 | - name: Populate instance config dict 45 | ansible.builtin.set_fact: 46 | instance_conf_dict: 47 | instance: "{{ item.Host }}" 48 | address: "{{ item.HostName }}" 49 | user: "{{ item.User }}" 50 | port: "{{ item.Port }}" 51 | identity_file: "{{ item.IdentityFile }}" 52 | with_items: "{{ server.results }}" 53 | register: instance_config_dict 54 | 55 | - name: Convert instance config dict to a list 56 | ansible.builtin.set_fact: 57 | instance_conf: "{{ instance_config_dict.results | map(attribute='ansible_facts.instance_conf_dict') | list }}" 58 | 59 | - name: Dump instance config 60 | ansible.builtin.copy: 61 | content: "{{ instance_conf | to_json | from_json | to_yaml }}" 62 | dest: "{{ molecule_instance_config }}" 63 | mode: "0600" 64 | -------------------------------------------------------------------------------- /test/vagrant-plugin/scenarios/molecule/default-compat/destroy.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Destroy 3 | hosts: localhost 4 | connection: local 5 | gather_facts: false 6 | no_log: "{{ molecule_no_log }}" 7 | tasks: 8 | - name: Destroy molecule instance(s) # noqa fqcn[action] 9 | vagrant: 10 | instance_name: "{{ item.name }}" 11 | platform_box: "{{ item.box | default(omit) }}" 12 | provider_name: "{{ molecule_yml.driver.provider.name | default(omit, true) }}" 13 | provider_options: "{{ item.provider_options | default(omit) }}" 14 | provider_raw_config_args: "{{ item.provider_raw_config_args | default(omit) }}" 15 | force_stop: "{{ item.force_stop | default(true) }}" 16 | 17 | state: destroy 18 | register: server 19 | with_items: "{{ molecule_yml.platforms }}" 20 | loop_control: 21 | label: "{{ item.name }}" 22 | no_log: false 23 | 24 | # NOTE(retr0h): Vagrant/VBox sucks and parallelizing instance deletion 25 | # causes issues. 26 | 27 | # Mandatory configuration for Molecule to function. 28 | 29 | - name: Populate instance config 30 | ansible.builtin.set_fact: 31 | instance_conf: {} 32 | - name: Dump instance config # noqa no-handler 33 | ansible.builtin.copy: 34 | content: | 35 | # Molecule managed 36 | {{ instance_conf | to_json | from_json | to_yaml }} 37 | dest: "{{ molecule_instance_config }}" 38 | mode: "0600" 39 | when: server.changed | bool 40 | -------------------------------------------------------------------------------- /test/vagrant-plugin/scenarios/molecule/default-compat/molecule.yml: -------------------------------------------------------------------------------- 1 | --- 2 | dependency: 3 | name: galaxy 4 | driver: 5 | name: vagrant 6 | provider: 7 | name: libvirt 8 | platforms: 9 | - name: instance 10 | provisioner: 11 | name: ansible 12 | -------------------------------------------------------------------------------- /test/vagrant-plugin/scenarios/molecule/default/converge.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Converge 3 | hosts: all 4 | gather_facts: false 5 | become: true 6 | tasks: 7 | - name: Sample task # noqa command-instead-of-shell 8 | ansible.builtin.shell: 9 | cmd: uname 10 | changed_when: false 11 | -------------------------------------------------------------------------------- /test/vagrant-plugin/scenarios/molecule/default/molecule.yml: -------------------------------------------------------------------------------- 1 | --- 2 | dependency: 3 | name: galaxy 4 | driver: 5 | name: vagrant 6 | provider: 7 | name: libvirt 8 | platforms: 9 | - name: instance 10 | provisioner: 11 | name: ansible 12 | -------------------------------------------------------------------------------- /test/vagrant-plugin/scenarios/molecule/hostname/converge.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Converge 3 | hosts: all 4 | gather_facts: false 5 | become: true 6 | tasks: 7 | - name: Sample task # noqa command-instead-of-shell 8 | ansible.builtin.shell: 9 | cmd: uname 10 | changed_when: false 11 | -------------------------------------------------------------------------------- /test/vagrant-plugin/scenarios/molecule/hostname/molecule.yml: -------------------------------------------------------------------------------- 1 | --- 2 | dependency: 3 | name: galaxy 4 | driver: 5 | name: vagrant 6 | provider: 7 | name: libvirt 8 | platforms: 9 | - name: instance-1 10 | box: ${TESTBOX:-debian/bookworm64} 11 | memory: 256 12 | cpus: 1 13 | - name: instance-2 14 | hostname: instance.example.com 15 | box: ${TESTBOX:-debian/bookworm64} 16 | memory: 256 17 | cpus: 1 18 | - name: instance-3 19 | hostname: false 20 | box: ${TESTBOX:-debian/bookworm64} 21 | memory: 256 22 | cpus: 1 23 | provisioner: 24 | name: ansible 25 | -------------------------------------------------------------------------------- /test/vagrant-plugin/scenarios/molecule/hostname/verify.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Check instance-1 3 | hosts: instance-1 4 | gather_facts: true 5 | gather_subset: 6 | - min 7 | tasks: 8 | - name: Ensure that host name is instance-1 9 | ansible.builtin.assert: 10 | that: 11 | - ansible_fqdn == "instance-1" 12 | 13 | - name: Check instance-2 14 | hosts: instance-2 15 | gather_facts: true 16 | gather_subset: 17 | - min 18 | tasks: 19 | - name: Ensure that host name is instance.example.com 20 | ansible.builtin.assert: 21 | that: 22 | - ansible_fqdn == "instance.example.com" 23 | 24 | - name: Check instance-3 25 | hosts: instance-3 26 | gather_facts: true 27 | gather_subset: 28 | - min 29 | tasks: 30 | - name: Ensure that host name is not instance-3 31 | ansible.builtin.assert: 32 | that: 33 | - ansible_fqdn != "instance-3" 34 | -------------------------------------------------------------------------------- /test/vagrant-plugin/scenarios/molecule/invalid/converge.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Converge 3 | hosts: all 4 | gather_facts: false 5 | become: true 6 | tasks: 7 | - name: Sample task # noqa command-instead-of-shell 8 | ansible.builtin.shell: 9 | cmd: uname 10 | changed_when: false 11 | -------------------------------------------------------------------------------- /test/vagrant-plugin/scenarios/molecule/invalid/molecule.yml: -------------------------------------------------------------------------------- 1 | --- 2 | dependency: 3 | name: galaxy 4 | driver: 5 | name: vagrant 6 | provider: 7 | name: libvirt 8 | platforms: 9 | - name: instance/foo 10 | provisioner: 11 | name: ansible 12 | -------------------------------------------------------------------------------- /test/vagrant-plugin/scenarios/molecule/multi-node/converge.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Converge 3 | hosts: all 4 | gather_facts: false 5 | become: true 6 | tasks: 7 | - name: Sample task # noqa command-instead-of-shell 8 | ansible.builtin.shell: 9 | cmd: uname 10 | changed_when: false 11 | -------------------------------------------------------------------------------- /test/vagrant-plugin/scenarios/molecule/multi-node/molecule.yml: -------------------------------------------------------------------------------- 1 | --- 2 | dependency: 3 | name: galaxy 4 | driver: 5 | name: vagrant 6 | provider: 7 | name: libvirt 8 | platforms: 9 | - name: instance-1 10 | box: ${TESTBOX:-debian/bookworm64} 11 | interfaces: 12 | - network_name: private_network 13 | ip: 192.168.56.2 14 | groups: 15 | - foo 16 | - bar 17 | memory: 256 18 | cpus: 1 19 | provider_options: 20 | # using session with network leads to troubles 21 | qemu_use_session: false 22 | config_options: 23 | synced_folder: true 24 | instance_raw_config_args: 25 | - 'vm.synced_folder ".", "/vagrant", type: "rsync"' 26 | - name: instance-2 27 | box: ${TESTBOX:-debian/bookworm64} 28 | interfaces: 29 | - network_name: private_network 30 | ip: 192.168.56.3 31 | groups: 32 | - foo 33 | - baz 34 | memory: 256 35 | cpus: 2 36 | provider_options: 37 | # using session with network leads to troubles 38 | qemu_use_session: false 39 | instance_raw_config_args: 40 | - 'vm.synced_folder ".", "/vagrant", type: "rsync"' 41 | provisioner: 42 | name: ansible 43 | -------------------------------------------------------------------------------- /test/vagrant-plugin/scenarios/molecule/multi-node/verify.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Check instance-1 3 | hosts: instance-1 4 | gather_facts: false 5 | become: true 6 | tasks: 7 | - name: Ping instance-2 # noqa command-instead-of-shell 8 | ansible.builtin.shell: 9 | cmd: ping -c3 192.168.56.3 10 | changed_when: false 11 | 12 | - name: Change instance-2 13 | hosts: instance-2 14 | gather_facts: false 15 | become: true 16 | tasks: 17 | - name: Ping instance-1 # noqa command-instead-of-shell 18 | ansible.builtin.shell: 19 | cmd: ping -c3 192.168.56.2 20 | changed_when: false 21 | -------------------------------------------------------------------------------- /test/vagrant-plugin/scenarios/molecule/network/converge.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Converge 3 | hosts: all 4 | gather_facts: false 5 | become: true 6 | tasks: 7 | - name: Sample task # noqa command-instead-of-shell 8 | ansible.builtin.shell: 9 | cmd: uname 10 | changed_when: false 11 | -------------------------------------------------------------------------------- /test/vagrant-plugin/scenarios/molecule/network/molecule.yml: -------------------------------------------------------------------------------- 1 | --- 2 | dependency: 3 | name: galaxy 4 | driver: 5 | name: vagrant 6 | provider: 7 | name: libvirt 8 | platforms: 9 | - name: instance 10 | box: ${TESTBOX:-centos/7} 11 | provider_options: 12 | # using session with network leads to troubles 13 | qemu_use_session: false 14 | interfaces: 15 | - network_name: private_network 16 | ip: 192.168.56.4 17 | auto_config: true 18 | provisioner: 19 | name: ansible 20 | verifier: 21 | name: ansible 22 | -------------------------------------------------------------------------------- /test/vagrant-plugin/scenarios/molecule/network/verify.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Verify 3 | hosts: all 4 | gather_facts: true 5 | gather_subset: 6 | - network 7 | tasks: 8 | - name: Check that there are 3 interfaces 9 | ansible.builtin.assert: 10 | that: 11 | - "{{ ansible_interfaces | length == 3 }}" 12 | -------------------------------------------------------------------------------- /test/vagrant-plugin/scenarios/molecule/provider_config_options/converge.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Converge 3 | hosts: all 4 | gather_facts: false 5 | become: true 6 | tasks: 7 | - name: Sample task # noqa command-instead-of-shell 8 | ansible.builtin.shell: 9 | cmd: uname 10 | changed_when: false 11 | -------------------------------------------------------------------------------- /test/vagrant-plugin/scenarios/molecule/provider_config_options/molecule.yml: -------------------------------------------------------------------------------- 1 | --- 2 | dependency: 3 | name: galaxy 4 | driver: 5 | name: vagrant 6 | provider: 7 | name: libvirt 8 | platforms: 9 | - name: instance 10 | provider_options: 11 | nic_model_type: e1000 12 | box: ${TESTBOX:-centos/7} 13 | provisioner: 14 | name: ansible 15 | verifier: 16 | name: ansible 17 | -------------------------------------------------------------------------------- /test/vagrant-plugin/scenarios/molecule/provider_config_options/verify.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Verify 3 | hosts: all 4 | gather_facts: true 5 | gather_subset: 6 | - network 7 | tasks: 8 | - name: Set interface dict name 9 | ansible.builtin.set_fact: 10 | iface: "{{ ansible_default_ipv4.interface }}" 11 | 12 | - name: Check network card pci infos 13 | ansible.builtin.assert: 14 | that: 15 | - ansible_facts[iface].module == 'e1000' 16 | -------------------------------------------------------------------------------- /test/vagrant-plugin/scenarios/molecule/vagrant_root/converge.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Converge 3 | hosts: all 4 | gather_facts: false 5 | become: true 6 | tasks: 7 | - name: Sample task # noqa command-instead-of-shell 8 | ansible.builtin.shell: 9 | cmd: uname 10 | changed_when: false 11 | -------------------------------------------------------------------------------- /test/vagrant-plugin/scenarios/molecule/vagrant_root/molecule.yml: -------------------------------------------------------------------------------- 1 | --- 2 | dependency: 3 | name: galaxy 4 | driver: 5 | name: vagrant 6 | provider: 7 | name: libvirt 8 | provision: true 9 | platforms: 10 | - name: instance 11 | box: ${TESTBOX:-centos/7} 12 | instance_raw_config_args: 13 | - 'vm.provision :shell, inline: "echo #{Dir.pwd} > /tmp/workdir"' 14 | provisioner: 15 | name: ansible 16 | verifier: 17 | name: ansible 18 | -------------------------------------------------------------------------------- /test/vagrant-plugin/scenarios/molecule/vagrant_root/verify.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Verify 3 | hosts: all 4 | tasks: 5 | - name: Look for /tmp/workdir 6 | ansible.builtin.stat: 7 | path: /tmp/workdir 8 | register: workdir 9 | 10 | - name: Make sure there's a /vagrant 11 | ansible.builtin.assert: 12 | that: 13 | - workdir.stat.exists | bool 14 | 15 | - name: Get /tmp/workdir file content 16 | ansible.builtin.command: 17 | cmd: cat /tmp/workdir 18 | changed_when: false 19 | register: workdir_content 20 | 21 | - name: Print molecule ephemeral directory 22 | ansible.builtin.debug: 23 | msg: "{{ lookup('env', 'MOLECULE_EPHEMERAL_DIRECTORY') }}" 24 | 25 | - name: Print workdir file content 26 | ansible.builtin.debug: 27 | var: workdir_content.stdout 28 | 29 | - name: Check /tmp/workdir content 30 | ansible.builtin.assert: 31 | that: 32 | - workdir_content.stdout == lookup('env', 'MOLECULE_EPHEMERAL_DIRECTORY') 33 | -------------------------------------------------------------------------------- /test/vagrant-plugin/test_driver.py: -------------------------------------------------------------------------------- 1 | from molecule import api 2 | 3 | 4 | def test_vagrant_driver_is_detected(): 5 | assert "vagrant" in [str(d) for d in api.drivers()] 6 | -------------------------------------------------------------------------------- /tools/Vagrantfile: -------------------------------------------------------------------------------- 1 | Vagrant.configure("2") do |config| 2 | config.vm.box = "generic/alpine316" 3 | config.vm.synced_folder '.', '/vagrant', disabled: true 4 | config.vm.provider :libvirt do |l| 5 | l.memory = 512 6 | l.nic_model_type = "e1000" 7 | l.driver = "qemu" 8 | l.cpu_mode = 'custom' 9 | l.cpu_model = 'qemu64' 10 | end 11 | config.vm.provision :shell, inline: "sudo apk add --no-progress --update python3 rsync" 12 | config.ssh.insert_key = false 13 | end 14 | -------------------------------------------------------------------------------- /tools/create_testbox.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -euxo pipefail 3 | 4 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" 5 | 6 | # Used to test that Vagrant is usable and also to pre-download the image 7 | # we will use during testing. 8 | cd "${DIR}" 9 | 10 | # macos: 11 | # brew install hashicorp/tap/hashicorp-vagrant 12 | 13 | vagrant box list | grep -qw testbox && exit 0 14 | 15 | vagrant global-status --prune 16 | 17 | rm -f testbox.box 18 | vagrant up --no-tty --debug 19 | vagrant halt 20 | vagrant package --output testbox.box 21 | vagrant box add testbox.box --name testbox 22 | vagrant destroy -f 23 | -------------------------------------------------------------------------------- /tools/extract_plugin_names.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | 4 | def print_plugin_names() -> None: 5 | import toml 6 | 7 | if len(sys.argv) != 2: 8 | raise SystemExit(f"Usage: {sys.argv[0]} ") 9 | 10 | filename = sys.argv[1] 11 | with open(filename) as f: 12 | toml_data = toml.load(f) 13 | 14 | result = toml_data["project"]["entry-points"]["molecule.driver"].keys() 15 | for i in result: 16 | print(i) # noqa: T201 17 | 18 | 19 | if __name__ == "__main__": 20 | print_plugin_names() 21 | -------------------------------------------------------------------------------- /tools/generate-templates.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -euo pipefail 4 | 5 | rm -rf test/roles/* || true 6 | mkdir -p test/roles 7 | 8 | while IFS='' read -r line; do DRIVER_NAMES+=("$line"); done < <(python "tools/extract_plugin_names.py" "pyproject.toml") 9 | 10 | cd test/roles 11 | for DRIVER_NAME in "${DRIVER_NAMES[@]}"; do 12 | ansible-galaxy role init "${DRIVER_NAME}"plugin 13 | cd "${DRIVER_NAME}"plugin 14 | ansible localhost -o -m lineinfile -a 'path=meta/main.yml line=" namespace: roles" insertafter=" author: your name"' 15 | molecule init scenario --driver-name="${DRIVER_NAME}" 16 | sed \ 17 | -e 's!author:.*!author: molecule-plugins!g' \ 18 | -e 's!namespace:.*!namespace: roles!g' \ 19 | -e 's!company:.*!company: ansible-community!g' \ 20 | -e 's!min_ansible_version:.*!min_ansible_version: "2.1"!g' \ 21 | -e 's!license:.*!license: MIT!g' \ 22 | -i.backup meta/main.yml 23 | # Not sure if the issue is in molecule or ansible-lint or pre-commit ansible-lint hook 24 | # As a workaround, kill the offending files. 25 | rm -rf tests 26 | cd .. 27 | done 28 | 29 | exit 0 30 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | # For more information about tox, see https://tox.readthedocs.io/en/latest/ 2 | [tox] 3 | minversion = 4.2.3 4 | envlist = 5 | lint 6 | pkg 7 | py 8 | py-{devel} 9 | 10 | # do not enable skip missing to avoid CI false positives 11 | skip_missing_interpreters = False 12 | isolated_build = True 13 | 14 | [testenv] 15 | description = 16 | Unit testing 17 | usedevelop = True 18 | skip_install = False 19 | # download assures tox gets newest pip, see https://github.com/tox-dev/tox/issues/791 20 | download = true 21 | # sitepackages = True 22 | extras = 23 | azure 24 | docker 25 | ec2 26 | gce 27 | podman 28 | openstack 29 | test 30 | vagrant 31 | deps = 32 | py-{devel}: git+https://github.com/ansible-community/molecule.git@main#egg=molecule[test] 33 | commands = 34 | ansible-galaxy install -r requirements.yml 35 | pytest --collect-only 36 | pytest --color=yes {tty:-s} 37 | setenv = 38 | ANSIBLE_FORCE_COLOR={env:ANSIBLE_FORCE_COLOR:1} 39 | ANSIBLE_INVENTORY={toxinidir}/tests/hosts.ini 40 | ANSIBLE_CONFIG={toxinidir}/ansible.cfg 41 | ANSIBLE_NOCOWS=1 42 | ANSIBLE_RETRY_FILES_ENABLED=0 43 | ANSIBLE_VERBOSITY={env:ANSIBLE_VERBOSITY:0} 44 | MOLECULE_NO_LOG={env:MOLECULE_NO_LOG:0} 45 | PIP_DISABLE_PIP_VERSION_CHECK=1 46 | PY_COLORS={env:PY_COLORS:1} 47 | PYTHONDONTWRITEBYTECODE=1 48 | # This should pass these args to molecule, no effect here as this is the default 49 | # but it validates that it accepts extra params. 50 | MOLECULE_OPTS=--destroy always 51 | TESTBOX={env:TESTBOX:testbox} 52 | passenv = 53 | CI 54 | CURL_CA_BUNDLE 55 | DOCKER_* 56 | PYTEST_* 57 | REQUESTS_CA_BUNDLE 58 | SSH_AUTH_SOCK 59 | SSL_CERT_FILE 60 | TOXENV 61 | TWINE_* 62 | OS_* 63 | allowlist_externals = 64 | bash 65 | twine 66 | pytest 67 | pre-commit 68 | 69 | [testenv:pkg] 70 | usedevelop = false 71 | skip_install = true 72 | deps = 73 | build >= 0.9.0 74 | twine >= 2.0.0 75 | commands = 76 | bash -c "rm -rf {toxinidir}/dist/ && mkdir -p {toxinidir}/dist/" 77 | # ./build.sh 78 | python -m build 79 | twine check --strict dist/* 80 | 81 | [testenv:lint] 82 | description = Performs linting, style checks 83 | allowlist_externals = 84 | bash 85 | rm 86 | deps = 87 | pre-commit 88 | toml 89 | commands = 90 | bash {toxinidir}/tools/generate-templates.sh 91 | pre-commit run -a 92 | rm -rf test/roles 93 | --------------------------------------------------------------------------------