From 7bdad1038f4fc88ce2612d786bf345b254b5ba54 Mon Sep 17 00:00:00 2001 From: abikouo <79859644+abikouo@users.noreply.github.com> Date: Fri, 7 Jan 2022 10:29:41 +0100 Subject: [PATCH] aws_s3 - fix issue when copy missing key from bucket (#603) aws_s3 - fix issue when copy missing key from bucket SUMMARY #602 ISSUE TYPE Bugfix Pull Request COMPONENT NAME aws_s3 ADDITIONAL INFORMATION Reviewed-by: Alina Buzachis Reviewed-by: None --- .../593-aws_s3-fix-copy-when-missing-key.yml | 2 + plugins/modules/aws_s3.py | 76 ++++++++++--------- .../targets/aws_s3/tasks/copy_object.yml | 16 ++++ 3 files changed, 59 insertions(+), 35 deletions(-) create mode 100644 changelogs/fragments/593-aws_s3-fix-copy-when-missing-key.yml diff --git a/changelogs/fragments/593-aws_s3-fix-copy-when-missing-key.yml b/changelogs/fragments/593-aws_s3-fix-copy-when-missing-key.yml new file mode 100644 index 00000000000..bc22d6ef934 --- /dev/null +++ b/changelogs/fragments/593-aws_s3-fix-copy-when-missing-key.yml @@ -0,0 +1,2 @@ +bugfixes: +- aws_s3 - fix exception raised when using module to copy from source to destination and key is missing from source (https://github.com/ansible-collections/amazon.aws/issues/602). diff --git a/plugins/modules/aws_s3.py b/plugins/modules/aws_s3.py index 5b185966d9f..a4582a41984 100644 --- a/plugins/modules/aws_s3.py +++ b/plugins/modules/aws_s3.py @@ -412,13 +412,16 @@ def etag_compare(module, s3, bucket, obj, version=None, local_file=None, content def get_etag(s3, bucket, obj, version=None): - if version: - key_check = s3.head_object(Bucket=bucket, Key=obj, VersionId=version) - else: - key_check = s3.head_object(Bucket=bucket, Key=obj) - if not key_check: + try: + if version: + key_check = s3.head_object(Bucket=bucket, Key=obj, VersionId=version) + else: + key_check = s3.head_object(Bucket=bucket, Key=obj) + if not key_check: + return None + return key_check['ETag'] + except is_boto3_error_code('404'): return None - return key_check['ETag'] def bucket_check(module, s3, bucket, validate=True): @@ -721,40 +724,43 @@ def copy_object_to_bucket(module, s3, bucket, obj, encrypt, metadata, validate, if module.params['copy_src'].get('version_id') is not None: version = module.params['copy_src'].get('version_id') bucketsrc.update({'VersionId': version}) - keyrtn = key_check(module, s3, bucketsrc['Bucket'], bucketsrc['Key'], version=version, validate=validate) - if keyrtn: - s_etag = get_etag(s3, bucketsrc['Bucket'], bucketsrc['Key'], version=version) - if s_etag == d_etag: - # Tags - tags, changed = ensure_tags(s3, module, bucket, obj) - if not changed: - module.exit_json(msg="ETag from source and destination are the same", changed=False) + if not key_check(module, s3, bucketsrc['Bucket'], bucketsrc['Key'], version=version, validate=validate): + # Key does not exist in source bucket + module.exit_json(msg="Key %s does not exist in bucket %s." % (bucketsrc['Key'], bucketsrc['Bucket']), changed=False) + + s_etag = get_etag(s3, bucketsrc['Bucket'], bucketsrc['Key'], version=version) + if s_etag == d_etag: + # Tags + tags, changed = ensure_tags(s3, module, bucket, obj) + if not changed: + module.exit_json(msg="ETag from source and destination are the same", changed=False) else: - params.update({'CopySource': bucketsrc}) - if encrypt: - 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'] - if metadata: - params['Metadata'] = {} - # determine object metadata and extra arguments - for option in metadata: - extra_args_option = option_in_extra_args(option) - if extra_args_option is not None: - params[extra_args_option] = metadata[option] - else: - params['Metadata'][option] = metadata[option] - - copy_result = s3.copy_object(**params) - for acl in module.params.get('permission'): - s3.put_object_acl(ACL=acl, Bucket=bucket, Key=obj) - # Tags - tags, changed = ensure_tags(s3, module, bucket, obj) + module.exit_json(msg="tags successfully updated.", changed=changed, tags=tags) + else: + params.update({'CopySource': bucketsrc}) + if encrypt: + 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'] + if metadata: + params['Metadata'] = {} + # determine object metadata and extra arguments + for option in metadata: + extra_args_option = option_in_extra_args(option) + if extra_args_option is not None: + params[extra_args_option] = metadata[option] + else: + params['Metadata'][option] = metadata[option] + copy_result = s3.copy_object(**params) + for acl in module.params.get('permission'): + s3.put_object_acl(ACL=acl, Bucket=bucket, Key=obj) + # Tags + tags, changed = ensure_tags(s3, module, bucket, obj) + module.exit_json(msg="Object copied from bucket %s to bucket %s." % (bucketsrc['Bucket'], bucket), tags=tags, changed=True) except is_boto3_error_code(IGNORE_S3_DROP_IN_EXCEPTIONS): module.warn("PutObjectAcl is not implemented by your storage provider. Set the permissions parameters to the empty list to avoid this warning") except (botocore.exceptions.BotoCoreError, botocore.exceptions.ClientError) as e: # pylint: disable=duplicate-except module.fail_json_aws(e, msg="Failed while copying object %s from bucket %s." % (obj, module.params['copy_src'].get('Bucket'))) - module.exit_json(msg="Object copied from bucket %s to bucket %s." % (bucketsrc['Bucket'], bucket), tags=tags, changed=True) def is_fakes3(s3_url): diff --git a/tests/integration/targets/aws_s3/tasks/copy_object.yml b/tests/integration/targets/aws_s3/tasks/copy_object.yml index 64267a0c9ca..18a3e9eb11d 100644 --- a/tests/integration/targets/aws_s3/tasks/copy_object.yml +++ b/tests/integration/targets/aws_s3/tasks/copy_object.yml @@ -112,6 +112,22 @@ that: - copy_result is not changed + - name: Copy from unexisting key should not succeed + aws_s3: + bucket: "{{ copy_bucket.dst }}" + mode: copy + object: missing_key.txt + copy_src: + bucket: "{{ copy_bucket.src }}" + object: this_key_does_not_exist.txt + register: result + + - name: Validate result when copying missing key + assert: + that: + - result is not changed + - 'result.msg == "Key this_key_does_not_exist.txt does not exist in bucket {{ copy_bucket.src }}."' + always: - include_tasks: delete_bucket.yml with_items: