# Buggy code
def calculate_multipart_etag(source_path, chunk_size=DEFAULT_CHUNK_SIZE):
    """
    calculates a multipart upload etag for amazon s3

    Arguments:

    source_path -- The file to calculate the etag for
    chunk_size -- The chunk size to calculate for.
    """

    md5s = []

    with open(source_path, "rb") as fp:
        while True:
            data = fp.read(chunk_size)

            if not data:
                break
            md5s.append(hashlib.md5(data))

    if len(md5s) == 1:
        new_etag = '"{}"'.format(md5s[0].hexdigest())
    else:  # > 1
        digests = b"".join(m.digest() for m in md5s)

        new_md5 = hashlib.md5(digests)
        new_etag = '"{}-{}"'.format(new_md5.hexdigest(), len(md5s))

    return new_etag


# Traceback (exceptions.ValueError)
Using /apps/mule-platform/mule/ansible.cfg as config file
Loading callback plugin default of type stdout, v2.0 from /apps/ansible/lib/ansible/plugins/callback/__init__.pyc

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

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

TASK [Gathering Facts] ********************************************************************************************
Using module file /apps/ansible/lib/ansible/modules/system/setup.py
<localhost> ESTABLISH LOCAL CONNECTION FOR USER: mulrddep
<localhost> EXEC /bin/sh -c 'echo ~ &amp;&amp; sleep 0'
<localhost> EXEC /bin/sh -c '( umask 77 &amp;&amp; mkdir -p "` echo /home/mulrddep/.ansible/tmp/ansible-tmp-1502754249.89-250223534758667 `" &amp;&amp; echo ansible-tmp-1502754249.89-250223534758667="` echo /home/mulrddep/.ansible/tmp/ansible-tmp-1502754249.89-250223534758667 `" ) &amp;&amp; sleep 0'
<localhost> PUT /tmp/tmpL1iEtj TO /home/mulrddep/.ansible/tmp/ansible-tmp-1502754249.89-250223534758667/setup.py
<localhost> EXEC /bin/sh -c 'chmod u+x /home/mulrddep/.ansible/tmp/ansible-tmp-1502754249.89-250223534758667/ /home/mulrddep/.ansible/tmp/ansible-tmp-1502754249.89-250223534758667/setup.py &amp;&amp; sleep 0'
<localhost> EXEC /bin/sh -c '/usr/bin/python /home/mulrddep/.ansible/tmp/ansible-tmp-1502754249.89-250223534758667/setup.py; rm -rf "/home/mulrddep/.ansible/tmp/ansible-tmp-1502754249.89-250223534758667/" > /dev/null 2>&amp;1 &amp;&amp; sleep 0'
ok: [localhost]
META: ran handlers


TASK [Upload to s3 bucket] ****************************************************************************************
task path: /apps/mule-platform/mule/s3.yml:36
Using module file /apps/ansible/lib/ansible/modules/cloud/amazon/s3_sync.py
<localhost> ESTABLISH LOCAL CONNECTION FOR USER: mulrddep
<localhost> EXEC /bin/sh -c 'echo ~ &amp;&amp; sleep 0'
<localhost> EXEC /bin/sh -c '( umask 77 &amp;&amp; mkdir -p "` echo /home/mulrddep/.ansible/tmp/ansible-tmp-1502754251.47-153974990253487 `" &amp;&amp; echo ansible-tmp-1502754251.47-153974990253487="` echo /home/mulrddep/.ansible/tmp/ansible-tmp-1502754251.47-153974990253487 `" ) &amp;&amp; sleep 0'
<localhost> PUT /tmp/tmpL1iEtj TO /home/mulrddep/.ansible/tmp/ansible-tmp-1502754251.47-153974990253487/s3_sync.py
<localhost> EXEC /bin/sh -c 'chmod u+x /home/mulrddep/.ansible/tmp/ansible-tmp-1502754251.47-153974990253487/ /home/mulrddep/.ansible/tmp/ansible-tmp-1502754251.47-153974990253487/s3_sync.py &amp;&amp; sleep 0'
<localhost> EXEC /bin/sh -c '/usr/bin/python /home/mulrddep/.ansible/tmp/ansible-tmp-1502754251.47-153974990253487/s3_sync.py; rm -rf "/home/mulrddep/.ansible/tmp/ansible-tmp-1502754251.47-153974990253487/" > /dev/null 2>&amp;1 &amp;&amp; sleep 0'
fatal: [localhost]: FAILED! => {
"changed": false,
"failed": true,
"invocation": {
"module_args": {
"aws_access_key": "ASIAILNFILJ74DT75P3A",
"aws_secret_key": "VALUE_SPECIFIED_IN_NO_LOG_PARAMETER",
"bucket": "suncorp-mule-connector-updatesites",
"ec2_url": null,
"exclude": ".*",
"file_change_strategy": "force",
"file_root": "/tmp/connector-life400/",
"include": "*",
"key_prefix": "connector-life400",
"mime_map": null,
"mode": "push",
"permission": null,
"profile": null,
"region": "ap-southeast-2",
"retries": null,
"security_token": "VALUE_SPECIFIED_IN_NO_LOG_PARAMETER",
"validate_certs": true
}
},
"msg": "zero length field name in format zero length field name in format - <type 'exceptions.ValueError'>",
"traceback": [
"Traceback (most recent call last):",
"  File \"/tmp/ansible_ZjFzBU/ansible_module_s3_sync.py\", line 478, in main",
"    result['filelist_local_etag'] = calculate_local_etag(result['filelist_s3'])",
"  File \"/tmp/ansible_ZjFzBU/ansible_module_s3_sync.py\", line 330, in calculate_local_etag",
"    retentry['local_etag'] = calculate_multipart_etag(fileentry['fullpath'])",
"  File \"/tmp/ansible_ZjFzBU/ansible_module_s3_sync.py\", line 265, in calculate_multipart_etag",
"    new_etag = '\"{}\"'.format(md5s[0].hexdigest())",
"ValueError: zero length field name in format"
]
}
to retry, use: --limit @/apps/mule-platform/mule/s3.retry

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

# Fixed code
def calculate_multipart_etag(source_path, chunk_size=DEFAULT_CHUNK_SIZE):
    """
    calculates a multipart upload etag for amazon s3

    Arguments:

    source_path -- The file to calculate the etag for
    chunk_size -- The chunk size to calculate for.
    """

    md5s = []

    with open(source_path, "rb") as fp:
        while True:
            data = fp.read(chunk_size)

            if not data:
                break
            md5s.append(hashlib.md5(data))

    if len(md5s) == 1:
        new_etag = '"{0}"'.format(md5s[0].hexdigest())
    else:  # > 1
        digests = b"".join(m.digest() for m in md5s)

        new_md5 = hashlib.md5(digests)
        new_etag = '"{0}-{1}"'.format(new_md5.hexdigest(), len(md5s))

    return new_etag