From 863e2571db5d70b03478fd18efef15c4bde88c10 Mon Sep 17 00:00:00 2001 From: Abhijeet Kasurde Date: Mon, 21 Aug 2023 18:25:03 -0700 Subject: [PATCH] debconf: idempotency for password question (#81484) * Gather value of password using debconf-get-selections command, use this information for idempotency. Fixes: #47676 Signed-off-by: Abhijeet Kasurde --- changelogs/fragments/debconf.yml | 3 ++ lib/ansible/modules/debconf.py | 41 +++++++++++++----- .../targets/debconf/tasks/main.yml | 42 ++++++++++++++++++- 3 files changed, 75 insertions(+), 11 deletions(-) create mode 100644 changelogs/fragments/debconf.yml diff --git a/changelogs/fragments/debconf.yml b/changelogs/fragments/debconf.yml new file mode 100644 index 00000000000..9db70d87e6a --- /dev/null +++ b/changelogs/fragments/debconf.yml @@ -0,0 +1,3 @@ +--- +bugfixes: + - debconf - idempotency in questions with type 'password' (https://github.com/ansible/ansible/issues/47676). diff --git a/lib/ansible/modules/debconf.py b/lib/ansible/modules/debconf.py index 0e7aad0036b..5ff14029801 100644 --- a/lib/ansible/modules/debconf.py +++ b/lib/ansible/modules/debconf.py @@ -27,11 +27,11 @@ attributes: platforms: debian notes: - This module requires the command line debconf tools. - - A number of questions have to be answered (depending on the package). + - Several questions have to be answered (depending on the package). Use 'debconf-show ' on any Debian or derivative with the package 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 C(no_log=True) to task while handling sensitive information using this module. + - It is highly recommended to add C(no_log=True) to the task while handling sensitive information using this module. - The debconf module does not reconfigure packages, it just updates the debconf database. An additional step is needed (typically with C(notify) if debconf makes a change) to reconfigure the package and apply the changes. @@ -46,7 +46,7 @@ notes: - The main issue is that the C(.config reconfigure) step for many packages will first reset the debconf database (overriding changes made by this module) by checking the on-disk configuration. If this is the case for your package then - dpkg-reconfigure will effectively ignore changes made by debconf. + dpkg-reconfigure will effectively ignore changes made by debconf. - However as dpkg-reconfigure only executes the C(.config) step if the file exists, it is possible to rename it to C(/var/lib/dpkg/info/.config.ignore) before executing C(dpkg-reconfigure -f noninteractive ) and then restore it. @@ -128,6 +128,28 @@ from ansible.module_utils.common.text.converters import to_text from ansible.module_utils.basic import AnsibleModule +def get_password_value(module, pkg, question, vtype): + getsel = module.get_bin_path('debconf-get-selections', True) + cmd = [getsel] + rc, out, err = module.run_command(cmd) + if rc != 0: + module.fail_json(msg="Failed to get the value '%s' from '%s'" % (question, pkg)) + + desired_line = None + for line in out.split("\n"): + if line.startswith(pkg): + desired_line = line + break + + if not desired_line: + module.fail_json(msg="Failed to find the value '%s' from '%s'" % (question, pkg)) + + (dpkg, dquestion, dvtype, dvalue) = desired_line.split() + if dquestion == question and dvtype == vtype: + return dvalue + return '' + + def get_selections(module, pkg): cmd = [module.get_bin_path('debconf-show', True), pkg] rc, out, err = module.run_command(' '.join(cmd)) @@ -151,10 +173,7 @@ def set_selection(module, pkg, question, vtype, value, unseen): cmd.append('-u') if vtype == 'boolean': - if value == 'True': - value = 'true' - elif value == 'False': - value = 'false' + value = value.lower() data = ' '.join([pkg, question, vtype, value]) return module.run_command(cmd, data=data) @@ -193,7 +212,6 @@ def main(): if question not in prev: changed = True else: - existing = prev[question] # ensure we compare booleans supplied to the way debconf sees them (true/false strings) @@ -201,6 +219,9 @@ def main(): value = to_text(value).lower() existing = to_text(prev[question]).lower() + if vtype == 'password': + existing = get_password_value(module, pkg, question, vtype) + if value != existing: changed = True @@ -215,12 +236,12 @@ def main(): prev = {question: prev[question]} else: prev[question] = '' + + diff_dict = {} if module._diff: after = prev.copy() after.update(curr) diff_dict = {'before': prev, 'after': after} - else: - diff_dict = {} module.exit_json(changed=changed, msg=msg, current=curr, previous=prev, diff=diff_dict) diff --git a/test/integration/targets/debconf/tasks/main.yml b/test/integration/targets/debconf/tasks/main.yml index d3d63cdfbbc..f923626868c 100644 --- a/test/integration/targets/debconf/tasks/main.yml +++ b/test/integration/targets/debconf/tasks/main.yml @@ -33,4 +33,44 @@ - 'debconf_test0.current is defined' - '"tzdata/Zones/Etc" in debconf_test0.current' - 'debconf_test0.current["tzdata/Zones/Etc"] == "UTC"' - when: ansible_distribution in ('Ubuntu', 'Debian') + + - name: install debconf-utils + apt: + name: debconf-utils + state: present + register: debconf_utils_deb_install + + - name: Check if password is set + debconf: + name: ddclient + question: ddclient/password + value: "MySecretValue" + vtype: password + register: debconf_test1 + + - name: validate results for test 1 + assert: + that: + - debconf_test1.changed + + - name: Change password again + debconf: + name: ddclient + question: ddclient/password + value: "MySecretValue" + vtype: password + no_log: yes + register: debconf_test2 + + - name: validate results for test 1 + assert: + that: + - not debconf_test2.changed + always: + - name: uninstall debconf-utils + apt: + name: debconf-utils + state: absent + when: debconf_utils_deb_install is changed + + when: ansible_distribution in ('Ubuntu', 'Debian') \ No newline at end of file