├── .codespellrc ├── .flake8 ├── .github ├── release-drafter.yml └── workflows │ ├── ack.yml │ ├── push.yml │ ├── release.yml │ └── tox.yml ├── .gitignore ├── .pre-commit-config.yaml ├── .pylintrc ├── .yamllint ├── LICENSE ├── README.rst ├── bindep.txt ├── conftest.py ├── molecule └── default │ ├── converge.yml │ ├── molecule.yml │ └── prepare.yml ├── mypy.ini ├── pyproject.toml ├── pytest.ini ├── requirements.yml ├── setup.cfg ├── src └── molecule_openstack │ ├── __init__.py │ ├── cookiecutter │ ├── cookiecutter.json │ └── {{cookiecutter.molecule_directory}} │ │ └── {{cookiecutter.scenario_name}} │ │ ├── INSTALL.rst │ │ ├── converge.yml │ │ └── molecule.yml │ ├── driver.py │ ├── playbooks │ ├── create.yml │ ├── destroy.yml │ └── prepare.yml │ └── test │ ├── __init__.py │ ├── conftest.py │ ├── functional │ ├── __init__.py │ └── test_scenario.py │ └── unit │ ├── __init__.py │ ├── test_driver.py │ └── test_func.py └── tox.ini /.codespellrc: -------------------------------------------------------------------------------- 1 | [codespell] 2 | ignore-words-list = keypair 3 | -------------------------------------------------------------------------------- /.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,D 7 | # 88 is official black default: 8 | max-line-length = 88 9 | -------------------------------------------------------------------------------- /.github/release-drafter.yml: -------------------------------------------------------------------------------- 1 | # see https://github.com/ansible-community/devtools 2 | _extends: ansible-community/devtools 3 | -------------------------------------------------------------------------------- /.github/workflows/ack.yml: -------------------------------------------------------------------------------- 1 | # See https://github.com/ansible-community/devtools/blob/main/.github/workflows/ack.yml 2 | name: ack 3 | on: 4 | pull_request_target: 5 | types: [opened, labeled, unlabeled, synchronize] 6 | 7 | jobs: 8 | ack: 9 | uses: ansible-community/devtools/.github/workflows/ack.yml@main 10 | -------------------------------------------------------------------------------- /.github/workflows/push.yml: -------------------------------------------------------------------------------- 1 | # See https://github.com/ansible-community/devtools/blob/main/.github/workflows/push.yml 2 | name: push 3 | on: 4 | push: 5 | branches: 6 | - main 7 | - 'releases/**' 8 | - 'stable/**' 9 | 10 | jobs: 11 | ack: 12 | uses: ansible-community/devtools/.github/workflows/push.yml@main 13 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: release 2 | 3 | on: 4 | release: 5 | types: [published] 6 | 7 | jobs: 8 | pypi: 9 | name: Publish to PyPI registry 10 | environment: release 11 | runs-on: ubuntu-20.04 12 | 13 | env: 14 | FORCE_COLOR: 1 15 | PY_COLORS: 1 16 | TOXENV: packaging 17 | TOX_PARALLEL_NO_SPINNER: 1 18 | 19 | steps: 20 | - name: Switch to using Python 3.8 by default 21 | uses: actions/setup-python@v2 22 | with: 23 | python-version: 3.8 24 | - name: Install tox 25 | run: >- 26 | python3 -m 27 | pip install 28 | --user 29 | tox 30 | - name: Check out src from Git 31 | uses: actions/checkout@v2 32 | with: 33 | fetch-depth: 0 # needed by setuptools-scm 34 | - name: Build dists 35 | run: python -m tox 36 | - name: Publish to test.pypi.org 37 | if: >- # "create" workflows run separately from "push" & "pull_request" 38 | github.event_name == 'release' 39 | uses: pypa/gh-action-pypi-publish@master 40 | with: 41 | password: ${{ secrets.testpypi_password }} 42 | repository_url: https://test.pypi.org/legacy/ 43 | - name: Publish to pypi.org 44 | if: >- # "create" workflows run separately from "push" & "pull_request" 45 | github.event_name == 'release' 46 | uses: pypa/gh-action-pypi-publish@master 47 | with: 48 | password: ${{ secrets.pypi_password }} 49 | -------------------------------------------------------------------------------- /.github/workflows/tox.yml: -------------------------------------------------------------------------------- 1 | name: tox 2 | 3 | on: 4 | create: # is used for publishing to TestPyPI 5 | tags: # any tag regardless of its name, no branches 6 | - "**" 7 | push: # only publishes pushes to the main branch to TestPyPI 8 | branches: # any integration branch but not tag 9 | - "main" 10 | pull_request: 11 | schedule: 12 | - cron: 1 0 * * * # Run daily at 0:01 UTC 13 | 14 | jobs: 15 | build: 16 | name: ${{ matrix.tox_env }} 17 | runs-on: ubuntu-latest 18 | strategy: 19 | fail-fast: false 20 | matrix: 21 | include: 22 | - tox_env: lint 23 | # - tox_env: docs 24 | - tox_env: py38 25 | PREFIX: PYTEST_REQPASS=3 26 | - tox_env: py38-devel 27 | PREFIX: PYTEST_REQPASS=3 28 | - tox_env: py39 29 | PREFIX: PYTEST_REQPASS=3 30 | - tox_env: py310 31 | PREFIX: PYTEST_REQPASS=3 32 | - tox_env: py310-devel 33 | PREFIX: PYTEST_REQPASS=3 34 | - tox_env: packaging 35 | 36 | steps: 37 | - name: Check out src from Git 38 | uses: actions/checkout@v2 39 | with: 40 | fetch-depth: 0 # needed by setuptools-scm 41 | 42 | - name: Install system dependencies 43 | run: | 44 | sudo apt-get update \ 45 | && sudo apt-get install -y ansible \ 46 | && ansible-doc -l | grep os_keypair 47 | 48 | - name: Find python version 49 | id: py_ver 50 | shell: python 51 | if: ${{ contains(matrix.tox_env, 'py') }} 52 | run: | 53 | v = '${{ matrix.tox_env }}'.split('-')[0].lstrip('py') 54 | print('::set-output name=version::{0}.{1}'.format(v[0],v[1:])) 55 | 56 | # Even our lint and other envs need access to tox 57 | - name: Install a default Python 58 | uses: actions/setup-python@v2 59 | if: ${{ ! contains(matrix.tox_env, 'py') }} 60 | 61 | # Be sure to install the version of python needed by a specific test, if necessary 62 | - name: Set up Python version 63 | uses: actions/setup-python@v2 64 | if: ${{ contains(matrix.tox_env, 'py') }} 65 | with: 66 | python-version: ${{ steps.py_ver.outputs.version }} 67 | 68 | - name: Install dependencies 69 | run: | 70 | python -m pip install -U pip 71 | pip install tox 72 | 73 | - name: Run tox -e ${{ matrix.tox_env }} 74 | run: | 75 | echo "${{ matrix.PREFIX }} tox -e ${{ matrix.tox_env }}" 76 | ${{ matrix.PREFIX }} tox -e ${{ matrix.tox_env }} 77 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | repos: 3 | - repo: https://github.com/PyCQA/doc8.git 4 | rev: 0.11.1 5 | hooks: 6 | - id: doc8 7 | - repo: https://github.com/PyCQA/isort 8 | rev: 5.10.1 9 | hooks: 10 | - id: isort 11 | - repo: https://github.com/psf/black 12 | rev: 22.3.0 13 | hooks: 14 | - id: black 15 | language_version: python3 16 | - repo: https://github.com/pre-commit/pre-commit-hooks.git 17 | rev: v4.2.0 18 | hooks: 19 | - id: end-of-file-fixer 20 | - id: trailing-whitespace 21 | - id: mixed-line-ending 22 | - id: check-byte-order-marker 23 | - id: check-executables-have-shebangs 24 | - id: check-merge-conflict 25 | - id: debug-statements 26 | - repo: https://github.com/pycqa/flake8.git 27 | rev: 4.0.1 28 | hooks: 29 | - id: flake8 30 | additional_dependencies: 31 | - pydocstyle>=5.1.1 32 | - flake8-absolute-import 33 | - flake8-black>=0.1.1 34 | - flake8-docstrings>=1.5.0 35 | - repo: https://github.com/adrienverge/yamllint.git 36 | rev: v1.26.3 37 | hooks: 38 | - id: yamllint 39 | files: \.(yaml|yml)$ 40 | types: [file, yaml] 41 | entry: yamllint --strict 42 | - repo: https://github.com/pre-commit/mirrors-mypy 43 | rev: v0.942 44 | hooks: 45 | - id: mypy 46 | # empty args needed in order to match mypy cli behavior 47 | args: [] 48 | entry: mypy src/ 49 | pass_filenames: false 50 | additional_dependencies: 51 | - pytest 52 | - molecule 53 | - repo: https://github.com/pre-commit/mirrors-pylint 54 | rev: v3.0.0a4 55 | hooks: 56 | - id: pylint 57 | additional_dependencies: 58 | - molecule 59 | - repo: https://github.com/codespell-project/codespell.git 60 | rev: v2.1.0 61 | hooks: 62 | - id: codespell 63 | name: codespell 64 | description: Checks for common misspellings in text files. 65 | entry: codespell 66 | language: python 67 | types: [text] 68 | args: [] 69 | require_serial: false 70 | additional_dependencies: [] 71 | -------------------------------------------------------------------------------- /.pylintrc: -------------------------------------------------------------------------------- 1 | [MESSAGES CONTROL] 2 | 3 | disable = 4 | # TODO(ssbarnea): remove temporary skips adding during initial adoption: 5 | abstract-method, 6 | arguments-differ, 7 | bad-continuation, 8 | broad-except, 9 | consider-merging-isinstance, 10 | consider-using-in, 11 | dangerous-default-value, 12 | duplicate-code, 13 | fixme, 14 | implicit-str-concat, 15 | import-error, 16 | import-outside-toplevel, 17 | inconsistent-return-statements, 18 | invalid-name, 19 | line-too-long, 20 | logging-format-interpolation, 21 | logging-not-lazy, 22 | misplaced-comparison-constant, 23 | missing-function-docstring, 24 | missing-module-docstring, 25 | no-else-raise, 26 | no-else-return, 27 | no-member, 28 | no-self-argument, 29 | no-self-use, 30 | no-value-for-parameter, 31 | not-callable, 32 | protected-access, 33 | raise-missing-from, 34 | redefined-builtin, 35 | redefined-outer-name, 36 | subprocess-run-check, 37 | super-init-not-called, 38 | super-with-arguments, 39 | too-few-public-methods, 40 | too-many-ancestors, 41 | too-many-arguments, 42 | too-many-public-methods, 43 | unidiomatic-typecheck, 44 | unnecessary-comprehension, 45 | unnecessary-lambda, 46 | unused-argument, 47 | unused-import, 48 | unused-variable, 49 | useless-object-inheritance, 50 | useless-super-delegation, 51 | 52 | [REPORTS] 53 | output-format = colorized 54 | 55 | [IMPORTS] 56 | preferred-modules = 57 | ansible:, 58 | -------------------------------------------------------------------------------- /.yamllint: -------------------------------------------------------------------------------- 1 | extends: default 2 | ignore: | 3 | **/cookiecutter/ 4 | .github/workflows/ 5 | .tox 6 | 7 | rules: 8 | braces: 9 | max-spaces-inside: 1 10 | level: error 11 | brackets: 12 | max-spaces-inside: 1 13 | level: error 14 | document-start: disable 15 | line-length: disable 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 PyContribs 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 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | ************************* 2 | Molecule OpenStack Plugin 3 | ************************* 4 | 5 | .. image:: https://badge.fury.io/py/molecule-openstack.svg 6 | :target: https://badge.fury.io/py/molecule-openstack 7 | :alt: PyPI Package 8 | 9 | .. image:: https://img.shields.io/badge/code%20style-black-000000.svg 10 | :target: https://github.com/python/black 11 | :alt: Python Black Code Style 12 | 13 | .. image:: https://img.shields.io/badge/Code%20of%20Conduct-Ansible-silver.svg 14 | :target: https://docs.ansible.com/ansible/latest/community/code_of_conduct.html 15 | :alt: Ansible Code of Conduct 16 | 17 | .. image:: https://img.shields.io/badge/Mailing%20lists-Ansible-orange.svg 18 | :target: https://docs.ansible.com/ansible/latest/community/communication.html#mailing-list-information 19 | :alt: Ansible mailing lists 20 | 21 | .. image:: https://img.shields.io/badge/license-MIT-brightgreen.svg 22 | :target: LICENSE 23 | :alt: Repository License 24 | 25 | Molecule OpenStack is designed to allow use of OpenStack Clouds for 26 | provisioning test resources. 27 | 28 | Please note that this driver is currently in its early stage of development. 29 | 30 | .. _installation-and-usage: 31 | 32 | Installation and Usage 33 | ====================== 34 | 35 | Install molecule-openstack and pre-requisites: 36 | 37 | .. code-block:: 38 | 39 | pip install molecule-openstack ansible openstacksdk 40 | 41 | Create a new role with molecule using the openstack driver: 42 | 43 | .. code-block:: 44 | 45 | molecule init role -d openstack 46 | 47 | Configure ``/molecule/default/molecule.yaml`` with required 48 | parameters based on your openstack cloud. A simple config is: 49 | 50 | .. code-block:: yaml 51 | 52 | dependency: 53 | name: galaxy 54 | driver: 55 | name: openstack 56 | platforms: 57 | - name: molecule-foo 58 | image: "ubuntu" 59 | flavor: "m1.medium" 60 | network: "private" 61 | fip_pool: "public" 62 | ssh_user: "ubuntu" 63 | provisioner: 64 | name: ansible 65 | verifier: 66 | name: ansible 67 | 68 | Argument ``fip_pool`` in only required when network is not an external 69 | network. Instead of configuring 70 | ``/molecule/default/molecule.yaml`` the following environment 71 | variables can be exported: 72 | 73 | .. code-block:: 74 | 75 | $ export MOLECULE_OPENSTACK_IMAGE=ubuntu 76 | $ export MOLECULE_OPENSTACK_FLAVOR=m1.medium 77 | $ export MOLECULE_OPENSTACK_NETWORK=private 78 | $ export MOLECULE_OPENSTACK_FIP_POOL=public 79 | $ export MOLECULE_OPENSTACK_SSH_USER=ubuntu 80 | 81 | After this molecule can be run from the base-dir of the role: 82 | 83 | .. code-block:: 84 | 85 | source ~/.openrc 86 | molecule test 87 | 88 | 89 | .. _functional-tests: 90 | 91 | Functional Tests 92 | ================ 93 | 94 | Functional tests can be run with tox but require access to an openstack 95 | cluster. They are not part of `ci` yet. To run them locally: 96 | 97 | .. code-block:: 98 | 99 | $ export MOLECULE_OPENSTACK_IMAGE= 100 | $ export MOLECULE_OPENSTACK_FLAVOR= 101 | $ export MOLECULE_OPENSTACK_NETWORK= 102 | $ export MOLECULE_OPENSTACK_FIP_POOL= 103 | $ export MOLECULE_OPENSTACK_SSH_USER= 104 | 105 | $ source openstack_openrc.sh 106 | 107 | $ tox -e py38-functional # or 39,310 108 | 109 | 110 | .. _get-involved: 111 | 112 | Get Involved 113 | ============ 114 | 115 | * Join us in the ``#ansible-devtools`` channel on `Libera`_. 116 | * Join the discussion in `molecule-users Forum`_. 117 | * Join the community working group by checking the `wiki`_. 118 | * Want to know about releases, subscribe to `ansible-announce list`_. 119 | * For the full list of Ansible email Lists, IRC channels see the 120 | `communication page`_. 121 | 122 | .. _`Libera`: https://web.libera.chat/?channel=#ansible-devtools 123 | .. _`molecule-users Forum`: https://groups.google.com/forum/#!forum/molecule-users 124 | .. _`wiki`: https://github.com/ansible/community/wiki/Molecule 125 | .. _`ansible-announce list`: https://groups.google.com/group/ansible-announce 126 | .. _`communication page`: https://docs.ansible.com/ansible/latest/community/communication.html 127 | 128 | .. _license: 129 | 130 | License 131 | ======= 132 | 133 | The `MIT`_ License. 134 | 135 | .. _`MIT`: https://github.com/ansible/molecule/blob/master/LICENSE 136 | 137 | The logo is licensed under the `Creative Commons NoDerivatives 4.0 License`_. 138 | 139 | If you have some other use in mind, contact us. 140 | 141 | .. _`Creative Commons NoDerivatives 4.0 License`: https://creativecommons.org/licenses/by-nd/4.0/ 142 | -------------------------------------------------------------------------------- /bindep.txt: -------------------------------------------------------------------------------- 1 | gcc-c++ [test platform:rpm] 2 | gcc [test platform:rpm] 3 | 4 | python3-devel [test !platform:centos-7 platform:rpm] 5 | python3-libselinux [platform:fedora] 6 | pkg-config [platform:dpkg] 7 | -------------------------------------------------------------------------------- /conftest.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ansible-community/molecule-openstack/74af5f082adf0d858d3e4f3958936bdb091b705e/conftest.py -------------------------------------------------------------------------------- /molecule/default/converge.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Converge 3 | hosts: all 4 | gather_facts: false 5 | become: true 6 | 7 | # TODO: add some code here 8 | -------------------------------------------------------------------------------- /molecule/default/molecule.yml: -------------------------------------------------------------------------------- 1 | --- 2 | driver: 3 | name: openstack 4 | platforms: 5 | - name: instance 6 | provisioner: 7 | name: ansible 8 | -------------------------------------------------------------------------------- /molecule/default/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 | -------------------------------------------------------------------------------- /mypy.ini: -------------------------------------------------------------------------------- 1 | [mypy] 2 | python_version = 3.8 3 | color_output = True 4 | error_summary = True 5 | disallow_untyped_calls=True 6 | warn_redundant_casts=True 7 | 8 | [mypy-molecule.*] 9 | ignore_missing_imports = True 10 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = [ 3 | "setuptools >= 42.0.2", # required by pyproject+setuptools_scm integration 4 | "setuptools_scm >= 1.15.0", 5 | "setuptools_scm_git_archive >= 1.0", 6 | "wheel", 7 | ] 8 | build-backend = "setuptools.build_meta" 9 | 10 | [tool.setuptools_scm] 11 | local_scheme = "no-local-version" 12 | -------------------------------------------------------------------------------- /pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | addopts = -v -rxXs --doctest-modules --durations 10 3 | doctest_optionflags = ALLOW_UNICODE ELLIPSIS 4 | norecursedirs = dist doc build .tox .eggs 5 | -------------------------------------------------------------------------------- /requirements.yml: -------------------------------------------------------------------------------- 1 | # helps ansible-lint install required collection when it is missing 2 | collections: 3 | - name: openstack.cloud 4 | version: ">=1.5.3" 5 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | name = molecule-openstack 3 | url = https://github.com/ansible-community/molecule-openstack 4 | project_urls = 5 | Bug Tracker = https://github.com/ansible-community/molecule-openstack/issues 6 | Release Management = https://github.com/ansible-community/molecule-openstack/projects 7 | CI = https://github.com/ansible-community/molecule-openstack/actions 8 | Discussions = https://github.com/ansible-community/molecule/discussions 9 | Source Code = https://github.com/ansible-community/molecule-openstack 10 | description = Molecule OpenStack Plugin :: run molecule tests on openstack 11 | long_description = file: README.rst 12 | long_description_content_type = text/x-rst 13 | author = Ansible by Red Hat 14 | author_email = info@ansible.com 15 | maintainer = Ansible by Red Hat 16 | maintainer_email = info@ansible.com 17 | license = MIT 18 | license_file = LICENSE 19 | classifiers = 20 | Development Status :: 4 - Beta 21 | Environment :: Console 22 | Intended Audience :: Developers 23 | Intended Audience :: Information Technology 24 | Intended Audience :: System Administrators 25 | License :: OSI Approved :: MIT License 26 | Natural Language :: English 27 | Operating System :: OS Independent 28 | Programming Language :: Python :: 3 29 | Programming Language :: Python :: 3.8 30 | Programming Language :: Python :: 3.9 31 | Programming Language :: Python :: 3.10 32 | 33 | Topic :: System :: Systems Administration 34 | Topic :: Utilities 35 | 36 | keywords = 37 | ansible 38 | roles 39 | testing 40 | molecule 41 | plugin 42 | openstack 43 | 44 | [options] 45 | use_scm_version = True 46 | python_requires = >=3.8 47 | package_dir = 48 | = src 49 | packages = find: 50 | include_package_data = True 51 | zip_safe = False 52 | 53 | # These are required in actual runtime: 54 | install_requires = 55 | molecule >= 3.4.0 56 | pyyaml >= 5.1, < 6 57 | 58 | [options.extras_require] 59 | test = 60 | molecule[test] 61 | 62 | [options.entry_points] 63 | molecule.driver = 64 | openstack = molecule_openstack.driver:Openstack 65 | 66 | [options.packages.find] 67 | where = src 68 | -------------------------------------------------------------------------------- /src/molecule_openstack/__init__.py: -------------------------------------------------------------------------------- 1 | """Molecule Openstack Driver.""" 2 | -------------------------------------------------------------------------------- /src/molecule_openstack/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_openstack/cookiecutter/{{cookiecutter.molecule_directory}}/{{cookiecutter.scenario_name}}/INSTALL.rst: -------------------------------------------------------------------------------- 1 | *********************************** 2 | Openstack driver installation guide 3 | *********************************** 4 | 5 | Requirements 6 | ============ 7 | 8 | * An OpenStack openrc 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_openstack' 23 | -------------------------------------------------------------------------------- /src/molecule_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 | include_role: 7 | name: "{{ cookiecutter.role_name }}" 8 | -------------------------------------------------------------------------------- /src/molecule_openstack/cookiecutter/{{cookiecutter.molecule_directory}}/{{cookiecutter.scenario_name}}/molecule.yml: -------------------------------------------------------------------------------- 1 | --- 2 | dependency: 3 | name: {{ cookiecutter.dependency_name }} 4 | driver: 5 | name: {{ cookiecutter.driver_name }} 6 | platforms: 7 | - name: molecule-{{ cookiecutter.role_name }} 8 | image: ${MOLECULE_OPENSTACK_IMAGE} 9 | flavor: ${MOLECULE_OPENSTACK_FLAVOR} 10 | network: ${MOLECULE_OPENSTACK_NETWORK:-molecule} 11 | fip_pool: ${MOLECULE_OPENSTACK_FIP_POOL} 12 | ssh_user: ${MOLECULE_OPENSTACK_SSH_USER:-cloud-user} 13 | provisioner: 14 | name: {{ cookiecutter.provisioner_name }} 15 | verifier: 16 | name: {{ cookiecutter.verifier_name }} 17 | -------------------------------------------------------------------------------- /src/molecule_openstack/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 | from typing import Dict 22 | 23 | from molecule.api import Driver 24 | 25 | from molecule import logger, util 26 | 27 | LOG = logger.get_logger(__name__) 28 | 29 | 30 | class Openstack(Driver): 31 | """ 32 | The class responsible for managing `OpenStack`_ instances. `OpenStack`_ 33 | is `not` the default driver used in Molecule. 34 | 35 | Molecule leverages Ansible's `openstack_module`_, by mapping variables 36 | from ``molecule.yml`` into ``create.yml`` and ``destroy.yml``. 37 | 38 | .. _`openstack_module`: https://docs.ansible.com/ansible/latest/os_server_module.html 39 | 40 | .. code-block:: yaml 41 | 42 | driver: 43 | name: openstack 44 | platforms: 45 | - name: instance 46 | 47 | .. code-block:: bash 48 | 49 | $ pip install molecule[openstack] 50 | 51 | Change the options passed to the ssh client. 52 | 53 | .. code-block:: yaml 54 | 55 | driver: 56 | name: openstack 57 | ssh_connection_options: 58 | - '-o ControlPath=~/.ansible/cp/%r@%h-%p' 59 | 60 | .. important:: 61 | 62 | Molecule does not merge lists, when overriding the developer must 63 | provide all options. 64 | 65 | Provide a list of files Molecule will preserve, relative to the scenario 66 | ephemeral directory, after any ``destroy`` subcommand execution. 67 | 68 | .. code-block:: yaml 69 | 70 | driver: 71 | name: openstack 72 | safe_files: 73 | - foo 74 | 75 | .. _`OpenStack`: https://www.openstack.org 76 | """ # noqa 77 | 78 | def __init__(self, config=None): 79 | super(Openstack, self).__init__(config) 80 | self._name = "openstack" 81 | 82 | @property 83 | def name(self): 84 | return self._name 85 | 86 | @name.setter 87 | def name(self, value): 88 | self._name = value 89 | 90 | @property 91 | def login_cmd_template(self): 92 | connection_options = " ".join(self.ssh_connection_options) 93 | 94 | return ( 95 | "ssh {{address}} " 96 | "-l {{user}} " 97 | "-p {{port}} " 98 | "-i {{identity_file}} " 99 | "{}" 100 | ).format(connection_options) 101 | 102 | @property 103 | def default_safe_files(self): 104 | return [self.instance_config] 105 | 106 | @property 107 | def default_ssh_connection_options(self): 108 | return self._get_ssh_connection_options() 109 | 110 | def login_options(self, instance_name): 111 | d = {"instance": instance_name} 112 | 113 | return util.merge_dicts(d, self._get_instance_config(instance_name)) 114 | 115 | def ansible_connection_options(self, instance_name): 116 | try: 117 | d = self._get_instance_config(instance_name) 118 | 119 | return { 120 | "ansible_user": d["user"], 121 | "ansible_host": d["address"], 122 | "ansible_port": d["port"], 123 | "ansible_private_key_file": d["identity_file"], 124 | "connection": "ssh", 125 | "ansible_ssh_common_args": " ".join(self.ssh_connection_options), 126 | } 127 | except StopIteration: 128 | return {} 129 | except IOError: 130 | # Instance has yet to be provisioned , therefore the 131 | # instance_config is not on disk. 132 | return {} 133 | 134 | def _get_instance_config(self, instance_name): 135 | instance_config_dict = util.safe_load_file(self._config.driver.instance_config) 136 | 137 | return next( 138 | item for item in instance_config_dict if item["instance"] == instance_name 139 | ) 140 | 141 | def sanity_checks(self): 142 | # FIXME(decentral1se): Implement sanity checks 143 | pass 144 | 145 | def template_dir(self): 146 | """Return path to its own cookiecutterm templates. It is used by init 147 | command in order to figure out where to load the templates from. 148 | """ 149 | return os.path.join(os.path.dirname(__file__), "cookiecutter") 150 | 151 | @property 152 | def required_collections(self) -> Dict[str, str]: 153 | """Return collections dict containing names and versions required.""" 154 | return {"openstack.cloud": "1.5.3"} 155 | -------------------------------------------------------------------------------- /src/molecule_openstack/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_port: 22 9 | 10 | security_group_name: molecule 11 | security_group_description: "Security group for testing Molecule" 12 | security_group_rules: 13 | - proto: tcp 14 | port: "{{ ssh_port }}" 15 | cidr: '0.0.0.0/0' 16 | - proto: icmp 17 | port: -1 18 | cidr: '0.0.0.0/0' 19 | - ethertype: IPv4 20 | group: "{{ security_group.id }}" 21 | - ethertype: IPv6 22 | group: "{{ security_group.id }}" 23 | 24 | key_pair_name: "key-pair-{{ molecule_yml['platforms'][0]['name'] }}" 25 | key_pair_path: "{{ lookup('env', 'MOLECULE_EPHEMERAL_DIRECTORY') }}/ssh_key" 26 | tasks: 27 | - name: Create security group 28 | openstack.cloud.security_group: 29 | name: "{{ security_group_name }}" 30 | description: "{{ security_group_description }}" 31 | register: security_group 32 | 33 | - name: Create security group rules 34 | openstack.cloud.security_group_rule: 35 | security_group: "{{ security_group_name }}" 36 | protocol: "{{ item.proto | default(omit) }}" 37 | port_range_min: "{{ item.port | default(omit) }}" 38 | port_range_max: "{{ item.port | default(omit) }}" 39 | remote_ip_prefix: "{{ item.cidr | default(omit) }}" 40 | remote_group: "{{ item.group | default(omit) }}" 41 | ethertype: "{{ item.ethertype | default(omit) }}" 42 | loop: "{{ security_group_rules | flatten(levels=1) }}" 43 | 44 | - name: Test for presence of local key pair 45 | ansible.builtin.stat: 46 | path: "{{ key_pair_path }}" 47 | register: key_pair_local 48 | 49 | - name: Delete remote key pair 50 | openstack.cloud.keypair: 51 | name: "{{ key_pair_name }}" 52 | state: absent 53 | when: not key_pair_local.stat.exists 54 | 55 | - name: Create key pair 56 | openstack.cloud.keypair: 57 | name: "{{ key_pair_name }}" 58 | register: key_pair 59 | 60 | - name: Persist the key pair 61 | ansible.builtin.copy: 62 | dest: "{{ key_pair_path }}" 63 | content: "{{ key_pair.key.private_key }}" 64 | mode: 0600 65 | when: key_pair.changed 66 | 67 | - name: Create molecule instance(s) 68 | openstack.cloud.server: 69 | name: "{{ item.name }}" 70 | image: "{{ item.image }}" 71 | flavor: "{{ item.flavor }}" 72 | security_groups: "{{ security_group_name }}" 73 | key_name: "{{ key_pair_name }}" 74 | network: "{{ item.network }}" 75 | floating_ip_pools: "{{ item.fip_pool | default(omit) }}" 76 | meta: 77 | ssh_user: "{{ item.ssh_user }}" 78 | register: server 79 | loop: "{{ molecule_yml.platforms | flatten(levels=1) }}" 80 | async: 7200 81 | poll: 0 82 | 83 | - name: Wait for instance(s) creation to complete 84 | ansible.builtin.async_status: 85 | jid: "{{ item.ansible_job_id }}" 86 | register: os_jobs 87 | until: os_jobs.finished 88 | retries: 300 89 | loop: "{{ server.results | flatten(levels=1) }}" 90 | 91 | # Mandatory configuration for Molecule to function. 92 | 93 | - name: Populate instance config dict 94 | ansible.builtin.set_fact: 95 | instance_conf_dict: { 96 | 'instance': "{{ item.openstack.name }}", 97 | 'instance_id': "{{ item.openstack.id }}", 98 | 'address': "{{ item.openstack.accessIPv4 }}", 99 | 'user': "{{ item.openstack.metadata.ssh_user }}", 100 | 'port': "{{ ssh_port }}", 101 | 'identity_file': "{{ key_pair_path }}", } 102 | loop: "{{ os_jobs.results | flatten(levels=1) }}" 103 | register: instance_config_dict 104 | when: server.changed | bool 105 | 106 | - name: Convert instance config dict to a list 107 | ansible.builtin.set_fact: 108 | instance_conf: "{{ instance_config_dict.results | map(attribute='ansible_facts.instance_conf_dict') | list }}" 109 | when: server.changed | bool 110 | 111 | - name: Dump instance config 112 | ansible.builtin.copy: 113 | content: "{{ instance_conf | to_json | from_json | to_yaml }}" 114 | dest: "{{ molecule_instance_config }}" 115 | when: server.changed | bool 116 | 117 | - name: Wait for SSH 118 | ansible.builtin.wait_for: 119 | port: "{{ ssh_port }}" 120 | host: "{{ item.address }}" 121 | search_regex: SSH 122 | delay: 10 123 | loop: "{{ lookup('file', molecule_instance_config) | from_yaml | flatten(levels=1) }}" 124 | -------------------------------------------------------------------------------- /src/molecule_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 | tasks: 8 | # molecule_instance_config might not be present, therefore ignore errors and default to an empty config 9 | - name: Destroy molecule instance(s) 10 | openstack.cloud.server: 11 | name: "{{ item.instance_id }}" 12 | state: absent 13 | delete_fip: true 14 | register: server 15 | loop: "{{ lookup('file', molecule_instance_config, errors='ignore') | default([], true) | from_yaml | flatten(levels=1) }}" 16 | async: 7200 17 | poll: 0 18 | 19 | - name: Wait for instance(s) deletion to complete 20 | ansible.builtin.async_status: 21 | jid: "{{ item.ansible_job_id }}" 22 | register: os_jobs 23 | until: os_jobs.finished 24 | retries: 300 25 | loop: "{{ server.results | flatten(levels=1) }}" 26 | 27 | # Mandatory configuration for Molecule to function. 28 | 29 | - name: Populate instance config 30 | ansible.builtin.set_fact: 31 | instance_conf: {} 32 | 33 | - name: Dump instance config 34 | ansible.builtin.copy: 35 | content: "{{ instance_conf | to_json | from_json | to_yaml }}" 36 | dest: "{{ molecule_instance_config }}" 37 | when: server.changed | bool 38 | -------------------------------------------------------------------------------- /src/molecule_openstack/playbooks/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 | -------------------------------------------------------------------------------- /src/molecule_openstack/test/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ansible-community/molecule-openstack/74af5f082adf0d858d3e4f3958936bdb091b705e/src/molecule_openstack/test/__init__.py -------------------------------------------------------------------------------- /src/molecule_openstack/test/conftest.py: -------------------------------------------------------------------------------- 1 | """Pytest Fixtures.""" 2 | import pytest # noqa 3 | from molecule.test.conftest import random_string, temp_dir # noqa 4 | -------------------------------------------------------------------------------- /src/molecule_openstack/test/functional/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ansible-community/molecule-openstack/74af5f082adf0d858d3e4f3958936bdb091b705e/src/molecule_openstack/test/functional/__init__.py -------------------------------------------------------------------------------- /src/molecule_openstack/test/functional/test_scenario.py: -------------------------------------------------------------------------------- 1 | import pathlib 2 | 3 | from molecule.test.conftest import change_dir_to 4 | from molecule.util import run_command 5 | 6 | from molecule import logger 7 | 8 | LOG = logger.get_logger(__name__) 9 | 10 | 11 | def test_command_init_scenario(tmp_path: pathlib.Path): 12 | """Verify that init scenario works.""" 13 | scenario_name = "default" 14 | 15 | with change_dir_to(tmp_path): 16 | scenario_directory = tmp_path / "molecule" / scenario_name 17 | cmd = [ 18 | "molecule", 19 | "init", 20 | "scenario", 21 | scenario_name, 22 | "--driver-name", 23 | "openstack", 24 | ] 25 | result = run_command(cmd) 26 | assert result.returncode == 0 27 | 28 | assert scenario_directory.exists() 29 | 30 | # run molecule reset as this may clean some leftovers from other 31 | # test runs and also ensure that reset works. 32 | result = run_command(["molecule", "reset"]) # default sceanario 33 | assert result.returncode == 0 34 | 35 | result = run_command(["molecule", "reset", "-s", scenario_name]) 36 | assert result.returncode == 0 37 | 38 | cmd = ["molecule", "--debug", "test", "-s", scenario_name] 39 | result = run_command(cmd) 40 | assert result.returncode == 0 41 | -------------------------------------------------------------------------------- /src/molecule_openstack/test/unit/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ansible-community/molecule-openstack/74af5f082adf0d858d3e4f3958936bdb091b705e/src/molecule_openstack/test/unit/__init__.py -------------------------------------------------------------------------------- /src/molecule_openstack/test/unit/test_driver.py: -------------------------------------------------------------------------------- 1 | """Unit tests.""" 2 | from molecule import api 3 | 4 | 5 | def test_driver_is_detected(): 6 | """Asserts that molecule recognizes the driver.""" 7 | assert any(str(d) == "openstack" for d in api.drivers()) 8 | -------------------------------------------------------------------------------- /src/molecule_openstack/test/unit/test_func.py: -------------------------------------------------------------------------------- 1 | import os 2 | import pathlib 3 | 4 | from molecule.test.conftest import change_dir_to 5 | from molecule.util import run_command 6 | 7 | from molecule import logger 8 | 9 | LOG = logger.get_logger(__name__) 10 | 11 | 12 | def test_command_init_role(temp_dir): 13 | os.path.join(temp_dir.strpath, "test-init") 14 | cmd = ["molecule", "init", "role", "acme.test_init"] 15 | assert run_command(cmd).returncode == 0 16 | 17 | 18 | def test_command_init_scenario(tmp_path: pathlib.Path): 19 | """Verify that init scenario works.""" 20 | scenario_name = "default" 21 | 22 | with change_dir_to(tmp_path): 23 | scenario_directory = tmp_path / "molecule" / scenario_name 24 | cmd = [ 25 | "molecule", 26 | "init", 27 | "scenario", 28 | scenario_name, 29 | "--driver-name", 30 | "openstack", 31 | ] 32 | result = run_command(cmd) 33 | assert result.returncode == 0 34 | 35 | assert scenario_directory.exists() 36 | 37 | # run molecule reset as this may clean some leftovers from other 38 | # test runs and also ensure that reset works. 39 | result = run_command(["molecule", "reset"]) # default sceanario 40 | assert result.returncode == 0 41 | 42 | result = run_command(["molecule", "reset", "-s", scenario_name]) 43 | assert result.returncode == 0 44 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | # For more information about tox, see https://tox.readthedocs.io/en/latest/ 2 | [tox] 3 | minversion = 3.24.5 4 | envlist = 5 | lint 6 | packaging 7 | py{38,39,310} 8 | py{38,39,310}-{devel} 9 | py{38,39,310}-functional 10 | 11 | # do not enable skip missing to avoid CI false positives 12 | skip_missing_interpreters = False 13 | isolated_build = True 14 | 15 | [testenv] 16 | description = 17 | Unit testing 18 | usedevelop = True 19 | # download assures tox gets newest pip, see https://github.com/tox-dev/tox/issues/791 20 | download = true 21 | # sitepackages = True 22 | extras = test 23 | deps = 24 | py{38,39,310}: molecule[test] 25 | py{38,39,310}-{devel}: git+https://github.com/ansible-community/molecule.git@main#egg=molecule[test] 26 | commands = 27 | pytest src/molecule_openstack/test/unit --collect-only 28 | pytest src/molecule_openstack/test/unit --color=yes {tty:-s} 29 | setenv = 30 | PIP_DISABLE_PIP_VERSION_CHECK=1 31 | PY_COLORS={env:PY_COLORS:1} 32 | # pip: Avoid 2020-01-01 warnings: https://github.com/pypa/pip/issues/6207 33 | PYTHONWARNINGS=ignore:DEPRECATION::pip._internal.cli.base_command 34 | PYTHONDONTWRITEBYTECODE=1 35 | # This should pass these args to molecule, no effect here as this is the default 36 | # but it validates that it accepts extra params. 37 | MOLECULE_OPTS=--destroy always 38 | passenv = 39 | CI 40 | CURL_CA_BUNDLE 41 | PYTEST_OPTIONS 42 | REQUESTS_CA_BUNDLE 43 | SSH_AUTH_SOCK 44 | SSL_CERT_FILE 45 | TOXENV 46 | TWINE_* 47 | allowlist_externals = 48 | bash 49 | twine 50 | pytest 51 | pre-commit 52 | 53 | [testenv:py{38,39,310}-functional] 54 | description = 55 | Functional testing, require access to openstack cluster 56 | usedevelop = True 57 | # download assures tox gets newest pip, see https://github.com/tox-dev/tox/issues/791 58 | download = true 59 | # sitepackages = True 60 | extras = test 61 | deps = 62 | molecule[test] 63 | ansible-base >=2.10.0 64 | ansible >= 2.10.0 65 | openstacksdk >= 0.50.0 66 | commands = 67 | pytest src/molecule_openstack/test/functional --collect-only 68 | pytest src/molecule_openstack/test/functional --color=yes {tty:-s} 69 | setenv = 70 | ANSIBLE_FORCE_COLOR={env:ANSIBLE_FORCE_COLOR:1} 71 | ANSIBLE_INVENTORY={toxinidir}/tests/hosts.ini 72 | ANSIBLE_CONFIG={toxinidir}/ansible.cfg 73 | ANSIBLE_NOCOWS=1 74 | ANSIBLE_RETRY_FILES_ENABLED=0 75 | ANSIBLE_STDOUT_CALLBACK={env:ANSIBLE_STDOUT_CALLBACK:debug} 76 | ANSIBLE_VERBOSITY={env:ANSIBLE_VERBOSITY:0} 77 | MOLECULE_NO_LOG={env:MOLECULE_NO_LOG:0} 78 | PIP_DISABLE_PIP_VERSION_CHECK=1 79 | PY_COLORS={env:PY_COLORS:1} 80 | # pip: Avoid 2020-01-01 warnings: https://github.com/pypa/pip/issues/6207 81 | PYTHONWARNINGS=ignore:DEPRECATION::pip._internal.cli.base_command 82 | PYTHONDONTWRITEBYTECODE=1 83 | # This should pass these args to molecule, no effect here as this is the default 84 | # but it validates that it accepts extra params. 85 | MOLECULE_OPTS=--destroy always 86 | passenv = 87 | CI 88 | CURL_CA_BUNDLE 89 | DOCKER_* 90 | PYTEST_OPTIONS 91 | REQUESTS_CA_BUNDLE 92 | SSH_AUTH_SOCK 93 | SSL_CERT_FILE 94 | TOXENV 95 | TRAVIS 96 | TRAVIS_* 97 | TWINE_* 98 | MOLECULE_* 99 | OS_* 100 | allowlist_externals = 101 | bash 102 | twine 103 | pytest 104 | pre-commit 105 | 106 | [testenv:packaging] 107 | description = 108 | Build package, verify metadata, install package and assert behavior when ansible is missing. 109 | usedevelop = false 110 | skip_install = true 111 | deps = 112 | collective.checkdocs >= 0.2 113 | pip >= 22.0 114 | build >= 0.7.0, < 0.8.0 115 | pip >= 22.0 116 | twine 117 | commands = 118 | rm -rfv {toxinidir}/dist/ 119 | python -m build \ 120 | --outdir {toxinidir}/dist/ \ 121 | {toxinidir} 122 | # metadata validation 123 | twine check --strict {toxinidir}/dist/* 124 | allowlist_externals = 125 | rm 126 | 127 | [testenv:lint] 128 | description = Performs linting, style checks 129 | skip_install = true 130 | sitepackages = false 131 | deps = 132 | pre-commit 133 | commands = 134 | pre-commit run -a 135 | --------------------------------------------------------------------------------