Repository: ansible/ansible-modules-core

Issue:
apt: return cache update status
Hi there,

I'm trying to detect when apt cache changes, I am doing this:

To make sure cache is old, on my vagrant host:

``` bash
$ rm -R /var/lib/apt/lists/* /var/lib/apt/periodic/update-success-stamp /var/cache/apt/*
```

In my ansible task

``` yaml
- apt: update_cache=true cache_valid_time=1
  register: apt_cache

- name: "Dist-Upgrade All Packages"
  apt: upgrade=dist
  when: apt_cache.changed
```

When I run my playbook, I see:

```
TASK: [common | apt update_cache=true cache_valid_time=1] ********************* 
ok: [deployer@127.0.0.1]

TASK: [common | Upgrade All Packages] ************************************ 
skipping: [deployer@127.0.0.1]
```

Why isn't ansible reporting a change?



Discussion:
The apt module only reports change when installing/updating/removing packages, it does not report change only on updating the cache.

Assuming we want to have the behavior to report a change if the cache is updated, it would require a change similar to:

``` diff
diff --git a/packaging/os/apt.py b/packaging/os/apt.py
index 9f5b8fd..7faeb89 100644
--- a/packaging/os/apt.py
+++ b/packaging/os/apt.py
@@ -568,11 +568,13 @@ def main():
                         # the old cache is less than cache_valid_time seconds old - so still valid
                         cache_valid = True

+            changed = False
             if cache_valid is not True:
                 cache.update()
                 cache.open(progress=None)
+                changed = True
             if not p['package'] and not p['upgrade'] and not p['deb']:
-                module.exit_json(changed=False)
+                module.exit_json(changed=changed)

         force_yes = p['force']
```

As @sivel explains, this is by design as the cache update is not considered a change by most which only expect 'changed' to reflect either a package being installed/uninstalled.

I don't think we want to change this behavior by default but we would be open to a flag that controls it or just returned the information about the cache update (which you can then use changed_when with).

So I'm going to label this a feature request instead of a bug.

+1 on feature request. 

With the current behaviour it is not possible to have an update_cache on e.g. sources.list change,
while having an previous update_cache with cache_valid_time.

Maybe it is an more appropriate way skiping the cache_valid_time, on some condition.

My situation is as follows. Perhaps someone has a better solution?

```
- name: add sources.list for ubuntu
  copy: src=sources.list.ubuntu.trusty dest=/etc/apt/sources.list
  when: ansible_distribution == "Ubuntu" and ansible_distribution_release == "trusty"
  register: new_sources

- name: update apt cache
  apt: update_cache=yes cache_valid_time=3600
  register: update_cache

- name: update apt cache
  apt: update_cache=yes
  when: new_sources|changed and not update_cache|changed 
```


Resolving patch:
diff --git a/packaging/os/apt.py b/packaging/os/apt.py
--- a/packaging/os/apt.py
+++ b/packaging/os/apt.py
@@ -138,6 +138,28 @@
 - apt: pkg=foo state=build-dep
 '''
 
+RETURN = '''
+cache_updated:
+    description: if the cache was updated or not
+    returned: success, in some cases
+    type: boolean
+    sample: True
+cache_update_time:
+    description: time of the last cache update (0 if unknown)
+    returned: success, in some cases
+    type: datetime
+    sample: 1425828348000
+stdout:
+    description: output from apt
+    returned: success, when needed
+    type: string
+    sample: "Reading package lists...\nBuilding dependency tree...\nReading state information...\nThe following extra packages will be installed:\n  apache2-bin ..."
+stderr:
+    description: error output from apt
+    returned: success, when needed
+    type: string
+    sample: "AH00558: apache2: Could not reliably determine the server's fully qualified domain name, using 127.0.1.1. Set the 'ServerName' directive globally to ..."
+'''
 
 import traceback
 # added to stave off future warnings about apt api
@@ -536,6 +558,8 @@ def main():
     if not APTITUDE_CMD and p.get('upgrade', None) in [ 'full', 'safe', 'yes' ]:
         module.fail_json(msg="Could not find aptitude. Please ensure it is installed.")
 
+    updated_cache = False
+    updated_cache_time = 0
     install_recommends = p['install_recommends']
     dpkg_options = expand_dpkg_options(p['dpkg_options'])
 
@@ -558,41 +582,41 @@ def main():
         if p['update_cache']:
             # Default is: always update the cache
             cache_valid = False
-            if p['cache_valid_time']:
-                tdelta = datetime.timedelta(seconds=p['cache_valid_time'])
+            now = datetime.datetime.now()
+            if p.get('cache_valid_time', False):
                 try:
                     mtime = os.stat(APT_UPDATE_SUCCESS_STAMP_PATH).st_mtime
                 except:
-                    mtime = False
-                if mtime is False:
                     # Looks like the update-success-stamp is not available
                     # Fallback: Checking the mtime of the lists
                     try:
                         mtime = os.stat(APT_LISTS_PATH).st_mtime
                     except:
+                        # No mtime could be read. We update the cache to be safe
                         mtime = False
-                if mtime is False:
-                    # No mtime could be read - looks like lists are not there
-                    # We update the cache to be safe
-                    cache_valid = False
-                else:
+
+                if mtime:
+                    tdelta = datetime.timedelta(seconds=p['cache_valid_time'])
                     mtimestamp = datetime.datetime.fromtimestamp(mtime)
-                    if mtimestamp + tdelta >= datetime.datetime.now():
-                        # dont update the cache
-                        # the old cache is less than cache_valid_time seconds old - so still valid
+                    if mtimestamp + tdelta >= now:
                         cache_valid = True
+                        updated_cache_time = int(time.mktime(mtimestamp.timetuple()))
 
             if cache_valid is not True:
                 cache.update()
                 cache.open(progress=None)
+                updated_cache = True
+                updated_cache_time = int(time.mktime(now.timetuple()))
             if not p['package'] and not p['upgrade'] and not p['deb']:
-                module.exit_json(changed=False)
+                module.exit_json(changed=False, cache_updated=updated_cache, cache_update_time=updated_cache_time)
+        else:
+            updated_cache = False
+            updated_cache_time = 0
 
         force_yes = p['force']
 
         if p['upgrade']:
-            upgrade(module, p['upgrade'], force_yes,
-                    p['default_release'], dpkg_options)
+            upgrade(module, p['upgrade'], force_yes, p['default_release'], dpkg_options)
 
         if p['deb']:
             if p['state'] != 'present':
@@ -622,6 +646,8 @@ def main():
                     force=force_yes, dpkg_options=dpkg_options,
                     build_dep=state_builddep)
             (success, retvals) = result
+            retvals['cache_updated']=updated_cache
+            retvals['cache_update_time']=updated_cache_time
             if success:
                 module.exit_json(**retvals)
             else:


Test patch: