diff --git a/.github/workflows/ansible-test.yml b/.github/workflows/ansible-test.yml index 7ec5c5632..211e2ad59 100644 --- a/.github/workflows/ansible-test.yml +++ b/.github/workflows/ansible-test.yml @@ -40,6 +40,7 @@ jobs: - stable-2.11 - stable-2.12 - stable-2.13 + - stable-2.14 - devel steps: @@ -67,7 +68,7 @@ jobs: # will run on all python versions it supports. python-version: 3.9 - # Install the head of the given branch (devel, stable-2.13) + # Install the head of the given branch (devel, stable-2.14) - name: Install ansible-base (${{ matrix.ansible }}) run: pip install https://github.com/ansible/ansible/archive/${{ matrix.ansible }}.tar.gz --disable-pip-version-check @@ -116,6 +117,7 @@ jobs: - stable-2.11 - stable-2.12 - stable-2.13 + - stable-2.14 - devel steps: @@ -190,6 +192,7 @@ jobs: - stable-2.11 - stable-2.12 - stable-2.13 + - stable-2.14 - devel python: - '3.6' @@ -301,7 +304,7 @@ jobs: matrix: ansible: - stable-2.12 - - stable-2.13 + - stable-2.14 python: - 3.9 runner: @@ -311,11 +314,11 @@ jobs: - default exclude: # To add to the fragility of testing docker stuff on MacOS, - # stable-2.13 test containers crash; unsure of exact cause + # stable-2.13+ test containers crash; unsure of exact cause # but likely due to old versions of the runtimes. # We'll just stick to 2.12 for now, better than nothing. - runner: macos-12 - ansible: stable-2.13 + ansible: stable-2.14 - runner: ubuntu-latest ansible: stable-2.12 diff --git a/.github/workflows/docs-push.yml b/.github/workflows/docs-push.yml index 658030e68..82a989507 100644 --- a/.github/workflows/docs-push.yml +++ b/.github/workflows/docs-push.yml @@ -38,7 +38,7 @@ jobs: if: github.repository == 'ansible-collections/community.hashi_vault' permissions: contents: read - needs: [build-docs] + needs: [validate-docs, build-docs] name: Publish Ansible Docs uses: ansible-community/github-docs-build/.github/workflows/_shared-docs-build-publish-surge.yml@main with: @@ -52,7 +52,7 @@ jobs: if: github.repository == 'ansible-collections/community.hashi_vault' permissions: contents: write - needs: [build-docs] + needs: [validate-docs, build-docs] name: Publish Ansible Docs uses: ansible-community/github-docs-build/.github/workflows/_shared-docs-build-publish-gh-pages.yml@main with: diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 16bfc8439..c25c812e8 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -5,6 +5,28 @@ community.hashi_vault Release Notes .. contents:: Topics +v3.3.1 +====== + +Release Summary +--------------- + +No functional changes in this release, this provides updated filter documentation for the public docsite. + +v3.3.0 +====== + +Release Summary +--------------- + +With the release of ``hvac`` version ``1.0.0``, we needed to update ``vault_token_create``'s support for orphan tokens. +The collection's changelog is now viewable in the Ansible documentation site. + +Minor Changes +------------- + +- vault_token_create - creation or orphan tokens uses ``hvac``'s new v1 method for creating orphans, or falls back to the v0 method if needed (https://github.com/ansible-collections/community.hashi_vault/issues/301). + v3.2.0 ====== diff --git a/README.md b/README.md index f1b4db97d..f4f76a23e 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,7 @@ If you use the Ansible package and don't update collections independently, use * * 2.11 * 2.12 * 2.13 +* 2.14 * devel (latest development commit) See [the CI configuration](https://github.com/ansible-collections/community.hashi_vault/blob/main/.github/workflows/ansible-test.yml) for the most accurate testing information. diff --git a/changelogs/changelog.yaml b/changelogs/changelog.yaml index 4e7ca01e8..18d6e4e38 100644 --- a/changelogs/changelog.yaml +++ b/changelogs/changelog.yaml @@ -526,3 +526,23 @@ releases: - 296-use-before-assignment.yml - 3.2.0.yml release_date: '2022-08-21' + 3.3.0: + changes: + minor_changes: + - vault_token_create - creation or orphan tokens uses ``hvac``'s new v1 method + for creating orphans, or falls back to the v0 method if needed (https://github.com/ansible-collections/community.hashi_vault/issues/301). + release_summary: 'With the release of ``hvac`` version ``1.0.0``, we needed + to update ``vault_token_create``''s support for orphan tokens. + + The collection''s changelog is now viewable in the Ansible documentation site.' + fragments: + - 3.3.0.yml + - 301-orphan-token-handling.yml + release_date: '2022-09-19' + 3.3.1: + changes: + release_summary: No functional changes in this release, this provides updated + filter documentation for the public docsite. + fragments: + - 3.3.1.yml + release_date: '2022-09-25' diff --git a/docs/docsite/rst/filter_guide.rst b/docs/docsite/rst/filter_guide.rst index 29ddbab78..8cee2b987 100644 --- a/docs/docsite/rst/filter_guide.rst +++ b/docs/docsite/rst/filter_guide.rst @@ -3,6 +3,11 @@ Filter guide ============ +.. note:: + + Filter Plugins are now included with other :ref:`plugin documentation `. + + .. contents:: Filters .. _ansible_collections.community.hashi_vault.docsite.filter_guide.vault_login_token: @@ -111,7 +116,7 @@ Which produces: "msg": "s.drgLxu6ZtttSVn5Zkoy0huMR" } -This filter is the equivalent of reading into the dictionary directly, but it has the advantage of providing semantic meaning and automatically working against the differing output of both the module and the lookup. +This filter is the equivalent of reading into the dictionary directly, but it has the advantages of providing semantic meaning and automatically working against the differing output of modules and lookups. .. code-block:: yaml+jinja diff --git a/docs/preview/.gitignore b/docs/preview/.gitignore index 66db1004b..2def98f08 100644 --- a/docs/preview/.gitignore +++ b/docs/preview/.gitignore @@ -1,3 +1,7 @@ +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + /temp-rst /build /rst/collections diff --git a/docs/preview/antsibull-docs.cfg b/docs/preview/antsibull-docs.cfg new file mode 100644 index 000000000..9714411eb --- /dev/null +++ b/docs/preview/antsibull-docs.cfg @@ -0,0 +1,22 @@ +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +breadcrumbs = true +indexes = true +use_html_blobs = false + +# You can specify ways to convert a collection name (.) to an URL here. +# You can replace either of or by "*" to match all values in that place, +# or use "*" for the collection name to match all collections. In the URL, you can use +# {namespace} and {name} for the two components of the collection name. If you want to use +# "{" or "}" in the URL, write "{{" or "}}" instead. Basically these are Python format +# strings (https://docs.python.org/3.8/library/string.html#formatstrings). +collection_url = { + * = "https://galaxy.ansible.com/{namespace}/{name}" +} + +# The same wildcard rules and formatting rules as for collection_url apply. +collection_install = { + * = "ansible-galaxy collection install {namespace}.{name}" +} diff --git a/docs/preview/build.sh b/docs/preview/build.sh index a19c8c81f..eeb0c4f8e 100755 --- a/docs/preview/build.sh +++ b/docs/preview/build.sh @@ -1,19 +1,25 @@ #!/usr/bin/env bash +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + set -e pushd "${BASH_SOURCE%/*}" # Create collection documentation into temporary directory rm -rf temp-rst mkdir -p temp-rst -antsibull-docs collection \ +antsibull-docs \ + --config-file antsibull-docs.cfg \ + collection \ --use-current \ --dest-dir temp-rst \ community.hashi_vault # Copy collection documentation into source directory -rsync -avc --delete-after temp-rst/collections/ rst/collections/ +rsync -cprv --delete-after temp-rst/collections/ rst/collections/ # Build Sphinx site -sphinx-build -M html rst build -c . +sphinx-build -M html rst build -c . -W --keep-going popd diff --git a/docs/preview/conf.py b/docs/preview/conf.py index 6862c5968..02685b344 100644 --- a/docs/preview/conf.py +++ b/docs/preview/conf.py @@ -1,3 +1,7 @@ +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + # This file only contains a selection of the most common options. For a full list see the # documentation: # http://www.sphinx-doc.org/en/master/config diff --git a/docs/preview/requirements.txt b/docs/preview/requirements.txt index 633de27cf..afc7e88e8 100644 --- a/docs/preview/requirements.txt +++ b/docs/preview/requirements.txt @@ -1,4 +1,8 @@ -antsibull-docs +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +antsibull-docs >= 1.0.0, < 2.0.0 ansible-pygments -sphinx -sphinx-ansible-theme +sphinx != 5.2.0.post0 # temporary, see https://github.com/ansible-community/antsibull-docs/issues/39, https://github.com/ansible-community/antsibull-docs/issues/40 +sphinx-ansible-theme >= 0.9.0 diff --git a/docs/preview/rst/index.rst b/docs/preview/rst/index.rst index dc3d17ed8..19db644bc 100644 --- a/docs/preview/rst/index.rst +++ b/docs/preview/rst/index.rst @@ -1,9 +1,13 @@ +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + .. _docsite_root_index: Ansible collection documentation preview ======================================== -This docsite contains documentation from ``community.hashi_vault``. +This docsite contains documentation for ``community.hashi_vault``. .. toctree:: diff --git a/galaxy.yml b/galaxy.yml index f3f9a527c..9086bd738 100644 --- a/galaxy.yml +++ b/galaxy.yml @@ -2,7 +2,7 @@ namespace: community name: hashi_vault -version: 3.3.0 +version: 3.4.0 readme: README.md authors: - Julie Davila (@juliedavila) diff --git a/plugins/doc_fragments/token_create.py b/plugins/doc_fragments/token_create.py index bd1dfd519..30031f076 100644 --- a/plugins/doc_fragments/token_create.py +++ b/plugins/doc_fragments/token_create.py @@ -15,15 +15,12 @@ class ModuleDocFragment(object): orphan: description: - When C(true), uses the C(/create-orphan) API endpoint, which requires C(sudo) (but not C(root)) to create an orphan. - - Implies I(no_parent=true). - - B(NOTE:) as of this writing, the underlying endpoint in the C(hvac) library to support this is deprecated and scheduled for removal in C(v1.0.0). - - If I(orphan=true) and we cannot access the intended endpoint, the call will be attempted with the C(/create) endpoint, which requires root. - - If a replacement is provided in C(hvac), we will add support for it. + - With C(hvac>=1.0.0), requires collection version C(>=3.3.0). type: bool default: false no_parent: description: - - This option only has effect if used by a C(root) or C(sudo) caller, or in combination with I(orphan=true). + - This option only has effect if used by a C(root) or C(sudo) caller and only when I(orphan=false). - When C(true), the token created will not have a parent. type: bool no_default_policy: diff --git a/plugins/filter/vault_login_token.yml b/plugins/filter/vault_login_token.yml new file mode 100644 index 000000000..e2946bafe --- /dev/null +++ b/plugins/filter/vault_login_token.yml @@ -0,0 +1,98 @@ +# (c) 2022, Brian Scholer (@briantist) +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later +--- +DOCUMENTATION: + name: vault_login_token + short_description: Extracts the Vault token from a login or token creation + version_added: 2.2.0 + description: + - Extracts the token value from the structure returned by a Vault token creation operation. + seealso: + - module: community.hashi_vault.vault_login + - module: community.hashi_vault.vault_token_create + - plugin: community.hashi_vault.vault_login + plugin_type: lookup + - plugin: community.hashi_vault.vault_token_create + plugin_type: lookup + - ref: Filter Guide + description: The C(community.hashi_vault) Filter Guide + notes: + - >- + This filter is the same as reading into the I(_input) dictionary directly, + but it provides semantic meaning and automatically works with the differing output of the modules and lookups. + See the Filter guide for more information. + options: + _input: + description: + - A dictionary matching the structure returned by a login or token creation. + type: dict + required: true + optional_field: + description: + - >- + If this field exists in the input dictionary, then the value of that field is used as the I(_input) value. + - >- + The default value deals with the difference between the output of lookup plugins, + and does not need to be changed in most cases. + - See the examples or the Filter guide for more information. + type: string + default: login + author: + - Brian Scholer (@briantist) + +EXAMPLES: | + - name: Set defaults + vars: + ansible_hashi_vault_url: https://vault:9801/ + ansible_hashi_vault_auth_method: userpass + ansible_hashi_vault_username: user + ansible_hashi_vault_password: "{{ lookup('env', 'MY_SECRET_PASSWORD') }}" + module_defaults: + community.hashi_vault.vault_login: + url: '{{ ansible_hashi_vault_url }}' + auth_method: '{{ ansible_hashi_vault_auth_method }}' + username: '{{ ansible_hashi_vault_username }}' + password: '{{ ansible_hashi_vault_password }}' + block: + - name: Perform a login with a lookup and display the token + vars: + login_response: "{{ lookup('community.hashi_vault.vault_login') }}" + debug: + msg: "The token is {{ login_response | community.hashi_vault.vault_login_token }}" + + - name: Perform a login with a module + community.hashi_vault.vault_login: + register: login_response + + - name: Display the token + debug: + msg: "The token is {{ login_response | community.hashi_vault.vault_login_token }}" + + - name: Use of optional_field + vars: + lookup_login_response: "{{ lookup('community.hashi_vault.vault_login') }}" + my_data: + something: somedata + vault_login: "{{ lookup_login_response }}" + + token_from_param: "{{ my_data | community.hashi_vault.vault_login_token(optional_field='vault_login') }}" + token_from_deref: "{{ my_data['vault_login'] | community.hashi_vault.vault_login_token }}" + # if the optional field doesn't exist, the dictionary itself is still checked + unused_optional: "{{ my_data['vault_login'] | community.hashi_vault.vault_login_token(optional_field='missing') }}" + block: + - name: Display the variables + ansible.builtin.debug: + var: '{{ item }}' + loop: + - my_data + - token_from_param + - token_from_deref + - unused_optional + +RETURN: + _value: + description: The token value. + returned: always + sample: s.nnrpog4i5gjizr6b8g1inwj3 + type: string diff --git a/plugins/lookup/vault_login.py b/plugins/lookup/vault_login.py index 5248fbd01..08980ecaa 100644 --- a/plugins/lookup/vault_login.py +++ b/plugins/lookup/vault_login.py @@ -18,7 +18,7 @@ - Performs a login operation against a given path in HashiCorp Vault, returning the login response, including the token. seealso: - module: community.hashi_vault.vault_login - - ref: community.hashi_vault.vault_login_token filter + - ref: community.hashi_vault.vault_login_token filter description: The official documentation for the C(community.hashi_vault.vault_login_token) filter plugin. notes: - This lookup does not use the term string and will not work correctly in loops. Only a single response will be returned. diff --git a/plugins/lookup/vault_token_create.py b/plugins/lookup/vault_token_create.py index 06757f657..9b19ae290 100644 --- a/plugins/lookup/vault_token_create.py +++ b/plugins/lookup/vault_token_create.py @@ -21,7 +21,7 @@ - ref: community.hashi_vault.vault_login lookup description: The official documentation for the C(community.hashi_vault.vault_login) lookup plugin. - module: community.hashi_vault.vault_login - - ref: community.hashi_vault.vault_login_token filter + - ref: community.hashi_vault.vault_login_token filter description: The official documentation for the C(community.hashi_vault.vault_login_token) filter plugin. notes: - Token creation is a write operation (creating a token persisted to storage), so this module always reports C(changed=True). @@ -134,7 +134,7 @@ class LookupModule(HashiVaultLookupBase): 'wrap_ttl', ] - LEGACY_OPTION_TRANSLATION = { + ORPHAN_OPTION_TRANSLATION = { 'id': 'token_id', 'role_name': 'role', 'type': 'token_type', @@ -166,29 +166,27 @@ def run(self, terms, variables=None, **kwargs): pass_thru_options = self._options_adapter.get_filled_options(*self.PASS_THRU_OPTION_NAMES) - if self.get_option('orphan'): - pass_thru_options['no_parent'] = True - - legacy_options = pass_thru_options.copy() + orphan_options = pass_thru_options.copy() for key in pass_thru_options.keys(): - if key in self.LEGACY_OPTION_TRANSLATION: - legacy_options[self.LEGACY_OPTION_TRANSLATION[key]] = legacy_options.pop(key) + if key in self.ORPHAN_OPTION_TRANSLATION: + orphan_options[self.ORPHAN_OPTION_TRANSLATION[key]] = orphan_options.pop(key) response = None if self.get_option('orphan'): - # this method is deprecated, but it's the only way through hvac to get - # at the /create-orphan endpoint at this time. - # See: https://github.com/hvac/hvac/issues/758 try: - response = client.create_token(orphan=True, **legacy_options) - except AttributeError: - display.warning("'create_token' method was not found. Attempting method that requires root privileges.") + try: + # this method was added in hvac 1.0.0 + # See: https://github.com/hvac/hvac/pull/869 + response = client.auth.token.create_orphan(**orphan_options) + except AttributeError: + # this method was removed in hvac 1.0.0 + # See: https://github.com/hvac/hvac/issues/758 + response = client.create_token(orphan=True, **orphan_options) except Exception as e: raise AnsibleError(e) - - if response is None: + else: try: response = client.auth.token.create(**pass_thru_options) except Exception as e: diff --git a/plugins/modules/vault_login.py b/plugins/modules/vault_login.py index 46df8e0e6..0cc411cec 100644 --- a/plugins/modules/vault_login.py +++ b/plugins/modules/vault_login.py @@ -21,7 +21,7 @@ seealso: - ref: community.hashi_vault.vault_login lookup description: The official documentation for the C(community.hashi_vault.vault_login) lookup plugin. - - ref: community.hashi_vault.vault_login_token filter + - ref: community.hashi_vault.vault_login_token filter description: The official documentation for the C(community.hashi_vault.vault_login_token) filter plugin. extends_documentation_fragment: - community.hashi_vault.connection diff --git a/plugins/modules/vault_token_create.py b/plugins/modules/vault_token_create.py index ffdaffb53..4fd757b06 100644 --- a/plugins/modules/vault_token_create.py +++ b/plugins/modules/vault_token_create.py @@ -24,7 +24,7 @@ - module: community.hashi_vault.vault_login - ref: community.hashi_vault.vault_login lookup description: The official documentation for the C(community.hashi_vault.vault_login) lookup plugin. - - ref: community.hashi_vault.vault_login_token filter + - ref: community.hashi_vault.vault_login_token filter description: The official documentation for the C(community.hashi_vault.vault_login_token) filter plugin. extends_documentation_fragment: - community.hashi_vault.connection @@ -131,7 +131,7 @@ ] -LEGACY_OPTION_TRANSLATION = { +ORPHAN_OPTION_TRANSLATION = { 'id': 'token_id', 'role_name': 'role', 'type': 'token_type', @@ -175,14 +175,11 @@ def run_module(): pass_thru_options = module.adapter.get_filled_options(*PASS_THRU_OPTION_NAMES) - if module.adapter.get_option('orphan'): - pass_thru_options['no_parent'] = True - - legacy_options = pass_thru_options.copy() + orphan_options = pass_thru_options.copy() for key in pass_thru_options.keys(): - if key in LEGACY_OPTION_TRANSLATION: - legacy_options[LEGACY_OPTION_TRANSLATION[key]] = legacy_options.pop(key) + if key in ORPHAN_OPTION_TRANSLATION: + orphan_options[ORPHAN_OPTION_TRANSLATION[key]] = orphan_options.pop(key) # token creation is a write operation, using storage and resources changed = True @@ -192,17 +189,18 @@ def run_module(): module.exit_json(changed=changed, login={'auth': {'client_token': None}}) if module.adapter.get_option('orphan'): - # this method is deprecated, but it's the only way through hvac to get - # at the /create-orphan endpoint at this time. - # See: https://github.com/hvac/hvac/issues/758 try: - response = client.create_token(orphan=True, **legacy_options) - except AttributeError: - module.warn("'create_token' method was not found. Attempting method that requires root privileges.") + try: + # this method was added in hvac 1.0.0 + # See: https://github.com/hvac/hvac/pull/869 + response = client.auth.token.create_orphan(**orphan_options) + except AttributeError: + # this method was removed in hvac 1.0.0 + # See: https://github.com/hvac/hvac/issues/758 + response = client.create_token(orphan=True, **orphan_options) except Exception as e: module.fail_json(msg=to_native(e), exception=traceback.format_exc()) - - if response is None: + else: try: response = client.auth.token.create(**pass_thru_options) except Exception as e: diff --git a/tests/integration/targets/lookup_vault_token_create/tasks/lookup_vault_token_create_test.yml b/tests/integration/targets/lookup_vault_token_create/tasks/lookup_vault_token_create_test.yml index f32de9996..de2c9cd7a 100644 --- a/tests/integration/targets/lookup_vault_token_create/tasks/lookup_vault_token_create_test.yml +++ b/tests/integration/targets/lookup_vault_token_create/tasks/lookup_vault_token_create_test.yml @@ -35,7 +35,7 @@ - assert: that: - orphan_result is failed - - orphan_result.msg is search('root or sudo privileges required to create orphan token') + - orphan_result.msg is search('permission denied') - name: (xfail) Create an orphan token with no_parent=true vars: diff --git a/tests/integration/targets/module_vault_token_create/tasks/module_vault_token_create_test.yml b/tests/integration/targets/module_vault_token_create/tasks/module_vault_token_create_test.yml index 2294aa331..947d530b5 100644 --- a/tests/integration/targets/module_vault_token_create/tasks/module_vault_token_create_test.yml +++ b/tests/integration/targets/module_vault_token_create/tasks/module_vault_token_create_test.yml @@ -37,7 +37,7 @@ - assert: that: - orphan_result is failed - - orphan_result.msg is search('root or sudo privileges required to create orphan token') + - orphan_result.msg is search('permission denied') - name: (xfail) Create an orphan token with no_parent=true register: no_parent_result diff --git a/tests/integration/targets/setup_vault_configure/vars/main.yml b/tests/integration/targets/setup_vault_configure/vars/main.yml index 29759d8b0..2d76ea951 100644 --- a/tests/integration/targets/setup_vault_configure/vars/main.yml +++ b/tests/integration/targets/setup_vault_configure/vars/main.yml @@ -77,9 +77,6 @@ vault_token_creator_policy: | path "auth/token/create/*" { capabilities = ["create", "update"] } - path "auth/token/create-orphan" { - capabilities = ["create", "update"] - } vault_orphan_creator_policy: | path "auth/token/create" { diff --git a/tests/unit/plugins/lookup/test_vault_token_create.py b/tests/unit/plugins/lookup/test_vault_token_create.py index 0407a475b..05a74f8b4 100644 --- a/tests/unit/plugins/lookup/test_vault_token_create.py +++ b/tests/unit/plugins/lookup/test_vault_token_create.py @@ -17,6 +17,7 @@ from ansible_collections.community.hashi_vault.plugins.plugin_utils._hashi_vault_lookup_base import HashiVaultLookupBase from .....plugins.lookup import vault_token_create +from .....plugins.module_utils._hashi_vault_common import HashiVaultValueError pytest.importorskip('hvac') @@ -55,7 +56,7 @@ def pass_thru_options(): @pytest.fixture -def legacy_option_translation(): +def orphan_option_translation(): return { 'id': 'token_id', 'role_name': 'role', @@ -78,6 +79,20 @@ def test_vault_token_create_no_hvac(self, vault_token_create_lookup, minimal_var with pytest.raises(AnsibleError, match=r"This plugin requires the 'hvac' Python library"): vault_token_create_lookup.run(terms='fake', variables=minimal_vars) + @pytest.mark.parametrize('exc', [HashiVaultValueError('throwaway msg'), NotImplementedError('throwaway msg')]) + def test_vault_token_create_authentication_error(self, vault_token_create_lookup, minimal_vars, authenticator, exc): + authenticator.authenticate.side_effect = exc + + with pytest.raises(AnsibleError, match=r'throwaway msg'): + vault_token_create_lookup.run(terms='fake', variables=minimal_vars) + + @pytest.mark.parametrize('exc', [HashiVaultValueError('throwaway msg'), NotImplementedError('throwaway msg')]) + def test_vault_token_create_auth_validation_error(self, vault_token_create_lookup, minimal_vars, authenticator, exc): + authenticator.validate.side_effect = exc + + with pytest.raises(AnsibleError, match=r'throwaway msg'): + vault_token_create_lookup.run(terms='fake', variables=minimal_vars) + def test_vault_token_create_extra_terms(self, vault_token_create_lookup, authenticator, minimal_vars): with mock.patch('ansible_collections.community.hashi_vault.plugins.lookup.vault_token_create.display.warning') as warning: with mock.patch.object(vault_token_create_lookup, 'authenticator', new=authenticator): @@ -97,24 +112,24 @@ def test_vault_token_create_passthru_options_expected(self, vault_token_create_l ) ) - def test_vault_token_create_legacy_options_expected(self, vault_token_create_lookup, legacy_option_translation, pass_thru_options): - # designed to catch the case where new legacy translations differ between tests and lookup + def test_vault_token_create_orphan_options_expected(self, vault_token_create_lookup, orphan_option_translation, pass_thru_options): + # designed to catch the case where new orphan translations differ between tests and lookup # and that all listed translations are present in passthru options - lookup_set = set(vault_token_create_lookup.LEGACY_OPTION_TRANSLATION.items()) - test_set = set(legacy_option_translation.items()) + lookup_set = set(vault_token_create_lookup.ORPHAN_OPTION_TRANSLATION.items()) + test_set = set(orphan_option_translation.items()) - lookup_key_set = set(vault_token_create_lookup.LEGACY_OPTION_TRANSLATION.keys()) + lookup_key_set = set(vault_token_create_lookup.ORPHAN_OPTION_TRANSLATION.keys()) pass_thru_key_set = set(pass_thru_options.keys()) assert lookup_set == test_set, ( - "Legacy options in lookup do not match legacy options in test:\nlookup: %r\ntest: %r" % ( + "Orphan options in lookup do not match orphan options in test:\nlookup: %r\ntest: %r" % ( dict(lookup_set - test_set), dict(test_set - lookup_set), ) ) - assert vault_token_create_lookup.LEGACY_OPTION_TRANSLATION.keys() <= pass_thru_options.keys(), ( - "Legacy option translation keys must exist in passthru options: %r" % ( + assert vault_token_create_lookup.ORPHAN_OPTION_TRANSLATION.keys() <= pass_thru_options.keys(), ( + "Orphan option translation keys must exist in passthru options: %r" % ( list(lookup_key_set - pass_thru_key_set), ) ) @@ -141,19 +156,20 @@ def test_vault_token_create_passthru_options(self, vault_token_create_lookup, au else: assert pass_thru_options.items() <= client.auth.token.create.call_args.kwargs.items() - def test_vault_token_create_legacy_options( - self, vault_token_create_lookup, authenticator, minimal_vars, pass_thru_options, legacy_option_translation, token_create_response + def test_vault_token_create_orphan_options( + self, vault_token_create_lookup, authenticator, minimal_vars, pass_thru_options, orphan_option_translation, token_create_response ): client = mock.MagicMock() - client.create_token.return_value = token_create_response + client.auth.token.create_orphan.return_value = token_create_response with mock.patch.object(vault_token_create_lookup, 'authenticator', new=authenticator): with mock.patch.object(vault_token_create_lookup.helper, 'get_vault_client', return_value=client): result = vault_token_create_lookup.run(terms=[], variables=minimal_vars, orphan=True, **pass_thru_options) client.auth.token.create.assert_not_called() - client.create_token.assert_called_once() + client.auth.token.create_orphan.assert_called_once() + client.create_token.assert_not_called() assert result[0] == token_create_response, ( "lookup result did not match expected result:\nlookup: %r\nexpected: %r" % (result, token_create_response) @@ -161,46 +177,65 @@ def test_vault_token_create_legacy_options( if sys.version_info < (3, 8): # TODO: remove when python < 3.8 is dropped - call_kwargs = client.create_token.call_args[1] + call_kwargs = client.auth.token.create_orphan.call_args[1] else: - call_kwargs = client.create_token.call_args.kwargs + call_kwargs = client.auth.token.create_orphan.call_args.kwargs - for name, legacy in legacy_option_translation.items(): + for name, orphan in orphan_option_translation.items(): assert name not in call_kwargs, ( - "'%s' was found in call to legacy method, should be '%s'" % (name, legacy) + "'%s' was found in call to orphan method, should be '%s'" % (name, orphan) ) - assert legacy in call_kwargs, ( - "'%s' (from '%s') was not found in call to legacy method" % (legacy, name) + assert orphan in call_kwargs, ( + "'%s' (from '%s') was not found in call to orphan method" % (orphan, name) ) - assert call_kwargs[legacy] == pass_thru_options.get(name), ( - "Expected legacy param '%s' not found or value did not match:\nvalue: %r\nexpected: %r" % ( - legacy, - call_kwargs.get(legacy), + assert call_kwargs[orphan] == pass_thru_options.get(name), ( + "Expected orphan param '%s' not found or value did not match:\nvalue: %r\nexpected: %r" % ( + orphan, + call_kwargs.get(orphan), pass_thru_options.get(name), ) ) - def test_vault_token_create_legacy_fallback(self, vault_token_create_lookup, authenticator, minimal_vars, pass_thru_options, token_create_response): + def test_vault_token_create_orphan_fallback(self, vault_token_create_lookup, authenticator, minimal_vars, pass_thru_options, token_create_response): client = mock.MagicMock() - client.create_token.side_effect = AttributeError - client.auth.token.create.return_value = token_create_response + client.create_token.return_value = token_create_response + client.auth.token.create_orphan.side_effect = AttributeError - with mock.patch('ansible_collections.community.hashi_vault.plugins.lookup.vault_token_create.display.warning') as warning: - with mock.patch.object(vault_token_create_lookup, 'authenticator', new=authenticator): - with mock.patch.object(vault_token_create_lookup.helper, 'get_vault_client', return_value=client): - result = vault_token_create_lookup.run(terms=[], variables=minimal_vars, orphan=True, **pass_thru_options) + with mock.patch.object(vault_token_create_lookup, 'authenticator', new=authenticator): + with mock.patch.object(vault_token_create_lookup.helper, 'get_vault_client', return_value=client): + result = vault_token_create_lookup.run(terms=[], variables=minimal_vars, orphan=True, **pass_thru_options) - warning.assert_called_once_with("'create_token' method was not found. Attempting method that requires root privileges.") - client.auth.token.create.assert_called_once() + client.auth.token.create_orphan.assert_called_once() + client.create_token.assert_called_once() - assert result[0] == token_create_response, ( - "lookup result did not match expected result:\nlookup: %r\nexpected: %r" % (result, token_create_response) - ) + assert result[0] == token_create_response, ( + "lookup result did not match expected result:\nlookup: %r\nexpected: %r" % (result, token_create_response) + ) + + def test_vault_token_create_exception_handling_standard(self, vault_token_create_lookup, authenticator, minimal_vars, pass_thru_options): + client = mock.MagicMock() + client.auth.token.create.side_effect = Exception('side_effect') + + with mock.patch.object(vault_token_create_lookup, 'authenticator', new=authenticator): + with mock.patch.object(vault_token_create_lookup.helper, 'get_vault_client', return_value=client): + with pytest.raises(AnsibleError, match=r'^side_effect$'): + vault_token_create_lookup.run(terms=[], variables=minimal_vars, **pass_thru_options) + + def test_vault_token_create_exception_handling_orphan(self, vault_token_create_lookup, authenticator, minimal_vars, pass_thru_options): + client = mock.MagicMock() + client.auth.token.create_orphan.side_effect = Exception('side_effect') + + with mock.patch.object(vault_token_create_lookup, 'authenticator', new=authenticator): + with mock.patch.object(vault_token_create_lookup.helper, 'get_vault_client', return_value=client): + with pytest.raises(AnsibleError, match=r'^side_effect$'): + vault_token_create_lookup.run(terms=[], variables=minimal_vars, orphan=True, **pass_thru_options) - # we're retesting that expected options were passed, even though there's a separate test for that, - # to ensure that nothing in the original legacy attempt mutates the non-legacy options during fallback - if sys.version_info < (3, 8): - # TODO: remove when python < 3.8 is dropped - assert pass_thru_options.items() <= client.auth.token.create.call_args[1].items() - else: - assert pass_thru_options.items() <= client.auth.token.create.call_args.kwargs.items() + def test_vault_token_create_exception_handling_orphan_fallback(self, vault_token_create_lookup, authenticator, minimal_vars, pass_thru_options): + client = mock.MagicMock() + client.create_token.side_effect = Exception('side_effect') + client.auth.token.create_orphan.side_effect = AttributeError + + with mock.patch.object(vault_token_create_lookup, 'authenticator', new=authenticator): + with mock.patch.object(vault_token_create_lookup.helper, 'get_vault_client', return_value=client): + with pytest.raises(AnsibleError, match=r'^side_effect$'): + vault_token_create_lookup.run(terms=[], variables=minimal_vars, orphan=True, **pass_thru_options) diff --git a/tests/unit/plugins/modules/test_vault_token_create.py b/tests/unit/plugins/modules/test_vault_token_create.py index f23cf9e4a..24e44b800 100644 --- a/tests/unit/plugins/modules/test_vault_token_create.py +++ b/tests/unit/plugins/modules/test_vault_token_create.py @@ -12,6 +12,7 @@ import json from .....plugins.modules import vault_token_create +from .....plugins.module_utils._hashi_vault_common import HashiVaultValueError pytestmark = pytest.mark.usefixtures( 'patch_ansible_module', @@ -24,7 +25,7 @@ def _connection_options(): return { 'auth_method': 'token', 'url': 'http://myvault', - 'token': 'throwaway', + 'token': 'rando', } @@ -61,7 +62,7 @@ def pass_thru_options(): @pytest.fixture -def legacy_option_translation(): +def orphan_option_translation(): return { 'id': 'token_id', 'role_name': 'role', @@ -76,6 +77,34 @@ def token_create_response(fixture_loader): class TestModuleVaultTokenCreate(): + @pytest.mark.parametrize('patch_ansible_module', [_combined_options()], indirect=True) + @pytest.mark.parametrize('exc', [HashiVaultValueError('throwaway msg'), NotImplementedError('throwaway msg')]) + def test_vault_token_create_authentication_error(self, authenticator, exc, capfd): + authenticator.authenticate.side_effect = exc + + with pytest.raises(SystemExit) as e: + vault_token_create.main() + + out, err = capfd.readouterr() + result = json.loads(out) + + assert e.value.code != 0, "result: %r" % (result,) + assert result['msg'] == 'throwaway msg', "result: %r" % result + + @pytest.mark.parametrize('patch_ansible_module', [_combined_options()], indirect=True) + @pytest.mark.parametrize('exc', [HashiVaultValueError('throwaway msg'), NotImplementedError('throwaway msg')]) + def test_vault_token_create_auth_validation_error(self, authenticator, exc, capfd): + authenticator.validate.side_effect = exc + + with pytest.raises(SystemExit) as e: + vault_token_create.main() + + out, err = capfd.readouterr() + result = json.loads(out) + + assert e.value.code != 0, "result: %r" % (result,) + assert result['msg'] == 'throwaway msg' + @pytest.mark.no_ansible_module_patch def test_vault_token_create_passthru_options_expected(self, pass_thru_options): # designed to catch the case where new passthru options differ between tests and module @@ -90,24 +119,24 @@ def test_vault_token_create_passthru_options_expected(self, pass_thru_options): ) @pytest.mark.no_ansible_module_patch - def test_vault_token_create_legacy_options_expected(self, legacy_option_translation, pass_thru_options): - # designed to catch the case where new legacy translations differ between tests and module + def test_vault_token_create_orphan_options_expected(self, orphan_option_translation, pass_thru_options): + # designed to catch the case where new orphan translations differ between tests and module # and that all listed translations are present in passthru options - module_set = set(vault_token_create.LEGACY_OPTION_TRANSLATION.items()) - test_set = set(legacy_option_translation.items()) + module_set = set(vault_token_create.ORPHAN_OPTION_TRANSLATION.items()) + test_set = set(orphan_option_translation.items()) - module_key_set = set(vault_token_create.LEGACY_OPTION_TRANSLATION.keys()) + module_key_set = set(vault_token_create.ORPHAN_OPTION_TRANSLATION.keys()) pass_thru_key_set = set(pass_thru_options.keys()) assert module_set == test_set, ( - "Legacy options in module do not match legacy options in test:\nmodule: %r\ntest: %r" % ( + "Orphan options in module do not match orphan options in test:\nmodule: %r\ntest: %r" % ( dict(module_set - test_set), dict(test_set - module_set), ) ) - assert vault_token_create.LEGACY_OPTION_TRANSLATION.keys() <= pass_thru_options.keys(), ( - "Legacy option translation keys must exist in passthru options: %r" % ( + assert vault_token_create.ORPHAN_OPTION_TRANSLATION.keys() <= pass_thru_options.keys(), ( + "Orphan option translation keys must exist in passthru options: %r" % ( list(module_key_set - pass_thru_key_set), ) ) @@ -124,6 +153,7 @@ def test_vault_token_create_passthru_options(self, pass_thru_options, token_crea result = json.loads(out) client.create_token.assert_not_called() + client.auth.token.create_orphan.assert_not_called() client.auth.token.create.assert_called_once() assert result['login'] == token_create_response, ( @@ -137,9 +167,9 @@ def test_vault_token_create_passthru_options(self, pass_thru_options, token_crea assert pass_thru_options.items() <= client.auth.token.create.call_args.kwargs.items() @pytest.mark.parametrize('patch_ansible_module', [_combined_options(orphan=True)], indirect=True) - def test_vault_token_create_legacy_options(self, pass_thru_options, legacy_option_translation, token_create_response, vault_client, capfd): + def test_vault_token_create_orphan_options(self, pass_thru_options, orphan_option_translation, token_create_response, vault_client, capfd): client = vault_client - client.create_token.return_value = token_create_response + client.auth.token.create_orphan.return_value = token_create_response with pytest.raises(SystemExit): vault_token_create.main() @@ -147,8 +177,9 @@ def test_vault_token_create_legacy_options(self, pass_thru_options, legacy_optio out, err = capfd.readouterr() result = json.loads(out) + client.create_token.assert_not_called() client.auth.token.create.assert_not_called() - client.create_token.assert_called_once() + client.auth.token.create_orphan.assert_called_once() assert result['login'] == token_create_response, ( "module result did not match expected result:\nmodule: %r\nexpected: %r" % (result['module'], token_create_response) @@ -156,30 +187,30 @@ def test_vault_token_create_legacy_options(self, pass_thru_options, legacy_optio if sys.version_info < (3, 8): # TODO: remove when python < 3.8 is dropped - call_kwargs = client.create_token.call_args[1] + call_kwargs = client.auth.token.create_orphan.call_args[1] else: - call_kwargs = client.create_token.call_args.kwargs + call_kwargs = client.auth.token.create_orphan.call_args.kwargs - for name, legacy in legacy_option_translation.items(): + for name, orphan in orphan_option_translation.items(): assert name not in call_kwargs, ( - "'%s' was found in call to legacy method, should be '%s'" % (name, legacy) + "'%s' was found in call to orphan method, should be '%s'" % (name, orphan) ) - assert legacy in call_kwargs, ( - "'%s' (from '%s') was not found in call to legacy method" % (legacy, name) + assert orphan in call_kwargs, ( + "'%s' (from '%s') was not found in call to orphan method" % (orphan, name) ) - assert call_kwargs[legacy] == pass_thru_options.get(name), ( - "Expected legacy param '%s' not found or value did not match:\nvalue: %r\nexpected: %r" % ( - legacy, - call_kwargs.get(legacy), + assert call_kwargs[orphan] == pass_thru_options.get(name), ( + "Expected orphan param '%s' not found or value did not match:\nvalue: %r\nexpected: %r" % ( + orphan, + call_kwargs.get(orphan), pass_thru_options.get(name), ) ) @pytest.mark.parametrize('patch_ansible_module', [_combined_options(orphan=True)], indirect=True) - def test_vault_token_create_legacy_fallback(self, pass_thru_options, token_create_response, vault_client, module_warn, capfd): + def test_vault_token_create_orphan_fallback(self, token_create_response, vault_client, capfd): client = vault_client - client.create_token.side_effect = AttributeError - client.auth.token.create.return_value = token_create_response + client.create_token.return_value = token_create_response + client.auth.token.create_orphan.side_effect = AttributeError with pytest.raises(SystemExit): vault_token_create.main() @@ -187,17 +218,52 @@ def test_vault_token_create_legacy_fallback(self, pass_thru_options, token_creat out, err = capfd.readouterr() result = json.loads(out) - module_warn.assert_called_once_with("'create_token' method was not found. Attempting method that requires root privileges.") - client.auth.token.create.assert_called_once() + client.auth.token.create_orphan.assert_called_once() + client.create_token.assert_called_once() assert result['login'] == token_create_response, ( "module result did not match expected result:\nmodule: %r\nexpected: %r" % (result['login'], token_create_response) ) - # we're retesting that expected options were passed, even though there's a separate test for that, - # to ensure that nothing in the original legacy attempt mutates the non-legacy options during fallback - if sys.version_info < (3, 8): - # TODO: remove when python < 3.8 is dropped - assert pass_thru_options.items() <= client.auth.token.create.call_args[1].items() - else: - assert pass_thru_options.items() <= client.auth.token.create.call_args.kwargs.items() + @pytest.mark.parametrize('patch_ansible_module', [_combined_options()], indirect=True) + def test_vault_token_create_exception_handling_standard(self, vault_client, capfd): + client = vault_client + client.auth.token.create.side_effect = Exception('side_effect') + + with pytest.raises(SystemExit) as e: + vault_token_create.main() + + out, err = capfd.readouterr() + result = json.loads(out) + + assert e.value.code != 0, "result: %r" % (result,) + assert result['msg'] == 'side_effect' + + @pytest.mark.parametrize('patch_ansible_module', [_combined_options(orphan=True)], indirect=True) + def test_vault_token_create_exception_handling_orphan(self, vault_client, capfd): + client = vault_client + client.auth.token.create_orphan.side_effect = Exception('side_effect') + + with pytest.raises(SystemExit) as e: + vault_token_create.main() + + out, err = capfd.readouterr() + result = json.loads(out) + + assert e.value.code != 0, "result: %r" % (result,) + assert result['msg'] == 'side_effect' + + @pytest.mark.parametrize('patch_ansible_module', [_combined_options(orphan=True)], indirect=True) + def test_vault_token_create_exception_handling_orphan_fallback(self, vault_client, capfd): + client = vault_client + client.create_token.side_effect = Exception('side_effect') + client.auth.token.create_orphan.side_effect = AttributeError + + with pytest.raises(SystemExit) as e: + vault_token_create.main() + + out, err = capfd.readouterr() + result = json.loads(out) + + assert e.value.code != 0, "result: %r" % (result,) + assert result['msg'] == 'side_effect'