Skip to content

Commit

Permalink
Inventory
Browse files Browse the repository at this point in the history
  • Loading branch information
abraverm committed May 1, 2024
1 parent 970c303 commit c1fcfc0
Show file tree
Hide file tree
Showing 3 changed files with 309 additions and 1 deletion.
180 changes: 179 additions & 1 deletion plugins/modules/s3_bucket.py
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,50 @@
type: bool
default: false
version_added: 6.0.0
inventory:
description:
- Enable S3 Inventory, saving list of the objects and their corresponding
metadata on a daily or weekly basis for an S3 bucket.
type: dict
suboptions:
destination:
type: dict
required: True
options:
account_id:
type: str
bucket:
type: str
required: True
format:
type: str
choices: [ 'CSV', 'OCR', 'Parquet' ]
default: CSV
prefix:
type: str
filter:
type: str
id:
type: str
required: True
schedule:
type: str
default: Weekly
choices: [ 'Daily', 'Weekly' ]
include_object_version:
type: str
default: All
choices: [ 'All', 'Current' ]
optional_fields:
type: list
elements: str
choices: [ "Size", "LastModifiedDate", "StorageClass", "ETag",
"IsMultipartUploaded", "ReplicationStatus", "EncryptionStatus",
"ObjectLockRetainUntilDate", "ObjectLockMode",
"ObjectLockLegalHoldStatus", "IntelligentTieringAccessTier",
"BucketKeyStatus", "ChecksumAlgorithm", "ObjectAccessControlList",
"ObjectOwner" ]
extends_documentation_fragment:
- amazon.aws.common.modules
- amazon.aws.region.modules
Expand Down Expand Up @@ -832,6 +875,50 @@ def handle_bucket_object_lock(s3_client, module: AnsibleAWSModule, name: str) ->
return object_lock_result


def handle_bucket_inventory(s3_client, module: AnsibleAWSModule, name: str) -> tuple[bool, dict]:
"""
Manage inventory configuration for an S3 bucket.
Parameters:
s3_client (boto3.client): The Boto3 S3 client object.
module (AnsibleAWSModule): The Ansible module object.
name (str): The name of the bucket to handle inventory for.
Returns:
A tuple containing a boolean indicating whether inventory settings were changed
and a dictionary containing the updated inventory.
"""
bucket_inventory_settings = module.params.get("inventory")
bucket_inventory_result = {}
bucket_inventory_changed = False

try:
bucket_inventory_status = get_bucket_inventory(s3_client, name)
bucket_inventory_result = bucket_inventory_status
except is_boto3_error_code(["NotImplemented", "XNotImplemented"]) as e:
if bucket_inventory_settings is not None:
module.fail_json(msg="Fetching bucket inventory state is not supported")
except is_boto3_error_code("ObjectLockConfigurationNotFoundError"): # pylint: disable=duplicate-except
if bucket_inventory_settings:
module.fail_json(msg="Enabling object lock for existing buckets is not supported")
object_lock_result = False
except is_boto3_error_code("AccessDenied") as e: # pylint: disable=duplicate-except
if bucket_inventory_settings is not None:
module.fail_json(msg="Permission denied fetching object lock state for bucket")
except (
botocore.exceptions.BotoCoreError,
botocore.exceptions.ClientError,
) as e: # pylint: disable=duplicate-except
module.fail_json_aws(e, msg="Failed to fetch bucket object lock state")
else:
if bucket_inventory_status is not None:
if not bucket_inventory_settings and bucket_inventory_status:
delete_bucket_inventory(s3_client, name)
bucket_inventory_changed = True
if bucket_inventory_settings and not bucket_inventory_status:
put_bucket_inventory(s3_client, name, bucket_inventory_settings)
bucket_inventory_changed = True

return bucket_inventory_changed, bucket_inventory_result

def create_or_update_bucket(s3_client, module: AnsibleAWSModule):
"""
Create or update an S3 bucket along with its associated configurations.
Expand Down Expand Up @@ -908,6 +995,10 @@ def create_or_update_bucket(s3_client, module: AnsibleAWSModule):
bucket_object_lock_result = handle_bucket_object_lock(s3_client, module, name)
result["object_lock_enabled"] = bucket_object_lock_result

# -- Inventory
bucket_inventory_changed, bucket_inventory_result = handle_bucket_inventory(s3_client, module, name)
result["bucket_inventory"] = bucket_inventory_result

# Module exit
changed = (
changed
Expand All @@ -919,6 +1010,7 @@ def create_or_update_bucket(s3_client, module: AnsibleAWSModule):
or encryption_changed
or bucket_ownership_changed
or bucket_acl_changed
or bucket_inventory_changed
)
module.exit_json(changed=changed, name=name, **result)

Expand Down Expand Up @@ -973,6 +1065,20 @@ def create_bucket(s3_client, bucket_name: str, location: str, object_lock_enable
return False


@AWSRetry.exponential_backoff(max_delay=120, catch_extra_error_codes=["NoSuchBucket", "OperationAborted"])
def put_bucket_inventory(s3_client, bucket_name: str, inventory: dict):
"""
Set inventory settings for an S3 bucket.
Parameters:
s3_client (boto3.client): The Boto3 S3 client object.
bucket_name (str): The name of the S3 bucket.
tags (dict): A dictionary containing the inventory settings to be set on the bucket.
Returns:
None
"""
s3_client.put_bucket_inventory_configuration(Bucket=bucket_name, InventoryConfiguration=inventory, Id=inventory.get("Id"))


@AWSRetry.exponential_backoff(max_delay=120, catch_extra_error_codes=["NoSuchBucket", "OperationAborted"])
def put_bucket_tagging(s3_client, bucket_name: str, tags: dict):
"""
Expand All @@ -987,6 +1093,37 @@ def put_bucket_tagging(s3_client, bucket_name: str, tags: dict):
s3_client.put_bucket_tagging(Bucket=bucket_name, Tagging={"TagSet": ansible_dict_to_boto3_tag_list(tags)})


@AWSRetry.exponential_backoff(max_delay=120, catch_extra_error_codes=["NoSuchBucket", "OperationAborted"])
def delete_bucket_inventory(s3_client, bucket_name: str, id: str):
"""
Delete the inventory settings for an S3 bucket.
Parameters:
s3_client (boto3.client): The Boto3 S3 client object.
bucket_name (str): The name of the S3 bucket.
Returns:
None
"""
s3_client.delete_bucket_inventory_configuration(Bucket=bucket_name, Id=id)


@AWSRetry.exponential_backoff(max_delay=120, catch_extra_error_codes=["NoSuchBucket", "OperationAborted"])
def get_bucket_inventory(s3_client, bucket_name: str) -> dict:
"""
Get the inventory settings for an S3 bucket.
Parameters:
s3_client (boto3.client): The Boto3 S3 client object.
bucket_name (str): The name of the S3 bucket.
Returns:
Current inventory settings.
"""
try:
result = s3_client.list_bucket_inventory_configurations(Bucket=bucket_name)
inventory_list = result.get("InventoryConfigurationList", [])
return inventory_list[0] if inventory_list else {}
except is_boto3_error_code("NoSuchConfiguration"): # pylint: disable=duplicate-except
return {}


@AWSRetry.exponential_backoff(max_delay=120, catch_extra_error_codes=["NoSuchBucket", "OperationAborted"])
def put_bucket_policy(s3_client, bucket_name: str, policy: dict):
"""
Expand Down Expand Up @@ -1733,6 +1870,47 @@ def main():
validate_bucket_name=dict(type="bool", default=True),
dualstack=dict(default=False, type="bool"),
object_lock_enabled=dict(type="bool"),
inventory=dict(
type="dict",
options=dict(
destination=dict(
type="dict",
options=dict(
account_id=dict(type="str"),
bucket=dict(type="str", required=True),
format=dict(type="str", choices=["CSV", "ORC", "Parquet"], default="CSV"),
prefix=dict(type="str"),
),
required=True,
),
filter=dict(type="str"),
optional_fields=dict(
type="list",
elements="str",
choices=[
"Size",
"LastModifiedDate",
"StorageClass",
"ETag",
"IsMultipartUploaded",
"ReplicationStatus",
"EncryptionStatus",
"ObjectLockRetainUntilDate",
"ObjectLockMode",
"ObjectLockLegalHoldStatus",
"IntelligentTieringAccessTier",
"BucketKeyStatus",
"ChecksumAlgorithm",
"ObjectAccessControlList",
"ObjectOwner",
],
default=[],
),
id=dict(type="str", required=True),
schedule=dict(type="str", default="Weekly", choices=["Daily", "Weekly"]),
included_object_versions=dict(type="str", default="All", choices=["All", "Current"]),
),
),
)

required_by = dict(
Expand Down
1 change: 1 addition & 0 deletions tests/integration/targets/s3_bucket/inventory
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ encryption_sse
public_access
acl
object_lock
inventory

[all:vars]
ansible_connection=local
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
---
- module_defaults:
group/aws:
access_key: "{{ aws_access_key }}"
secret_key: "{{ aws_secret_key }}"
session_token: "{{ security_token | default(omit) }}"
region: "{{ aws_region }}"
block:
- ansible.builtin.set_fact:
local_bucket_name: "{{ bucket_name | hash('md5')}}-inventory-source"
local_dest_bucket_name: "{{ bucket_name | hash('md5')}}-inventory-target"
# ============================================================
- name: Create a simple bucket to be inventory destination
amazon.aws.s3_bucket:
name: "{{ local_dest_bucket_name }}"
state: present
register: output

- ansible.builtin.assert:
that:
- output.changed

- name: Create a simple bucket with minimal inventory configuration
amazon.aws.s3_bucket:
name: "{{ local_bucket_name }}"
state: present
inventory:
id: "{{ local_dest_bucket_name }}"
destination:
bucket: "arn:aws:s3:::{{ local_dest_bucket_name }}"
register: output

- ansible.builtin.assert:
that:
- output.changed
- output.bucket_inventory

- name: Re-configure inventory configuration
amazon.aws.s3_bucket:
name: "{{ local_bucket_name }}"
state: present
inventory:
id: "{{ local_dest_bucket_name }}"
destination:
bucket: "arn:aws:s3:::{{ local_dest_bucket_name }}"
schedule: "Daily"
register: output

- ansible.builtin.assert:
that:
- output.changed
- output.bucket_inventory

- name: Re-configure inventory configuration (idempotency)
amazon.aws.s3_bucket:
name: "{{ local_bucket_name }}"
state: present
inventory:
id: "{{ local_dest_bucket_name }}"
destination:
bucket: "arn:aws:s3:::{{ local_dest_bucket_name }}"
schedule: "Daily"
register: output

- ansible.builtin.assert:
that:
- output is not changed
- output.bucket_inventory


- name: Delete inventory configuration
amazon.aws.s3_bucket:
name: "{{ local_bucket_name }}"
state: present
register: output

- ansible.builtin.assert:
that:
- output is changed
- not output.bucket_inventory|bool

- name: Delete inventory configuration (idempotency)
amazon.aws.s3_bucket:
name: "{{ local_bucket_name }}"
state: present
register: output

- ansible.builtin.assert:
that:
- output is not changed
- not output.bucket_inventory|bool

# ============================================================

- name: Delete testing s3 bucket
amazon.aws.s3_bucket:
name: "{{ local_bucket_name }}"
state: absent
register: output

- ansible.builtin.assert:
that:
- output.changed


- name: Delete testing inventory s3 bucket
amazon.aws.s3_bucket:
name: "{{ local_dest_bucket_name }}"
state: absent
register: output

- ansible.builtin.assert:
that:
- output.changed


# ============================================================
always:
- name: Ensure all buckets are deleted
amazon.aws.s3_bucket:
name: "{{ local_bucket_name }}"
state: absent
ignore_errors: true

- name: Ensure all buckets are deleted
amazon.aws.s3_bucket:
name: "{{ local_dest_bucket_name }}"
state: absent
ignore_errors: true

0 comments on commit c1fcfc0

Please sign in to comment.