├── .config
├── ansible-lint-ignore.txt
├── ansible-lint.yaml
├── constraints.txt
├── dictionary.txt
├── molecule.spec
├── pydoclint-baseline.txt
├── requirements-docs.txt
├── requirements-test.txt
├── requirements-testinfra.txt
└── requirements.in
├── .env
├── .github
├── CODEOWNERS
├── CODE_OF_CONDUCT.md
├── ISSUE_TEMPLATE
│ ├── bug.yml
│ ├── config.yml
│ └── security_bug_report.md
├── dependabot.yml
├── patchback.yml
├── release-drafter.yml
└── workflows
│ ├── ack.yml
│ ├── push.yml
│ ├── redirects.yml
│ ├── release.yml
│ └── tox.yml
├── .gitignore
├── .pre-commit-config.yaml
├── .python-version
├── .readthedocs.yml
├── .sonarcloud.properties
├── .vscode
├── extensions.json
├── settings.json
└── tasks.json
├── LICENSE
├── README.md
├── ansible.cfg
├── codecov.yml
├── collections
├── README.md
└── requirements.yml
├── cspell.config.yaml
├── docs
├── _static
│ └── images
│ │ ├── favicon.ico
│ │ ├── logo.png
│ │ └── logo_big.png
├── asset
│ ├── molecule.png
│ ├── molecule.psd
│ ├── mollie.png
│ └── mollie.psd
├── ci.md
├── configuration.md
├── contributing.md
├── examples
│ ├── docker.md
│ ├── kubevirt.md
│ └── podman.md
├── faq.md
├── getting-started.md
├── google04e29a42ae6e6cbc.html
├── guides
│ ├── custom-image.md
│ ├── docker-rootless.md
│ ├── inside-a-container.md
│ ├── monolith.md
│ ├── parallel.md
│ ├── podman-inside-docker.md
│ ├── sharing.md
│ └── systemd-container.md
├── images
│ └── collection_structure.png
├── index.md
├── installation.md
├── next.md
├── redirects.yml
└── usage.md
├── mise.toml
├── mkdocs.yml
├── molecule
├── pyproject.toml
├── src
└── molecule
│ ├── __init__.py
│ ├── __main__.py
│ ├── api.py
│ ├── app.py
│ ├── command
│ ├── __init__.py
│ ├── base.py
│ ├── check.py
│ ├── cleanup.py
│ ├── converge.py
│ ├── create.py
│ ├── dependency.py
│ ├── destroy.py
│ ├── drivers.py
│ ├── idempotence.py
│ ├── init
│ │ ├── __init__.py
│ │ ├── base.py
│ │ ├── init.py
│ │ └── scenario.py
│ ├── list.py
│ ├── login.py
│ ├── matrix.py
│ ├── prepare.py
│ ├── reset.py
│ ├── side_effect.py
│ ├── syntax.py
│ ├── test.py
│ └── verify.py
│ ├── config.py
│ ├── console.py
│ ├── constants.py
│ ├── data
│ ├── __init__.py
│ ├── driver.json
│ ├── init-scenario.yml
│ ├── molecule.json
│ └── templates
│ │ └── scenario
│ │ ├── converge.yml.j2
│ │ ├── create.yml.j2
│ │ ├── destroy.yml.j2
│ │ └── molecule.yml.j2
│ ├── dependency
│ ├── __init__.py
│ ├── ansible_galaxy
│ │ ├── __init__.py
│ │ ├── base.py
│ │ ├── collections.py
│ │ └── roles.py
│ ├── base.py
│ └── shell.py
│ ├── driver
│ ├── __init__.py
│ ├── base.py
│ └── delegated.py
│ ├── exceptions.py
│ ├── interpolation.py
│ ├── logger.py
│ ├── model
│ ├── __init__.py
│ └── schema_v3.py
│ ├── platforms.py
│ ├── provisioner
│ ├── __init__.py
│ ├── ansible.py
│ ├── ansible_playbook.py
│ ├── ansible_playbooks.py
│ └── base.py
│ ├── py.typed
│ ├── scenario.py
│ ├── scenarios.py
│ ├── shell.py
│ ├── state.py
│ ├── status.py
│ ├── text.py
│ ├── types.py
│ ├── util.py
│ └── verifier
│ ├── __init__.py
│ ├── ansible.py
│ ├── base.py
│ └── testinfra.py
├── tests
├── __init__.py
├── conftest.py
├── fixtures
│ ├── integration
│ │ └── test_command
│ │ │ ├── molecule
│ │ │ ├── default
│ │ │ │ ├── converge.yml
│ │ │ │ ├── create.yml
│ │ │ │ ├── destroy.yml
│ │ │ │ └── molecule.yml
│ │ │ ├── docker
│ │ │ │ ├── converge.yml
│ │ │ │ ├── create.yml
│ │ │ │ ├── destroy.yml
│ │ │ │ ├── molecule.yml
│ │ │ │ ├── requirements.yml
│ │ │ │ └── tasks
│ │ │ │ │ └── create-fail.yml
│ │ │ ├── kubevirt
│ │ │ │ ├── converge.yml
│ │ │ │ ├── create.yml
│ │ │ │ ├── destroy.yml
│ │ │ │ ├── molecule.yml
│ │ │ │ ├── requirements.yml
│ │ │ │ └── tasks
│ │ │ │ │ ├── create_vm.yml
│ │ │ │ │ └── create_vm_dictionary.yml
│ │ │ ├── podman
│ │ │ │ ├── converge.yml
│ │ │ │ ├── create.yml
│ │ │ │ ├── destroy.yml
│ │ │ │ ├── molecule.yml
│ │ │ │ ├── requirements.yml
│ │ │ │ └── tasks
│ │ │ │ │ └── create-fail.yml
│ │ │ └── smoke
│ │ │ │ ├── converge.yml
│ │ │ │ └── molecule.yml
│ │ │ └── scenarios
│ │ │ ├── cleanup
│ │ │ └── molecule
│ │ │ │ └── default
│ │ │ │ ├── cleanup.yml
│ │ │ │ ├── converge.yml
│ │ │ │ ├── molecule.yml
│ │ │ │ └── tests
│ │ │ │ └── test_cleanup.py
│ │ │ ├── dependency
│ │ │ └── molecule
│ │ │ │ ├── ansible-galaxy
│ │ │ │ ├── converge.yml
│ │ │ │ ├── molecule.yml
│ │ │ │ └── requirements.yml
│ │ │ │ └── shell
│ │ │ │ ├── converge.yml
│ │ │ │ └── molecule.yml
│ │ │ ├── driver
│ │ │ ├── delegated
│ │ │ │ ├── meta
│ │ │ │ │ └── main.yml
│ │ │ │ └── molecule
│ │ │ │ │ └── default
│ │ │ │ │ ├── cleanup.yml
│ │ │ │ │ ├── converge.yml
│ │ │ │ │ ├── create.yml
│ │ │ │ │ ├── destroy.yml
│ │ │ │ │ ├── molecule.yml
│ │ │ │ │ ├── prepare.yml
│ │ │ │ │ ├── side_effect.yml
│ │ │ │ │ └── verify.yml
│ │ │ └── delegated_invalid_role_name_with_role_name_check_equals_to_1
│ │ │ │ ├── meta
│ │ │ │ └── main.yml
│ │ │ │ └── molecule
│ │ │ │ └── default
│ │ │ │ ├── converge.yml
│ │ │ │ ├── create.yml
│ │ │ │ ├── destroy.yml
│ │ │ │ └── molecule.yml
│ │ │ ├── host_group_vars
│ │ │ ├── group_vars
│ │ │ │ └── example
│ │ │ │ │ └── all.yml
│ │ │ ├── host_vars
│ │ │ │ └── extra-host
│ │ │ ├── hosts
│ │ │ └── molecule
│ │ │ │ ├── default
│ │ │ │ ├── converge.yml
│ │ │ │ ├── group_vars
│ │ │ │ │ └── example
│ │ │ │ │ │ └── all.yml
│ │ │ │ └── molecule.yml
│ │ │ │ └── links
│ │ │ │ ├── converge.yml
│ │ │ │ └── molecule.yml
│ │ │ ├── idempotence
│ │ │ ├── molecule
│ │ │ │ └── raises
│ │ │ │ │ ├── converge.yml
│ │ │ │ │ └── molecule.yml
│ │ │ └── tasks
│ │ │ │ └── main.yml
│ │ │ ├── interpolation
│ │ │ └── molecule
│ │ │ │ └── default
│ │ │ │ ├── converge.yml
│ │ │ │ └── molecule.yml
│ │ │ ├── overrride_driver
│ │ │ └── molecule
│ │ │ │ └── default
│ │ │ │ ├── converge.yml
│ │ │ │ └── molecule.yml
│ │ │ ├── side_effect
│ │ │ └── molecule
│ │ │ │ └── default
│ │ │ │ ├── converge.yml
│ │ │ │ ├── molecule.yml
│ │ │ │ ├── side_effect.yml
│ │ │ │ └── tests
│ │ │ │ └── test_side_effect.py
│ │ │ ├── test_destroy_strategy
│ │ │ └── molecule
│ │ │ │ └── default
│ │ │ │ ├── converge.yml
│ │ │ │ ├── molecule.yml
│ │ │ │ └── tests
│ │ │ │ └── test_destroy_strategy.py
│ │ │ └── verifier
│ │ │ ├── .pre-commit-config.yaml
│ │ │ └── molecule
│ │ │ ├── testinfra-pre-commit
│ │ │ ├── converge.yml
│ │ │ ├── molecule.yml
│ │ │ └── tests
│ │ │ │ └── test_testinfra_pre_commit.py
│ │ │ └── testinfra
│ │ │ ├── converge.yml
│ │ │ ├── molecule.yml
│ │ │ ├── roles
│ │ │ └── molecule
│ │ │ │ └── tasks
│ │ │ │ └── main.yml
│ │ │ ├── shared
│ │ │ └── test_shared.py
│ │ │ └── tests
│ │ │ └── test_testinfra.py
│ └── resources
│ │ ├── .yamllint
│ │ ├── broken-collection
│ │ └── galaxy.yml
│ │ ├── molecule.yml
│ │ ├── playbooks
│ │ └── delegated
│ │ │ ├── create.yml
│ │ │ ├── destroy.yml
│ │ │ └── inventory
│ │ │ └── group_vars
│ │ │ └── all
│ │ │ └── all.yml
│ │ ├── roles
│ │ └── molecule
│ │ │ ├── meta
│ │ │ └── main.yml
│ │ │ └── tasks
│ │ │ └── main.yml
│ │ ├── sample-collection
│ │ ├── CHANGELOG.md
│ │ ├── galaxy.yml
│ │ ├── meta
│ │ │ └── runtime.yml
│ │ ├── molecule
│ │ │ └── default
│ │ │ │ ├── converge.yml
│ │ │ │ └── molecule.yml
│ │ └── roles
│ │ │ └── get_rich
│ │ │ └── tasks
│ │ │ └── main.yml
│ │ └── schema_instance_files
│ │ ├── invalid
│ │ └── molecule_delegated.yml
│ │ └── valid
│ │ └── molecule.yml
├── integration
│ ├── __init__.py
│ ├── conftest.py
│ └── test_command.py
└── unit
│ ├── __init__.py
│ ├── command
│ ├── __init__.py
│ ├── conftest.py
│ ├── init
│ │ ├── __init__.py
│ │ └── test_scenario.py
│ ├── test_base.py
│ ├── test_check.py
│ ├── test_cleanup.py
│ ├── test_converge.py
│ ├── test_create.py
│ ├── test_dependency.py
│ ├── test_destroy.py
│ ├── test_idempotence.py
│ ├── test_list.py
│ ├── test_login.py
│ ├── test_matrix.py
│ ├── test_prepare.py
│ ├── test_side_effect.py
│ ├── test_syntax.py
│ ├── test_test.py
│ └── test_verify.py
│ ├── conftest.py
│ ├── dependency
│ ├── __init__.py
│ ├── ansible_galaxy
│ │ ├── __init__.py
│ │ ├── test_collections.py
│ │ └── test_roles.py
│ └── test_shell.py
│ ├── driver
│ ├── __init__.py
│ └── test_delegated.py
│ ├── lint
│ └── __init__.py
│ ├── model
│ ├── __init__.py
│ └── v2
│ │ ├── __init__.py
│ │ ├── conftest.py
│ │ ├── test_dependency_section.py
│ │ ├── test_driver_section.py
│ │ ├── test_platforms_section.py
│ │ ├── test_provisioner_section.py
│ │ ├── test_scenario_section.py
│ │ ├── test_schema.py
│ │ └── test_verifier_section.py
│ ├── provisioner
│ ├── __init__.py
│ ├── test_ansible.py
│ ├── test_ansible_playbook.py
│ └── test_ansible_playbooks.py
│ ├── test_api.py
│ ├── test_config.py
│ ├── test_interpolation.py
│ ├── test_logger.py
│ ├── test_platforms.py
│ ├── test_scenario.py
│ ├── test_scenarios.py
│ ├── test_scenarios_ordered.py
│ ├── test_shell.py
│ ├── test_state.py
│ ├── test_status.py
│ ├── test_text.py
│ ├── test_util.py
│ └── verifier
│ ├── __init__.py
│ ├── test_ansible.py
│ └── test_testinfra.py
├── tools
├── get-version.sh
├── opts.txt
├── report-coverage
├── test-setup.sh
└── update-version.sh
└── tox.ini
/.config/ansible-lint-ignore.txt:
--------------------------------------------------------------------------------
1 | # This file contains ignores rule violations for ansible-lint
2 | src/molecule/test/scenarios/driver/delegated_invalid_role_name_with_role_name_check_equals_to_1/meta/main.yml role-name
3 | src/molecule/test/scenarios/driver/delegated_invalid_role_name_with_role_name_check_equals_to_1/meta/main.yml schema[meta]
4 |
--------------------------------------------------------------------------------
/.config/ansible-lint.yaml:
--------------------------------------------------------------------------------
1 | exclude_paths:
2 | - .tox
3 | - .venv
4 | - mkdocs.yml # using tags
5 | # not really playbooks:
6 | kinds:
7 | - "yaml": "**/.config/ansible-lint.yaml"
8 | - "yaml": "**/.github/*.yml"
9 | - "yaml": "**/.pre-commit-config.yaml"
10 | - "yaml": "**/.readthedocs.yml"
11 | - "yaml": "**/codecov.yml"
12 | - "yaml": "**/cspell.config.yaml"
13 | - "yaml": "**/docs/*.yml"
14 |
--------------------------------------------------------------------------------
/.config/dictionary.txt:
--------------------------------------------------------------------------------
1 | anyconfig
2 | argnames
3 | argvalues
4 | autouse
5 | autovivification
6 | booleaness
7 | cacheprovider
8 | caplog
9 | capsys
10 | cfmpr
11 | cgroupfs
12 | cgroupns
13 | clicolor
14 | cloudinitdisk
15 | connopts
16 | containerdisk
17 | credssp
18 | decentral
19 | dedriver
20 | delenv
21 | dependee
22 | dequènes
23 | devel
24 | dind
25 | dotglob
26 | emptydisk
27 | endgroup
28 | endraw
29 | enoent
30 | envlist
31 | envname
32 | ericsysmin
33 | euxo
34 | exitstatus
35 | failover
36 | fileglob
37 | followlinks
38 | fooo
39 | frob
40 | geerlingguy
41 | getfixturevalue
42 | goss
43 | helloworld
44 | hookwrapper
45 | hostvars
46 | htmldir
47 | indentless
48 | jinaj
49 | jodewey
50 | jpvanhal
51 | jsonfile
52 | keypair
53 | kubevirt
54 | libcloud
55 | libera
56 | libpod
57 | lifecycle
58 | lockf
59 | makereport
60 | maxsplit
61 | metacopy
62 | mfoo
63 | minversion
64 | modifyitems
65 | mountopt
66 | multibranch
67 | mygroups
68 | nitz
69 | nocows
70 | nodestroy
71 | nodev
72 | nopasswd
73 | notest
74 | ntlm
75 | overlayfs
76 | parallelizable
77 | passenv
78 | passwordless
79 | prepender
80 | prerun
81 | printenv
82 | profisioner
83 | pycolors
84 | Qalthos
85 | quux
86 | reget
87 | reraised
88 | resetall
89 | rolebinding
90 | rrequirements
91 | runcmd
92 | saez
93 | schemafile
94 | seccomp
95 | selectattr
96 | serviceaccount
97 | sessionfinish
98 | setenv
99 | signoff
100 | skipsdist
101 | ssbarnea
102 | stestr
103 | stopall
104 | stty
105 | superitem
106 | sysexit
107 | templater
108 | testenv
109 | testinfra
110 | testrole
111 | testvar
112 | timedout
113 | tmpfs
114 | topdown
115 | tryfirst
116 | ulimits
117 | ultral
118 | unittests
119 | urandom
120 | usefixtures
121 | usermod
122 | vfoo
123 | virt
124 | virt's
125 | virtenv
126 | virtualmachines
127 | xdist
128 | zzyzx
129 |
--------------------------------------------------------------------------------
/.config/molecule.spec:
--------------------------------------------------------------------------------
1 | # All tests require Internet access
2 | # to test in mock use: --enable-network --with check
3 | # to test in a privileged environment use:
4 | # --with check --with privileged_tests
5 | %bcond_with check
6 | %bcond_with privileged_tests
7 |
8 | Name: molecule
9 | Version: VERSION_PLACEHOLDER
10 | Release: 1%{?dist}
11 | Summary: Molecule testing framework for Ansible content
12 |
13 | License: MIT
14 | URL: https://github.com/ansible-community/molecule
15 | Source0: %{pypi_source}
16 |
17 | BuildArch: noarch
18 |
19 | BuildRequires: pyproject-rpm-macros
20 | BuildRequires: python%{python3_pkgversion}-build
21 | BuildRequires: python%{python3_pkgversion}-devel
22 | BuildRequires: python%{python3_pkgversion}-pip
23 | BuildRequires: python%{python3_pkgversion}-setuptools
24 | BuildRequires: python%{python3_pkgversion}-setuptools_scm
25 | BuildRequires: python%{python3_pkgversion}-wheel
26 | %if %{with check}
27 | # These are required for tests:
28 | BuildRequires: python%{python3_pkgversion}-pyyaml
29 | BuildRequires: python%{python3_pkgversion}-pytest
30 | BuildRequires: python%{python3_pkgversion}-pytest-xdist
31 | BuildRequires: python%{python3_pkgversion}-libselinux
32 | BuildRequires: git
33 | %endif
34 | # Named based on fedora 35:
35 | Requires: ansible-core
36 | Requires: python3-enrich
37 | Requires: python3-packaging
38 | Requires: python3-pyyaml
39 | Requires: python3-rich
40 |
41 | # generate_buildrequires
42 | # pyproject_buildrequires
43 |
44 | %description
45 | Molecule provides support for testing with multiple instances, operating
46 | systems and distributions, virtualization providers, test frameworks and
47 | testing scenarios.
48 |
49 | %prep
50 | %autosetup
51 |
52 |
53 | %build
54 | %pyproject_wheel
55 |
56 |
57 | %install
58 | %pyproject_install
59 |
60 |
61 | %if %{with check}
62 | %check
63 | PYTHONPATH=%{buildroot}%{python3_sitelib} \
64 | pytest-3 \
65 | -v \
66 | --disable-pytest-warnings \
67 | --numprocesses=auto \
68 | %if %{with privileged_tests}
69 | tests
70 | %else
71 | tests/unit
72 | %endif
73 | %endif
74 |
75 |
76 | %files
77 | %{python3_sitelib}/molecule/
78 | %{python3_sitelib}/molecule-*.dist-info/
79 | %{_bindir}/molecule
80 | %license LICENSE
81 | %doc docs/* README.md
82 |
83 | %changelog
84 | Available at https://github.com/ansible-community/molecule/releases
85 |
--------------------------------------------------------------------------------
/.config/requirements-docs.txt:
--------------------------------------------------------------------------------
1 | mkdocs-ansible>=24.3.0
2 | linkchecker>=10.4.0
3 |
--------------------------------------------------------------------------------
/.config/requirements-test.txt:
--------------------------------------------------------------------------------
1 | ansi2html >= 1.8.0
2 | ansible-lint >= 6.12.1
3 | coverage[toml]
4 | docker >= 7.1.0 # testing
5 | filelock >= 3.9.0
6 | mypy
7 | pexpect >= 4.9.0, < 5
8 | pre-commit
9 | pydoclint
10 | pylint
11 | pytest
12 | pytest-mock >= 3.10.0
13 | pytest-plus >= 0.7.0
14 | pytest-xdist
15 | pytest-instafail
16 | requests != 2.32.0 # https://github.com/docker/docker-py/issues/3256
17 | ruff
18 | toml-sort
19 | tox
20 | types-jsonschema
21 | types-pexpect
22 | types-pyyaml
23 |
--------------------------------------------------------------------------------
/.config/requirements-testinfra.txt:
--------------------------------------------------------------------------------
1 | pytest-testinfra>=8.1.0
2 |
--------------------------------------------------------------------------------
/.config/requirements.in:
--------------------------------------------------------------------------------
1 | ansible-compat >= 25.1.4
2 | ansible-core >= 2.15.0
3 | click >= 8.0, < 9
4 | click-help-colors
5 | enrich >= 1.2.7
6 | jsonschema >= 4.9.1
7 | Jinja2 >= 2.11.3
8 | packaging
9 | pluggy >= 0.7.1, < 2.0
10 | PyYAML >= 5.1
11 | rich >= 9.5.1
12 | wcmatch>=8.1.2 # MIT
13 |
--------------------------------------------------------------------------------
/.env:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | # PWD is supposed to be the directory container the .envrc file based on the
3 | # specs. This trick should isolate our testing from user environment.
4 | ANSIBLE_HOME=$PWD/.ansible
5 | ANSIBLE_CONFIG=$PWD/ansible.cfg
6 |
--------------------------------------------------------------------------------
/.github/CODEOWNERS:
--------------------------------------------------------------------------------
1 | * @ansible/devtools
2 |
--------------------------------------------------------------------------------
/.github/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Community Code of Conduct
2 |
3 | Please see the official
4 | [Ansible Community Code of Conduct](https://docs.ansible.com/ansible/latest/community/code_of_conduct.html).
5 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug.yml:
--------------------------------------------------------------------------------
1 | ---
2 | # https://docs.github.com/en/communities/using-templates-to-encourage-useful-issues-and-pull-requests/syntax-for-githubs-form-schema
3 | name: Bug Report
4 | description: >
5 | Please test the main branch before creating new issues. Do not use this
6 | form to ask for support.
7 | # title: "[Bug]: "
8 | labels: ["new"]
9 | projects: ["ansible/86"]
10 | # assignees:
11 | # - octocat
12 | body:
13 | - type: checkboxes
14 | id: checklist
15 | attributes:
16 | label: Prerequisites
17 | description: >
18 | Create new issues only if all checks were made and you checked [FAQ](https://ansible.readthedocs.io/projects/molecule/faq/) and the
19 | [discussions forum](https://github.com/ansible/molecule/discussions).
20 | options:
21 | - label: This was not already reported in the past (duplicate check)
22 | required: true
23 | - label: It does reproduce it with code from main branch (latest unreleased version)
24 | required: true
25 | - label: I include a minimal example for reproducing the bug
26 | required: true
27 | - label: The bug is not trivial, as for those a direct pull-request is preferred
28 | required: true
29 | - label: Running `pip check` does not report any conflicts
30 | required: true
31 | - label: I was able to reproduce the issue on a different machine
32 | required: true
33 | - label: The issue is not specific to any driver other than 'default' one
34 | required: true
35 |
36 | - type: markdown
37 | attributes:
38 | value: |
39 | Also check [FAQ](https://ansible.readthedocs.io/projects/molecule/faq/) and the
40 | [discussions forum](https://github.com/ansible/molecule/discussions) before creating a new issue.
41 |
42 | - type: textarea
43 | id: environment
44 | attributes:
45 | label: Environment
46 | description: >
47 | Include all output of `molecule --version` and
48 | anything else that might be relevant, such **operating system**.
49 | validations:
50 | required: true
51 |
52 | - type: textarea
53 | id: what-happened
54 | attributes:
55 | label: What happened
56 | description: >
57 | Also tell us, what did you **expect** to happen?
58 | Please give some details of the feature being requested or what
59 | should happen if providing a bug report.
60 | placeholder: Tell us what you see!
61 | validations:
62 | required: true
63 |
64 | - type: textarea
65 | id: reproducer
66 | attributes:
67 | label: Reproducing example
68 | render: yml
69 | description: >
70 | Please write a [minimum complete verifiable YAML example](https://stackoverflow.com/help/mcve) that is reproduces the issue without
71 | causing other side effects. If the issue cannot be reproduce with a single
72 | YAML file, please paste a link to [gist](https://gist.github.com/) or to a full git repository that contain this example.
73 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/config.yml:
--------------------------------------------------------------------------------
1 | blank_issues_enabled: false
2 | contact_links:
3 | - name: Propose a new feature or behavior change
4 | url: https://github.com/ansible/molecule/discussions/categories/ideas
5 | about: Always check for existing threads before adding a new one
6 | - name: Ask a question on Discussions area
7 | url: https://github.com/ansible/molecule/discussions
8 | about: Recommended place for questions, debates, consultation.
9 | - name: Ask a question on StackOverflow
10 | url: https://stackoverflow.com/questions/tagged/molecule
11 | about: For well defined questions and answers use StackOverflow
12 | - name: Molecule Plugins (drivers)
13 | about: This includes some drivers such azure, docker, ec2, gce, podman and vagrant
14 | url: https://github.com/ansible-community/molecule-plugins/
15 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/security_bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Security bug report
3 | about: How to report security vulnerabilities
4 | ---
5 |
6 | For all security related bugs, email security@ansible.com instead of using this issue tracker and you will receive a prompt response.
7 |
8 | For more information, see https://docs.ansible.com/ansible/latest/community/reporting_bugs_and_features.html
9 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | ---
2 | version: 2
3 | updates:
4 | - package-ecosystem: pip
5 | directory: /.config/
6 | schedule:
7 | day: sunday
8 | interval: weekly
9 | labels:
10 | - dependabot-deps-updates
11 | - skip-changelog
12 | groups:
13 | dependencies:
14 | patterns:
15 | - "*"
16 | - package-ecosystem: "github-actions"
17 | directory: "/"
18 | schedule:
19 | interval: daily
20 | labels:
21 | - "dependencies"
22 | - "skip-changelog"
23 |
--------------------------------------------------------------------------------
/.github/patchback.yml:
--------------------------------------------------------------------------------
1 | ---
2 | backport_branch_prefix: patchback/backports/
3 | backport_label_prefix: backport-
4 | target_branch_prefix: stable/
5 |
--------------------------------------------------------------------------------
/.github/release-drafter.yml:
--------------------------------------------------------------------------------
1 | ---
2 | # see https://github.com/ansible/team-devtools
3 | _extends: ansible/team-devtools
4 |
--------------------------------------------------------------------------------
/.github/workflows/ack.yml:
--------------------------------------------------------------------------------
1 | # See https://github.com/ansible/team-devtools/blob/main/.github/workflows/ack.yml
2 | name: ack
3 |
4 | concurrency:
5 | group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
6 | cancel-in-progress: true
7 |
8 | on:
9 | pull_request_target:
10 | types: [opened, labeled, unlabeled, synchronize]
11 |
12 | jobs:
13 | ack:
14 | uses: ansible/team-devtools/.github/workflows/ack.yml@token_revised
15 | secrets: inherit
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 | workflow_dispatch:
11 |
12 | jobs:
13 | ack:
14 | uses: ansible/team-devtools/.github/workflows/push.yml@main
15 |
--------------------------------------------------------------------------------
/.github/workflows/redirects.yml:
--------------------------------------------------------------------------------
1 | ---
2 | # Sync RTD redirects
3 | name: redirects
4 |
5 | on:
6 | push:
7 | branches:
8 | - main
9 | paths:
10 | - docs/redirects.yml
11 | - .github/workflows/redirects.yml
12 |
13 | # Manually triggered using GitHub's UI
14 | workflow_dispatch:
15 |
16 | jobs:
17 | docs:
18 | environment: release
19 | runs-on: ubuntu-24.04
20 | steps:
21 | - uses: actions/checkout@v4
22 | - uses: actions/setup-python@v5
23 |
24 | - name: Upgrade Python toolchain
25 | run: python3 -m pip install --upgrade pip setuptools wheel
26 |
27 | - name: Install readthedocs-cli
28 | run: python3 -m pip install readthedocs-cli
29 |
30 | - name: Sync redirects
31 | run: rtd projects ansible-lint redirects sync -f docs/redirects.yml --wet-run
32 | env:
33 | RTD_TOKEN: ${{ secrets.RTD_TOKEN }}
34 |
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | ---
2 | name: release
3 |
4 | on:
5 | release:
6 | types: [published]
7 |
8 | jobs:
9 | release:
10 | environment: release
11 | runs-on: ubuntu-24.04
12 | permissions:
13 | id-token: write
14 |
15 | env:
16 | FORCE_COLOR: 1
17 | PY_COLORS: 1
18 |
19 | steps:
20 | - name: Switch to using Python 3.12 by default
21 | uses: actions/setup-python@v5
22 | with:
23 | python-version: "3.12"
24 |
25 | - name: Install tox
26 | run: python3 -m pip install --user "tox>=4.0.0"
27 |
28 | - name: Check out src from Git
29 | uses: actions/checkout@v4
30 | with:
31 | fetch-depth: 0 # needed by setuptools-scm
32 |
33 | - name: Build dists
34 | run: python3 -m tox -e pkg
35 |
36 | - name: Publish to pypi.org
37 | uses: pypa/gh-action-pypi-publish@release/v1
38 |
39 | forum_post:
40 | needs: release
41 | runs-on: ubuntu-24.04
42 |
43 | steps:
44 | - name: Retreive the forum post script from team-devtools
45 | run: curl -O https://raw.githubusercontent.com/ansible/team-devtools/main/.github/workflows/forum_post.py
46 |
47 | - name: Run the forum post script
48 | run: python3 forum_post.py ${{ github.event.repository.full_name }} ${{ github.event.release.tag_name }} ${{ secrets.FORUM_KEY }} ${{ secrets.FORUM_USER }}
49 |
--------------------------------------------------------------------------------
/.github/workflows/tox.yml:
--------------------------------------------------------------------------------
1 | ---
2 | name: tox
3 |
4 | on:
5 | merge_group:
6 | branches:
7 | - "main"
8 | push:
9 | branches:
10 | - "main"
11 | pull_request:
12 | branches:
13 | - "main"
14 | workflow_call:
15 |
16 | concurrency:
17 | group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }}
18 | cancel-in-progress: true
19 |
20 | jobs:
21 | tox:
22 | uses: ansible/team-devtools/.github/workflows/tox.yml@main
23 | with:
24 | default_python: "3.10" # for lint
25 | max_python: "3.13"
26 | jobs_producing_coverage: 8
27 | other_names_also: "eco"
28 |
--------------------------------------------------------------------------------
/.python-version:
--------------------------------------------------------------------------------
1 | 3.10
2 |
3 | # Needed by dependabot, see https://github.com/dependabot/dependabot-core/issues/1455
4 |
--------------------------------------------------------------------------------
/.readthedocs.yml:
--------------------------------------------------------------------------------
1 | ---
2 | version: 2
3 |
4 | mkdocs:
5 | fail_on_warning: true
6 | configuration: mkdocs.yml
7 |
8 | build:
9 | os: ubuntu-24.04
10 | tools:
11 | python: "3.12"
12 | commands:
13 | - pip install --user tox
14 | - python3 -m tox -e docs
15 | python:
16 | install:
17 | - method: pip
18 | path: tox
19 | - method: pip
20 | path: .
21 | extra_requirements:
22 | - docs
23 | submodules:
24 | include: all
25 | recursive: true
26 |
--------------------------------------------------------------------------------
/.sonarcloud.properties:
--------------------------------------------------------------------------------
1 | sonar.python.version=3.10, 3.11, 3.12, 3.13
2 | sonar.sources=src/
3 | sonar.tests=tests/
4 |
--------------------------------------------------------------------------------
/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | "recommendations": [
3 | "charliermarsh.ruff",
4 | "esbenp.prettier-vscode",
5 | "gruntfuggly.triggertaskonsave",
6 | "markis.code-coverage",
7 | "ms-python.debugpy",
8 | "ms-python.mypy-type-checker",
9 | "ms-python.pylint",
10 | "ms-python.python",
11 | "sonarsource.sonarlint-vscode",
12 | "streetsidesoftware.code-spell-checker"
13 | ],
14 | "unwantedRecommendations": [
15 | "ms-python.isort",
16 | "ms-python.flake8",
17 | "ms-toolsai.jupyter",
18 | "ms-python.black-formatter"
19 | ]
20 | }
21 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "[jsonc]": {
3 | "editor.defaultFormatter": "esbenp.prettier-vscode"
4 | },
5 | "[python]": {
6 | "editor.codeActionsOnSave": {
7 | "source.fixAll": "explicit",
8 | "source.organizeImports": "explicit"
9 | },
10 | "editor.defaultFormatter": "charliermarsh.ruff",
11 | "editor.formatOnSave": true
12 | },
13 | "flake8.importStrategy": "fromEnvironment",
14 | "markiscodecoverage.searchCriteria": ".cache/.coverage/lcov.info",
15 | "mypy-type-checker.args": ["--config-file=${workspaceFolder}/pyproject.toml"],
16 | "mypy-type-checker.importStrategy": "fromEnvironment",
17 | "mypy-type-checker.reportingScope": "workspace",
18 | "pylint.importStrategy": "fromEnvironment",
19 | "python.testing.pytestArgs": ["tests"],
20 | "python.testing.pytestEnabled": true,
21 | "python.testing.unittestEnabled": false,
22 | "triggerTaskOnSave.tasks": {
23 | "pydoclint": ["*.py"]
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/.vscode/tasks.json:
--------------------------------------------------------------------------------
1 | {
2 | // See https://go.microsoft.com/fwlink/?LinkId=733558
3 | // for the documentation about the tasks.json format
4 | "version": "2.0.0",
5 | "tasks": [
6 | {
7 | "label": "pydoclint",
8 | "type": "shell",
9 | "command": "pydoclint",
10 | "args": ["."],
11 | "presentation": {
12 | "reveal": "never"
13 | },
14 | "problemMatcher": {
15 | "owner": "pydoclint",
16 | "fileLocation": ["relative", "${workspaceFolder}"],
17 | "pattern": {
18 | "regexp": "^(.*?):(\\d+):\\s(.*?):\\s(.*)$",
19 | "file": 1,
20 | "line": 2,
21 | "code": 3,
22 | "message": 4
23 | }
24 | },
25 | "group": {
26 | "kind": "none",
27 | "isDefault": true
28 | }
29 | }
30 | ]
31 | }
32 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License
2 |
3 | Copyright (c) 2015-2018 Cisco Systems, Inc.
4 | Copyright (c) 2018 Red Hat, Inc.
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy
7 | of this software and associated documentation files (the "Software"), to deal
8 | in the Software without restriction, including without limitation the rights
9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | copies of the Software, and to permit persons to whom the Software is
11 | furnished to do so, subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in
14 | all copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | THE SOFTWARE.
23 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # About Ansible Molecule
2 |
3 | [](https://pypi.org/project/molecule/)
4 | [](https://ansible.readthedocs.io/projects/molecule)
5 | [](https://github.com/ansible-community/molecule/actions)
6 | [](https://github.com/python/black)
7 | [](https://docs.ansible.com/ansible/latest/community/code_of_conduct.html)
8 | [](https://forum.ansible.com/tag/molecule)
9 | [](LICENSE)
10 |
11 | Molecule project is designed to aid in the development and testing of
12 | [Ansible](https://ansible.com) roles.
13 |
14 | Molecule provides support for testing with multiple instances, operating
15 | systems and distributions, virtualization providers, test frameworks and
16 | testing scenarios.
17 |
18 | Molecule encourages an approach that results in consistently developed
19 | roles that are well-written, easily understood and maintained.
20 |
21 | Molecule supports only the latest two major versions of Ansible (N/N-1).
22 |
23 | Once installed, the command line can be called using any of the methods
24 | below:
25 |
26 | ```bash
27 | molecule ...
28 | python3 -m molecule ... # python module calling method
29 | ```
30 |
31 | # Documentation
32 |
33 | Read the documentation and more at .
34 |
35 | # Get Involved
36 |
37 | See the [Talk to us](https://ansible.readthedocs.io/projects/molecule/contributing/#talk-to-us) section of the documentation to ask questions, find help, and join the conversation.
38 |
39 | For complete details, see the
40 | [Ansible communication guide](https://docs.ansible.com/ansible/devel/community/communication.html).
41 |
42 | If you want to get moving fast and make a quick patch:
43 |
44 | ```bash
45 | $ git clone https://github.com/ansible-community/molecule && cd molecule
46 | $ python3 -m venv .venv && source .venv/bin/activate
47 | $ python3 -m pip install -U setuptools pip tox
48 | ```
49 |
50 | And you're ready to make your changes!
51 |
52 | # Authors
53 |
54 | Molecule project was created by [Retr0h](https://github.com/retr0h) and
55 | it is now community-maintained as part of the
56 | [Ansible](https://ansible.com) by Red Hat project.
57 |
58 | # License
59 |
60 | The
61 | [MIT](https://github.com/ansible-community/molecule/blob/main/LICENSE)
62 | License.
63 |
64 | The logo is licensed under the [Creative Commons NoDerivatives 4.0
65 | License](https://creativecommons.org/licenses/by-nd/4.0/).
66 |
67 | If you have some other use in mind, contact us.
68 |
--------------------------------------------------------------------------------
/ansible.cfg:
--------------------------------------------------------------------------------
1 | # User during testing to ensure local user config does not affect testing
2 |
--------------------------------------------------------------------------------
/codecov.yml:
--------------------------------------------------------------------------------
1 | comment: false
2 | coverage:
3 | status:
4 | patch: true
5 |
--------------------------------------------------------------------------------
/collections/README.md:
--------------------------------------------------------------------------------
1 | These collections are here for ansible-lint so the ansible-lint pre-commit action can install them prior to linting the repository.
2 |
--------------------------------------------------------------------------------
/collections/requirements.yml:
--------------------------------------------------------------------------------
1 | # This file is only used for testing purposes as it helps ansible-lint to
2 | # install dependencies when these are missing.
3 | collections:
4 | - name: community.crypto
5 | - name: community.docker
6 | version: ">=3.10.4"
7 | - name: containers.podman
8 | - name: kubernetes.core
9 |
--------------------------------------------------------------------------------
/cspell.config.yaml:
--------------------------------------------------------------------------------
1 | dictionaryDefinitions:
2 | - name: words
3 | path: .config/dictionary.txt
4 | addWords: true
5 | dictionaries:
6 | - bash
7 | - networking-terms
8 | - python
9 | - words
10 | - "!aws"
11 | - "!backwards-compatibility"
12 | - "!cryptocurrencies"
13 | - "!cpp"
14 | ignorePaths:
15 | - .config/requirements*
16 | - \.*
17 | - cspell.config.yaml
18 | - mkdocs.yml
19 | - pyproject.toml
20 | - tox.ini
21 |
22 | languageSettings:
23 | - languageId: python
24 | allowCompoundWords: false
25 |
--------------------------------------------------------------------------------
/docs/_static/images/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ansible/molecule/bfb18224c98e7ad4cc0c49b03a8398df1f550a01/docs/_static/images/favicon.ico
--------------------------------------------------------------------------------
/docs/_static/images/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ansible/molecule/bfb18224c98e7ad4cc0c49b03a8398df1f550a01/docs/_static/images/logo.png
--------------------------------------------------------------------------------
/docs/_static/images/logo_big.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ansible/molecule/bfb18224c98e7ad4cc0c49b03a8398df1f550a01/docs/_static/images/logo_big.png
--------------------------------------------------------------------------------
/docs/asset/molecule.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ansible/molecule/bfb18224c98e7ad4cc0c49b03a8398df1f550a01/docs/asset/molecule.png
--------------------------------------------------------------------------------
/docs/asset/molecule.psd:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ansible/molecule/bfb18224c98e7ad4cc0c49b03a8398df1f550a01/docs/asset/molecule.psd
--------------------------------------------------------------------------------
/docs/asset/mollie.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ansible/molecule/bfb18224c98e7ad4cc0c49b03a8398df1f550a01/docs/asset/mollie.png
--------------------------------------------------------------------------------
/docs/asset/mollie.psd:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ansible/molecule/bfb18224c98e7ad4cc0c49b03a8398df1f550a01/docs/asset/mollie.psd
--------------------------------------------------------------------------------
/docs/examples/docker.md:
--------------------------------------------------------------------------------
1 | # Using docker containers
2 |
3 | Below you can see a scenario that is using docker containers as test hosts.
4 | When you run `molecule test --scenario-name docker` the `create`, `converge` and
5 | `destroy` steps will be run one after another.
6 |
7 | This example is using Ansible playbooks and it does not need any molecule
8 | plugins to run. You can fully control which test requirements you need to be
9 | installed.
10 |
11 | ## Config playbook
12 |
13 | ```yaml title="molecule.yml"
14 | {!tests/fixtures/integration/test_command/molecule/docker/molecule.yml!}
15 | ```
16 |
17 | ```yaml title="requirements.yml"
18 | {!tests/fixtures/integration/test_command/molecule/docker/requirements.yml!}
19 | ```
20 |
21 | ## Create playbook
22 |
23 | ```yaml title="create.yml"
24 | {!tests/fixtures/integration/test_command/molecule/docker/create.yml!}
25 | ```
26 |
27 | ```yaml title="tasks/create-fail.yml"
28 | {!tests/fixtures/integration/test_command/molecule/docker/tasks/create-fail.yml!}
29 | ```
30 |
31 | ## Converge playbook
32 |
33 | ```yaml title="converge.yml"
34 | {!tests/fixtures/integration/test_command/molecule/docker/converge.yml!}
35 | ```
36 |
37 | ## Destroy playbook
38 |
39 | ```yaml title="destroy.yml"
40 | {!tests/fixtures/integration/test_command/molecule/docker/destroy.yml!}
41 | ```
42 |
--------------------------------------------------------------------------------
/docs/examples/podman.md:
--------------------------------------------------------------------------------
1 | # Using podman containers
2 |
3 | Below you can see a scenario that is using podman containers as test hosts.
4 | When you run `molecule test --scenario-name podman` the `create`, `converge` and
5 | `destroy` steps will be run one after another.
6 |
7 | This example is using Ansible playbooks and it does not need any molecule
8 | plugins to run. You can fully control which test requirements you need to be
9 | installed.
10 |
11 | ## Config playbook
12 |
13 | ```yaml title="molecule.yml"
14 | {!tests/fixtures/integration/test_command/molecule/podman/molecule.yml!}
15 | ```
16 |
17 | ```yaml title="requirements.yml"
18 | {!tests/fixtures/integration/test_command/molecule/podman/requirements.yml!}
19 | ```
20 |
21 | ## Create playbook
22 |
23 | ```yaml title="create.yml"
24 | {!tests/fixtures/integration/test_command/molecule/podman/create.yml!}
25 | ```
26 |
27 | ```yaml title="tasks/create-fail.yml"
28 | {!tests/fixtures/integration/test_command/molecule/podman/tasks/create-fail.yml!}
29 | ```
30 |
31 | ## Converge playbook
32 |
33 | ```yaml title="converge.yml"
34 | {!tests/fixtures/integration/test_command/molecule/podman/converge.yml!}
35 | ```
36 |
37 | ## Destroy playbook
38 |
39 | ```yaml title="destroy.yml"
40 | {!tests/fixtures/integration/test_command/molecule/podman/destroy.yml!}
41 | ```
42 |
--------------------------------------------------------------------------------
/docs/google04e29a42ae6e6cbc.html:
--------------------------------------------------------------------------------
1 | google-site-verification: google04e29a42ae6e6cbc.html
2 |
--------------------------------------------------------------------------------
/docs/guides/custom-image.md:
--------------------------------------------------------------------------------
1 | ## Customizing the Docker Image Used by a Scenario/Platform
2 |
3 | The docker driver supports using pre-built images and `docker build`
4 | -ing local customizations for each scenario's platform. The Docker
5 | image used by a scenario is governed by the following configuration
6 | items:
7 |
8 | 1. `platforms[*].image`: Docker image name:tag to use as base image.
9 |
10 | 2. `platforms[*].pre_build_image`: Whether to customize base image or
11 | use as-is[^1].
12 |
13 | > - When `true`, use the specified `platform[].image` as-is.
14 | > - When `false`, exec `docker build` to customize base image
15 | > using either:
16 | >
17 | > > - Dockerfile specified by `platforms[*].dockerfile` or
18 | > > - Dockerfile rendered from `Dockerfile.j2` template (in
19 | > > scenario dir)
20 |
21 | The `Dockerfile.j2` template is generated at
22 | `molecule init scenario`-time when `--driver-name` is `docker`. The
23 | template can be customized as needed to create the desired modifications
24 | to the Docker image used in the scenario.
25 |
26 | Note: `platforms[*].pre_build_image` defaults to `true` in each
27 | scenario's generated `molecule.yml` file.
28 |
29 | [^1]:
30 | [Implementation in molecule docker
31 | driver](https://github.com/ansible-community/molecule-plugins/blob/main/src/molecule_plugins/docker/playbooks/create.yml)
32 |
--------------------------------------------------------------------------------
/docs/guides/docker-rootless.md:
--------------------------------------------------------------------------------
1 | ## Docker With Non-Privileged User
2 |
3 | The Molecule Docker driver executes Ansible playbooks as the
4 | root user. If your workflow requires adding support for running as a
5 | non-privileged user, then adapt `molecule.yml` and `Dockerfile.j2` as
6 | follows.
7 |
8 | Note: The `Dockerfile` templating and image building processes are only
9 | done for scenarios with `pre_build_image = False`, which is not the
10 | default setting in generated `molecule.yml` files.
11 |
12 | To modify the Docker image to support running as a normal user:
13 |
14 | Append the following code block to the end of `Dockerfile.j2`. It
15 | creates an `ansible` user with passwordless sudo privileges. Note the
16 | variable `SUDO_GROUP` depends on the target distribution as Debian uses `sudo`
17 | instead of `wheel` group.
18 |
19 | ```docker
20 | {% raw %}
21 | # Create `ansible` user with sudo permissions and membership in `DEPLOY_GROUP`
22 | # This template gets rendered using `loop: "{{ molecule_yml.platforms }}"`, so
23 | # each `item` is an element of platforms list from the molecule.yml file for this scenario.
24 | ENV ANSIBLE_USER=ansible DEPLOY_GROUP=deployer
25 | ENV SUDO_GROUP={{'sudo' if 'debian' in item.image else 'wheel' }}
26 | RUN set -xe \
27 | && groupadd -r ${ANSIBLE_USER} \
28 | && groupadd -r ${DEPLOY_GROUP} \
29 | && useradd -m -g ${ANSIBLE_USER} ${ANSIBLE_USER} \
30 | && usermod -aG ${SUDO_GROUP} ${ANSIBLE_USER} \
31 | && usermod -aG ${DEPLOY_GROUP} ${ANSIBLE_USER} \
32 | && sed -i "/^%${SUDO_GROUP}/s/ALL\$/NOPASSWD:ALL/g" /etc/sudoers
33 | {% endraw %}
34 | ```
35 |
36 | Modify `provisioner.inventory` in `molecule.yml` as follows:
37 |
38 | ```yaml
39 | platforms:
40 | - name: instance
41 | image: quay.io/centos/centos:stream8
42 | # …
43 | ```
44 |
45 | ```yaml
46 | provisioner:
47 | name: ansible
48 | # …
49 | inventory:
50 | host_vars:
51 | # setting for the platform instance named 'instance'
52 | instance:
53 | ansible_user: ansible
54 | ```
55 |
56 | Make sure to use your **platform instance name**. In this case
57 | `instance`.
58 |
59 | An example for a different platform instance name:
60 |
61 | ```yaml
62 | platforms:
63 | - name: centos8
64 | image: quay.io/centos/centos:stream8
65 | # …
66 | ```
67 |
68 | ```yaml
69 | provisioner:
70 | name: ansible
71 | # …
72 | inventory:
73 | host_vars:
74 | # setting for the platform instance named 'centos8'
75 | centos8:
76 | ansible_user: ansible
77 | ```
78 |
79 | To test it, add the following task to `tasks/main.yml`. It fails,
80 | because the non-privileged user is not allowed to create a folder in
81 | `/opt/`. This needs to be performed using `sudo`.
82 |
83 | To perform the task using `sudo`, uncomment `become: yes`. Now the task
84 | will succeed.
85 |
86 | ```yaml
87 | - name: Create apps dir
88 | file:
89 | path: /opt/examples
90 | owner: ansible
91 | group: deployer
92 | mode: 0775
93 | state: directory
94 | # become: yes
95 | ```
96 |
97 | Don't forget to run `molecule destroy` if image has already been
98 | created.
99 |
--------------------------------------------------------------------------------
/docs/guides/inside-a-container.md:
--------------------------------------------------------------------------------
1 | ## Running inside a container
2 |
3 | Molecule is built into a Docker image by the [Ansible Dev Tools](https://ansible.readthedocs.io/projects/dev-tools/container/) project.
4 |
5 | Any questions or bugs related to use of Molecule from within a container
6 | should be addressed by the Ansible Dev Tools
7 | project.
8 |
--------------------------------------------------------------------------------
/docs/guides/monolith.md:
--------------------------------------------------------------------------------
1 | ## Monolith Repo
2 |
3 | Molecule is generally used to test playbooks or roles in isolation.
4 | However, it can also test them from a monolith repo.
5 |
6 | ```bash
7 | $ tree monolith-repo -L 3 --prune
8 | monolith-repo
9 | ├── library
10 | │ └── foo.py
11 | ├── plugins
12 | │ └── filters
13 | │ └── foo.py
14 | └── roles
15 | ├── bar
16 | │ └── README.md
17 | ├── baz
18 | │ └── README.md
19 | └── foo
20 | └── README.md
21 | ```
22 |
23 | The role initialized with Molecule (baz in this case) would simply
24 | reference the dependent roles via its `converge.yml` or meta
25 | dependencies.
26 |
27 | Molecule can test complex scenarios leveraging this technique.
28 |
29 | ```bash
30 | $ cd monolith-repo/roles/baz
31 | $ molecule test
32 | ```
33 |
34 | Molecule is simply setting the `ANSIBLE_*` environment variables. To
35 | view the environment variables set during a Molecule operation pass the
36 | `--debug` flag.
37 |
38 | ```bash
39 | $ molecule --debug test
40 |
41 | DEBUG: ANSIBLE ENVIRONMENT
42 | ---
43 | ANSIBLE_CONFIG: /private/tmp/monolith-repo/roles/baz/molecule/default/.molecule/ansible.cfg
44 | ```
45 |
46 | Molecule can be customized any number of ways. Updating the
47 | provisioner's env section in `molecule.yml` to suit the needs of the
48 | developer and layout of the project.
49 |
50 | ```yaml
51 | provisioner:
52 | name: ansible
53 | env:
54 | ANSIBLE_$VAR: $VALUE
55 | ```
56 |
--------------------------------------------------------------------------------
/docs/guides/parallel.md:
--------------------------------------------------------------------------------
1 | ## Running Molecule processes in parallel mode
2 |
3 | !!! warning
4 |
5 | This functionality should be considered experimental. It is part of
6 | ongoing work towards enabling parallelizable functionality across all
7 | moving parts in the execution of the Molecule feature set.
8 |
9 | !!! note
10 |
11 | Only the following sequences support parallelizable functionality:
12 |
13 | > - `check_sequence`: `molecule check --parallel`
14 | > - `destroy_sequence`: `molecule destroy --parallel`
15 | > - `test_sequence`: `molecule test --parallel`
16 |
17 | It is currently only available for use with the Docker driver.
18 |
19 | When Molecule receives the `--parallel` flag it will generate a
20 | [UUID](https://en.wikipedia.org/wiki/Universally_unique_identifier) for
21 | the duration of the testing sequence and will use that unique identifier
22 | to cache the run-time state for that process. The parallel Molecule
23 | processes cached state and created instances will therefore not
24 | interfere with each other.
25 |
26 | Molecule uses a new and separate caching folder for this in the
27 | `$HOME/.cache/molecule_parallel` location. Molecule exposes a new
28 | environment variable `MOLECULE_PARALLEL` which can enable this
29 | functionality.
30 |
31 | It is possible to run Molecule processes in parallel using another tool
32 | to orchestrate the parallelization (such as [GNU
33 | Parallel](https://www.gnu.org/software/parallel/) or
34 | [Pytest](https://docs.pytest.org/en/latest/)). If you do so, make sure
35 | Molecule knows it is running in parallel mode by specifying the
36 | `--parallel` flag to your command(s) to avoid concurrency issues.
37 |
--------------------------------------------------------------------------------
/docs/guides/podman-inside-docker.md:
--------------------------------------------------------------------------------
1 | # Podman inside Docker
2 |
3 | Sometimes your CI system comes prepared to run with Docker but you want
4 | to test podman into it. This `prepare.yml` playbook would let podman run
5 | inside a privileged Docker host by adding some required settings:
6 |
7 | ```yaml
8 | - name: prepare
9 | hosts: podman-in-docker
10 | tasks:
11 | - name: install fuse-overlayfs
12 | package:
13 | name:
14 | - fuse-overlayfs
15 |
16 | - name: create containers config dir
17 | file:
18 | group: root
19 | mode: a=rX,u+w
20 | owner: root
21 | path: /etc/containers
22 | state: directory
23 |
24 | - name: make podman use fuse-overlayfs storage
25 | copy:
26 | content: |
27 | # See man 5 containers-storage.conf for more information
28 | [storage]
29 | driver = "overlay"
30 | [storage.options.overlay]
31 | mount_program = "/usr/bin/fuse-overlayfs"
32 | mountopt = "nodev,metacopy=on"
33 | dest: /etc/containers/storage.conf
34 | group: root
35 | mode: a=r,u+w
36 | owner: root
37 |
38 | - name: make podman use cgroupfs cgroup manager
39 | copy:
40 | content: |
41 | # See man 5 libpod.conf for more information
42 | cgroup_manager = "cgroupfs"
43 | dest: /etc/containers/libpod.conf
44 | group: root
45 | mode: a=r,u+w
46 | owner: root
47 | ```
48 |
49 | Another option is to configure the same settings directly into the
50 | `molecule.yml` definition:
51 |
52 | ```yaml
53 | driver:
54 | name: podman
55 | platforms:
56 | - name: podman-in-docker
57 | # ... other options
58 | cgroup_manager: cgroupfs
59 | storage_opt: overlay.mount_program=/usr/bin/fuse-overlayfs
60 | storage_driver: overlay
61 | ```
62 |
63 | At the time of writing, [Gitlab CI shared runners run privileged Docker
64 | hosts](https://docs.gitlab.com/ee/user/gitlab_com/#shared-runners) and
65 | are suitable for these workarounds.
66 |
--------------------------------------------------------------------------------
/docs/guides/sharing.md:
--------------------------------------------------------------------------------
1 | ## Sharing Across Scenarios
2 |
3 | Playbooks and tests can be shared across scenarios.
4 |
5 | ```bash
6 | $ tree shared-tests
7 | shared-tests
8 | ├── molecule
9 | │ ├── centos
10 | │ │ └── molecule.yml
11 | │ ├── resources
12 | │ │ ├── playbooks
13 | │ │ │ ├── Dockerfile.j2 (optional)
14 | │ │ │ ├── create.yml
15 | │ │ │ ├── destroy.yml
16 | │ │ │ ├── converge.yml # <-- previously called playbook.yml
17 | │ │ │ └── prepare.yml
18 | │ │ └── tests
19 | │ │ └── test_default.py
20 | │ ├── ubuntu
21 | │ │ └── molecule.yml
22 | │ └── ubuntu-upstart
23 | │ └── molecule.yml
24 | ```
25 |
26 | Tests and playbooks can be shared across scenarios.
27 |
28 | In this example the `tests` directory lives in a shared
29 | location and `molecule.yml` points to the shared tests.
30 |
31 | ```yaml
32 | verifier:
33 | name: testinfra
34 | directory: ../resources/tests/
35 | ```
36 |
37 | In this second example the actions `create`,
38 | `destroy`, `converge` and `prepare`
39 | are loaded from a shared directory.
40 |
41 | ```yaml
42 | provisioner:
43 | name: ansible
44 | playbooks:
45 | create: ../resources/playbooks/create.yml
46 | destroy: ../resources/playbooks/destroy.yml
47 | converge: ../resources/playbooks/converge.yml
48 | prepare: ../resources/playbooks/prepare.yml
49 | ```
50 |
--------------------------------------------------------------------------------
/docs/guides/systemd-container.md:
--------------------------------------------------------------------------------
1 | ## Systemd Container
2 |
3 | To start a service which requires systemd, [in a non-privileged
4 | container](https://developers.redhat.com/blog/2016/09/13/running-systemd-in-a-non-privileged-container/),
5 | configure `molecule.yml` with a systemd compliant image, tmpfs, volumes,
6 | and command as follows.
7 |
8 | ```yaml
9 | platforms:
10 | - name: instance
11 | image: quay.io/centos/centos:stream8
12 | command: /sbin/init
13 | tmpfs:
14 | - /run
15 | - /tmp
16 | volumes:
17 | - /sys/fs/cgroup:/sys/fs/cgroup:ro
18 | ```
19 |
20 | When needed, such security profiles can be reused (for example [the one
21 | available in
22 | Fedora](https://src.fedoraproject.org/rpms/docker/raw/88fa030b904d7af200b150e10ea4a700f759cca4/f/seccomp.json)):
23 |
24 | ```yaml
25 | platforms:
26 | - name: instance
27 | image: debian:stretch
28 | command: /sbin/init
29 | security_opts:
30 | - seccomp=path/to/seccomp.json
31 | tmpfs:
32 | - /run
33 | - /tmp
34 | volumes:
35 | - /sys/fs/cgroup:/sys/fs/cgroup:ro
36 | ```
37 |
38 | The developer can also opt to [start the container with extended
39 | privileges](https://blog.docker.com/2013/09/docker-can-now-run-within-docker/),
40 | by either giving it `SYS_ADMIN` capabilities or running it in
41 | `privileged` mode.
42 |
43 | !!! warning
44 |
45 | Use caution when using `privileged` mode or `SYS_ADMIN` capabilities as
46 | it grants the container elevated access to the underlying system.
47 |
48 | To limit the scope of the extended privileges, grant `SYS_ADMIN`
49 | capabilities along with the same image, command, and volumes as shown in
50 | the `non-privileged` example.
51 |
52 | ```yaml
53 | platforms:
54 | - name: instance
55 | image: quay.io/centos/centos:stream8
56 | command: /sbin/init
57 | capabilities:
58 | - SYS_ADMIN
59 | volumes:
60 | - /sys/fs/cgroup:/sys/fs/cgroup:ro
61 | ```
62 |
63 | To start the container in `privileged` mode, set the privileged flag
64 | along with the same image and command as shown in the `non-privileged`
65 | example.
66 |
67 | ```yaml
68 | platforms:
69 | - name: instance
70 | image: quay.io/centos/centos:stream8
71 | command: /sbin/init
72 | privileged: True
73 | ```
74 |
--------------------------------------------------------------------------------
/docs/images/collection_structure.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ansible/molecule/bfb18224c98e7ad4cc0c49b03a8398df1f550a01/docs/images/collection_structure.png
--------------------------------------------------------------------------------
/docs/index.md:
--------------------------------------------------------------------------------
1 | ---
2 | hide:
3 | - navigation
4 | - toc
5 | ---
6 |
7 | # About Ansible Molecule
8 |
9 | Molecule project is designed to aid in the development and testing of
10 | [Ansible](https://ansible.com) roles.
11 |
12 | Molecule provides support for testing with multiple instances, operating
13 | systems and distributions, virtualization providers, test frameworks and
14 | testing scenarios.
15 |
16 | Molecule encourages an approach that results in consistently developed
17 | roles that are well-written, easily understood and maintained.
18 |
19 | Molecule supports only the latest two major versions of Ansible (N/N-1).
20 |
21 | Once installed, the command line can be called using any of the methods
22 | below:
23 |
24 | ```bash
25 | molecule ...
26 | python3 -m molecule ... # python module calling method
27 | ```
28 |
29 | # External Resources
30 |
31 | Below you can see a list of useful articles and presentations, recently
32 | updated being listed first:
33 |
34 | - [Ansible Collections: Role Tests with
35 | Molecule](https://ericsysmin.com/2020/04/30/ansible-collections-role-tests-with-molecule/)
36 | @ericsysmin
37 | - [Molecule v3 Slides](https://sbarnea.com/slides/molecule/#/)
38 | @ssbarnea.
39 | - [Testing your Ansible roles with
40 | Molecule](https://www.jeffgeerling.com/blog/2018/testing-your-ansible-roles-molecule)
41 | @geerlingguy
42 | - [How to test Ansible and don't go
43 | nuts](https://www.goncharov.xyz/it/ansible-testing-en.html)
44 | @ultral
45 |
--------------------------------------------------------------------------------
/docs/next.md:
--------------------------------------------------------------------------------
1 | # Molecule Next
2 |
3 | Molecule "next" is the future major version of molecule, one that is currently
4 | available from the main branch. One of the main goals of the new version is to
5 | reduce the amount of magic and just rely on ansible core features.
6 |
7 | # Implemented changes
8 |
9 | - `roles-path` and `collections-paths` are no longer configurable for
10 | dependencies. Users are expected to make use of [ansible.cfg](https://docs.ansible.com/ansible/latest/reference_appendices/config.html) file to
11 | alter them when needed.
12 |
13 | - `molecule init` command is now only available to create a scenario
14 | using `molecule init scenario`.
15 | Users will no longer be able to create a role.
16 | Instead, users can make use of [ansible-galaxy](https://docs.ansible.com/ansible/latest/galaxy/dev_guide.html) to create a collection or role.
17 |
18 | - From v6, `testinfra` is now an optional dependency.
19 | It will be removed in the next major release(v7).
20 |
21 | # Planned changes
22 |
23 | - Refactoring how dependencies are installed
24 | - Bringing ephemeral directory under scenario folder instead of the current
25 | inconvenient location under `~/.cache/molecule/...`
26 | - Addition of a minimal `ansible.cfg` file under the scenario folder that can
27 | be used to tell Ansible from where to load testing content. This is to replace
28 | current Molecule magic around roles, collections and library paths and
29 | test inventory location. Once done you will be able to run molecule playbooks with Ansible directly without
30 | having to define these folders.
31 |
--------------------------------------------------------------------------------
/docs/redirects.yml:
--------------------------------------------------------------------------------
1 | # Authoritative list of redirects we have configured in RTD,
2 | # https://pypi.org/project/readthedocs-cli/
3 | ---
4 | - type: page
5 | from_url: /en/latest/
6 | to_url: /
7 | - type: page
8 | from_url: /en/latest/getting-started/
9 | to_url: /getting-started/
10 | # Keep this last or it will match all the other rules
11 | - type: sphinx_htmldir
12 | from_url: ""
13 | to_url: ""
14 |
--------------------------------------------------------------------------------
/docs/usage.md:
--------------------------------------------------------------------------------
1 | # Command Line Reference
2 |
3 | ## Special commands
4 |
5 | - drivers
6 | - init
7 | - list
8 | - login
9 | - matrix
10 | - reset
11 |
12 | ## Valid actions
13 |
14 | - check
15 | - cleanup
16 |
17 | - This action has cleanup and is not enabled by default.
18 | See the provisioner's documentation for further details.
19 |
20 | - **converge** : Converge will execute the sequence necessary to converge the instances.
21 | - create \*\* driver
22 | - dependency
23 | - destroy \*\* all, parallel, driver
24 | - idempotence
25 | - prepare \*\* force
26 | - side-effect
27 | - syntax
28 | - test - \*\* - Test command will execute the sequence necessary to test the instances.
29 | - verify
30 |
31 | ### -s, --scenario-name
32 |
33 | ### --parallel / --no-parallel
34 |
35 | ### Passing extra arguments to the provisioner
36 |
37 | ```
38 | ... -- -vvv --tags foo,bar
39 |
40 | Providing additional command line arguments to the `ansible-playbook`
41 | command. Use this option with care, as there is no sanitation or
42 | validation of input. Options passed on the CLI override options
43 | provided in provisioner's `options` section of `molecule.yml`.
44 | ```
45 |
46 | ## molecule init
47 |
48 | ### molecule init scenario
49 |
50 | ## molecule list
51 |
52 | List command shows information about current scenarios.
53 |
54 | ```
55 | molecule list
56 | ```
57 |
58 | ## molecule login
59 |
60 | ## molecule matrix
61 |
62 | Matrix will display the subcommand's ordered list of actions, which can
63 | be changed in
64 | [scenario](configuration.md#scenario)
65 | configuration.
66 |
67 | ## Test sequence commands
68 |
69 | We can tell Molecule to create an instance with:
70 |
71 | ```bash
72 | molecule create
73 | ```
74 |
75 | We can verify that Molecule has created the instance and they're up and
76 | running with:
77 |
78 | ```bash
79 | molecule list
80 | ```
81 |
82 | Now, let's add a task to our role under `tasks/main.yml` file like so:
83 |
84 | ```yaml
85 | - name: Molecule Hello World!
86 | ansible.builtin.debug:
87 | msg: Hello, World!
88 | ```
89 |
90 | We can then tell Molecule to test our role against our instance with:
91 |
92 | ```bash
93 | molecule converge
94 | ```
95 |
96 | If we want to manually inspect the instance afterward, we can run:
97 |
98 | ```bash
99 | molecule login
100 | ```
101 |
102 | We now have a free hand to experiment with the instance state.
103 |
104 | Finally, we can exit the instance and destroy it with:
105 |
106 | ```bash
107 | molecule destroy
108 | ```
109 |
110 | !!! note
111 |
112 | If Molecule reports any errors, it can be useful to pass the `--debug`
113 | option to get more verbose output.
114 |
--------------------------------------------------------------------------------
/mise.toml:
--------------------------------------------------------------------------------
1 | [settings]
2 | idiomatic_version_file_enable_tools = []
3 |
--------------------------------------------------------------------------------
/molecule:
--------------------------------------------------------------------------------
1 | tests/fixtures/integration/test_command/molecule
--------------------------------------------------------------------------------
/src/molecule/__init__.py:
--------------------------------------------------------------------------------
1 | # Copyright (c) 2019 Red Hat, Inc.
2 | # Copyright (c) 2015-2018 Cisco Systems, 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 | """Molecule version information."""
22 |
23 | from __future__ import annotations
24 |
25 |
26 | try:
27 | from ._version import ( # type: ignore[import-not-found, unused-ignore]
28 | __version__,
29 | version,
30 | )
31 | except ImportError: # pragma: no cover
32 | __version__ = "0.1.dev1"
33 | version = __version__
34 |
35 | __all__ = ("__version__", "version")
36 |
--------------------------------------------------------------------------------
/src/molecule/__main__.py:
--------------------------------------------------------------------------------
1 | # Copyright (c) 2018 Red Hat, 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 | """Molecule CLI main entry point."""
21 |
22 | from __future__ import annotations
23 |
24 | from molecule.shell import main
25 |
26 |
27 | if __name__ == "__main__":
28 | main() # pylint: disable=no-value-for-parameter
29 |
--------------------------------------------------------------------------------
/src/molecule/app.py:
--------------------------------------------------------------------------------
1 | """Molecule Application Module."""
2 |
3 | from __future__ import annotations
4 |
5 | from functools import lru_cache
6 | from pathlib import Path
7 | from subprocess import CalledProcessError, CompletedProcess
8 | from typing import TYPE_CHECKING
9 |
10 | from ansible_compat.runtime import Runtime
11 |
12 | from molecule.util import print_environment_vars
13 |
14 |
15 | if TYPE_CHECKING:
16 | from pathlib import Path
17 |
18 |
19 | class App:
20 | """App class that keep runtime status."""
21 |
22 | def __init__(self, path: Path) -> None:
23 | """Create a new app instance.
24 |
25 | Args:
26 | path: The path to the project.
27 | """
28 | self.runtime = Runtime(project_dir=path, isolated=False)
29 |
30 | def run_command( # noqa: PLR0913
31 | self,
32 | cmd: str | list[str],
33 | env: dict[str, str] | None = None,
34 | cwd: Path | None = None,
35 | *,
36 | debug: bool = False,
37 | echo: bool = False, # noqa: ARG002
38 | quiet: bool = False, # noqa: ARG002
39 | check: bool = False,
40 | ) -> CompletedProcess[str]:
41 | """Execute the given command and returns None.
42 |
43 | Args:
44 | cmd: A list of strings containing the command to run.
45 | env: A dict containing the shell's environment.
46 | cwd: An optional Path to the working directory.
47 | debug: An optional bool to toggle debug output.
48 | echo: An optional bool to toggle command echo.
49 | quiet: An optional bool to toggle command output.
50 | check: An optional bool to toggle command error checking.
51 |
52 | Returns:
53 | A completed process object.
54 |
55 | Raises:
56 | CalledProcessError: If return code is nonzero and check is True.
57 | """
58 | if debug:
59 | print_environment_vars(env)
60 |
61 | result = self.runtime.run(
62 | args=cmd,
63 | env=env,
64 | cwd=cwd,
65 | tee=True,
66 | set_acp=False,
67 | )
68 | if result.returncode != 0 and check:
69 | raise CalledProcessError(
70 | returncode=result.returncode,
71 | cmd=result.args,
72 | output=result.stdout,
73 | stderr=result.stderr,
74 | )
75 | return result
76 |
77 |
78 | @lru_cache
79 | def get_app(path: Path) -> App:
80 | """Return the app instance.
81 |
82 | Args:
83 | path: The path to the project.
84 |
85 | Returns:
86 | App: The app instance.
87 | """
88 | return App(path)
89 |
--------------------------------------------------------------------------------
/src/molecule/command/__init__.py:
--------------------------------------------------------------------------------
1 | # noqa: D104
2 | #
3 | # Copyright (c) 2015-2018 Cisco Systems, Inc.
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
7 | # deal in the Software without restriction, including without limitation the
8 | # rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
9 | # sell 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
13 | # all 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
20 | # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
21 | # DEALINGS IN THE SOFTWARE.
22 |
23 | # NOTE(retr0h): Importing into the ``molecule.command`` namespace, to prevent
24 | # collisions (e.g. ``list``). The CLI usage may conflict with reserved words
25 | # or builtins.
26 | from __future__ import annotations
27 |
28 | from molecule.command import (
29 | base, # noqa: F401
30 | check, # noqa: F401
31 | cleanup, # noqa: F401
32 | converge, # noqa: F401
33 | create, # noqa: F401
34 | dependency, # noqa: F401
35 | destroy, # noqa: F401
36 | drivers, # noqa: F401
37 | idempotence, # noqa: F401
38 | list, # noqa: A004, F401
39 | login, # noqa: F401
40 | matrix, # noqa: F401
41 | prepare, # noqa: F401
42 | reset, # noqa: F401
43 | side_effect, # noqa: F401
44 | syntax, # noqa: F401
45 | test, # noqa: F401
46 | verify, # noqa: F401
47 | )
48 | from molecule.command.init import init # noqa: F401
49 |
--------------------------------------------------------------------------------
/src/molecule/command/dependency.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 | """Dependency Command Module."""
21 |
22 | from __future__ import annotations
23 |
24 | import logging
25 |
26 | from typing import TYPE_CHECKING
27 |
28 | import click
29 |
30 | from molecule.command import base
31 |
32 |
33 | if TYPE_CHECKING:
34 | from molecule.types import CommandArgs, MoleculeArgs
35 |
36 |
37 | LOG = logging.getLogger(__name__)
38 |
39 |
40 | class Dependency(base.Base):
41 | """Dependency Command Class."""
42 |
43 | def execute(self, action_args: list[str] | None = None) -> None: # noqa: ARG002
44 | """Execute the actions necessary to perform a `molecule dependency`.
45 |
46 | Args:
47 | action_args: Arguments for this command. Unused.
48 | """
49 | if self._config.dependency:
50 | self._config.dependency.execute()
51 |
52 |
53 | @base.click_command_ex()
54 | @click.pass_context
55 | @base.click_command_options
56 | def dependency(
57 | ctx: click.Context,
58 | /,
59 | scenario_name: list[str] | None,
60 | exclude: list[str],
61 | *,
62 | __all: bool,
63 | report: bool,
64 | shared_inventory: bool,
65 | ) -> None: # pragma: no cover
66 | """Manage the role's dependencies.
67 |
68 | \f
69 | Args:
70 | ctx: Click context object holding commandline arguments.
71 | scenario_name: Name of the scenario to target.
72 | exclude: Name of the scenarios to avoid targeting.
73 | __all: Whether molecule should target scenario_name or all scenarios.
74 | report: Whether to show an after-run summary report.
75 | shared_inventory: Whether the inventory should be shared between scenarios.
76 | """ # noqa: D301
77 | args: MoleculeArgs = ctx.obj.get("args")
78 | subcommand = base._get_subcommand(__name__) # noqa: SLF001
79 | command_args: CommandArgs = {
80 | "subcommand": subcommand,
81 | "report": report,
82 | "shared_inventory": shared_inventory,
83 | }
84 |
85 | if __all:
86 | scenario_name = None
87 |
88 | base.execute_cmdline_scenarios(scenario_name, args, command_args, excludes=exclude)
89 |
--------------------------------------------------------------------------------
/src/molecule/command/drivers.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 | """Create Command Module."""
21 |
22 | from __future__ import annotations
23 |
24 | import logging
25 |
26 | import click
27 |
28 | from molecule import api
29 | from molecule.command import base
30 | from molecule.console import console
31 |
32 |
33 | LOG = logging.getLogger(__name__)
34 |
35 |
36 | @base.click_command_ex()
37 | @click.pass_context
38 | @click.option(
39 | "--format",
40 | "-f",
41 | type=click.Choice(["simple", "plain"]),
42 | default="simple",
43 | help="Change output format. (simple)",
44 | )
45 | def drivers(
46 | ctx: click.Context, # noqa: ARG001
47 | format: str, # noqa: A002
48 | ) -> None: # pragma: no cover
49 | """List drivers.
50 |
51 | \f
52 | Args:
53 | ctx: Click context object holding commandline arguments.
54 | format: Output format to use.
55 | """ # noqa: D301
56 | drivers = [] # pylint: disable=redefined-outer-name
57 | for driver in api.drivers().values():
58 | description = str(driver)
59 | if format == "plain":
60 | description = f"{driver!s:16s}[logging.level.notset] {driver.title} Version {driver.version} from {driver.module} python module.)[/logging.level.notset]"
61 | drivers.append([driver, description])
62 | console.print(description)
63 |
--------------------------------------------------------------------------------
/src/molecule/command/init/__init__.py:
--------------------------------------------------------------------------------
1 | # noqa: D104
2 |
--------------------------------------------------------------------------------
/src/molecule/command/init/base.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 | """Base class used by init command."""
21 |
22 | from __future__ import annotations
23 |
24 | import abc
25 | import logging
26 |
27 | from pathlib import Path
28 |
29 | from molecule.exceptions import MoleculeError
30 |
31 |
32 | LOG = logging.getLogger(__name__)
33 |
34 |
35 | class Base(abc.ABC):
36 | """Init Command Base Class."""
37 |
38 | @abc.abstractmethod
39 | def execute(self, action_args: list[str] | None = None) -> None:
40 | """Abstract method to execute the command.
41 |
42 | Args:
43 | action_args: An optional list of arguments to pass to the action.
44 | """
45 |
46 | def _validate_template_dir(self, template_dir: str) -> None:
47 | if not Path(template_dir).is_dir():
48 | msg = f"The specified template directory ({template_dir!s}) does not exist"
49 | raise MoleculeError(message=msg)
50 |
--------------------------------------------------------------------------------
/src/molecule/command/init/init.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 | """Base class used by init command."""
21 |
22 | from __future__ import annotations
23 |
24 | import logging
25 |
26 | from molecule.command import base
27 | from molecule.command.init import scenario
28 |
29 |
30 | LOG = logging.getLogger(__name__)
31 |
32 |
33 | @base.click_group_ex()
34 | def init() -> None: # pragma: no cover
35 | """Initialize a new scenario."""
36 |
37 |
38 | init.add_command(scenario.scenario)
39 |
--------------------------------------------------------------------------------
/src/molecule/command/reset.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 | """Lint Command Module."""
21 |
22 | from __future__ import annotations
23 |
24 | import logging
25 |
26 | from typing import TYPE_CHECKING
27 |
28 | import click
29 |
30 | from molecule.api import drivers
31 | from molecule.command import base
32 |
33 |
34 | if TYPE_CHECKING:
35 | from molecule.types import CommandArgs, MoleculeArgs
36 |
37 |
38 | LOG = logging.getLogger(__name__)
39 |
40 |
41 | @base.click_command_ex()
42 | @click.pass_context
43 | @click.option(
44 | "--scenario-name",
45 | "-s",
46 | default=base.MOLECULE_DEFAULT_SCENARIO_NAME,
47 | help=f"Name of the scenario to target. ({base.MOLECULE_DEFAULT_SCENARIO_NAME})",
48 | )
49 | def reset(
50 | ctx: click.Context,
51 | scenario_name: str,
52 | ) -> None: # pragma: no cover
53 | """Reset molecule temporary folders.
54 |
55 | \f
56 | Args:
57 | ctx: Click context object holding commandline arguments.
58 | scenario_name: Name of the scenario to target.
59 | """ # noqa: D301
60 | args: MoleculeArgs = ctx.obj.get("args")
61 | subcommand = base._get_subcommand(__name__) # noqa: SLF001
62 | command_args: CommandArgs = {"subcommand": subcommand}
63 |
64 | base.execute_cmdline_scenarios([scenario_name], args, command_args)
65 | for driver in drivers().values():
66 | driver.reset()
67 |
--------------------------------------------------------------------------------
/src/molecule/command/syntax.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 | """Syntax Command Module."""
21 |
22 | from __future__ import annotations
23 |
24 | import logging
25 |
26 | from typing import TYPE_CHECKING
27 |
28 | import click
29 |
30 | from molecule.command import base
31 |
32 |
33 | if TYPE_CHECKING:
34 | from molecule.types import CommandArgs, MoleculeArgs
35 |
36 |
37 | LOG = logging.getLogger(__name__)
38 |
39 |
40 | class Syntax(base.Base):
41 | """Syntax Command Class."""
42 |
43 | def execute(self, action_args: list[str] | None = None) -> None: # noqa: ARG002
44 | """Execute the actions necessary to perform a `molecule syntax`.
45 |
46 | Args:
47 | action_args: Arguments for this command. Unused.
48 | """
49 | if self._config.provisioner:
50 | self._config.provisioner.syntax()
51 |
52 |
53 | @base.click_command_ex()
54 | @click.pass_context
55 | @base.click_command_options
56 | def syntax(
57 | ctx: click.Context,
58 | /,
59 | scenario_name: list[str] | None,
60 | exclude: list[str],
61 | *,
62 | __all: bool,
63 | report: bool,
64 | shared_inventory: bool,
65 | ) -> None: # pragma: no cover
66 | """Use the provisioner to syntax check the role.
67 |
68 | \f
69 | Args:
70 | ctx: Click context object holding commandline arguments.
71 | scenario_name: Name of the scenario to target.
72 | exclude: Name of the scenarios to avoid targeting.
73 | __all: Whether molecule should target scenario_name or all scenarios.
74 | report: Whether to show an after-run summary report.
75 | shared_inventory: Whether the inventory should be shared between scenarios.
76 | """ # noqa: D301
77 | args: MoleculeArgs = ctx.obj.get("args")
78 | subcommand = base._get_subcommand(__name__) # noqa: SLF001
79 | command_args: CommandArgs = {
80 | "subcommand": subcommand,
81 | "report": report,
82 | "shared_inventory": shared_inventory,
83 | }
84 |
85 | if __all:
86 | scenario_name = None
87 |
88 | base.execute_cmdline_scenarios(scenario_name, args, command_args, excludes=exclude)
89 |
--------------------------------------------------------------------------------
/src/molecule/command/verify.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 | """Verify Command Module."""
21 |
22 | from __future__ import annotations
23 |
24 | import logging
25 |
26 | from typing import TYPE_CHECKING
27 |
28 | import click
29 |
30 | from molecule.command import base
31 |
32 |
33 | if TYPE_CHECKING:
34 | from molecule.types import CommandArgs, MoleculeArgs
35 |
36 |
37 | LOG = logging.getLogger(__name__)
38 |
39 |
40 | class Verify(base.Base):
41 | """Verify Command Class."""
42 |
43 | def execute(self, action_args: list[str] | None = None) -> None:
44 | """Execute the actions necessary to perform a `molecule verify`.
45 |
46 | Args:
47 | action_args: Arguments for this command.
48 | """
49 | self._config.verifier.execute(action_args)
50 |
51 |
52 | @base.click_command_ex()
53 | @click.pass_context
54 | @base.click_command_options
55 | def verify(
56 | ctx: click.Context,
57 | /,
58 | scenario_name: list[str] | None,
59 | exclude: list[str],
60 | *,
61 | __all: bool,
62 | report: bool,
63 | shared_inventory: bool,
64 | ) -> None: # pragma: no cover
65 | """Run automated tests against instances.
66 |
67 | \f
68 | Args:
69 | ctx: Click context object holding commandline arguments.
70 | scenario_name: Name of the scenario to target.
71 | exclude: Name of the scenarios to avoid targeting.
72 | __all: Whether molecule should target scenario_name or all scenarios.
73 | report: Whether to show an after-run summary report.
74 | shared_inventory: Whether the inventory should be shared between scenarios.
75 | """ # noqa: D301
76 | args: MoleculeArgs = ctx.obj.get("args")
77 | subcommand = base._get_subcommand(__name__) # noqa: SLF001
78 | command_args: CommandArgs = {
79 | "subcommand": subcommand,
80 | "report": report,
81 | "shared_inventory": shared_inventory,
82 | }
83 |
84 | if __all:
85 | scenario_name = None
86 |
87 | base.execute_cmdline_scenarios(scenario_name, args, command_args, excludes=exclude)
88 |
--------------------------------------------------------------------------------
/src/molecule/console.py:
--------------------------------------------------------------------------------
1 | """Console and terminal utilities."""
2 |
3 | from __future__ import annotations
4 |
5 | import os
6 | import sys
7 |
8 | from typing import Any
9 |
10 | from enrich.console import Console
11 | from rich.style import Style
12 | from rich.theme import Theme
13 |
14 |
15 | theme = Theme(
16 | {
17 | "info": "dim cyan",
18 | "warning": "magenta",
19 | "danger": "bold red",
20 | "scenario": "green",
21 | "action": "green",
22 | "section_title": "bold cyan",
23 | "logging.level.notset": Style(dim=True),
24 | "logging.level.debug": Style(color="white", dim=True),
25 | "logging.level.info": Style(color="blue"),
26 | "logging.level.warning": Style(color="red"),
27 | "logging.level.error": Style(color="red", bold=True),
28 | "logging.level.critical": Style(color="red", bold=True),
29 | "logging.level.success": Style(color="green", bold=True),
30 | },
31 | )
32 |
33 |
34 | # Based on Ansible implementation
35 | def to_bool(a: object) -> bool:
36 | """Return a bool for the arg.
37 |
38 | Args:
39 | a: A value to coerce to bool.
40 |
41 | Returns:
42 | A bool representation of a.
43 | """
44 | if a is None or isinstance(a, bool):
45 | return bool(a)
46 | if isinstance(a, str):
47 | a = a.lower()
48 | return a in ("yes", "on", "1", "true", 1)
49 |
50 |
51 | def should_do_markup() -> bool:
52 | """Decide about use of ANSI colors.
53 |
54 | Returns:
55 | Whether the output should be colored.
56 | """
57 | py_colors = None
58 |
59 | # https://xkcd.com/927/
60 | for v in ["PY_COLORS", "CLICOLOR", "FORCE_COLOR", "ANSIBLE_FORCE_COLOR"]:
61 | value = os.environ.get(v, None)
62 | if value is not None:
63 | py_colors = to_bool(value)
64 | break
65 |
66 | # If deliberately disabled colors
67 | if os.environ.get("NO_COLOR", None):
68 | return False
69 |
70 | # User configuration requested colors
71 | if py_colors is not None:
72 | return to_bool(py_colors)
73 |
74 | term = os.environ.get("TERM", "")
75 | if "xterm" in term:
76 | return True
77 |
78 | if term == "dumb":
79 | return False
80 |
81 | # Use tty detection logic as last resort because there are numerous
82 | # factors that can make isatty return a misleading value, including:
83 | # - stdin.isatty() is the only one returning true, even on a real terminal
84 | # - stderr returning false if user user uses a error stream coloring solution
85 | return sys.stdout.isatty()
86 |
87 |
88 | # Define ANSIBLE_FORCE_COLOR if markup is enabled and another value is not
89 | # already given. This assures that Ansible subprocesses are still colored,
90 | # even if they do not run with a real TTY.
91 | if should_do_markup():
92 | os.environ["ANSIBLE_FORCE_COLOR"] = os.environ.get("ANSIBLE_FORCE_COLOR", "1")
93 |
94 | console_options: dict[str, Any] = {"emoji": False, "theme": theme, "soft_wrap": True}
95 |
96 | console = Console(
97 | force_terminal=should_do_markup(),
98 | theme=theme,
99 | record=True,
100 | redirect=True,
101 | )
102 | console_options_stderr = console_options.copy()
103 | console_options_stderr["stderr"] = True
104 | console_stderr: Console = Console(**console_options_stderr)
105 |
--------------------------------------------------------------------------------
/src/molecule/constants.py:
--------------------------------------------------------------------------------
1 | """Constants used by molecule."""
2 |
3 | from __future__ import annotations
4 |
5 |
6 | RC_SUCCESS = 0
7 | RC_TIMEOUT = 3
8 | RC_SETUP_ERROR = 4 # Broken setup, like missing Ansible
9 | RC_UNKNOWN_ERROR = 5 # Unexpected errors for which we do not have more specific codes, yet
10 |
11 |
12 | MOLECULE_HEADER = "# Molecule managed"
13 |
--------------------------------------------------------------------------------
/src/molecule/data/__init__.py:
--------------------------------------------------------------------------------
1 | """Module containing molecule data files."""
2 |
--------------------------------------------------------------------------------
/src/molecule/data/driver.json:
--------------------------------------------------------------------------------
1 | {
2 | "$defs": {
3 | "MoleculeDriverModel": {
4 | "properties": {
5 | "name": {
6 | "enum": ["delegated", "default"],
7 | "title": "Name",
8 | "type": "string"
9 | },
10 | "options": {
11 | "$ref": "#/$defs/MoleculeDriverOptionsModel"
12 | },
13 | "safe_files": {
14 | "items": {
15 | "type": "string"
16 | },
17 | "title": "SafeFiles",
18 | "type": "array"
19 | },
20 | "ssh_connection_options": {
21 | "items": {
22 | "type": "string"
23 | },
24 | "title": "SshConnectionOptions",
25 | "type": "array"
26 | }
27 | },
28 | "title": "MoleculeDriverModel",
29 | "type": "object"
30 | },
31 | "MoleculeDriverOptionsModel": {
32 | "additionalProperties": false,
33 | "properties": {
34 | "ansible_connection_options": {
35 | "additionalProperties": {
36 | "type": "string"
37 | },
38 | "title": "Ansible Connection Options",
39 | "type": "object"
40 | },
41 | "login_cmd_template": {
42 | "title": "Login Cmd Template",
43 | "type": "string"
44 | },
45 | "managed": {
46 | "title": "Managed",
47 | "type": "boolean"
48 | }
49 | },
50 | "title": "MoleculeDriverOptionsModel",
51 | "type": "object"
52 | },
53 | "MoleculePlatformModel": {
54 | "additionalProperties": true,
55 | "properties": {
56 | "box": {
57 | "title": "Box",
58 | "type": "string"
59 | },
60 | "children": {
61 | "items": {
62 | "type": "string"
63 | },
64 | "type": "array"
65 | },
66 | "groups": {
67 | "items": {
68 | "type": "string"
69 | },
70 | "title": "Groups",
71 | "type": "array"
72 | },
73 | "hostname": {
74 | "title": "Hostname",
75 | "type": ["string", "boolean"]
76 | },
77 | "name": {
78 | "title": "Name",
79 | "type": "string"
80 | }
81 | },
82 | "required": ["name"],
83 | "title": "MoleculePlatformModel",
84 | "type": "object"
85 | }
86 | },
87 | "$id": "https://raw.githubusercontent.com/ansible-community/molecule/main/src/molecule/driver/driver.json",
88 | "$schema": "http://json-schema.org/draft-07/schema",
89 | "examples": ["molecule/*/molecule.yml"],
90 | "properties": {
91 | "driver": {
92 | "$ref": "#/$defs/MoleculeDriverModel"
93 | },
94 | "platforms": {
95 | "items": {
96 | "$ref": "#/$defs/MoleculePlatformModel"
97 | },
98 | "title": "Platforms",
99 | "type": "array"
100 | }
101 | },
102 | "required": ["driver"],
103 | "title": "Molecule Delegated Driver Schema",
104 | "type": "object"
105 | }
106 |
--------------------------------------------------------------------------------
/src/molecule/data/init-scenario.yml:
--------------------------------------------------------------------------------
1 | - name: Create a new molecule scenario
2 | hosts: localhost
3 | gather_facts: false
4 | vars_prompt:
5 | - name: scenario_name
6 | prompt: What is the scenario name?
7 | vars:
8 | dest: "{{ ('molecule/' + (scenario_name | default('default'))) | realpath }}"
9 | tasks:
10 | - name: Check if destination folder exists
11 | ansible.builtin.file:
12 | path: "{{ dest }}"
13 | state: directory
14 | mode: "0700"
15 | - name: Check if destination folder is empty
16 | ansible.builtin.find:
17 | paths: "{{ dest }}"
18 | register: dest_content
19 | - name: Fail if destination folder is not empty
20 | when: dest_content.matched > 0
21 | ansible.builtin.fail:
22 | msg: Refused to expand templates as destination folder '{{ dest }}' as it already has content in it.
23 | - name: Expand templates
24 | vars:
25 | dest_file: "{{ dest }}/{{ item | basename | regex_replace('\\.j2$', '') }}"
26 | ansible.builtin.template:
27 | src: "{{ item }}"
28 | dest: "{{ dest_file }}"
29 | mode: "0644"
30 | with_fileglob:
31 | - templates/scenario/*.j2
32 | loop_control:
33 | label: "{{ dest_file | relpath }}"
34 | when:
35 | - ((driver_name in ['containers', 'docker', 'podman']) and
36 | (item | regex_search('create|destroy') is none) or
37 | (driver_name not in ['containers', 'docker', 'podman']))
38 |
--------------------------------------------------------------------------------
/src/molecule/data/templates/scenario/converge.yml.j2:
--------------------------------------------------------------------------------
1 | ---
2 | - name: Converge
3 | hosts: all
4 | gather_facts: false
5 | tasks:
6 | - name: Replace this task with one that validates your content
7 | ansible.builtin.debug:
8 | msg: "This is the effective test"
9 |
--------------------------------------------------------------------------------
/src/molecule/data/templates/scenario/create.yml.j2:
--------------------------------------------------------------------------------
1 | ---
2 | {% raw -%}
3 | - name: Create
4 | hosts: localhost
5 | connection: local
6 | gather_facts: false
7 | # no_log: "{{ molecule_no_log }}"
8 | tasks:
9 | # TODO: Developer must implement and populate 'server' variable
10 |
11 | - name: Create instance config
12 | when: server.changed | default(false) | bool # noqa no-handler
13 | block:
14 | - name: Populate instance config dict # noqa jinja
15 | ansible.builtin.set_fact:
16 | instance_conf_dict: {}
17 | # instance': "{{ }}",
18 | # address': "{{ }}",
19 | # user': "{{ }}",
20 | # port': "{{ }}",
21 | # 'identity_file': "{{ }}", }
22 | with_items: "{{ server.results }}"
23 | register: instance_config_dict
24 |
25 | - name: Convert instance config dict to a list
26 | ansible.builtin.set_fact:
27 | instance_conf: "{{ instance_config_dict.results | map(attribute='ansible_facts.instance_conf_dict') | list }}"
28 |
29 | - name: Dump instance config
30 | ansible.builtin.copy:
31 | content: |
32 | # Molecule managed
33 |
34 | {{ instance_conf | to_json | from_json | to_yaml }}
35 | dest: "{{ molecule_instance_config }}"
36 | mode: "0600"
37 | {%- endraw %}
38 |
--------------------------------------------------------------------------------
/src/molecule/data/templates/scenario/destroy.yml.j2:
--------------------------------------------------------------------------------
1 | ---
2 | {% raw -%}
3 | - name: Destroy
4 | hosts: localhost
5 | connection: local
6 | gather_facts: false
7 | # no_log: "{{ molecule_no_log }}"
8 | tasks:
9 | # Developer must implement.
10 |
11 | # Mandatory configuration for Molecule to function.
12 |
13 | - name: Populate instance config
14 | ansible.builtin.set_fact:
15 | instance_conf: {}
16 |
17 | - name: Dump instance config
18 | ansible.builtin.copy:
19 | content: |
20 | # Molecule managed
21 |
22 | {{ instance_conf | to_json | from_json | to_yaml }}
23 | dest: "{{ molecule_instance_config }}"
24 | mode: "0600"
25 | when: server.changed | default(false) | bool # noqa no-handler
26 | {%- endraw %}
27 |
--------------------------------------------------------------------------------
/src/molecule/data/templates/scenario/molecule.yml.j2:
--------------------------------------------------------------------------------
1 | ---
2 | driver:
3 | name: {{ driver_name }}
4 | platforms:
5 | - name: instance
6 | {% if driver_name in ['containers', 'docker', 'podman'] %}
7 | image: quay.io/centos/centos:stream8
8 | pre_build_image: true
9 | {% else %}
10 | # you might want to add your own variables here based on what provisioning
11 | # you are doing like:
12 | # image: quay.io/centos/centos:stream8
13 | {% endif %}
14 |
--------------------------------------------------------------------------------
/src/molecule/dependency/__init__.py:
--------------------------------------------------------------------------------
1 | # noqa: D104
2 |
--------------------------------------------------------------------------------
/src/molecule/dependency/ansible_galaxy/collections.py:
--------------------------------------------------------------------------------
1 | """Ansible Galaxy dependencies for lists of collections."""
2 |
3 | from __future__ import annotations
4 |
5 | import logging
6 |
7 | from pathlib import Path
8 | from typing import TYPE_CHECKING, cast
9 |
10 | from molecule import util
11 | from molecule.dependency.ansible_galaxy.base import AnsibleGalaxyBase
12 |
13 |
14 | if TYPE_CHECKING:
15 | from collections.abc import MutableMapping
16 |
17 |
18 | LOG = logging.getLogger(__name__)
19 |
20 |
21 | class Collections(AnsibleGalaxyBase):
22 | """Collection-specific Ansible Galaxy dependency handling.
23 |
24 | Attributes:
25 | FILTER_OPTS: Keys to remove from the dictionary returned by options().
26 | COMMANDS: Arguments to send to ansible-galaxy to install the appropriate type of content.
27 | """
28 |
29 | FILTER_OPTS = ("role-file",)
30 | COMMANDS = ("collection", "install")
31 |
32 | @property
33 | def default_options(self) -> MutableMapping[str, str | bool]:
34 | """Default options for this dependency.
35 |
36 | Returns:
37 | Default options for this dependency.
38 | """
39 | general = super().default_options
40 | specific = util.merge_dicts(
41 | general,
42 | {
43 | "requirements-file": str(
44 | Path(
45 | self._config.scenario.directory,
46 | "collections.yml",
47 | ),
48 | ),
49 | },
50 | )
51 |
52 | return specific # noqa: RET504
53 |
54 | @property
55 | def requirements_file(self) -> str:
56 | """Path to requirements file.
57 |
58 | Returns:
59 | Path to the requirements file for this dependency.
60 | """
61 | return cast("str", self.options["requirements-file"])
62 |
--------------------------------------------------------------------------------
/src/molecule/dependency/ansible_galaxy/roles.py:
--------------------------------------------------------------------------------
1 | """Ansible Galaxy dependencies for lists of roles."""
2 |
3 | from __future__ import annotations
4 |
5 | import logging
6 |
7 | from pathlib import Path
8 | from typing import TYPE_CHECKING, cast
9 |
10 | from molecule import util
11 | from molecule.dependency.ansible_galaxy.base import AnsibleGalaxyBase
12 |
13 |
14 | if TYPE_CHECKING:
15 | from collections.abc import MutableMapping
16 |
17 |
18 | LOG = logging.getLogger(__name__)
19 |
20 |
21 | class Roles(AnsibleGalaxyBase):
22 | """Role-specific Ansible Galaxy dependency handling.
23 |
24 | Attributes:
25 | FILTER_OPTS: Keys to remove from the dictionary returned by options().
26 | COMMANDS: Arguments to send to ansible-galaxy to install the appropriate type of content.
27 | """
28 |
29 | FILTER_OPTS = ("requirements-file",)
30 | COMMANDS = ("install",)
31 |
32 | @property
33 | def default_options(self) -> MutableMapping[str, str | bool]:
34 | """Default options for this dependency.
35 |
36 | Returns:
37 | Default options for this dependency.
38 | """
39 | general = super().default_options
40 | specific = util.merge_dicts(
41 | general,
42 | {
43 | "role-file": str(
44 | Path(
45 | self._config.scenario.directory,
46 | "requirements.yml",
47 | ),
48 | ),
49 | },
50 | )
51 | return specific # noqa: RET504
52 |
53 | @property
54 | def requirements_file(self) -> str:
55 | """Path to requirements file.
56 |
57 | Returns:
58 | Path to the requirements file for this dependency.
59 | """
60 | return cast("str", self.options["role-file"])
61 |
--------------------------------------------------------------------------------
/src/molecule/driver/__init__.py:
--------------------------------------------------------------------------------
1 | # noqa: D104
2 |
--------------------------------------------------------------------------------
/src/molecule/exceptions.py:
--------------------------------------------------------------------------------
1 | """Custom Molecule exceptions."""
2 |
3 | from __future__ import annotations
4 |
5 | import logging
6 |
7 | from typing import TYPE_CHECKING
8 |
9 |
10 | if TYPE_CHECKING:
11 | from collections.abc import Sequence
12 | from warnings import WarningMessage
13 |
14 |
15 | LOG = logging.getLogger(__name__)
16 |
17 |
18 | class MoleculeError(Exception):
19 | """Generic Molecule error."""
20 |
21 | def __init__(
22 | self,
23 | message: str = "",
24 | code: int = 1,
25 | warns: Sequence[WarningMessage] = (),
26 | ) -> None:
27 | """Custom exception to handle scenario run failures.
28 |
29 | Args:
30 | message: The message to display about the problem.
31 | code: Exit code to use when exiting.
32 | warns: Warnings about the problem to issue.
33 | """
34 | super().__init__()
35 |
36 | if message:
37 | LOG.critical(message, extra={"highlighter": False})
38 |
39 | for warn in warns:
40 | LOG.warning(warn.__dict__["message"].args[0])
41 |
42 | self.code = code
43 |
44 |
45 | class ScenarioFailureError(MoleculeError):
46 | """Details about a scenario that failed."""
47 |
--------------------------------------------------------------------------------
/src/molecule/model/__init__.py:
--------------------------------------------------------------------------------
1 | # noqa: D104
2 |
--------------------------------------------------------------------------------
/src/molecule/provisioner/__init__.py:
--------------------------------------------------------------------------------
1 | # noqa: D104
2 |
--------------------------------------------------------------------------------
/src/molecule/provisioner/base.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 | """Provisioner Base Module."""
21 |
22 | from __future__ import annotations
23 |
24 | import abc
25 |
26 | from typing import TYPE_CHECKING
27 |
28 |
29 | if TYPE_CHECKING:
30 | from molecule.config import Config
31 |
32 |
33 | class Base(abc.ABC):
34 | """Provisioner Base Class."""
35 |
36 | def __init__(self, config: Config) -> None:
37 | """Initialize code for all :ref:`Provisioner` classes.
38 |
39 | Args:
40 | config: An instance of a Molecule config.
41 | """
42 | self._config = config
43 |
44 | @property
45 | @abc.abstractmethod
46 | def default_options(self) -> dict[str, str | bool]: # pragma: no cover
47 | """Get default CLI arguments provided to ``cmd``.
48 |
49 | Returns:
50 | The default CLI arguments.
51 | """
52 |
53 | @property
54 | @abc.abstractmethod
55 | def default_env(self) -> dict[str, str]: # pragma: no cover
56 | """Get default env variables provided to ``cmd``.
57 |
58 | Returns:
59 | The default env variables.
60 | """
61 |
62 | @property
63 | @abc.abstractmethod
64 | def name(self) -> str: # pragma: no cover
65 | """Name of the provisioner.
66 |
67 | Returns:
68 | The provisioner's name.
69 | """
70 |
--------------------------------------------------------------------------------
/src/molecule/py.typed:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ansible/molecule/bfb18224c98e7ad4cc0c49b03a8398df1f550a01/src/molecule/py.typed
--------------------------------------------------------------------------------
/src/molecule/status.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 | """Status Module."""
21 |
22 | from __future__ import annotations
23 |
24 | from typing import NamedTuple
25 |
26 |
27 | class Status(NamedTuple):
28 | """Scenario status information.
29 |
30 | Attributes:
31 | instance_name: Name of the instance.
32 | driver_name: Name of the driver being used.
33 | provisioner_name: Name of the provisioner being used.
34 | scenario_name: Name of the scenario being run.
35 | created: Has the scenario been created.
36 | converged: Has the scenario converged.
37 | """
38 |
39 | instance_name: str
40 | driver_name: str
41 | provisioner_name: str
42 | scenario_name: str
43 | created: str
44 | converged: str
45 |
--------------------------------------------------------------------------------
/src/molecule/verifier/__init__.py:
--------------------------------------------------------------------------------
1 | # noqa: D104
2 |
--------------------------------------------------------------------------------
/tests/__init__.py:
--------------------------------------------------------------------------------
1 | # noqa: D104
2 |
--------------------------------------------------------------------------------
/tests/fixtures/integration/test_command/molecule/default/converge.yml:
--------------------------------------------------------------------------------
1 | ---
2 | - name: Converge
3 | hosts: all
4 | gather_facts: false
5 | tasks:
6 | - name: Replace this task with one that validates your content
7 | ansible.builtin.debug:
8 | msg: "This is the effective test"
9 |
--------------------------------------------------------------------------------
/tests/fixtures/integration/test_command/molecule/default/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 | # TODO: Developer must implement and populate 'server' variable
9 |
10 | - name: Create instance config
11 | when: server.changed | default(false) | bool # noqa no-handler
12 | block:
13 | - name: Populate instance config dict # noqa jinja
14 | ansible.builtin.set_fact:
15 | instance_conf_dict:
16 | {
17 | "instance": "{{ }}",
18 | "address": "{{ }}",
19 | "user": "{{ }}",
20 | "port": "{{ }}",
21 | "identity_file": "{{ }}",
22 | }
23 | with_items: "{{ server.results }}"
24 | register: instance_config_dict
25 |
26 | - name: Convert instance config dict to a list
27 | ansible.builtin.set_fact:
28 | instance_conf: "{{ instance_config_dict.results | map(attribute='ansible_facts.instance_conf_dict') | list }}"
29 |
30 | - name: Dump instance config
31 | ansible.builtin.copy:
32 | content: |
33 | # Molecule managed
34 |
35 | {{ instance_conf | to_json | from_json | to_yaml }}
36 | dest: "{{ molecule_instance_config }}"
37 | mode: "0600"
38 |
--------------------------------------------------------------------------------
/tests/fixtures/integration/test_command/molecule/default/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 | # Developer must implement.
9 |
10 | # Mandatory configuration for Molecule to function.
11 |
12 | - name: Populate instance config
13 | ansible.builtin.set_fact:
14 | instance_conf: {}
15 |
16 | - name: Dump instance config
17 | ansible.builtin.copy:
18 | content: |
19 | # Molecule managed
20 |
21 | {{ instance_conf | to_json | from_json | to_yaml }}
22 | dest: "{{ molecule_instance_config }}"
23 | mode: "0600"
24 | when: server.changed | default(false) | bool # noqa no-handler
25 |
--------------------------------------------------------------------------------
/tests/fixtures/integration/test_command/molecule/default/molecule.yml:
--------------------------------------------------------------------------------
1 | ---
2 | platforms:
3 | - name: instance
4 | # you might want to add your own variables here based on what provisioning
5 | # you are doing like:
6 | # image: quay.io/centos/centos:stream8
7 |
--------------------------------------------------------------------------------
/tests/fixtures/integration/test_command/molecule/docker/converge.yml:
--------------------------------------------------------------------------------
1 | - name: Fail if molecule group is missing
2 | hosts: localhost
3 | tasks:
4 | - name: Print some info
5 | ansible.builtin.debug:
6 | msg: "{{ groups }}"
7 |
8 | - name: Assert group existence
9 | ansible.builtin.assert:
10 | that: "'molecule' in groups"
11 | fail_msg: |
12 | molecule group was not found inside inventory groups: {{ groups }}
13 |
14 | - name: Converge
15 | hosts: molecule
16 | # We disable gather facts because it would fail due to our container not
17 | # having python installed. This will not prevent use from running 'raw'
18 | # commands. Most molecule users are expected to use containers that already
19 | # have python installed in order to avoid notable delays installing it.
20 | gather_facts: false
21 | tasks:
22 | - name: Check uname
23 | ansible.builtin.raw: uname -a
24 | register: result
25 | changed_when: false
26 |
27 | - name: Print some info
28 | ansible.builtin.assert:
29 | that: result.stdout is ansible.builtin.search("^Linux")
30 |
--------------------------------------------------------------------------------
/tests/fixtures/integration/test_command/molecule/docker/create.yml:
--------------------------------------------------------------------------------
1 | - name: Create
2 | hosts: localhost
3 | gather_facts: false
4 | vars:
5 | molecule_inventory:
6 | all:
7 | hosts: {}
8 | molecule: {}
9 | tasks:
10 | - name: Create a container
11 | community.docker.docker_container:
12 | name: "{{ item.name }}"
13 | image: "{{ item.image }}"
14 | state: started
15 | command: sleep 1d
16 | log_driver: json-file
17 | register: result
18 | loop: "{{ molecule_yml.platforms }}"
19 |
20 | - name: Print some info
21 | ansible.builtin.debug:
22 | msg: "{{ result.results }}"
23 |
24 | - name: Fail if container is not running
25 | when: >
26 | item.container.State.ExitCode != 0 or
27 | not item.container.State.Running
28 | ansible.builtin.include_tasks:
29 | file: tasks/create-fail.yml
30 | loop: "{{ result.results }}"
31 | loop_control:
32 | label: "{{ item.container.Name }}"
33 |
34 | - name: Add container to molecule_inventory
35 | vars:
36 | inventory_partial_yaml: |
37 | all:
38 | children:
39 | molecule:
40 | hosts:
41 | "{{ item.name }}":
42 | ansible_connection: community.docker.docker
43 | ansible.builtin.set_fact:
44 | molecule_inventory: >
45 | {{ molecule_inventory | combine(inventory_partial_yaml | from_yaml, recursive=true) }}
46 | loop: "{{ molecule_yml.platforms }}"
47 | loop_control:
48 | label: "{{ item.name }}"
49 |
50 | - name: Dump molecule_inventory
51 | ansible.builtin.copy:
52 | content: |
53 | {{ molecule_inventory | to_yaml }}
54 | dest: "{{ molecule_ephemeral_directory }}/inventory/molecule_inventory.yml"
55 | mode: "0600"
56 |
57 | - name: Force inventory refresh
58 | ansible.builtin.meta: refresh_inventory
59 |
60 | - name: Fail if molecule group is missing
61 | ansible.builtin.assert:
62 | that: "'molecule' in groups"
63 | fail_msg: |
64 | molecule group was not found inside inventory groups: {{ groups }}
65 | run_once: true # noqa: run-once[task]
66 |
67 | # we want to avoid errors like "Failed to create temporary directory"
68 | - name: Validate that inventory was refreshed
69 | hosts: molecule
70 | gather_facts: false
71 | tasks:
72 | - name: Check uname
73 | ansible.builtin.raw: uname -a
74 | register: result
75 | changed_when: false
76 |
77 | - name: Display uname info
78 | ansible.builtin.debug:
79 | msg: "{{ result.stdout }}"
80 |
--------------------------------------------------------------------------------
/tests/fixtures/integration/test_command/molecule/docker/destroy.yml:
--------------------------------------------------------------------------------
1 | - name: Destroy molecule containers
2 | hosts: molecule
3 | gather_facts: false
4 | tasks:
5 | - name: Stop and remove container
6 | delegate_to: localhost
7 | community.docker.docker_container:
8 | name: "{{ inventory_hostname }}"
9 | state: absent
10 | auto_remove: true
11 |
12 | - name: Remove dynamic molecule inventory
13 | hosts: localhost
14 | gather_facts: false
15 | tasks:
16 | - name: Remove dynamic inventory file
17 | ansible.builtin.file:
18 | path: "{{ molecule_ephemeral_directory }}/inventory/molecule_inventory.yml"
19 | state: absent
20 |
--------------------------------------------------------------------------------
/tests/fixtures/integration/test_command/molecule/docker/molecule.yml:
--------------------------------------------------------------------------------
1 | dependency:
2 | name: galaxy
3 | options:
4 | requirements-file: requirements.yml
5 | platforms:
6 | - name: molecule-ubuntu
7 | image: ubuntu:18.04
8 |
--------------------------------------------------------------------------------
/tests/fixtures/integration/test_command/molecule/docker/requirements.yml:
--------------------------------------------------------------------------------
1 | collections:
2 | - name: community.docker
3 | version: ">=3.10.4"
4 |
--------------------------------------------------------------------------------
/tests/fixtures/integration/test_command/molecule/docker/tasks/create-fail.yml:
--------------------------------------------------------------------------------
1 | - name: Retrieve container log
2 | ansible.builtin.command:
3 | cmd: >-
4 | {% raw %}
5 | docker logs
6 | {% endraw %}
7 | {{ item.stdout_lines[0] }}
8 | changed_when: false
9 | register: logfile_cmd
10 |
11 | - name: Display container log
12 | ansible.builtin.fail:
13 | msg: "{{ logfile_cmd.stderr }}"
14 |
--------------------------------------------------------------------------------
/tests/fixtures/integration/test_command/molecule/kubevirt/converge.yml:
--------------------------------------------------------------------------------
1 | - name: Fail if molecule group is missing
2 | hosts: localhost
3 | tasks:
4 | - name: Print some info
5 | ansible.builtin.debug:
6 | msg: "{{ groups }}"
7 |
8 | - name: Assert group existence
9 | ansible.builtin.assert:
10 | that: "'molecule' in groups"
11 | fail_msg: |
12 | molecule group was not found inside inventory groups: {{ groups }}
13 |
14 | - name: Converge
15 | hosts: molecule
16 | # We disable gather facts because it would fail due to our container not
17 | # having python installed. This will not prevent use from running 'raw'
18 | # commands. Most molecule users are expected to use containers that already
19 | # have python installed in order to avoid notable delays installing it.
20 | gather_facts: false
21 | tasks:
22 | - name: Check uname
23 | ansible.builtin.raw: uname -a
24 | register: result
25 | changed_when: false
26 |
27 | - name: Print some info
28 | ansible.builtin.assert:
29 | that: result.stdout is ansible.builtin.match("Linux")
30 |
--------------------------------------------------------------------------------
/tests/fixtures/integration/test_command/molecule/kubevirt/destroy.yml:
--------------------------------------------------------------------------------
1 | ---
2 | - name: Destroy
3 | hosts: localhost
4 | connection: local
5 | gather_facts: false
6 | tasks:
7 | - name: Delete VM Instance in KubeVirt
8 | kubernetes.core.k8s:
9 | state: absent
10 | kind: VirtualMachine
11 | name: "{{ vm.name }}"
12 | namespace: "{{ vm.namespace }}"
13 | loop: "{{ molecule_yml.platforms }}"
14 | loop_control:
15 | loop_var: vm
16 |
17 | - name: Delete NodePort Service in KubeVirt
18 | kubernetes.core.k8s:
19 | state: absent
20 | kind: Service
21 | name: "{{ vm.name }}"
22 | namespace: "{{ vm.namespace }}"
23 | loop: "{{ molecule_yml.platforms }}"
24 | loop_control:
25 | loop_var: vm
26 |
--------------------------------------------------------------------------------
/tests/fixtures/integration/test_command/molecule/kubevirt/molecule.yml:
--------------------------------------------------------------------------------
1 | ---
2 | dependency:
3 | name: galaxy
4 | options:
5 | requirements-file: requirements.yml
6 | role-file: requirements.yml
7 | platforms:
8 | - name: rhel9
9 | image: registry.redhat.io/rhel9/rhel-guest-image
10 | namespace:
11 | ssh_service:
12 | type: NodePort
13 | ansible_user: cloud-user
14 | memory: 1Gi
15 | - name: rhel8
16 | image: registry.redhat.io/rhel8/rhel-guest-image
17 | namespace:
18 | ssh_service:
19 | type: NodePort
20 | ansible_user: cloud-user
21 | memory: 1Gi
22 | provisioner:
23 | name: ansible
24 | config_options:
25 | defaults:
26 | interpreter_python: auto_silent
27 | callback_whitelist: profile_tasks, timer, yaml
28 | ssh_connection:
29 | pipelining: false
30 | log: true
31 | verifier:
32 | name: ansible
33 | scenario:
34 | test_sequence:
35 | - dependency
36 | - destroy
37 | - syntax
38 | - create
39 | - converge
40 | - idempotence
41 | - side_effect
42 | - verify
43 | - destroy
44 |
--------------------------------------------------------------------------------
/tests/fixtures/integration/test_command/molecule/kubevirt/requirements.yml:
--------------------------------------------------------------------------------
1 | ---
2 | collections:
3 | - name: kubernetes.core
4 | - name: community.crypto
5 |
--------------------------------------------------------------------------------
/tests/fixtures/integration/test_command/molecule/kubevirt/tasks/create_vm_dictionary.yml:
--------------------------------------------------------------------------------
1 | ---
2 | - name: Create VM dictionary
3 | vars:
4 | # This variable block is setting the `ssh_service_address` variable.
5 | # It first checks if the service type of the SSH service is 'NodePort'.
6 | # If it is, it retrieves the 'nodePort' from the services results.
7 | ssh_service_address: >-
8 | {%- set svc_type = vm.ssh_service.type | default(None) -%}
9 | {%- if svc_type == 'NodePort' -%}
10 | {{ (node_port_services.results | selectattr('vm.name', '==', vm.name) | first)['resources'][0]['spec']['ports'][0]['nodePort'] }}
11 | {%- endif -%}
12 | ansible.builtin.set_fact:
13 | # Here, the task is updating the `molecule_systems` dictionary with new VM information.
14 | # If `molecule_systems` doesn't exist, it is created as an empty dictionary.
15 | # Then it is combined with a new dictionary for the current VM, containing ansible connection details.
16 | molecule_systems: >-
17 | {{
18 | molecule_systems | default({}) | combine({
19 | vm.name: {
20 | 'ansible_user': vm.ansible_user,
21 | 'ansible_host': nodeport_host,
22 | 'ansible_ssh_port': ssh_service_address,
23 | 'ansible_ssh_private_key_file': temporary_ssh_key_path
24 | }
25 | })
26 | }}
27 |
--------------------------------------------------------------------------------
/tests/fixtures/integration/test_command/molecule/podman/converge.yml:
--------------------------------------------------------------------------------
1 | - name: Fail if molecule group is missing
2 | hosts: localhost
3 | tasks:
4 | - name: Print some info
5 | ansible.builtin.debug:
6 | msg: "{{ groups }}"
7 |
8 | - name: Assert group existence
9 | ansible.builtin.assert:
10 | that: "'molecule' in groups"
11 | fail_msg: |
12 | molecule group was not found inside inventory groups: {{ groups }}
13 |
14 | - name: Converge
15 | hosts: molecule
16 | # We disable gather facts because it would fail due to our container not
17 | # having python installed. This will not prevent use from running 'raw'
18 | # commands. Most molecule users are expected to use containers that already
19 | # have python installed in order to avoid notable delays installing it.
20 | gather_facts: false
21 | tasks:
22 | - name: Check uname
23 | ansible.builtin.raw: uname -a
24 | register: result
25 | changed_when: false
26 |
27 | - name: Print some info
28 | ansible.builtin.assert:
29 | that: result.stdout is ansible.builtin.search("^Linux")
30 |
--------------------------------------------------------------------------------
/tests/fixtures/integration/test_command/molecule/podman/create.yml:
--------------------------------------------------------------------------------
1 | - name: Create
2 | hosts: localhost
3 | gather_facts: false
4 | vars:
5 | molecule_inventory:
6 | all:
7 | hosts: {}
8 | children:
9 | molecule:
10 | hosts: {}
11 |
12 | tasks:
13 | - name: Create a container
14 | containers.podman.podman_container:
15 | name: "{{ item.name }}"
16 | image: "{{ item.image }}"
17 | privileged: "{{ item.privileged | default(omit) }}"
18 | volumes: "{{ item.volumes | default(omit) }}"
19 | capabilities: "{{ item.capabilities | default(omit) }}"
20 | systemd: "{{ item.systemd | default(omit) }}"
21 | state: started
22 | command: "{{ item.command | default('sleep 1d') }}"
23 | # bash -c "while true; do sleep 10000; done"
24 | log_driver: json-file
25 | register: result
26 | loop: "{{ molecule_yml.platforms }}"
27 |
28 | - name: Print some info
29 | ansible.builtin.debug:
30 | msg: "{{ result.results }}"
31 |
32 | - name: Fail if container is not running
33 | when: >
34 | item.container.State.ExitCode != 0 or
35 | not item.container.State.Running
36 | ansible.builtin.include_tasks:
37 | file: tasks/create-fail.yml
38 | loop: "{{ result.results }}"
39 | loop_control:
40 | label: "{{ item.container.Name }}"
41 |
42 | - name: Add container to molecule_inventory
43 | vars:
44 | inventory_partial_yaml: |
45 | all:
46 | children:
47 | molecule:
48 | hosts:
49 | "{{ item.name }}":
50 | ansible_connection: containers.podman.podman
51 | ansible.builtin.set_fact:
52 | molecule_inventory: >
53 | {{ molecule_inventory | combine(inventory_partial_yaml | from_yaml, recursive=true) }}
54 | loop: "{{ molecule_yml.platforms }}"
55 | loop_control:
56 | label: "{{ item.name }}"
57 |
58 | - name: Dump molecule_inventory
59 | ansible.builtin.copy:
60 | content: |
61 | {{ molecule_inventory | to_yaml }}
62 | dest: "{{ molecule_ephemeral_directory }}/inventory/molecule_inventory.yml"
63 | mode: "0600"
64 |
65 | - name: Force inventory refresh
66 | ansible.builtin.meta: refresh_inventory
67 |
68 | - name: Fail if molecule group is missing
69 | ansible.builtin.assert:
70 | that: "'molecule' in groups"
71 | fail_msg: |
72 | molecule group was not found inside inventory groups: {{ groups }}
73 | run_once: true # noqa: run-once[task]
74 |
75 | # we want to avoid errors like "Failed to create temporary directory"
76 | - name: Validate that inventory was refreshed
77 | hosts: molecule
78 | gather_facts: false
79 | tasks:
80 | - name: Check uname
81 | ansible.builtin.raw: uname -a
82 | register: result
83 | changed_when: false
84 |
85 | - name: Display uname info
86 | ansible.builtin.debug:
87 | msg: "{{ result.stdout }}"
88 |
--------------------------------------------------------------------------------
/tests/fixtures/integration/test_command/molecule/podman/destroy.yml:
--------------------------------------------------------------------------------
1 | - name: Destroy molecule containers
2 | hosts: molecule
3 | gather_facts: false
4 | tasks:
5 | - name: Stop and remove container
6 | delegate_to: localhost
7 | containers.podman.podman_container:
8 | name: "{{ inventory_hostname }}"
9 | state: absent
10 | rm: true
11 | - name: Remove potentially stopped container
12 | delegate_to: localhost
13 | ansible.builtin.command:
14 | cmd: podman container rm --ignore {{ inventory_hostname }}
15 | changed_when: false
16 |
17 | - name: Remove dynamic molecule inventory
18 | hosts: localhost
19 | gather_facts: false
20 | tasks:
21 | - name: Remove dynamic inventory file
22 | ansible.builtin.file:
23 | path: "{{ molecule_ephemeral_directory }}/inventory/molecule_inventory.yml"
24 | state: absent
25 |
--------------------------------------------------------------------------------
/tests/fixtures/integration/test_command/molecule/podman/molecule.yml:
--------------------------------------------------------------------------------
1 | dependency:
2 | name: galaxy
3 | options:
4 | requirements-file: requirements.yml
5 | platforms:
6 | - name: molecule-ubuntu
7 | image: ubuntu:18.04
8 | driver:
9 | options:
10 | managed: false
11 | login_cmd_template: "podman exec -ti {instance} bash"
12 | ansible_connection_options:
13 | ansible_connection: podman
14 |
--------------------------------------------------------------------------------
/tests/fixtures/integration/test_command/molecule/podman/requirements.yml:
--------------------------------------------------------------------------------
1 | collections:
2 | - containers.podman
3 |
--------------------------------------------------------------------------------
/tests/fixtures/integration/test_command/molecule/podman/tasks/create-fail.yml:
--------------------------------------------------------------------------------
1 | - name: Retrieve container log
2 | ansible.builtin.command:
3 | cmd: >-
4 | {% raw %}
5 | podman logs
6 | {% endraw %}
7 | {{ item.stdout_lines[0] }}
8 | # podman inspect --format='{{.HostConfig.LogConfig.Path}}'
9 | changed_when: false
10 | register: logfile_cmd
11 |
12 | - name: Display container log
13 | ansible.builtin.fail:
14 | msg: "{{ logfile_cmd.stderr }}"
15 |
--------------------------------------------------------------------------------
/tests/fixtures/integration/test_command/molecule/smoke/converge.yml:
--------------------------------------------------------------------------------
1 | ---
2 | - name: Converge
3 | hosts: localhost
4 | connection: local
5 | tasks:
6 | - name: Assert CWD is the same as playbook_dir
7 | ansible.builtin.assert:
8 | that:
9 | - playbook_dir == pwd
10 | msg: >
11 | Molecule should automatically chdir to scenario directory {{ playbook_dir }}
12 | but we found that it runs from {{ pwd }} instead."
13 | vars:
14 | pwd: "{{ lookup('pipe', 'pwd') }}"
15 |
--------------------------------------------------------------------------------
/tests/fixtures/integration/test_command/molecule/smoke/molecule.yml:
--------------------------------------------------------------------------------
1 | platforms:
2 | - name: localhost
3 |
--------------------------------------------------------------------------------
/tests/fixtures/integration/test_command/scenarios/cleanup/molecule/default/cleanup.yml:
--------------------------------------------------------------------------------
1 | ---
2 | - name: Cleanup external hosts
3 | hosts: localhost
4 | connection: local
5 | tasks:
6 | - name: Debug
7 | ansible.builtin.debug:
8 | msg: "Cleaned up"
9 |
--------------------------------------------------------------------------------
/tests/fixtures/integration/test_command/scenarios/cleanup/molecule/default/converge.yml:
--------------------------------------------------------------------------------
1 | ---
2 | - name: Converge
3 | hosts: all
4 | gather_facts: false
5 | tasks:
6 | - name: Touch a file
7 | ansible.builtin.command: touch /tmp/cleanup
8 | args:
9 | creates: /tmp/cleanup
10 |
--------------------------------------------------------------------------------
/tests/fixtures/integration/test_command/scenarios/cleanup/molecule/default/molecule.yml:
--------------------------------------------------------------------------------
1 | ---
2 | dependency:
3 | name: galaxy
4 | driver:
5 | name: default
6 | platforms:
7 | - name: instance
8 | image: ${TEST_BASE_IMAGE}
9 | command: /sbin/init
10 | privileged: true
11 | provisioner:
12 | name: ansible
13 | playbooks:
14 | cleanup: cleanup.yml
15 | verifier:
16 | name: ansible
17 |
--------------------------------------------------------------------------------
/tests/fixtures/integration/test_command/scenarios/cleanup/molecule/default/tests/test_cleanup.py:
--------------------------------------------------------------------------------
1 | """Testinfra tests.""" # noqa: INP001
2 |
3 | from __future__ import annotations
4 |
5 | import os
6 |
7 | import testinfra.utils.ansible_runner
8 |
9 |
10 | testinfra_hosts = testinfra.utils.ansible_runner.AnsibleRunner(
11 | os.environ["MOLECULE_INVENTORY_FILE"],
12 | ).get_hosts("all")
13 |
14 |
15 | def test_hosts_file(host): # type: ignore[no-untyped-def] # noqa: ANN201
16 | """Validate host file."""
17 | f = host.file("/etc/hosts")
18 |
19 | assert f.exists
20 | assert f.user == "root"
21 | assert f.group == "root"
22 |
--------------------------------------------------------------------------------
/tests/fixtures/integration/test_command/scenarios/dependency/molecule/ansible-galaxy/converge.yml:
--------------------------------------------------------------------------------
1 | ---
2 | - name: Converge
3 | hosts: all
4 | gather_facts: false
5 | tasks:
6 | - name: Install requirements for collection community.molecule
7 | ansible.builtin.pip:
8 | name: molecule
9 | delegate_to: localhost
10 |
11 | - name: Validate that collection was installed
12 | ansible.builtin.debug:
13 | msg: "{{ 'foo' | community.molecule.header }}"
14 |
15 | - name: Test installed role
16 | ansible.builtin.include_role:
17 | name: ssbarnea.ansible_role_helloworld
18 |
--------------------------------------------------------------------------------
/tests/fixtures/integration/test_command/scenarios/dependency/molecule/ansible-galaxy/molecule.yml:
--------------------------------------------------------------------------------
1 | ---
2 | dependency:
3 | name: galaxy
4 | options:
5 | role-file: molecule/ansible-galaxy/requirements.yml
6 | requirements-file: molecule/ansible-galaxy/requirements.yml
7 | driver:
8 | name: default
9 | platforms:
10 | - name: instance
11 | image: ${TEST_BASE_IMAGE}
12 | provisioner:
13 | name: ansible
14 | scenario:
15 | name: ansible-galaxy
16 | verifier:
17 | name: ansible
18 |
--------------------------------------------------------------------------------
/tests/fixtures/integration/test_command/scenarios/dependency/molecule/ansible-galaxy/requirements.yml:
--------------------------------------------------------------------------------
1 | # See https://docs.ansible.com/ansible/latest/galaxy/user_guide.html#installing-roles-and-collections-from-the-same-requirements-yml-file
2 | collections:
3 | - community.molecule
4 | roles:
5 | - name: ssbarnea.ansible_role_helloworld
6 |
--------------------------------------------------------------------------------
/tests/fixtures/integration/test_command/scenarios/dependency/molecule/shell/converge.yml:
--------------------------------------------------------------------------------
1 | ---
2 | - name: Converge
3 | hosts: all
4 | gather_facts: false
5 | tasks:
6 | - name: Install requirements for collection community.molecule
7 | ansible.builtin.pip:
8 | name: molecule
9 | delegate_to: localhost
10 |
11 | - name: Validate that collection was installed
12 | ansible.builtin.debug:
13 | msg: "{{ 'foo' | community.molecule.header }}"
14 |
15 | - name: Test installed role
16 | ansible.builtin.include_role:
17 | name: ssbarnea.ansible_role_helloworld
18 |
--------------------------------------------------------------------------------
/tests/fixtures/integration/test_command/scenarios/dependency/molecule/shell/molecule.yml:
--------------------------------------------------------------------------------
1 | ---
2 | dependency:
3 | name: shell
4 | command: >
5 | bash -c "
6 | ansible-galaxy collection install community.molecule --force &&
7 | ansible-galaxy role install ssbarnea.ansible_role_helloworld
8 | "
9 | driver:
10 | name: default
11 | platforms:
12 | - name: instance
13 | image: ${TEST_BASE_IMAGE}
14 | provisioner:
15 | name: ansible
16 | scenario:
17 | name: shell
18 | verifier:
19 | name: ansible
20 |
--------------------------------------------------------------------------------
/tests/fixtures/integration/test_command/scenarios/driver/delegated/meta/main.yml:
--------------------------------------------------------------------------------
1 | ---
2 | dependencies: []
3 |
4 | galaxy_info:
5 | author: Molecule Developer
6 | description: Role to test ansible_compat installation of role
7 | namespace: molecule
8 | role_name: delegated_test
9 | license: GPL
10 | min_ansible_version: "2.15"
11 |
--------------------------------------------------------------------------------
/tests/fixtures/integration/test_command/scenarios/driver/delegated/molecule/default/cleanup.yml:
--------------------------------------------------------------------------------
1 | ---
2 | - name: Cleanup
3 | hosts: all
4 | gather_facts: false
5 | become: false
6 |
--------------------------------------------------------------------------------
/tests/fixtures/integration/test_command/scenarios/driver/delegated/molecule/default/converge.yml:
--------------------------------------------------------------------------------
1 | ---
2 | - name: Converge
3 | hosts: all
4 | gather_facts: false
5 | become: false
6 |
--------------------------------------------------------------------------------
/tests/fixtures/integration/test_command/scenarios/driver/delegated/molecule/default/create.yml:
--------------------------------------------------------------------------------
1 | ---
2 | - name: Create
3 | hosts: all
4 | gather_facts: false
5 |
--------------------------------------------------------------------------------
/tests/fixtures/integration/test_command/scenarios/driver/delegated/molecule/default/destroy.yml:
--------------------------------------------------------------------------------
1 | ---
2 | - name: Destroy
3 | hosts: all
4 | gather_facts: false
5 |
--------------------------------------------------------------------------------
/tests/fixtures/integration/test_command/scenarios/driver/delegated/molecule/default/molecule.yml:
--------------------------------------------------------------------------------
1 | ---
2 | driver:
3 | name: default
4 | platforms:
5 | - name: instance
6 | provisioner:
7 | name: ansible
8 | inventory:
9 | hosts:
10 | all:
11 | hosts:
12 | instance:
13 | ansible_host: localhost
14 | default_sequence:
15 | - converge
16 | test_sequence:
17 | # - prepare
18 | - converge
19 | # - verify
20 | verify_sequence:
21 | - converge
22 |
--------------------------------------------------------------------------------
/tests/fixtures/integration/test_command/scenarios/driver/delegated/molecule/default/prepare.yml:
--------------------------------------------------------------------------------
1 | ---
2 | - name: Prepare
3 | hosts: all
4 | gather_facts: false
5 | become: false
6 |
--------------------------------------------------------------------------------
/tests/fixtures/integration/test_command/scenarios/driver/delegated/molecule/default/side_effect.yml:
--------------------------------------------------------------------------------
1 | ---
2 | - name: Side Effect
3 | hosts: all
4 | gather_facts: false
5 | become: false
6 |
--------------------------------------------------------------------------------
/tests/fixtures/integration/test_command/scenarios/driver/delegated/molecule/default/verify.yml:
--------------------------------------------------------------------------------
1 | ---
2 | - name: Verify
3 | hosts: all
4 | gather_facts: false
5 | become: false
6 |
--------------------------------------------------------------------------------
/tests/fixtures/integration/test_command/scenarios/driver/delegated_invalid_role_name_with_role_name_check_equals_to_1/meta/main.yml:
--------------------------------------------------------------------------------
1 | ---
2 | # noqa: role-name
3 | dependencies: []
4 |
5 | galaxy_info:
6 | author: Molecule Developer
7 | description: Role to test ansible_compat installation of role
8 | namespace: molecule
9 | role_name: delegated-test
10 | license: GPL
11 | min_ansible_version: "2.15"
12 |
--------------------------------------------------------------------------------
/tests/fixtures/integration/test_command/scenarios/driver/delegated_invalid_role_name_with_role_name_check_equals_to_1/molecule/default/converge.yml:
--------------------------------------------------------------------------------
1 | ---
2 | - name: Converge
3 | hosts: all
4 | gather_facts: false
5 | become: false
6 |
--------------------------------------------------------------------------------
/tests/fixtures/integration/test_command/scenarios/driver/delegated_invalid_role_name_with_role_name_check_equals_to_1/molecule/default/create.yml:
--------------------------------------------------------------------------------
1 | ---
2 | - name: Create
3 | hosts: all
4 | gather_facts: false
5 |
--------------------------------------------------------------------------------
/tests/fixtures/integration/test_command/scenarios/driver/delegated_invalid_role_name_with_role_name_check_equals_to_1/molecule/default/destroy.yml:
--------------------------------------------------------------------------------
1 | ---
2 | - name: Destroy
3 | hosts: all
4 | gather_facts: false
5 |
--------------------------------------------------------------------------------
/tests/fixtures/integration/test_command/scenarios/driver/delegated_invalid_role_name_with_role_name_check_equals_to_1/molecule/default/molecule.yml:
--------------------------------------------------------------------------------
1 | ---
2 | role_name_check: 1
3 | driver:
4 | name: default
5 | platforms:
6 | - name: instance
7 | provisioner:
8 | name: ansible
9 | inventory:
10 | hosts:
11 | all:
12 | hosts:
13 | instance:
14 | ansible_host: localhost
15 | default_sequence:
16 | - converge
17 | test_sequence:
18 | # - prepare
19 | - converge
20 | # - verify
21 | verify_sequence:
22 | - converge
23 |
--------------------------------------------------------------------------------
/tests/fixtures/integration/test_command/scenarios/host_group_vars/group_vars/example/all.yml:
--------------------------------------------------------------------------------
1 | ---
2 | host_group_vars_group_vars_linked: true
3 |
--------------------------------------------------------------------------------
/tests/fixtures/integration/test_command/scenarios/host_group_vars/host_vars/extra-host:
--------------------------------------------------------------------------------
1 | ---
2 | host_group_vars_extra_host_linked: true
3 |
--------------------------------------------------------------------------------
/tests/fixtures/integration/test_command/scenarios/host_group_vars/hosts:
--------------------------------------------------------------------------------
1 | extra-host
2 |
--------------------------------------------------------------------------------
/tests/fixtures/integration/test_command/scenarios/host_group_vars/molecule/default/converge.yml:
--------------------------------------------------------------------------------
1 | ---
2 | - name: Molecule test fixture
3 | hosts: instance
4 | gather_facts: false
5 | tasks:
6 | - name: Host vars host_var for host host-group-vars from molecule.yml
7 | ansible.builtin.debug:
8 | var: host_group_vars_host_molecule_yml
9 |
10 | - name: Host vars from host_vars existing directory
11 | ansible.builtin.debug:
12 | var: host_group_vars_host_vars_dir
13 |
14 | - name: Group vars group_var for group example from molecule.yml
15 | ansible.builtin.debug:
16 | var:
17 | - host_group_vars_example_group_one_molecule_yml
18 | - host_group_vars_example_group_two_molecule_yml
19 |
20 | - name: Group vars from group_vars existing directory
21 | ansible.builtin.debug:
22 | var: host_group_vars_group_vars_dir
23 |
24 | - name: Group vars group_var from child group example_1 from molecule.yml
25 | ansible.builtin.debug:
26 | var: host_group_vars_example_1_child_group_molecule_yml
27 |
28 | - name: Variable from extra_host from molecule.yml
29 | ansible.builtin.debug:
30 | var: hostvars['extra_host']['host_group_vars_extra_host_molecule_yml']
31 |
32 | - name: Molecule test fixture
33 | hosts: example
34 | gather_facts: false
35 | tasks:
36 | - name: Dummy converge of example group
37 | ansible.builtin.debug:
38 | var: ansible_host
39 |
40 | - name: Molecule test fixture
41 | hosts: example_1
42 | gather_facts: false
43 | tasks:
44 | - name: Dummy converge of child example_1 group
45 | ansible.builtin.debug:
46 | var: ansible_host
47 |
--------------------------------------------------------------------------------
/tests/fixtures/integration/test_command/scenarios/host_group_vars/molecule/default/group_vars/example/all.yml:
--------------------------------------------------------------------------------
1 | ---
2 | host_group_vars_group_vars_dir: true
3 |
--------------------------------------------------------------------------------
/tests/fixtures/integration/test_command/scenarios/host_group_vars/molecule/default/molecule.yml:
--------------------------------------------------------------------------------
1 | ---
2 | dependency:
3 | name: galaxy
4 | driver:
5 | name: default
6 | platforms:
7 | - name: instance
8 | image: ${TEST_BASE_IMAGE}
9 | groups:
10 | - example
11 | children:
12 | - example_1
13 | provisioner:
14 | name: ansible
15 | inventory:
16 | hosts:
17 | all:
18 | hosts:
19 | extra_host:
20 | host_group_vars_extra_host_molecule_yml: true
21 | host_vars:
22 | instance:
23 | host_group_vars_host_molecule_yml: true
24 | group_vars:
25 | example:
26 | host_group_vars_example_group_one_molecule_yml: true
27 | host_group_vars_example_group_two_molecule_yml: true
28 | example_1:
29 | host_group_vars_example_1_child_group_molecule_yml: true
30 | verifier:
31 | name: ansible
32 |
--------------------------------------------------------------------------------
/tests/fixtures/integration/test_command/scenarios/host_group_vars/molecule/links/converge.yml:
--------------------------------------------------------------------------------
1 | ---
2 | - name: Molecule test fixture
3 | hosts: example
4 | gather_facts: false
5 | tasks:
6 | - name: Host vars from host_vars links
7 | ansible.builtin.debug:
8 | var: host_group_vars_host_vars_linked
9 |
10 | - name: Group vars from group_vars links
11 | ansible.builtin.debug:
12 | var: host_group_vars_group_vars_linked
13 |
14 | - name: Variable from extra inventory link
15 | ansible.builtin.debug:
16 | var: hostvars['extra-host']['host_group_vars_extra_host_linked']
17 |
--------------------------------------------------------------------------------
/tests/fixtures/integration/test_command/scenarios/host_group_vars/molecule/links/molecule.yml:
--------------------------------------------------------------------------------
1 | ---
2 | dependency:
3 | name: galaxy
4 | driver:
5 | name: default
6 | platforms:
7 | - name: instance
8 | image: ${TEST_BASE_IMAGE}
9 | groups:
10 | - example
11 | children:
12 | - example_1
13 | provisioner:
14 | name: ansible
15 | inventory:
16 | links:
17 | hosts: ../../hosts
18 | host_vars: ../../host_vars
19 | group_vars: ../../group_vars
20 | scenario:
21 | name: links
22 | verifier:
23 | name: testinfra
24 |
--------------------------------------------------------------------------------
/tests/fixtures/integration/test_command/scenarios/idempotence/molecule/raises/converge.yml:
--------------------------------------------------------------------------------
1 | ---
2 | - name: Converge
3 | hosts: all
4 | gather_facts: false
5 | become: true
6 | roles: []
7 |
--------------------------------------------------------------------------------
/tests/fixtures/integration/test_command/scenarios/idempotence/molecule/raises/molecule.yml:
--------------------------------------------------------------------------------
1 | ---
2 | dependency:
3 | name: galaxy
4 | driver:
5 | name: default
6 | platforms:
7 | - name: instance
8 | image: ${TEST_BASE_IMAGE}
9 | provisioner:
10 | name: ansible
11 | # commented on purpose to validate that molecule will inherit it from
12 | # parent folder name:
13 | # scenario:
14 | # name: raises
15 | verifier:
16 | name: ansible
17 |
--------------------------------------------------------------------------------
/tests/fixtures/integration/test_command/scenarios/idempotence/tasks/main.yml:
--------------------------------------------------------------------------------
1 | ---
2 | # Steps provided by @saez0pub
3 | # Taken from https://github.com/ansible-community/molecule/issues/835
4 |
5 | - name: Create /tmp/test1
6 | ansible.builtin.file:
7 | name: /tmp/test1
8 | state: directory
9 | mode: "0600"
10 |
11 | - name: Replace /tmp/test1 by /tmp/test2
12 | ansible.builtin.shell: |
13 | set -euxo pipefail
14 | ls -ld /tmp/test1 | grep root
15 | changed_when: false
16 |
17 | - name: Fix /tmp/test1 perms
18 | ansible.builtin.file:
19 | name: /tmp/test1
20 | state: directory
21 | owner: lp
22 | group: lp
23 | mode: "0600"
24 |
--------------------------------------------------------------------------------
/tests/fixtures/integration/test_command/scenarios/interpolation/molecule/default/converge.yml:
--------------------------------------------------------------------------------
1 | ---
2 | - name: Converge
3 | hosts: all
4 | gather_facts: false
5 | tasks:
6 | - name: Dummy task
7 | ansible.builtin.command: /bin/true
8 | changed_when: false
9 |
--------------------------------------------------------------------------------
/tests/fixtures/integration/test_command/scenarios/interpolation/molecule/default/molecule.yml:
--------------------------------------------------------------------------------
1 | ---
2 | dependency:
3 | name: galaxy
4 | driver:
5 | name: ${DRIVER_NAME}
6 | platforms:
7 | - name: $INSTANCE_NAME
8 | image: ${TEST_BASE_IMAGE}
9 | provisioner:
10 | name: ansible
11 | verifier:
12 | name: ansible
13 |
--------------------------------------------------------------------------------
/tests/fixtures/integration/test_command/scenarios/overrride_driver/molecule/default/converge.yml:
--------------------------------------------------------------------------------
1 | ---
2 | - name: Converge
3 | hosts: all
4 | gather_facts: false
5 | tasks:
6 | - name: Dummy task
7 | ansible.builtin.command: /bin/true
8 | changed_when: false
9 |
--------------------------------------------------------------------------------
/tests/fixtures/integration/test_command/scenarios/overrride_driver/molecule/default/molecule.yml:
--------------------------------------------------------------------------------
1 | ---
2 | dependency:
3 | name: galaxy
4 | driver:
5 | name: default
6 | platforms:
7 | - name: instance
8 | image: ${TEST_BASE_IMAGE}
9 | provisioner:
10 | name: ansible
11 | verifier:
12 | name: ansible
13 |
--------------------------------------------------------------------------------
/tests/fixtures/integration/test_command/scenarios/side_effect/molecule/default/converge.yml:
--------------------------------------------------------------------------------
1 | ---
2 | - name: Converge
3 | hosts: all
4 | gather_facts: false
5 | tasks:
6 | - name: Check for test file
7 | ansible.builtin.stat:
8 | path: /tmp/testfile
9 | register: test_file
10 |
11 | - name: Create the test file
12 | ansible.builtin.file:
13 | path: /tmp/testfile
14 | state: touch
15 | mode: "0644"
16 | when: not test_file.stat.exists
17 |
--------------------------------------------------------------------------------
/tests/fixtures/integration/test_command/scenarios/side_effect/molecule/default/molecule.yml:
--------------------------------------------------------------------------------
1 | ---
2 | dependency:
3 | name: galaxy
4 | driver:
5 | name: default
6 | platforms:
7 | - name: instance
8 | image: ${TEST_BASE_IMAGE}
9 | command: /sbin/init
10 | privileged: true
11 | provisioner:
12 | name: ansible
13 | playbooks:
14 | side_effect: side_effect.yml
15 | verifier:
16 | name: ansible
17 |
--------------------------------------------------------------------------------
/tests/fixtures/integration/test_command/scenarios/side_effect/molecule/default/side_effect.yml:
--------------------------------------------------------------------------------
1 | ---
2 | - name: Side Effect
3 | hosts: all
4 | gather_facts: false
5 | no_log: "{{ molecule_no_log }}"
6 | tasks:
7 | - name: Delete the test file as a planned side effect
8 | ansible.builtin.file:
9 | path: /tmp/testfile
10 | state: absent
11 |
--------------------------------------------------------------------------------
/tests/fixtures/integration/test_command/scenarios/side_effect/molecule/default/tests/test_side_effect.py:
--------------------------------------------------------------------------------
1 | """Testinfra tests.""" # noqa: INP001
2 |
3 | from __future__ import annotations
4 |
5 | import os
6 |
7 | import testinfra.utils.ansible_runner
8 |
9 |
10 | testinfra_hosts = testinfra.utils.ansible_runner.AnsibleRunner(
11 | os.environ["MOLECULE_INVENTORY_FILE"],
12 | ).get_hosts("all")
13 |
14 |
15 | def test_side_effect_removed_file(host): # type: ignore[no-untyped-def] # noqa: ANN201
16 | """Validate that file was removed."""
17 | assert not host.file("/tmp/testfile").exists # noqa: S108
18 |
--------------------------------------------------------------------------------
/tests/fixtures/integration/test_command/scenarios/test_destroy_strategy/molecule/default/converge.yml:
--------------------------------------------------------------------------------
1 | ---
2 | - name: Converge
3 | hosts: all
4 | gather_facts: false
5 | become: true
6 | tasks:
7 | - name: Force a converge failure
8 | ansible.builtin.command: /bin/false
9 | changed_when: false
10 |
--------------------------------------------------------------------------------
/tests/fixtures/integration/test_command/scenarios/test_destroy_strategy/molecule/default/molecule.yml:
--------------------------------------------------------------------------------
1 | ---
2 | dependency:
3 | name: galaxy
4 | driver:
5 | name: default
6 | platforms:
7 | - name: instance
8 | image: ${TEST_BASE_IMAGE}
9 | provisioner:
10 | name: ansible
11 | verifier:
12 | name: ansible
13 |
--------------------------------------------------------------------------------
/tests/fixtures/integration/test_command/scenarios/test_destroy_strategy/molecule/default/tests/test_destroy_strategy.py:
--------------------------------------------------------------------------------
1 | """Testinfra tests.""" # noqa: INP001
2 |
3 | from __future__ import annotations
4 |
5 | import os
6 |
7 | import testinfra.utils.ansible_runner
8 |
9 |
10 | testinfra_hosts = testinfra.utils.ansible_runner.AnsibleRunner(
11 | os.environ["MOLECULE_INVENTORY_FILE"],
12 | ).get_hosts("all")
13 |
14 |
15 | def test_hostname(host): # type: ignore[no-untyped-def] # noqa: ANN201
16 | """Validate hostname."""
17 | assert host.check_output("hostname -s") == "instance"
18 |
19 |
20 | def test_etc_molecule_directory(host): # type: ignore[no-untyped-def] # noqa: ANN201
21 | """Validate molecule directory."""
22 | f = host.file("/etc/molecule")
23 |
24 | assert f.is_directory
25 | assert f.user == "root"
26 | assert f.group == "root"
27 | assert f.mode == 0o755 # noqa: PLR2004
28 |
29 |
30 | def test_etc_molecule_ansible_hostname_file(host): # type: ignore[no-untyped-def] # noqa: ANN201
31 | """Validate molecule instance file."""
32 | f = host.file("/etc/molecule/instance")
33 |
34 | assert f.is_file
35 | assert f.user == "root"
36 | assert f.group == "root"
37 | assert f.mode == 0o644 # noqa: PLR2004
38 |
--------------------------------------------------------------------------------
/tests/fixtures/integration/test_command/scenarios/verifier/.pre-commit-config.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | repos:
3 | - repo: local
4 | hooks:
5 | - id: flake8
6 | name: flake8
7 | entry: python3 -m flake8 --max-line-length=120
8 | language: system
9 | types: [python]
10 |
--------------------------------------------------------------------------------
/tests/fixtures/integration/test_command/scenarios/verifier/molecule/testinfra-pre-commit/converge.yml:
--------------------------------------------------------------------------------
1 | ---
2 | - name: Converge
3 | hosts: all
4 | tasks:
5 | - name: Create /tmp/molecule
6 | ansible.builtin.file:
7 | dest: /etc/molecule
8 | group: root
9 | owner: root
10 | mode: "0755"
11 | state: directory
12 |
13 | - name: Create /etc/molecule/{{ ansible_hostname }}
14 | ansible.builtin.copy:
15 | dest: "/etc/molecule/{{ ansible_hostname }}"
16 | group: root
17 | owner: root
18 | mode: "0644"
19 | content: "{{ ansible_hostname }}"
20 |
--------------------------------------------------------------------------------
/tests/fixtures/integration/test_command/scenarios/verifier/molecule/testinfra-pre-commit/molecule.yml:
--------------------------------------------------------------------------------
1 | ---
2 | dependency:
3 | name: galaxy
4 | driver:
5 | name: default
6 | platforms:
7 | - name: instance
8 | image: ${TEST_BASE_IMAGE}
9 | provisioner:
10 | name: ansible
11 | scenario:
12 | name: testinfra-pre-commit
13 | verifier:
14 | name: testinfra
15 | options:
16 | vvv: true
17 | additional_files_or_dirs:
18 | - ../shared/test_*.py
19 |
--------------------------------------------------------------------------------
/tests/fixtures/integration/test_command/scenarios/verifier/molecule/testinfra-pre-commit/tests/test_testinfra_pre_commit.py:
--------------------------------------------------------------------------------
1 | """Testinfra tests.""" # noqa: INP001
2 |
3 | from __future__ import annotations
4 |
5 |
6 | def test_ansible_hostname(host): # type: ignore[no-untyped-def] # noqa: ANN201
7 | """Validate hostname."""
8 | f = host.file("/tmp/molecule/instance-1") # noqa: S108
9 | assert not f.exists
10 |
--------------------------------------------------------------------------------
/tests/fixtures/integration/test_command/scenarios/verifier/molecule/testinfra/converge.yml:
--------------------------------------------------------------------------------
1 | ---
2 | - name: Converge
3 | gather_facts: false
4 | hosts: all
5 | roles:
6 | - molecule
7 |
--------------------------------------------------------------------------------
/tests/fixtures/integration/test_command/scenarios/verifier/molecule/testinfra/molecule.yml:
--------------------------------------------------------------------------------
1 | ---
2 | dependency:
3 | name: galaxy
4 | driver:
5 | name: default
6 | platforms:
7 | - name: instance
8 | image: ${TEST_BASE_IMAGE}
9 | provisioner:
10 | name: ansible
11 | env:
12 | ANSIBLE_ROLES_PATH: ../../../../resources/roles/
13 | scenario:
14 | name: testinfra
15 | verifier:
16 | name: testinfra
17 | options:
18 | vvv: true
19 | additional_files_or_dirs:
20 | - ../shared/test_*.py
21 |
--------------------------------------------------------------------------------
/tests/fixtures/integration/test_command/scenarios/verifier/molecule/testinfra/roles/molecule/tasks/main.yml:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ansible/molecule/bfb18224c98e7ad4cc0c49b03a8398df1f550a01/tests/fixtures/integration/test_command/scenarios/verifier/molecule/testinfra/roles/molecule/tasks/main.yml
--------------------------------------------------------------------------------
/tests/fixtures/integration/test_command/scenarios/verifier/molecule/testinfra/shared/test_shared.py:
--------------------------------------------------------------------------------
1 | """Testinfra tests.""" # noqa: INP001
2 |
3 | from __future__ import annotations
4 |
5 | import os
6 |
7 | import testinfra.utils.ansible_runner
8 |
9 |
10 | testinfra_hosts = testinfra.utils.ansible_runner.AnsibleRunner(
11 | os.environ["MOLECULE_INVENTORY_FILE"],
12 | ).get_hosts("all")
13 |
14 |
15 | def test_ansible_hostname(host): # type: ignore[no-untyped-def] # noqa: ANN201
16 | """Validate hostname."""
17 | f = host.file("/tmp/molecule/instance-1") # noqa: S108
18 |
19 | assert not f.exists
20 |
--------------------------------------------------------------------------------
/tests/fixtures/integration/test_command/scenarios/verifier/molecule/testinfra/tests/test_testinfra.py:
--------------------------------------------------------------------------------
1 | """Testinfra tests.""" # noqa: INP001
2 |
3 | from __future__ import annotations
4 |
5 | import os
6 |
7 | import testinfra.utils.ansible_runner
8 |
9 |
10 | testinfra_hosts = testinfra.utils.ansible_runner.AnsibleRunner(
11 | os.environ["MOLECULE_INVENTORY_FILE"],
12 | ).get_hosts("all")
13 |
14 |
15 | def test_ansible_hostname(host): # type: ignore[no-untyped-def] # noqa: ANN201
16 | """Validate hostname."""
17 | f = host.file("/tmp/molecule/instance-1") # noqa: S108
18 |
19 | assert not f.exists
20 |
--------------------------------------------------------------------------------
/tests/fixtures/resources/.yamllint:
--------------------------------------------------------------------------------
1 | extends: default
2 |
3 | rules:
4 | braces:
5 | max-spaces-inside: 1
6 | level: error
7 | brackets:
8 | max-spaces-inside: 1
9 | level: error
10 | line-length: disable
11 | truthy: disable
12 |
--------------------------------------------------------------------------------
/tests/fixtures/resources/broken-collection/galaxy.yml:
--------------------------------------------------------------------------------
1 | name: baddies
2 | name_space: acme
3 | version: 1.0.0
4 |
--------------------------------------------------------------------------------
/tests/fixtures/resources/molecule.yml:
--------------------------------------------------------------------------------
1 | ---
2 | dependency:
3 | name: galaxy
4 | driver:
5 | name: default
6 | platforms: []
7 | provisioner:
8 | name: ansible
9 | scenario:
10 | name: ansible-galaxy
11 | verifier:
12 | name: ansible
13 |
--------------------------------------------------------------------------------
/tests/fixtures/resources/playbooks/delegated/create.yml:
--------------------------------------------------------------------------------
1 | ---
2 | - name: Create
3 | hosts: localhost
4 | connection: local
5 | gather_facts: false
6 | tasks: []
7 |
--------------------------------------------------------------------------------
/tests/fixtures/resources/playbooks/delegated/destroy.yml:
--------------------------------------------------------------------------------
1 | ---
2 | - name: Destroy
3 | hosts: localhost
4 | connection: local
5 | gather_facts: false
6 | tasks: []
7 |
--------------------------------------------------------------------------------
/tests/fixtures/resources/playbooks/delegated/inventory/group_vars/all/all.yml:
--------------------------------------------------------------------------------
1 | ---
2 | base_dir: /tmp
3 |
--------------------------------------------------------------------------------
/tests/fixtures/resources/roles/molecule/meta/main.yml:
--------------------------------------------------------------------------------
1 | ---
2 | galaxy_info:
3 | author: Ansible Molecule by Red Hat
4 | description: Test Molecule role
5 | license: MIT
6 | min_ansible_version: "2.15"
7 | platforms:
8 | - name: GenericLinux
9 | versions:
10 | - all
11 | galaxy_tags: []
12 | dependencies: []
13 |
--------------------------------------------------------------------------------
/tests/fixtures/resources/roles/molecule/tasks/main.yml:
--------------------------------------------------------------------------------
1 | ---
2 | - name: Create /etc/molecule
3 | ansible.builtin.file:
4 | dest: /etc/molecule
5 | group: root
6 | owner: root
7 | mode: "0755"
8 | state: directory
9 |
10 | - name: Create /etc/molecule/{{ ansible_host }}
11 | ansible.builtin.copy:
12 | dest: "/etc/molecule/{{ ansible_host }}"
13 | group: root
14 | owner: root
15 | mode: "0644"
16 | content: "{{ ansible_host }}"
17 |
--------------------------------------------------------------------------------
/tests/fixtures/resources/sample-collection/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
--------------------------------------------------------------------------------
/tests/fixtures/resources/sample-collection/galaxy.yml:
--------------------------------------------------------------------------------
1 | name: goodies
2 | namespace: acme
3 | version: 1.0.0
4 | readme: README.md
5 | authors:
6 | - Red Hat
7 | description: Acme Goodies Collection
8 | build_ignore:
9 | - "*.egg-info"
10 | - .ansible
11 | - .DS_Store
12 | - .eggs
13 | - .gitignore
14 | - .mypy_cache
15 | - .pytest_cache
16 | - .stestr
17 | - .stestr.conf
18 | - .tox
19 | - .vscode
20 | - MANIFEST.in
21 | - build
22 | - dist
23 | - doc
24 | - report.html
25 | - setup.cfg
26 | - setup.py
27 | - "tests/unit/*.*"
28 | - README.rst
29 | - tox.ini
30 |
31 | repository: https://opendev.org/openstack/tripleo-repos
32 | license_file: LICENSE
33 | tags:
34 | - tools
35 |
--------------------------------------------------------------------------------
/tests/fixtures/resources/sample-collection/meta/runtime.yml:
--------------------------------------------------------------------------------
1 | {}
2 |
--------------------------------------------------------------------------------
/tests/fixtures/resources/sample-collection/molecule/default/converge.yml:
--------------------------------------------------------------------------------
1 | ---
2 | - name: Converge
3 | hosts: localhost
4 | tasks:
5 | - name: "Include sample role from current collection"
6 | ansible.builtin.include_role:
7 | name: acme.goodies.get_rich
8 |
--------------------------------------------------------------------------------
/tests/fixtures/resources/sample-collection/molecule/default/molecule.yml:
--------------------------------------------------------------------------------
1 | ---
2 | dependency:
3 | name: galaxy
4 | driver:
5 | name: default
6 | platforms:
7 | - name: instance
8 | provisioner:
9 | name: ansible
10 | verifier:
11 | name: ansible
12 |
--------------------------------------------------------------------------------
/tests/fixtures/resources/sample-collection/roles/get_rich/tasks/main.yml:
--------------------------------------------------------------------------------
1 | - name: Some task inside foo.bar collection
2 | ansible.builtin.debug:
3 | msg: "hello world!"
4 |
--------------------------------------------------------------------------------
/tests/fixtures/resources/schema_instance_files/invalid/molecule_delegated.yml:
--------------------------------------------------------------------------------
1 | driver:
2 | name: default
3 | platforms:
4 | - name: foo
5 | children: 2 # invalid, must be list of strings
6 |
--------------------------------------------------------------------------------
/tests/fixtures/resources/schema_instance_files/valid/molecule.yml:
--------------------------------------------------------------------------------
1 | ---
2 | dependency:
3 | name: shell
4 | enabled: true
5 | command: path/to/command --flag1 subcommand --flag2
6 | options:
7 | ignore-certs: true
8 | ignore-errors: true
9 | env:
10 | FOO: bar
11 |
12 | driver:
13 | name: default
14 | options:
15 | managed: false
16 | login_cmd_template: ...
17 | ansible_connection_options:
18 | ansible_connection: ssh
19 |
20 | log: true
21 |
22 | platforms:
23 | - name: ubi8
24 | hostname: ubi8
25 | children: [] # list of strings
26 | unknown_property_foo: bar # unknown properties should be allowed for drivers
27 | groups:
28 | - ubi8
29 | # vagrant ones
30 | box: foo/bar
31 |
32 | - name: ubi7
33 | hostname: ubi7
34 | children: ["ubi8"]
35 | groups:
36 | - ubi7
37 |
38 | provisioner:
39 | playbooks:
40 | prepare: prepare.yml
41 | inventory:
42 | hosts:
43 | all:
44 | hosts:
45 | ubi8:
46 | ansible_python_interpreter: /usr/bin/python3
47 | ubi7:
48 | selinux: permissive
49 | ubi8:
50 | selinux: enforced
51 | name: ansible
52 | log: true
53 | env:
54 | ANSIBLE_STDOUT_CALLBACK: yaml
55 | config_options:
56 | defaults:
57 | fact_caching: jsonfile
58 | fact_caching_connection: /tmp/molecule/facts
59 |
60 | scenario:
61 | test_sequence:
62 | - destroy
63 | - create
64 | - prepare
65 | - converge
66 | - check
67 | - verify
68 | - destroy
69 |
70 | verifier:
71 | name: testinfra
72 |
--------------------------------------------------------------------------------
/tests/integration/__init__.py:
--------------------------------------------------------------------------------
1 | """Integration tests."""
2 |
--------------------------------------------------------------------------------
/tests/integration/conftest.py:
--------------------------------------------------------------------------------
1 | # Copyright (c) 2015-2018 Cisco Systems, Inc. # noqa: D100
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 | from __future__ import annotations
22 |
23 | from typing import TYPE_CHECKING
24 |
25 | import pytest
26 |
27 | from molecule import logger
28 | from molecule.app import get_app
29 |
30 |
31 | if TYPE_CHECKING:
32 | from collections.abc import Iterator
33 | from pathlib import Path
34 |
35 |
36 | LOG = logger.get_logger(__name__)
37 |
38 |
39 | @pytest.fixture(name="with_scenario")
40 | def _with_scenario( # noqa: PLR0913
41 | request: pytest.FixtureRequest,
42 | monkeypatch: pytest.MonkeyPatch,
43 | test_ephemeral_dir_env: dict[str, str],
44 | scenario_to_test: str,
45 | scenario_name: str,
46 | test_fixture_dir: Path,
47 | ) -> Iterator[None]:
48 | """Yield to test with a scenario, changes dir and does cleanup.
49 |
50 | Args:
51 | request: The pytest request object.
52 | monkeypatch: The pytest monkeypatch object.
53 | test_ephemeral_dir_env: The ephemeral directory environment variables.
54 | scenario_to_test: The scenario to test.
55 | scenario_name: The scenario name.
56 | test_fixture_dir: The test fixture directory.
57 |
58 | Yields:
59 | None:
60 | """
61 | scenario_directory = test_fixture_dir / "scenarios" / scenario_to_test
62 |
63 | monkeypatch.chdir(scenario_directory)
64 |
65 | yield
66 | if request.node.rep_call.failed:
67 | return
68 | if scenario_name:
69 | msg = f"CLEANUP: Destroying instances for {scenario_name}"
70 | LOG.info(msg)
71 | cmd = ["molecule", "destroy", "--scenario-name", scenario_name]
72 | assert (
73 | get_app(scenario_directory).run_command(cmd=cmd, env=test_ephemeral_dir_env).returncode
74 | == 0
75 | )
76 |
--------------------------------------------------------------------------------
/tests/unit/__init__.py:
--------------------------------------------------------------------------------
1 | # noqa: D104
2 |
--------------------------------------------------------------------------------
/tests/unit/command/__init__.py:
--------------------------------------------------------------------------------
1 | # noqa: D104
2 |
--------------------------------------------------------------------------------
/tests/unit/command/conftest.py:
--------------------------------------------------------------------------------
1 | # Copyright (c) 2015-2018 Cisco Systems, Inc. # noqa: D100
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 | from __future__ import annotations
21 |
22 | import pytest
23 |
24 |
25 | @pytest.fixture
26 | def command_patched_ansible_create(mocker): # type: ignore[no-untyped-def] # noqa: ANN201, D103
27 | return mocker.patch("molecule.provisioner.ansible.Ansible.create")
28 |
29 |
30 | @pytest.fixture
31 | def command_driver_delegated_section_data(): # type: ignore[no-untyped-def] # noqa: ANN201, D103
32 | x = {
33 | "driver": {
34 | "name": "default",
35 | "options": {
36 | "managed": False,
37 | },
38 | },
39 | }
40 | # if "DOCKER_HOST" in os.environ:
41 | # x["driver"]["options"]["ansible_docker_extra_args"] = "-H={}".format(
42 | return x # noqa: RET504
43 |
44 |
45 | @pytest.fixture
46 | def command_driver_delegated_managed_section_data(): # type: ignore[no-untyped-def] # noqa: ANN201, D103
47 | return {"driver": {"name": "default", "managed": True}}
48 |
--------------------------------------------------------------------------------
/tests/unit/command/init/__init__.py:
--------------------------------------------------------------------------------
1 | # noqa: D104
2 |
--------------------------------------------------------------------------------
/tests/unit/command/test_check.py:
--------------------------------------------------------------------------------
1 | # Copyright (c) 2015-2018 Cisco Systems, Inc. # noqa: D100
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 | from __future__ import annotations
21 |
22 | from typing import TYPE_CHECKING
23 |
24 | import pytest
25 |
26 | from molecule.command import check
27 |
28 |
29 | if TYPE_CHECKING:
30 | from unittest.mock import MagicMock, Mock
31 |
32 | from pytest_mock import MockerFixture
33 |
34 | from molecule import config
35 |
36 |
37 | @pytest.fixture
38 | def _patched_ansible_check(mocker: MockerFixture) -> MagicMock:
39 | return mocker.patch("molecule.provisioner.ansible.Ansible.check")
40 |
41 |
42 | # NOTE(retr0h): The use of the `patched_config_validate` fixture, disables
43 | # config.Config._validate from executing. Thus preventing odd side-effects
44 | # throughout patched.assert_called unit tests.
45 | def test_check_execute( # noqa: D103
46 | mocker: MockerFixture,
47 | caplog: pytest.LogCaptureFixture,
48 | _patched_ansible_check: Mock, # noqa: PT019
49 | patched_config_validate: Mock,
50 | config_instance: config.Config,
51 | ) -> None:
52 | c = check.Check(config_instance)
53 | c.execute()
54 |
55 | assert "default" in caplog.text
56 | assert "check" in caplog.text
57 |
--------------------------------------------------------------------------------
/tests/unit/command/test_cleanup.py:
--------------------------------------------------------------------------------
1 | # Copyright (c) 2015-2018 Cisco Systems, Inc. # noqa: D100
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 | from __future__ import annotations
21 |
22 | import os
23 |
24 | from typing import TYPE_CHECKING
25 |
26 | import pytest
27 |
28 | from molecule import config, util
29 | from molecule.command import cleanup
30 |
31 |
32 | if TYPE_CHECKING:
33 | from typing import Literal
34 | from unittest.mock import MagicMock, Mock
35 |
36 | from pytest_mock import MockerFixture
37 |
38 | from molecule.types import ProvisionerData
39 |
40 |
41 | @pytest.fixture
42 | def _command_provisioner_section_with_cleanup_data() -> dict[
43 | Literal["provisioner"],
44 | ProvisionerData,
45 | ]:
46 | return {"provisioner": {"name": "ansible", "playbooks": {"cleanup": "cleanup.yml"}}}
47 |
48 |
49 | @pytest.fixture
50 | def _patched_ansible_cleanup(mocker: MockerFixture) -> MagicMock:
51 | return mocker.patch("molecule.provisioner.ansible.Ansible.cleanup")
52 |
53 |
54 | # NOTE(retr0h): The use of the `patched_config_validate` fixture, disables
55 | # config.Config._validate from executing. Thus preventing odd side-effects
56 | # throughout patched.assert_called unit tests.
57 | @pytest.mark.parametrize(
58 | "config_instance",
59 | ["_command_provisioner_section_with_cleanup_data"], # noqa: PT007
60 | indirect=True,
61 | )
62 | def test_cleanup_execute( # noqa: D103
63 | mocker: MockerFixture,
64 | _patched_ansible_cleanup: Mock, # noqa: PT019
65 | caplog: pytest.LogCaptureFixture,
66 | patched_config_validate: Mock,
67 | config_instance: config.Config,
68 | ) -> None:
69 | pb = os.path.join(config_instance.scenario.directory, "cleanup.yml") # noqa: PTH118
70 | util.write_file(pb, "")
71 |
72 | cu = cleanup.Cleanup(config_instance)
73 | cu.execute()
74 |
75 | assert "cleanup" in caplog.text
76 |
77 | _patched_ansible_cleanup.assert_called_once_with()
78 |
79 |
80 | def test_cleanup_execute_skips_when_playbook_not_configured( # noqa: D103
81 | caplog: pytest.LogCaptureFixture,
82 | _patched_ansible_cleanup: Mock, # noqa: PT019
83 | config_instance: config.Config,
84 | ) -> None:
85 | cu = cleanup.Cleanup(config_instance)
86 | cu.execute()
87 |
88 | msg = "Skipping, cleanup playbook not configured."
89 | assert msg in caplog.text
90 |
91 | assert not _patched_ansible_cleanup.called
92 |
--------------------------------------------------------------------------------
/tests/unit/command/test_converge.py:
--------------------------------------------------------------------------------
1 | # Copyright (c) 2015-2018 Cisco Systems, Inc. # noqa: D100
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 | from __future__ import annotations
21 |
22 | from typing import TYPE_CHECKING, Any
23 |
24 | from click.testing import CliRunner
25 |
26 | from molecule.command import converge
27 | from molecule.shell import main
28 |
29 |
30 | if TYPE_CHECKING:
31 | from unittest.mock import Mock
32 |
33 | import pytest
34 |
35 | from pytest_mock import MockerFixture
36 |
37 | from molecule import config
38 |
39 |
40 | # NOTE(retr0h): The use of the `patched_config_validate` fixture, disables
41 | # config.Config._validate from executing. Thus preventing odd side-effects
42 | # throughout patched.assert_called unit tests.
43 | def test_converge_execute( # noqa: D103
44 | mocker: MockerFixture,
45 | caplog: pytest.LogCaptureFixture,
46 | patched_ansible_converge: Mock,
47 | patched_config_validate: Any, # noqa: ANN401
48 | config_instance: config.Config,
49 | ) -> None:
50 | c = converge.Converge(config_instance)
51 | c.execute()
52 |
53 | assert "default" in caplog.text
54 | assert "converge" in caplog.text
55 |
56 | patched_ansible_converge.assert_called_once_with()
57 |
58 | assert config_instance.state.converged
59 |
60 |
61 | def test_ansible_args_passed_to_scenarios_get_configs(mocker: MockerFixture) -> None: # noqa: D103
62 | # Scenarios patch is needed to safely invoke CliRunner
63 | # in the test environment and block scenario execution
64 | mocker.patch("molecule.scenarios.Scenarios")
65 | patched_get_configs = mocker.patch("molecule.command.base.get_configs")
66 |
67 | runner = CliRunner()
68 | args = ("converge", "--", "-e", "testvar=testvalue")
69 | ansible_args = args[2:]
70 | runner.invoke(main, args, obj={})
71 |
72 | # call index [0][2] is the 3rd positional argument to get_configs,
73 | # which should be the tuple of parsed ansible_args from the CLI
74 | assert patched_get_configs.call_args[0][2] == ansible_args
75 |
--------------------------------------------------------------------------------
/tests/unit/command/test_create.py:
--------------------------------------------------------------------------------
1 | # Copyright (c) 2015-2018 Cisco Systems, Inc. # noqa: D100
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 | from __future__ import annotations
21 |
22 | from typing import TYPE_CHECKING
23 |
24 | import pytest
25 |
26 | from molecule.command import create
27 |
28 |
29 | if TYPE_CHECKING:
30 | from unittest.mock import MagicMock, Mock
31 |
32 | from pytest_mock import MockerFixture
33 |
34 | from molecule import config
35 |
36 |
37 | @pytest.fixture
38 | def _patched_create_setup(mocker: MockerFixture) -> MagicMock:
39 | return mocker.patch("molecule.command.create.Create._setup")
40 |
41 |
42 | # NOTE(retr0h): The use of the `patched_config_validate` fixture, disables
43 | # config.Config._validate from executing. Thus preventing odd side-effects
44 | # throughout patched.assert_called unit tests.
45 | @pytest.mark.skip(reason="create not running for delegated")
46 | def test_create_execute( # noqa: D103
47 | mocker: MockerFixture,
48 | caplog: pytest.LogCaptureFixture,
49 | command_patched_ansible_create: Mock,
50 | patched_config_validate: Mock,
51 | config_instance: config.Config,
52 | ) -> None:
53 | c = create.Create(config_instance)
54 | c.execute()
55 |
56 | assert "default" in caplog.text
57 | assert "converge" in caplog.text
58 |
59 | assert config_instance.state.driver == "default"
60 |
61 | command_patched_ansible_create.assert_called_once_with()
62 |
63 | assert config_instance.state.created
64 |
65 |
66 | @pytest.mark.skip(reason="create not running for delegated")
67 | def test_execute_skips_when_instances_already_created( # noqa: D103
68 | caplog: pytest.LogCaptureFixture,
69 | command_patched_ansible_create: Mock,
70 | config_instance: config.Config,
71 | ) -> None:
72 | config_instance.state.change_state("created", True) # noqa: FBT003
73 | c = create.Create(config_instance)
74 | c.execute()
75 |
76 | msg = "Skipping, instances already created."
77 | assert msg in caplog.text
78 |
79 | assert not command_patched_ansible_create.called
80 |
--------------------------------------------------------------------------------
/tests/unit/command/test_dependency.py:
--------------------------------------------------------------------------------
1 | # Copyright (c) 2015-2018 Cisco Systems, Inc. # noqa: D100
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 | from __future__ import annotations
21 |
22 | from typing import TYPE_CHECKING
23 |
24 | from molecule.command import dependency
25 |
26 |
27 | if TYPE_CHECKING:
28 | from unittest.mock import Mock
29 |
30 | import pytest
31 |
32 | from pytest_mock import MockerFixture
33 |
34 | from molecule import config
35 |
36 |
37 | # NOTE(retr0h): The use of the `patched_config_validate` fixture, disables
38 | # config.Config._validate from executing. Thus preventing odd side-effects
39 | # throughout patched.assert_called unit tests.
40 | def test_dependency_execute( # noqa: D103
41 | mocker: MockerFixture,
42 | caplog: pytest.LogCaptureFixture,
43 | patched_ansible_galaxy: Mock,
44 | patched_config_validate: Mock,
45 | config_instance: config.Config,
46 | ) -> None:
47 | d = dependency.Dependency(config_instance)
48 | d.execute()
49 |
50 | patched_ansible_galaxy.assert_called_once_with()
51 |
52 | assert "default" in caplog.text
53 | assert "dependency" in caplog.text
54 |
--------------------------------------------------------------------------------
/tests/unit/command/test_destroy.py:
--------------------------------------------------------------------------------
1 | # Copyright (c) 2015-2018 Cisco Systems, Inc. # noqa: D100
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 | from __future__ import annotations
21 |
22 | from typing import TYPE_CHECKING
23 |
24 | import pytest
25 |
26 | from molecule.command import destroy
27 |
28 |
29 | if TYPE_CHECKING:
30 | from unittest.mock import MagicMock, Mock
31 |
32 | from pytest_mock import MockerFixture
33 |
34 | from molecule import config
35 |
36 |
37 | @pytest.fixture
38 | def _patched_ansible_destroy(mocker: MockerFixture) -> MagicMock:
39 | return mocker.patch("molecule.provisioner.ansible.Ansible.destroy")
40 |
41 |
42 | @pytest.fixture
43 | def _patched_destroy_setup(mocker: MockerFixture) -> MagicMock:
44 | return mocker.patch("molecule.command.destroy.Destroy._setup")
45 |
46 |
47 | # NOTE(retr0h): The use of the `patched_config_validate` fixture, disables
48 | # config.Config._validate from executing. Thus preventing odd side-effects
49 | # throughout patched.assert_called unit tests.
50 | @pytest.mark.skip(reason="destroy not running for delegated")
51 | def test_destroy_execute( # noqa: D103
52 | mocker: MockerFixture,
53 | caplog: pytest.LogCaptureFixture,
54 | patched_config_validate: Mock,
55 | _patched_ansible_destroy: Mock, # noqa: PT019
56 | config_instance: config.Config,
57 | ) -> None:
58 | d = destroy.Destroy(config_instance)
59 | d.execute()
60 |
61 | assert "destroy" in caplog.text
62 |
63 | assert "verify" in caplog.text
64 |
65 | _patched_ansible_destroy.assert_called_once_with()
66 |
67 | assert not config_instance.state.converged
68 | assert not config_instance.state.created
69 |
70 |
71 | @pytest.mark.parametrize(
72 | "config_instance",
73 | ["command_driver_delegated_section_data"], # noqa: PT007
74 | indirect=True,
75 | )
76 | def test_execute_skips_when_destroy_strategy_is_never( # noqa: D103
77 | _patched_destroy_setup: Mock, # noqa: PT019
78 | caplog: pytest.LogCaptureFixture,
79 | _patched_ansible_destroy: Mock, # noqa: PT019
80 | config_instance: config.Config,
81 | ) -> None:
82 | config_instance.command_args = {"destroy": "never"}
83 |
84 | d = destroy.Destroy(config_instance)
85 | d.execute()
86 |
87 | msg = "Skipping, '--destroy=never' requested."
88 | assert msg in caplog.text
89 |
90 | assert not _patched_ansible_destroy.called
91 |
--------------------------------------------------------------------------------
/tests/unit/command/test_list.py:
--------------------------------------------------------------------------------
1 | # Copyright (c) 2015-2018 Cisco Systems, Inc. # noqa: D100
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 | from __future__ import annotations
21 |
22 | from typing import TYPE_CHECKING
23 |
24 | from molecule.command import list # noqa: A004
25 | from molecule.status import Status
26 |
27 |
28 | if TYPE_CHECKING:
29 | import pytest
30 |
31 | from molecule import config
32 |
33 |
34 | def test_list_execute( # noqa: D103
35 | capsys: pytest.CaptureFixture[str],
36 | config_instance: config.Config,
37 | ) -> None:
38 | l = list.List(config_instance) # noqa: E741
39 | x = [
40 | Status(
41 | instance_name="instance-1",
42 | driver_name="default",
43 | provisioner_name="ansible",
44 | scenario_name="default",
45 | created="false",
46 | converged="false",
47 | ),
48 | Status(
49 | instance_name="instance-2",
50 | driver_name="default",
51 | provisioner_name="ansible",
52 | scenario_name="default",
53 | created="false",
54 | converged="false",
55 | ),
56 | ]
57 |
58 | assert x == l.execute()
59 |
--------------------------------------------------------------------------------
/tests/unit/command/test_matrix.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 | """Unittests for matrix command."""
21 |
--------------------------------------------------------------------------------
/tests/unit/command/test_syntax.py:
--------------------------------------------------------------------------------
1 | # Copyright (c) 2015-2018 Cisco Systems, Inc. # noqa: D100
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 | from __future__ import annotations
21 |
22 | from typing import TYPE_CHECKING
23 |
24 | import pytest
25 |
26 | from molecule.command import syntax
27 |
28 |
29 | if TYPE_CHECKING:
30 | from unittest.mock import MagicMock, Mock
31 |
32 | from pytest_mock import MockerFixture
33 |
34 | from molecule import config
35 |
36 |
37 | @pytest.fixture
38 | def _patched_ansible_syntax(mocker: MockerFixture) -> MagicMock:
39 | return mocker.patch("molecule.provisioner.ansible.Ansible.syntax")
40 |
41 |
42 | # NOTE(retr0h): The use of the `patched_config_validate` fixture, disables
43 | # config.Config._validate from executing. Thus preventing odd side-effects
44 | # throughout patched.assert_called unit tests.
45 | def test_syntax_execute( # noqa: D103
46 | mocker: MockerFixture,
47 | caplog: pytest.LogCaptureFixture,
48 | _patched_ansible_syntax: Mock, # noqa: PT019
49 | patched_config_validate: Mock,
50 | config_instance: config.Config,
51 | ) -> None:
52 | s = syntax.Syntax(config_instance)
53 | s.execute()
54 |
55 | assert "default" in caplog.text
56 | assert "syntax" in caplog.text
57 |
58 | _patched_ansible_syntax.assert_called_once_with()
59 |
--------------------------------------------------------------------------------
/tests/unit/command/test_test.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 | """Unittests for test command."""
21 |
--------------------------------------------------------------------------------
/tests/unit/command/test_verify.py:
--------------------------------------------------------------------------------
1 | # Copyright (c) 2015-2018 Cisco Systems, Inc. # noqa: D100
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 | from __future__ import annotations
21 |
22 | from typing import TYPE_CHECKING
23 |
24 | from molecule.command import verify
25 |
26 |
27 | if TYPE_CHECKING:
28 | from unittest.mock import Mock
29 |
30 | import pytest
31 |
32 | from pytest_mock import MockerFixture
33 |
34 | from molecule import config
35 |
36 |
37 | # NOTE(retr0h): The use of the `patched_config_validate` fixture, disables
38 | # config.Config._validate from executing. Thus preventing odd side-effects
39 | # throughout patched.assert_called unit tests.
40 | def test_verify_execute( # noqa: D103
41 | mocker: MockerFixture,
42 | caplog: pytest.LogCaptureFixture,
43 | patched_default_verifier: Mock,
44 | patched_config_validate: Mock,
45 | config_instance: config.Config,
46 | ) -> None:
47 | v = verify.Verify(config_instance)
48 | v.execute()
49 |
50 | assert "default" in caplog.text
51 | assert "verify" in caplog.text
52 |
--------------------------------------------------------------------------------
/tests/unit/dependency/__init__.py:
--------------------------------------------------------------------------------
1 | # noqa: D104
2 |
--------------------------------------------------------------------------------
/tests/unit/dependency/ansible_galaxy/__init__.py:
--------------------------------------------------------------------------------
1 | # noqa: D104
2 |
--------------------------------------------------------------------------------
/tests/unit/driver/__init__.py:
--------------------------------------------------------------------------------
1 | # noqa: D104
2 |
--------------------------------------------------------------------------------
/tests/unit/lint/__init__.py:
--------------------------------------------------------------------------------
1 | # noqa: D104
2 |
--------------------------------------------------------------------------------
/tests/unit/model/__init__.py:
--------------------------------------------------------------------------------
1 | # noqa: D104
2 |
--------------------------------------------------------------------------------
/tests/unit/model/v2/__init__.py:
--------------------------------------------------------------------------------
1 | # noqa: D104
2 |
--------------------------------------------------------------------------------
/tests/unit/model/v2/conftest.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 | """Unittest for v2 config format."""
21 |
22 | from __future__ import annotations
23 |
24 | from typing import TYPE_CHECKING
25 |
26 | import pytest
27 |
28 | from molecule import util
29 |
30 |
31 | if TYPE_CHECKING:
32 | from pathlib import Path
33 | from typing import Any
34 |
35 |
36 | @pytest.fixture(name="molecule_file")
37 | def fixture_molecule_file(resources_folder_path: Path) -> Path:
38 | """Return path to molecule file.
39 |
40 | Args:
41 | resources_folder_path: Path to the resources folder.
42 |
43 | Returns:
44 | Path to molecule file.
45 | """
46 | return resources_folder_path / "molecule.yml"
47 |
48 |
49 | @pytest.fixture
50 | def config(molecule_file: Path, request: pytest.FixtureRequest) -> dict[str, Any]:
51 | """Return merged molecule file data.
52 |
53 | Args:
54 | molecule_file: Path to molecule file.
55 | request: Pytest fixture request.
56 |
57 | Returns:
58 | Merged molecule file data.
59 | """
60 | with molecule_file.open() as f:
61 | d = util.safe_load(f)
62 | if hasattr(request, "param"):
63 | if isinstance(request.getfixturevalue(request.param), str):
64 | d2 = util.safe_load(request.getfixturevalue(request.param))
65 | else:
66 | d2 = request.getfixturevalue(request.param)
67 | d = util.merge_dicts(d, d2)
68 |
69 | return d # type: ignore[no-any-return]
70 |
71 |
72 | @pytest.fixture
73 | def _model_platforms_delegated_section_data() -> str:
74 | return """
75 | ---
76 | platforms:
77 | - name: instance
78 | """.strip()
79 |
--------------------------------------------------------------------------------
/tests/unit/model/v2/test_platforms_section.py:
--------------------------------------------------------------------------------
1 | # Copyright (c) 2015-2018 Cisco Systems, Inc. # noqa: D100
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 | from __future__ import annotations
21 |
22 | import pytest
23 |
24 | from molecule.model import schema_v3
25 |
26 |
27 | @pytest.mark.parametrize(
28 | "config",
29 | ["_model_platforms_delegated_section_data"], # noqa: PT007
30 | indirect=True,
31 | )
32 | def test_platforms_delegated(config): # type: ignore[no-untyped-def] # noqa: ANN201, D103
33 | assert not schema_v3.validate(config)
34 |
--------------------------------------------------------------------------------
/tests/unit/model/v2/test_scenario_section.py:
--------------------------------------------------------------------------------
1 | # Copyright (c) 2015-2018 Cisco Systems, Inc. # noqa: D100
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 | from __future__ import annotations
21 |
22 | import pytest
23 |
24 | from molecule.model import schema_v3
25 |
26 |
27 | @pytest.fixture
28 | def _model_scenario_section_data(): # type: ignore[no-untyped-def] # noqa: ANN202
29 | return {
30 | "scenario": {
31 | "name": "foo",
32 | "check_sequence": ["check"],
33 | "converge_sequence": ["converge"],
34 | "create_sequence": ["create"],
35 | "destroy_sequence": ["destroy"],
36 | "test_sequence": ["test"],
37 | },
38 | }
39 |
40 |
41 | @pytest.mark.parametrize("config", ["_model_scenario_section_data"], indirect=True) # noqa: PT007
42 | def test_scenario(config): # type: ignore[no-untyped-def] # noqa: ANN201, D103
43 | assert not schema_v3.validate(config)
44 |
45 |
46 | @pytest.fixture
47 | def _model_scenario_errors_section_data(): # type: ignore[no-untyped-def] # noqa: ANN202
48 | return {"scenario": {"name": 0}}
49 |
50 |
51 | @pytest.mark.parametrize(
52 | "config",
53 | ["_model_scenario_errors_section_data"], # noqa: PT007
54 | indirect=True,
55 | )
56 | def test_scenario_has_errors(config): # type: ignore[no-untyped-def] # noqa: ANN201, D103
57 | x = ["0 is not of type 'string'"]
58 |
59 | assert x == schema_v3.validate(config)
60 |
--------------------------------------------------------------------------------
/tests/unit/model/v2/test_schema.py:
--------------------------------------------------------------------------------
1 | # Copyright (c) 2015-2018 Cisco Systems, Inc. # noqa: D100
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 | from __future__ import annotations
21 |
22 | import typing
23 |
24 | from molecule.model import schema_v3
25 | from tests.conftest import run
26 |
27 |
28 | if typing.TYPE_CHECKING:
29 | from pathlib import Path
30 |
31 |
32 | def test_base_config(config): # type: ignore[no-untyped-def] # noqa: ANN201, D103
33 | assert not schema_v3.validate(config)
34 |
35 |
36 | def test_molecule_schema(resources_folder_path: Path) -> None:
37 | """Test the molecule schema.
38 |
39 | Args:
40 | resources_folder_path: Path to the resources folder.
41 | """
42 | cmd = [
43 | "uv",
44 | "tool",
45 | "run",
46 | "check-jsonschema",
47 | "-v",
48 | "--schemafile",
49 | "src/molecule/data/molecule.json",
50 | f"{resources_folder_path}/schema_instance_files/valid/molecule.yml",
51 | ]
52 | assert run(cmd).returncode == 0
53 |
54 | cmd = [
55 | "uv",
56 | "tool",
57 | "run",
58 | "check-jsonschema",
59 | "-v",
60 | "--schemafile",
61 | "src/molecule/data/driver.json",
62 | f"{resources_folder_path}/schema_instance_files/invalid/molecule_delegated.yml",
63 | ]
64 | assert run(cmd).returncode != 0
65 |
--------------------------------------------------------------------------------
/tests/unit/model/v2/test_verifier_section.py:
--------------------------------------------------------------------------------
1 | # Copyright (c) 2015-2018 Cisco Systems, Inc. # noqa: D100
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 | from __future__ import annotations
21 |
22 | import pytest
23 |
24 | from molecule.model import schema_v3
25 |
26 |
27 | @pytest.fixture
28 | def _model_verifier_section_data(): # type: ignore[no-untyped-def] # noqa: ANN202
29 | return {
30 | "verifier": {
31 | "name": "testinfra",
32 | "enabled": True,
33 | "directory": "foo",
34 | "options": {"foo": "bar"},
35 | "env": {"FOO": "foo", "FOO_BAR": "foo_bar"},
36 | "additional_files_or_dirs": ["foo"],
37 | },
38 | }
39 |
40 |
41 | @pytest.mark.parametrize("config", ["_model_verifier_section_data"], indirect=True) # noqa: PT007
42 | def test_verifier(config): # type: ignore[no-untyped-def] # noqa: ANN201, D103
43 | assert not schema_v3.validate(config)
44 |
45 |
46 | @pytest.fixture
47 | def _model_verifier_errors_section_data(): # type: ignore[no-untyped-def] # noqa: ANN202
48 | return {
49 | "verifier": {
50 | "name": 0,
51 | },
52 | }
53 |
54 |
55 | @pytest.mark.parametrize(
56 | "config",
57 | ["_model_verifier_errors_section_data"], # noqa: PT007
58 | indirect=True,
59 | )
60 | def test_verifier_has_errors(config): # type: ignore[no-untyped-def] # noqa: ANN201, D103
61 | x = ["0 is not one of ['ansible', 'goss', 'inspec', 'testinfra']"]
62 |
63 | assert x == schema_v3.validate(config)
64 |
65 |
66 | @pytest.fixture
67 | def _model_verifier_allows_testinfra_section_data(): # type: ignore[no-untyped-def] # noqa: ANN202
68 | return {"verifier": {"name": "testinfra"}}
69 |
70 |
71 | @pytest.fixture
72 | def _model_verifier_allows_ansible_section_data(): # type: ignore[no-untyped-def] # noqa: ANN202
73 | return {"verifier": {"name": "ansible"}}
74 |
75 |
76 | @pytest.mark.parametrize(
77 | "config",
78 | [ # noqa: PT007
79 | ("_model_verifier_allows_testinfra_section_data"),
80 | ("_model_verifier_allows_ansible_section_data"),
81 | ],
82 | indirect=True,
83 | )
84 | def test_verifier_allows_name(config): # type: ignore[no-untyped-def] # noqa: ANN201, D103
85 | assert not schema_v3.validate(config)
86 |
--------------------------------------------------------------------------------
/tests/unit/provisioner/__init__.py:
--------------------------------------------------------------------------------
1 | # noqa: D104
2 |
--------------------------------------------------------------------------------
/tests/unit/test_api.py:
--------------------------------------------------------------------------------
1 | # Copyright (c) 2019 Red Hat, Inc. # noqa: D100
2 | # Copyright (c) 2015-2018 Cisco Systems, 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 | from __future__ import annotations
22 |
23 | from molecule import api
24 |
25 |
26 | def test_api_drivers() -> None: # noqa: D103
27 | results = api.drivers()
28 |
29 | for result in results.values():
30 | assert isinstance(result, api.Driver)
31 |
32 | assert "default" in results
33 |
34 |
35 | def test_api_verifiers() -> None: # noqa: D103
36 | x = ["testinfra", "ansible"]
37 |
38 | assert all(elem in api.verifiers() for elem in x)
39 |
--------------------------------------------------------------------------------
/tests/unit/test_platforms.py:
--------------------------------------------------------------------------------
1 | # Copyright (c) 2015-2018 Cisco Systems, Inc. # noqa: D100
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 | from __future__ import annotations
21 |
22 | import pytest
23 |
24 | from molecule import config, platforms
25 |
26 |
27 | @pytest.fixture
28 | def _instance(config_instance: config.Config): # type: ignore[no-untyped-def] # noqa: ANN202
29 | return platforms.Platforms(config_instance)
30 |
31 |
32 | def test_instances_property(_instance): # type: ignore[no-untyped-def] # noqa: ANN201, PT019, D103
33 | x = [
34 | {"groups": ["foo", "bar"], "name": "instance-1", "children": ["child1"]},
35 | {"groups": ["baz", "foo"], "name": "instance-2", "children": ["child2"]},
36 | ]
37 |
38 | assert x == _instance.instances
39 |
40 |
41 | @pytest.fixture(name="platform_name")
42 | def fixture_platform_name(request, config_instance: config.Config): # type: ignore[no-untyped-def] # noqa: ANN201, D103
43 | return platforms.Platforms(config_instance, platform_name=request.param)
44 |
45 |
46 | @pytest.mark.parametrize("platform_name", ["instance-1"], indirect=True) # noqa: PT007
47 | def test_instances_property_with_platform_name_instance_1(platform_name): # type: ignore[no-untyped-def] # noqa: ANN201, D103
48 | x = [
49 | {"groups": ["foo", "bar"], "name": "instance-1", "children": ["child1"]},
50 | ]
51 |
52 | assert x == platform_name.instances
53 |
54 |
55 | @pytest.mark.parametrize("platform_name", ["instance-2"], indirect=True) # noqa: PT007
56 | def test_instances_property_with_platform_name_instance_2(platform_name): # type: ignore[no-untyped-def] # noqa: ANN201, D103
57 | x = [
58 | {"groups": ["baz", "foo"], "name": "instance-2", "children": ["child2"]},
59 | ]
60 |
61 | assert x == platform_name.instances
62 |
--------------------------------------------------------------------------------
/tests/unit/test_scenarios_ordered.py:
--------------------------------------------------------------------------------
1 | # Copyright (c) 2018 Marc Dequènes (Duck) # noqa: D100
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 | from __future__ import annotations
21 |
22 | import copy
23 |
24 | import pytest
25 |
26 | from molecule import config, scenarios
27 |
28 |
29 | @pytest.fixture
30 | def _instance(config_instance: config.Config): # type: ignore[no-untyped-def] # noqa: ANN202
31 | config_instance_1 = copy.deepcopy(config_instance)
32 | config_instance_1.config["scenario"]["name"] = "two"
33 | config_instance_1.molecule_file = config_instance_1.molecule_file.replace(
34 | "default",
35 | "02_foo",
36 | )
37 |
38 | config_instance_2 = copy.deepcopy(config_instance)
39 | config_instance_2.config["scenario"]["name"] = "one"
40 | config_instance_2.molecule_file = config_instance_2.molecule_file.replace(
41 | "default",
42 | "01_foo",
43 | )
44 |
45 | config_instance_3 = copy.deepcopy(config_instance)
46 | config_instance_3.config["scenario"]["name"] = "three"
47 | config_instance_3.molecule_file = config_instance_3.molecule_file.replace(
48 | "default",
49 | "03_foo",
50 | )
51 |
52 | return scenarios.Scenarios(
53 | [config_instance_1, config_instance_2, config_instance_3],
54 | )
55 |
56 |
57 | def test_all_ordered(_instance): # type: ignore[no-untyped-def] # noqa: ANN201, PT019, D103
58 | result = _instance.all
59 |
60 | assert len(result) == 3 # noqa: PLR2004
61 | assert result[0].name == "one"
62 | assert result[1].name == "two"
63 | assert result[2].name == "three"
64 |
--------------------------------------------------------------------------------
/tests/unit/test_shell.py:
--------------------------------------------------------------------------------
1 | # Copyright (c) 2015-2018 Cisco Systems, Inc. # noqa: D100
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 | from __future__ import annotations
21 |
22 | import pytest
23 |
24 | from molecule import shell
25 |
26 |
27 | def test_shell() -> None: # noqa: D103
28 | with pytest.raises(SystemExit):
29 | shell.main() # pylint: disable=no-value-for-parameter
30 |
--------------------------------------------------------------------------------
/tests/unit/test_status.py:
--------------------------------------------------------------------------------
1 | # Copyright (c) 2015-2018 Cisco Systems, Inc. # noqa: D100
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 | from __future__ import annotations
21 |
22 | import pytest
23 |
24 | from molecule.status import Status
25 |
26 |
27 | @pytest.fixture
28 | def _instance() -> Status:
29 | s = Status(
30 | instance_name="instance",
31 | driver_name="driver",
32 | provisioner_name="provisioner",
33 | scenario_name="scenario",
34 | created="True",
35 | converged="False",
36 | )
37 |
38 | return s # noqa: RET504
39 |
40 |
41 | def test__instance_name_attribute(_instance: Status) -> None: # noqa: PT019, D103
42 | assert _instance.instance_name == "instance"
43 |
44 |
45 | def test_status_driver_name_attribute(_instance: Status) -> None: # noqa: PT019, D103
46 | assert _instance.driver_name == "driver"
47 |
48 |
49 | def test_status_provisioner_name_attribute(_instance: Status) -> None: # noqa: PT019, D103
50 | assert _instance.provisioner_name == "provisioner"
51 |
52 |
53 | def test_status_scenario_name_attribute(_instance: Status) -> None: # noqa: PT019, D103
54 | assert _instance.scenario_name == "scenario"
55 |
56 |
57 | def test_status_created_attribute(_instance: Status) -> None: # noqa: PT019, D103
58 | assert _instance.created == "True"
59 |
60 |
61 | def test_status_converged_attribute(_instance: Status) -> None: # noqa: PT019, D103
62 | assert _instance.converged == "False"
63 |
--------------------------------------------------------------------------------
/tests/unit/test_text.py:
--------------------------------------------------------------------------------
1 | """Test the molecule text module."""
2 |
3 | from __future__ import annotations
4 |
5 | from molecule.text import (
6 | camelize,
7 | strip_ansi_color,
8 | strip_ansi_escape,
9 | title,
10 | underscore,
11 | )
12 |
13 |
14 | def test_camelize() -> None: # noqa: D103
15 | assert camelize("foo") == "Foo"
16 | assert camelize("foo_bar") == "FooBar"
17 | assert camelize("foo_bar_baz") == "FooBarBaz"
18 |
19 |
20 | def test_strip_ansi_color() -> None: # noqa: D103
21 | s = "foo\x1b[0m\x1b[0m\x1b[0m\n\x1b[0m\x1b[0m\x1b[0m\x1b[0m\x1b[0m"
22 |
23 | assert strip_ansi_color(s) == "foo\n"
24 |
25 |
26 | def test_strip_ansi_escape() -> None: # noqa: D103
27 | string = "ls\r\n\x1b[00m\x1b[01;31mfoo\x1b[00m\r\n\x1b[01;31m"
28 |
29 | assert strip_ansi_escape(string) == "ls\r\nfoo\r\n"
30 |
31 |
32 | def test_title() -> None: # noqa: D103
33 | assert title("foo") == "Foo"
34 | assert title("foo_bar") == "Foo Bar"
35 |
36 |
37 | def test_underscore() -> None: # noqa: D103
38 | assert underscore("Foo") == "foo"
39 | assert underscore("FooBar") == "foo_bar"
40 | assert underscore("FooBarBaz") == "foo_bar_baz"
41 |
--------------------------------------------------------------------------------
/tests/unit/verifier/__init__.py:
--------------------------------------------------------------------------------
1 | # noqa: D104
2 |
--------------------------------------------------------------------------------
/tools/get-version.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -e
3 | {
4 | python3 -c "import setuptools_scm" || python3 -m pip install --user setuptools-scm
5 | } 1>&2 # redirect stdout to stderr to avoid polluting the output
6 | python3 -m setuptools_scm | \
7 | sed 's/Guessed Version\([^+]\+\).*/\1/'
8 |
--------------------------------------------------------------------------------
/tools/opts.txt:
--------------------------------------------------------------------------------
1 | distro
2 |
--------------------------------------------------------------------------------
/tools/report-coverage:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | # cspell: ignore exuo
3 | set -euo pipefail
4 | coverage combine -q "--data-file=${TOX_ENV_DIR}/.coverage" "${TOX_ENV_DIR}"/.coverage.*
5 | coverage xml "--data-file=${TOX_ENV_DIR}/.coverage" -o "${TOX_ENV_DIR}/coverage.xml" --ignore-errors --fail-under=0
6 | COVERAGE_FILE="${TOX_ENV_DIR}/.coverage" coverage lcov --fail-under=0 --ignore-errors -q
7 | COVERAGE_FILE="${TOX_ENV_DIR}/.coverage" coverage report --ignore-errors
8 |
--------------------------------------------------------------------------------
/tools/test-setup.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -euxo pipefail
3 | # Used by Zuul CI to perform extra bootstrapping
4 |
5 | # Bumping system tox because version from CentOS 7 is too old
6 | # We are not using pip --user due to few bugs in tox role which does not allow
7 | # us to override how is called. Once these are addressed we will switch back
8 | # non-sudo mode.
9 | PYTHON=$(command -v python3 python | head -n1)
10 |
11 | sudo "$PYTHON" -m pip install "tox>=3.14.6"
12 | #
13 | # Bootstrapping of services needed for testing, like docker or podman, is done
14 | # via pre.yaml playbook from ansible-tox-molecule job:
15 | # https://github.com/ansible/ansible-zuul-jobs/blob/master/playbooks/ansible-tox-molecule/pre.yaml
16 | #
17 | # test-setup.sh runs before that playbook.
18 |
--------------------------------------------------------------------------------
/tools/update-version.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | DIR=$(dirname "$0")
3 | VERSION=$(./tools/get-version.sh)
4 | mkdir -p "${DIR}/../dist"
5 | sed -e "s/VERSION_PLACEHOLDER/${VERSION}/" \
6 | "${DIR}/../.config/molecule.spec" \
7 | > "${DIR}/../dist/molecule.spec"
8 |
--------------------------------------------------------------------------------