Repository: ansible/ansible
Pull request: dpkg_selections: Check if package exists before selection operation
##### SUMMARY

Fixes: #81404

Signed-off-by: Abhijeet Kasurde <akasurde@redhat.com>

##### ISSUE TYPE
- Bugfix Pull Request




##### COMPONENT NAME
changelogs/fragments/dpkg_selections.yml
lib/ansible/modules/dpkg_selections.py
test/integration/targets/dpkg_selections/tasks/dpkg_selections.yaml



Problem statement:
Module ansible.builtin.dpkg_selections not idempotent.
### Summary

The module `ansible.builtin.dpkg_selections` keeps on changing with the same arguments.

### Issue Type

Bug Report

### Component Name

dpkg_selections

### Ansible Version

```console
$ ansible --version
ansible [core 2.15.1]
  config file = None
  configured module search path = ['/Users/OBFUSCATED/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
  ansible python module location = /Users/OBFUSCATED/venv/lib/python3.9/site-packages/ansible
  ansible collection location = /Users/OBFUSCATED/.ansible/collections:/usr/share/ansible/collections
  executable location = /Users/OBFUSCATED/venv/bin/ansible
  python version = 3.9.17 (main, Jun  6 2023, 14:33:55) [Clang 14.0.3 (clang-1403.0.22.14.1)] (/Users/OBFUSCATED/venv/bin/python)
  jinja version = 3.1.2
  libyaml = True
```


### Configuration

```console
# if using a version older than ansible-core 2.12 you should omit the '-t all'
$ ansible-config dump --only-changed -t all
CONFIG_FILE() = None
```


### OS / Environment

Controller: Mac OS X
Target: A debian container.

### Steps to Reproduce

The simplest form I could create:

```shell
ansible -m ansible.builtin.dpkg_selections -a "name=kernel selection=hold" -i 2ca39af7901a, -c docker all
```

### Expected Results

I was expecting a `changed: true` once, but actually it's always changed.

### Actual Results

```console
2ca39af7901a | CHANGED => {
    "after": "hold",
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python3"
    },
    "before": "not present",
    "changed": true
}
```


### Code of Conduct

- [X] I agree to follow the Ansible Code of Conduct

Hints from issue discussion:
Files identified in the description:

* [`lib/ansible/modules/dpkg_selections.py`](https://github.com/ansible/ansible/blob/devel/lib/ansible/modules/dpkg_selections.py)

If these files are incorrect, please update the `component name` section of the description or use the [component](https://github.com/mkrizek/ansibotmini/blob/main/README.md) bot command.

<!--- boilerplate: components_banner --->

Commit message: Make CI happy

Signed-off-by: Abhijeet Kasurde <akasurde@redhat.com>

Patch to review:
diff --git a/changelogs/fragments/dpkg_selections.yml b/changelogs/fragments/dpkg_selections.yml
new file mode 100644
index 00000000000000..db46c21ee126ca
--- /dev/null
+++ b/changelogs/fragments/dpkg_selections.yml
@@ -0,0 +1,3 @@
+---
+bugfixes:
+- dpkg_selections - check if the package exists before preforming the selection operation (https://github.com/ansible/ansible/issues/81404).
diff --git a/lib/ansible/modules/dpkg_selections.py b/lib/ansible/modules/dpkg_selections.py
index 71226f01b06edd..7c8a72508056da 100644
--- a/lib/ansible/modules/dpkg_selections.py
+++ b/lib/ansible/modules/dpkg_selections.py
@@ -54,6 +54,7 @@
 '''
 
 from ansible.module_utils.basic import AnsibleModule
+from ansible.module_utils.common.locale import get_best_parsable_locale
 
 
 def main():
@@ -67,12 +68,18 @@ def main():
 
     dpkg = module.get_bin_path('dpkg', True)
 
+    locale = get_best_parsable_locale(module)
+    DPKG_ENV = dict(LANG=locale, LC_ALL=locale, LC_MESSAGES=locale, LC_CTYPE=locale)
+    module.run_command_environ_update = DPKG_ENV
+
     name = module.params['name']
     selection = module.params['selection']
 
     # Get current settings.
     rc, out, err = module.run_command([dpkg, '--get-selections', name], check_rc=True)
-    if not out:
+    if 'no packages found matching' in err:
+        module.fail_json(msg="Failed to find package '%s' to perform selection '%s'." % (name, selection))
+    elif not out:
         current = 'not present'
     else:
         current = out.split()[1]
diff --git a/test/integration/targets/dpkg_selections/tasks/dpkg_selections.yaml b/test/integration/targets/dpkg_selections/tasks/dpkg_selections.yaml
index 080db262bcfba5..016d7716d03247 100644
--- a/test/integration/targets/dpkg_selections/tasks/dpkg_selections.yaml
+++ b/test/integration/targets/dpkg_selections/tasks/dpkg_selections.yaml
@@ -87,3 +87,15 @@
   apt:
     name: hello
     state: absent
+
+- name: Try to select non-existent package
+  dpkg_selections:
+    name: kernel
+    selection: hold
+  ignore_errors: yes
+  register: result
+
+- name: Check that module fails for non-existent package
+  assert:
+    that:
+      - "'Failed to find package' in result.msg"


Code review comments:
diff_hunk: @@ -0,0 +1,3 @@
+---
+bugfixes:
+- dpkg_selections - check if the package exists before preforming the selection operation (https://github.com/ansible/ansible/issues/81404).
path: changelogs/fragments/dpkg_selections.yml
text: @author:
```suggestion
- dpkg_selections - check if the package exists before performing the selection operation (https://github.com/ansible/ansible/issues/81404).
```