Repository: ansible/ansible

Issue:
Ziploader (ansiballz) fails to work if time predates 1980
<!---
Please do not report issues/requests related to Ansible modules here !!

Report them to the appropriate modules-core or modules-extras project:
  - https://github.com/ansible/ansible-modules-core/issues
  - https://github.com/ansible/ansible-modules-extras/issues

Also verify first that your issue/request is not already reported in GitHub
-->


##### ISSUE TYPE
<!--- Pick one below and delete the rest: -->
 - Bug Report

##### COMPONENT NAME
<!--- Name of the plugin/task/feature -->
ziploader

##### ANSIBLE VERSION
<!--- Paste verbatim output from “ansible --version” between quotes below -->
v2.1 and newer

##### CONFIGURATION
<!---
Mention any settings you have changed/added/removed in ansible.cfg
(or using the ANSIBLE_* environment variables).
-->
Raspberry Pi 3 running aarch64

##### OS / ENVIRONMENT
<!---
Mention the OS you are running Ansible from, and the OS you are
managing, or say “N/A” for anything that is not platform-specific.
-->
CentOS 7

##### SUMMARY
<!--- Explain the problem briefly -->
Raspberry Pi's don't have a hardware clock and depends heavily on NTP. Which means that systems won't have a proper date set (they go to epoch by default) until NTP is installed, configured and working.

Now there is a silly problem with zip that it fails to work if the time predates 1980.

```
[dag@moria berries]$ ansible-playbook config.yml -l berry4 -u root

PLAY [berries] *****************************************************************

TASK [setup] *******************************************************************
An exception occurred during task execution. To see the full traceback, use -vvv. The error was: ValueError: ZIP does not support timestamps before 1980
fatal: [berry4]: FAILED! => {"changed": false, "failed": true, "module_stderr": "Traceback (most recent call last):\n  File \"<stdin>\", line 128, in <module>\n  File \"/usr/lib64/python2.7/zipfile.py\", line 1202, in writestr\n    date_time=time.localtime(time.time())[:6])\n  File \"/usr/lib64/python2.7/zipfile.py\", line 305, in __init__\n    raise ValueError('ZIP does not support timestamps before 1980')\nValueError: ZIP does not support timestamps before 1980\n", "module_stdout": "", "msg": "MODULE FAILURE"}
```

Not sure if we can easily do anything about this, or even if this is something we want to fix, as workarounds are possible using raw/command. It's still a burden that ziploader has this dependency to the realtime clock especially since there's a lot to do to set up NTP (network, packages, configuration)...


Discussion:
@abadger FYI
no reason given ... but this seems to be from the underlying library
https://docs.python.org/2/library/zipfile.html#zipfile.ZipInfo.date_time

Resolving patch:
diff --git a/lib/ansible/executor/module_common.py b/lib/ansible/executor/module_common.py
--- a/lib/ansible/executor/module_common.py
+++ b/lib/ansible/executor/module_common.py
@@ -22,6 +22,7 @@
 
 import ast
 import base64
+import datetime
 import imp
 import json
 import os
@@ -317,7 +318,12 @@ def debug(command, zipped_mod, json_params):
             # py3: zipped_mod will be text, py2: it's bytes.  Need bytes at the end
             sitecustomize = u'import sys\\nsys.path.insert(0,"%%s")\\n' %%  zipped_mod
             sitecustomize = sitecustomize.encode('utf-8')
-            z.writestr('sitecustomize.py', sitecustomize)
+            # Use a ZipInfo to work around zipfile limitation on hosts with
+            # clocks set to a pre-1980 year (for instance, Raspberry Pi)
+            zinfo = zipfile.ZipInfo()
+            zinfo.filename = 'sitecustomize.py'
+            zinfo.date_time = ( %(year)i, %(month)i, %(day)i, %(hour)i, %(minute)i, %(second)i)
+            z.writestr(zinfo, sitecustomize)
             z.close()
 
             exitcode = invoke_module(module, zipped_mod, ANSIBALLZ_PARAMS)
@@ -680,6 +686,7 @@ def _find_snippet_imports(module_name, module_data, module_path, module_args, ta
         interpreter_parts = interpreter.split(u' ')
         interpreter = u"'{0}'".format(u"', '".join(interpreter_parts))
 
+        now=datetime.datetime.utcnow()
         output.write(to_bytes(ACTIVE_ANSIBALLZ_TEMPLATE % dict(
             zipdata=zipdata,
             ansible_module=module_name,
@@ -687,6 +694,12 @@ def _find_snippet_imports(module_name, module_data, module_path, module_args, ta
             shebang=shebang,
             interpreter=interpreter,
             coding=ENCODING_STRING,
+            year=now.year,
+            month=now.month,
+            day=now.day,
+            hour=now.hour,
+            minute=now.minute,
+            second=now.second,
         )))
         module_data = output.getvalue()
 


Test patch: