summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--MANIFEST.in1
-rw-r--r--PKG-INFO2
-rw-r--r--changelogs/CHANGELOG-v2.10.rst35
-rw-r--r--changelogs/changelog.yaml73
-rw-r--r--docs/docsite/Makefile6
-rw-r--r--docs/docsite/rst/collections/all_plugins.rst11
-rw-r--r--docs/docsite/rst/dev_guide/developing_collections.rst31
-rw-r--r--docs/docsite/rst/galaxy/user_guide.rst4
-rw-r--r--docs/docsite/rst/index.rst1
-rw-r--r--docs/docsite/rst/scenario_guides/guide_docker.rst305
-rw-r--r--docs/docsite/rst/user_guide/complex_data_manipulation.rst19
-rw-r--r--docs/docsite/rst/user_guide/playbooks_advanced_syntax.rst2
-rw-r--r--docs/docsite/rst/user_guide/vault.rst9
-rw-r--r--docs/man/man1/ansible-config.12
-rw-r--r--docs/man/man1/ansible-console.12
-rw-r--r--docs/man/man1/ansible-doc.12
-rw-r--r--docs/man/man1/ansible-galaxy.12
-rw-r--r--docs/man/man1/ansible-inventory.12
-rw-r--r--docs/man/man1/ansible-playbook.12
-rw-r--r--docs/man/man1/ansible-pull.12
-rw-r--r--docs/man/man1/ansible-vault.12
-rw-r--r--docs/man/man1/ansible.12
-rw-r--r--lib/ansible/cli/galaxy.py6
-rw-r--r--lib/ansible/inventory/group.py2
-rw-r--r--lib/ansible/inventory/host.py2
-rw-r--r--lib/ansible/module_utils/basic.py5
-rw-r--r--lib/ansible/modules/apt.py11
-rw-r--r--lib/ansible/modules/apt_key.py22
-rw-r--r--lib/ansible/modules/apt_repository.py18
-rw-r--r--lib/ansible/modules/assemble.py10
-rw-r--r--lib/ansible/modules/async_wrapper.py13
-rw-r--r--lib/ansible/modules/copy.py43
-rw-r--r--lib/ansible/modules/cron.py60
-rw-r--r--lib/ansible/modules/debconf.py13
-rw-r--r--lib/ansible/modules/debug.py20
-rw-r--r--lib/ansible/modules/fetch.py9
-rw-r--r--lib/ansible/modules/file.py34
-rw-r--r--lib/ansible/modules/group.py13
-rw-r--r--lib/ansible/modules/iptables.py34
-rw-r--r--lib/ansible/modules/lineinfile.py21
-rw-r--r--lib/ansible/modules/ping.py28
-rw-r--r--lib/ansible/modules/replace.py23
-rw-r--r--lib/ansible/modules/rpm_key.py13
-rw-r--r--lib/ansible/modules/script.py35
-rw-r--r--lib/ansible/modules/service.py19
-rw-r--r--lib/ansible/modules/service_facts.py9
-rw-r--r--lib/ansible/modules/setup.py29
-rw-r--r--lib/ansible/modules/shell.py34
-rw-r--r--lib/ansible/modules/slurp.py8
-rw-r--r--lib/ansible/modules/stat.py34
-rw-r--r--lib/ansible/modules/subversion.py12
-rw-r--r--lib/ansible/modules/systemd.py31
-rw-r--r--lib/ansible/modules/tempfile.py8
-rw-r--r--lib/ansible/modules/template.py14
-rw-r--r--lib/ansible/modules/unarchive.py11
-rw-r--r--lib/ansible/modules/user.py104
-rw-r--r--lib/ansible/playbook/task.py2
-rw-r--r--lib/ansible/plugins/connection/paramiko_ssh.py2
-rw-r--r--lib/ansible/plugins/connection/psrp.py2
-rw-r--r--lib/ansible/plugins/connection/winrm.py2
-rw-r--r--lib/ansible/plugins/strategy/__init__.py3
-rw-r--r--lib/ansible/release.py2
-rw-r--r--lib/ansible/template/__init__.py3
-rwxr-xr-xtest/integration/targets/ansible-galaxy/runme.sh11
-rw-r--r--test/integration/targets/ansible-test/ansible_collections/ns/col/plugins/filter/check_pylint.py21
-rw-r--r--test/integration/targets/ansible-test/ansible_collections/ns/col/plugins/modules/bad.py34
-rw-r--r--test/integration/targets/ansible-test/ansible_collections/ns/col/tests/integration/targets/hello/files/bad.py16
-rw-r--r--test/integration/targets/ansible-test/ansible_collections/ns/col/tests/sanity/ignore.txt6
-rwxr-xr-xtest/integration/targets/ansible-test/collection-tests/venv.sh4
-rw-r--r--test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/roles/common_handlers/handlers/main.yml21
-rw-r--r--test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/roles/test_fqcn_handlers/tasks/main.yml11
-rwxr-xr-xtest/integration/targets/collections_runtime_pythonpath/runme.sh2
-rwxr-xr-xtest/integration/targets/connection/test.sh2
-rw-r--r--test/integration/targets/connection/test_reset_connection.yml5
-rw-r--r--test/integration/targets/copy/tasks/selinux.yml35
-rwxr-xr-xtest/integration/targets/delegate_to/runme.sh1
-rw-r--r--test/integration/targets/expect/aliases1
-rw-r--r--test/integration/targets/expect/tasks/main.yml5
-rwxr-xr-xtest/integration/targets/filter_urls/runme.sh1
-rwxr-xr-xtest/integration/targets/groupby_filter/runme.sh1
-rwxr-xr-xtest/integration/targets/hash/runme.sh3
-rw-r--r--test/integration/targets/hash/test_inv1.yml10
-rw-r--r--test/integration/targets/hash/test_inv2.yml8
-rw-r--r--test/integration/targets/hash/test_inventory_hash.yml41
-rwxr-xr-xtest/integration/targets/incidental_inventory_docker_swarm/runme.sh1
-rwxr-xr-xtest/integration/targets/lookup_password/runme.sh1
-rw-r--r--test/integration/targets/noexec/aliases3
-rw-r--r--test/integration/targets/noexec/inventory1
-rwxr-xr-xtest/integration/targets/noexec/runme.sh9
-rw-r--r--test/integration/targets/noexec/test-noexec.yml8
-rwxr-xr-xtest/integration/targets/old_style_cache_plugins/runme.sh1
-rw-r--r--test/integration/targets/pip/tasks/main.yml34
-rw-r--r--test/integration/targets/pip/tasks/pip.yml4
-rw-r--r--test/integration/targets/setup_paramiko/install-Darwin-python-3.yml (renamed from test/integration/targets/setup_paramiko/install-MacOSX-10-python-3.yml)0
-rw-r--r--test/integration/targets/setup_paramiko/install.yml1
-rw-r--r--test/integration/targets/setup_paramiko/setup.sh1
-rw-r--r--test/integration/targets/setup_paramiko/uninstall-Darwin-python-3.yml (renamed from test/integration/targets/setup_paramiko/uninstall-MacOSX-10-python-3.yml)0
-rw-r--r--test/integration/targets/setup_paramiko/uninstall.yml1
-rw-r--r--test/integration/targets/setup_pexpect/files/constraints.txt2
-rw-r--r--test/integration/targets/setup_pexpect/meta/main.yml2
-rw-r--r--test/integration/targets/setup_pexpect/tasks/main.yml6
-rw-r--r--test/integration/targets/systemd/handlers/main.yml4
-rw-r--r--test/integration/targets/systemd/tasks/main.yml133
-rw-r--r--test/integration/targets/systemd/tasks/test_unit_template.yml50
-rw-r--r--test/integration/targets/systemd/templates/sleeper@.service8
-rw-r--r--test/integration/targets/systemd/vars/Debian.yml2
-rw-r--r--test/integration/targets/systemd/vars/default.yml2
-rwxr-xr-xtest/integration/targets/template_jinja2_latest/runme.sh1
-rw-r--r--test/integration/targets/user/tasks/main.yml1132
-rw-r--r--test/integration/targets/user/tasks/test_create_system_user.yml12
-rw-r--r--test/integration/targets/user/tasks/test_create_user.yml67
-rw-r--r--test/integration/targets/user/tasks/test_create_user_home.yml136
-rw-r--r--test/integration/targets/user/tasks/test_create_user_password.yml90
-rw-r--r--test/integration/targets/user/tasks/test_create_user_uid.yml26
-rw-r--r--test/integration/targets/user/tasks/test_expires.yml147
-rw-r--r--test/integration/targets/user/tasks/test_expires_new_account.yml55
-rw-r--r--test/integration/targets/user/tasks/test_expires_new_account_epoch_negative.yml112
-rw-r--r--test/integration/targets/user/tasks/test_local.yml169
-rw-r--r--test/integration/targets/user/tasks/test_local_expires.yml (renamed from test/integration/targets/user/tasks/expires_local.yml)0
-rw-r--r--test/integration/targets/user/tasks/test_no_home_fallback.yml106
-rw-r--r--test/integration/targets/user/tasks/test_password_lock.yml140
-rw-r--r--test/integration/targets/user/tasks/test_password_lock_new_user.yml63
-rw-r--r--test/integration/targets/user/tasks/test_remove_user.yml19
-rw-r--r--test/integration/targets/user/tasks/test_shadow_backup.yml21
-rw-r--r--test/integration/targets/user/tasks/test_ssh_key_passphrase.yml29
-rwxr-xr-xtest/integration/targets/vault/runme.sh4
-rw-r--r--test/lib/ansible_test/_data/completion/remote.txt1
-rw-r--r--test/lib/ansible_test/_data/injector/virtualenv-isolated.sh8
-rw-r--r--test/lib/ansible_test/_data/injector/virtualenv.sh8
-rw-r--r--test/lib/ansible_test/_data/requirements/constraints.txt6
-rw-r--r--test/lib/ansible_test/_data/requirements/sanity.pylint.txt2
-rw-r--r--test/lib/ansible_test/_data/sanity/pylint/plugins/unwanted.py (renamed from test/lib/ansible_test/_data/sanity/pylint/plugins/blacklist.py)108
-rw-r--r--test/lib/ansible_test/_data/setup/remote.sh10
-rw-r--r--test/lib/ansible_test/_internal/sanity/pylint.py8
-rw-r--r--test/sanity/ignore.txt4
135 files changed, 2433 insertions, 1845 deletions
diff --git a/MANIFEST.in b/MANIFEST.in
index 0eb9d004..0385260d 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -3,6 +3,7 @@ include COPYING
include SYMLINK_CACHE.json
include requirements.txt
recursive-include docs *
+include docs/docsite/rst/collections/all_plugins.rst
exclude docs/docsite/rst_warnings
recursive-exclude docs/docsite/_build *
recursive-exclude docs/docsite/_extensions *.pyc *.pyo
diff --git a/PKG-INFO b/PKG-INFO
index af9262e3..2974df94 100644
--- a/PKG-INFO
+++ b/PKG-INFO
@@ -1,6 +1,6 @@
Metadata-Version: 1.2
Name: ansible-base
-Version: 2.10.4
+Version: 2.10.5
Summary: Radically simple IT automation
Home-page: https://ansible.com/
Author: Ansible, Inc.
diff --git a/changelogs/CHANGELOG-v2.10.rst b/changelogs/CHANGELOG-v2.10.rst
index 0eac3697..8ecf7f1e 100644
--- a/changelogs/CHANGELOG-v2.10.rst
+++ b/changelogs/CHANGELOG-v2.10.rst
@@ -5,6 +5,41 @@ Ansible Base 2.10 "When the Levee Breaks" Release Notes
.. contents:: Topics
+v2.10.5
+=======
+
+Release Summary
+---------------
+
+| Release Date: 2021-01-18
+| `Porting Guide <https://docs.ansible.com/ansible/devel/porting_guides.html>`__
+
+
+Minor Changes
+-------------
+
+- ansible-test - Changed the internal name of the custom plugin used to identify use of unwanted imports and functions.
+- ansible-test - The ``pylint`` sanity test is now skipped with a warning on Python 3.9 due to unresolved upstream regressions.
+- ansible-test - The ``pylint`` sanity test is now supported on Python 3.8.
+- ansible-test - add macOS 11.1 as a remote target (https://github.com/ansible/ansible/pull/72622)
+- ansible-test - remote macOS instances no longer install ``virtualenv`` during provisioning
+- ansible-test - virtualenv helper scripts now prefer ``venv`` on Python 3 over ``virtualenv`` if the ``ANSIBLE_TEST_PREFER_VENV`` environment variable is set
+
+Bugfixes
+--------
+
+- Apply ``_wrap_native_text`` only for builtin filters specified in STRING_TYPE_FILTERS.
+- Documentation change to the apt module to reference lock files (https://github.com/ansible/ansible/issues/73079).
+- Fix --list-tasks format `role_name : task_name` when task name contains the role name. (https://github.com/ansible/ansible/issues/72505)
+- Fix ansible-galaxy collection list to show collections in site-packages (https://github.com/ansible/ansible/issues/70147)
+- Fix bytestring vs string comparison in module_utils.basic.is_special_selinux_path() so that special-cased filesystems which don't support SELinux context attributes still allow files to be manipulated on them. (https://github.com/ansible/ansible/issues/70244)
+- Fix notifying handlers via `role_name : handler_name` when handler name contains the role name. (https://github.com/ansible/ansible/issues/70582)
+- async - Fix Python 3 interpreter parsing from module by comparing with bytes (https://github.com/ansible/ansible/issues/70690)
+- inventory - pass the vars dictionary to combine_vars instead of an individual key's value (https://github.com/ansible/ansible/issues/72975).
+- paramiko connection plugin - Ensure we only reset the connection when one has been previously established (https://github.com/ansible/ansible/issues/65812)
+- systemd - preserve the full unit name when using a templated service and ``systemd`` failed to parse dbus due to a known bug in ``systemd`` (https://github.com/ansible/ansible/pull/72985)
+- user - do the right thing when ``password_lock=True`` and ``password`` are used together (https://github.com/ansible/ansible/issues/72992)
+
v2.10.4
=======
diff --git a/changelogs/changelog.yaml b/changelogs/changelog.yaml
index 73aa9bb1..55b3d52f 100644
--- a/changelogs/changelog.yaml
+++ b/changelogs/changelog.yaml
@@ -2282,3 +2282,76 @@ releases:
- skip_invalid_coll_name_when_listing.yml
- v2.10.4rc1_summary.yaml
release_date: '2020-12-07'
+ 2.10.5:
+ changes:
+ release_summary: '| Release Date: 2021-01-18
+
+ | `Porting Guide <https://docs.ansible.com/ansible/devel/porting_guides.html>`__
+
+ '
+ codename: When the Levee Breaks
+ fragments:
+ - v2.10.5_summary.yaml
+ release_date: '2021-01-18'
+ 2.10.5rc1:
+ changes:
+ bugfixes:
+ - Apply ``_wrap_native_text`` only for builtin filters specified in STRING_TYPE_FILTERS.
+ - Documentation change to the apt module to reference lock files (https://github.com/ansible/ansible/issues/73079).
+ - 'Fix --list-tasks format `role_name : task_name` when task name contains the
+ role name. (https://github.com/ansible/ansible/issues/72505)'
+ - Fix ansible-galaxy collection list to show collections in site-packages (https://github.com/ansible/ansible/issues/70147)
+ - Fix bytestring vs string comparison in module_utils.basic.is_special_selinux_path()
+ so that special-cased filesystems which don't support SELinux context attributes
+ still allow files to be manipulated on them. (https://github.com/ansible/ansible/issues/70244)
+ - 'Fix notifying handlers via `role_name : handler_name` when handler name contains
+ the role name. (https://github.com/ansible/ansible/issues/70582)'
+ - async - Fix Python 3 interpreter parsing from module by comparing with bytes
+ (https://github.com/ansible/ansible/issues/70690)
+ - inventory - pass the vars dictionary to combine_vars instead of an individual
+ key's value (https://github.com/ansible/ansible/issues/72975).
+ - paramiko connection plugin - Ensure we only reset the connection when one
+ has been previously established (https://github.com/ansible/ansible/issues/65812)
+ - 'systemd - preserve the full unit name when using a templated service and
+ ``systemd`` failed to parse dbus due to a known bug in ``systemd`` (https://github.com/ansible/ansible/pull/72985)
+
+ '
+ - 'user - do the right thing when ``password_lock=True`` and ``password`` are
+ used together (https://github.com/ansible/ansible/issues/72992)
+
+ '
+ minor_changes:
+ - ansible-test - Changed the internal name of the custom plugin used to identify
+ use of unwanted imports and functions.
+ - ansible-test - The ``pylint`` sanity test is now skipped with a warning on
+ Python 3.9 due to unresolved upstream regressions.
+ - ansible-test - The ``pylint`` sanity test is now supported on Python 3.8.
+ - ansible-test - add macOS 11.1 as a remote target (https://github.com/ansible/ansible/pull/72622)
+ - ansible-test - remote macOS instances no longer install ``virtualenv`` during
+ provisioning
+ - ansible-test - virtualenv helper scripts now prefer ``venv`` on Python 3 over
+ ``virtualenv`` if the ``ANSIBLE_TEST_PREFER_VENV`` environment variable is
+ set
+ release_summary: '| Release Date: 2021-01-11
+
+ | `Porting Guide <https://docs.ansible.com/ansible/devel/porting_guides.html>`__
+
+ '
+ codename: When the Levee Breaks
+ fragments:
+ - 65812-paramiko-attribute-error.yml
+ - 70244-selinux-special-fs.yml
+ - 70690-async-interpreter.yml
+ - 72511-always-prepend-role-to-task-name.yml
+ - 72979-fix-inventory-merge-hash-replace.yaml
+ - 72992-user-account-lock-always-changes.yml
+ - 73079-update-documentation-around-apt-lock.yml
+ - ansible-test-pylint-plugin-name.yml
+ - ansible-test-pylint-python-3.8-3.9.yml
+ - ansible-test-venv-virtualenv-fallback.yml
+ - ci-add-macos-11.yml
+ - collection-list-site-packages.yaml
+ - systemd-preserve-full-unit-name.yml
+ - v2.10.5rc1_summary.yaml
+ - wrap_native_text-non-collections-only.yml
+ release_date: '2021-01-11'
diff --git a/docs/docsite/Makefile b/docs/docsite/Makefile
index c3c2d4c7..bca734a5 100644
--- a/docs/docsite/Makefile
+++ b/docs/docsite/Makefile
@@ -94,7 +94,11 @@ clean:
rm -f rst/reference_appendices/playbooks_keywords.rst
rm -f rst/dev_guide/collections_galaxy_meta.rst
rm -f rst/cli/*.rst
- rm -rf rst/collections/*
+ for filename in `ls rst/collections/` ; do \
+ if test x"$$filename" != x'all_plugins.rst' ; then \
+ rm -rf "rst/collections/$$filename"; \
+ fi \
+ done
@echo "Cleaning up legacy generated rst locations"
rm -rf rst/modules
rm -f rst/plugins/*/*.rst
diff --git a/docs/docsite/rst/collections/all_plugins.rst b/docs/docsite/rst/collections/all_plugins.rst
new file mode 100644
index 00000000..35232f7d
--- /dev/null
+++ b/docs/docsite/rst/collections/all_plugins.rst
@@ -0,0 +1,11 @@
+.. _all_modules_and_plugins:
+
+Indexes of all modules and plugins
+----------------------------------
+
+.. toctree::
+ :maxdepth: 1
+ :caption: Plugin indexes
+ :glob:
+
+ index_*
diff --git a/docs/docsite/rst/dev_guide/developing_collections.rst b/docs/docsite/rst/dev_guide/developing_collections.rst
index dd757e55..3aa25502 100644
--- a/docs/docsite/rst/dev_guide/developing_collections.rst
+++ b/docs/docsite/rst/dev_guide/developing_collections.rst
@@ -536,19 +536,30 @@ First, look at `Ansible Collection Checklist <https://github.com/ansible-collect
To migrate content from one collection to another, if the collections are parts of `Ansible distribution <https://github.com/ansible-community/ansible-build-data/blob/main/2.10/ansible.in>`_:
-#. Copy content from the source (old) collection to the target collection.
-#. Deprecate the module/plugin with ``removal_version`` scheduled for the next major version in ``meta/runtime.yml`` of the source collection. The deprecation must be released after the copied content has been included in a release of the target collection.
-#. When the next major release comes:
-
- * remove the module/plugin from the source collection
- * add ``redirect`` to the corresponding entry in ``meta/runtime.yml``
- * remove ``removal_version`` from there
+#. Copy content from the source (old) collection to the target (new) collection.
+#. Deprecate the module/plugin with ``removal_version`` scheduled for the next major version in ``meta/runtime.yml`` of the old collection. The deprecation must be released after the copied content has been included in a release of the new collection.
+#. When the next major release of the old collection is prepared:
+
+ * remove the module/plugin from the old collection
+ * remove the symlink stored in ``plugin/modules`` directory if appropriate (mainly when removing from ``community.general`` and ``community.network``)
+ * remove related unit and integration tests
+ * remove specific module utils
+ * remove specific documentation fragments if there are any in the old collection
+ * add a changelog fragment containing entries for ``removed_features`` and ``breaking_changes``; you can see an example of a changelog fragment in this `pull request <https://github.com/ansible-collections/community.general/pull/1304>`_
+ * change ``meta/runtime.yml`` in the old collection:
+
+ * add ``redirect`` to the corresponding module/plugin's entry
+ * in particular, add ``redirect`` for the removed module utils and documentation fragments if applicable
+ * remove ``removal_version`` from there
+ * remove related entries from ``tests/sanity/ignore.txt`` files if exist
+ * remove changelog fragments for removed content that are not yet part of the changelog (in other words, do not modify `changelogs/changelog.yaml` and do not delete files mentioned in it)
+ * remove requirements that are no longer required in ``tests/unit/requirements.txt``, ``tests/requirements.yml`` and ``galaxy.yml``
According to the above, you need to create at least three PRs as follows:
-#. Create a PR against the target collection to copy the content.
-#. Deprecate the module/plugin in the source collection.
-#. Later create a PR against the source collection to remove the content according to the schedule.
+#. Create a PR against the new collection to copy the content.
+#. Deprecate the module/plugin in the old collection.
+#. Later create a PR against the old collection to remove the content according to the schedule.
Adding the content to the new collection
diff --git a/docs/docsite/rst/galaxy/user_guide.rst b/docs/docsite/rst/galaxy/user_guide.rst
index 85b20c07..bbadfe8c 100644
--- a/docs/docsite/rst/galaxy/user_guide.rst
+++ b/docs/docsite/rst/galaxy/user_guide.rst
@@ -270,8 +270,8 @@ Use the following example as a guide for specifying roles in *requirements.yml*:
# from galaxy
- name: yatesr.timezone
- # from locally cloned git repository (file:// requires full paths)
- - src: file:///home/bennojoy/nginx
+ # from locally cloned git repository (git+file:// requires full paths)
+ - src: git+file:///home/bennojoy/nginx
# from GitHub
- src: https://github.com/bennojoy/nginx
diff --git a/docs/docsite/rst/index.rst b/docs/docsite/rst/index.rst
index 4c99b502..c001115d 100644
--- a/docs/docsite/rst/index.rst
+++ b/docs/docsite/rst/index.rst
@@ -75,6 +75,7 @@ Ansible releases a new major release of Ansible approximately three to four time
:caption: Reference & Appendices
collections/index
+ collections/all_plugins
reference_appendices/playbooks_keywords
reference_appendices/common_return_values
reference_appendices/config
diff --git a/docs/docsite/rst/scenario_guides/guide_docker.rst b/docs/docsite/rst/scenario_guides/guide_docker.rst
index 9c992a17..c3f019bd 100644
--- a/docs/docsite/rst/scenario_guides/guide_docker.rst
+++ b/docs/docsite/rst/scenario_guides/guide_docker.rst
@@ -1,112 +1,62 @@
Docker Guide
============
-Ansible offers the following modules for orchestrating Docker containers:
+The `community.docker collection <https://galaxy.ansible.com/community/docker>`_ offers several modules and plugins for orchestrating Docker containers and Docker Swarm.
- docker_compose
- Use your existing Docker compose files to orchestrate containers on a single Docker daemon or on
- Swarm. Supports compose versions 1 and 2.
+.. contents::
+ :local:
+ :depth: 1
- docker_container
- Manages the container lifecycle by providing the ability to create, update, stop, start and destroy a
- container.
-
- docker_image
- Provides full control over images, including: build, pull, push, tag and remove.
-
- docker_image_info
- Inspects one or more images in the Docker host's image cache, providing the information for making
- decision or assertions in a playbook.
-
- docker_login
- Authenticates with Docker Hub or any Docker registry and updates the Docker Engine config file, which
- in turn provides password-free pushing and pulling of images to and from the registry.
-
- docker (dynamic inventory)
- Dynamically builds an inventory of all the available containers from a set of one or more Docker hosts.
-
-
-Ansible 2.1.0 includes major updates to the Docker modules, marking the start of a project to create a complete and
-integrated set of tools for orchestrating containers. In addition to the above modules, we are also working on the
-following:
-
-Still using Dockerfile to build images? Check out `ansible-bender <https://github.com/ansible-community/ansible-bender>`_,
-and start building images from your Ansible playbooks.
-
-Use `Ansible Operator <https://learn.openshift.com/ansibleop/ansible-operator-overview/>`_
-to launch your docker-compose file on `OpenShift <https://www.okd.io/>`_. Go from an app on your laptop to a fully
-scalable app in the cloud with Kubernetes in just a few moments.
-
-There's more planned. See the latest ideas and thinking at the `Ansible proposal repo <https://github.com/ansible/proposals/tree/master/docker>`_.
Requirements
------------
-Using the docker modules requires having the `Docker SDK for Python <https://docker-py.readthedocs.io/en/stable/>`_
-installed on the host running Ansible. You will need to have >= 1.7.0 installed. For Python 2.7 or
-Python 3, you can install it as follows:
+Most of the modules and plugins in community.docker require the `Docker SDK for Python <https://docker-py.readthedocs.io/en/stable/>`_. The SDK needs to be installed on the machines where the modules and plugins are executed, and for the Python version(s) with which the modules and plugins are executed. You can use the :ref:`community.general.python_requirements_info module <ansible_collections.community.general.python_requirements_info_module>` to make sure that the Docker SDK for Python is installed on the correct machine and for the Python version used by Ansible.
+
+Note that plugins (inventory plugins and connection plugins) are always executed in the context of Ansible itself. If you use a plugin that requires the Docker SDK for Python, you need to install it on the machine running ``ansible`` or ``ansible-playbook`` and for the same Python interpreter used by Ansible. To see which Python is used, run ``ansible --version``.
+
+You can install the Docker SDK for Python for Python 2.7 or Python 3 as follows:
.. code-block:: bash
$ pip install docker
-For Python 2.6, you need a version before 2.0. For these versions, the SDK was called ``docker-py``,
-so you need to install it as follows:
+For Python 2.6, you need a version before 2.0. For these versions, the SDK was called ``docker-py``, so you need to install it as follows:
.. code-block:: bash
- $ pip install 'docker-py>=1.7.0'
+ $ pip install 'docker-py>=1.10.0'
-Please note that only one of ``docker`` and ``docker-py`` must be installed. Installing both will result in
-a broken installation. If this happens, Ansible will detect it and inform you about it::
+Please install only one of ``docker`` or ``docker-py``. Installing both will result in a broken installation. If this happens, Ansible will detect it and inform you about it. If that happens, you must uninstall both and reinstall the correct version.
- Cannot have both the docker-py and docker python modules installed together as they use the same
- namespace and cause a corrupt installation. Please uninstall both packages, and re-install only
- the docker-py or docker python module. It is recommended to install the docker module if no support
- for Python 2.6 is required. Please note that simply uninstalling one of the modules can leave the
- other module in a broken state.
-
-The docker_compose module also requires `docker-compose <https://github.com/docker/compose>`_
-
-.. code-block:: bash
-
- $ pip install 'docker-compose>=1.7.0'
+If in doubt, always install ``docker`` and never ``docker-py``.
Connecting to the Docker API
----------------------------
-You can connect to a local or remote API using parameters passed to each task or by setting environment variables.
-The order of precedence is command line parameters and then environment variables. If neither a command line
-option or an environment variable is found, a default value will be used. The default values are provided under
-`Parameters`_
+You can connect to a local or remote API using parameters passed to each task or by setting environment variables. The order of precedence is command line parameters and then environment variables. If neither a command line option nor an environment variable is found, Ansible uses the default value provided under `Parameters`_.
Parameters
..........
-Control how modules connect to the Docker API by passing the following parameters:
+Most plugins and modules can be configured by the following parameters:
docker_host
- The URL or Unix socket path used to connect to the Docker API. Defaults to ``unix://var/run/docker.sock``.
- To connect to a remote host, provide the TCP connection string. For example: ``tcp://192.0.2.23:2376``. If
- TLS is used to encrypt the connection to the API, then the module will automatically replace 'tcp' in the
- connection URL with 'https'.
+ The URL or Unix socket path used to connect to the Docker API. Defaults to ``unix://var/run/docker.sock``. To connect to a remote host, provide the TCP connection string (for example: ``tcp://192.0.2.23:2376``). If TLS is used to encrypt the connection to the API, then the module will automatically replace 'tcp' in the connection URL with 'https'.
api_version
- The version of the Docker API running on the Docker Host. Defaults to the latest version of the API supported
- by docker-py.
+ The version of the Docker API running on the Docker Host. Defaults to the latest version of the API supported by the Docker SDK for Python installed.
timeout
The maximum amount of time in seconds to wait on a response from the API. Defaults to 60 seconds.
tls
- Secure the connection to the API by using TLS without verifying the authenticity of the Docker host server.
- Defaults to False.
+ Secure the connection to the API by using TLS without verifying the authenticity of the Docker host server. Defaults to ``false``.
- tls_verify
- Secure the connection to the API by using TLS and verifying the authenticity of the Docker host server.
- Default is False.
+ validate_certs
+ Secure the connection to the API by using TLS and verifying the authenticity of the Docker host server. Default is ``false``.
cacert_path
Use a CA certificate when performing server verification by providing the path to a CA certificate file.
@@ -118,19 +68,18 @@ Control how modules connect to the Docker API by passing the following parameter
Path to the client's TLS key file.
tls_hostname
- When verifying the authenticity of the Docker Host server, provide the expected name of the server. Defaults
- to 'localhost'.
+ When verifying the authenticity of the Docker Host server, provide the expected name of the server. Defaults to ``localhost``.
ssl_version
- Provide a valid SSL version number. Default value determined by docker-py, which at the time of this writing
- was 1.0
+ Provide a valid SSL version number. The default value is determined by the Docker SDK for Python.
-Environment Variables
+Environment variables
.....................
-Control how the modules connect to the Docker API by setting the following variables in the environment of the host
-running Ansible:
+You can also control how the plugins and modules connect to the Docker API by setting the following environment variables.
+
+For plugins, they have to be set for the environment Ansible itself runs in. For modules, they have to be set for the environment the modules are executed in. For modules running on remote machines, the environment variables have to be set on that machine for the user used to execute the modules with.
DOCKER_HOST
The URL or Unix socket path used to connect to the Docker API.
@@ -155,176 +104,124 @@ running Ansible:
Secure the connection to the API by using TLS and verify the authenticity of the Docker Host.
-Dynamic Inventory Script
-------------------------
-The inventory script generates dynamic inventory by making API requests to one or more Docker APIs. It's dynamic
-because the inventory is generated at run-time rather than being read from a static file. The script generates the
-inventory by connecting to one or many Docker APIs and inspecting the containers it finds at each API. Which APIs the
-script contacts can be defined using environment variables or a configuration file.
+Plain Docker daemon: images, networks, volumes, and containers
+--------------------------------------------------------------
-Groups
-......
-The script will create the following host groups:
+For working with a plain Docker daemon, that is without Swarm, there are connection plugins, an inventory plugin, and several modules available:
- - container id
- - container name
- - container short id
- - image_name (image_<image name>)
- - docker_host
- - running
- - stopped
+ docker connection plugin
+ The :ref:`community.docker.docker connection plugin <ansible_collections.community.docker.docker_connection>` uses the Docker CLI utility to connect to Docker containers and execute modules in them. It essentially wraps ``docker exec`` and ``docker cp``. This connection plugin is supported by the :ref:`ansible.posix.synchronize module <ansible_collections.ansible.posix.synchronize_module>`.
-Examples
-........
+ docker_api connection plugin
+ The :ref:`community.docker.docker_api connection plugin <ansible_collections.community.docker.docker_api_connection>` talks directly to the Docker daemon to connect to Docker containers and execute modules in them.
-You can run the script interactively from the command line or pass it as the inventory to a playbook. Here are few
-examples to get you started:
+ docker_containers inventory plugin
+ The :ref:`community.docker.docker_containers inventory plugin <ansible_collections.community.docker.docker_containers_inventory>` allows you to dynamically add Docker containers from a Docker Daemon to your Ansible inventory. See :ref:`dynamic_inventory` for details on dynamic inventories.
-.. code-block:: bash
+ The `docker inventory script <https://github.com/ansible-collections/community.general/blob/main/scripts/inventory/docker.py>`_ is deprecated. Please use the inventory plugin instead. The inventory plugin has several compatibility options. If you need to collect Docker containers from multiple Docker daemons, you need to add every Docker daemon as an individual inventory source.
- # Connect to the Docker API on localhost port 4243 and format the JSON output
- DOCKER_HOST=tcp://localhost:4243 ./docker.py --pretty
+ docker_host_info module
+ The :ref:`community.docker.docker_host_info module <ansible_collections.community.docker.docker_host_info_module>` allows you to retrieve information on a Docker daemon, such as all containers, images, volumes, networks and so on.
- # Any container's ssh port exposed on 0.0.0.0 will be mapped to
- # another IP address (where Ansible will attempt to connect via SSH)
- DOCKER_DEFAULT_IP=192.0.2.5 ./docker.py --pretty
+ docker_login module
+ The :ref:`community.docker.docker_login module <ansible_collections.community.docker.docker_login_module>` allows you to log in and out of a remote registry, such as Docker Hub or a private registry. It provides similar functionality to the ``docker login`` and ``docker logout`` CLI commands.
- # Run as input to a playbook:
- ansible-playbook -i ./docker.py docker_inventory_test.yml
+ docker_prune module
+ The :ref:`community.docker.docker_prune module <ansible_collections.community.docker.docker_prune_module>` allows you to prune no longer needed containers, images, volumes and so on. It provides similar functionality to the ``docker prune`` CLI command.
- # Simple playbook to invoke with the above example:
+ docker_image module
+ The :ref:`community.docker.docker_image module <ansible_collections.community.docker.docker_image_module>` provides full control over images, including: build, pull, push, tag and remove.
- - name: Test docker_inventory, this will not connect to any hosts
- hosts: all
- gather_facts: no
- tasks:
- - debug:
- msg: "Container - {{ inventory_hostname }}"
+ docker_image_info module
+ The :ref:`community.docker.docker_image_info module <ansible_collections.community.docker.docker_image_info_module>` allows you to list and inspect images.
-Configuration
-.............
-You can control the behavior of the inventory script by defining environment variables, or
-creating a docker.yml file (sample provided in https://raw.githubusercontent.com/ansible-collections/community.general/main/scripts/inventory/docker.py). The order of precedence is the docker.yml
-file and then environment variables.
+ docker_network module
+ The :ref:`community.docker.docker_network module <ansible_collections.community.docker.docker_network_module>` provides full control over Docker networks.
+ docker_network_info module
+ The :ref:`community.docker.docker_network_info module <ansible_collections.community.docker.docker_network_info_module>` allows you to inspect Docker networks.
-Environment Variables
-;;;;;;;;;;;;;;;;;;;;;;
+ docker_volume_info module
+ The :ref:`community.docker.docker_volume_info module <ansible_collections.community.docker.docker_volume_info_module>` provides full control over Docker volumes.
-To connect to a single Docker API the following variables can be defined in the environment to control the connection
-options. These are the same environment variables used by the Docker modules.
+ docker_volume module
+ The :ref:`community.docker.docker_volume module <ansible_collections.community.docker.docker_volume_module>` allows you to inspect Docker volumes.
- DOCKER_HOST
- The URL or Unix socket path used to connect to the Docker API. Defaults to unix://var/run/docker.sock.
+ docker_container module
+ The :ref:`community.docker.docker_container module <ansible_collections.community.docker.docker_container_module>` manages the container lifecycle by providing the ability to create, update, stop, start and destroy a Docker container.
- DOCKER_API_VERSION:
- The version of the Docker API running on the Docker Host. Defaults to the latest version of the API supported
- by docker-py.
+ docker_container_info module
+ The :ref:`community.docker.docker_container_info module <ansible_collections.community.docker.docker_container_info_module>` allows you to inspect a Docker container.
- DOCKER_TIMEOUT:
- The maximum amount of time in seconds to wait on a response from the API. Defaults to 60 seconds.
- DOCKER_TLS:
- Secure the connection to the API by using TLS without verifying the authenticity of the Docker host server.
- Defaults to False.
+Docker Compose
+--------------
- DOCKER_TLS_VERIFY:
- Secure the connection to the API by using TLS and verifying the authenticity of the Docker host server.
- Default is False
+The :ref:`community.docker.docker_compose module <ansible_collections.community.docker.docker_compose_module>`
+allows you to use your existing Docker compose files to orchestrate containers on a single Docker daemon or on Swarm.
+Supports compose versions 1 and 2.
- DOCKER_TLS_HOSTNAME:
- When verifying the authenticity of the Docker Host server, provide the expected name of the server. Defaults
- to localhost.
+Next to Docker SDK for Python, you need to install `docker-compose <https://github.com/docker/compose>`_ on the remote machines to use the module.
- DOCKER_CERT_PATH:
- Path to the directory containing the client certificate, client key and CA certificate.
- DOCKER_SSL_VERSION:
- Provide a valid SSL version number. Default value determined by docker-py, which at the time of this writing
- was 1.0
+Docker Machine
+--------------
-In addition to the connection variables there are a couple variables used to control the execution and output of the
-script:
+The :ref:`community.docker.docker_machine inventory plugin <ansible_collections.community.docker.docker_machine_inventory>` allows you to dynamically add Docker Machine hosts to your Ansible inventory.
- DOCKER_CONFIG_FILE
- Path to the configuration file. Defaults to ./docker.yml.
- DOCKER_PRIVATE_SSH_PORT:
- The private port (container port) on which SSH is listening for connections. Defaults to 22.
+Docker stack
+------------
+
+The :ref:`community.docker.docker_stack module <ansible_collections.community.docker.docker_stack_module>` module allows you to control Docker stacks. Information on stacks can be retrieved by the :ref:`community.docker.docker_stack_info module <ansible_collections.community.docker.docker_stack_info_module>`, and information on stack tasks can be retrieved by the :ref:`community.docker.docker_stack_task_info module <ansible_collections.community.docker.docker_stack_task_info_module>`.
- DOCKER_DEFAULT_IP:
- The IP address to assign to ansible_host when the container's SSH port is mapped to interface '0.0.0.0'.
+Docker Swarm
+------------
-Configuration File
-;;;;;;;;;;;;;;;;;;
+The community.docker collection provides multiple plugins and modules for managing Docker Swarms.
-Using a configuration file provides a means for defining a set of Docker APIs from which to build an inventory.
+Swarm management
+................
-The default name of the file is derived from the name of the inventory script. By default the script will look for
-basename of the script (in other words, docker) with an extension of '.yml'.
+One inventory plugin and several modules are provided to manage Docker Swarms:
-You can also override the default name of the script by defining DOCKER_CONFIG_FILE in the environment.
+ docker_swarm inventory plugin
+ The :ref:`community.docker.docker_swarm inventory plugin <ansible_collections.community.docker.docker_swarm_inventory>` allows you to dynamically add all Docker Swarm nodes to your Ansible inventory.
-Here's what you can define in docker_inventory.yml:
+ docker_swarm module
+ The :ref:`community.docker.docker_swarm module <ansible_collections.community.docker.docker_swarm_module>` allows you to globally configure Docker Swarm manager nodes to join and leave swarms, and to change the Docker Swarm configuration.
- defaults
- Defines a default connection. Defaults will be taken from this and applied to any values not provided
- for a host defined in the hosts list.
+ docker_swarm_info module
+ The :ref:`community.docker.docker_swarm_info module <ansible_collections.community.docker.docker_swarm_info_module>` allows you to retrieve information on Docker Swarm.
- hosts
- If you wish to get inventory from more than one Docker host, define a hosts list.
+ docker_node module
+ The :ref:`community.docker.docker_node module <ansible_collections.community.docker.docker_node_module>` allows you to manage Docker Swarm nodes.
-For the default host and each host in the hosts list define the following attributes:
+ docker_node_info module
+ The :ref:`community.docker.docker_node_info module <ansible_collections.community.docker.docker_node_info_module>` allows you to retrieve information on Docker Swarm nodes.
-.. code-block:: yaml
+Configuration management
+........................
- host:
- description: The URL or Unix socket path used to connect to the Docker API.
- required: yes
+The community.docker collection offers modules to manage Docker Swarm configurations and secrets:
- tls:
- description: Connect using TLS without verifying the authenticity of the Docker host server.
- default: false
- required: false
+ docker_config module
+ The :ref:`community.docker.docker_config module <ansible_collections.community.docker.docker_config_module>` allows you to create and modify Docker Swarm configs.
- tls_verify:
- description: Connect using TLS without verifying the authenticity of the Docker host server.
- default: false
- required: false
+ docker_secret module
+ The :ref:`community.docker.docker_secret module <ansible_collections.community.docker.docker_secret_module>` allows you to create and modify Docker Swarm secrets.
- cert_path:
- description: Path to the client's TLS certificate file.
- default: null
- required: false
- cacert_path:
- description: Use a CA certificate when performing server verification by providing the path to a CA certificate file.
- default: null
- required: false
+Swarm services
+..............
- key_path:
- description: Path to the client's TLS key file.
- default: null
- required: false
+Docker Swarm services can be created and updated with the :ref:`community.docker.docker_swarm_service module <ansible_collections.community.docker.docker_swarm_service_module>`, and information on them can be queried by the :ref:`community.docker.docker_swarm_service_info module <ansible_collections.community.docker.docker_swarm_service_info_module>`.
- version:
- description: The Docker API version.
- required: false
- default: will be supplied by the docker-py module.
- timeout:
- description: The amount of time in seconds to wait on an API response.
- required: false
- default: 60
+Helpful links
+-------------
- default_ip:
- description: The IP address to assign to ansible_host when the container's SSH port is mapped to interface
- '0.0.0.0'.
- required: false
- default: 127.0.0.1
+Still using Dockerfile to build images? Check out `ansible-bender <https://github.com/ansible-community/ansible-bender>`_, and start building images from your Ansible playbooks.
- private_ssh_port:
- description: The port containers use for SSH
- required: false
- default: 22
+Use `Ansible Operator <https://learn.openshift.com/ansibleop/ansible-operator-overview/>`_ to launch your docker-compose file on `OpenShift <https://www.okd.io/>`_. Go from an app on your laptop to a fully scalable app in the cloud with Kubernetes in just a few moments.
diff --git a/docs/docsite/rst/user_guide/complex_data_manipulation.rst b/docs/docsite/rst/user_guide/complex_data_manipulation.rst
index 5aa230ad..253362b7 100644
--- a/docs/docsite/rst/user_guide/complex_data_manipulation.rst
+++ b/docs/docsite/rst/user_guide/complex_data_manipulation.rst
@@ -47,7 +47,8 @@ There are several ways to do it in Ansible, this is just one example:
tasks:
- name: Show extracted list of keys from a list of dictionaries
- debug: msg="{{ chains | map('extract', chains_config) | map(attribute='configs') | flatten | map(attribute='type') | flatten }}"
+ ansible.builtin.debug:
+ msg: "{{ chains | map('extract', chains_config) | map(attribute='configs') | flatten | map(attribute='type') | flatten }}"
vars:
chains: [1, 2]
chains_config:
@@ -97,7 +98,8 @@ In this case, we want to find the mount point for a given path across our machin
path: /var/lib/cache
tasks:
- name: The mount point for {{path}}, found using the Ansible mount facts, [-1] is the same as the 'last' filter
- debug: msg="{{(ansible_facts.mounts | selectattr('mount', 'in', path) | list | sort(attribute='mount'))[-1]['mount']}}"
+ ansible.builtin.debug:
+ msg: "{{(ansible_facts.mounts | selectattr('mount', 'in', path) | list | sort(attribute='mount'))[-1]['mount']}}"
@@ -110,8 +112,8 @@ The special ``omit`` variable ONLY works with module options, but we can still u
:caption: Inline list filtering when feeding a module option
:emphasize-lines: 3, 7
- - name: enable a list of Windows features, by name
- set_fact:
+ - name: Enable a list of Windows features, by name
+ ansible.builtin.set_fact:
win_feature_list: "{{ namestuff | reject('equalto', omit) | list }}"
vars:
namestuff:
@@ -126,8 +128,8 @@ Another way is to avoid adding elements to the list in the first place, so you c
:caption: Using set_fact in a loop to increment a list conditionally
:emphasize-lines: 3, 4, 6
- - name: build unique list with some items conditionally omittted
- set_fact:
+ - name: Build unique list with some items conditionally omitted
+ ansible.builtin.set_fact:
namestuff: ' {{ (namestuff | default([])) | union([item]) }}'
when: item != omit
loop:
@@ -181,7 +183,7 @@ A bit more complex, using ``set_fact`` and a ``loop`` to create/update a diction
:emphasize-lines: 3, 4
- name: Uses 'combine' to update the dictionary and 'zip' to make pairs of both lists
- set_fact:
+ ansible.builtin.set_fact:
mydict: "{{ mydict | default({}) | combine({item[0]: item[1]}) }}"
loop: "{{ (keys | zip(values)) | list }}"
vars:
@@ -229,7 +231,8 @@ https://www.reddit.com/r/ansible/comments/gj5a93/trying_to_get_uptime_from_secon
.. code-block:: YAML+Jinja
- - debug:
+ - name: Show the uptime in a certain format
+ ansible.builtin.debug:
msg: Timedelta {{ now() - now().fromtimestamp(now(fmt='%s') | int - ansible_uptime_seconds) }}
diff --git a/docs/docsite/rst/user_guide/playbooks_advanced_syntax.rst b/docs/docsite/rst/user_guide/playbooks_advanced_syntax.rst
index 2edf36d3..03d4243f 100644
--- a/docs/docsite/rst/user_guide/playbooks_advanced_syntax.rst
+++ b/docs/docsite/rst/user_guide/playbooks_advanced_syntax.rst
@@ -95,7 +95,7 @@ Now, you can re-use the value of ``app_version`` within the value of ``custom_n
- *my_version
tasks:
- name: Using Anchor value
- debug:
+ ansible.builtin.debug:
msg: My app is called "{{ webapp.custom_name | join('-') }}".
You've anchored the value of ``version`` with the ``&my_version`` anchor, and re-used it with the ``*my_version`` alias. Anchors and aliases let you access nested values inside dictionaries.
diff --git a/docs/docsite/rst/user_guide/vault.rst b/docs/docsite/rst/user_guide/vault.rst
index d84aefec..abb2fadb 100644
--- a/docs/docsite/rst/user_guide/vault.rst
+++ b/docs/docsite/rst/user_guide/vault.rst
@@ -83,14 +83,7 @@ You can memorize your vault password, or manually copy vault passwords from any
Storing passwords in files
^^^^^^^^^^^^^^^^^^^^^^^^^^
-To store a vault password in a file, enter the password as a string on a single line in the file. Make sure the permissions on the file are appropriate. Do not add password files to source control. If you have multiple passwords, you can store them all in a single file, as long as they all have vault IDs. For each password, create a separate line and enter the vault ID, a space, then the password as a string. For example:
-
-.. code-block:: text
-
- dev my_dev_pass
- test my_test_pass
- prod my_prod_pass
-
+To store a vault password in a file, enter the password as a string on a single line in the file. Make sure the permissions on the file are appropriate. Do not add password files to source control.
.. _vault_password_client_scripts:
diff --git a/docs/man/man1/ansible-config.1 b/docs/man/man1/ansible-config.1
index 326196f7..01a7e73f 100644
--- a/docs/man/man1/ansible-config.1
+++ b/docs/man/man1/ansible-config.1
@@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText.
.
-.TH ANSIBLE-CONFIG 1 "" "Ansible 2.10.4" "System administration commands"
+.TH ANSIBLE-CONFIG 1 "" "Ansible 2.10.5" "System administration commands"
.SH NAME
ansible-config \- View ansible configuration.
.
diff --git a/docs/man/man1/ansible-console.1 b/docs/man/man1/ansible-console.1
index 7c752210..f4ddb069 100644
--- a/docs/man/man1/ansible-console.1
+++ b/docs/man/man1/ansible-console.1
@@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText.
.
-.TH ANSIBLE-CONSOLE 1 "" "Ansible 2.10.4" "System administration commands"
+.TH ANSIBLE-CONSOLE 1 "" "Ansible 2.10.5" "System administration commands"
.SH NAME
ansible-console \- REPL console for executing Ansible tasks.
.
diff --git a/docs/man/man1/ansible-doc.1 b/docs/man/man1/ansible-doc.1
index 6dce5337..240beefd 100644
--- a/docs/man/man1/ansible-doc.1
+++ b/docs/man/man1/ansible-doc.1
@@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText.
.
-.TH ANSIBLE-DOC 1 "" "Ansible 2.10.4" "System administration commands"
+.TH ANSIBLE-DOC 1 "" "Ansible 2.10.5" "System administration commands"
.SH NAME
ansible-doc \- plugin documentation tool
.
diff --git a/docs/man/man1/ansible-galaxy.1 b/docs/man/man1/ansible-galaxy.1
index 70a622e8..f678a4aa 100644
--- a/docs/man/man1/ansible-galaxy.1
+++ b/docs/man/man1/ansible-galaxy.1
@@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText.
.
-.TH ANSIBLE-GALAXY 1 "" "Ansible 2.10.4" "System administration commands"
+.TH ANSIBLE-GALAXY 1 "" "Ansible 2.10.5" "System administration commands"
.SH NAME
ansible-galaxy \- Perform various Role and Collection related operations.
.
diff --git a/docs/man/man1/ansible-inventory.1 b/docs/man/man1/ansible-inventory.1
index d934332e..e9b4206b 100644
--- a/docs/man/man1/ansible-inventory.1
+++ b/docs/man/man1/ansible-inventory.1
@@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText.
.
-.TH ANSIBLE-INVENTORY 1 "" "Ansible 2.10.4" "System administration commands"
+.TH ANSIBLE-INVENTORY 1 "" "Ansible 2.10.5" "System administration commands"
.SH NAME
ansible-inventory \- None
.
diff --git a/docs/man/man1/ansible-playbook.1 b/docs/man/man1/ansible-playbook.1
index de60183c..d694c8f4 100644
--- a/docs/man/man1/ansible-playbook.1
+++ b/docs/man/man1/ansible-playbook.1
@@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText.
.
-.TH ANSIBLE-PLAYBOOK 1 "" "Ansible 2.10.4" "System administration commands"
+.TH ANSIBLE-PLAYBOOK 1 "" "Ansible 2.10.5" "System administration commands"
.SH NAME
ansible-playbook \- Runs Ansible playbooks, executing the defined tasks on the targeted hosts.
.
diff --git a/docs/man/man1/ansible-pull.1 b/docs/man/man1/ansible-pull.1
index f90f5484..18ad3be6 100644
--- a/docs/man/man1/ansible-pull.1
+++ b/docs/man/man1/ansible-pull.1
@@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText.
.
-.TH ANSIBLE-PULL 1 "" "Ansible 2.10.4" "System administration commands"
+.TH ANSIBLE-PULL 1 "" "Ansible 2.10.5" "System administration commands"
.SH NAME
ansible-pull \- pulls playbooks from a VCS repo and executes them for the local host
.
diff --git a/docs/man/man1/ansible-vault.1 b/docs/man/man1/ansible-vault.1
index b8b7b6de..042fbc9a 100644
--- a/docs/man/man1/ansible-vault.1
+++ b/docs/man/man1/ansible-vault.1
@@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText.
.
-.TH ANSIBLE-VAULT 1 "" "Ansible 2.10.4" "System administration commands"
+.TH ANSIBLE-VAULT 1 "" "Ansible 2.10.5" "System administration commands"
.SH NAME
ansible-vault \- encryption/decryption utility for Ansible data files
.
diff --git a/docs/man/man1/ansible.1 b/docs/man/man1/ansible.1
index b5efa1ff..61c87271 100644
--- a/docs/man/man1/ansible.1
+++ b/docs/man/man1/ansible.1
@@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText.
.
-.TH ANSIBLE 1 "" "Ansible 2.10.4" "System administration commands"
+.TH ANSIBLE 1 "" "Ansible 2.10.5" "System administration commands"
.SH NAME
ansible \- Define and run a single task 'playbook' against a set of hosts
.
diff --git a/lib/ansible/cli/galaxy.py b/lib/ansible/cli/galaxy.py
index 447fd128..71cdee49 100644
--- a/lib/ansible/cli/galaxy.py
+++ b/lib/ansible/cli/galaxy.py
@@ -43,6 +43,7 @@ from ansible.parsing.dataloader import DataLoader
from ansible.parsing.yaml.loader import AnsibleLoader
from ansible.playbook.role.requirement import RoleRequirement
from ansible.template import Templar
+from ansible.utils.collection_loader import AnsibleCollectionConfig
from ansible.utils.display import Display
from ansible.utils.plugin_docs import get_versioned_doclink
@@ -163,7 +164,8 @@ class GalaxyCLI(CLI):
collections_path = opt_help.argparse.ArgumentParser(add_help=False)
collections_path.add_argument('-p', '--collection-path', dest='collections_path', type=opt_help.unfrack_path(pathsep=True),
- default=C.COLLECTIONS_PATHS, action=opt_help.PrependListAction,
+ default=AnsibleCollectionConfig.collection_paths,
+ action=opt_help.PrependListAction,
help="One or more directories to search for collections in addition "
"to the default COLLECTIONS_PATHS. Separate multiple paths "
"with '{0}'.".format(os.path.pathsep))
@@ -1253,7 +1255,7 @@ class GalaxyCLI(CLI):
collections_search_paths = set(context.CLIARGS['collections_path'])
collection_name = context.CLIARGS['collection']
- default_collections_path = C.config.get_configuration_definition('COLLECTIONS_PATHS').get('default')
+ default_collections_path = AnsibleCollectionConfig.collection_paths
warnings = []
path_found = False
diff --git a/lib/ansible/inventory/group.py b/lib/ansible/inventory/group.py
index 0dd91bb7..e7878d35 100644
--- a/lib/ansible/inventory/group.py
+++ b/lib/ansible/inventory/group.py
@@ -248,7 +248,7 @@ class Group:
self.set_priority(int(value))
else:
if key in self.vars and isinstance(self.vars[key], MutableMapping) and isinstance(value, Mapping):
- self.vars[key] = combine_vars(self.vars[key], value)
+ self.vars = combine_vars(self.vars, {key: value})
else:
self.vars[key] = value
diff --git a/lib/ansible/inventory/host.py b/lib/ansible/inventory/host.py
index 7ad30079..a3e41f53 100644
--- a/lib/ansible/inventory/host.py
+++ b/lib/ansible/inventory/host.py
@@ -143,7 +143,7 @@ class Host:
def set_variable(self, key, value):
if key in self.vars and isinstance(self.vars[key], MutableMapping) and isinstance(value, Mapping):
- self.vars[key] = combine_vars(self.vars[key], value)
+ self.vars = combine_vars(self.vars, {key: value})
else:
self.vars[key] = value
diff --git a/lib/ansible/module_utils/basic.py b/lib/ansible/module_utils/basic.py
index 24d225f5..cd190f50 100644
--- a/lib/ansible/module_utils/basic.py
+++ b/lib/ansible/module_utils/basic.py
@@ -978,11 +978,12 @@ class AnsibleModule(object):
f.close()
except Exception:
return (False, None)
+
path_mount_point = self.find_mount_point(path)
+
for line in mount_data:
(device, mount_point, fstype, options, rest) = line.split(' ', 4)
-
- if path_mount_point == mount_point:
+ if to_bytes(path_mount_point) == to_bytes(mount_point):
for fs in self._selinux_special_fs:
if fs in fstype:
special_context = self.selinux_context(path_mount_point)
diff --git a/lib/ansible/modules/apt.py b/lib/ansible/modules/apt.py
index 7e9fed6e..144a27d2 100644
--- a/lib/ansible/modules/apt.py
+++ b/lib/ansible/modules/apt.py
@@ -245,6 +245,17 @@ EXAMPLES = '''
apt:
autoremove: yes
+# Sometimes apt tasks fail because apt is locked by an autoupdate or by a race condition on a thread.
+# To check for a lock file before executing, and keep trying until the lock file is released:
+- name: Install packages only when the apt process is not locked
+ apt:
+ name: foo
+ state: present
+ register: apt_action
+ retries: 100
+ until: apt_action is success or ('Failed to lock apt for exclusive operation' not in apt_action.msg and '/var/lib/dpkg/lock' not in apt_action.msg)
+
+
'''
RETURN = '''
diff --git a/lib/ansible/modules/apt_key.py b/lib/ansible/modules/apt_key.py
index d8bb6e15..10570813 100644
--- a/lib/ansible/modules/apt_key.py
+++ b/lib/ansible/modules/apt_key.py
@@ -25,7 +25,8 @@ notes:
- "Use full fingerprint (40 characters) key ids to avoid key collisions.
To generate a full-fingerprint imported key: C(apt-key adv --list-public-keys --with-fingerprint --with-colons)."
- If you specify both the key id and the URL with C(state=present), the task can verify or add the key as needed.
- - Adding a new key requires an apt cache update (e.g. using the apt module's update_cache option)
+ - Adding a new key requires an apt cache update (e.g. using the M(ansible.builtin.apt) module's update_cache option).
+ - Supports C(check_mode).
requirements:
- gpg
options:
@@ -43,7 +44,7 @@ options:
- The path to a keyfile on the remote server to add to the keyring.
keyring:
description:
- - The full path to specific keyring file in /etc/apt/trusted.gpg.d/
+ - The full path to specific keyring file in C(/etc/apt/trusted.gpg.d/).
version_added: "1.3"
url:
description:
@@ -67,45 +68,46 @@ options:
EXAMPLES = '''
- name: Add an apt key by id from a keyserver
- apt_key:
+ ansible.builtin.apt_key:
keyserver: keyserver.ubuntu.com
id: 36A1D7869245C8950F966E92D8576A8BA88D21E9
- name: Add an Apt signing key, uses whichever key is at the URL
- apt_key:
+ ansible.builtin.apt_key:
url: https://ftp-master.debian.org/keys/archive-key-6.0.asc
state: present
- name: Add an Apt signing key, will not download if present
- apt_key:
+ ansible.builtin.apt_key:
id: 9FED2BCBDCD29CDF762678CBAED4B06F473041FA
url: https://ftp-master.debian.org/keys/archive-key-6.0.asc
state: present
- name: Remove a Apt specific signing key, leading 0x is valid
- apt_key:
+ ansible.builtin.apt_key:
id: 0x9FED2BCBDCD29CDF762678CBAED4B06F473041FA
state: absent
# Use armored file since utf-8 string is expected. Must be of "PGP PUBLIC KEY BLOCK" type.
-- name: Add a key from a file on the Ansible server.
- apt_key:
+- name: Add a key from a file on the Ansible server
+ ansible.builtin.apt_key:
data: "{{ lookup('file', 'apt.asc') }}"
state: present
- name: Add an Apt signing key to a specific keyring file
- apt_key:
+ ansible.builtin.apt_key:
id: 9FED2BCBDCD29CDF762678CBAED4B06F473041FA
url: https://ftp-master.debian.org/keys/archive-key-6.0.asc
keyring: /etc/apt/trusted.gpg.d/debian.gpg
- name: Add Apt signing key on remote server to keyring
- apt_key:
+ ansible.builtin.apt_key:
id: 9FED2BCBDCD29CDF762678CBAED4B06F473041FA
file: /tmp/apt.gpg
state: present
'''
+RETURN = '''#'''
# FIXME: standardize into module_common
from traceback import format_exc
diff --git a/lib/ansible/modules/apt_repository.py b/lib/ansible/modules/apt_repository.py
index 834bdec1..de39424e 100644
--- a/lib/ansible/modules/apt_repository.py
+++ b/lib/ansible/modules/apt_repository.py
@@ -20,6 +20,7 @@ description:
notes:
- This module works on Debian, Ubuntu and their derivatives.
- This module supports Debian Squeeze (version 6) as well as its successors.
+ - Supports C(check_mode).
options:
repo:
description:
@@ -68,7 +69,8 @@ options:
codename:
description:
- Override the distribution codename to use for PPA repositories.
- Should usually only be set when working with a PPA on a non-Ubuntu target (e.g. Debian or Mint)
+ Should usually only be set when working with a PPA on
+ a non-Ubuntu target (for example, Debian or Mint).
version_added: '2.3'
author:
- Alexander Saltanov (@sashka)
@@ -80,36 +82,38 @@ requirements:
EXAMPLES = '''
- name: Add specified repository into sources list
- apt_repository:
+ ansible.builtin.apt_repository:
repo: deb http://archive.canonical.com/ubuntu hardy partner
state: present
- name: Add specified repository into sources list using specified filename
- apt_repository:
+ ansible.builtin.apt_repository:
repo: deb http://dl.google.com/linux/chrome/deb/ stable main
state: present
filename: google-chrome
- name: Add source repository into sources list
- apt_repository:
+ ansible.builtin.apt_repository:
repo: deb-src http://archive.canonical.com/ubuntu hardy partner
state: present
- name: Remove specified repository from sources list
- apt_repository:
+ ansible.builtin.apt_repository:
repo: deb http://archive.canonical.com/ubuntu hardy partner
state: absent
- name: Add nginx stable repository from PPA and install its signing key on Ubuntu target
- apt_repository:
+ ansible.builtin.apt_repository:
repo: ppa:nginx/stable
- name: Add nginx stable repository from PPA and install its signing key on Debian target
- apt_repository:
+ ansible.builtin.apt_repository:
repo: 'ppa:nginx/stable'
codename: trusty
'''
+RETURN = '''#'''
+
import glob
import json
import os
diff --git a/lib/ansible/modules/assemble.py b/lib/ansible/modules/assemble.py
index 81814eda..1e5fa64b 100644
--- a/lib/ansible/modules/assemble.py
+++ b/lib/ansible/modules/assemble.py
@@ -87,23 +87,25 @@ extends_documentation_fragment:
EXAMPLES = r'''
- name: Assemble from fragments from a directory
- assemble:
+ ansible.builtin.assemble:
src: /etc/someapp/fragments
dest: /etc/someapp/someapp.conf
-- name: Inserted provided delimiter in between each fragment
- assemble:
+- name: Insert the provided delimiter between fragments
+ ansible.builtin.assemble:
src: /etc/someapp/fragments
dest: /etc/someapp/someapp.conf
delimiter: '### START FRAGMENT ###'
- name: Assemble a new "sshd_config" file into place, after passing validation with sshd
- assemble:
+ ansible.builtin.assemble:
src: /etc/ssh/conf.d/
dest: /etc/ssh/sshd_config
validate: /usr/sbin/sshd -t -f %s
'''
+RETURN = r'''#'''
+
import codecs
import os
import re
diff --git a/lib/ansible/modules/async_wrapper.py b/lib/ansible/modules/async_wrapper.py
index 640e74cf..5379726a 100644
--- a/lib/ansible/modules/async_wrapper.py
+++ b/lib/ansible/modules/async_wrapper.py
@@ -21,7 +21,7 @@ import time
import syslog
import multiprocessing
-from ansible.module_utils._text import to_text
+from ansible.module_utils._text import to_text, to_bytes
PY3 = sys.version_info[0] == 3
@@ -114,14 +114,11 @@ def _filter_non_json_lines(data):
def _get_interpreter(module_path):
- module_fd = open(module_path, 'rb')
- try:
+ with open(module_path, 'rb') as module_fd:
head = module_fd.read(1024)
- if head[0:2] != '#!':
+ if head[0:2] != b'#!':
return None
- return head[2:head.index('\n')].strip().split(' ')
- finally:
- module_fd.close()
+ return head[2:head.index(b'\n')].strip().split(b' ')
def _make_temp_dir(path):
@@ -152,7 +149,7 @@ def _run_module(wrapped_cmd, jid, job_path):
filtered_outdata = ''
stderr = ''
try:
- cmd = shlex.split(wrapped_cmd)
+ cmd = [to_bytes(c, errors='surrogate_or_strict') for c in shlex.split(wrapped_cmd)]
# call the module interpreter directly (for non-binary modules)
# this permits use of a script for an interpreter on non-Linux platforms
interpreter = _get_interpreter(cmd[0])
diff --git a/lib/ansible/modules/copy.py b/lib/ansible/modules/copy.py
index 0dddb3ff..3ec4e446 100644
--- a/lib/ansible/modules/copy.py
+++ b/lib/ansible/modules/copy.py
@@ -118,6 +118,7 @@ extends_documentation_fragment:
- validate
notes:
- The M(ansible.builtin.copy) module recursively copy facility does not scale to lots (>hundreds) of files.
+- Supports C(check_mode).
seealso:
- module: ansible.builtin.assemble
- module: ansible.builtin.fetch
@@ -132,7 +133,7 @@ author:
EXAMPLES = r'''
- name: Copy file with owner and permissions
- copy:
+ ansible.builtin.copy:
src: /srv/myfiles/foo.conf
dest: /etc/foo.conf
owner: foo
@@ -140,7 +141,7 @@ EXAMPLES = r'''
mode: '0644'
- name: Copy file with owner and permission, using symbolic representation
- copy:
+ ansible.builtin.copy:
src: /srv/myfiles/foo.conf
dest: /etc/foo.conf
owner: foo
@@ -148,7 +149,7 @@ EXAMPLES = r'''
mode: u=rw,g=r,o=r
- name: Another symbolic mode example, adding some permissions and removing others
- copy:
+ ansible.builtin.copy:
src: /srv/myfiles/foo.conf
dest: /etc/foo.conf
owner: foo
@@ -156,7 +157,7 @@ EXAMPLES = r'''
mode: u+rw,g-wx,o-rwx
- name: Copy a new "ntp.conf" file into place, backing up the original if it differs from the copied version
- copy:
+ ansible.builtin.copy:
src: /mine/ntp.conf
dest: /etc/ntp.conf
owner: root
@@ -165,31 +166,31 @@ EXAMPLES = r'''
backup: yes
- name: Copy a new "sudoers" file into place, after passing validation with visudo
- copy:
+ ansible.builtin.copy:
src: /mine/sudoers
dest: /etc/sudoers
validate: /usr/sbin/visudo -csf %s
- name: Copy a "sudoers" file on the remote machine for editing
- copy:
+ ansible.builtin.copy:
src: /etc/sudoers
dest: /etc/sudoers.edit
remote_src: yes
validate: /usr/sbin/visudo -csf %s
- name: Copy using inline content
- copy:
+ ansible.builtin.copy:
content: '# This file was moved to /etc/other.conf'
dest: /etc/mine.conf
- name: If follow=yes, /path/to/file will be overwritten by contents of foo.conf
- copy:
+ ansible.builtin.copy:
src: /etc/foo.conf
dest: /path/to/link # link to /path/to/file
follow: yes
- name: If follow=no, /path/to/link will become a file and be overwritten by contents of foo.conf
- copy:
+ ansible.builtin.copy:
src: /etc/foo.conf
dest: /path/to/link # link to /path/to/file
follow: no
@@ -197,62 +198,62 @@ EXAMPLES = r'''
RETURN = r'''
dest:
- description: Destination file/path
+ description: Destination file/path.
returned: success
type: str
sample: /path/to/file.txt
src:
- description: Source file used for the copy on the target machine
+ description: Source file used for the copy on the target machine.
returned: changed
type: str
sample: /home/httpd/.ansible/tmp/ansible-tmp-1423796390.97-147729857856000/source
md5sum:
- description: MD5 checksum of the file after running copy
+ description: MD5 checksum of the file after running copy.
returned: when supported
type: str
sample: 2a5aeecc61dc98c4d780b14b330e3282
checksum:
- description: SHA1 checksum of the file after running copy
+ description: SHA1 checksum of the file after running copy.
returned: success
type: str
sample: 6e642bb8dd5c2e027bf21dd923337cbb4214f827
backup_file:
- description: Name of backup file created
+ description: Name of backup file created.
returned: changed and if backup=yes
type: str
sample: /path/to/file.txt.2015-02-12@22:09~
gid:
- description: Group id of the file, after execution
+ description: Group id of the file, after execution.
returned: success
type: int
sample: 100
group:
- description: Group of the file, after execution
+ description: Group of the file, after execution.
returned: success
type: str
sample: httpd
owner:
- description: Owner of the file, after execution
+ description: Owner of the file, after execution.
returned: success
type: str
sample: httpd
uid:
- description: Owner id of the file, after execution
+ description: Owner id of the file, after execution.
returned: success
type: int
sample: 100
mode:
- description: Permissions of the target, after execution
+ description: Permissions of the target, after execution.
returned: success
type: str
sample: 0644
size:
- description: Size of the target, after execution
+ description: Size of the target, after execution.
returned: success
type: int
sample: 1220
state:
- description: State of the target, after execution
+ description: State of the target, after execution.
returned: success
type: str
sample: file
diff --git a/lib/ansible/modules/cron.py b/lib/ansible/modules/cron.py
index 93277a49..2424f5c0 100644
--- a/lib/ansible/modules/cron.py
+++ b/lib/ansible/modules/cron.py
@@ -33,8 +33,8 @@ options:
name:
description:
- Description of a crontab entry or, if env is set, the name of environment variable.
- - Required if C(state=absent).
- - Note that if name is not set and C(state=present), then a
+ - Required if I(state=absent).
+ - Note that if name is not set and I(state=present), then a
new crontab entry will always be created, regardless of existing ones.
- This parameter will always be required in future releases.
type: str
@@ -47,7 +47,7 @@ options:
description:
- The command to execute or, if env is set, the value of environment variable.
- The command should not contain line breaks.
- - Required if C(state=present).
+ - Required if I(state=present).
type: str
aliases: [ value ]
state:
@@ -60,10 +60,10 @@ options:
description:
- If specified, uses this file instead of an individual user's crontab.
- If this is a relative path, it is interpreted with respect to I(/etc/cron.d).
- - If it is absolute, it will typically be I(/etc/crontab).
+ - If it is absolute, it will typically be C(/etc/crontab).
- Many linux distros expect (and some require) the filename portion to consist solely
of upper- and lower-case letters, digits, underscores, and hyphens.
- - To use the C(cron_file) parameter you must specify the C(user) as well.
+ - To use the I(cron_file) parameter you must specify the I(user) as well.
type: str
backup:
description:
@@ -73,34 +73,34 @@ options:
default: no
minute:
description:
- - Minute when the job should run ( 0-59, *, */2, etc )
+ - Minute when the job should run (C(0-59), C(*), C(*/2), and so on).
type: str
default: "*"
hour:
description:
- - Hour when the job should run ( 0-23, *, */2, etc )
+ - Hour when the job should run (C(0-23), C(*), C(*/2), and so on).
type: str
default: "*"
day:
description:
- - Day of the month the job should run ( 1-31, *, */2, etc )
+ - Day of the month the job should run (C(1-31), C(*), C(*/2), and so on).
type: str
default: "*"
aliases: [ dom ]
month:
description:
- - Month of the year the job should run ( 1-12, *, */2, etc )
+ - Month of the year the job should run (C(1-12), C(*), C(*/2), and so on).
type: str
default: "*"
weekday:
description:
- - Day of the week that the job should run ( 0-6 for Sunday-Saturday, *, etc )
+ - Day of the week that the job should run (C(0-6) for Sunday-Saturday, C(*), and so on).
type: str
default: "*"
aliases: [ dow ]
reboot:
description:
- - If the job should be run at reboot. This option is deprecated. Users should use special_time.
+ - If the job should be run at reboot. This option is deprecated. Users should use I(special_time).
version_added: "1.0"
type: bool
default: no
@@ -113,7 +113,7 @@ options:
disabled:
description:
- If the job should be disabled (commented out) in the crontab.
- - Only has effect if C(state=present).
+ - Only has effect if I(state=present).
type: bool
default: no
version_added: "2.0"
@@ -121,66 +121,68 @@ options:
description:
- If set, manages a crontab's environment variable.
- New variables are added on top of crontab.
- - C(name) and C(value) parameters are the name and the value of environment variable.
+ - I(name) and I(value) parameters are the name and the value of environment variable.
type: bool
default: false
version_added: "2.1"
insertafter:
description:
- - Used with C(state=present) and C(env).
+ - Used with I(state=present) and I(env).
- If specified, the environment variable will be inserted after the declaration of specified environment variable.
type: str
version_added: "2.1"
insertbefore:
description:
- - Used with C(state=present) and C(env).
+ - Used with I(state=present) and I(env).
- If specified, the environment variable will be inserted before the declaration of specified environment variable.
type: str
version_added: "2.1"
requirements:
- cron (or cronie on CentOS)
author:
- - Dane Summers (@dsummersl)
- - Mike Grozak (@rhaido)
- - Patrick Callahan (@dirtyharrycallahan)
- - Evan Kaufman (@EvanK)
- - Luca Berruti (@lberruti)
+ - Dane Summers (@dsummersl)
+ - Mike Grozak (@rhaido)
+ - Patrick Callahan (@dirtyharrycallahan)
+ - Evan Kaufman (@EvanK)
+ - Luca Berruti (@lberruti)
+notes:
+ - Supports C(check_mode).
'''
EXAMPLES = r'''
- name: Ensure a job that runs at 2 and 5 exists. Creates an entry like "0 5,2 * * ls -alh > /dev/null"
- cron:
+ ansible.builtin.cron:
name: "check dirs"
minute: "0"
hour: "5,2"
job: "ls -alh > /dev/null"
- name: 'Ensure an old job is no longer present. Removes any job that is prefixed by "#Ansible: an old job" from the crontab'
- cron:
+ ansible.builtin.cron:
name: "an old job"
state: absent
- name: Creates an entry like "@reboot /some/job.sh"
- cron:
+ ansible.builtin.cron:
name: "a job for reboot"
special_time: reboot
job: "/some/job.sh"
- name: Creates an entry like "PATH=/opt/bin" on top of crontab
- cron:
+ ansible.builtin.cron:
name: PATH
env: yes
job: /opt/bin
- name: Creates an entry like "APP_HOME=/srv/app" and insert it after PATH declaration
- cron:
+ ansible.builtin.cron:
name: APP_HOME
env: yes
job: /srv/app
insertafter: PATH
- name: Creates a cron file under /etc/cron.d
- cron:
+ ansible.builtin.cron:
name: yum autoupdate
weekday: "2"
minute: "0"
@@ -190,18 +192,20 @@ EXAMPLES = r'''
cron_file: ansible_yum-autoupdate
- name: Removes a cron file from under /etc/cron.d
- cron:
+ ansible.builtin.cron:
name: "yum autoupdate"
cron_file: ansible_yum-autoupdate
state: absent
- name: Removes "APP_HOME" environment variable from crontab
- cron:
+ ansible.builtin.cron:
name: APP_HOME
env: yes
state: absent
'''
+RETURN = r'''#'''
+
import os
import platform
import pwd
diff --git a/lib/ansible/modules/debconf.py b/lib/ansible/modules/debconf.py
index 8dbb30d3..dd6cd635 100644
--- a/lib/ansible/modules/debconf.py
+++ b/lib/ansible/modules/debconf.py
@@ -23,6 +23,7 @@ notes:
installed to see questions/settings available.
- Some distros will always record tasks involving the setting of passwords as changed. This is due to debconf-get-selections masking passwords.
- It is highly recommended to add I(no_log=True) to task while handling sensitive information using this module.
+ - Supports C(check_mode).
requirements:
- debconf
- debconf-utils
@@ -61,32 +62,32 @@ author:
EXAMPLES = r'''
- name: Set default locale to fr_FR.UTF-8
- debconf:
+ ansible.builtin.debconf:
name: locales
question: locales/default_environment_locale
value: fr_FR.UTF-8
vtype: select
- name: Set to generate locales
- debconf:
+ ansible.builtin.debconf:
name: locales
question: locales/locales_to_be_generated
value: en_US.UTF-8 UTF-8, fr_FR.UTF-8 UTF-8
vtype: multiselect
- name: Accept oracle license
- debconf:
+ ansible.builtin.debconf:
name: oracle-java7-installer
question: shared/accepted-oracle-license-v1-1
value: 'true'
vtype: select
- name: Specifying package you can register/return the list of questions and current values
- debconf:
+ ansible.builtin.debconf:
name: tzdata
- name: Pre-configure tripwire site passphrase
- debconf:
+ ansible.builtin.debconf:
name: tripwire
question: tripwire/site-passphrase
value: "{{ site_passphrase }}"
@@ -94,6 +95,8 @@ EXAMPLES = r'''
no_log: True
'''
+RETURN = r'''#'''
+
from ansible.module_utils._text import to_text
from ansible.module_utils.basic import AnsibleModule
diff --git a/lib/ansible/modules/debug.py b/lib/ansible/modules/debug.py
index bd8eb7e2..86c4b951 100644
--- a/lib/ansible/modules/debug.py
+++ b/lib/ansible/modules/debug.py
@@ -49,29 +49,27 @@ author:
'''
EXAMPLES = r'''
-# Example that prints the loopback address and gateway for each host
-- debug:
- msg: System {{ inventory_hostname }} has uuid {{ ansible_product_uuid }}
-
-- debug:
+- name: Print the gateway for each host when defined
+ ansible.builtin.debug:
msg: System {{ inventory_hostname }} has gateway {{ ansible_default_ipv4.gateway }}
when: ansible_default_ipv4.gateway is defined
-# Example that prints return information from the previous task
-- shell: /usr/bin/uptime
+- name: Get uptime information
+ ansible.builtin.shell: /usr/bin/uptime
register: result
-- debug:
+- name: Print return information from the previous task
+ ansible.builtin.debug:
var: result
verbosity: 2
- name: Display all variables/facts known for a host
- debug:
+ ansible.builtin.debug:
var: hostvars[inventory_hostname]
verbosity: 4
-# Example that prints two lines of messages, but only if there is an environment value set
-- debug:
+- name: Prints two lines of messages, but only if there is an environment value set
+ ansible.builtin.debug:
msg:
- "Provisioning based on YOUR_KEY which is: {{ lookup('env', 'YOUR_KEY') }}"
- "These servers were built using the password of '{{ password_used }}'. Please retain this for later use."
diff --git a/lib/ansible/modules/fetch.py b/lib/ansible/modules/fetch.py
index d2f886dc..0c25a202 100644
--- a/lib/ansible/modules/fetch.py
+++ b/lib/ansible/modules/fetch.py
@@ -71,6 +71,7 @@ notes:
also explicitly set C(fail_on_missing) to C(no) to get the
non-failing behaviour.
- This module is also supported for Windows targets.
+- Supports C(check_mode).
seealso:
- module: ansible.builtin.copy
- module: ansible.builtin.slurp
@@ -81,24 +82,24 @@ author:
EXAMPLES = r'''
- name: Store file into /tmp/fetched/host.example.com/tmp/somefile
- fetch:
+ ansible.builtin.fetch:
src: /tmp/somefile
dest: /tmp/fetched
- name: Specifying a path directly
- fetch:
+ ansible.builtin.fetch:
src: /tmp/somefile
dest: /tmp/prefix-{{ inventory_hostname }}
flat: yes
- name: Specifying a destination path
- fetch:
+ ansible.builtin.fetch:
src: /tmp/uniquefile
dest: /tmp/special/
flat: yes
- name: Storing in a path relative to the playbook
- fetch:
+ ansible.builtin.fetch:
src: /tmp/uniquefile
dest: special/prefix-{{ inventory_hostname }}
flat: yes
diff --git a/lib/ansible/modules/file.py b/lib/ansible/modules/file.py
index 76a3e130..f55f6a37 100644
--- a/lib/ansible/modules/file.py
+++ b/lib/ansible/modules/file.py
@@ -111,6 +111,8 @@ seealso:
- module: ansible.builtin.stat
- module: ansible.builtin.template
- module: ansible.windows.win_file
+notes:
+- Supports C(check_mode).
author:
- Ansible Core Team
- Michael DeHaan
@@ -118,21 +120,21 @@ author:
EXAMPLES = r'''
- name: Change file ownership, group and permissions
- file:
+ ansible.builtin.file:
path: /etc/foo.conf
owner: foo
group: foo
mode: '0644'
- name: Give insecure permissions to an existing file
- file:
+ ansible.builtin.file:
path: /work
owner: root
group: root
mode: '1777'
- name: Create a symbolic link
- file:
+ ansible.builtin.file:
src: /file/to/link/to
dest: /path/to/symlink
owner: foo
@@ -140,7 +142,7 @@ EXAMPLES = r'''
state: link
- name: Create two hard links
- file:
+ ansible.builtin.file:
src: '/tmp/{{ item.src }}'
dest: '{{ item.dest }}'
state: hard
@@ -149,19 +151,19 @@ EXAMPLES = r'''
- { src: z, dest: k }
- name: Touch a file, using symbolic modes to set the permissions (equivalent to 0644)
- file:
+ ansible.builtin.file:
path: /etc/foo.conf
state: touch
mode: u=rw,g=r,o=r
- name: Touch the same file, but add/remove some permissions
- file:
+ ansible.builtin.file:
path: /etc/foo.conf
state: touch
mode: u+rw,g-wx,o-rwx
-- name: Touch again the same file, but dont change times this makes the task idempotent
- file:
+- name: Touch again the same file, but do not change times this makes the task idempotent
+ ansible.builtin.file:
path: /etc/foo.conf
state: touch
mode: u+rw,g-wx,o-rwx
@@ -169,26 +171,26 @@ EXAMPLES = r'''
access_time: preserve
- name: Create a directory if it does not exist
- file:
+ ansible.builtin.file:
path: /etc/some_directory
state: directory
mode: '0755'
- name: Update modification and access time of given file
- file:
+ ansible.builtin.file:
path: /etc/some_file
state: file
modification_time: now
access_time: now
- name: Set access time based on seconds from epoch value
- file:
+ ansible.builtin.file:
path: /etc/another_file
state: file
access_time: '{{ "%Y%m%d%H%M.%S" | strftime(stat_var.stat.atime) }}'
- name: Recursively change ownership of a directory
- file:
+ ansible.builtin.file:
path: /etc/foo
state: directory
recurse: yes
@@ -196,24 +198,24 @@ EXAMPLES = r'''
group: foo
- name: Remove file (delete file)
- file:
+ ansible.builtin.file:
path: /etc/foo.txt
state: absent
- name: Recursively remove directory
- file:
+ ansible.builtin.file:
path: /etc/foo
state: absent
'''
RETURN = r'''
dest:
- description: Destination file/path, equal to the value passed to I(path)
+ description: Destination file/path, equal to the value passed to I(path).
returned: state=touch, state=hard, state=link
type: str
sample: /path/to/file.txt
path:
- description: Destination file/path, equal to the value passed to I(path)
+ description: Destination file/path, equal to the value passed to I(path).
returned: state=absent, state=directory, state=file
type: str
sample: /path/to/file.txt
diff --git a/lib/ansible/modules/group.py b/lib/ansible/modules/group.py
index 5c25d86f..1deb967a 100644
--- a/lib/ansible/modules/group.py
+++ b/lib/ansible/modules/group.py
@@ -62,20 +62,21 @@ seealso:
- module: ansible.windows.win_group
author:
- Stephen Fromm (@sfromm)
+notes:
+- Supports C(check_mode).
'''
EXAMPLES = '''
- name: Ensure group "somegroup" exists
- group:
+ ansible.builtin.group:
name: somegroup
state: present
- name: Ensure group "docker" exists with correct gid
- group:
+ ansible.builtin.group:
name: docker
state: present
gid: 1750
-
'''
RETURN = r'''
@@ -85,17 +86,17 @@ gid:
type: int
sample: 1001
name:
- description: Group name
+ description: Group name.
returned: always
type: str
sample: users
state:
- description: Whether the group is present or not
+ description: Whether the group is present or not.
returned: always
type: str
sample: 'absent'
system:
- description: Whether the group is a system group or not
+ description: Whether the group is a system group or not.
returned: When C(state) is 'present'
type: bool
sample: False
diff --git a/lib/ansible/modules/iptables.py b/lib/ansible/modules/iptables.py
index efe31c60..994f6404 100644
--- a/lib/ansible/modules/iptables.py
+++ b/lib/ansible/modules/iptables.py
@@ -265,8 +265,8 @@ options:
type: str
ctstate:
description:
- - C(ctstate) is a list of the connection states to match in the conntrack module.
- - Possible states are C(INVALID), C(NEW), C(ESTABLISHED), C(RELATED), C(UNTRACKED), C(SNAT), C(DNAT)
+ - A list of the connection states to match in the conntrack module.
+ - Possible values are C(INVALID), C(NEW), C(ESTABLISHED), C(RELATED), C(UNTRACKED), C(SNAT), C(DNAT).
type: list
default: []
src_range:
@@ -306,7 +306,7 @@ options:
reject_with:
description:
- 'Specifies the error packet type to return while rejecting. It implies
- "jump: REJECT"'
+ "jump: REJECT".'
type: str
version_added: "2.1"
icmp_type:
@@ -343,14 +343,14 @@ options:
EXAMPLES = r'''
- name: Block specific IP
- iptables:
+ ansible.builtin.iptables:
chain: INPUT
source: 8.8.8.8
jump: DROP
become: yes
- name: Forward port 80 to 8600
- iptables:
+ ansible.builtin.iptables:
table: nat
chain: PREROUTING
in_interface: eth0
@@ -363,14 +363,14 @@ EXAMPLES = r'''
become: yes
- name: Allow related and established connections
- iptables:
+ ansible.builtin.iptables:
chain: INPUT
ctstate: ESTABLISHED,RELATED
jump: ACCEPT
become: yes
- name: Allow new incoming SYN packets on TCP port 22 (SSH)
- iptables:
+ ansible.builtin.iptables:
chain: INPUT
protocol: tcp
destination_port: 22
@@ -380,14 +380,14 @@ EXAMPLES = r'''
comment: Accept new SSH connections.
- name: Match on IP ranges
- iptables:
+ ansible.builtin.iptables:
chain: FORWARD
src_range: 192.168.1.100-192.168.1.199
dst_range: 10.0.0.1-10.0.0.50
jump: ACCEPT
- name: Tag all outbound tcp packets with DSCP mark 8
- iptables:
+ ansible.builtin.iptables:
chain: OUTPUT
jump: DSCP
table: mangle
@@ -395,7 +395,7 @@ EXAMPLES = r'''
protocol: tcp
- name: Tag all outbound tcp packets with DSCP DiffServ class CS1
- iptables:
+ ansible.builtin.iptables:
chain: OUTPUT
jump: DSCP
table: mangle
@@ -403,7 +403,7 @@ EXAMPLES = r'''
protocol: tcp
- name: Insert a rule on line 5
- iptables:
+ ansible.builtin.iptables:
chain: INPUT
protocol: tcp
destination_port: 8080
@@ -412,19 +412,19 @@ EXAMPLES = r'''
rule_num: 5
- name: Set the policy for the INPUT chain to DROP
- iptables:
+ ansible.builtin.iptables:
chain: INPUT
policy: DROP
- name: Reject tcp with tcp-reset
- iptables:
+ ansible.builtin.iptables:
chain: INPUT
protocol: tcp
reject_with: tcp-reset
ip_version: ipv4
- name: Set tcp flags
- iptables:
+ ansible.builtin.iptables:
chain: OUTPUT
jump: DROP
protocol: tcp
@@ -437,20 +437,20 @@ EXAMPLES = r'''
- FIN
- name: Iptables flush filter
- iptables:
+ ansible.builtin.iptables:
chain: "{{ item }}"
flush: yes
with_items: [ 'INPUT', 'FORWARD', 'OUTPUT' ]
- name: Iptables flush nat
- iptables:
+ ansible.builtin.iptables:
table: nat
chain: '{{ item }}'
flush: yes
with_items: [ 'INPUT', 'OUTPUT', 'PREROUTING', 'POSTROUTING' ]
- name: Log packets arriving into an user-defined chain
- iptables:
+ ansible.builtin.iptables:
chain: LOGGING
action: append
state: present
diff --git a/lib/ansible/modules/lineinfile.py b/lib/ansible/modules/lineinfile.py
index 4e856e50..7b364499 100644
--- a/lib/ansible/modules/lineinfile.py
+++ b/lib/ansible/modules/lineinfile.py
@@ -125,6 +125,7 @@ extends_documentation_fragment:
- validate
notes:
- As of Ansible 2.3, the I(dest) option has been changed to I(path) as default, but I(dest) still works as well.
+ - Supports C(check_mode).
seealso:
- module: ansible.builtin.blockinfile
- module: ansible.builtin.copy
@@ -140,19 +141,19 @@ author:
EXAMPLES = r'''
# NOTE: Before 2.3, option 'dest', 'destfile' or 'name' was used instead of 'path'
- name: Ensure SELinux is set to enforcing mode
- lineinfile:
+ ansible.builtin.lineinfile:
path: /etc/selinux/config
regexp: '^SELINUX='
line: SELINUX=enforcing
- name: Make sure group wheel is not in the sudoers configuration
- lineinfile:
+ ansible.builtin.lineinfile:
path: /etc/sudoers
state: absent
regexp: '^%wheel'
- name: Replace a localhost entry with our own
- lineinfile:
+ ansible.builtin.lineinfile:
path: /etc/hosts
regexp: '^127\.0\.0\.1'
line: 127.0.0.1 localhost
@@ -161,28 +162,28 @@ EXAMPLES = r'''
mode: '0644'
- name: Ensure the default Apache port is 8080
- lineinfile:
+ ansible.builtin.lineinfile:
path: /etc/httpd/conf/httpd.conf
regexp: '^Listen '
insertafter: '^#Listen '
line: Listen 8080
- name: Ensure we have our own comment added to /etc/services
- lineinfile:
+ ansible.builtin.lineinfile:
path: /etc/services
regexp: '^# port for http'
insertbefore: '^www.*80/tcp'
line: '# port for http by default'
- name: Add a line to a file if the file does not exist, without passing regexp
- lineinfile:
+ ansible.builtin.lineinfile:
path: /tmp/testfile
line: 192.168.1.99 foo.lab.net foo
create: yes
# NOTE: Yaml requires escaping backslashes in double quotes but not in single quotes
- name: Ensure the JBoss memory settings are exactly as needed
- lineinfile:
+ ansible.builtin.lineinfile:
path: /opt/jboss-as/bin/standalone.conf
regexp: '^(.*)Xms(\d+)m(.*)$'
line: '\1Xms${xms}m\3'
@@ -190,7 +191,7 @@ EXAMPLES = r'''
# NOTE: Fully quoted because of the ': ' on the line. See the Gotchas in the YAML docs.
- name: Validate the sudoers file before saving
- lineinfile:
+ ansible.builtin.lineinfile:
path: /etc/sudoers
state: present
regexp: '^%ADMIN ALL='
@@ -199,13 +200,15 @@ EXAMPLES = r'''
# See https://docs.python.org/3/library/re.html for further details on syntax
- name: Use backrefs with alternative group syntax to avoid conflicts with variable values
- lineinfile:
+ ansible.builtin.lineinfile:
path: /tmp/config
regexp: ^(host=).*
line: \g<1>{{ hostname }}
backrefs: yes
'''
+RETURN = r'''#'''
+
import os
import re
import tempfile
diff --git a/lib/ansible/modules/ping.py b/lib/ansible/modules/ping.py
index fe9238f6..2cb1fb23 100644
--- a/lib/ansible/modules/ping.py
+++ b/lib/ansible/modules/ping.py
@@ -15,12 +15,12 @@ module: ping
version_added: historical
short_description: Try to connect to host, verify a usable python and return C(pong) on success
description:
- - A trivial test module, this module always returns C(pong) on successful
- contact. It does not make sense in playbooks, but it is useful from
- C(/usr/bin/ansible) to verify the ability to login and that a usable Python is configured.
- - This is NOT ICMP ping, this is just a trivial test module that requires Python on the remote-node.
- - For Windows targets, use the M(ansible.windows.win_ping) module instead.
- - For Network targets, use the M(ansible.netcommon.net_ping) module instead.
+ - A trivial test module, this module always returns C(pong) on successful
+ contact. It does not make sense in playbooks, but it is useful from
+ C(/usr/bin/ansible) to verify the ability to login and that a usable Python is configured.
+ - This is NOT ICMP ping, this is just a trivial test module that requires Python on the remote-node.
+ - For Windows targets, use the M(ansible.windows.win_ping) module instead.
+ - For Network targets, use the M(ansible.netcommon.net_ping) module instead.
options:
data:
description:
@@ -29,11 +29,13 @@ options:
type: str
default: pong
seealso:
-- module: ansible.netcommon.net_ping
-- module: ansible.windows.win_ping
+ - module: ansible.netcommon.net_ping
+ - module: ansible.windows.win_ping
author:
- - Ansible Core Team
- - Michael DeHaan
+ - Ansible Core Team
+ - Michael DeHaan
+notes:
+ - Supports C(check_mode).
'''
EXAMPLES = '''
@@ -41,16 +43,16 @@ EXAMPLES = '''
# ansible webservers -m ping
- name: Example from an Ansible Playbook
- ping:
+ ansible.builtin.ping:
- name: Induce an exception to see what happens
- ping:
+ ansible.builtin.ping:
data: crash
'''
RETURN = '''
ping:
- description: value provided with the data parameter
+ description: Value provided with the data parameter.
returned: success
type: str
sample: pong
diff --git a/lib/ansible/modules/replace.py b/lib/ansible/modules/replace.py
index a694d9e7..ed9fd1f2 100644
--- a/lib/ansible/modules/replace.py
+++ b/lib/ansible/modules/replace.py
@@ -95,24 +95,25 @@ notes:
previous incorrect behavior, you may be need to adjust your tasks.
See U(https://github.com/ansible/ansible/issues/31354) for details.
- Option I(follow) has been removed in Ansible 2.5, because this module modifies the contents of the file so I(follow=no) doesn't make sense.
+ - Supports C(check_mode).
'''
EXAMPLES = r'''
- name: Before Ansible 2.3, option 'dest', 'destfile' or 'name' was used instead of 'path'
- replace:
+ ansible.builtin.replace:
path: /etc/hosts
regexp: '(\s+)old\.host\.name(\s+.*)?$'
replace: '\1new.host.name\2'
- name: Replace after the expression till the end of the file (requires Ansible >= 2.4)
- replace:
+ ansible.builtin.replace:
path: /etc/apache2/sites-available/default.conf
after: 'NameVirtualHost [*]'
regexp: '^(.+)$'
replace: '# \1'
- name: Replace before the expression till the begin of the file (requires Ansible >= 2.4)
- replace:
+ ansible.builtin.replace:
path: /etc/apache2/sites-available/default.conf
before: '# live site config'
regexp: '^(.+)$'
@@ -121,7 +122,7 @@ EXAMPLES = r'''
# Prior to Ansible 2.7.10, using before and after in combination did the opposite of what was intended.
# see https://github.com/ansible/ansible/issues/31354 for details.
- name: Replace between the expressions (requires Ansible >= 2.4)
- replace:
+ ansible.builtin.replace:
path: /etc/hosts
after: '<VirtualHost [*]>'
before: '</VirtualHost>'
@@ -129,7 +130,7 @@ EXAMPLES = r'''
replace: '# \1'
- name: Supports common file attributes
- replace:
+ ansible.builtin.replace:
path: /home/jdoe/.ssh/known_hosts
regexp: '^old\.host\.name[^\n]*\n'
owner: jdoe
@@ -137,34 +138,36 @@ EXAMPLES = r'''
mode: '0644'
- name: Supports a validate command
- replace:
+ ansible.builtin.replace:
path: /etc/apache/ports
regexp: '^(NameVirtualHost|Listen)\s+80\s*$'
replace: '\1 127.0.0.1:8080'
validate: '/usr/sbin/apache2ctl -f %s -t'
- name: Short form task (in ansible 2+) necessitates backslash-escaped sequences
- replace: path=/etc/hosts regexp='\\b(localhost)(\\d*)\\b' replace='\\1\\2.localdomain\\2 \\1\\2'
+ ansible.builtin.replace: path=/etc/hosts regexp='\\b(localhost)(\\d*)\\b' replace='\\1\\2.localdomain\\2 \\1\\2'
- name: Long form task does not
- replace:
+ ansible.builtin.replace:
path: /etc/hosts
regexp: '\b(localhost)(\d*)\b'
replace: '\1\2.localdomain\2 \1\2'
- name: Explicitly specifying positional matched groups in replacement
- replace:
+ ansible.builtin.replace:
path: /etc/ssh/sshd_config
regexp: '^(ListenAddress[ ]+)[^\n]+$'
replace: '\g<1>0.0.0.0'
- name: Explicitly specifying named matched groups
- replace:
+ ansible.builtin.replace:
path: /etc/ssh/sshd_config
regexp: '^(?P<dctv>ListenAddress[ ]+)(?P<host>[^\n]+)$'
replace: '#\g<dctv>\g<host>\n\g<dctv>0.0.0.0'
'''
+RETURN = r'''#'''
+
import os
import re
import tempfile
diff --git a/lib/ansible/modules/rpm_key.py b/lib/ansible/modules/rpm_key.py
index 350cf4e3..c24d1ce1 100644
--- a/lib/ansible/modules/rpm_key.py
+++ b/lib/ansible/modules/rpm_key.py
@@ -42,29 +42,34 @@ options:
- This will be used to verify the specified key.
type: str
version_added: 2.9
+notes:
+ - Supports C(check_mode).
'''
EXAMPLES = '''
- name: Import a key from a url
- rpm_key:
+ ansible.builtin.rpm_key:
state: present
key: http://apt.sw.be/RPM-GPG-KEY.dag.txt
- name: Import a key from a file
- rpm_key:
+ ansible.builtin.rpm_key:
state: present
key: /path/to/key.gpg
- name: Ensure a key is not present in the db
- rpm_key:
+ ansible.builtin.rpm_key:
state: absent
key: DEADB33F
- name: Verify the key, using a fingerprint, before import
- rpm_key:
+ ansible.builtin.rpm_key:
key: /path/to/RPM-GPG-KEY.dag.txt
fingerprint: EBC6 E12C 62B1 C734 026B 2122 A20E 5214 6B8D 79E6
'''
+
+RETURN = r'''#'''
+
import re
import os.path
import tempfile
diff --git a/lib/ansible/modules/script.py b/lib/ansible/modules/script.py
index 3e013820..4333edfa 100644
--- a/lib/ansible/modules/script.py
+++ b/lib/ansible/modules/script.py
@@ -11,12 +11,12 @@ module: script
version_added: "0.9"
short_description: Runs a local script on a remote node after transferring it
description:
- - The C(script) module takes the script name followed by a list of space-delimited arguments.
- - Either a free form command or C(cmd) parameter is required, see the examples.
- - The local script at path will be transferred to the remote node and then executed.
- - The given script will be processed through the shell environment on the remote node.
- - This module does not require python on the remote system, much like the M(ansible.builtin.raw) module.
- - This module is also supported for Windows targets.
+ - The C(script) module takes the script name followed by a list of space-delimited arguments.
+ - Either a free form command or C(cmd) parameter is required, see the examples.
+ - The local script at path will be transferred to the remote node and then executed.
+ - The given script will be processed through the shell environment on the remote node.
+ - This module does not require python on the remote system, much like the M(ansible.builtin.raw) module.
+ - This module is also supported for Windows targets.
options:
free_form:
description:
@@ -47,41 +47,42 @@ notes:
stderr is sent to stdout. If you depend on separated stdout and stderr result keys, please switch to a copy+command set of tasks instead of using script.
- If the path to the local script contains spaces, it needs to be quoted.
- This module is also supported for Windows targets.
+ - Does not support C(check_mode).
seealso:
-- module: ansible.builtin.shell
-- module: ansible.windows.win_shell
+ - module: ansible.builtin.shell
+ - module: ansible.windows.win_shell
author:
- - Ansible Core Team
- - Michael DeHaan
+ - Ansible Core Team
+ - Michael DeHaan
extends_documentation_fragment:
- - decrypt
+ - decrypt
'''
EXAMPLES = r'''
- name: Run a script with arguments (free form)
- script: /some/local/script.sh --some-argument 1234
+ ansible.builtin.script: /some/local/script.sh --some-argument 1234
- name: Run a script with arguments (using 'cmd' parameter)
- script:
+ ansible.builtin.script:
cmd: /some/local/script.sh --some-argument 1234
- name: Run a script only if file.txt does not exist on the remote node
- script: /some/local/create_file.sh --some-argument 1234
+ ansible.builtin.script: /some/local/create_file.sh --some-argument 1234
args:
creates: /the/created/file.txt
- name: Run a script only if file.txt exists on the remote node
- script: /some/local/remove_file.sh --some-argument 1234
+ ansible.builtin.script: /some/local/remove_file.sh --some-argument 1234
args:
removes: /the/removed/file.txt
- name: Run a script using an executable in a non-system path
- script: /some/local/script
+ ansible.builtin.script: /some/local/script
args:
executable: /some/remote/executable
- name: Run a script using an executable in a system path
- script: /some/local/script.py
+ ansible.builtin.script: /some/local/script.py
args:
executable: python3
'''
diff --git a/lib/ansible/modules/service.py b/lib/ansible/modules/service.py
index 8ed03618..f4eb7097 100644
--- a/lib/ansible/modules/service.py
+++ b/lib/ansible/modules/service.py
@@ -79,8 +79,9 @@ options:
version_added: 2.2
notes:
- For AIX, group subsystem names can be used.
+ - Supports C(check_mode).
seealso:
-- module: ansible.windows.win_service
+ - module: ansible.windows.win_service
author:
- Ansible Core Team
- Michael DeHaan
@@ -88,43 +89,45 @@ author:
EXAMPLES = r'''
- name: Start service httpd, if not started
- service:
+ ansible.builtin.service:
name: httpd
state: started
- name: Stop service httpd, if started
- service:
+ ansible.builtin.service:
name: httpd
state: stopped
- name: Restart service httpd, in all cases
- service:
+ ansible.builtin.service:
name: httpd
state: restarted
- name: Reload service httpd, in all cases
- service:
+ ansible.builtin.service:
name: httpd
state: reloaded
- name: Enable service httpd, and not touch the state
- service:
+ ansible.builtin.service:
name: httpd
enabled: yes
- name: Start service foo, based on running process /usr/bin/foo
- service:
+ ansible.builtin.service:
name: foo
pattern: /usr/bin/foo
state: started
- name: Restart network service for interface eth0
- service:
+ ansible.builtin.service:
name: network
state: restarted
args: eth0
'''
+RETURN = r'''#'''
+
import glob
import json
import os
diff --git a/lib/ansible/modules/service_facts.py b/lib/ansible/modules/service_facts.py
index 407be921..91de089c 100644
--- a/lib/ansible/modules/service_facts.py
+++ b/lib/ansible/modules/service_facts.py
@@ -14,7 +14,7 @@ DOCUMENTATION = r'''
module: service_facts
short_description: Return service state information as fact data
description:
- - Return service state information as fact data for various service management utilities
+ - Return service state information as fact data for various service management utilities.
version_added: "2.5"
requirements: ["Any of the following supported init systems: systemd, sysv, upstart"]
@@ -25,6 +25,7 @@ notes:
C(ansible_facts.services.zuul-gateway). It is instead recommended to
using the string value of the service name as the key in order to obtain
the fact data value like C(ansible_facts.services['zuul-gateway'])
+ - Supports C(check_mode).
author:
- Adam Miller (@maxamillion)
@@ -32,11 +33,11 @@ author:
EXAMPLES = r'''
- name: Populate service facts
- service_facts:
+ ansible.builtin.service_facts:
-- debug:
+- name: Print service facts
+ ansible.builtin.debug:
var: ansible_facts.services
-
'''
RETURN = r'''
diff --git a/lib/ansible/modules/setup.py b/lib/ansible/modules/setup.py
index 083d3d44..f2040dfc 100644
--- a/lib/ansible/modules/setup.py
+++ b/lib/ansible/modules/setup.py
@@ -28,19 +28,16 @@ options:
use C(!all,!min), and specify the particular fact subsets.
Use the filter parameter if you do not want to display some collected
facts."
- required: false
default: "all"
gather_timeout:
version_added: "2.2"
description:
- Set the default timeout in seconds for individual fact gathering.
- required: false
default: 10
filter:
version_added: "1.1"
description:
- If supplied, only return facts that match this shell-style (fnmatch) wildcard.
- required: false
default: "*"
fact_path:
version_added: "1.3"
@@ -57,7 +54,6 @@ options:
exists on the target host. Files in this path MUST be PowerShell scripts C(.ps1)
which outputs an object. This object will be formatted by Ansible as json so the
script should be outputting a raw hashtable, array, or other primitive object.
- required: false
default: /etc/ansible/facts.d
description:
- This module is automatically called by playbooks to gather useful
@@ -79,6 +75,7 @@ notes:
C(filter) as this is provided by a simpler implementation of the module.
- This module is also supported for Windows targets.
- This module should be run with elevated privileges on BSD systems to gather facts like ansible_product_version.
+ - Supports C(check_mode).
author:
- "Ansible Core Team"
- "Michael DeHaan"
@@ -86,44 +83,44 @@ author:
EXAMPLES = """
# Display facts from all hosts and store them indexed by I(hostname) at C(/tmp/facts).
-# ansible all -m setup --tree /tmp/facts
+# ansible all -m ansible.builtin.setup --tree /tmp/facts
# Display only facts regarding memory found by ansible on all hosts and output them.
-# ansible all -m setup -a 'filter=ansible_*_mb'
+# ansible all -m ansible.builtin.setup -a 'filter=ansible_*_mb'
# Display only facts returned by facter.
-# ansible all -m setup -a 'filter=facter_*'
+# ansible all -m ansible.builtin.setup -a 'filter=facter_*'
# Collect only facts returned by facter.
-# ansible all -m setup -a 'gather_subset=!all,!any,facter'
+# ansible all -m ansible.builtin.setup -a 'gather_subset=!all,!any,facter'
- name: Collect only facts returned by facter
- setup:
+ ansible.builtin.setup:
gather_subset:
- '!all'
- '!any'
- facter
# Display only facts about certain interfaces.
-# ansible all -m setup -a 'filter=ansible_eth[0-2]'
+# ansible all -m ansible.builtin.setup -a 'filter=ansible_eth[0-2]'
# Restrict additional gathered facts to network and virtual (includes default minimum facts)
-# ansible all -m setup -a 'gather_subset=network,virtual'
+# ansible all -m ansible.builtin.setup -a 'gather_subset=network,virtual'
# Collect only network and virtual (excludes default minimum facts)
-# ansible all -m setup -a 'gather_subset=!all,!any,network,virtual'
+# ansible all -m ansible.builtin.setup -a 'gather_subset=!all,!any,network,virtual'
# Do not call puppet facter or ohai even if present.
-# ansible all -m setup -a 'gather_subset=!facter,!ohai'
+# ansible all -m ansible.builtin.setup -a 'gather_subset=!facter,!ohai'
# Only collect the default minimum amount of facts:
-# ansible all -m setup -a 'gather_subset=!all'
+# ansible all -m ansible.builtin.setup -a 'gather_subset=!all'
# Collect no facts, even the default minimum subset of facts:
-# ansible all -m setup -a 'gather_subset=!all,!min'
+# ansible all -m ansible.builtin.setup -a 'gather_subset=!all,!min'
# Display facts from Windows hosts with custom facts stored in C(C:\\custom_facts).
-# ansible windows -m setup -a "fact_path='c:\\custom_facts'"
+# ansible windows -m ansible.builtin.setup -a "fact_path='c:\\custom_facts'"
"""
# import module snippets
diff --git a/lib/ansible/modules/shell.py b/lib/ansible/modules/shell.py
index 17727352..5da88196 100644
--- a/lib/ansible/modules/shell.py
+++ b/lib/ansible/modules/shell.py
@@ -99,37 +99,37 @@ author:
EXAMPLES = r'''
- name: Execute the command in remote shell; stdout goes to the specified file on the remote
- shell: somescript.sh >> somelog.txt
+ ansible.builtin.shell: somescript.sh >> somelog.txt
- name: Change the working directory to somedir/ before executing the command
- shell: somescript.sh >> somelog.txt
+ ansible.builtin.shell: somescript.sh >> somelog.txt
args:
chdir: somedir/
# You can also use the 'args' form to provide the options.
- name: This command will change the working directory to somedir/ and will only run when somedir/somelog.txt doesn't exist
- shell: somescript.sh >> somelog.txt
+ ansible.builtin.shell: somescript.sh >> somelog.txt
args:
chdir: somedir/
creates: somelog.txt
# You can also use the 'cmd' parameter instead of free form format.
- name: This command will change the working directory to somedir/
- shell:
+ ansible.builtin.shell:
cmd: ls -l | grep log
chdir: somedir/
- name: Run a command that uses non-posix shell-isms (in this example /bin/sh doesn't handle redirection and wildcards together but bash does)
- shell: cat < /tmp/*txt
+ ansible.builtin.shell: cat < /tmp/*txt
args:
executable: /bin/bash
- name: Run a command using a templated variable (always use quote filter to avoid injection)
- shell: cat {{ myfile|quote }}
+ ansible.builtin.shell: cat {{ myfile|quote }}
# You can use shell to run other executables to perform actions inline
- name: Run expect to wait for a successful PXE boot via out-of-band CIMC
- shell: |
+ ansible.builtin.shell: |
set timeout 300
spawn ssh admin@{{ cimc_host }}
@@ -149,7 +149,7 @@ EXAMPLES = r'''
# Disabling warnings
- name: Using curl to connect to a host via SOCKS proxy (unsupported in uri). Ordinarily this would throw a warning
- shell: curl --socks5 localhost:9000 http://www.ansible.com
+ ansible.builtin.shell: curl --socks5 localhost:9000 http://www.ansible.com
args:
warn: no
'''
@@ -161,47 +161,47 @@ msg:
type: bool
sample: True
start:
- description: The command execution start time
+ description: The command execution start time.
returned: always
type: str
sample: '2016-02-25 09:18:26.429568'
end:
- description: The command execution end time
+ description: The command execution end time.
returned: always
type: str
sample: '2016-02-25 09:18:26.755339'
delta:
- description: The command execution delta time
+ description: The command execution delta time.
returned: always
type: str
sample: '0:00:00.325771'
stdout:
- description: The command standard output
+ description: The command standard output.
returned: always
type: str
sample: 'Clustering node rabbit@slave1 with rabbit@master …'
stderr:
- description: The command standard error
+ description: The command standard error.
returned: always
type: str
sample: 'ls: cannot access foo: No such file or directory'
cmd:
- description: The command executed by the task
+ description: The command executed by the task.
returned: always
type: str
sample: 'rabbitmqctl join_cluster rabbit@master'
rc:
- description: The command return code (0 means success)
+ description: The command return code (0 means success).
returned: always
type: int
sample: 0
stdout_lines:
- description: The command standard output split in lines
+ description: The command standard output split in lines.
returned: always
type: list
sample: [u'Clustering node rabbit@slave1 with rabbit@master …']
stderr_lines:
- description: The command standard error split in lines
+ description: The command standard error split in lines.
returned: always
type: list
sample: [u'ls cannot access foo: No such file or directory', u'ls …']
diff --git a/lib/ansible/modules/slurp.py b/lib/ansible/modules/slurp.py
index 0bc94015..919c79cc 100644
--- a/lib/ansible/modules/slurp.py
+++ b/lib/ansible/modules/slurp.py
@@ -28,6 +28,7 @@ notes:
- This module returns an 'in memory' base64 encoded version of the file, take
into account that this will require at least twice the RAM as the original file size.
- This module is also supported for Windows targets.
+ - Supports C(check_mode).
seealso:
- module: ansible.builtin.fetch
author:
@@ -37,11 +38,12 @@ author:
EXAMPLES = r'''
- name: Find out what the remote machine's mounts are
- slurp:
+ ansible.builtin.slurp:
src: /proc/mounts
register: mounts
-- debug:
+- name: Print returned information
+ ansible.builtin.debug:
msg: "{{ mounts['content'] | b64decode }}"
# From the commandline, find the pid of the remote machine's sshd
@@ -56,6 +58,8 @@ EXAMPLES = r'''
# 2179
'''
+RETURN = r'''#'''
+
import base64
import os
diff --git a/lib/ansible/modules/stat.py b/lib/ansible/modules/stat.py
index 2a5fbcde..717a8ac2 100644
--- a/lib/ansible/modules/stat.py
+++ b/lib/ansible/modules/stat.py
@@ -64,6 +64,8 @@ options:
seealso:
- module: ansible.builtin.file
- module: ansible.windows.win_stat
+notes:
+- Supports C(check_mode).
author: Bruce Pennypacker (@bpennypacker)
'''
@@ -71,10 +73,11 @@ EXAMPLES = r'''
# Obtain the stats of /etc/foo.conf, and check that the file still belongs
# to 'root'. Fail otherwise.
- name: Get stats of a file
- stat:
+ ansible.builtin.stat:
path: /etc/foo.conf
register: st
-- fail:
+- name: Fail if the file does not belong to 'root'
+ ansible.builtin.fail:
msg: "Whoops! file ownership has changed"
when: st.stat.pw_name != 'root'
@@ -84,23 +87,27 @@ EXAMPLES = r'''
# Run this to understand the structure, the skipped ones do not pass the
# check performed by 'when'
- name: Get stats of the FS object
- stat:
+ ansible.builtin.stat:
path: /path/to/something
register: sym
-- debug:
+- name: Print a debug message
+ ansible.builtin.debug:
msg: "islnk isn't defined (path doesn't exist)"
when: sym.stat.islnk is not defined
-- debug:
+- name: Print a debug message
+ ansible.builtin.debug:
msg: "islnk is defined (path must exist)"
when: sym.stat.islnk is defined
-- debug:
+- name: Print a debug message
+ ansible.builtin.debug:
msg: "Path exists and is a symlink"
when: sym.stat.islnk is defined and sym.stat.islnk
-- debug:
+- name: Print a debug message
+ ansible.builtin.debug:
msg: "Path exists and isn't a symlink"
when: sym.stat.islnk is defined and sym.stat.islnk == False
@@ -108,27 +115,28 @@ EXAMPLES = r'''
# Determine if a path exists and is a directory. Note that we need to test
# both that p.stat.isdir actually exists, and also that it's set to true.
- name: Get stats of the FS object
- stat:
+ ansible.builtin.stat:
path: /path/to/something
register: p
-- debug:
+- name: Print a debug message
+ ansible.builtin.debug:
msg: "Path exists and is a directory"
when: p.stat.isdir is defined and p.stat.isdir
-- name: Don't do checksum
- stat:
+- name: Don not do checksum
+ ansible.builtin.stat:
path: /path/to/myhugefile
get_checksum: no
- name: Use sha256 to calculate checksum
- stat:
+ ansible.builtin.stat:
path: /path/to/something
checksum_algorithm: sha256
'''
RETURN = r'''
stat:
- description: dictionary containing all the stat data, some platforms might add additional fields
+ description: Dictionary containing all the stat data, some platforms might add additional fields.
returned: success
type: complex
contains:
diff --git a/lib/ansible/modules/subversion.py b/lib/ansible/modules/subversion.py
index 730d26f0..4a5e1dae 100644
--- a/lib/ansible/modules/subversion.py
+++ b/lib/ansible/modules/subversion.py
@@ -18,8 +18,8 @@ version_added: "0.7"
author:
- Dane Summers (@dsummersl) <njharman@gmail.com>
notes:
- - Requires I(svn) to be installed on the client.
- This module does not handle externals.
+ - Supports C(check_mode).
options:
repo:
description:
@@ -44,7 +44,7 @@ options:
in_place:
description:
- If the directory exists, then the working copy will be checked-out over-the-top using
- svn checkout --force; if force is specified then existing files with different content are reverted
+ svn checkout --force; if force is specified then existing files with different content are reverted.
type: bool
default: "no"
version_added: "2.6"
@@ -92,24 +92,26 @@ requirements:
EXAMPLES = '''
- name: Checkout subversion repository to specified folder
- subversion:
+ ansible.builtin.subversion:
repo: svn+ssh://an.example.org/path/to/repo
dest: /src/checkout
- name: Export subversion directory to folder
- subversion:
+ ansible.builtin.subversion:
repo: svn+ssh://an.example.org/path/to/repo
dest: /src/export
export: yes
- name: Get information about the repository whether or not it has already been cloned locally
-- subversion:
+ ansible.builtin.subversion:
repo: svn+ssh://an.example.org/path/to/repo
dest: /src/checkout
checkout: no
update: no
'''
+RETURN = r'''#'''
+
import os
import re
diff --git a/lib/ansible/modules/systemd.py b/lib/ansible/modules/systemd.py
index a0bf8057..26e79a54 100644
--- a/lib/ansible/modules/systemd.py
+++ b/lib/ansible/modules/systemd.py
@@ -63,8 +63,8 @@ options:
type: bool
scope:
description:
- - run systemctl within a given service manager scope, either as the default system scope (system),
- the current user's scope (user), or the scope of all users (global).
+ - Run systemctl within a given service manager scope, either as the default system scope C(system),
+ the current user's scope C(user), or the scope of all users C(global).
- "For systemd to work with 'user', the executing user must have its own instance of dbus started (systemd requirement).
The user dbus process is normally started during normal login, but not during the run of Ansible tasks.
Otherwise you will probably get a 'Failed to connect to bus: no such file or directory' error."
@@ -83,56 +83,57 @@ notes:
and all except 'daemon_reload' (and 'daemon_reexec' since 2.8) also require 'name'.
- Before 2.4 you always required 'name'.
- Globs are not supported in name, i.e ``postgres*.service``.
+ - Supports C(check_mode).
requirements:
- A system managed by systemd.
'''
EXAMPLES = '''
- name: Make sure a service is running
- systemd:
+ ansible.builtin.systemd:
state: started
name: httpd
- name: Stop service cron on debian, if running
- systemd:
+ ansible.builtin.systemd:
name: cron
state: stopped
- name: Restart service cron on centos, in all cases, also issue daemon-reload to pick up config changes
- systemd:
+ ansible.builtin.systemd:
state: restarted
daemon_reload: yes
name: crond
- name: Reload service httpd, in all cases
- systemd:
+ ansible.builtin.systemd:
name: httpd
state: reloaded
- name: Enable service httpd and ensure it is not masked
- systemd:
+ ansible.builtin.systemd:
name: httpd
enabled: yes
masked: no
- name: Enable a timer for dnf-automatic
- systemd:
+ ansible.builtin.systemd:
name: dnf-automatic.timer
state: started
enabled: yes
- name: Just force systemd to reread configs (2.4 and above)
- systemd:
+ ansible.builtin.systemd:
daemon_reload: yes
- name: Just force systemd to re-execute itself (2.8 and above)
- systemd:
+ ansible.builtin.systemd:
daemon_reexec: yes
'''
RETURN = '''
status:
- description: A dictionary with the key=value pairs returned from `systemctl show`
+ description: A dictionary with the key=value pairs returned from `systemctl show`.
returned: success
type: complex
sample: {
@@ -419,10 +420,10 @@ def main():
elif err and rc == 1 and 'Failed to parse bus message' in err:
result['status'] = parse_systemctl_show(to_native(out).split('\n'))
- unit, sep, suffix = unit.partition('@')
- unit_search = '{unit}{sep}*'.format(unit=unit, sep=sep)
- (rc, out, err) = module.run_command("{systemctl} list-unit-files '{unit_search}'".format(systemctl=systemctl, unit_search=unit_search))
- is_systemd = unit in out
+ unit_base, sep, suffix = unit.partition('@')
+ unit_search = '{unit_base}{sep}'.format(unit_base=unit_base, sep=sep)
+ (rc, out, err) = module.run_command("{systemctl} list-unit-files '{unit_search}*'".format(systemctl=systemctl, unit_search=unit_search))
+ is_systemd = unit_search in out
(rc, out, err) = module.run_command("{systemctl} is-active '{unit}'".format(systemctl=systemctl, unit=unit))
result['status']['ActiveState'] = out.rstrip('\n')
diff --git a/lib/ansible/modules/tempfile.py b/lib/ansible/modules/tempfile.py
index 706f4910..ff70ae80 100644
--- a/lib/ansible/modules/tempfile.py
+++ b/lib/ansible/modules/tempfile.py
@@ -50,18 +50,18 @@ author:
EXAMPLES = """
- name: Create temporary build directory
- tempfile:
+ ansible.builtin.tempfile:
state: directory
suffix: build
- name: Create temporary file
- tempfile:
+ ansible.builtin.tempfile:
state: file
suffix: temp
register: tempfile_1
- name: Use the registered var and the file module to remove the temporary file
- file:
+ ansible.builtin.file:
path: "{{ tempfile_1.path }}"
state: absent
when: tempfile_1.path is defined
@@ -69,7 +69,7 @@ EXAMPLES = """
RETURN = '''
path:
- description: Path to created file or directory
+ description: Path to created file or directory.
returned: success
type: str
sample: "/tmp/ansible.bMlvdk"
diff --git a/lib/ansible/modules/template.py b/lib/ansible/modules/template.py
index bc1a2730..43b4ce3d 100644
--- a/lib/ansible/modules/template.py
+++ b/lib/ansible/modules/template.py
@@ -14,7 +14,7 @@ DOCUMENTATION = r'''
---
module: template
version_added: historical
-short_description: Template a file out to a remote server
+short_description: Template a file out to a target host
options:
follow:
description:
@@ -43,7 +43,7 @@ extends_documentation_fragment:
EXAMPLES = r'''
- name: Template a file to /etc/file.conf
- template:
+ ansible.builtin.template:
src: /mytemplates/foo.j2
dest: /etc/file.conf
owner: bin
@@ -51,7 +51,7 @@ EXAMPLES = r'''
mode: '0644'
- name: Template a file, using symbolic modes (equivalent to 0644)
- template:
+ ansible.builtin.template:
src: /mytemplates/foo.j2
dest: /etc/file.conf
owner: bin
@@ -59,7 +59,7 @@ EXAMPLES = r'''
mode: u=rw,g=r,o=r
- name: Copy a version of named.conf that is dependent on the OS. setype obtained by doing ls -Z /etc/named.conf on original file
- template:
+ ansible.builtin.template:
src: named.conf_{{ ansible_os_family }}.j2
dest: /etc/named.conf
group: named
@@ -67,19 +67,19 @@ EXAMPLES = r'''
mode: 0640
- name: Create a DOS-style text file from a template
- template:
+ ansible.builtin.template:
src: config.ini.j2
dest: /share/windows/config.ini
newline_sequence: '\r\n'
- name: Copy a new sudoers file into place, after passing validation with visudo
- template:
+ ansible.builtin.template:
src: /mine/sudoers
dest: /etc/sudoers
validate: /usr/sbin/visudo -cf %s
- name: Update sshd configuration safely, avoid locking yourself out
- template:
+ ansible.builtin.template:
src: etc/ssh/sshd_config.j2
dest: /etc/ssh/sshd_config
owner: root
diff --git a/lib/ansible/modules/unarchive.py b/lib/ansible/modules/unarchive.py
index 749d0288..d35a7786 100644
--- a/lib/ansible/modules/unarchive.py
+++ b/lib/ansible/modules/unarchive.py
@@ -16,7 +16,7 @@ DOCUMENTATION = r'''
---
module: unarchive
version_added: '1.4'
-short_description: Unpacks an archive after (optionally) copying it from the local machine.
+short_description: Unpacks an archive after (optionally) copying it from the local machine
description:
- The C(unarchive) module unpacks an archive. It will not unpack a compressed file that does not contain an archive.
- By default, it will copy the source file from the local system to the target before unpacking.
@@ -105,6 +105,7 @@ notes:
are not touched. This is the same behavior as a normal archive extraction.
- Existing files/directories in the destination which are not in the archive
are ignored for purposes of deciding if the archive should be unpacked or not.
+ - Supports C(check_mode).
seealso:
- module: community.general.archive
- module: community.general.iso_extract
@@ -114,24 +115,24 @@ author: Michael DeHaan
EXAMPLES = r'''
- name: Extract foo.tgz into /var/lib/foo
- unarchive:
+ ansible.builtin.unarchive:
src: foo.tgz
dest: /var/lib/foo
- name: Unarchive a file that is already on the remote machine
- unarchive:
+ ansible.builtin.unarchive:
src: /tmp/foo.zip
dest: /usr/local/bin
remote_src: yes
- name: Unarchive a file that needs to be downloaded (added in 2.0)
- unarchive:
+ ansible.builtin.unarchive:
src: https://example.com/example.zip
dest: /usr/local/bin
remote_src: yes
- name: Unarchive a file with extra options
- unarchive:
+ ansible.builtin.unarchive:
src: /tmp/foo.zip
dest: /usr/local/bin
extra_opts:
diff --git a/lib/ansible/modules/user.py b/lib/ansible/modules/user.py
index c57fadc9..0e37cbf5 100644
--- a/lib/ansible/modules/user.py
+++ b/lib/ansible/modules/user.py
@@ -192,9 +192,10 @@ options:
version_added: "1.9"
password_lock:
description:
- - Lock the password (usermod -L, pw lock, usermod -C).
- - BUT implementation differs on different platforms, this option does not always mean the user cannot login via other methods.
- - This option does not disable the user, only lock the password. Do not change the password in the same task.
+ - Lock the password (C(usermod -L), C(usermod -U), C(pw lock)).
+ - Implementation differs by platform. This option does not always mean the user cannot login using other methods.
+ - This option does not disable the user, only lock the password.
+ - This must be set to C(False) in order to unlock a currently locked password. The absence of this parameter will not unlock a password.
- Currently supported on Linux, FreeBSD, DragonFlyBSD, NetBSD, OpenBSD.
type: bool
version_added: "2.6"
@@ -658,7 +659,10 @@ class User(object):
if self.password is not None:
cmd.append('-p')
- cmd.append(self.password)
+ if self.password_lock:
+ cmd.append('!%s' % self.password)
+ else:
+ cmd.append(self.password)
if self.create_home:
if not self.local:
@@ -844,9 +848,15 @@ class User(object):
# usermod will refuse to unlock a user with no password, module shows 'changed' regardless
cmd.append('-U')
- if self.update_password == 'always' and self.password is not None and info[1] != self.password:
+ if self.update_password == 'always' and self.password is not None and info[1].lstrip('!') != self.password.lstrip('!'):
+ # Remove options that are mutually exclusive with -p
+ cmd = [c for c in cmd if c not in ['-U', '-L']]
cmd.append('-p')
- cmd.append(self.password)
+ if self.password_lock:
+ # Lock the account and set the hash in a single command
+ cmd.append('!%s' % self.password)
+ else:
+ cmd.append(self.password)
(rc, out, err) = (None, '', '')
@@ -1208,6 +1218,31 @@ class FreeBsdUser(User):
SHADOWFILE_EXPIRE_INDEX = 6
DATE_FORMAT = '%d-%b-%Y'
+ def _handle_lock(self):
+ info = self.user_info()
+ if self.password_lock and not info[1].startswith('*LOCKED*'):
+ cmd = [
+ self.module.get_bin_path('pw', True),
+ 'lock',
+ self.name
+ ]
+ if self.uid is not None and info[2] != int(self.uid):
+ cmd.append('-u')
+ cmd.append(self.uid)
+ return self.execute_command(cmd)
+ elif self.password_lock is False and info[1].startswith('*LOCKED*'):
+ cmd = [
+ self.module.get_bin_path('pw', True),
+ 'unlock',
+ self.name
+ ]
+ if self.uid is not None and info[2] != int(self.uid):
+ cmd.append('-u')
+ cmd.append(self.uid)
+ return self.execute_command(cmd)
+
+ return (None, '', '')
+
def remove_user(self):
cmd = [
self.module.get_bin_path('pw', True),
@@ -1279,6 +1314,7 @@ class FreeBsdUser(User):
# system cannot be handled currently - should we error if its requested?
# create the user
(rc, out, err) = self.execute_command(cmd)
+
if rc is not None and rc != 0:
self.module.fail_json(name=self.name, msg=err, rc=rc)
@@ -1290,7 +1326,18 @@ class FreeBsdUser(User):
self.password,
self.name
]
- return self.execute_command(cmd)
+ _rc, _out, _err = self.execute_command(cmd)
+ if rc is None:
+ rc = _rc
+ out += _out
+ err += _err
+
+ # we have to lock/unlock the password in a distinct command
+ _rc, _out, _err = self._handle_lock()
+ if rc is None:
+ rc = _rc
+ out += _out
+ err += _err
return (rc, out, err)
@@ -1394,45 +1441,38 @@ class FreeBsdUser(User):
cmd.append('-e')
cmd.append(str(calendar.timegm(self.expires)))
+ (rc, out, err) = (None, '', '')
+
# modify the user if cmd will do anything
if cmd_len != len(cmd):
- (rc, out, err) = self.execute_command(cmd)
+ (rc, _out, _err) = self.execute_command(cmd)
+ out += _out
+ err += _err
+
if rc is not None and rc != 0:
self.module.fail_json(name=self.name, msg=err, rc=rc)
- else:
- (rc, out, err) = (None, '', '')
# we have to set the password in a second command
- if self.update_password == 'always' and self.password is not None and info[1] != self.password:
+ if self.update_password == 'always' and self.password is not None and info[1].lstrip('*LOCKED*') != self.password.lstrip('*LOCKED*'):
cmd = [
self.module.get_bin_path('chpass', True),
'-p',
self.password,
self.name
]
- return self.execute_command(cmd)
+ _rc, _out, _err = self.execute_command(cmd)
+ if rc is None:
+ rc = _rc
+ out += _out
+ err += _err
# we have to lock/unlock the password in a distinct command
- if self.password_lock and not info[1].startswith('*LOCKED*'):
- cmd = [
- self.module.get_bin_path('pw', True),
- 'lock',
- self.name
- ]
- if self.uid is not None and info[2] != int(self.uid):
- cmd.append('-u')
- cmd.append(self.uid)
- return self.execute_command(cmd)
- elif self.password_lock is False and info[1].startswith('*LOCKED*'):
- cmd = [
- self.module.get_bin_path('pw', True),
- 'unlock',
- self.name
- ]
- if self.uid is not None and info[2] != int(self.uid):
- cmd.append('-u')
- cmd.append(self.uid)
- return self.execute_command(cmd)
+ _rc, _out, _err = self._handle_lock()
+ if rc is None:
+ rc = _rc
+ out += _out
+ err += _err
+
return (rc, out, err)
diff --git a/lib/ansible/playbook/task.py b/lib/ansible/playbook/task.py
index 9762505c..e9f0a0ab 100644
--- a/lib/ansible/playbook/task.py
+++ b/lib/ansible/playbook/task.py
@@ -121,7 +121,7 @@ class Task(Base, Conditional, Taggable, CollectionSearch):
if self._role:
role_name = self._role.get_name(include_role_fqcn=include_role_fqcn)
- if self._role and self.name and role_name not in self.name:
+ if self._role and self.name:
return "%s : %s" % (role_name, self.name)
elif self.name:
return self.name
diff --git a/lib/ansible/plugins/connection/paramiko_ssh.py b/lib/ansible/plugins/connection/paramiko_ssh.py
index 96a76d67..62861094 100644
--- a/lib/ansible/plugins/connection/paramiko_ssh.py
+++ b/lib/ansible/plugins/connection/paramiko_ssh.py
@@ -536,6 +536,8 @@ class Connection(ConnectionBase):
f.write("%s %s %s\n" % (hostname, keytype, key.get_base64()))
def reset(self):
+ if not self._connected:
+ return
self.close()
self._connect()
diff --git a/lib/ansible/plugins/connection/psrp.py b/lib/ansible/plugins/connection/psrp.py
index 9fab9693..38a6259b 100644
--- a/lib/ansible/plugins/connection/psrp.py
+++ b/lib/ansible/plugins/connection/psrp.py
@@ -407,6 +407,8 @@ class Connection(ConnectionBase):
return self
def reset(self):
+ if not self._connected:
+ return
display.vvvvv("PSRP: Reset Connection", host=self._psrp_host)
self.runspace = None
self._connect()
diff --git a/lib/ansible/plugins/connection/winrm.py b/lib/ansible/plugins/connection/winrm.py
index 3a1bc3d4..cd464574 100644
--- a/lib/ansible/plugins/connection/winrm.py
+++ b/lib/ansible/plugins/connection/winrm.py
@@ -520,6 +520,8 @@ class Connection(ConnectionBase):
return self
def reset(self):
+ if not self._connected:
+ return
self.protocol = None
self.shell_id = None
self._connect()
diff --git a/lib/ansible/plugins/strategy/__init__.py b/lib/ansible/plugins/strategy/__init__.py
index 2e6d49e7..e60b9431 100644
--- a/lib/ansible/plugins/strategy/__init__.py
+++ b/lib/ansible/plugins/strategy/__init__.py
@@ -1015,10 +1015,7 @@ class StrategyBase:
notified_hosts += failed_hosts
if len(notified_hosts) > 0:
- saved_name = handler.name
- handler.name = handler_name
self._tqm.send_callback('v2_playbook_on_handler_task_start', handler)
- handler.name = saved_name
bypass_host_loop = False
try:
diff --git a/lib/ansible/release.py b/lib/ansible/release.py
index e15e306e..b0f9555a 100644
--- a/lib/ansible/release.py
+++ b/lib/ansible/release.py
@@ -19,6 +19,6 @@
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
-__version__ = '2.10.4'
+__version__ = '2.10.5'
__author__ = 'Ansible, Inc.'
__codename__ = 'When the Levee Breaks'
diff --git a/lib/ansible/template/__init__.py b/lib/ansible/template/__init__.py
index e1884c94..eb1276c2 100644
--- a/lib/ansible/template/__init__.py
+++ b/lib/ansible/template/__init__.py
@@ -510,7 +510,8 @@ class JinjaPluginIntercept(MutableMapping):
for func_name, func in iteritems(method_map()):
fq_name = '.'.join((parent_prefix, func_name))
# FIXME: detect/warn on intra-collection function name collisions
- if USE_JINJA2_NATIVE and func_name in C.STRING_TYPE_FILTERS:
+ if USE_JINJA2_NATIVE and fq_name.startswith(('ansible.builtin.', 'ansible.legacy.')) and \
+ func_name in C.STRING_TYPE_FILTERS:
self._collection_jinja_func_cache[fq_name] = _wrap_native_text(func)
else:
self._collection_jinja_func_cache[fq_name] = _unroll_iterator(func)
diff --git a/test/integration/targets/ansible-galaxy/runme.sh b/test/integration/targets/ansible-galaxy/runme.sh
index 2cd59825..3a7c4a39 100755
--- a/test/integration/targets/ansible-galaxy/runme.sh
+++ b/test/integration/targets/ansible-galaxy/runme.sh
@@ -415,6 +415,17 @@ f_ansible_galaxy_status \
unset ANSIBLE_COLLECTIONS_PATH
+f_ansible_galaxy_status \
+ "collection list with collections installed from python package"
+
+ mkdir -p test-site-packages
+ ln -s "${galaxy_testdir}/local/ansible_collections" test-site-packages/ansible_collections
+ ansible-galaxy collection list
+ PYTHONPATH="./test-site-packages/:$PYTHONPATH" ansible-galaxy collection list | tee out.txt
+
+ grep ".ansible/collections/ansible_collections" out.txt
+ grep "test-site-packages/ansible_collections" out.txt
+
## end ansible-galaxy collection list
diff --git a/test/integration/targets/ansible-test/ansible_collections/ns/col/plugins/filter/check_pylint.py b/test/integration/targets/ansible-test/ansible_collections/ns/col/plugins/filter/check_pylint.py
new file mode 100644
index 00000000..359fbf07
--- /dev/null
+++ b/test/integration/targets/ansible-test/ansible_collections/ns/col/plugins/filter/check_pylint.py
@@ -0,0 +1,21 @@
+"""
+These test cases verify ansible-test version constraints for pylint and its dependencies across Python versions.
+The initial test cases were discovered while testing various Python versions against ansible/ansible.
+"""
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+# Python 3.8 fails with astroid 2.2.5 but works on 2.3.3
+# syntax-error: Cannot import 'string' due to syntax error 'invalid syntax (&lt;unknown&gt;, line 109)'
+# Python 3.9 fails with astroid 2.2.5 but works on 2.3.3
+# syntax-error: Cannot import 'string' due to syntax error 'invalid syntax (&lt;unknown&gt;, line 104)'
+import string
+
+# Python 3.9 fails with pylint 2.3.1 or 2.4.4 with astroid 2.3.3 but works with pylint 2.5.0 and astroid 2.4.0
+# 'Call' object has no attribute 'value'
+result = {None: None}[{}.get('something')]
+
+# pylint 2.3.1 and 2.4.4 report the following error but 2.5.0 and 2.6.0 do not
+# blacklisted-name: Black listed name "foo"
+# see: https://github.com/PyCQA/pylint/issues/3701
+foo = {}.keys()
diff --git a/test/integration/targets/ansible-test/ansible_collections/ns/col/plugins/modules/bad.py b/test/integration/targets/ansible-test/ansible_collections/ns/col/plugins/modules/bad.py
new file mode 100644
index 00000000..e79613bb
--- /dev/null
+++ b/test/integration/targets/ansible-test/ansible_collections/ns/col/plugins/modules/bad.py
@@ -0,0 +1,34 @@
+#!/usr/bin/python
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+DOCUMENTATION = '''
+module: bad
+short_description: Bad test module
+description: Bad test module.
+author:
+ - Ansible Core Team
+'''
+
+EXAMPLES = '''
+- bad:
+'''
+
+RETURN = ''''''
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible import constants # intentionally trigger pylint ansible-bad-module-import error
+
+
+def main():
+ module = AnsibleModule(
+ argument_spec=dict(),
+ )
+
+ module.exit_json()
+
+
+if __name__ == '__main__':
+ main()
diff --git a/test/integration/targets/ansible-test/ansible_collections/ns/col/tests/integration/targets/hello/files/bad.py b/test/integration/targets/ansible-test/ansible_collections/ns/col/tests/integration/targets/hello/files/bad.py
new file mode 100644
index 00000000..82215438
--- /dev/null
+++ b/test/integration/targets/ansible-test/ansible_collections/ns/col/tests/integration/targets/hello/files/bad.py
@@ -0,0 +1,16 @@
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+import tempfile
+
+try:
+ import urllib2 # intentionally trigger pylint ansible-bad-import error
+except ImportError:
+ urllib2 = None
+
+try:
+ from urllib2 import Request # intentionally trigger pylint ansible-bad-import-from error
+except ImportError:
+ Request = None
+
+tempfile.mktemp() # intentionally trigger pylint ansible-bad-function error
diff --git a/test/integration/targets/ansible-test/ansible_collections/ns/col/tests/sanity/ignore.txt b/test/integration/targets/ansible-test/ansible_collections/ns/col/tests/sanity/ignore.txt
index e69de29b..079d0161 100644
--- a/test/integration/targets/ansible-test/ansible_collections/ns/col/tests/sanity/ignore.txt
+++ b/test/integration/targets/ansible-test/ansible_collections/ns/col/tests/sanity/ignore.txt
@@ -0,0 +1,6 @@
+plugins/filter/check_pylint.py pylint:blacklisted-name
+plugins/modules/bad.py import
+plugins/modules/bad.py pylint:ansible-bad-module-import
+tests/integration/targets/hello/files/bad.py pylint:ansible-bad-function
+tests/integration/targets/hello/files/bad.py pylint:ansible-bad-import
+tests/integration/targets/hello/files/bad.py pylint:ansible-bad-import-from
diff --git a/test/integration/targets/ansible-test/collection-tests/venv.sh b/test/integration/targets/ansible-test/collection-tests/venv.sh
index 6ff496b6..862c8ad9 100755
--- a/test/integration/targets/ansible-test/collection-tests/venv.sh
+++ b/test/integration/targets/ansible-test/collection-tests/venv.sh
@@ -5,6 +5,10 @@ set -eux -o pipefail
cp -a "${TEST_DIR}/ansible_collections" "${WORK_DIR}"
cd "${WORK_DIR}/ansible_collections/ns/col"
+# rename the sanity ignore file to match the current ansible version and update import ignores with the python version
+ansible_version="$(python -c 'import ansible.release; print(".".join(ansible.release.__version__.split(".")[:2]))')"
+sed "s/ import$/ import-${ANSIBLE_TEST_PYTHON_VERSION}/;" < "tests/sanity/ignore.txt" > "tests/sanity/ignore-${ansible_version}.txt"
+
# common args for all tests
# each test will be run in a separate venv to verify that requirements have been properly specified
common=(--venv --python "${ANSIBLE_TEST_PYTHON_VERSION}" --color --truncate 0 "${@}")
diff --git a/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/roles/common_handlers/handlers/main.yml b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/roles/common_handlers/handlers/main.yml
index d9f73231..186368f5 100644
--- a/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/roles/common_handlers/handlers/main.yml
+++ b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/roles/common_handlers/handlers/main.yml
@@ -4,3 +4,24 @@
set_fact:
handler_counter: '{{ handler_counter|int + 1 }}'
failed_when: handler_counter|int > 1
+
+# The following handler contains the role name and should be callable as:
+# 'common_handlers test_fqcn_handler'
+# 'common_handlers : common_handlers test_fqcn_handler`
+# 'testns.testcoll.common_handlers : common_handlers test_fqcn_handler'
+- name: common_handlers test_fqcn_handler
+ set_fact:
+ handler_counter: '{{ handler_counter|int + 1}}'
+ failed_when: handler_counter|int > 2
+
+# The following handler starts with 'role name : ' and should _not_ be listed as:
+# 'common_handlers : common_handlers : test_fqcn_handler`
+# 'testns.testcoll.common_handlers : common_handlers : test_fqcn_handler'
+- name: 'common_handlers : test_fqcn_handler'
+ meta: noop
+
+# The following handler starts with 'fqcn : ' and should _not_ be listed as:
+# 'common_handlers : testns.testcoll.common_handlers : test_fqcn_handler`
+# 'testns.testcoll.common_handlers : testns.testcoll.common_handlers : test_fqcn_handler'
+- name: 'testns.testcoll.common_handlers : test_fqcn_handler'
+ meta: noop
diff --git a/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/roles/test_fqcn_handlers/tasks/main.yml b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/roles/test_fqcn_handlers/tasks/main.yml
index db8767d2..6eadb7c2 100644
--- a/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/roles/test_fqcn_handlers/tasks/main.yml
+++ b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/roles/test_fqcn_handlers/tasks/main.yml
@@ -1,7 +1,16 @@
-- debug:
+- name: Fire fqcn handler 1
+ debug:
msg: Fire fqcn handler
changed_when: true
notify:
- 'testns.testcoll.common_handlers : test_fqcn_handler'
- 'common_handlers : test_fqcn_handler'
- 'test_fqcn_handler'
+
+- debug:
+ msg: Fire fqcn handler with role name
+ changed_when: true
+ notify:
+ - 'testns.testcoll.common_handlers : common_handlers test_fqcn_handler'
+ - 'common_handlers : common_handlers test_fqcn_handler'
+ - 'common_handlers test_fqcn_handler'
diff --git a/test/integration/targets/collections_runtime_pythonpath/runme.sh b/test/integration/targets/collections_runtime_pythonpath/runme.sh
index 654104a1..41236e8b 100755
--- a/test/integration/targets/collections_runtime_pythonpath/runme.sh
+++ b/test/integration/targets/collections_runtime_pythonpath/runme.sh
@@ -5,7 +5,7 @@ set -eux -o pipefail
export PIP_DISABLE_PIP_VERSION_CHECK=1
-
+export ANSIBLE_TEST_PREFER_VENV=1
source virtualenv.sh
diff --git a/test/integration/targets/connection/test.sh b/test/integration/targets/connection/test.sh
index 18fb2b77..ad672e23 100755
--- a/test/integration/targets/connection/test.sh
+++ b/test/integration/targets/connection/test.sh
@@ -21,3 +21,5 @@ then
else
echo "SUCCESS: Connection vars not found"
fi
+
+ansible-playbook test_reset_connection.yml -i "${INVENTORY}" "$@"
diff --git a/test/integration/targets/connection/test_reset_connection.yml b/test/integration/targets/connection/test_reset_connection.yml
new file mode 100644
index 00000000..2f6cb8dc
--- /dev/null
+++ b/test/integration/targets/connection/test_reset_connection.yml
@@ -0,0 +1,5 @@
+- hosts: "{{ target_hosts }}"
+ gather_facts: no
+ tasks:
+ # https://github.com/ansible/ansible/issues/65812
+ - meta: reset_connection
diff --git a/test/integration/targets/copy/tasks/selinux.yml b/test/integration/targets/copy/tasks/selinux.yml
new file mode 100644
index 00000000..6bd3b04f
--- /dev/null
+++ b/test/integration/targets/copy/tasks/selinux.yml
@@ -0,0 +1,35 @@
+# Ensure that our logic for special filesystems works as intended
+# https://github.com/ansible/ansible/issues/70244
+- block:
+ - name: Install dosfstools
+ yum:
+ name: dosfstools
+ state: present
+
+ - name: Create a file to use for a fat16 filesystem
+ command: dd if=/dev/zero of=/fat16 bs=1024 count=10240
+
+ - name: mkfs.fat
+ command: mkfs.fat -F16 /fat16
+
+ - name: Mount it
+ command: mount /fat16 /mnt
+
+ - name: Copy a file to it
+ copy:
+ src: /etc/fstab
+ dest: /mnt/fstab
+ always:
+ - name: Unmount it
+ command: umount /mnt
+ ignore_errors: true
+
+ - name: Nuke /fat16
+ file:
+ path: /fat16
+ state: absent
+
+ - name: Uninstall dosfstools
+ yum:
+ name: dosfstools
+ state: absent
diff --git a/test/integration/targets/delegate_to/runme.sh b/test/integration/targets/delegate_to/runme.sh
index 697fc393..44059552 100755
--- a/test/integration/targets/delegate_to/runme.sh
+++ b/test/integration/targets/delegate_to/runme.sh
@@ -63,6 +63,7 @@ ansible-playbook has_hostvars.yml -i inventory -v "$@"
# test ansible_x_interpreter
# python
+export ANSIBLE_TEST_PREFER_VENV=1
source virtualenv.sh
(
cd "${OUTPUT_DIR}"/venv/bin
diff --git a/test/integration/targets/expect/aliases b/test/integration/targets/expect/aliases
index ca7c9128..7211b8d0 100644
--- a/test/integration/targets/expect/aliases
+++ b/test/integration/targets/expect/aliases
@@ -1,2 +1,3 @@
shippable/posix/group2
destructive
+needs/target/setup_pexpect
diff --git a/test/integration/targets/expect/tasks/main.yml b/test/integration/targets/expect/tasks/main.yml
index 7feaec4d..0c408d28 100644
--- a/test/integration/targets/expect/tasks/main.yml
+++ b/test/integration/targets/expect/tasks/main.yml
@@ -16,9 +16,8 @@
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
- name: Install test requirements
- pip:
- name: pexpect
- state: present
+ import_role:
+ name: setup_pexpect
- name: record the test_command file
set_fact: test_command_file={{output_dir | expanduser}}/test_command.py
diff --git a/test/integration/targets/filter_urls/runme.sh b/test/integration/targets/filter_urls/runme.sh
index f6460acb..9362a385 100755
--- a/test/integration/targets/filter_urls/runme.sh
+++ b/test/integration/targets/filter_urls/runme.sh
@@ -6,6 +6,7 @@ export ANSIBLE_ROLES_PATH=../
ansible-playbook runme.yml "$@"
+export ANSIBLE_TEST_PREFER_VENV=1
source virtualenv.sh
# This is necessary for installing Jinja 2.6. We need this because Jinja 2.6
diff --git a/test/integration/targets/groupby_filter/runme.sh b/test/integration/targets/groupby_filter/runme.sh
index e5099aa1..07894b0f 100755
--- a/test/integration/targets/groupby_filter/runme.sh
+++ b/test/integration/targets/groupby_filter/runme.sh
@@ -2,6 +2,7 @@
set -eux
+export ANSIBLE_TEST_PREFER_VENV=1
source virtualenv.sh
pip install -U jinja2==2.9.4
diff --git a/test/integration/targets/hash/runme.sh b/test/integration/targets/hash/runme.sh
index 9448e4e0..3689d83b 100755
--- a/test/integration/targets/hash/runme.sh
+++ b/test/integration/targets/hash/runme.sh
@@ -6,3 +6,6 @@ JSON_ARG='{"test_hash":{"extra_args":"this is an extra arg"}}'
ANSIBLE_HASH_BEHAVIOUR=replace ansible-playbook test_hash.yml -i ../../inventory -v "$@" -e "${JSON_ARG}"
ANSIBLE_HASH_BEHAVIOUR=merge ansible-playbook test_hash.yml -i ../../inventory -v "$@" -e "${JSON_ARG}"
+
+ANSIBLE_HASH_BEHAVIOUR=replace ansible-playbook test_inventory_hash.yml -i test_inv1.yml -i test_inv2.yml -v "$@"
+ANSIBLE_HASH_BEHAVIOUR=merge ansible-playbook test_inventory_hash.yml -i test_inv1.yml -i test_inv2.yml -v "$@"
diff --git a/test/integration/targets/hash/test_inv1.yml b/test/integration/targets/hash/test_inv1.yml
new file mode 100644
index 00000000..02bd017f
--- /dev/null
+++ b/test/integration/targets/hash/test_inv1.yml
@@ -0,0 +1,10 @@
+all:
+ hosts:
+ host1:
+ test_inventory_host_hash:
+ host_var1: "inventory 1"
+ host_var2: "inventory 1"
+ vars:
+ test_inventory_group_hash:
+ group_var1: "inventory 1"
+ group_var2: "inventory 1"
diff --git a/test/integration/targets/hash/test_inv2.yml b/test/integration/targets/hash/test_inv2.yml
new file mode 100644
index 00000000..6529b933
--- /dev/null
+++ b/test/integration/targets/hash/test_inv2.yml
@@ -0,0 +1,8 @@
+all:
+ hosts:
+ host1:
+ test_inventory_host_hash:
+ host_var1: "inventory 2"
+ vars:
+ test_inventory_group_hash:
+ group_var1: "inventory 2"
diff --git a/test/integration/targets/hash/test_inventory_hash.yml b/test/integration/targets/hash/test_inventory_hash.yml
new file mode 100644
index 00000000..1091b135
--- /dev/null
+++ b/test/integration/targets/hash/test_inventory_hash.yml
@@ -0,0 +1,41 @@
+---
+- hosts: localhost
+ gather_facts: no
+ vars:
+ host_hash_merged: {'host_var1': 'inventory 2', 'host_var2': 'inventory 1'}
+ host_hash_replaced: {'host_var1': 'inventory 2'}
+ group_hash_merged: {'group_var1': 'inventory 2', 'group_var2': 'inventory 1'}
+ group_hash_replaced: {'group_var1': 'inventory 2'}
+ tasks:
+
+ - name: debug hash behaviour result
+ debug:
+ var: "{{ lookup('env', 'ANSIBLE_HASH_BEHAVIOUR') }}"
+ verbosity: 2
+
+ - name: assert hash behaviour is merge or replace
+ assert:
+ that:
+ - lookup('env', 'ANSIBLE_HASH_BEHAVIOUR') in ('merge', 'replace')
+
+ - name: debug test_inventory_host_hash
+ debug:
+ var: hostvars['host1']['test_inventory_host_hash']
+ verbosity: 2
+
+ - name: debug test_inventory_group_hash
+ debug:
+ var: test_inventory_group_hash
+ verbosity: 2
+
+ - assert:
+ that:
+ - hostvars['host1']['test_inventory_host_hash'] == host_hash_replaced
+ - test_inventory_group_hash == group_hash_replaced
+ when: "lookup('env', 'ANSIBLE_HASH_BEHAVIOUR') == 'replace'"
+
+ - assert:
+ that:
+ - hostvars['host1']['test_inventory_host_hash'] == host_hash_merged
+ - test_inventory_group_hash == group_hash_merged
+ when: "lookup('env', 'ANSIBLE_HASH_BEHAVIOUR') == 'merge'"
diff --git a/test/integration/targets/incidental_inventory_docker_swarm/runme.sh b/test/integration/targets/incidental_inventory_docker_swarm/runme.sh
index e2ba6869..b93d386a 100755
--- a/test/integration/targets/incidental_inventory_docker_swarm/runme.sh
+++ b/test/integration/targets/incidental_inventory_docker_swarm/runme.sh
@@ -8,7 +8,6 @@ cleanup() {
echo "Cleanup"
ansible-playbook playbooks/swarm_cleanup.yml
echo "Done"
- exit 0
}
trap cleanup INT TERM EXIT
diff --git a/test/integration/targets/lookup_password/runme.sh b/test/integration/targets/lookup_password/runme.sh
index a3637a7e..ac2c1704 100755
--- a/test/integration/targets/lookup_password/runme.sh
+++ b/test/integration/targets/lookup_password/runme.sh
@@ -2,6 +2,7 @@
set -eux
+export ANSIBLE_TEST_PREFER_VENV=1
source virtualenv.sh
# Requirements have to be installed prior to running ansible-playbook
diff --git a/test/integration/targets/noexec/aliases b/test/integration/targets/noexec/aliases
new file mode 100644
index 00000000..66a77c7b
--- /dev/null
+++ b/test/integration/targets/noexec/aliases
@@ -0,0 +1,3 @@
+shippable/posix/group2
+skip/docker
+skip/macos
diff --git a/test/integration/targets/noexec/inventory b/test/integration/targets/noexec/inventory
new file mode 100644
index 00000000..ab9b62c8
--- /dev/null
+++ b/test/integration/targets/noexec/inventory
@@ -0,0 +1 @@
+not_empty # avoid empty empty hosts list warning without defining explicit localhost
diff --git a/test/integration/targets/noexec/runme.sh b/test/integration/targets/noexec/runme.sh
new file mode 100755
index 00000000..ff706558
--- /dev/null
+++ b/test/integration/targets/noexec/runme.sh
@@ -0,0 +1,9 @@
+#!/usr/bin/env bash
+
+set -eux
+
+trap 'umount "${OUTPUT_DIR}/ramdisk"' EXIT
+
+mkdir "${OUTPUT_DIR}/ramdisk"
+mount -t tmpfs -o size=32m,noexec,rw tmpfs "${OUTPUT_DIR}/ramdisk"
+ANSIBLE_REMOTE_TMP="${OUTPUT_DIR}/ramdisk" ansible-playbook -i inventory "$@" test-noexec.yml
diff --git a/test/integration/targets/noexec/test-noexec.yml b/test/integration/targets/noexec/test-noexec.yml
new file mode 100644
index 00000000..3c7d756b
--- /dev/null
+++ b/test/integration/targets/noexec/test-noexec.yml
@@ -0,0 +1,8 @@
+- hosts: localhost
+ gather_facts: false
+ tasks:
+ - ping:
+
+ - command: sleep 1
+ async: 2
+ poll: 1
diff --git a/test/integration/targets/old_style_cache_plugins/runme.sh b/test/integration/targets/old_style_cache_plugins/runme.sh
index 13911bd5..86d2433b 100755
--- a/test/integration/targets/old_style_cache_plugins/runme.sh
+++ b/test/integration/targets/old_style_cache_plugins/runme.sh
@@ -2,6 +2,7 @@
set -eux
+export ANSIBLE_TEST_PREFER_VENV=1
source virtualenv.sh
# Run test if dependencies are installed
diff --git a/test/integration/targets/pip/tasks/main.yml b/test/integration/targets/pip/tasks/main.yml
index 05879c18..c0a36c43 100644
--- a/test/integration/targets/pip/tasks/main.yml
+++ b/test/integration/targets/pip/tasks/main.yml
@@ -1,20 +1,30 @@
# Current pip unconditionally uses md5.
# We can re-enable if pip switches to a different hash or allows us to not check md5.
-- name: find virtualenv command
- command: "which virtualenv virtualenv-{{ ansible_python.version.major }}.{{ ansible_python.version.minor }}"
- register: command
- ignore_errors: true
+- name: Python 2
+ when: ansible_python.version.major == 2
+ block:
+ - name: find virtualenv command
+ command: "which virtualenv virtualenv-{{ ansible_python.version.major }}.{{ ansible_python.version.minor }}"
+ register: command
+ ignore_errors: true
-- name: is virtualenv available to python -m
- command: '{{ ansible_python_interpreter }} -m virtualenv'
- register: python_m
- when: not command.stdout_lines
- failed_when: python_m.rc != 2
+ - name: is virtualenv available to python -m
+ command: '{{ ansible_python_interpreter }} -m virtualenv'
+ register: python_m
+ when: not command.stdout_lines
+ failed_when: python_m.rc != 2
-- name: remember selected virtualenv command
- set_fact:
- virtualenv: "{{ command.stdout_lines[0] if command is successful else ansible_python_interpreter ~ ' -m virtualenv' }}"
+ - name: remember selected virtualenv command
+ set_fact:
+ virtualenv: "{{ command.stdout_lines[0] if command is successful else ansible_python_interpreter ~ ' -m virtualenv' }}"
+
+- name: Python 3+
+ when: ansible_python.version.major > 2
+ block:
+ - name: remember selected virtualenv command
+ set_fact:
+ virtualenv: "{{ ansible_python_interpreter ~ ' -m venv' }}"
- block:
- name: install git, needed for repo installs
diff --git a/test/integration/targets/pip/tasks/pip.yml b/test/integration/targets/pip/tasks/pip.yml
index 6281bbe8..572c7b6f 100644
--- a/test/integration/targets/pip/tasks/pip.yml
+++ b/test/integration/targets/pip/tasks/pip.yml
@@ -521,7 +521,7 @@
### test virtualenv_command begin ###
- name: Test virtualenv command with arguments
- when: "ansible_system == 'Linux'"
+ when: ansible_python.version.major == 2
block:
- name: make sure the virtualenv does not exist
file:
@@ -533,7 +533,7 @@
pip:
name: "{{ pip_test_package }}"
virtualenv: "{{ output_dir }}/pipenv"
- virtualenv_command: "virtualenv --verbose"
+ virtualenv_command: "{{ command.stdout_lines[0] | basename }} --verbose"
state: present
register: version13
diff --git a/test/integration/targets/setup_paramiko/install-MacOSX-10-python-3.yml b/test/integration/targets/setup_paramiko/install-Darwin-python-3.yml
index a156f806..a156f806 100644
--- a/test/integration/targets/setup_paramiko/install-MacOSX-10-python-3.yml
+++ b/test/integration/targets/setup_paramiko/install-Darwin-python-3.yml
diff --git a/test/integration/targets/setup_paramiko/install.yml b/test/integration/targets/setup_paramiko/install.yml
index 194bd51f..e98abe33 100644
--- a/test/integration/targets/setup_paramiko/install.yml
+++ b/test/integration/targets/setup_paramiko/install.yml
@@ -13,5 +13,6 @@
with_first_found:
- "install-{{ ansible_distribution }}-{{ ansible_distribution_major_version }}-python-{{ ansible_python.version.major }}.yml"
- "install-{{ ansible_os_family }}-{{ ansible_distribution_major_version }}-python-{{ ansible_python.version.major }}.yml"
+ - "install-{{ ansible_os_family }}-python-{{ ansible_python.version.major }}.yml"
- "install-python-{{ ansible_python.version.major }}.yml"
- "install-fail.yml"
diff --git a/test/integration/targets/setup_paramiko/setup.sh b/test/integration/targets/setup_paramiko/setup.sh
index 64b935cd..8c4f6f1c 100644
--- a/test/integration/targets/setup_paramiko/setup.sh
+++ b/test/integration/targets/setup_paramiko/setup.sh
@@ -3,6 +3,7 @@
set -eux
+export ANSIBLE_TEST_PREFER_VENV=1
source virtualenv.sh # for pip installs, if needed, otherwise unused
ansible-playbook ../setup_paramiko/install.yml -i ../setup_paramiko/inventory "$@"
trap 'ansible-playbook ../setup_paramiko/uninstall.yml -i ../setup_paramiko/inventory "$@"' EXIT
diff --git a/test/integration/targets/setup_paramiko/uninstall-MacOSX-10-python-3.yml b/test/integration/targets/setup_paramiko/uninstall-Darwin-python-3.yml
index 69a68e42..69a68e42 100644
--- a/test/integration/targets/setup_paramiko/uninstall-MacOSX-10-python-3.yml
+++ b/test/integration/targets/setup_paramiko/uninstall-Darwin-python-3.yml
diff --git a/test/integration/targets/setup_paramiko/uninstall.yml b/test/integration/targets/setup_paramiko/uninstall.yml
index 46a16d91..48ff68e6 100644
--- a/test/integration/targets/setup_paramiko/uninstall.yml
+++ b/test/integration/targets/setup_paramiko/uninstall.yml
@@ -10,6 +10,7 @@
with_first_found:
- "uninstall-{{ ansible_distribution }}-{{ ansible_distribution_major_version }}-python-{{ ansible_python.version.major }}.yml"
- "uninstall-{{ ansible_os_family }}-{{ ansible_distribution_major_version }}-python-{{ ansible_python.version.major }}.yml"
+ - "uninstall-{{ ansible_os_family }}-python-{{ ansible_python.version.major }}.yml"
- "uninstall-{{ ansible_pkg_mgr }}-python-{{ ansible_python.version.major }}.yml"
- "uninstall-{{ ansible_pkg_mgr }}.yml"
- "uninstall-fail.yml"
diff --git a/test/integration/targets/setup_pexpect/files/constraints.txt b/test/integration/targets/setup_pexpect/files/constraints.txt
new file mode 100644
index 00000000..c78ecdad
--- /dev/null
+++ b/test/integration/targets/setup_pexpect/files/constraints.txt
@@ -0,0 +1,2 @@
+pexpect == 4.8.0
+ptyprocess < 0.7.0 ; python_version < '2.7' # ptyprocess >= 0.7.0 not compatible with Python 2.6
diff --git a/test/integration/targets/setup_pexpect/meta/main.yml b/test/integration/targets/setup_pexpect/meta/main.yml
new file mode 100644
index 00000000..1810d4be
--- /dev/null
+++ b/test/integration/targets/setup_pexpect/meta/main.yml
@@ -0,0 +1,2 @@
+dependencies:
+ - setup_remote_tmp_dir
diff --git a/test/integration/targets/setup_pexpect/tasks/main.yml b/test/integration/targets/setup_pexpect/tasks/main.yml
index ef57fe6f..690fe441 100644
--- a/test/integration/targets/setup_pexpect/tasks/main.yml
+++ b/test/integration/targets/setup_pexpect/tasks/main.yml
@@ -1,4 +1,10 @@
+- name: Copy constraints file
+ copy:
+ src: constraints.txt
+ dest: "{{ remote_tmp_dir }}/pexpect-constraints.txt"
+
- name: Install pexpect
pip:
name: pexpect
+ extra_args: '--constraint "{{ remote_tmp_dir }}/pexpect-constraints.txt"'
state: present
diff --git a/test/integration/targets/systemd/handlers/main.yml b/test/integration/targets/systemd/handlers/main.yml
new file mode 100644
index 00000000..8643a2a0
--- /dev/null
+++ b/test/integration/targets/systemd/handlers/main.yml
@@ -0,0 +1,4 @@
+- name: remove unit file
+ file:
+ path: /etc/systemd/system/sleeper@.service
+ state: absent
diff --git a/test/integration/targets/systemd/tasks/main.yml b/test/integration/targets/systemd/tasks/main.yml
index 867a554d..96781eb8 100644
--- a/test/integration/targets/systemd/tasks/main.yml
+++ b/test/integration/targets/systemd/tasks/main.yml
@@ -20,70 +20,73 @@
## systemctl
##
-- name: check for systemctl command
- shell: which systemctl
- failed_when: False
- register: systemctl_check
-
-- meta: end_host
- when: systemctl_check.rc != 0
-
-- set_fact:
- ssh_service: '{{ "ssh" if ansible_os_family == "Debian" else "sshd" }}'
-
-- block:
- - name: get a list of running services
- shell: systemctl | fgrep 'running' | awk '{print $1}' | sed 's/\.service//g' | fgrep -v '.' | egrep ^[a-z]
- register: running_names
- - debug: var=running_names
-
- - name: check running state
- systemd:
- name: "{{ running_names.stdout_lines|random }}"
- state: started
- register: systemd_test0
- - debug: var=systemd_test0
- - name: validate results for test0
- assert:
- that:
- - 'systemd_test0.changed is defined'
- - 'systemd_test0.name is defined'
- - 'systemd_test0.state is defined'
- - 'systemd_test0.status is defined'
- - 'not systemd_test0.changed'
- - 'systemd_test0.state == "started"'
-
- - name: the module must fail when a service is not found
- systemd:
- name: '{{ fake_service }}'
- state: stopped
- register: result
- ignore_errors: yes
-
- - assert:
- that:
- - result is failed
- - 'result is search("Could not find the requested service {{ fake_service }}")'
-
- - name: the module must fail in check_mode as well when a service is not found
- systemd:
- name: '{{ fake_service }}'
- state: stopped
- register: result
- check_mode: yes
- ignore_errors: yes
-
- - assert:
- that:
- - result is failed
- - 'result is search("Could not find the requested service {{ fake_service }}")'
-
- - name: check that the module works even when systemd is offline (eg in chroot)
- systemd:
- name: "{{ running_names.stdout_lines|random }}"
- state: started
- environment:
- SYSTEMD_OFFLINE: 1
+- name: End if this system does not use systemd
+ meta: end_host
+ when: ansible_facts.service_mgr != 'systemd'
+
+- name: Include distribution specific variables
+ include_vars: "{{ lookup('first_found', params) }}"
+ vars:
+ params:
+ files:
+ - "{{ ansible_facts.distribution }}.yml"
+ - "{{ ansible_facts.os_family }}.yml"
+ - default.yml
+ paths:
+ - vars
+
+- name: get a list of running services
+ shell: systemctl | fgrep 'running' | awk '{print $1}' | sed 's/\.service//g' | fgrep -v '.' | egrep ^[a-z]
+ register: running_names
+- debug: var=running_names
+
+- name: check running state
+ systemd:
+ name: "{{ running_names.stdout_lines|random }}"
+ state: started
+ register: systemd_test0
+- debug: var=systemd_test0
+- name: validate results for test0
+ assert:
+ that:
+ - 'systemd_test0.changed is defined'
+ - 'systemd_test0.name is defined'
+ - 'systemd_test0.state is defined'
+ - 'systemd_test0.status is defined'
+ - 'not systemd_test0.changed'
+ - 'systemd_test0.state == "started"'
+
+- name: the module must fail when a service is not found
+ systemd:
+ name: '{{ fake_service }}'
+ state: stopped
+ register: result
+ ignore_errors: yes
+
+- assert:
+ that:
+ - result is failed
+ - 'result is search("Could not find the requested service {{ fake_service }}")'
+
+- name: the module must fail in check_mode as well when a service is not found
+ systemd:
+ name: '{{ fake_service }}'
+ state: stopped
+ register: result
+ check_mode: yes
+ ignore_errors: yes
+
+- assert:
+ that:
+ - result is failed
+ - 'result is search("Could not find the requested service {{ fake_service }}")'
+
+- name: check that the module works even when systemd is offline (eg in chroot)
+ systemd:
+ name: "{{ running_names.stdout_lines|random }}"
+ state: started
+ environment:
+ SYSTEMD_OFFLINE: 1
- name: Disable ssh 1
systemd:
@@ -114,3 +117,5 @@
- systemd_disable_ssh_2 is not changed
- systemd_enable_ssh_1 is changed
- systemd_enable_ssh_2 is not changed
+
+- import_tasks: test_unit_template.yml
diff --git a/test/integration/targets/systemd/tasks/test_unit_template.yml b/test/integration/targets/systemd/tasks/test_unit_template.yml
new file mode 100644
index 00000000..47cb1c78
--- /dev/null
+++ b/test/integration/targets/systemd/tasks/test_unit_template.yml
@@ -0,0 +1,50 @@
+- name: Copy service file
+ template:
+ src: sleeper@.service
+ dest: /etc/systemd/system/sleeper@.service
+ owner: root
+ group: root
+ mode: '0644'
+ notify: remove unit file
+
+- name: Reload systemd
+ systemd:
+ daemon_reload: yes
+
+- name: Start and enable service using unit template
+ systemd:
+ name: sleeper@100.service
+ state: started
+ enabled: yes
+ register: template_test_1
+
+- name: Start and enable service using unit template again
+ systemd:
+ name: sleeper@100.service
+ state: started
+ enabled: yes
+ register: template_test_2
+
+- name: Stop and disable service using unit template
+ systemd:
+ name: sleeper@100.service
+ state: stopped
+ enabled: no
+ register: template_test_3
+
+- name: Stop and disable service using unit template again
+ systemd:
+ name: sleeper@100.service
+ state: stopped
+ enabled: no
+ register: template_test_4
+
+- name:
+ assert:
+ that:
+ - template_test_1 is changed
+ - template_test_1 is success
+ - template_test_2 is not changed
+ - template_test_2 is success
+ - template_test_3 is changed
+ - template_test_4 is not changed
diff --git a/test/integration/targets/systemd/templates/sleeper@.service b/test/integration/targets/systemd/templates/sleeper@.service
new file mode 100644
index 00000000..8b47982a
--- /dev/null
+++ b/test/integration/targets/systemd/templates/sleeper@.service
@@ -0,0 +1,8 @@
+[Unit]
+Description=Basic service to use as a template
+
+[Service]
+ExecStart={{ sleep_bin_path }} %i
+
+[Install]
+WantedBy=multi-user.target
diff --git a/test/integration/targets/systemd/vars/Debian.yml b/test/integration/targets/systemd/vars/Debian.yml
new file mode 100644
index 00000000..9760744d
--- /dev/null
+++ b/test/integration/targets/systemd/vars/Debian.yml
@@ -0,0 +1,2 @@
+ssh_service: ssh
+sleep_bin_path: /bin/sleep
diff --git a/test/integration/targets/systemd/vars/default.yml b/test/integration/targets/systemd/vars/default.yml
new file mode 100644
index 00000000..57491ff0
--- /dev/null
+++ b/test/integration/targets/systemd/vars/default.yml
@@ -0,0 +1,2 @@
+ssh_service: sshd
+sleep_bin_path: /usr/bin/sleep
diff --git a/test/integration/targets/template_jinja2_latest/runme.sh b/test/integration/targets/template_jinja2_latest/runme.sh
index 6a20eb5d..d6a09677 100755
--- a/test/integration/targets/template_jinja2_latest/runme.sh
+++ b/test/integration/targets/template_jinja2_latest/runme.sh
@@ -2,6 +2,7 @@
set -eux
+export ANSIBLE_TEST_PREFER_VENV=1
source virtualenv.sh
pip install -U -r requirements.txt
diff --git a/test/integration/targets/user/tasks/main.yml b/test/integration/targets/user/tasks/main.yml
index 19b12742..3b8ff377 100644
--- a/test/integration/targets/user/tasks/main.yml
+++ b/test/integration/targets/user/tasks/main.yml
@@ -17,1120 +17,18 @@
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
#
-## user add
-
-- name: remove the test user
- user:
- name: ansibulluser
- state: absent
-
-- name: try to create a user
- user:
- name: ansibulluser
- state: present
- register: user_test0_0
-
-- name: create the user again
- user:
- name: ansibulluser
- state: present
- register: user_test0_1
-
-- debug:
- var: user_test0
- verbosity: 2
-
-- name: make a list of users
- script: userlist.sh {{ ansible_facts.distribution }}
- register: user_names
-
-- debug:
- var: user_names
- verbosity: 2
-
-- name: validate results for testcase 0
- assert:
- that:
- - user_test0_0 is changed
- - user_test0_1 is not changed
- - '"ansibulluser" in user_names.stdout_lines'
-
-# create system user
-
-- name: remove user
- user:
- name: ansibulluser
- state: absent
-
-- name: create system user
- user:
- name: ansibulluser
- state: present
- system: yes
-
-# test adding user with uid
-# https://github.com/ansible/ansible/issues/62969
-- name: remove the test user
- user:
- name: ansibulluser
- state: absent
-
-- name: try to create a user with uid
- user:
- name: ansibulluser
- state: present
- uid: 572
- register: user_test01_0
-
-- name: create the user again
- user:
- name: ansibulluser
- state: present
- uid: 572
- register: user_test01_1
-
-- name: validate results for testcase 0
- assert:
- that:
- - user_test01_0 is changed
- - user_test01_1 is not changed
-
-# test user add with password
-- name: add an encrypted password for user
- user:
- name: ansibulluser
- password: "$6$rounds=656000$TT4O7jz2M57npccl$33LF6FcUMSW11qrESXL1HX0BS.bsiT6aenFLLiVpsQh6hDtI9pJh5iY7x8J7ePkN4fP8hmElidHXaeD51pbGS."
- state: present
- update_password: always
- register: test_user_encrypt0
-
-- name: there should not be warnings
- assert:
- that: "'warnings' not in test_user_encrypt0"
-
-# https://github.com/ansible/ansible/issues/65711
-- name: Test updating password only on creation
- user:
- name: ansibulluser
- password: '*'
- update_password: on_create
- register: test_user_update_password
-
-- name: Ensure password was not changed
- assert:
- that:
- - test_user_update_password is not changed
-
-- name: Verify password hash for Linux
- when: ansible_facts.os_family in ['RedHat', 'Debian', 'Suse']
- block:
- - name: LINUX | Get shadow entry for ansibulluser
- getent:
- database: shadow
- key: ansibulluser
-
- - name: LINUX | Ensure password hash was not removed
- assert:
- that:
- - getent_shadow['ansibulluser'][1] != '*'
-
-- block:
- - name: add an plaintext password for user
- user:
- name: ansibulluser
- password: "plaintextpassword"
- state: present
- update_password: always
- register: test_user_encrypt1
-
- - name: there should be a warning complains that the password is plaintext
- assert:
- that: "'warnings' in test_user_encrypt1"
-
- - name: add an invalid hashed password
- user:
- name: ansibulluser
- password: "$6$rounds=656000$tgK3gYTyRLUmhyv2$lAFrYUQwn7E6VsjPOwQwoSx30lmpiU9r/E0Al7tzKrR9mkodcMEZGe9OXD0H/clOn6qdsUnaL4zefy5fG+++++"
- state: present
- update_password: always
- register: test_user_encrypt2
-
- - name: there should be a warning complains about the character set of password
- assert:
- that: "'warnings' in test_user_encrypt2"
-
- - name: change password to '!'
- user:
- name: ansibulluser
- password: '!'
- register: test_user_encrypt3
-
- - name: change password to '*'
- user:
- name: ansibulluser
- password: '*'
- register: test_user_encrypt4
-
- - name: change password to '*************'
- user:
- name: ansibulluser
- password: '*************'
- register: test_user_encrypt5
-
- - name: there should be no warnings when setting the password to '!', '*' or '*************'
- assert:
- that:
- - "'warnings' not in test_user_encrypt3"
- - "'warnings' not in test_user_encrypt4"
- - "'warnings' not in test_user_encrypt5"
- when: ansible_facts.system != 'Darwin'
-
-
-# https://github.com/ansible/ansible/issues/42484
-# Skipping macOS for now since there is a bug when changing home directory
-- block:
- - name: create user specifying home
- user:
- name: ansibulluser
- state: present
- home: "{{ user_home_prefix[ansible_facts.system] }}/ansibulluser"
- register: user_test3_0
-
- - name: create user again specifying home
- user:
- name: ansibulluser
- state: present
- home: "{{ user_home_prefix[ansible_facts.system] }}/ansibulluser"
- register: user_test3_1
-
- - name: change user home
- user:
- name: ansibulluser
- state: present
- home: "{{ user_home_prefix[ansible_facts.system] }}/ansibulluser-mod"
- register: user_test3_2
-
- - name: change user home back
- user:
- name: ansibulluser
- state: present
- home: "{{ user_home_prefix[ansible_facts.system] }}/ansibulluser"
- register: user_test3_3
-
- - name: validate results for testcase 3
- assert:
- that:
- - user_test3_0 is not changed
- - user_test3_1 is not changed
- - user_test3_2 is changed
- - user_test3_3 is changed
- when: ansible_facts.system != 'Darwin'
-
-# https://github.com/ansible/ansible/issues/41393
-# Create a new user account with a path that has parent directories that do not exist
-- name: Create user with home path that has parents that do not exist
- user:
- name: ansibulluser2
- state: present
- home: "{{ user_home_prefix[ansible_facts.system] }}/in2deep/ansibulluser2"
- register: create_home_with_no_parent_1
-
-- name: Create user with home path that has parents that do not exist again
- user:
- name: ansibulluser2
- state: present
- home: "{{ user_home_prefix[ansible_facts.system] }}/in2deep/ansibulluser2"
- register: create_home_with_no_parent_2
-
-- name: Check the created home directory
- stat:
- path: "{{ user_home_prefix[ansible_facts.system] }}/in2deep/ansibulluser2"
- register: home_with_no_parent_3
-
-- name: Ensure user with non-existing parent paths was created successfully
- assert:
- that:
- - create_home_with_no_parent_1 is changed
- - create_home_with_no_parent_1.home == user_home_prefix[ansible_facts.system] ~ '/in2deep/ansibulluser2'
- - create_home_with_no_parent_2 is not changed
- - home_with_no_parent_3.stat.uid == create_home_with_no_parent_1.uid
- - home_with_no_parent_3.stat.gr_name == default_user_group[ansible_facts.distribution] | default('ansibulluser2')
-
-- name: Cleanup test account
- user:
- name: ansibulluser2
- home: "{{ user_home_prefix[ansible_facts.system] }}/in2deep/ansibulluser2"
- state: absent
- remove: yes
-
-- name: Remove testing dir
- file:
- path: "{{ user_home_prefix[ansible_facts.system] }}/in2deep/"
- state: absent
-
-
-# https://github.com/ansible/ansible/issues/60307
-# Make sure we can create a user when the home directory is missing
-- name: Create user with home path that does not exist
- user:
- name: ansibulluser3
- state: present
- home: "{{ user_home_prefix[ansible_facts.system] }}/nosuchdir"
- createhome: no
-
-- name: Cleanup test account
- user:
- name: ansibulluser3
- state: absent
- remove: yes
-
-# https://github.com/ansible/ansible/issues/70589
-# Create user with create_home: no and parent directory does not exist.
-- name: "Check if parent dir for home dir for user exists (before)"
- stat:
- path: "{{ user_home_prefix[ansible_facts.system] }}/thereisnodir"
- register: create_user_no_create_home_with_no_parent_parent_dir_before
-
-- name: "Create user with create_home == no and home path parent dir does not exist"
- user:
- name: randomuser
- state: present
- create_home: false
- home: "{{ user_home_prefix[ansible_facts.system] }}/thereisnodir/randomuser"
- register: create_user_no_create_home_with_no_parent
-
-- name: "Check if parent dir for home dir for user exists (after)"
- stat:
- path: "{{ user_home_prefix[ansible_facts.system] }}/thereisnodir"
- register: create_user_no_create_home_with_no_parent_parent_dir_after
-
-- name: "Check if home for user is created"
- stat:
- path: "{{ user_home_prefix[ansible_facts.system] }}/thereisnodir/randomuser"
- register: create_user_no_create_home_with_no_parent_home_dir
-
-- name: "Ensure user with non-existing parent paths with create_home: no was created successfully"
- assert:
- that:
- - not create_user_no_create_home_with_no_parent_parent_dir_before.stat.exists
- - not create_user_no_create_home_with_no_parent_parent_dir_after.stat.isdir is defined
- - not create_user_no_create_home_with_no_parent_home_dir.stat.exists
-
-- name: Cleanup test account
- user:
- name: randomuser
- state: absent
- remove: yes
-
-## user check
-
-- name: run existing user check tests
- user:
- name: "{{ user_names.stdout_lines | random }}"
- state: present
- create_home: no
- loop: "{{ range(1, 5+1) | list }}"
- register: user_test1
-
-- debug:
- var: user_test1
- verbosity: 2
-
-- name: validate results for testcase 1
- assert:
- that:
- - user_test1.results is defined
- - user_test1.results | length == 5
-
-- name: validate changed results for testcase 1
- assert:
- that:
- - "user_test1.results[0] is not changed"
- - "user_test1.results[1] is not changed"
- - "user_test1.results[2] is not changed"
- - "user_test1.results[3] is not changed"
- - "user_test1.results[4] is not changed"
- - "user_test1.results[0]['state'] == 'present'"
- - "user_test1.results[1]['state'] == 'present'"
- - "user_test1.results[2]['state'] == 'present'"
- - "user_test1.results[3]['state'] == 'present'"
- - "user_test1.results[4]['state'] == 'present'"
-
-
-## user remove
-
-- name: try to delete the user
- user:
- name: ansibulluser
- state: absent
- force: true
- register: user_test2
-
-- name: make a new list of users
- script: userlist.sh {{ ansible_facts.distribution }}
- register: user_names2
-
-- debug:
- var: user_names2
- verbosity: 2
-
-- name: validate results for testcase 2
- assert:
- that:
- - '"ansibulluser" not in user_names2.stdout_lines'
-
-
-## create user without home and test fallback home dir create
-
-- block:
- - name: create the user
- user:
- name: ansibulluser
-
- - name: delete the user and home dir
- user:
- name: ansibulluser
- state: absent
- force: true
- remove: true
-
- - name: create the user without home
- user:
- name: ansibulluser
- create_home: no
-
- - name: create the user home dir
- user:
- name: ansibulluser
- register: user_create_home_fallback
-
- - name: stat home dir
- stat:
- path: '{{ user_create_home_fallback.home }}'
- register: user_create_home_fallback_dir
-
- - name: read UMASK from /etc/login.defs and return mode
- shell: |
- import re
- import os
- try:
- for line in open('/etc/login.defs').readlines():
- m = re.match(r'^UMASK\s+(\d+)$', line)
- if m:
- umask = int(m.group(1), 8)
- except:
- umask = os.umask(0)
- mode = oct(0o777 & ~umask)
- print(str(mode).replace('o', ''))
- args:
- executable: "{{ ansible_python_interpreter }}"
- register: user_login_defs_umask
-
- - name: validate that user home dir is created
- assert:
- that:
- - user_create_home_fallback is changed
- - user_create_home_fallback_dir.stat.exists
- - user_create_home_fallback_dir.stat.isdir
- - user_create_home_fallback_dir.stat.pw_name == 'ansibulluser'
- - user_create_home_fallback_dir.stat.mode == user_login_defs_umask.stdout
- when: ansible_facts.system != 'Darwin'
-
-- block:
- - name: create non-system user on macOS to test the shell is set to /bin/bash
- user:
- name: macosuser
- register: macosuser_output
-
- - name: validate the shell is set to /bin/bash
- assert:
- that:
- - 'macosuser_output.shell == "/bin/bash"'
-
- - name: cleanup
- user:
- name: macosuser
- state: absent
-
- - name: create system user on macos to test the shell is set to /usr/bin/false
- user:
- name: macosuser
- system: yes
- register: macosuser_output
-
- - name: validate the shell is set to /usr/bin/false
- assert:
- that:
- - 'macosuser_output.shell == "/usr/bin/false"'
-
- - name: cleanup
- user:
- name: macosuser
- state: absent
-
- - name: create non-system user on macos and set the shell to /bin/sh
- user:
- name: macosuser
- shell: /bin/sh
- register: macosuser_output
-
- - name: validate the shell is set to /bin/sh
- assert:
- that:
- - 'macosuser_output.shell == "/bin/sh"'
-
- - name: cleanup
- user:
- name: macosuser
- state: absent
- when: ansible_facts.distribution == "MacOSX"
-
-
-## user expires
-# Date is March 3, 2050
-- name: Set user expiration
- user:
- name: ansibulluser
- state: present
- expires: 2529881062
- register: user_test_expires1
- tags:
- - timezone
-
-- name: Set user expiration again to ensure no change is made
- user:
- name: ansibulluser
- state: present
- expires: 2529881062
- register: user_test_expires2
- tags:
- - timezone
-
-- name: Ensure that account with expiration was created and did not change on subsequent run
- assert:
- that:
- - user_test_expires1 is changed
- - user_test_expires2 is not changed
-
-- name: Verify expiration date for Linux
- block:
- - name: LINUX | Get expiration date for ansibulluser
- getent:
- database: shadow
- key: ansibulluser
-
- - name: LINUX | Ensure proper expiration date was set
- assert:
- that:
- - getent_shadow['ansibulluser'][6] == '29281'
- when: ansible_facts.os_family in ['RedHat', 'Debian', 'Suse']
-
-
-- name: Verify expiration date for BSD
- block:
- - name: BSD | Get expiration date for ansibulluser
- shell: 'grep ansibulluser /etc/master.passwd | cut -d: -f 7'
- changed_when: no
- register: bsd_account_expiration
-
- - name: BSD | Ensure proper expiration date was set
- assert:
- that:
- - bsd_account_expiration.stdout == '2529881062'
- when: ansible_facts.os_family == 'FreeBSD'
-
-- name: Change timezone
- timezone:
- name: America/Denver
- register: original_timezone
- tags:
- - timezone
-
-- name: Change system timezone to make sure expiration comparison works properly
- block:
- - name: Create user with expiration again to ensure no change is made in a new timezone
- user:
- name: ansibulluser
- state: present
- expires: 2529881062
- register: user_test_different_tz
- tags:
- - timezone
-
- - name: Ensure that no change was reported
- assert:
- that:
- - user_test_different_tz is not changed
- tags:
- - timezone
-
- always:
- - name: Restore original timezone - {{ original_timezone.diff.before.name }}
- timezone:
- name: "{{ original_timezone.diff.before.name }}"
- when: original_timezone.diff.before.name != "n/a"
- tags:
- - timezone
-
- - name: Restore original timezone when n/a
- file:
- path: /etc/sysconfig/clock
- state: absent
- when:
- - original_timezone.diff.before.name == "n/a"
- - "'/etc/sysconfig/clock' in original_timezone.msg"
- tags:
- - timezone
-
-
-- name: Unexpire user
- user:
- name: ansibulluser
- state: present
- expires: -1
- register: user_test_expires3
-
-- name: Verify un expiration date for Linux
- block:
- - name: LINUX | Get expiration date for ansibulluser
- getent:
- database: shadow
- key: ansibulluser
-
- - name: LINUX | Ensure proper expiration date was set
- assert:
- msg: "expiry is supposed to be empty or -1, not {{ getent_shadow['ansibulluser'][6] }}"
- that:
- - not getent_shadow['ansibulluser'][6] or getent_shadow['ansibulluser'][6] | int < 0
- when: ansible_facts.os_family in ['RedHat', 'Debian', 'Suse']
-
-- name: Verify un expiration date for Linux/BSD
- block:
- - name: Unexpire user again to check for change
- user:
- name: ansibulluser
- state: present
- expires: -1
- register: user_test_expires4
-
- - name: Ensure first expiration reported a change and second did not
- assert:
- msg: The second run of the expiration removal task reported a change when it should not
- that:
- - user_test_expires3 is changed
- - user_test_expires4 is not changed
- when: ansible_facts.os_family in ['RedHat', 'Debian', 'Suse', 'FreeBSD']
-
-- name: Verify un expiration date for BSD
- block:
- - name: BSD | Get expiration date for ansibulluser
- shell: 'grep ansibulluser /etc/master.passwd | cut -d: -f 7'
- changed_when: no
- register: bsd_account_expiration
-
- - name: BSD | Ensure proper expiration date was set
- assert:
- msg: "expiry is supposed to be '0', not {{ bsd_account_expiration.stdout }}"
- that:
- - bsd_account_expiration.stdout == '0'
- when: ansible_facts.os_family == 'FreeBSD'
-
-# Test setting no expiration when creating a new account
-# https://github.com/ansible/ansible/issues/44155
-- name: Remove ansibulluser
- user:
- name: ansibulluser
- state: absent
-
-- name: Create user account without expiration
- user:
- name: ansibulluser
- state: present
- expires: -1
- register: user_test_create_no_expires_1
-
-- name: Create user account without expiration again
- user:
- name: ansibulluser
- state: present
- expires: -1
- register: user_test_create_no_expires_2
-
-- name: Ensure changes were made appropriately
- assert:
- msg: Setting 'expires='-1 resulted in incorrect changes
- that:
- - user_test_create_no_expires_1 is changed
- - user_test_create_no_expires_2 is not changed
-
-- name: Verify un expiration date for Linux
- block:
- - name: LINUX | Get expiration date for ansibulluser
- getent:
- database: shadow
- key: ansibulluser
-
- - name: LINUX | Ensure proper expiration date was set
- assert:
- msg: "expiry is supposed to be empty or -1, not {{ getent_shadow['ansibulluser'][6] }}"
- that:
- - not getent_shadow['ansibulluser'][6] or getent_shadow['ansibulluser'][6] | int < 0
- when: ansible_facts.os_family in ['RedHat', 'Debian', 'Suse']
-
-- name: Verify un expiration date for BSD
- block:
- - name: BSD | Get expiration date for ansibulluser
- shell: 'grep ansibulluser /etc/master.passwd | cut -d: -f 7'
- changed_when: no
- register: bsd_account_expiration
-
- - name: BSD | Ensure proper expiration date was set
- assert:
- msg: "expiry is supposed to be '0', not {{ bsd_account_expiration.stdout }}"
- that:
- - bsd_account_expiration.stdout == '0'
- when: ansible_facts.os_family == 'FreeBSD'
-
-# Test setting epoch 0 expiration when creating a new account, then removing the expiry
-# https://github.com/ansible/ansible/issues/47114
-- name: Remove ansibulluser
- user:
- name: ansibulluser
- state: absent
-
-- name: Create user account with epoch 0 expiration
- user:
- name: ansibulluser
- state: present
- expires: 0
- register: user_test_expires_create0_1
-
-- name: Create user account with epoch 0 expiration again
- user:
- name: ansibulluser
- state: present
- expires: 0
- register: user_test_expires_create0_2
-
-- name: Change the user account to remove the expiry time
- user:
- name: ansibulluser
- expires: -1
- register: user_test_remove_expires_1
-
-- name: Change the user account to remove the expiry time again
- user:
- name: ansibulluser
- expires: -1
- register: user_test_remove_expires_2
-
-
-- name: Verify un expiration date for Linux
- block:
- - name: LINUX | Ensure changes were made appropriately
- assert:
- msg: Creating an account with 'expries=0' then removing that expriation with 'expires=-1' resulted in incorrect changes
- that:
- - user_test_expires_create0_1 is changed
- - user_test_expires_create0_2 is not changed
- - user_test_remove_expires_1 is changed
- - user_test_remove_expires_2 is not changed
-
- - name: LINUX | Get expiration date for ansibulluser
- getent:
- database: shadow
- key: ansibulluser
-
- - name: LINUX | Ensure proper expiration date was set
- assert:
- msg: "expiry is supposed to be empty or -1, not {{ getent_shadow['ansibulluser'][6] }}"
- that:
- - not getent_shadow['ansibulluser'][6] or getent_shadow['ansibulluser'][6] | int < 0
- when: ansible_facts.os_family in ['RedHat', 'Debian', 'Suse']
-
-
-- name: Verify proper expiration behavior for BSD
- block:
- - name: BSD | Ensure changes were made appropriately
- assert:
- msg: Creating an account with 'expries=0' then removing that expriation with 'expires=-1' resulted in incorrect changes
- that:
- - user_test_expires_create0_1 is changed
- - user_test_expires_create0_2 is not changed
- - user_test_remove_expires_1 is not changed
- - user_test_remove_expires_2 is not changed
- when: ansible_facts.os_family == 'FreeBSD'
-
-# Test expiration with a very large negative number. This should have the same
-# result as setting -1.
-- name: Set expiration date using very long negative number
- user:
- name: ansibulluser
- state: present
- expires: -2529881062
- register: user_test_expires5
-
-- name: Ensure no change was made
- assert:
- that:
- - user_test_expires5 is not changed
-
-- name: Verify un expiration date for Linux
- block:
- - name: LINUX | Get expiration date for ansibulluser
- getent:
- database: shadow
- key: ansibulluser
-
- - name: LINUX | Ensure proper expiration date was set
- assert:
- msg: "expiry is supposed to be empty or -1, not {{ getent_shadow['ansibulluser'][6] }}"
- that:
- - not getent_shadow['ansibulluser'][6] or getent_shadow['ansibulluser'][6] | int < 0
- when: ansible_facts.os_family in ['RedHat', 'Debian', 'Suse']
-
-- name: Verify un expiration date for BSD
- block:
- - name: BSD | Get expiration date for ansibulluser
- shell: 'grep ansibulluser /etc/master.passwd | cut -d: -f 7'
- changed_when: no
- register: bsd_account_expiration
-
- - name: BSD | Ensure proper expiration date was set
- assert:
- msg: "expiry is supposed to be '0', not {{ bsd_account_expiration.stdout }}"
- that:
- - bsd_account_expiration.stdout == '0'
- when: ansible_facts.os_family == 'FreeBSD'
-
-
-## shadow backup
-- block:
- - name: Create a user to test shadow file backup
- user:
- name: ansibulluser
- state: present
- register: result
-
- - name: Find shadow backup files
- find:
- path: /etc
- patterns: 'shadow\..*~$'
- use_regex: yes
- register: shadow_backups
-
- - name: Assert that a backup file was created
- assert:
- that:
- - result.bakup
- - shadow_backups.files | map(attribute='path') | list | length > 0
- when: ansible_facts.os_family == 'Solaris'
-
-
-# Test creating ssh key with passphrase
-- name: Remove ansibulluser
- user:
- name: ansibulluser
- state: absent
-
-- name: Create user with ssh key
- user:
- name: ansibulluser
- state: present
- generate_ssh_key: yes
- force: yes
- ssh_key_file: "{{ output_dir }}/test_id_rsa"
- ssh_key_passphrase: secret_passphrase
-
-- name: Unlock ssh key
- command: "ssh-keygen -y -f {{ output_dir }}/test_id_rsa -P secret_passphrase"
- register: result
-
-- name: Check that ssh key was unlocked successfully
- assert:
- that:
- - result.rc == 0
-
-- name: Clean ssh key
- file:
- path: "{{ output_dir }}/test_id_rsa"
- state: absent
- when: ansible_os_family == 'FreeBSD'
-
-
-## password lock
-- block:
- - name: Set password for ansibulluser
- user:
- name: ansibulluser
- password: "$6$rounds=656000$TT4O7jz2M57npccl$33LF6FcUMSW11qrESXL1HX0BS.bsiT6aenFLLiVpsQh6hDtI9pJh5iY7x8J7ePkN4fP8hmElidHXaeD51pbGS."
-
- - name: Lock account
- user:
- name: ansibulluser
- password_lock: yes
- register: password_lock_1
-
- - name: Lock account again
- user:
- name: ansibulluser
- password_lock: yes
- register: password_lock_2
-
- - name: Unlock account
- user:
- name: ansibulluser
- password_lock: no
- register: password_lock_3
-
- - name: Unlock account again
- user:
- name: ansibulluser
- password_lock: no
- register: password_lock_4
-
- - name: Ensure task reported changes appropriately
- assert:
- msg: The password_lock tasks did not make changes appropriately
- that:
- - password_lock_1 is changed
- - password_lock_2 is not changed
- - password_lock_3 is changed
- - password_lock_4 is not changed
-
- - name: Lock account
- user:
- name: ansibulluser
- password_lock: yes
-
- - name: Verify account lock for BSD
- block:
- - name: BSD | Get account status
- shell: "{{ status_command[ansible_facts['system']] }}"
- register: account_status_locked
-
- - name: Unlock account
- user:
- name: ansibulluser
- password_lock: no
-
- - name: BSD | Get account status
- shell: "{{ status_command[ansible_facts['system']] }}"
- register: account_status_unlocked
-
- - name: FreeBSD | Ensure account is locked
- assert:
- that:
- - "'LOCKED' in account_status_locked.stdout"
- - "'LOCKED' not in account_status_unlocked.stdout"
- when: ansible_facts['system'] == 'FreeBSD'
-
- when: ansible_facts['system'] in ['FreeBSD', 'OpenBSD']
-
- - name: Verify account lock for Linux
- block:
- - name: LINUX | Get account status
- getent:
- database: shadow
- key: ansibulluser
-
- - name: LINUX | Ensure account is locked
- assert:
- that:
- - getent_shadow['ansibulluser'][0].startswith('!')
-
- - name: Unlock account
- user:
- name: ansibulluser
- password_lock: no
-
- - name: LINUX | Get account status
- getent:
- database: shadow
- key: ansibulluser
-
- - name: LINUX | Ensure account is unlocked
- assert:
- that:
- - not getent_shadow['ansibulluser'][0].startswith('!')
-
- when: ansible_facts['system'] == 'Linux'
-
- always:
- - name: Unlock account
- user:
- name: ansibulluser
- password_lock: no
-
- when: ansible_facts['system'] in ['FreeBSD', 'OpenBSD', 'Linux']
-
-
- ## Check local mode
- # Even if we don't have a system that is bound to a directory, it's useful
- # to run with local: true to exercise the code path that reads through the local
- # user database file.
- # https://github.com/ansible/ansible/issues/50947
-
-- name: Create /etc/gshadow
- file:
- path: /etc/gshadow
- state: touch
- when: ansible_facts.os_family == 'Suse'
- tags:
- - user_test_local_mode
-
-- name: Create /etc/libuser.conf
- file:
- path: /etc/libuser.conf
- state: touch
- when:
- - ansible_facts.distribution == 'Ubuntu'
- - ansible_facts.distribution_major_version is version_compare('16', '==')
- tags:
- - user_test_local_mode
-
-- name: Ensure luseradd is present
- action: "{{ ansible_facts.pkg_mgr }}"
- args:
- name: libuser
- state: present
- when: ansible_facts.system in ['Linux']
- tags:
- - user_test_local_mode
-
-- name: Create local account that already exists to check for warning
- user:
- name: root
- local: yes
- register: local_existing
- tags:
- - user_test_local_mode
-
-- name: Create local_ansibulluser
- user:
- name: local_ansibulluser
- state: present
- local: yes
- register: local_user_test_1
- tags:
- - user_test_local_mode
-
-- name: Create local_ansibulluser again
- user:
- name: local_ansibulluser
- state: present
- local: yes
- register: local_user_test_2
- tags:
- - user_test_local_mode
-
-- name: Remove local_ansibulluser
- user:
- name: local_ansibulluser
- state: absent
- remove: yes
- local: yes
- register: local_user_test_remove_1
- tags:
- - user_test_local_mode
-
-- name: Remove local_ansibulluser again
- user:
- name: local_ansibulluser
- state: absent
- remove: yes
- local: yes
- register: local_user_test_remove_2
- tags:
- - user_test_local_mode
-
-- name: Create test groups
- group:
- name: "{{ item }}"
- loop:
- - testgroup1
- - testgroup2
- - testgroup3
- - testgroup4
- tags:
- - user_test_local_mode
-
-- name: Create local_ansibulluser with groups
- user:
- name: local_ansibulluser
- state: present
- local: yes
- groups: ['testgroup1', 'testgroup2']
- register: local_user_test_3
- ignore_errors: yes
- tags:
- - user_test_local_mode
-
-- name: Append groups for local_ansibulluser
- user:
- name: local_ansibulluser
- state: present
- local: yes
- groups: ['testgroup3', 'testgroup4']
- append: yes
- register: local_user_test_4
- ignore_errors: yes
- tags:
- - user_test_local_mode
-
-- name: Test append without groups for local_ansibulluser
- user:
- name: local_ansibulluser
- state: present
- append: yes
- register: local_user_test_5
- ignore_errors: yes
- tags:
- - user_test_local_mode
-
-- name: Remove local_ansibulluser again
- user:
- name: local_ansibulluser
- state: absent
- remove: yes
- local: yes
- tags:
- - user_test_local_mode
-
-- name: Remove test groups
- group:
- name: "{{ item }}"
- state: absent
- loop:
- - testgroup1
- - testgroup2
- - testgroup3
- - testgroup4
- tags:
- - user_test_local_mode
-
-- name: Ensure local user accounts were created and removed properly
- assert:
- that:
- - local_user_test_1 is changed
- - local_user_test_2 is not changed
- - local_user_test_3 is changed
- - local_user_test_4 is changed
- - local_user_test_remove_1 is changed
- - local_user_test_remove_2 is not changed
- tags:
- - user_test_local_mode
-
-- name: Ensure warnings were displayed properly
- assert:
- that:
- - local_user_test_1['warnings'] | length > 0
- - local_user_test_1['warnings'] | first is search('The local user account may already exist')
- - local_user_test_5['warnings'] is search("'append' is set, but no 'groups' are specified. Use 'groups'")
- - local_existing['warnings'] is not defined
- when: ansible_facts.system in ['Linux']
- tags:
- - user_test_local_mode
-
-- name: Test expires for local users
- import_tasks: expires_local.yml
+- import_tasks: test_create_user.yml
+- import_tasks: test_create_system_user.yml
+- import_tasks: test_create_user_uid.yml
+- import_tasks: test_create_user_password.yml
+- import_tasks: test_create_user_home.yml
+- import_tasks: test_remove_user.yml
+- import_tasks: test_no_home_fallback.yml
+- import_tasks: test_expires.yml
+- import_tasks: test_expires_new_account.yml
+- import_tasks: test_expires_new_account_epoch_negative.yml
+- import_tasks: test_shadow_backup.yml
+- import_tasks: test_ssh_key_passphrase.yml
+- import_tasks: test_password_lock.yml
+- import_tasks: test_password_lock_new_user.yml
+- import_tasks: test_local.yml
diff --git a/test/integration/targets/user/tasks/test_create_system_user.yml b/test/integration/targets/user/tasks/test_create_system_user.yml
new file mode 100644
index 00000000..da746c50
--- /dev/null
+++ b/test/integration/targets/user/tasks/test_create_system_user.yml
@@ -0,0 +1,12 @@
+# create system user
+
+- name: remove user
+ user:
+ name: ansibulluser
+ state: absent
+
+- name: create system user
+ user:
+ name: ansibulluser
+ state: present
+ system: yes
diff --git a/test/integration/targets/user/tasks/test_create_user.yml b/test/integration/targets/user/tasks/test_create_user.yml
new file mode 100644
index 00000000..bced7905
--- /dev/null
+++ b/test/integration/targets/user/tasks/test_create_user.yml
@@ -0,0 +1,67 @@
+- name: remove the test user
+ user:
+ name: ansibulluser
+ state: absent
+
+- name: try to create a user
+ user:
+ name: ansibulluser
+ state: present
+ register: user_test0_0
+
+- name: create the user again
+ user:
+ name: ansibulluser
+ state: present
+ register: user_test0_1
+
+- debug:
+ var: user_test0
+ verbosity: 2
+
+- name: make a list of users
+ script: userlist.sh {{ ansible_facts.distribution }}
+ register: user_names
+
+- debug:
+ var: user_names
+ verbosity: 2
+
+- name: validate results for testcase 0
+ assert:
+ that:
+ - user_test0_0 is changed
+ - user_test0_1 is not changed
+ - '"ansibulluser" in user_names.stdout_lines'
+
+- name: run existing user check tests
+ user:
+ name: "{{ user_names.stdout_lines | random }}"
+ state: present
+ create_home: no
+ loop: "{{ range(1, 5+1) | list }}"
+ register: user_test1
+
+- debug:
+ var: user_test1
+ verbosity: 2
+
+- name: validate results for testcase 1
+ assert:
+ that:
+ - user_test1.results is defined
+ - user_test1.results | length == 5
+
+- name: validate changed results for testcase 1
+ assert:
+ that:
+ - "user_test1.results[0] is not changed"
+ - "user_test1.results[1] is not changed"
+ - "user_test1.results[2] is not changed"
+ - "user_test1.results[3] is not changed"
+ - "user_test1.results[4] is not changed"
+ - "user_test1.results[0]['state'] == 'present'"
+ - "user_test1.results[1]['state'] == 'present'"
+ - "user_test1.results[2]['state'] == 'present'"
+ - "user_test1.results[3]['state'] == 'present'"
+ - "user_test1.results[4]['state'] == 'present'"
diff --git a/test/integration/targets/user/tasks/test_create_user_home.yml b/test/integration/targets/user/tasks/test_create_user_home.yml
new file mode 100644
index 00000000..1b529f76
--- /dev/null
+++ b/test/integration/targets/user/tasks/test_create_user_home.yml
@@ -0,0 +1,136 @@
+# https://github.com/ansible/ansible/issues/42484
+# Skipping macOS for now since there is a bug when changing home directory
+- name: Test home directory creation
+ when: ansible_facts.system != 'Darwin'
+ block:
+ - name: create user specifying home
+ user:
+ name: ansibulluser
+ state: present
+ home: "{{ user_home_prefix[ansible_facts.system] }}/ansibulluser"
+ register: user_test3_0
+
+ - name: create user again specifying home
+ user:
+ name: ansibulluser
+ state: present
+ home: "{{ user_home_prefix[ansible_facts.system] }}/ansibulluser"
+ register: user_test3_1
+
+ - name: change user home
+ user:
+ name: ansibulluser
+ state: present
+ home: "{{ user_home_prefix[ansible_facts.system] }}/ansibulluser-mod"
+ register: user_test3_2
+
+ - name: change user home back
+ user:
+ name: ansibulluser
+ state: present
+ home: "{{ user_home_prefix[ansible_facts.system] }}/ansibulluser"
+ register: user_test3_3
+
+ - name: validate results for testcase 3
+ assert:
+ that:
+ - user_test3_0 is not changed
+ - user_test3_1 is not changed
+ - user_test3_2 is changed
+ - user_test3_3 is changed
+
+# https://github.com/ansible/ansible/issues/41393
+# Create a new user account with a path that has parent directories that do not exist
+- name: Create user with home path that has parents that do not exist
+ user:
+ name: ansibulluser2
+ state: present
+ home: "{{ user_home_prefix[ansible_facts.system] }}/in2deep/ansibulluser2"
+ register: create_home_with_no_parent_1
+
+- name: Create user with home path that has parents that do not exist again
+ user:
+ name: ansibulluser2
+ state: present
+ home: "{{ user_home_prefix[ansible_facts.system] }}/in2deep/ansibulluser2"
+ register: create_home_with_no_parent_2
+
+- name: Check the created home directory
+ stat:
+ path: "{{ user_home_prefix[ansible_facts.system] }}/in2deep/ansibulluser2"
+ register: home_with_no_parent_3
+
+- name: Ensure user with non-existing parent paths was created successfully
+ assert:
+ that:
+ - create_home_with_no_parent_1 is changed
+ - create_home_with_no_parent_1.home == user_home_prefix[ansible_facts.system] ~ '/in2deep/ansibulluser2'
+ - create_home_with_no_parent_2 is not changed
+ - home_with_no_parent_3.stat.uid == create_home_with_no_parent_1.uid
+ - home_with_no_parent_3.stat.gr_name == default_user_group[ansible_facts.distribution] | default('ansibulluser2')
+
+- name: Cleanup test account
+ user:
+ name: ansibulluser2
+ home: "{{ user_home_prefix[ansible_facts.system] }}/in2deep/ansibulluser2"
+ state: absent
+ remove: yes
+
+- name: Remove testing dir
+ file:
+ path: "{{ user_home_prefix[ansible_facts.system] }}/in2deep/"
+ state: absent
+
+
+# https://github.com/ansible/ansible/issues/60307
+# Make sure we can create a user when the home directory is missing
+- name: Create user with home path that does not exist
+ user:
+ name: ansibulluser3
+ state: present
+ home: "{{ user_home_prefix[ansible_facts.system] }}/nosuchdir"
+ createhome: no
+
+- name: Cleanup test account
+ user:
+ name: ansibulluser3
+ state: absent
+ remove: yes
+
+# https://github.com/ansible/ansible/issues/70589
+# Create user with create_home: no and parent directory does not exist.
+- name: "Check if parent dir for home dir for user exists (before)"
+ stat:
+ path: "{{ user_home_prefix[ansible_facts.system] }}/thereisnodir"
+ register: create_user_no_create_home_with_no_parent_parent_dir_before
+
+- name: "Create user with create_home == no and home path parent dir does not exist"
+ user:
+ name: randomuser
+ state: present
+ create_home: false
+ home: "{{ user_home_prefix[ansible_facts.system] }}/thereisnodir/randomuser"
+ register: create_user_no_create_home_with_no_parent
+
+- name: "Check if parent dir for home dir for user exists (after)"
+ stat:
+ path: "{{ user_home_prefix[ansible_facts.system] }}/thereisnodir"
+ register: create_user_no_create_home_with_no_parent_parent_dir_after
+
+- name: "Check if home for user is created"
+ stat:
+ path: "{{ user_home_prefix[ansible_facts.system] }}/thereisnodir/randomuser"
+ register: create_user_no_create_home_with_no_parent_home_dir
+
+- name: "Ensure user with non-existing parent paths with create_home: no was created successfully"
+ assert:
+ that:
+ - not create_user_no_create_home_with_no_parent_parent_dir_before.stat.exists
+ - not create_user_no_create_home_with_no_parent_parent_dir_after.stat.isdir is defined
+ - not create_user_no_create_home_with_no_parent_home_dir.stat.exists
+
+- name: Cleanup test account
+ user:
+ name: randomuser
+ state: absent
+ remove: yes
diff --git a/test/integration/targets/user/tasks/test_create_user_password.yml b/test/integration/targets/user/tasks/test_create_user_password.yml
new file mode 100644
index 00000000..02aae003
--- /dev/null
+++ b/test/integration/targets/user/tasks/test_create_user_password.yml
@@ -0,0 +1,90 @@
+# test user add with password
+- name: add an encrypted password for user
+ user:
+ name: ansibulluser
+ password: "$6$rounds=656000$TT4O7jz2M57npccl$33LF6FcUMSW11qrESXL1HX0BS.bsiT6aenFLLiVpsQh6hDtI9pJh5iY7x8J7ePkN4fP8hmElidHXaeD51pbGS."
+ state: present
+ update_password: always
+ register: test_user_encrypt0
+
+- name: there should not be warnings
+ assert:
+ that: "'warnings' not in test_user_encrypt0"
+
+# https://github.com/ansible/ansible/issues/65711
+- name: Test updating password only on creation
+ user:
+ name: ansibulluser
+ password: '*'
+ update_password: on_create
+ register: test_user_update_password
+
+- name: Ensure password was not changed
+ assert:
+ that:
+ - test_user_update_password is not changed
+
+- name: Verify password hash for Linux
+ when: ansible_facts.os_family in ['RedHat', 'Debian', 'Suse']
+ block:
+ - name: LINUX | Get shadow entry for ansibulluser
+ getent:
+ database: shadow
+ key: ansibulluser
+
+ - name: LINUX | Ensure password hash was not removed
+ assert:
+ that:
+ - getent_shadow['ansibulluser'][1] != '*'
+
+- name: Test plaintext warning
+ when: ansible_facts.system != 'Darwin'
+ block:
+ - name: add an plaintext password for user
+ user:
+ name: ansibulluser
+ password: "plaintextpassword"
+ state: present
+ update_password: always
+ register: test_user_encrypt1
+
+ - name: there should be a warning complains that the password is plaintext
+ assert:
+ that: "'warnings' in test_user_encrypt1"
+
+ - name: add an invalid hashed password
+ user:
+ name: ansibulluser
+ password: "$6$rounds=656000$tgK3gYTyRLUmhyv2$lAFrYUQwn7E6VsjPOwQwoSx30lmpiU9r/E0Al7tzKrR9mkodcMEZGe9OXD0H/clOn6qdsUnaL4zefy5fG+++++"
+ state: present
+ update_password: always
+ register: test_user_encrypt2
+
+ - name: there should be a warning complains about the character set of password
+ assert:
+ that: "'warnings' in test_user_encrypt2"
+
+ - name: change password to '!'
+ user:
+ name: ansibulluser
+ password: '!'
+ register: test_user_encrypt3
+
+ - name: change password to '*'
+ user:
+ name: ansibulluser
+ password: '*'
+ register: test_user_encrypt4
+
+ - name: change password to '*************'
+ user:
+ name: ansibulluser
+ password: '*************'
+ register: test_user_encrypt5
+
+ - name: there should be no warnings when setting the password to '!', '*' or '*************'
+ assert:
+ that:
+ - "'warnings' not in test_user_encrypt3"
+ - "'warnings' not in test_user_encrypt4"
+ - "'warnings' not in test_user_encrypt5"
diff --git a/test/integration/targets/user/tasks/test_create_user_uid.yml b/test/integration/targets/user/tasks/test_create_user_uid.yml
new file mode 100644
index 00000000..9ac8a96f
--- /dev/null
+++ b/test/integration/targets/user/tasks/test_create_user_uid.yml
@@ -0,0 +1,26 @@
+# test adding user with uid
+# https://github.com/ansible/ansible/issues/62969
+- name: remove the test user
+ user:
+ name: ansibulluser
+ state: absent
+
+- name: try to create a user with uid
+ user:
+ name: ansibulluser
+ state: present
+ uid: 572
+ register: user_test01_0
+
+- name: create the user again
+ user:
+ name: ansibulluser
+ state: present
+ uid: 572
+ register: user_test01_1
+
+- name: validate results for testcase 0
+ assert:
+ that:
+ - user_test01_0 is changed
+ - user_test01_1 is not changed
diff --git a/test/integration/targets/user/tasks/test_expires.yml b/test/integration/targets/user/tasks/test_expires.yml
new file mode 100644
index 00000000..8c238934
--- /dev/null
+++ b/test/integration/targets/user/tasks/test_expires.yml
@@ -0,0 +1,147 @@
+# Date is March 3, 2050
+- name: Set user expiration
+ user:
+ name: ansibulluser
+ state: present
+ expires: 2529881062
+ register: user_test_expires1
+ tags:
+ - timezone
+
+- name: Set user expiration again to ensure no change is made
+ user:
+ name: ansibulluser
+ state: present
+ expires: 2529881062
+ register: user_test_expires2
+ tags:
+ - timezone
+
+- name: Ensure that account with expiration was created and did not change on subsequent run
+ assert:
+ that:
+ - user_test_expires1 is changed
+ - user_test_expires2 is not changed
+
+- name: Verify expiration date for Linux
+ block:
+ - name: LINUX | Get expiration date for ansibulluser
+ getent:
+ database: shadow
+ key: ansibulluser
+
+ - name: LINUX | Ensure proper expiration date was set
+ assert:
+ that:
+ - getent_shadow['ansibulluser'][6] == '29281'
+ when: ansible_facts.os_family in ['RedHat', 'Debian', 'Suse']
+
+
+- name: Verify expiration date for BSD
+ block:
+ - name: BSD | Get expiration date for ansibulluser
+ shell: 'grep ansibulluser /etc/master.passwd | cut -d: -f 7'
+ changed_when: no
+ register: bsd_account_expiration
+
+ - name: BSD | Ensure proper expiration date was set
+ assert:
+ that:
+ - bsd_account_expiration.stdout == '2529881062'
+ when: ansible_facts.os_family == 'FreeBSD'
+
+- name: Change timezone
+ timezone:
+ name: America/Denver
+ register: original_timezone
+ tags:
+ - timezone
+
+- name: Change system timezone to make sure expiration comparison works properly
+ block:
+ - name: Create user with expiration again to ensure no change is made in a new timezone
+ user:
+ name: ansibulluser
+ state: present
+ expires: 2529881062
+ register: user_test_different_tz
+ tags:
+ - timezone
+
+ - name: Ensure that no change was reported
+ assert:
+ that:
+ - user_test_different_tz is not changed
+ tags:
+ - timezone
+
+ always:
+ - name: Restore original timezone - {{ original_timezone.diff.before.name }}
+ timezone:
+ name: "{{ original_timezone.diff.before.name }}"
+ when: original_timezone.diff.before.name != "n/a"
+ tags:
+ - timezone
+
+ - name: Restore original timezone when n/a
+ file:
+ path: /etc/sysconfig/clock
+ state: absent
+ when:
+ - original_timezone.diff.before.name == "n/a"
+ - "'/etc/sysconfig/clock' in original_timezone.msg"
+ tags:
+ - timezone
+
+
+- name: Unexpire user
+ user:
+ name: ansibulluser
+ state: present
+ expires: -1
+ register: user_test_expires3
+
+- name: Verify un expiration date for Linux
+ block:
+ - name: LINUX | Get expiration date for ansibulluser
+ getent:
+ database: shadow
+ key: ansibulluser
+
+ - name: LINUX | Ensure proper expiration date was set
+ assert:
+ msg: "expiry is supposed to be empty or -1, not {{ getent_shadow['ansibulluser'][6] }}"
+ that:
+ - not getent_shadow['ansibulluser'][6] or getent_shadow['ansibulluser'][6] | int < 0
+ when: ansible_facts.os_family in ['RedHat', 'Debian', 'Suse']
+
+- name: Verify un expiration date for Linux/BSD
+ block:
+ - name: Unexpire user again to check for change
+ user:
+ name: ansibulluser
+ state: present
+ expires: -1
+ register: user_test_expires4
+
+ - name: Ensure first expiration reported a change and second did not
+ assert:
+ msg: The second run of the expiration removal task reported a change when it should not
+ that:
+ - user_test_expires3 is changed
+ - user_test_expires4 is not changed
+ when: ansible_facts.os_family in ['RedHat', 'Debian', 'Suse', 'FreeBSD']
+
+- name: Verify un expiration date for BSD
+ block:
+ - name: BSD | Get expiration date for ansibulluser
+ shell: 'grep ansibulluser /etc/master.passwd | cut -d: -f 7'
+ changed_when: no
+ register: bsd_account_expiration
+
+ - name: BSD | Ensure proper expiration date was set
+ assert:
+ msg: "expiry is supposed to be '0', not {{ bsd_account_expiration.stdout }}"
+ that:
+ - bsd_account_expiration.stdout == '0'
+ when: ansible_facts.os_family == 'FreeBSD'
diff --git a/test/integration/targets/user/tasks/test_expires_new_account.yml b/test/integration/targets/user/tasks/test_expires_new_account.yml
new file mode 100644
index 00000000..b77d137f
--- /dev/null
+++ b/test/integration/targets/user/tasks/test_expires_new_account.yml
@@ -0,0 +1,55 @@
+# Test setting no expiration when creating a new account
+# https://github.com/ansible/ansible/issues/44155
+- name: Remove ansibulluser
+ user:
+ name: ansibulluser
+ state: absent
+
+- name: Create user account without expiration
+ user:
+ name: ansibulluser
+ state: present
+ expires: -1
+ register: user_test_create_no_expires_1
+
+- name: Create user account without expiration again
+ user:
+ name: ansibulluser
+ state: present
+ expires: -1
+ register: user_test_create_no_expires_2
+
+- name: Ensure changes were made appropriately
+ assert:
+ msg: Setting 'expires='-1 resulted in incorrect changes
+ that:
+ - user_test_create_no_expires_1 is changed
+ - user_test_create_no_expires_2 is not changed
+
+- name: Verify un expiration date for Linux
+ block:
+ - name: LINUX | Get expiration date for ansibulluser
+ getent:
+ database: shadow
+ key: ansibulluser
+
+ - name: LINUX | Ensure proper expiration date was set
+ assert:
+ msg: "expiry is supposed to be empty or -1, not {{ getent_shadow['ansibulluser'][6] }}"
+ that:
+ - not getent_shadow['ansibulluser'][6] or getent_shadow['ansibulluser'][6] | int < 0
+ when: ansible_facts.os_family in ['RedHat', 'Debian', 'Suse']
+
+- name: Verify un expiration date for BSD
+ block:
+ - name: BSD | Get expiration date for ansibulluser
+ shell: 'grep ansibulluser /etc/master.passwd | cut -d: -f 7'
+ changed_when: no
+ register: bsd_account_expiration
+
+ - name: BSD | Ensure proper expiration date was set
+ assert:
+ msg: "expiry is supposed to be '0', not {{ bsd_account_expiration.stdout }}"
+ that:
+ - bsd_account_expiration.stdout == '0'
+ when: ansible_facts.os_family == 'FreeBSD'
diff --git a/test/integration/targets/user/tasks/test_expires_new_account_epoch_negative.yml b/test/integration/targets/user/tasks/test_expires_new_account_epoch_negative.yml
new file mode 100644
index 00000000..77a07c4a
--- /dev/null
+++ b/test/integration/targets/user/tasks/test_expires_new_account_epoch_negative.yml
@@ -0,0 +1,112 @@
+# Test setting epoch 0 expiration when creating a new account, then removing the expiry
+# https://github.com/ansible/ansible/issues/47114
+- name: Remove ansibulluser
+ user:
+ name: ansibulluser
+ state: absent
+
+- name: Create user account with epoch 0 expiration
+ user:
+ name: ansibulluser
+ state: present
+ expires: 0
+ register: user_test_expires_create0_1
+
+- name: Create user account with epoch 0 expiration again
+ user:
+ name: ansibulluser
+ state: present
+ expires: 0
+ register: user_test_expires_create0_2
+
+- name: Change the user account to remove the expiry time
+ user:
+ name: ansibulluser
+ expires: -1
+ register: user_test_remove_expires_1
+
+- name: Change the user account to remove the expiry time again
+ user:
+ name: ansibulluser
+ expires: -1
+ register: user_test_remove_expires_2
+
+
+- name: Verify un expiration date for Linux
+ block:
+ - name: LINUX | Ensure changes were made appropriately
+ assert:
+ msg: Creating an account with 'expries=0' then removing that expriation with 'expires=-1' resulted in incorrect changes
+ that:
+ - user_test_expires_create0_1 is changed
+ - user_test_expires_create0_2 is not changed
+ - user_test_remove_expires_1 is changed
+ - user_test_remove_expires_2 is not changed
+
+ - name: LINUX | Get expiration date for ansibulluser
+ getent:
+ database: shadow
+ key: ansibulluser
+
+ - name: LINUX | Ensure proper expiration date was set
+ assert:
+ msg: "expiry is supposed to be empty or -1, not {{ getent_shadow['ansibulluser'][6] }}"
+ that:
+ - not getent_shadow['ansibulluser'][6] or getent_shadow['ansibulluser'][6] | int < 0
+ when: ansible_facts.os_family in ['RedHat', 'Debian', 'Suse']
+
+
+- name: Verify proper expiration behavior for BSD
+ block:
+ - name: BSD | Ensure changes were made appropriately
+ assert:
+ msg: Creating an account with 'expries=0' then removing that expriation with 'expires=-1' resulted in incorrect changes
+ that:
+ - user_test_expires_create0_1 is changed
+ - user_test_expires_create0_2 is not changed
+ - user_test_remove_expires_1 is not changed
+ - user_test_remove_expires_2 is not changed
+ when: ansible_facts.os_family == 'FreeBSD'
+
+
+# Test expiration with a very large negative number. This should have the same
+# result as setting -1.
+- name: Set expiration date using very long negative number
+ user:
+ name: ansibulluser
+ state: present
+ expires: -2529881062
+ register: user_test_expires5
+
+- name: Ensure no change was made
+ assert:
+ that:
+ - user_test_expires5 is not changed
+
+- name: Verify un expiration date for Linux
+ block:
+ - name: LINUX | Get expiration date for ansibulluser
+ getent:
+ database: shadow
+ key: ansibulluser
+
+ - name: LINUX | Ensure proper expiration date was set
+ assert:
+ msg: "expiry is supposed to be empty or -1, not {{ getent_shadow['ansibulluser'][6] }}"
+ that:
+ - not getent_shadow['ansibulluser'][6] or getent_shadow['ansibulluser'][6] | int < 0
+ when: ansible_facts.os_family in ['RedHat', 'Debian', 'Suse']
+
+- name: Verify un expiration date for BSD
+ block:
+ - name: BSD | Get expiration date for ansibulluser
+ shell: 'grep ansibulluser /etc/master.passwd | cut -d: -f 7'
+ changed_when: no
+ register: bsd_account_expiration
+
+ - name: BSD | Ensure proper expiration date was set
+ assert:
+ msg: "expiry is supposed to be '0', not {{ bsd_account_expiration.stdout }}"
+ that:
+ - bsd_account_expiration.stdout == '0'
+ when: ansible_facts.os_family == 'FreeBSD'
diff --git a/test/integration/targets/user/tasks/test_local.yml b/test/integration/targets/user/tasks/test_local.yml
new file mode 100644
index 00000000..16c79c57
--- /dev/null
+++ b/test/integration/targets/user/tasks/test_local.yml
@@ -0,0 +1,169 @@
+## Check local mode
+# Even if we don't have a system that is bound to a directory, it's useful
+# to run with local: true to exercise the code path that reads through the local
+# user database file.
+# https://github.com/ansible/ansible/issues/50947
+
+- name: Create /etc/gshadow
+ file:
+ path: /etc/gshadow
+ state: touch
+ when: ansible_facts.os_family == 'Suse'
+ tags:
+ - user_test_local_mode
+
+- name: Create /etc/libuser.conf
+ file:
+ path: /etc/libuser.conf
+ state: touch
+ when:
+ - ansible_facts.distribution == 'Ubuntu'
+ - ansible_facts.distribution_major_version is version_compare('16', '==')
+ tags:
+ - user_test_local_mode
+
+- name: Ensure luseradd is present
+ action: "{{ ansible_facts.pkg_mgr }}"
+ args:
+ name: libuser
+ state: present
+ when: ansible_facts.system in ['Linux']
+ tags:
+ - user_test_local_mode
+
+- name: Create local account that already exists to check for warning
+ user:
+ name: root
+ local: yes
+ register: local_existing
+ tags:
+ - user_test_local_mode
+
+- name: Create local_ansibulluser
+ user:
+ name: local_ansibulluser
+ state: present
+ local: yes
+ register: local_user_test_1
+ tags:
+ - user_test_local_mode
+
+- name: Create local_ansibulluser again
+ user:
+ name: local_ansibulluser
+ state: present
+ local: yes
+ register: local_user_test_2
+ tags:
+ - user_test_local_mode
+
+- name: Remove local_ansibulluser
+ user:
+ name: local_ansibulluser
+ state: absent
+ remove: yes
+ local: yes
+ register: local_user_test_remove_1
+ tags:
+ - user_test_local_mode
+
+- name: Remove local_ansibulluser again
+ user:
+ name: local_ansibulluser
+ state: absent
+ remove: yes
+ local: yes
+ register: local_user_test_remove_2
+ tags:
+ - user_test_local_mode
+
+- name: Create test groups
+ group:
+ name: "{{ item }}"
+ loop:
+ - testgroup1
+ - testgroup2
+ - testgroup3
+ - testgroup4
+ tags:
+ - user_test_local_mode
+
+- name: Create local_ansibulluser with groups
+ user:
+ name: local_ansibulluser
+ state: present
+ local: yes
+ groups: ['testgroup1', 'testgroup2']
+ register: local_user_test_3
+ ignore_errors: yes
+ tags:
+ - user_test_local_mode
+
+- name: Append groups for local_ansibulluser
+ user:
+ name: local_ansibulluser
+ state: present
+ local: yes
+ groups: ['testgroup3', 'testgroup4']
+ append: yes
+ register: local_user_test_4
+ ignore_errors: yes
+ tags:
+ - user_test_local_mode
+
+- name: Test append without groups for local_ansibulluser
+ user:
+ name: local_ansibulluser
+ state: present
+ append: yes
+ register: local_user_test_5
+ ignore_errors: yes
+ tags:
+ - user_test_local_mode
+
+- name: Remove local_ansibulluser again
+ user:
+ name: local_ansibulluser
+ state: absent
+ remove: yes
+ local: yes
+ tags:
+ - user_test_local_mode
+
+- name: Remove test groups
+ group:
+ name: "{{ item }}"
+ state: absent
+ loop:
+ - testgroup1
+ - testgroup2
+ - testgroup3
+ - testgroup4
+ tags:
+ - user_test_local_mode
+
+- name: Ensure local user accounts were created and removed properly
+ assert:
+ that:
+ - local_user_test_1 is changed
+ - local_user_test_2 is not changed
+ - local_user_test_3 is changed
+ - local_user_test_4 is changed
+ - local_user_test_remove_1 is changed
+ - local_user_test_remove_2 is not changed
+ tags:
+ - user_test_local_mode
+
+- name: Ensure warnings were displayed properly
+ assert:
+ that:
+ - local_user_test_1['warnings'] | length > 0
+ - local_user_test_1['warnings'] | first is search('The local user account may already exist')
+ - local_user_test_5['warnings'] is search("'append' is set, but no 'groups' are specified. Use 'groups'")
+ - local_existing['warnings'] is not defined
+ when: ansible_facts.system in ['Linux']
+ tags:
+ - user_test_local_mode
+
+- name: Test expires for local users
+ import_tasks: test_local_expires.yml
diff --git a/test/integration/targets/user/tasks/expires_local.yml b/test/integration/targets/user/tasks/test_local_expires.yml
index e6620353..e6620353 100644
--- a/test/integration/targets/user/tasks/expires_local.yml
+++ b/test/integration/targets/user/tasks/test_local_expires.yml
diff --git a/test/integration/targets/user/tasks/test_no_home_fallback.yml b/test/integration/targets/user/tasks/test_no_home_fallback.yml
new file mode 100644
index 00000000..f7627fae
--- /dev/null
+++ b/test/integration/targets/user/tasks/test_no_home_fallback.yml
@@ -0,0 +1,106 @@
+## create user without home and test fallback home dir create
+
+- name: Test home directory creation
+ when: ansible_facts.system != 'Darwin'
+ block:
+ - name: create the user
+ user:
+ name: ansibulluser
+
+ - name: delete the user and home dir
+ user:
+ name: ansibulluser
+ state: absent
+ force: true
+ remove: true
+
+ - name: create the user without home
+ user:
+ name: ansibulluser
+ create_home: no
+
+ - name: create the user home dir
+ user:
+ name: ansibulluser
+ register: user_create_home_fallback
+
+ - name: stat home dir
+ stat:
+ path: '{{ user_create_home_fallback.home }}'
+ register: user_create_home_fallback_dir
+
+ - name: read UMASK from /etc/login.defs and return mode
+ shell: |
+ import re
+ import os
+ try:
+ for line in open('/etc/login.defs').readlines():
+ m = re.match(r'^UMASK\s+(\d+)$', line)
+ if m:
+ umask = int(m.group(1), 8)
+ except:
+ umask = os.umask(0)
+ mode = oct(0o777 & ~umask)
+ print(str(mode).replace('o', ''))
+ args:
+ executable: "{{ ansible_python_interpreter }}"
+ register: user_login_defs_umask
+
+ - name: validate that user home dir is created
+ assert:
+ that:
+ - user_create_home_fallback is changed
+ - user_create_home_fallback_dir.stat.exists
+ - user_create_home_fallback_dir.stat.isdir
+ - user_create_home_fallback_dir.stat.pw_name == 'ansibulluser'
+ - user_create_home_fallback_dir.stat.mode == user_login_defs_umask.stdout
+
+- name: Create non-system user
+ when: ansible_facts.distribution == "MacOSX"
+ block:
+ - name: create non-system user on macOS to test the shell is set to /bin/bash
+ user:
+ name: macosuser
+ register: macosuser_output
+
+ - name: validate the shell is set to /bin/bash
+ assert:
+ that:
+ - 'macosuser_output.shell == "/bin/bash"'
+
+ - name: cleanup
+ user:
+ name: macosuser
+ state: absent
+
+ - name: create system user on macOS to test the shell is set to /usr/bin/false
+ user:
+ name: macosuser
+ system: yes
+ register: macosuser_output
+
+ - name: validate the shell is set to /usr/bin/false
+ assert:
+ that:
+ - 'macosuser_output.shell == "/usr/bin/false"'
+
+ - name: cleanup
+ user:
+ name: macosuser
+ state: absent
+
+ - name: create non-system user on macos and set the shell to /bin/sh
+ user:
+ name: macosuser
+ shell: /bin/sh
+ register: macosuser_output
+
+ - name: validate the shell is set to /bin/sh
+ assert:
+ that:
+ - 'macosuser_output.shell == "/bin/sh"'
+
+ - name: cleanup
+ user:
+ name: macosuser
+ state: absent
diff --git a/test/integration/targets/user/tasks/test_password_lock.yml b/test/integration/targets/user/tasks/test_password_lock.yml
new file mode 100644
index 00000000..dde374ee
--- /dev/null
+++ b/test/integration/targets/user/tasks/test_password_lock.yml
@@ -0,0 +1,140 @@
+- name: Test password lock
+ when: ansible_facts.system in ['FreeBSD', 'OpenBSD', 'Linux']
+ block:
+ - name: Remove ansibulluser
+ user:
+ name: ansibulluser
+ state: absent
+ remove: yes
+
+ - name: Create ansibulluser with password
+ user:
+ name: ansibulluser
+ password: "$6$rounds=656000$TT4O7jz2M57npccl$33LF6FcUMSW11qrESXL1HX0BS.bsiT6aenFLLiVpsQh6hDtI9pJh5iY7x8J7ePkN4fP8hmElidHXaeD51pbGS."
+
+ - name: Lock account without password parameter
+ user:
+ name: ansibulluser
+ password_lock: yes
+ register: password_lock_1
+
+ - name: Lock account without password parameter again
+ user:
+ name: ansibulluser
+ password_lock: yes
+ register: password_lock_2
+
+ - name: Unlock account without password parameter
+ user:
+ name: ansibulluser
+ password_lock: no
+ register: password_lock_3
+
+ - name: Unlock account without password parameter again
+ user:
+ name: ansibulluser
+ password_lock: no
+ register: password_lock_4
+
+ - name: Lock account with password parameter
+ user:
+ name: ansibulluser
+ password_lock: yes
+ password: "$6$rounds=656000$TT4O7jz2M57npccl$33LF6FcUMSW11qrESXL1HX0BS.bsiT6aenFLLiVpsQh6hDtI9pJh5iY7x8J7ePkN4fP8hmElidHXaeD51pbGS."
+ register: password_lock_5
+
+ - name: Lock account with password parameter again
+ user:
+ name: ansibulluser
+ password_lock: yes
+ password: "$6$rounds=656000$TT4O7jz2M57npccl$33LF6FcUMSW11qrESXL1HX0BS.bsiT6aenFLLiVpsQh6hDtI9pJh5iY7x8J7ePkN4fP8hmElidHXaeD51pbGS."
+ register: password_lock_6
+
+ - name: Unlock account with password parameter
+ user:
+ name: ansibulluser
+ password_lock: no
+ password: "$6$rounds=656000$TT4O7jz2M57npccl$33LF6FcUMSW11qrESXL1HX0BS.bsiT6aenFLLiVpsQh6hDtI9pJh5iY7x8J7ePkN4fP8hmElidHXaeD51pbGS."
+ register: password_lock_7
+
+ - name: Unlock account with password parameter again
+ user:
+ name: ansibulluser
+ password_lock: no
+ password: "$6$rounds=656000$TT4O7jz2M57npccl$33LF6FcUMSW11qrESXL1HX0BS.bsiT6aenFLLiVpsQh6hDtI9pJh5iY7x8J7ePkN4fP8hmElidHXaeD51pbGS."
+ register: password_lock_8
+
+ - name: Ensure task reported changes appropriately
+ assert:
+ msg: The password_lock tasks did not make changes appropriately
+ that:
+ - password_lock_1 is changed
+ - password_lock_2 is not changed
+ - password_lock_3 is changed
+ - password_lock_4 is not changed
+ - password_lock_5 is changed
+ - password_lock_6 is not changed
+ - password_lock_7 is changed
+ - password_lock_8 is not changed
+
+ - name: Lock account
+ user:
+ name: ansibulluser
+ password_lock: yes
+
+ - name: Verify account lock for BSD
+ when: ansible_facts.system in ['FreeBSD', 'OpenBSD']
+ block:
+ - name: BSD | Get account status
+ shell: "{{ status_command[ansible_facts['system']] }}"
+ register: account_status_locked
+
+ - name: Unlock account
+ user:
+ name: ansibulluser
+ password_lock: no
+
+ - name: BSD | Get account status
+ shell: "{{ status_command[ansible_facts['system']] }}"
+ register: account_status_unlocked
+
+ - name: FreeBSD | Ensure account is locked
+ assert:
+ that:
+ - "'LOCKED' in account_status_locked.stdout"
+ - "'LOCKED' not in account_status_unlocked.stdout"
+ when: ansible_facts['system'] == 'FreeBSD'
+
+ - name: Verify account lock for Linux
+ when: ansible_facts.system == 'Linux'
+ block:
+ - name: LINUX | Get account status
+ getent:
+ database: shadow
+ key: ansibulluser
+
+ - name: LINUX | Ensure account is locked
+ assert:
+ that:
+ - getent_shadow['ansibulluser'][0].startswith('!')
+
+ - name: Unlock account
+ user:
+ name: ansibulluser
+ password_lock: no
+
+ - name: LINUX | Get account status
+ getent:
+ database: shadow
+ key: ansibulluser
+
+ - name: LINUX | Ensure account is unlocked
+ assert:
+ that:
+ - not getent_shadow['ansibulluser'][0].startswith('!')
+
+ always:
+ - name: Unlock account
+ user:
+ name: ansibulluser
+ password_lock: no
diff --git a/test/integration/targets/user/tasks/test_password_lock_new_user.yml b/test/integration/targets/user/tasks/test_password_lock_new_user.yml
new file mode 100644
index 00000000..dd4f23da
--- /dev/null
+++ b/test/integration/targets/user/tasks/test_password_lock_new_user.yml
@@ -0,0 +1,63 @@
+- name: Test password lock
+ when: ansible_facts.system in ['FreeBSD', 'OpenBSD', 'Linux']
+ block:
+ - name: Remove ansibulluser
+ user:
+ name: ansibulluser
+ state: absent
+ remove: yes
+
+ - name: Create ansibulluser with password and locked
+ user:
+ name: ansibulluser
+ password: "$6$rounds=656000$TT4O7jz2M57npccl$33LF6FcUMSW11qrESXL1HX0BS.bsiT6aenFLLiVpsQh6hDtI9pJh5iY7x8J7ePkN4fP8hmElidHXaeD51pbGS."
+ password_lock: yes
+ register: create_with_lock_1
+
+ - name: Create ansibulluser with password and locked again
+ user:
+ name: ansibulluser
+ password: "$6$rounds=656000$TT4O7jz2M57npccl$33LF6FcUMSW11qrESXL1HX0BS.bsiT6aenFLLiVpsQh6hDtI9pJh5iY7x8J7ePkN4fP8hmElidHXaeD51pbGS."
+ password_lock: yes
+ register: create_with_lock_2
+
+ - name: Ensure task reported changes appropriately
+ assert:
+ msg: The password_lock tasks did not make changes appropriately
+ that:
+ - create_with_lock_1 is changed
+ - create_with_lock_2 is not changed
+
+ - name: Verify account lock for BSD
+ when: ansible_facts.system in ['FreeBSD', 'OpenBSD']
+ block:
+ - name: BSD | Get account status
+ shell: "{{ status_command[ansible_facts['system']] }}"
+ register: account_status_locked
+
+ - name: FreeBSD | Ensure account is locked
+ assert:
+ that:
+ - "'LOCKED' in account_status_locked.stdout"
+ when: ansible_facts.system == 'FreeBSD'
+
+
+ - name: Verify account lock for Linux
+ when: ansible_facts.system == 'Linux'
+ block:
+ - name: LINUX | Get account status
+ getent:
+ database: shadow
+ key: ansibulluser
+
+ - name: LINUX | Ensure account is locked
+ assert:
+ that:
+ - getent_shadow['ansibulluser'][0].startswith('!')
+
+
+ always:
+ - name: Unlock account
+ user:
+ name: ansibulluser
+ password_lock: no
diff --git a/test/integration/targets/user/tasks/test_remove_user.yml b/test/integration/targets/user/tasks/test_remove_user.yml
new file mode 100644
index 00000000..dea71cbf
--- /dev/null
+++ b/test/integration/targets/user/tasks/test_remove_user.yml
@@ -0,0 +1,19 @@
+- name: try to delete the user
+ user:
+ name: ansibulluser
+ state: absent
+ force: true
+ register: user_test2
+
+- name: make a new list of users
+ script: userlist.sh {{ ansible_facts.distribution }}
+ register: user_names2
+
+- debug:
+ var: user_names2
+ verbosity: 2
+
+- name: validate results for testcase 2
+ assert:
+ that:
+ - '"ansibulluser" not in user_names2.stdout_lines'
diff --git a/test/integration/targets/user/tasks/test_shadow_backup.yml b/test/integration/targets/user/tasks/test_shadow_backup.yml
new file mode 100644
index 00000000..2655fbf2
--- /dev/null
+++ b/test/integration/targets/user/tasks/test_shadow_backup.yml
@@ -0,0 +1,21 @@
+- name: Test shadow backup on Solaris
+ when: ansible_facts.os_family == 'Solaris'
+ block:
+ - name: Create a user to test shadow file backup
+ user:
+ name: ansibulluser
+ state: present
+ register: result
+
+ - name: Find shadow backup files
+ find:
+ path: /etc
+ patterns: 'shadow\..*~$'
+ use_regex: yes
+ register: shadow_backups
+
+ - name: Assert that a backup file was created
+ assert:
+ that:
+ - result.bakup
+ - shadow_backups.files | map(attribute='path') | list | length > 0
diff --git a/test/integration/targets/user/tasks/test_ssh_key_passphrase.yml b/test/integration/targets/user/tasks/test_ssh_key_passphrase.yml
new file mode 100644
index 00000000..bb0486da
--- /dev/null
+++ b/test/integration/targets/user/tasks/test_ssh_key_passphrase.yml
@@ -0,0 +1,29 @@
+# Test creating ssh key with passphrase
+- name: Remove ansibulluser
+ user:
+ name: ansibulluser
+ state: absent
+
+- name: Create user with ssh key
+ user:
+ name: ansibulluser
+ state: present
+ generate_ssh_key: yes
+ force: yes
+ ssh_key_file: "{{ output_dir }}/test_id_rsa"
+ ssh_key_passphrase: secret_passphrase
+
+- name: Unlock ssh key
+ command: "ssh-keygen -y -f {{ output_dir }}/test_id_rsa -P secret_passphrase"
+ register: result
+
+- name: Check that ssh key was unlocked successfully
+ assert:
+ that:
+ - result.rc == 0
+
+- name: Clean ssh key
+ file:
+ path: "{{ output_dir }}/test_id_rsa"
+ state: absent
+ when: ansible_os_family == 'FreeBSD'
diff --git a/test/integration/targets/vault/runme.sh b/test/integration/targets/vault/runme.sh
index e3b21d7f..197095bc 100755
--- a/test/integration/targets/vault/runme.sh
+++ b/test/integration/targets/vault/runme.sh
@@ -1,6 +1,8 @@
#!/usr/bin/env bash
set -euvx
+
+export ANSIBLE_TEST_PREFER_VENV=1
source virtualenv.sh
@@ -521,4 +523,4 @@ ansible-playbook -i ../../inventory -v "$@" --vault-password-file vault-password
# Ensure we don't leave unencrypted temp files dangling
ansible-playbook -v "$@" --vault-password-file vault-password test_dangling_temp.yml
-ansible-playbook "$@" --vault-password-file vault-password single_vault_as_string.yml \ No newline at end of file
+ansible-playbook "$@" --vault-password-file vault-password single_vault_as_string.yml
diff --git a/test/lib/ansible_test/_data/completion/remote.txt b/test/lib/ansible_test/_data/completion/remote.txt
index 109a8088..dea4367b 100644
--- a/test/lib/ansible_test/_data/completion/remote.txt
+++ b/test/lib/ansible_test/_data/completion/remote.txt
@@ -2,6 +2,7 @@ freebsd/11.1 python=2.7,3.6 python_dir=/usr/local/bin
freebsd/12.1 python=3.6,2.7 python_dir=/usr/local/bin
osx/10.11 python=2.7 python_dir=/usr/local/bin
macos/10.15 python=3.8 python_dir=/usr/local/bin
+macos/11.1 python=3.9 python_dir=/usr/local/bin
rhel/7.6 python=2.7
rhel/7.8 python=2.7
rhel/7.9 python=2.7
diff --git a/test/lib/ansible_test/_data/injector/virtualenv-isolated.sh b/test/lib/ansible_test/_data/injector/virtualenv-isolated.sh
index 82f79980..af92a056 100644
--- a/test/lib/ansible_test/_data/injector/virtualenv-isolated.sh
+++ b/test/lib/ansible_test/_data/injector/virtualenv-isolated.sh
@@ -2,7 +2,13 @@
# Create and activate a fresh virtual environment with `source virtualenv-isolated.sh`.
rm -rf "${OUTPUT_DIR}/venv"
-"${ANSIBLE_TEST_PYTHON_INTERPRETER}" -m virtualenv --python "${ANSIBLE_TEST_PYTHON_INTERPRETER}" "${OUTPUT_DIR}/venv"
+
+# Try to use 'venv' if it is available, then fallback to 'virtualenv' since some systems provide 'venv' although it is non-functional.
+if [ -z "${ANSIBLE_TEST_PREFER_VENV:-}" ] || [[ "${ANSIBLE_TEST_PYTHON_VERSION}" =~ ^2\. ]] || ! "${ANSIBLE_TEST_PYTHON_INTERPRETER}" -m venv "${OUTPUT_DIR}/venv" > /dev/null 2>&1; then
+ rm -rf "${OUTPUT_DIR}/venv"
+ "${ANSIBLE_TEST_PYTHON_INTERPRETER}" -m virtualenv --python "${ANSIBLE_TEST_PYTHON_INTERPRETER}" "${OUTPUT_DIR}/venv"
+fi
+
set +ux
source "${OUTPUT_DIR}/venv/bin/activate"
set -ux
diff --git a/test/lib/ansible_test/_data/injector/virtualenv.sh b/test/lib/ansible_test/_data/injector/virtualenv.sh
index ccde2974..282e6074 100644
--- a/test/lib/ansible_test/_data/injector/virtualenv.sh
+++ b/test/lib/ansible_test/_data/injector/virtualenv.sh
@@ -2,7 +2,13 @@
# Create and activate a fresh virtual environment with `source virtualenv.sh`.
rm -rf "${OUTPUT_DIR}/venv"
-"${ANSIBLE_TEST_PYTHON_INTERPRETER}" -m virtualenv --system-site-packages --python "${ANSIBLE_TEST_PYTHON_INTERPRETER}" "${OUTPUT_DIR}/venv"
+
+# Try to use 'venv' if it is available, then fallback to 'virtualenv' since some systems provide 'venv' although it is non-functional.
+if [ -z "${ANSIBLE_TEST_PREFER_VENV:-}" ] || [[ "${ANSIBLE_TEST_PYTHON_VERSION}" =~ ^2\. ]] || ! "${ANSIBLE_TEST_PYTHON_INTERPRETER}" -m venv --system-site-packages "${OUTPUT_DIR}/venv" > /dev/null 2>&1; then
+ rm -rf "${OUTPUT_DIR}/venv"
+ "${ANSIBLE_TEST_PYTHON_INTERPRETER}" -m virtualenv --system-site-packages --python "${ANSIBLE_TEST_PYTHON_INTERPRETER}" "${OUTPUT_DIR}/venv"
+fi
+
set +ux
source "${OUTPUT_DIR}/venv/bin/activate"
set -ux
diff --git a/test/lib/ansible_test/_data/requirements/constraints.txt b/test/lib/ansible_test/_data/requirements/constraints.txt
index f613ef0e..e043bc52 100644
--- a/test/lib/ansible_test/_data/requirements/constraints.txt
+++ b/test/lib/ansible_test/_data/requirements/constraints.txt
@@ -52,12 +52,12 @@ antsibull-changelog == 0.7.0
antsibull >= 0.21.0
# freeze pylint and its requirements for consistent test results
-astroid == 2.2.5
+astroid == 2.3.3
isort == 4.3.15
-lazy-object-proxy == 1.3.1
+lazy-object-proxy == 1.4.3
mccabe == 0.6.1
pylint == 2.3.1
-typed-ast == 1.4.0 # 1.4.0 is required to compile on Python 3.8
+typed-ast == 1.4.1
wrapt == 1.11.1
# freeze pycodestyle for consistent test results
diff --git a/test/lib/ansible_test/_data/requirements/sanity.pylint.txt b/test/lib/ansible_test/_data/requirements/sanity.pylint.txt
index 1b800bd0..438ca51d 100644
--- a/test/lib/ansible_test/_data/requirements/sanity.pylint.txt
+++ b/test/lib/ansible_test/_data/requirements/sanity.pylint.txt
@@ -1,3 +1,3 @@
-pylint ; python_version < '3.9' # installation fails on python 3.9.0b1
+pylint
pyyaml # needed for collection_detail.py
mccabe # pylint complexity testing
diff --git a/test/lib/ansible_test/_data/sanity/pylint/plugins/blacklist.py b/test/lib/ansible_test/_data/sanity/pylint/plugins/unwanted.py
index ac53aeda..7012feaa 100644
--- a/test/lib/ansible_test/_data/sanity/pylint/plugins/blacklist.py
+++ b/test/lib/ansible_test/_data/sanity/pylint/plugins/unwanted.py
@@ -14,8 +14,8 @@ ANSIBLE_TEST_MODULES_PATH = os.environ['ANSIBLE_TEST_MODULES_PATH']
ANSIBLE_TEST_MODULE_UTILS_PATH = os.environ['ANSIBLE_TEST_MODULE_UTILS_PATH']
-class BlacklistEntry:
- """Defines a import blacklist entry."""
+class UnwantedEntry:
+ """Defines an unwanted import."""
def __init__(self, alternative, modules_only=False, names=None, ignore_paths=None):
"""
:type alternative: str
@@ -58,11 +58,11 @@ def is_module_path(path):
return path.startswith(ANSIBLE_TEST_MODULES_PATH) or path.startswith(ANSIBLE_TEST_MODULE_UTILS_PATH)
-class AnsibleBlacklistChecker(BaseChecker):
- """Checker for blacklisted imports and functions."""
+class AnsibleUnwantedChecker(BaseChecker):
+ """Checker for unwanted imports and functions."""
__implements__ = (IAstroidChecker,)
- name = 'blacklist'
+ name = 'unwanted'
BAD_IMPORT = 'ansible-bad-import'
BAD_IMPORT_FROM = 'ansible-bad-import-from'
@@ -84,58 +84,58 @@ class AnsibleBlacklistChecker(BaseChecker):
'Identifies imports which should not be used.'),
)
- blacklist_imports = dict(
+ unwanted_imports = dict(
# Additional imports that we may want to start checking:
- # boto=BlacklistEntry('boto3', modules_only=True),
- # requests=BlacklistEntry('ansible.module_utils.urls', modules_only=True),
- # urllib=BlacklistEntry('ansible.module_utils.urls', modules_only=True),
+ # boto=UnwantedEntry('boto3', modules_only=True),
+ # requests=UnwantedEntry('ansible.module_utils.urls', modules_only=True),
+ # urllib=UnwantedEntry('ansible.module_utils.urls', modules_only=True),
# see https://docs.python.org/2/library/urllib2.html
- urllib2=BlacklistEntry('ansible.module_utils.urls',
- ignore_paths=(
- '/lib/ansible/module_utils/urls.py',
- )),
+ urllib2=UnwantedEntry('ansible.module_utils.urls',
+ ignore_paths=(
+ '/lib/ansible/module_utils/urls.py',
+ )),
# see https://docs.python.org/3.7/library/collections.abc.html
- collections=BlacklistEntry('ansible.module_utils.common._collections_compat',
- ignore_paths=(
- '/lib/ansible/module_utils/common/_collections_compat.py',
- ),
- names=(
- 'MappingView',
- 'ItemsView',
- 'KeysView',
- 'ValuesView',
- 'Mapping', 'MutableMapping',
- 'Sequence', 'MutableSequence',
- 'Set', 'MutableSet',
- 'Container',
- 'Hashable',
- 'Sized',
- 'Callable',
- 'Iterable',
- 'Iterator',
- )),
+ collections=UnwantedEntry('ansible.module_utils.common._collections_compat',
+ ignore_paths=(
+ '/lib/ansible/module_utils/common/_collections_compat.py',
+ ),
+ names=(
+ 'MappingView',
+ 'ItemsView',
+ 'KeysView',
+ 'ValuesView',
+ 'Mapping', 'MutableMapping',
+ 'Sequence', 'MutableSequence',
+ 'Set', 'MutableSet',
+ 'Container',
+ 'Hashable',
+ 'Sized',
+ 'Callable',
+ 'Iterable',
+ 'Iterator',
+ )),
)
- blacklist_functions = {
+ unwanted_functions = {
# see https://docs.python.org/2/library/tempfile.html#tempfile.mktemp
- 'tempfile.mktemp': BlacklistEntry('tempfile.mkstemp'),
-
- 'sys.exit': BlacklistEntry('exit_json or fail_json',
- ignore_paths=(
- '/lib/ansible/module_utils/basic.py',
- '/lib/ansible/modules/async_wrapper.py',
- '/lib/ansible/module_utils/common/removed.py',
- ),
- modules_only=True),
-
- 'builtins.print': BlacklistEntry('module.log or module.debug',
- ignore_paths=(
- '/lib/ansible/module_utils/basic.py',
- '/lib/ansible/module_utils/common/removed.py',
- ),
- modules_only=True),
+ 'tempfile.mktemp': UnwantedEntry('tempfile.mkstemp'),
+
+ 'sys.exit': UnwantedEntry('exit_json or fail_json',
+ ignore_paths=(
+ '/lib/ansible/module_utils/basic.py',
+ '/lib/ansible/modules/async_wrapper.py',
+ '/lib/ansible/module_utils/common/removed.py',
+ ),
+ modules_only=True),
+
+ 'builtins.print': UnwantedEntry('module.log or module.debug',
+ ignore_paths=(
+ '/lib/ansible/module_utils/basic.py',
+ '/lib/ansible/module_utils/common/removed.py',
+ ),
+ modules_only=True),
}
def visit_import(self, node):
@@ -163,7 +163,7 @@ class AnsibleBlacklistChecker(BaseChecker):
module = last_child.name
- entry = self.blacklist_imports.get(module)
+ entry = self.unwanted_imports.get(module)
if entry and entry.names:
if entry.applies_to(self.linter.current_file, node.attrname):
@@ -183,7 +183,7 @@ class AnsibleBlacklistChecker(BaseChecker):
if not func:
continue
- entry = self.blacklist_functions.get(func)
+ entry = self.unwanted_functions.get(func)
if entry and entry.applies_to(self.linter.current_file):
self.add_message(self.BAD_FUNCTION, args=(entry.alternative, func), node=node)
@@ -197,7 +197,7 @@ class AnsibleBlacklistChecker(BaseChecker):
"""
self._check_module_import(node, modname)
- entry = self.blacklist_imports.get(modname)
+ entry = self.unwanted_imports.get(modname)
if not entry:
return
@@ -213,7 +213,7 @@ class AnsibleBlacklistChecker(BaseChecker):
"""
self._check_module_import(node, modname)
- entry = self.blacklist_imports.get(modname)
+ entry = self.unwanted_imports.get(modname)
if not entry:
return
@@ -239,4 +239,4 @@ class AnsibleBlacklistChecker(BaseChecker):
def register(linter):
"""required method to auto register this checker """
- linter.register_checker(AnsibleBlacklistChecker(linter))
+ linter.register_checker(AnsibleUnwantedChecker(linter))
diff --git a/test/lib/ansible_test/_data/setup/remote.sh b/test/lib/ansible_test/_data/setup/remote.sh
index 654f678d..93dead5d 100644
--- a/test/lib/ansible_test/_data/setup/remote.sh
+++ b/test/lib/ansible_test/_data/setup/remote.sh
@@ -88,18 +88,10 @@ elif [ "${platform}" = "centos" ]; then
done
install_pip
-elif [ "${platform}" = "macos" ]; then
- while true; do
- pip3 install --disable-pip-version-check --quiet \
- 'virtualenv<20' \
- && break
- echo "Failed to install packages. Sleeping before trying again..."
- sleep 10
- done
elif [ "${platform}" = "osx" ]; then
while true; do
pip install --disable-pip-version-check --quiet \
- 'virtualenv<20' \
+ 'virtualenv==16.7.10' \
&& break
echo "Failed to install packages. Sleeping before trying again..."
sleep 10
diff --git a/test/lib/ansible_test/_internal/sanity/pylint.py b/test/lib/ansible_test/_internal/sanity/pylint.py
index 769a1717..324e5873 100644
--- a/test/lib/ansible_test/_internal/sanity/pylint.py
+++ b/test/lib/ansible_test/_internal/sanity/pylint.py
@@ -64,6 +64,14 @@ class PylintTest(SanitySingleVersion):
"""Error code for ansible-test matching the format used by the underlying test program, or None if the program does not use error codes."""
return 'ansible-test'
+ @property
+ def supported_python_versions(self): # type: () -> t.Optional[t.Tuple[str, ...]]
+ """A tuple of supported Python versions or None if the test does not depend on specific Python versions."""
+ # Python 3.9 is not supported on pylint < 2.5.0.
+ # Unfortunately pylint 2.5.0 and later include an unfixed regression.
+ # See: https://github.com/PyCQA/pylint/issues/3701
+ return tuple(python_version for python_version in super(PylintTest, self).supported_python_versions if python_version not in ('3.9',))
+
def filter_targets(self, targets): # type: (t.List[TestTarget]) -> t.List[TestTarget]
"""Return the given list of test targets, filtered to include only those relevant for the test."""
return [target for target in targets if os.path.splitext(target.path)[1] == '.py' or is_subdir(target.path, 'bin')]
diff --git a/test/sanity/ignore.txt b/test/sanity/ignore.txt
index e1b2b007..f1b926e6 100644
--- a/test/sanity/ignore.txt
+++ b/test/sanity/ignore.txt
@@ -211,6 +211,10 @@ test/integration/targets/ansible-runner/files/adhoc_example1.py future-import-bo
test/integration/targets/ansible-runner/files/adhoc_example1.py metaclass-boilerplate
test/integration/targets/ansible-runner/files/playbook_example1.py future-import-boilerplate
test/integration/targets/ansible-runner/files/playbook_example1.py metaclass-boilerplate
+test/integration/targets/ansible-test/ansible_collections/ns/col/tests/integration/targets/hello/files/bad.py pylint:ansible-bad-import
+test/integration/targets/ansible-test/ansible_collections/ns/col/tests/integration/targets/hello/files/bad.py pylint:ansible-bad-import-from
+test/integration/targets/ansible-test/ansible_collections/ns/col/tests/integration/targets/hello/files/bad.py pylint:ansible-bad-function
+test/integration/targets/ansible-test/ansible_collections/ns/col/plugins/filter/check_pylint.py pylint:blacklisted-name
test/integration/targets/ansible-test/ansible_collections/ns/col/plugins/modules/hello.py pylint:relative-beyond-top-level
test/integration/targets/ansible-test/ansible_collections/ns/col/tests/unit/plugins/module_utils/test_my_util.py pylint:relative-beyond-top-level
test/integration/targets/ansible-test/ansible_collections/ns/col/tests/unit/plugins/modules/test_hello.py pylint:relative-beyond-top-level