From 38aa0ec8ad14f98beeb2f129f5bf30b4a0969cc5 Mon Sep 17 00:00:00 2001 From: "patchback[bot]" <45432694+patchback[bot]@users.noreply.github.com> Date: Mon, 17 May 2021 14:14:44 +0200 Subject: [PATCH] Add option missing to passwordstore lookup (#2500) (#2541) Add ability to ignore error on missing pass file to allow processing the output further via another filters (mainly the default filter) without updating the pass file itself. It also contains the option to create the pass file, like the option create=true does. Finally, it also allows to issue a warning only, if the pass file is not found. (cherry picked from commit 350380ba8c91030b69ec4fe2b087fb62ee82c389) Co-authored-by: Jan Baier <7996094+baierjan@users.noreply.github.com> --- ...asswordstore-add_option_ignore_missing.yml | 3 + plugins/lookup/passwordstore.py | 56 +++++++++++++++++-- .../lookup_passwordstore/tasks/tests.yml | 31 ++++++++++ 3 files changed, 85 insertions(+), 5 deletions(-) create mode 100644 changelogs/fragments/2500-passwordstore-add_option_ignore_missing.yml diff --git a/changelogs/fragments/2500-passwordstore-add_option_ignore_missing.yml b/changelogs/fragments/2500-passwordstore-add_option_ignore_missing.yml new file mode 100644 index 0000000000..6141ac7747 --- /dev/null +++ b/changelogs/fragments/2500-passwordstore-add_option_ignore_missing.yml @@ -0,0 +1,3 @@ +minor_changes: + - passwordstore lookup - add option ``missing`` to choose what to do if the password file is missing + (https://github.com/ansible-collections/community.general/pull/2500). diff --git a/plugins/lookup/passwordstore.py b/plugins/lookup/passwordstore.py index 79c69ed962..976dfb837e 100644 --- a/plugins/lookup/passwordstore.py +++ b/plugins/lookup/passwordstore.py @@ -25,9 +25,9 @@ DOCUMENTATION = ''' env: - name: PASSWORD_STORE_DIR create: - description: Create the password if it does not already exist. + description: Create the password if it does not already exist. Takes precedence over C(missing). type: bool - default: 'no' + default: false overwrite: description: Overwrite the password if it does already exist. type: bool @@ -60,6 +60,22 @@ DOCUMENTATION = ''' description: use alphanumeric characters. type: bool default: 'no' + missing: + description: + - List of preference about what to do if the password file is missing. + - If I(create=true), the value for this option is ignored and assumed to be C(create). + - If set to C(error), the lookup will error out if the passname does not exist. + - If set to C(create), the passname will be created with the provided length I(length) if it does not exist. + - If set to C(empty) or C(warn), will return a C(none) in case the passname does not exist. + When using C(lookup) and not C(query), this will be translated to an empty string. + version_added: 3.1.0 + type: str + default: error + choices: + - error + - warn + - empty + - create ''' EXAMPLES = """ # Debug is used for examples, BAD IDEA to show passwords on screen @@ -67,12 +83,28 @@ EXAMPLES = """ ansible.builtin.debug: msg: "{{ lookup('community.general.passwordstore', 'example/test')}}" +- name: Basic lookup. Warns if example/test does not exist and returns empty string + ansible.builtin.debug: + msg: "{{ lookup('community.general.passwordstore', 'example/test missing=warn')}}" + - name: Create pass with random 16 character password. If password exists just give the password ansible.builtin.debug: var: mypassword vars: mypassword: "{{ lookup('community.general.passwordstore', 'example/test create=true')}}" +- name: Create pass with random 16 character password. If password exists just give the password + ansible.builtin.debug: + var: mypassword + vars: + mypassword: "{{ lookup('community.general.passwordstore', 'example/test missing=create')}}" + +- name: Prints 'abc' if example/test does not exist, just give the password otherwise + ansible.builtin.debug: + var: mypassword + vars: + mypassword: "{{ lookup('community.general.passwordstore', 'example/test missing=empty') | default('abc', true) }}" + - name: Different size password ansible.builtin.debug: msg: "{{ lookup('community.general.passwordstore', 'example/test create=true length=42')}}" @@ -111,10 +143,13 @@ import yaml from distutils import util from ansible.errors import AnsibleError, AnsibleAssertionError from ansible.module_utils._text import to_bytes, to_native, to_text +from ansible.utils.display import Display from ansible.utils.encrypt import random_password from ansible.plugins.lookup import LookupBase from ansible import constants as C +display = Display() + # backhacked check_output with input for python 2.7 # http://stackoverflow.com/questions/10103551/passing-data-to-subprocess-check-output @@ -178,12 +213,17 @@ class LookupModule(LookupBase): self.paramvals[key] = util.strtobool(self.paramvals[key]) except (ValueError, AssertionError) as e: raise AnsibleError(e) + if self.paramvals['missing'] not in ['error', 'warn', 'create', 'empty']: + raise AnsibleError("{0} is not a valid option for missing".format(self.paramvals['missing'])) if not isinstance(self.paramvals['length'], int): if self.paramvals['length'].isdigit(): self.paramvals['length'] = int(self.paramvals['length']) else: raise AnsibleError("{0} is not a correct value for length".format(self.paramvals['length'])) + if self.paramvals['create']: + self.paramvals['missing'] = 'create' + # Collect pass environment variables from the plugin's parameters. self.env = os.environ.copy() @@ -224,9 +264,11 @@ class LookupModule(LookupBase): if e.returncode != 0 and 'not in the password store' in e.output: # if pass returns 1 and return string contains 'is not in the password store.' # We need to determine if this is valid or Error. - if not self.paramvals['create']: - raise AnsibleError('passname: {0} not found, use create=True'.format(self.passname)) + if self.paramvals['missing'] == 'error': + raise AnsibleError('passwordstore: passname {0} not found and missing=error is set'.format(self.passname)) else: + if self.paramvals['missing'] == 'warn': + display.warning('passwordstore: passname {0} not found'.format(self.passname)) return False else: raise AnsibleError(e) @@ -294,6 +336,7 @@ class LookupModule(LookupBase): 'userpass': '', 'length': 16, 'backup': False, + 'missing': 'error', } for term in terms: @@ -304,6 +347,9 @@ class LookupModule(LookupBase): else: result.append(self.get_passresult()) else: # password does not exist - if self.paramvals['create']: + if self.paramvals['missing'] == 'create': result.append(self.generate_password()) + else: + result.append(None) + return result diff --git a/tests/integration/targets/lookup_passwordstore/tasks/tests.yml b/tests/integration/targets/lookup_passwordstore/tasks/tests.yml index aba5457c0a..e69ba5e572 100644 --- a/tests/integration/targets/lookup_passwordstore/tasks/tests.yml +++ b/tests/integration/targets/lookup_passwordstore/tasks/tests.yml @@ -61,6 +61,37 @@ that: - readpass == newpass +- name: Create a password using missing=create + set_fact: + newpass: "{{ lookup('community.general.passwordstore', 'test-missing-create missing=create length=8') }}" + +- name: Fetch password from an existing file + set_fact: + readpass: "{{ lookup('community.general.passwordstore', 'test-missing-create') }}" + +- name: Verify password + assert: + that: + - readpass == newpass + +- name: Fetch password from existing file using missing=empty + set_fact: + readpass: "{{ lookup('community.general.passwordstore', 'test-missing-create missing=empty') }}" + +- name: Verify password + assert: + that: + - readpass == newpass + +- name: Fetch password from non-existing file using missing=empty + set_fact: + readpass: "{{ query('community.general.passwordstore', 'test-missing-pass missing=empty') }}" + +- name: Verify password + assert: + that: + - readpass == [ none ] + # As inserting multiline passwords on the commandline would require something # like expect, simply create it by using default gpg on a file with the correct # structure.