Repository: cloud-custodian/cloud-custodian

Issue:
s3 inventory action
enable / disable, and configure.


Discussion:

Resolving patch:
diff --git a/c7n/resources/s3.py b/c7n/resources/s3.py
--- a/c7n/resources/s3.py
+++ b/c7n/resources/s3.py
@@ -1601,6 +1601,97 @@ def process_resource_set(self, resource_set, tags):
             self.manager.session_factory, resource_set, remove_tags=tags)
 
 
+@actions.register('set-inventory')
+class SetInventory(BucketActionBase):
+    """Configure bucket inventories for an s3 bucket.
+    """
+    schema = type_schema(
+        'set-inventory',
+        required=['name', 'destination'],
+        state={'enum': ['enabled', 'disabled', 'absent']},
+        name={'type': 'string', 'description': 'Name of inventory'},
+        destination={'type': 'string', 'description': 'Name of destination bucket'},
+        prefix={'type': 'string', 'description': 'Destination prefix'},
+        versions={'enum': ['All', 'Current']},
+        schedule={'enum': ['Daily', 'Weekly']},
+        fields={'type': 'array', 'items': {'enum': [
+            'Size', 'LastModifiedDate', 'StorageClass', 'ETag',
+            'IsMultipartUploaded', 'ReplicationStatus']}})
+
+    permissions = ('s3:PutInventoryConfiguration', 's3:GetInventoryConfiguration')
+
+    def process(self, buckets):
+        with self.executor_factory(max_workers=2) as w:
+            list(w.map(self.process_bucket, buckets))
+
+    def process_bucket(self, b):
+        inventory_name = self.data.get('name')
+        destination = self.data.get('destination')
+        prefix = self.data.get('prefix', '')
+        schedule = self.data.get('schedule', 'Daily')
+        fields = self.data.get('fields', ['LastModifiedDate', 'Size'])
+        versions = self.data.get('versions', 'Current')
+        state = self.data.get('state', 'enabled')
+
+        if not prefix:
+            prefix = "Inventories/%s" % (self.manager.config.account_id)
+
+        client = bucket_client(local_session(self.manager.session_factory), b)
+        if state == 'absent':
+            try:
+                client.delete_bucket_inventory_configuration(
+                    Bucket=b['Name'], Id=inventory_name)
+            except ClientError as e:
+                if e.response['Error']['Code'] != 'NoSuchConfiguration':
+                    raise
+            return
+
+        inventory = {
+            'Destination': {
+                'S3BucketDestination': {
+                    'Bucket': "arn:aws:s3:::%s" % destination,
+                    'Format': 'CSV'}
+            },
+            'IsEnabled': state == 'enabled' and True or False,
+            'Id': inventory_name,
+            'OptionalFields': fields,
+            'IncludedObjectVersions': versions,
+            'Schedule': {
+                'Frequency': schedule
+            }
+        }
+
+        if prefix:
+            inventory['Destination']['S3BucketDestination']['Prefix'] = prefix
+
+        found = self.get_inventory_delta(client, inventory, b)
+        if found:
+            return
+        if found is False:
+            self.log.debug("updating bucket:%s inventory configuration id:%s",
+                           b['Name'], inventory_name)
+        client.put_bucket_inventory_configuration(
+            Bucket=b['Name'], Id=inventory_name, InventoryConfiguration=inventory)
+
+    def get_inventory_delta(self, client, inventory, b):
+        inventories = client.list_bucket_inventory_configurations(Bucket=b['Name'])
+        found = None
+        for i in inventories.get('InventoryConfigurationList', []):
+            if i['Id'] != inventory['Id']:
+                continue
+            found = True
+            for k, v in inventory.items():
+                if k not in i:
+                    found = False
+                    continue
+                if isinstance(v, list):
+                    v.sort()
+                    i[k].sort()
+                if i[k] != v:
+                    found = False
+        return found
+
+
 @actions.register('delete')
 class DeleteBucket(ScanBucket):
     """Action deletes a S3 bucket


Test patch:
diff --git a/tests/data/placebo/test_s3_inventory/s3.CreateBucket_1.json b/tests/data/placebo/test_s3_inventory/s3.CreateBucket_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_s3_inventory/s3.CreateBucket_1.json
@@ -0,0 +1,20 @@
+{
+    "status_code": 200, 
+    "data": {
+        "Location": "/custodian-test-data", 
+        "ResponseMetadata": {
+            "HTTPStatusCode": 200, 
+            "RetryAttempts": 0, 
+            "HostId": "eNbmqTw2a9k+2Ub3mJU71p8jgZ+vC+gyWCqzMRYglcM4J+/sOubzAY7WJyVdl3EUCoThIcYPz0s=", 
+            "RequestId": "3415E4693C23948D", 
+            "HTTPHeaders": {
+                "content-length": "0", 
+                "x-amz-id-2": "eNbmqTw2a9k+2Ub3mJU71p8jgZ+vC+gyWCqzMRYglcM4J+/sOubzAY7WJyVdl3EUCoThIcYPz0s=", 
+                "server": "AmazonS3", 
+                "x-amz-request-id": "3415E4693C23948D", 
+                "location": "/custodian-test-data", 
+                "date": "Fri, 26 May 2017 10:08:53 GMT"
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_s3_inventory/s3.CreateBucket_2.json b/tests/data/placebo/test_s3_inventory/s3.CreateBucket_2.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_s3_inventory/s3.CreateBucket_2.json
@@ -0,0 +1,20 @@
+{
+    "status_code": 200, 
+    "data": {
+        "Location": "/custodian-inv", 
+        "ResponseMetadata": {
+            "HTTPStatusCode": 200, 
+            "RetryAttempts": 0, 
+            "HostId": "Nj6J7mPUAFhH8hYcw2bzc6M+twnxMSV+k5pkzvk1QHqnjsqtPc/CqWzQ8h7jdnPookJT3McMk+c=", 
+            "RequestId": "C74D4D9DC017E901", 
+            "HTTPHeaders": {
+                "content-length": "0", 
+                "x-amz-id-2": "Nj6J7mPUAFhH8hYcw2bzc6M+twnxMSV+k5pkzvk1QHqnjsqtPc/CqWzQ8h7jdnPookJT3McMk+c=", 
+                "server": "AmazonS3", 
+                "x-amz-request-id": "C74D4D9DC017E901", 
+                "location": "/custodian-inv", 
+                "date": "Fri, 26 May 2017 10:08:54 GMT"
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_s3_inventory/s3.DeleteBucketInventoryConfiguration_1.json b/tests/data/placebo/test_s3_inventory/s3.DeleteBucketInventoryConfiguration_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_s3_inventory/s3.DeleteBucketInventoryConfiguration_1.json
@@ -0,0 +1,17 @@
+{
+    "status_code": 204, 
+    "data": {
+        "ResponseMetadata": {
+            "HTTPStatusCode": 204, 
+            "RetryAttempts": 0, 
+            "HostId": "bSenDRs2F0G3F/A6+OlK3OdT17i45Yw09Oc1OHLo2/dTTTtwXWY26V0phB++qxv9lEkgkdn8aSI=", 
+            "RequestId": "211E4CD6C1F1474D", 
+            "HTTPHeaders": {
+                "x-amz-id-2": "bSenDRs2F0G3F/A6+OlK3OdT17i45Yw09Oc1OHLo2/dTTTtwXWY26V0phB++qxv9lEkgkdn8aSI=", 
+                "date": "Fri, 26 May 2017 10:08:56 GMT", 
+                "x-amz-request-id": "211E4CD6C1F1474D", 
+                "server": "AmazonS3"
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_s3_inventory/s3.DeleteBucket_1.json b/tests/data/placebo/test_s3_inventory/s3.DeleteBucket_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_s3_inventory/s3.DeleteBucket_1.json
@@ -0,0 +1,17 @@
+{
+    "status_code": 204, 
+    "data": {
+        "ResponseMetadata": {
+            "HTTPStatusCode": 204, 
+            "RetryAttempts": 0, 
+            "HostId": "avdgCPwqhOXXxGlBruGCM0ilGsHR+U4QlF9nQ1xbFp65uYYXx/vWKoIPE0YED0V0znodyL4WcLQ=", 
+            "RequestId": "C37F96B59F0BBA09", 
+            "HTTPHeaders": {
+                "x-amz-id-2": "avdgCPwqhOXXxGlBruGCM0ilGsHR+U4QlF9nQ1xbFp65uYYXx/vWKoIPE0YED0V0znodyL4WcLQ=", 
+                "date": "Fri, 26 May 2017 10:08:57 GMT", 
+                "x-amz-request-id": "C37F96B59F0BBA09", 
+                "server": "AmazonS3"
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_s3_inventory/s3.DeleteBucket_2.json b/tests/data/placebo/test_s3_inventory/s3.DeleteBucket_2.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_s3_inventory/s3.DeleteBucket_2.json
@@ -0,0 +1,17 @@
+{
+    "status_code": 204, 
+    "data": {
+        "ResponseMetadata": {
+            "HTTPStatusCode": 204, 
+            "RetryAttempts": 0, 
+            "HostId": "pwG8teDh/BYMbVS08LQgkwi0NKaBjfVeDD+eaTY86uRbDTtQpxruSGc4s0rl6jt2okGfjNsIZ/k=", 
+            "RequestId": "0392278707B5AF37", 
+            "HTTPHeaders": {
+                "x-amz-id-2": "pwG8teDh/BYMbVS08LQgkwi0NKaBjfVeDD+eaTY86uRbDTtQpxruSGc4s0rl6jt2okGfjNsIZ/k=", 
+                "date": "Fri, 26 May 2017 10:08:57 GMT", 
+                "x-amz-request-id": "0392278707B5AF37", 
+                "server": "AmazonS3"
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_s3_inventory/s3.ListBucketInventoryConfigurations_1.json b/tests/data/placebo/test_s3_inventory/s3.ListBucketInventoryConfigurations_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_s3_inventory/s3.ListBucketInventoryConfigurations_1.json
@@ -0,0 +1,39 @@
+{
+    "status_code": 200, 
+    "data": {
+        "InventoryConfigurationList": [
+            {
+                "Schedule": {
+                    "Frequency": "Daily"
+                }, 
+                "IsEnabled": true, 
+                "Destination": {
+                    "S3BucketDestination": {
+                        "Prefix": "abcdef", 
+                        "Bucket": "arn:aws:s3:::custodian-inv", 
+                        "Format": "CSV"
+                    }
+                }, 
+                "OptionalFields": [
+                    "LastModifiedDate"
+                ], 
+                "IncludedObjectVersions": "All", 
+                "Id": "something"
+            }
+        ], 
+        "IsTruncated": false, 
+        "ResponseMetadata": {
+            "HTTPStatusCode": 200, 
+            "RetryAttempts": 0, 
+            "HostId": "YF73oA7U/XhS1tfHR7ZfVSSl9A/znZRS8a7IiOqv6b8sE+dJR/vklHEcL3AJJDShYkyXlkr+3Sk=", 
+            "RequestId": "47CF100C0C839218", 
+            "HTTPHeaders": {
+                "x-amz-id-2": "YF73oA7U/XhS1tfHR7ZfVSSl9A/znZRS8a7IiOqv6b8sE+dJR/vklHEcL3AJJDShYkyXlkr+3Sk=", 
+                "date": "Fri, 26 May 2017 10:08:55 GMT", 
+                "content-length": "604", 
+                "x-amz-request-id": "47CF100C0C839218", 
+                "server": "AmazonS3"
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_s3_inventory/s3.ListBucketInventoryConfigurations_2.json b/tests/data/placebo/test_s3_inventory/s3.ListBucketInventoryConfigurations_2.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_s3_inventory/s3.ListBucketInventoryConfigurations_2.json
@@ -0,0 +1,40 @@
+{
+    "status_code": 200, 
+    "data": {
+        "InventoryConfigurationList": [
+            {
+                "Schedule": {
+                    "Frequency": "Daily"
+                }, 
+                "IsEnabled": true, 
+                "Destination": {
+                    "S3BucketDestination": {
+                        "Prefix": "Inventories/644160558196", 
+                        "Bucket": "arn:aws:s3:::custodian-inv", 
+                        "Format": "CSV"
+                    }
+                }, 
+                "OptionalFields": [
+                    "LastModifiedDate", 
+                    "Size"
+                ], 
+                "IncludedObjectVersions": "Current", 
+                "Id": "something"
+            }
+        ], 
+        "IsTruncated": false, 
+        "ResponseMetadata": {
+            "HTTPStatusCode": 200, 
+            "RetryAttempts": 0, 
+            "HostId": "WOqIF4wNBlUz6PS9ltHzqxSlM+0cjRkpldOCR8OmNqjpDOjkvJ5NCfm6MM7kPsuT3Vp/hUNyGwM=", 
+            "RequestId": "3F7FB3CF3EC3A957", 
+            "HTTPHeaders": {
+                "x-amz-id-2": "WOqIF4wNBlUz6PS9ltHzqxSlM+0cjRkpldOCR8OmNqjpDOjkvJ5NCfm6MM7kPsuT3Vp/hUNyGwM=", 
+                "date": "Fri, 26 May 2017 10:08:56 GMT", 
+                "content-length": "645", 
+                "x-amz-request-id": "3F7FB3CF3EC3A957", 
+                "server": "AmazonS3"
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_s3_inventory/s3.ListBucketInventoryConfigurations_3.json b/tests/data/placebo/test_s3_inventory/s3.ListBucketInventoryConfigurations_3.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_s3_inventory/s3.ListBucketInventoryConfigurations_3.json
@@ -0,0 +1,19 @@
+{
+    "status_code": 200, 
+    "data": {
+        "IsTruncated": false, 
+        "ResponseMetadata": {
+            "HTTPStatusCode": 200, 
+            "RetryAttempts": 0, 
+            "HostId": "cOfIoXJv9PNaqWheOJT2zOdsilocPKlwdLx4aeYhkgHdX4WDdjSmXcLpnoJGV3M9mnjEP7Q4jwQ=", 
+            "RequestId": "8C1F2FB3CDA0AC8B", 
+            "HTTPHeaders": {
+                "x-amz-id-2": "cOfIoXJv9PNaqWheOJT2zOdsilocPKlwdLx4aeYhkgHdX4WDdjSmXcLpnoJGV3M9mnjEP7Q4jwQ=", 
+                "date": "Fri, 26 May 2017 10:08:57 GMT", 
+                "content-length": "189", 
+                "x-amz-request-id": "8C1F2FB3CDA0AC8B", 
+                "server": "AmazonS3"
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_s3_inventory/s3.ListBuckets_1.json b/tests/data/placebo/test_s3_inventory/s3.ListBuckets_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_s3_inventory/s3.ListBuckets_1.json
@@ -0,0 +1,103 @@
+{
+    "status_code": 200, 
+    "data": {
+        "Owner": {
+            "DisplayName": "mandeep.bal", 
+            "ID": "e7c8bb65a5fc49cf906715eae09de9e4bb7861a96361ba79b833aa45f6833b15"
+        }, 
+        "Buckets": [
+            {
+                "CreationDate": {
+                    "hour": 10, 
+                    "__class__": "datetime", 
+                    "month": 7, 
+                    "second": 14, 
+                    "microsecond": 0, 
+                    "year": 2016, 
+                    "day": 24, 
+                    "minute": 38
+                }, 
+                "Name": "config-bucket-644160558196"
+            }, 
+            {
+                "CreationDate": {
+                    "hour": 10, 
+                    "__class__": "datetime", 
+                    "month": 5, 
+                    "second": 54, 
+                    "microsecond": 0, 
+                    "year": 2017, 
+                    "day": 26, 
+                    "minute": 8
+                }, 
+                "Name": "custodian-inv"
+            }, 
+            {
+                "CreationDate": {
+                    "hour": 19, 
+                    "__class__": "datetime", 
+                    "month": 4, 
+                    "second": 38, 
+                    "microsecond": 0, 
+                    "year": 2017, 
+                    "day": 27, 
+                    "minute": 14
+                }, 
+                "Name": "custodian-log-archive"
+            }, 
+            {
+                "CreationDate": {
+                    "hour": 21, 
+                    "__class__": "datetime", 
+                    "month": 11, 
+                    "second": 20, 
+                    "microsecond": 0, 
+                    "year": 2016, 
+                    "day": 6, 
+                    "minute": 18
+                }, 
+                "Name": "custodian-skunk-billing"
+            }, 
+            {
+                "CreationDate": {
+                    "hour": 8, 
+                    "__class__": "datetime", 
+                    "month": 7, 
+                    "second": 21, 
+                    "microsecond": 0, 
+                    "year": 2016, 
+                    "day": 24, 
+                    "minute": 50
+                }, 
+                "Name": "custodian-skunk-trails"
+            }, 
+            {
+                "CreationDate": {
+                    "hour": 10, 
+                    "__class__": "datetime", 
+                    "month": 5, 
+                    "second": 53, 
+                    "microsecond": 0, 
+                    "year": 2017, 
+                    "day": 26, 
+                    "minute": 8
+                }, 
+                "Name": "custodian-test-data"
+            }
+        ], 
+        "ResponseMetadata": {
+            "HTTPStatusCode": 200, 
+            "RetryAttempts": 0, 
+            "HostId": "xVB7b0mWfEkjOETl+fLKnN4Ea4H7ley1maCkmU95CoiDBx3ga/Nh0AJkOPH9FXcMTDRfGSEeAoY=", 
+            "RequestId": "58F380792274FB2D", 
+            "HTTPHeaders": {
+                "x-amz-id-2": "xVB7b0mWfEkjOETl+fLKnN4Ea4H7ley1maCkmU95CoiDBx3ga/Nh0AJkOPH9FXcMTDRfGSEeAoY=", 
+                "server": "AmazonS3", 
+                "transfer-encoding": "chunked", 
+                "x-amz-request-id": "58F380792274FB2D", 
+                "date": "Fri, 26 May 2017 10:08:54 GMT", 
+                "content-type": "application/xml"
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_s3_inventory/s3.ListBuckets_2.json b/tests/data/placebo/test_s3_inventory/s3.ListBuckets_2.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_s3_inventory/s3.ListBuckets_2.json
@@ -0,0 +1,103 @@
+{
+    "status_code": 200, 
+    "data": {
+        "Owner": {
+            "DisplayName": "mandeep.bal", 
+            "ID": "e7c8bb65a5fc49cf906715eae09de9e4bb7861a96361ba79b833aa45f6833b15"
+        }, 
+        "Buckets": [
+            {
+                "CreationDate": {
+                    "hour": 10, 
+                    "__class__": "datetime", 
+                    "month": 7, 
+                    "second": 14, 
+                    "microsecond": 0, 
+                    "year": 2016, 
+                    "day": 24, 
+                    "minute": 38
+                }, 
+                "Name": "config-bucket-644160558196"
+            }, 
+            {
+                "CreationDate": {
+                    "hour": 10, 
+                    "__class__": "datetime", 
+                    "month": 5, 
+                    "second": 54, 
+                    "microsecond": 0, 
+                    "year": 2017, 
+                    "day": 26, 
+                    "minute": 8
+                }, 
+                "Name": "custodian-inv"
+            }, 
+            {
+                "CreationDate": {
+                    "hour": 19, 
+                    "__class__": "datetime", 
+                    "month": 4, 
+                    "second": 38, 
+                    "microsecond": 0, 
+                    "year": 2017, 
+                    "day": 27, 
+                    "minute": 14
+                }, 
+                "Name": "custodian-log-archive"
+            }, 
+            {
+                "CreationDate": {
+                    "hour": 21, 
+                    "__class__": "datetime", 
+                    "month": 11, 
+                    "second": 20, 
+                    "microsecond": 0, 
+                    "year": 2016, 
+                    "day": 6, 
+                    "minute": 18
+                }, 
+                "Name": "custodian-skunk-billing"
+            }, 
+            {
+                "CreationDate": {
+                    "hour": 8, 
+                    "__class__": "datetime", 
+                    "month": 7, 
+                    "second": 21, 
+                    "microsecond": 0, 
+                    "year": 2016, 
+                    "day": 24, 
+                    "minute": 50
+                }, 
+                "Name": "custodian-skunk-trails"
+            }, 
+            {
+                "CreationDate": {
+                    "hour": 10, 
+                    "__class__": "datetime", 
+                    "month": 5, 
+                    "second": 53, 
+                    "microsecond": 0, 
+                    "year": 2017, 
+                    "day": 26, 
+                    "minute": 8
+                }, 
+                "Name": "custodian-test-data"
+            }
+        ], 
+        "ResponseMetadata": {
+            "HTTPStatusCode": 200, 
+            "RetryAttempts": 0, 
+            "HostId": "s291p9zUAkn4Xsv9mEk9RCGsC3lcz4Ajj1u4uSqPLcmMmvZs7Ue4Ywsieq/3DbHSEgezjLsnpaQ=", 
+            "RequestId": "4CF2D3E795AB4F42", 
+            "HTTPHeaders": {
+                "x-amz-id-2": "s291p9zUAkn4Xsv9mEk9RCGsC3lcz4Ajj1u4uSqPLcmMmvZs7Ue4Ywsieq/3DbHSEgezjLsnpaQ=", 
+                "server": "AmazonS3", 
+                "transfer-encoding": "chunked", 
+                "x-amz-request-id": "4CF2D3E795AB4F42", 
+                "date": "Fri, 26 May 2017 10:08:56 GMT", 
+                "content-type": "application/xml"
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_s3_inventory/s3.PutBucketInventoryConfiguration_1.json b/tests/data/placebo/test_s3_inventory/s3.PutBucketInventoryConfiguration_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_s3_inventory/s3.PutBucketInventoryConfiguration_1.json
@@ -0,0 +1,17 @@
+{
+    "status_code": 204, 
+    "data": {
+        "ResponseMetadata": {
+            "HTTPStatusCode": 204, 
+            "RetryAttempts": 0, 
+            "HostId": "lW07/EBUpPpUCTlX5/5ZpSCVvGXGuywLPHQmkQxpgIpxRF1lze9gdK2LYWP8uGjL0ChVAmGDpGU=", 
+            "RequestId": "02F5B0C69CB2BAB0", 
+            "HTTPHeaders": {
+                "x-amz-id-2": "lW07/EBUpPpUCTlX5/5ZpSCVvGXGuywLPHQmkQxpgIpxRF1lze9gdK2LYWP8uGjL0ChVAmGDpGU=", 
+                "date": "Fri, 26 May 2017 10:08:54 GMT", 
+                "x-amz-request-id": "02F5B0C69CB2BAB0", 
+                "server": "AmazonS3"
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_s3_inventory/s3.PutBucketInventoryConfiguration_2.json b/tests/data/placebo/test_s3_inventory/s3.PutBucketInventoryConfiguration_2.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_s3_inventory/s3.PutBucketInventoryConfiguration_2.json
@@ -0,0 +1,17 @@
+{
+    "status_code": 204, 
+    "data": {
+        "ResponseMetadata": {
+            "HTTPStatusCode": 204, 
+            "RetryAttempts": 0, 
+            "HostId": "vXDDPoNkeWWM/q2RvWLAGHg/KhcD5hYWe74ZnJgN66LD52yET9zOIL346lbgvYoml4YyW1tTpfc=", 
+            "RequestId": "183A0A60B158FE77", 
+            "HTTPHeaders": {
+                "x-amz-id-2": "vXDDPoNkeWWM/q2RvWLAGHg/KhcD5hYWe74ZnJgN66LD52yET9zOIL346lbgvYoml4YyW1tTpfc=", 
+                "date": "Fri, 26 May 2017 10:08:55 GMT", 
+                "x-amz-request-id": "183A0A60B158FE77", 
+                "server": "AmazonS3"
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/test_s3.py b/tests/test_s3.py
--- a/tests/test_s3.py
+++ b/tests/test_s3.py
@@ -114,6 +114,80 @@ def test_metrics(self):
             'AWS/S3.NumberOfObjects.Average' in resources[0]['c7n.metrics'])
 
 
+class BucketInventory(BaseTest):
+
+    def test_inventory(self):
+        bname = 'custodian-test-data'
+        inv_bname = 'custodian-inv'
+        inv_name = 'something'
+
+        self.patch(s3.S3, 'executor_factory', MainThreadExecutor)
+        self.patch(s3, 'S3_AUGMENT_TABLE', [])
+
+        session_factory = self.replay_flight_data('test_s3_inventory')
+
+        client = session_factory().client('s3')
+        client.create_bucket(Bucket=bname)
+        client.create_bucket(Bucket=inv_bname)
+        
+        self.addCleanup(client.delete_bucket, Bucket=bname)
+        self.addCleanup(client.delete_bucket, Bucket=inv_bname)        
+
+        inv = {
+            'Destination': {
+                'S3BucketDestination': {
+                    'Bucket': "arn:aws:s3:::%s" % inv_bname,
+                    'Format': 'CSV',
+                    'Prefix': 'abcdef'},
+            },
+            'IsEnabled': True,
+            'Id': inv_name,
+            'IncludedObjectVersions': 'All',
+            'OptionalFields': ['LastModifiedDate'],
+            'Schedule': {
+                'Frequency': 'Daily'}
+            }
+            
+        client.put_bucket_inventory_configuration(
+            Bucket=bname,
+            Id=inv_name,
+            InventoryConfiguration=inv)
+        
+        p = self.load_policy({
+            'name': 's3-inv',
+            'resource': 's3',
+            'filters': [
+                {'Name': 'custodian-test-data'}],
+            'actions': [
+                {'type': 'set-inventory',
+                 'destination': inv_bname,
+                 'name': inv_name}]
+            }, session_factory=session_factory)
+        self.assertEqual(len(p.run()), 1)
+        invs = client.list_bucket_inventory_configurations(
+            Bucket=bname).get('InventoryConfigurationList')
+        self.assertTrue(invs)
+        self.assertEqual(sorted(invs[0]['OptionalFields']), ['LastModifiedDate', 'Size'])
+            
+
+        p = self.load_policy({
+            'name': 's3-inv',
+            'resource': 's3',
+            'filters': [
+                {'Name': 'custodian-test-data'}],
+            'actions': [
+                {'type': 'set-inventory',
+                 'destination': inv_bname,
+                 'state': 'absent',
+                 'name': inv_name}]
+            }, session_factory=session_factory)
+
+        self.assertEqual(len(p.run()), 1)
+        self.assertFalse(
+            client.list_bucket_inventory_configurations(Bucket=bname).get('InventoryConfigurationList'))
+        
+        
+        
 class BucketDelete(BaseTest):
 
     def test_delete_replicated_bucket(self):