# Buggy code
def main():
    module = AnsibleModule(
        argument_spec=dict(
            bucket=dict(required=True),
            object=dict(default=None),
            src=dict(default=None),
            dest=dict(default=None),
            expiration=dict(default=600, aliases=["expiry"]),
            mode=dict(
                choices=["get", "put", "delete", "create", "get_url", "get_str"],
                required=True,
            ),
            permission=dict(
                choices=["private", "public-read", "authenticated-read"],
                default="private",
            ),
            headers=dict(type="dict", default={}),
            gs_secret_key=dict(no_log=True, required=True),
            gs_access_key=dict(required=True),
            overwrite=dict(default=True, type="bool", aliases=["force"]),
        ),
    )

    if not HAS_BOTO:
        module.fail_json(msg="boto 2.9+ required for this module")

    bucket = module.params.get("bucket")
    obj = module.params.get("object")
    src = module.params.get("src")
    dest = module.params.get("dest")
    if dest:
        dest = os.path.expanduser(dest)
    mode = module.params.get("mode")
    expiry = module.params.get("expiration")
    gs_secret_key = module.params.get("gs_secret_key")
    gs_access_key = module.params.get("gs_access_key")
    overwrite = module.params.get("overwrite")

    if mode == "put":
        if not src or not object:
            module.fail_json(
                msg="When using PUT, src, bucket, object are mandatory parameters"
            )
    if mode == "get":
        if not dest or not object:
            module.fail_json(
                msg="When using GET, dest, bucket, object are mandatory parameters"
            )
    if obj:
        obj = os.path.expanduser(module.params["object"])

    try:
        gs = boto.connect_gs(gs_access_key, gs_secret_key)
    except boto.exception.NoAuthHandlerFound as e:
        module.fail_json(msg=str(e))

    if mode == "get":
        if not bucket_check(module, gs, bucket) or not key_check(
            module, gs, bucket, obj
        ):
            module.fail_json(msg="Target bucket/key cannot be found", failed=True)
        if not path_check(dest):
            download_gsfile(module, gs, bucket, obj, dest)
        else:
            handle_get(module, gs, bucket, obj, overwrite, dest)

    if mode == "put":
        if not path_check(src):
            module.fail_json(msg="Local object for PUT does not exist", failed=True)
        handle_put(module, gs, bucket, obj, overwrite, src, expiry)

    # Support for deleting an object if we have both params.
    if mode == "delete":
        handle_delete(module, gs, bucket, obj)

    if mode == "create":
        handle_create(module, gs, bucket, obj)

    if mode == "get_url":
        if bucket and obj:
            if bucket_check(module, gs, bucket) and key_check(module, gs, bucket, obj):
                get_download_url(module, gs, bucket, obj, expiry)
            else:
                module.fail_json(msg="Key/Bucket does not exist", failed=True)
        else:
            module.fail_json(
                msg="Bucket and Object parameters must be set", failed=True
            )

    # --------------------------- Get the String contents of an Object -------------------------
    if mode == "get_str":
        if bucket and obj:
            if bucket_check(module, gs, bucket) and key_check(module, gs, bucket, obj):
                download_gsstr(module, gs, bucket, obj)
            else:
                module.fail_json(msg="Key/Bucket does not exist", failed=True)
        else:
            module.fail_json(
                msg="Bucket and Object parameters must be set", failed=True
            )


# Traceback (TypeError)
(junkenv)cory@cory-ThinkPad-X1-Carbon-2nd:~/panopta/infrastructure/src/ansible$ ansible-playbook -i localhost gcp_config.yml -e "project_id=$PROJECT envname=$ENVIRONMENT gs_access_key=$GS_KEY gs_secret_key=$GS_SECRET" -vvvv
Using /home/cory/panopta/infrastructure/src/ansible/ansible.cfg as config file
[WARNING]: Host file not found: localhost

[WARNING]: provided hosts list is empty, only localhost is available

Loading callback plugin default of type stdout, v2.0 from /home/cory/junkenv/local/lib/python2.7/site-packages/ansible/plugins/callback/__init__.pyc

PLAYBOOK: gcp_config.yml *******************************************************
1 plays in gcp_config.yml

PLAY [localhost] ***************************************************************

TASK [upload template to GC Storage] *******************************************
task path: /home/cory/panopta/infrastructure/src/ansible/roles/controlpanel-lb/tasks/gcp.yml:6
Using module file /home/cory/junkenv/local/lib/python2.7/site-packages/ansible/modules/core/cloud/google/gc_storage.py
<127.0.0.1> ESTABLISH LOCAL CONNECTION FOR USER: cory
<127.0.0.1> EXEC /bin/sh -c '( umask 77 &amp;&amp; mkdir -p "` echo $HOME/.ansible/tmp/ansible-tmp-1482251197.78-85869852942481 `" &amp;&amp; echo ansible-tmp-1482251197.78-85869852942481="` echo $HOME/.ansible/tmp/ansible-tmp-1482251197.78-85869852942481 `" ) &amp;&amp; sleep 0'
<127.0.0.1> PUT /tmp/tmpA3n_Ws TO /home/cory/.ansible/tmp/ansible-tmp-1482251197.78-85869852942481/gc_storage.py
<127.0.0.1> EXEC /bin/sh -c 'chmod u+x /home/cory/.ansible/tmp/ansible-tmp-1482251197.78-85869852942481/ /home/cory/.ansible/tmp/ansible-tmp-1482251197.78-85869852942481/gc_storage.py &amp;&amp; sleep 0'
<127.0.0.1> EXEC /bin/sh -c 'python /home/cory/.ansible/tmp/ansible-tmp-1482251197.78-85869852942481/gc_storage.py; rm -rf "/home/cory/.ansible/tmp/ansible-tmp-1482251197.78-85869852942481/" > /dev/null 2>&amp;1 &amp;&amp; sleep 0'
An exception occurred during task execution. The full traceback is:
Traceback (most recent call last):
File "/tmp/ansible_wbzVcO/ansible_module_gc_storage.py", line 437, in <module>
main()
File "/tmp/ansible_wbzVcO/ansible_module_gc_storage.py", line 405, in main
handle_put(module, gs, bucket, obj, overwrite, src, expiry)
File "/tmp/ansible_wbzVcO/ansible_module_gc_storage.py", line 308, in handle_put
upload_gsfile(module, gs, bucket, obj, src, expiration)
File "/tmp/ansible_wbzVcO/ansible_module_gc_storage.py", line 244, in upload_gsfile
url = key.generate_url(expiry)
File "/home/cory/junkenv/local/lib/python2.7/site-packages/boto/s3/key.py", line 707, in generate_url
version_id)
File "/home/cory/junkenv/local/lib/python2.7/site-packages/boto/s3/connection.py", line 393, in generate_url
expires = int(time.time() + expires_in)
TypeError: unsupported operand type(s) for +: 'float' and 'str'

fatal: [localhost]: FAILED! => {
"changed": false,
"failed": true,
"invocation": {
"module_name": "gc_storage"
},
"module_stderr": "Traceback (most recent call last):\n  File \"/tmp/ansible_wbzVcO/ansible_module_gc_storage.py\", line 437, in <module>\n    main()\n  File \"/tmp/ansible_wbzVcO/ansible_module_gc_storage.py\", line 405, in main\n    handle_put(module, gs, bucket, obj, overwrite, src, expiry)\n  File \"/tmp/ansible_wbzVcO/ansible_module_gc_storage.py\", line 308, in handle_put\n    upload_gsfile(module, gs, bucket, obj, src, expiration)\n  File \"/tmp/ansible_wbzVcO/ansible_module_gc_storage.py\", line 244, in upload_gsfile\n    url = key.generate_url(expiry)\n  File \"/home/cory/junkenv/local/lib/python2.7/site-packages/boto/s3/key.py\", line 707, in generate_url\n    version_id)\n  File \"/home/cory/junkenv/local/lib/python2.7/site-packages/boto/s3/connection.py\", line 393, in generate_url\n    expires = int(time.time() + expires_in)\nTypeError: unsupported operand type(s) for +: 'float' and 'str'\n",
"module_stdout": "",
"msg": "MODULE FAILURE"
}
to retry, use: --limit @/home/cory/panopta/infrastructure/src/ansible/gcp_config.retry

PLAY RECAP *********************************************************************
localhost                  : ok=0    changed=0    unreachable=0    failed=1

(junkenv)cory@cory-ThinkPad-X1-Carbon-2nd:~/panopta/infrastructure/src/ansible$

# Fixed code
def main():
    module = AnsibleModule(
        argument_spec=dict(
            bucket=dict(required=True),
            object=dict(default=None),
            src=dict(default=None),
            dest=dict(default=None),
            expiration=dict(type="int", default=600, aliases=["expiry"]),
            mode=dict(
                choices=["get", "put", "delete", "create", "get_url", "get_str"],
                required=True,
            ),
            permission=dict(
                choices=["private", "public-read", "authenticated-read"],
                default="private",
            ),
            headers=dict(type="dict", default={}),
            gs_secret_key=dict(no_log=True, required=True),
            gs_access_key=dict(required=True),
            overwrite=dict(default=True, type="bool", aliases=["force"]),
        ),
    )

    if not HAS_BOTO:
        module.fail_json(msg="boto 2.9+ required for this module")

    bucket = module.params.get("bucket")
    obj = module.params.get("object")
    src = module.params.get("src")
    dest = module.params.get("dest")
    if dest:
        dest = os.path.expanduser(dest)
    mode = module.params.get("mode")
    expiry = module.params.get("expiration")
    gs_secret_key = module.params.get("gs_secret_key")
    gs_access_key = module.params.get("gs_access_key")
    overwrite = module.params.get("overwrite")

    if mode == "put":
        if not src or not object:
            module.fail_json(
                msg="When using PUT, src, bucket, object are mandatory parameters"
            )
    if mode == "get":
        if not dest or not object:
            module.fail_json(
                msg="When using GET, dest, bucket, object are mandatory parameters"
            )
    if obj:
        obj = os.path.expanduser(module.params["object"])

    try:
        gs = boto.connect_gs(gs_access_key, gs_secret_key)
    except boto.exception.NoAuthHandlerFound as e:
        module.fail_json(msg=str(e))

    if mode == "get":
        if not bucket_check(module, gs, bucket) or not key_check(
            module, gs, bucket, obj
        ):
            module.fail_json(msg="Target bucket/key cannot be found", failed=True)
        if not path_check(dest):
            download_gsfile(module, gs, bucket, obj, dest)
        else:
            handle_get(module, gs, bucket, obj, overwrite, dest)

    if mode == "put":
        if not path_check(src):
            module.fail_json(msg="Local object for PUT does not exist", failed=True)
        handle_put(module, gs, bucket, obj, overwrite, src, expiry)

    # Support for deleting an object if we have both params.
    if mode == "delete":
        handle_delete(module, gs, bucket, obj)

    if mode == "create":
        handle_create(module, gs, bucket, obj)

    if mode == "get_url":
        if bucket and obj:
            if bucket_check(module, gs, bucket) and key_check(module, gs, bucket, obj):
                get_download_url(module, gs, bucket, obj, expiry)
            else:
                module.fail_json(msg="Key/Bucket does not exist", failed=True)
        else:
            module.fail_json(
                msg="Bucket and Object parameters must be set", failed=True
            )

    # --------------------------- Get the String contents of an Object -------------------------
    if mode == "get_str":
        if bucket and obj:
            if bucket_check(module, gs, bucket) and key_check(module, gs, bucket, obj):
                download_gsstr(module, gs, bucket, obj)
            else:
                module.fail_json(msg="Key/Bucket does not exist", failed=True)
        else:
            module.fail_json(
                msg="Bucket and Object parameters must be set", failed=True
            )