[aws] Support custom KMS keys in aws_s3 module (#35761)

* Allow the use of 'aws:kms' as an encryption method
* Allow the use of a non standard KMS key
* Deduce whether AWS Signature Version 4 is required rather than specifying with a parameter
This commit is contained in:
Will Thames 2018-06-07 01:22:52 +10:00 committed by Ryan Brown
parent 146cc2dd9c
commit f61164406e
2 changed files with 483 additions and 304 deletions

View file

@ -52,6 +52,14 @@ options:
- When set for PUT mode, asks for server-side encryption. - When set for PUT mode, asks for server-side encryption.
default: True default: True
version_added: "2.0" version_added: "2.0"
encryption_mode:
description:
- What encryption mode to use if C(encrypt) is set
default: AES256
choices:
- AES256
- aws:kms
version_added: "2.7"
expiration: expiration:
description: description:
- Time limit (in seconds) for the URL generated and returned by S3/Walrus when performing a mode=put or mode=geturl operation. - Time limit (in seconds) for the URL generated and returned by S3/Walrus when performing a mode=put or mode=geturl operation.
@ -140,6 +148,10 @@ options:
GetObject permission but no other permissions. In this case using the option mode: get will fail without specifying GetObject permission but no other permissions. In this case using the option mode: get will fail without specifying
ignore_nonexistent_bucket: True." ignore_nonexistent_bucket: True."
version_added: "2.3" version_added: "2.3"
encryption_kms_key_id:
description:
- KMS key id to use when encrypting objects using C(aws:kms) encryption. Ignored if encryption is not C(aws:kms)
version_added: "2.7"
requirements: [ "boto3", "botocore" ] requirements: [ "boto3", "botocore" ]
author: author:
@ -290,6 +302,10 @@ except ImportError:
pass # will be detected by imported HAS_BOTO3 pass # will be detected by imported HAS_BOTO3
class Sigv4Required(Exception):
pass
def key_check(module, s3, bucket, obj, version=None, validate=True): def key_check(module, s3, bucket, obj, version=None, validate=True):
exists = True exists = True
try: try:
@ -443,7 +459,9 @@ def create_dirkey(module, s3, bucket, obj, encrypt):
try: try:
params = {'Bucket': bucket, 'Key': obj, 'Body': b''} params = {'Bucket': bucket, 'Key': obj, 'Body': b''}
if encrypt: if encrypt:
params['ServerSideEncryption'] = 'AES256' params['ServerSideEncryption'] = module.params['encryption_mode']
if module.params['encryption_kms_key_id'] and module.params['encryption_mode'] == 'aws:kms':
params['SSEKMSKeyId'] = module.params['encryption_kms_key_id']
s3.put_object(**params) s3.put_object(**params)
for acl in module.params.get('permission'): for acl in module.params.get('permission'):
@ -481,7 +499,9 @@ def upload_s3file(module, s3, bucket, obj, src, expiry, metadata, encrypt, heade
try: try:
extra = {} extra = {}
if encrypt: if encrypt:
extra['ServerSideEncryption'] = 'AES256' extra['ServerSideEncryption'] = module.params['encryption_mode']
if module.params['encryption_kms_key_id'] and module.params['encryption_mode'] == 'aws:kms':
extra['SSEKMSKeyId'] = module.params['encryption_kms_key_id']
if metadata: if metadata:
extra['Metadata'] = {} extra['Metadata'] = {}
@ -522,7 +542,9 @@ def download_s3file(module, s3, bucket, obj, dest, retries, version=None):
else: else:
key = s3.get_object(Bucket=bucket, Key=obj) key = s3.get_object(Bucket=bucket, Key=obj)
except botocore.exceptions.ClientError as e: except botocore.exceptions.ClientError as e:
if e.response['Error']['Code'] != "404": if e.response['Error']['Code'] == 'InvalidArgument' and 'require AWS Signature Version 4' in to_text(e):
raise Sigv4Required()
elif e.response['Error']['Code'] != "404":
module.fail_json(msg="Could not find the key %s." % obj, exception=traceback.format_exc(), **camel_dict_to_snake_dict(e.response)) module.fail_json(msg="Could not find the key %s." % obj, exception=traceback.format_exc(), **camel_dict_to_snake_dict(e.response))
for x in range(0, retries + 1): for x in range(0, retries + 1):
@ -551,8 +573,11 @@ def download_s3str(module, s3, bucket, obj, version=None, validate=True):
contents = to_native(s3.get_object(Bucket=bucket, Key=obj)["Body"].read()) contents = to_native(s3.get_object(Bucket=bucket, Key=obj)["Body"].read())
module.exit_json(msg="GET operation complete", contents=contents, changed=True) module.exit_json(msg="GET operation complete", contents=contents, changed=True)
except botocore.exceptions.ClientError as e: except botocore.exceptions.ClientError as e:
module.fail_json(msg="Failed while getting contents of object %s as a string." % obj, if e.response['Error']['Code'] == 'InvalidArgument' and 'require AWS Signature Version 4' in to_text(e):
exception=traceback.format_exc(), **camel_dict_to_snake_dict(e.response)) raise Sigv4Required()
else:
module.fail_json(msg="Failed while getting contents of object %s as a string." % obj,
exception=traceback.format_exc(), **camel_dict_to_snake_dict(e.response))
def get_download_url(module, s3, bucket, obj, expiry, changed=True): def get_download_url(module, s3, bucket, obj, expiry, changed=True):
@ -584,7 +609,7 @@ def is_walrus(s3_url):
return False return False
def get_s3_connection(module, aws_connect_kwargs, location, rgw, s3_url): def get_s3_connection(module, aws_connect_kwargs, location, rgw, s3_url, sig_4=False):
if s3_url and rgw: # TODO - test this if s3_url and rgw: # TODO - test this
rgw = urlparse(s3_url) rgw = urlparse(s3_url)
params = dict(module=module, conn_type='client', resource='s3', use_ssl=rgw.scheme == 'https', region=location, endpoint=s3_url, **aws_connect_kwargs) params = dict(module=module, conn_type='client', resource='s3', use_ssl=rgw.scheme == 'https', region=location, endpoint=s3_url, **aws_connect_kwargs)
@ -607,6 +632,10 @@ def get_s3_connection(module, aws_connect_kwargs, location, rgw, s3_url):
params = dict(module=module, conn_type='client', resource='s3', region=location, endpoint=walrus, **aws_connect_kwargs) params = dict(module=module, conn_type='client', resource='s3', region=location, endpoint=walrus, **aws_connect_kwargs)
else: else:
params = dict(module=module, conn_type='client', resource='s3', region=location, endpoint=s3_url, **aws_connect_kwargs) params = dict(module=module, conn_type='client', resource='s3', region=location, endpoint=s3_url, **aws_connect_kwargs)
if module.params['mode'] == 'put' and module.params['encryption_mode'] == 'aws:kms':
params['config'] = botocore.client.Config(signature_version='s3v4')
elif module.params['mode'] in ('get', 'getstr') and sig_4:
params['config'] = botocore.client.Config(signature_version='s3v4')
return boto3_conn(**params) return boto3_conn(**params)
@ -617,6 +646,7 @@ def main():
bucket=dict(required=True), bucket=dict(required=True),
dest=dict(default=None, type='path'), dest=dict(default=None, type='path'),
encrypt=dict(default=True, type='bool'), encrypt=dict(default=True, type='bool'),
encryption_mode=dict(choices=['AES256', 'aws:kms'], default='AES256'),
expiry=dict(default=600, type='int', aliases=['expiration']), expiry=dict(default=600, type='int', aliases=['expiration']),
headers=dict(type='dict'), headers=dict(type='dict'),
marker=dict(default=""), marker=dict(default=""),
@ -632,7 +662,8 @@ def main():
s3_url=dict(aliases=['S3_URL']), s3_url=dict(aliases=['S3_URL']),
rgw=dict(default='no', type='bool'), rgw=dict(default='no', type='bool'),
src=dict(), src=dict(),
ignore_nonexistent_bucket=dict(default=False, type='bool') ignore_nonexistent_bucket=dict(default=False, type='bool'),
encryption_kms_key_id=dict()
), ),
) )
module = AnsibleModule( module = AnsibleModule(
@ -746,18 +777,30 @@ def main():
if keysum_compare(module, dest, s3, bucket, obj, version=version): if keysum_compare(module, dest, s3, bucket, obj, version=version):
sum_matches = True sum_matches = True
if overwrite == 'always': if overwrite == 'always':
download_s3file(module, s3, bucket, obj, dest, retries, version=version) try:
download_s3file(module, s3, bucket, obj, dest, retries, version=version)
except Sigv4Required:
s3 = get_s3_connection(module, aws_connect_kwargs, location, rgw, s3_url, sig_4=True)
download_s3file(module, s3, bucket, obj, dest, retries, version=version)
else: else:
module.exit_json(msg="Local and remote object are identical, ignoring. Use overwrite=always parameter to force.", changed=False) module.exit_json(msg="Local and remote object are identical, ignoring. Use overwrite=always parameter to force.", changed=False)
else: else:
sum_matches = False sum_matches = False
if overwrite in ('always', 'different'): if overwrite in ('always', 'different'):
download_s3file(module, s3, bucket, obj, dest, retries, version=version) try:
download_s3file(module, s3, bucket, obj, dest, retries, version=version)
except Sigv4Required:
s3 = get_s3_connection(module, aws_connect_kwargs, location, rgw, s3_url, sig_4=True)
download_s3file(module, s3, bucket, obj, dest, retries, version=version)
else: else:
module.exit_json(msg="WARNING: Checksums do not match. Use overwrite parameter to force download.") module.exit_json(msg="WARNING: Checksums do not match. Use overwrite parameter to force download.")
else: else:
download_s3file(module, s3, bucket, obj, dest, retries, version=version) try:
download_s3file(module, s3, bucket, obj, dest, retries, version=version)
except Sigv4Required:
s3 = get_s3_connection(module, aws_connect_kwargs, location, rgw, s3_url, sig_4=True)
download_s3file(module, s3, bucket, obj, dest, retries, version=version)
# if our mode is a PUT operation (upload), go through the procedure as appropriate ... # if our mode is a PUT operation (upload), go through the procedure as appropriate ...
if mode == 'put': if mode == 'put':
@ -887,7 +930,11 @@ def main():
if bucket and obj: if bucket and obj:
keyrtn = key_check(module, s3, bucket, obj, version=version, validate=validate) keyrtn = key_check(module, s3, bucket, obj, version=version, validate=validate)
if keyrtn: if keyrtn:
download_s3str(module, s3, bucket, obj, version=version) try:
download_s3str(module, s3, bucket, obj, version=version)
except Sigv4Required:
s3 = get_s3_connection(module, aws_connect_kwargs, location, rgw, s3_url, sig_4=True)
download_s3str(module, s3, bucket, obj, version=version)
elif version is not None: elif version is not None:
module.fail_json(msg="Key %s with version id %s does not exist." % (obj, version)) module.fail_json(msg="Key %s with version id %s does not exist." % (obj, version))
else: else:

View file

@ -1,6 +1,6 @@
--- ---
# tasks file for test_s3 # tasks file for test_s3
# ============================================================
- name: set up aws connection info - name: set up aws connection info
set_fact: set_fact:
aws_connection_info: &aws_connection_info aws_connection_info: &aws_connection_info
@ -9,333 +9,465 @@
security_token: "{{ security_token }}" security_token: "{{ security_token }}"
region: "{{ aws_region }}" region: "{{ aws_region }}"
no_log: yes no_log: yes
# ============================================================
- name: test create bucket
aws_s3:
bucket: "{{ bucket_name }}"
mode: create
<<: *aws_connection_info
register: result
- name: assert changed is True
assert:
that:
- result.changed == True
# ============================================================
- name: trying to create a bucket name that already exists
aws_s3:
bucket: "{{ bucket_name }}"
mode: create
<<: *aws_connection_info
register: result
- name: assert changed is False since the bucket already exists
assert:
that:
- result.changed == False
# ============================================================
- name: create temporary file object to put in a bucket
tempfile:
register: tmp1
- name: make random contents
set_fact:
content: "{{ lookup('password', '/dev/null chars=ascii_letters,digits,hexdigits,punctuation') }}"
- name: give temporary file data - block:
copy: - name: test create bucket
content: "{{ content }}"
dest: "{{ tmp1.path }}"
- name: get the stat of the file
stat:
path: "{{ tmp1.path }}"
get_checksum: yes
register: file1stat
# ============================================================
- name: test putting an object in the bucket
aws_s3:
bucket: "{{ bucket_name }}"
mode: put
src: "{{ tmp1.path }}"
object: delete.txt
<<: *aws_connection_info
retries: 3
delay: 3
register: result
- name: assert object exists
assert:
that:
- result.changed == True
- result.msg == "PUT operation complete"
# ============================================================
- name: check that roles file lookups work as expected
aws_s3:
bucket: "{{ bucket_name }}"
mode: put
src: hello.txt
object: hello.txt
<<: *aws_connection_info
retries: 3
delay: 3
register: result
- name: assert object exists
assert:
that:
- result.changed == True
- result.msg == "PUT operation complete"
- name: remove hello.txt (deletion tests are later)
aws_s3:
bucket: "{{ bucket_name }}"
mode: delobj
object: hello.txt
<<: *aws_connection_info
retries: 3
delay: 3
register: result
# ============================================================
- name: create a second temp file to download the object from the bucket
tempfile:
register: tmp2
- name: test get object
aws_s3:
bucket: "{{ bucket_name }}"
mode: get
dest: "{{ tmp2.path }}"
object: delete.txt
<<: *aws_connection_info
retries: 3
delay: 3
register: result
until: "result.msg == 'GET operation complete'"
- name: get the stat of the file so we can compare the checksums
stat:
path: "{{ tmp2.path }}"
get_checksum: yes
register: file2stat
- name: assert checksums are the same
assert:
that:
- file1stat.stat.checksum == file2stat.stat.checksum
# ============================================================
- name: test geturl of the object
aws_s3:
bucket: "{{ bucket_name }}"
mode: geturl
object: delete.txt
<<: *aws_connection_info
retries: 3
delay: 3
register: result
until: result.changed
- name: assert we have the object's url
assert:
that:
- "'Download url:' in result.msg"
- result.changed == True
# ============================================================
- name: test getstr of the object
aws_s3:
bucket: "{{ bucket_name }}"
mode: getstr
object: delete.txt
<<: *aws_connection_info
retries: 3
delay: 3
register: result
- name: assert that we have the object's contents
assert:
that:
- result.msg == "GET operation complete"
- result.contents == content
# ============================================================
- name: test list to get all objects in the bucket
aws_s3:
bucket: "{{ bucket_name }}"
mode: list
<<: *aws_connection_info
retries: 3
delay: 3
register: result
- name: assert that the keys are correct
assert:
that:
- "'delete.txt' in result.s3_keys"
- result.msg == "LIST operation complete"
# ============================================================
- name: test delobj to just delete an object in the bucket
aws_s3:
bucket: "{{ bucket_name }}"
mode: delobj
object: delete.txt
<<: *aws_connection_info
retries: 3
delay: 3
register: result
- name: assert that delete.txt is no longer an object in the bucket deleteme
assert:
that:
- "'Object deleted from bucket' in result.msg"
- result.changed == True
- name: assert that delete.txt is no longer an object in the bucket deleteme
assert:
that:
- "'Object deleted from bucket' in result.msg"
- result.changed == True
# ============================================================
- name: test creation of empty path
aws_s3:
bucket: "{{ bucket_name }}"
mode: create
object: foo/bar/baz/
<<: *aws_connection_info
retries: 3
delay: 3
register: result
- name: assert that empty path is created
assert:
that:
- "'Virtual directory foo/bar/baz/ created' in result.msg"
- result.changed == True
- name: test deletion of empty path
aws_s3:
bucket: "{{ bucket_name }}"
mode: delobj
object: foo/bar/baz/
<<: *aws_connection_info
retries: 3
delay: 3
# ============================================================
- name: test delete bucket
aws_s3:
bucket: "{{ bucket_name }}"
mode: delete
<<: *aws_connection_info
register: result
retries: 3
delay: 3
until: result.changed
- name: assert that changed is True
assert:
that:
- result.changed == True
# ============================================================
- name: delete temporary file 1
file:
state: absent
path: "{{ tmp1.path }}"
- name: delete temporary file 2
file:
state: absent
path: "{{ tmp2.path }}"
# ============================================================
- name: test create a bucket with a dot in the name
aws_s3:
bucket: "{{ bucket_name + '.bucket' }}"
mode: create
<<: *aws_connection_info
register: result
- name: assert that changed is True
assert:
that:
- result.changed == True
# ============================================================
- name: test delete a bucket with a dot in the name
aws_s3:
bucket: "{{ bucket_name + '.bucket' }}"
mode: delete
<<: *aws_connection_info
register: result
- name: assert that changed is True
assert:
that:
- result.changed == True
# ============================================================
- name: test delete a nonexistent bucket
aws_s3:
bucket: "{{ bucket_name + '.bucket' }}"
mode: delete
<<: *aws_connection_info
register: result
- name: assert that changed is False
assert:
that:
- result.changed == False
# ============================================================
- name: create a tempfile for the path
tempfile:
register: tmp1
- name: make tempfile 4 GB for OSX
command:
_raw_params: "dd if=/dev/zero of={{ tmp1.path }} bs=1m count=4096"
when: ansible_distribution == 'MacOSX'
- name: make tempfile 4 GB for linux
command:
_raw_params: "dd if=/dev/zero of={{ tmp1.path }} bs=1M count=4096"
when: ansible_distribution == 'Linux'
- name: test multipart download - platform specific
block:
- name: make a bucket to upload the file
aws_s3: aws_s3:
bucket: "{{ bucket_name }}" bucket: "{{ bucket_name }}"
mode: create mode: create
<<: *aws_connection_info <<: *aws_connection_info
register: result
- name: assert changed is True
assert:
that:
- result.changed == True
- name: upload the file to the bucket - name: trying to create a bucket name that already exists
aws_s3:
bucket: "{{ bucket_name }}"
mode: create
<<: *aws_connection_info
register: result
- name: assert changed is False since the bucket already exists
assert:
that:
- result.changed == False
- name: create temporary file object to put in a bucket
tempfile:
register: tmp1
- name: make random contents
set_fact:
content: "{{ lookup('password', '/dev/null chars=ascii_letters,digits,hexdigits,punctuation') }}"
- name: give temporary file data
copy:
content: "{{ content }}"
dest: "{{ tmp1.path }}"
- name: get the stat of the file
stat:
path: "{{ tmp1.path }}"
get_checksum: yes
register: file1stat
- name: test putting an object in the bucket
aws_s3: aws_s3:
bucket: "{{ bucket_name }}" bucket: "{{ bucket_name }}"
mode: put mode: put
src: "{{ tmp1.path }}" src: "{{ tmp1.path }}"
object: multipart.txt object: delete.txt
<<: *aws_connection_info <<: *aws_connection_info
retries: 3
delay: 3
register: result
- name: assert object exists
assert:
that:
- result.changed == True
- result.msg == "PUT operation complete"
- name: download file once - name: check that roles file lookups work as expected
aws_s3:
bucket: "{{ bucket_name }}"
mode: put
src: hello.txt
object: hello.txt
<<: *aws_connection_info
retries: 3
delay: 3
register: result
- name: assert object exists
assert:
that:
- result.changed == True
- result.msg == "PUT operation complete"
- name: remove hello.txt (deletion tests are later)
aws_s3:
bucket: "{{ bucket_name }}"
mode: delobj
object: hello.txt
<<: *aws_connection_info
retries: 3
delay: 3
register: result
- name: create a second temp file to download the object from the bucket
tempfile:
register: tmp2
- name: test get object
aws_s3: aws_s3:
bucket: "{{ bucket_name }}" bucket: "{{ bucket_name }}"
mode: get mode: get
dest: /tmp/multipart_download.txt dest: "{{ tmp2.path }}"
object: multipart.txt object: delete.txt
overwrite: different <<: *aws_connection_info
retries: 3
delay: 3
register: result
until: "result.msg == 'GET operation complete'"
- name: get the stat of the file so we can compare the checksums
stat:
path: "{{ tmp2.path }}"
get_checksum: yes
register: file2stat
- name: assert checksums are the same
assert:
that:
- file1stat.stat.checksum == file2stat.stat.checksum
- name: test geturl of the object
aws_s3:
bucket: "{{ bucket_name }}"
mode: geturl
object: delete.txt
<<: *aws_connection_info
retries: 3
delay: 3
register: result
until: result.changed
- name: assert we have the object's url
assert:
that:
- "'Download url:' in result.msg"
- result.changed == True
- name: test getstr of the object
aws_s3:
bucket: "{{ bucket_name }}"
mode: getstr
object: delete.txt
<<: *aws_connection_info
retries: 3
delay: 3
register: result
- name: assert that we have the object's contents
assert:
that:
- result.msg == "GET operation complete"
- result.contents == content
- name: test list to get all objects in the bucket
aws_s3:
bucket: "{{ bucket_name }}"
mode: list
<<: *aws_connection_info
retries: 3
delay: 3
register: result
- name: assert that the keys are correct
assert:
that:
- "'delete.txt' in result.s3_keys"
- result.msg == "LIST operation complete"
- name: test delobj to just delete an object in the bucket
aws_s3:
bucket: "{{ bucket_name }}"
mode: delobj
object: delete.txt
<<: *aws_connection_info
retries: 3
delay: 3
register: result
- name: assert that delete.txt is no longer an object in the bucket deleteme
assert:
that:
- "'Object deleted from bucket' in result.msg"
- result.changed == True
- name: assert that delete.txt is no longer an object in the bucket deleteme
assert:
that:
- "'Object deleted from bucket' in result.msg"
- result.changed == True
- name: clean up temp file
file:
path: "{{ tmp2.path }}"
state: absent
- name: test putting an encrypted object in the bucket
aws_s3:
bucket: "{{ bucket_name }}"
mode: put
src: "{{ tmp1.path }}"
encrypt: yes
object: delete_encrypt.txt
<<: *aws_connection_info
retries: 3
delay: 3
register: result
- name: assert object exists
assert:
that:
- result.changed == True
- result.msg == "PUT operation complete"
- name: create a second temp file to download the object from the bucket
tempfile:
register: tmp2
- name: test get encrypted object
aws_s3:
bucket: "{{ bucket_name }}"
mode: get
dest: "{{ tmp2.path }}"
object: delete_encrypt.txt
<<: *aws_connection_info
retries: 3
delay: 3
register: result
until: "result.msg == 'GET operation complete'"
- name: get the stat of the file so we can compare the checksums
stat:
path: "{{ tmp2.path }}"
get_checksum: yes
register: file2stat
- name: assert checksums are the same
assert:
that:
- file1stat.stat.checksum == file2stat.stat.checksum
- name: delete encrypted file
aws_s3:
bucket: "{{ bucket_name }}"
mode: delobj
object: delete_encrypt.txt
<<: *aws_connection_info
retries: 3
delay: 3
- name: clean up temp file
file:
path: "{{ tmp2.path }}"
state: absent
- name: test putting an aws:kms encrypted object in the bucket
aws_s3:
bucket: "{{ bucket_name }}"
mode: put
src: "{{ tmp1.path }}"
encrypt: yes
encryption_mode: aws:kms
object: delete_encrypt_kms.txt
<<: *aws_connection_info
retries: 3
delay: 3
register: result
- name: assert object exists
assert:
that:
- result.changed == True
- result.msg == "PUT operation complete"
- name: create a second temp file to download the object from the bucket
tempfile:
register: tmp2
- name: test get KMS encrypted object
aws_s3:
bucket: "{{ bucket_name }}"
mode: get
dest: "{{ tmp2.path }}"
object: delete_encrypt_kms.txt
<<: *aws_connection_info
retries: 3
delay: 3
register: result
until: "result.msg == 'GET operation complete'"
- name: get the stat of the file so we can compare the checksums
stat:
path: "{{ tmp2.path }}"
get_checksum: yes
register: file2stat
- name: assert checksums are the same
assert:
that:
- file1stat.stat.checksum == file2stat.stat.checksum
# FIXME - could use a test that checks uploaded file is *actually* aws:kms encrypted
- name: test get KMS encrypted object using v4 signature
aws_s3:
bucket: "{{ bucket_name }}"
mode: get
dest: "{{ tmp2.path }}"
object: delete_encrypt_kms.txt
<<: *aws_connection_info <<: *aws_connection_info
retries: 3 retries: 3
delay: 3 delay: 3
until: "result.msg == 'GET operation complete'" until: "result.msg == 'GET operation complete'"
register: result - name: delete KMS encrypted file
- name: assert the file was downloaded once
assert:
that:
- result.changed
- name: download file again
aws_s3: aws_s3:
bucket: "{{ bucket_name }}" bucket: "{{ bucket_name }}"
mode: get mode: delobj
dest: /tmp/multipart_download.txt object: delete_encrypt_kms.txt
object: multipart.txt
overwrite: different
<<: *aws_connection_info <<: *aws_connection_info
register: result retries: 3
delay: 3
- name: clean up temp file
file:
path: "{{ tmp2.path }}"
state: absent
- name: assert the file was not redownloaded # FIXME: could use a test that checks non standard KMS key
# but that would require ability to create and remove such keys.
# PRs exist for that, but propose deferring until after merge.
- name: test creation of empty path
aws_s3:
bucket: "{{ bucket_name }}"
mode: create
object: foo/bar/baz/
<<: *aws_connection_info
retries: 3
delay: 3
register: result
- name: assert that empty path is created
assert: assert:
that: that:
- not result.changed - "'Virtual directory foo/bar/baz/ created' in result.msg"
- result.changed == True
- name: test deletion of empty path
aws_s3:
bucket: "{{ bucket_name }}"
mode: delobj
object: foo/bar/baz/
<<: *aws_connection_info
retries: 3
delay: 3
- name: delete file used for upload - name: test delete bucket
aws_s3:
bucket: "{{ bucket_name }}"
mode: delete
<<: *aws_connection_info
register: result
retries: 3
delay: 3
until: result.changed
- name: assert that changed is True
assert:
that:
- result.changed == True
- name: test create a bucket with a dot in the name
aws_s3:
bucket: "{{ bucket_name + '.bucket' }}"
mode: create
<<: *aws_connection_info
register: result
- name: assert that changed is True
assert:
that:
- result.changed == True
- name: test delete a bucket with a dot in the name
aws_s3:
bucket: "{{ bucket_name + '.bucket' }}"
mode: delete
<<: *aws_connection_info
register: result
- name: assert that changed is True
assert:
that:
- result.changed == True
- name: test delete a nonexistent bucket
aws_s3:
bucket: "{{ bucket_name + '.bucket' }}"
mode: delete
<<: *aws_connection_info
register: result
- name: assert that changed is False
assert:
that:
- result.changed == False
- name: make tempfile 4 GB for OSX
command:
_raw_params: "dd if=/dev/zero of={{ tmp1.path }} bs=1m count=4096"
when: ansible_distribution == 'MacOSX'
- name: make tempfile 4 GB for linux
command:
_raw_params: "dd if=/dev/zero of={{ tmp1.path }} bs=1M count=4096"
when: ansible_system == 'Linux'
- name: test multipart download - platform specific
block:
- name: make a bucket to upload the file
aws_s3:
bucket: "{{ bucket_name }}"
mode: create
<<: *aws_connection_info
- name: upload the file to the bucket
aws_s3:
bucket: "{{ bucket_name }}"
mode: put
src: "{{ tmp1.path }}"
object: multipart.txt
<<: *aws_connection_info
- name: download file once
aws_s3:
bucket: "{{ bucket_name }}"
mode: get
dest: "{{ tmp2.path }}"
object: multipart.txt
overwrite: different
<<: *aws_connection_info
retries: 3
delay: 3
until: "result.msg == 'GET operation complete'"
register: result
- name: assert the file was downloaded once
assert:
that:
- result.changed
- name: download file again
aws_s3:
bucket: "{{ bucket_name }}"
mode: get
dest: "{{ tmp2.path }}"
object: multipart.txt
overwrite: different
<<: *aws_connection_info
register: result
- name: assert the file was not redownloaded
assert:
that:
- not result.changed
when: ansible_system == 'Linux' or ansible_distribution == 'MacOSX'
always:
###### TEARDOWN STARTS HERE ######
- name: remove uploaded files
aws_s3:
bucket: "{{ bucket_name }}"
mode: delobj
object: "{{ item }}"
<<: *aws_connection_info
with_items:
- hello.txt
- delete.txt
- delete_encrypt.txt
- delete_encrypt_kms.txt
ignore_errors: yes
- name: delete temporary file 1
file: file:
state: absent state: absent
path: "{{ tmp1.path }}" path: "{{ tmp1.path }}"
ignore_errors: yes
- name: delete downloaded file - name: delete temporary file 2
file: file:
state: absent state: absent
path: /tmp/multipart_download.txt path: "{{ tmp2.path }}"
ignore_errors: yes
- name: delete the bucket - name: delete the bucket
aws_s3: aws_s3:
bucket: "{{ bucket_name }}" bucket: "{{ bucket_name }}"
mode: delete mode: delete
<<: *aws_connection_info <<: *aws_connection_info
ignore_errors: yes
when: ansible_distribution in ['MacOSX', 'Linux']
# ============================================================