Repository: ansible/ansible-modules-core

Issue:
yum module doesn't install group if 'state=latest' but does if 'state=present'
I've seen #2510 and it seems to address the _name_ of the group, not whether the state is present or latest.  Neither the note nor the Redhat docs that @abadger linked to address the issue of "state", just the group name.

<!--- Verify first that your issue/request is not already reported in GitHub -->
##### ISSUE TYPE
- Bug Report
##### COMPONENT NAME

yum module
##### ANSIBLE VERSION

```
ansible 2.1.0.0
  config file =
  configured module search path = Default w/o overrides
```
##### CONFIGURATION

No changes.
##### OS / ENVIRONMENT

Running ansible built via homebrew on a Mac running 10.10.5.
Targets are Digital Ocean Droplets (for these tests) running Digital Ocean's CentOS 7 image.
##### SUMMARY

Using state=latest with the yum module to install groups (e.g. the Development Group) does not actually install anything, but using state=present does.

This playbook will install the Development Group

```
- hosts: all
  tasks:
  - name: "install development group"
    yum: name="{{ item }}" state=present
    with_items:
      - '@development'
```

This one does not:

```
- hosts: all
  tasks:
  - name: "install development group"
    yum: name="{{ item }}" state=latest
    with_items:
      - '@development'
```
##### STEPS TO REPRODUCE

<!---
For bugs, show exactly how to reproduce the problem.
For new features, show how the feature would be used.
-->

Create a "droplet" with Digital Ocean's CentOS 7 x64 image (a 512mb droplet is fine).

Use

```
ansible-playbook -i <droplet_ip_addr>, -u root --private-key=<path to private key> playbook.yml
```

to run either of the playbooks included above.

Log into the machine, e.g. with

```
ssh -i <path to private key> root@<droplet ip addr>
```

and execute `cc` and you should see this (when state=latest):

```
[root@yumbug ~]# cc
-bash: cc: command not found
```

or this (when state=present):

```
[root@yumbug ~]# cc
cc: fatal error: no input files
compilation terminated.
```
##### EXPECTED RESULTS

See above.
##### ACTUAL RESULTS

See above.



Discussion:
Reproduced. Preliminary patch ...

```
diff --git a/packaging/os/yum.py b/packaging/os/yum.py
index cc0ad41..80ae0b2 100644
--- a/packaging/os/yum.py
+++ b/packaging/os/yum.py
@@ -837,6 +837,7 @@ def latest(module, items, repoq, yum_basecmd, conf_file, en_repos, dis_repos):
             # some guess work involved with groups. update @<group> will install the group if missing
             if spec.startswith('@'):
                 pkgs['update'].append(spec)
+                will_update.add(spec)
                 continue
             # dep/pkgname  - find it
             else:
```

Based on this commit, it seems like installing a group with latest was broken by new checkmode logic:

https://github.com/ansible/ansible-modules-core/commit/e5e0a70f#diff-50dbb2224ca8ab1fc7b6b693f9be32faR761

With the patch I first posted, state=latest returns changed=True for every run. Need to figure out how update detection would work in that case.

Slightly better approach ...

```
diff --git a/packaging/os/yum.py b/packaging/os/yum.py
index cc0ad41..bc3fad9 100644
--- a/packaging/os/yum.py
+++ b/packaging/os/yum.py
@@ -837,6 +837,7 @@ def latest(module, items, repoq, yum_basecmd, conf_file, en_repos, dis_repos):
             # some guess work involved with groups. update @<group> will install the group if missing
             if spec.startswith('@'):
                 pkgs['update'].append(spec)
+                will_update.add(spec)
                 continue
             # dep/pkgname  - find it
             else:
@@ -902,21 +903,26 @@ def latest(module, items, repoq, yum_basecmd, conf_file, en_repos, dis_repos):
         return res

     # run commands
+    res['yum_commands_run'] = []
     if cmd:     # update all
         rc, out, err = module.run_command(cmd)
         res['changed'] = True
     else:
         if len(pkgs['install']) > 0:    # install missing
             cmd = yum_basecmd + ['install'] + pkgs['install']
+            res['yum_commands_run'].append(' '.join(cmd))
             rc, out, err = module.run_command(cmd)
-            res['changed'] = True
+            if not out.strip().endswith("No packages marked for update"):
+                res['changed'] = True
         else:
             rc, out, err = [0, '', '']

         if len(will_update) > 0:     # update present
             cmd = yum_basecmd + ['update'] + pkgs['update']
+            res['yum_commands_run'].append(' '.join(cmd))
             rc2, out2, err2 = module.run_command(cmd)
-            res['changed'] = True
+            if not out2.strip().endswith("No packages marked for update"):
+                res['changed'] = True
         else:
             rc2, out2, err2 = [0, '', '']
```

This one works on centos6 also ...

```
diff --git a/packaging/os/yum.py b/packaging/os/yum.py
index cc0ad41..7581a3b 100644
--- a/packaging/os/yum.py
+++ b/packaging/os/yum.py
@@ -837,6 +837,7 @@ def latest(module, items, repoq, yum_basecmd, conf_file, en_repos, dis_repos):
             # some guess work involved with groups. update @<group> will install the group if missing
             if spec.startswith('@'):
                 pkgs['update'].append(spec)
+                will_update.add(spec)
                 continue
             # dep/pkgname  - find it
             else:
@@ -909,14 +910,16 @@ def latest(module, items, repoq, yum_basecmd, conf_file, en_repos, dis_repos):
         if len(pkgs['install']) > 0:    # install missing
             cmd = yum_basecmd + ['install'] + pkgs['install']
             rc, out, err = module.run_command(cmd)
-            res['changed'] = True
+            if not out.strip().lower().endswith("no packages marked for update"):
+                res['changed'] = True
         else:
             rc, out, err = [0, '', '']

         if len(will_update) > 0:     # update present
             cmd = yum_basecmd + ['update'] + pkgs['update']
             rc2, out2, err2 = module.run_command(cmd)
-            res['changed'] = True
+            if not out2.strip().lower().endswith("no packages marked for update"):
+                res['changed'] = True
         else:
             rc2, out2, err2 = [0, '', '']
```


Resolving patch:
diff --git a/packaging/os/yum.py b/packaging/os/yum.py
--- a/packaging/os/yum.py
+++ b/packaging/os/yum.py
@@ -837,6 +837,7 @@ def latest(module, items, repoq, yum_basecmd, conf_file, en_repos, dis_repos):
             # some guess work involved with groups. update @<group> will install the group if missing
             if spec.startswith('@'):
                 pkgs['update'].append(spec)
+                will_update.add(spec)
                 continue
             # dep/pkgname  - find it
             else:
@@ -909,14 +910,16 @@ def latest(module, items, repoq, yum_basecmd, conf_file, en_repos, dis_repos):
         if len(pkgs['install']) > 0:    # install missing
             cmd = yum_basecmd + ['install'] + pkgs['install']
             rc, out, err = module.run_command(cmd)
-            res['changed'] = True
+            if not out.strip().lower().endswith("no packages marked for update"):
+                res['changed'] = True
         else:
             rc, out, err = [0, '', '']
 
         if len(will_update) > 0:     # update present
             cmd = yum_basecmd + ['update'] + pkgs['update']
             rc2, out2, err2 = module.run_command(cmd)
-            res['changed'] = True
+            if not out2.strip().lower().endswith("no packages marked for update"):
+                res['changed'] = True
         else:
             rc2, out2, err2 = [0, '', '']
 
@@ -937,7 +940,14 @@ def latest(module, items, repoq, yum_basecmd, conf_file, en_repos, dis_repos):
 def ensure(module, state, pkgs, conf_file, enablerepo, disablerepo,
            disable_gpg_check, exclude, repoq):
 
-    yumbin = module.get_bin_path('yum')
+    # fedora will redirect yum to dnf, which has incompatibilities
+    # with how this module expects yum to operate. If yum-deprecated
+    # is available, use that instead to emulate the old behaviors.
+    if module.get_bin_path('yum-deprecated'):
+        yumbin = module.get_bin_path('yum-deprecated')
+    else:
+        yumbin = module.get_bin_path('yum')
+
     # need debug level 2 to get 'Nothing to do' for groupinstall.
     yum_basecmd = [yumbin, '-d', '2', '-y']
 


Test patch: