Repository: cloud-custodian/cloud-custodian

Issue:
How to make a EBS snapshot before delete?
I can't figure out how to make a EBS snapshot before delete. I'm trying to filter unattached EBS and take a snapshot and delete, but EBS does not have a action of snapshot like EC2 does.


Discussion:
yeah. we're missing that action, we should add it.

Resolving patch:
diff --git a/c7n/resources/ebs.py b/c7n/resources/ebs.py
--- a/c7n/resources/ebs.py
+++ b/c7n/resources/ebs.py
@@ -28,7 +28,14 @@
 from c7n.resources.kms import ResourceKmsKeyAlias
 from c7n.query import QueryResourceManager
 from c7n.utils import (
-    local_session, set_annotation, chunks, type_schema, worker, camelResource)
+    camelResource,
+    chunks,
+    get_retry,
+    local_session,
+    set_annotation,
+    type_schema,
+    worker,
+)
 from c7n.resources.ami import AMI
 
 log = logging.getLogger('custodian.ebs')
@@ -883,6 +890,34 @@ def _wait_on_resource(
                 self.log.debug("Instance: %s stopped" % instance_id)
 
 
+@actions.register('snapshot')
+class CreateSnapshot(BaseAction):
+    """Snapshot an EBS volume
+
+    :example:
+
+        .. code-block: yaml
+
+            policies:
+              - name: snapshot-volumes
+                resource: ebs
+                filters:
+                  - Attachments: []
+                  - State: available
+                actions:
+                  - snapshot
+    """
+    permissions = ('ec2:CreateSnapshot',)
+    schema = type_schema('snapshot')
+
+    def process(self, volumes):
+        client = local_session(self.manager.session_factory).client('ec2')
+        retry = get_retry(['Throttled'], max_attempts=5)
+        for vol in volumes:
+            vol_id = vol['VolumeId']
+            retry(client.create_snapshot, VolumeId=vol_id)
+
+
 @actions.register('delete')
 class Delete(BaseAction):
     """Delete an ebs volume.


Test patch:
diff --git a/tests/data/placebo/test_ebs_snapshot/ec2.CreateSnapshot_1.json b/tests/data/placebo/test_ebs_snapshot/ec2.CreateSnapshot_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_ebs_snapshot/ec2.CreateSnapshot_1.json
@@ -0,0 +1,35 @@
+{
+    "status_code": 200, 
+    "data": {
+        "Description": "", 
+        "ResponseMetadata": {
+            "RetryAttempts": 0, 
+            "HTTPStatusCode": 200, 
+            "RequestId": "526a3e1a-2002-4ec6-8860-cc09008173ac", 
+            "HTTPHeaders": {
+                "transfer-encoding": "chunked", 
+                "vary": "Accept-Encoding", 
+                "server": "AmazonEC2", 
+                "content-type": "text/xml;charset=UTF-8", 
+                "date": "Wed, 03 May 2017 14:43:16 GMT"
+            }
+        }, 
+        "Encrypted": false, 
+        "VolumeId": "vol-01adbb6a4f175941d", 
+        "State": "pending", 
+        "VolumeSize": 8, 
+        "Progress": "", 
+        "StartTime": {
+            "hour": 14, 
+            "__class__": "datetime", 
+            "month": 5, 
+            "second": 17, 
+            "microsecond": 0, 
+            "year": 2017, 
+            "day": 3, 
+            "minute": 43
+        }, 
+        "SnapshotId": "snap-0c8b611065fed033e", 
+        "OwnerId": "644160558196"
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_ebs_snapshot/ec2.DescribeSnapshots_1.json b/tests/data/placebo/test_ebs_snapshot/ec2.DescribeSnapshots_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_ebs_snapshot/ec2.DescribeSnapshots_1.json
@@ -0,0 +1,39 @@
+{
+    "status_code": 200, 
+    "data": {
+        "ResponseMetadata": {
+            "RetryAttempts": 0, 
+            "HTTPStatusCode": 200, 
+            "RequestId": "48383548-8c6e-47de-815e-33f0ebe7f580", 
+            "HTTPHeaders": {
+                "transfer-encoding": "chunked", 
+                "vary": "Accept-Encoding", 
+                "server": "AmazonEC2", 
+                "content-type": "text/xml;charset=UTF-8", 
+                "date": "Wed, 03 May 2017 14:43:17 GMT"
+            }
+        }, 
+        "Snapshots": [
+            {
+                "Description": "", 
+                "Encrypted": false, 
+                "VolumeId": "vol-01adbb6a4f175941d", 
+                "State": "pending", 
+                "VolumeSize": 8, 
+                "Progress": "99%", 
+                "StartTime": {
+                    "hour": 14, 
+                    "__class__": "datetime", 
+                    "month": 5, 
+                    "second": 17, 
+                    "microsecond": 0, 
+                    "year": 2017, 
+                    "day": 3, 
+                    "minute": 43
+                }, 
+                "SnapshotId": "snap-0c8b611065fed033e", 
+                "OwnerId": "644160558196"
+            }
+        ]
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_ebs_snapshot/ec2.DescribeVolumes_1.json b/tests/data/placebo/test_ebs_snapshot/ec2.DescribeVolumes_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_ebs_snapshot/ec2.DescribeVolumes_1.json
@@ -0,0 +1,142 @@
+{
+    "status_code": 200, 
+    "data": {
+        "ResponseMetadata": {
+            "RetryAttempts": 0, 
+            "HTTPStatusCode": 200, 
+            "RequestId": "53e62ac7-14d4-4657-9385-e1ffeb34edea", 
+            "HTTPHeaders": {
+                "transfer-encoding": "chunked", 
+                "vary": "Accept-Encoding", 
+                "server": "AmazonEC2", 
+                "content-type": "text/xml;charset=UTF-8", 
+                "date": "Wed, 03 May 2017 14:43:16 GMT"
+            }
+        }, 
+        "Volumes": [
+            {
+                "AvailabilityZone": "us-east-1b", 
+                "Attachments": [
+                    {
+                        "AttachTime": {
+                            "hour": 14, 
+                            "__class__": "datetime", 
+                            "month": 3, 
+                            "second": 28, 
+                            "microsecond": 0, 
+                            "year": 2017, 
+                            "day": 28, 
+                            "minute": 55
+                        }, 
+                        "InstanceId": "i-0a0b51bcf11a8cdfb", 
+                        "VolumeId": "vol-01adbb6a4f175941d", 
+                        "State": "attached", 
+                        "DeleteOnTermination": true, 
+                        "Device": "/dev/xvda"
+                    }
+                ], 
+                "Encrypted": false, 
+                "VolumeType": "gp2", 
+                "VolumeId": "vol-01adbb6a4f175941d", 
+                "State": "in-use", 
+                "Iops": 100, 
+                "SnapshotId": "snap-037f1f9e6c8ea4d65", 
+                "CreateTime": {
+                    "hour": 14, 
+                    "__class__": "datetime", 
+                    "month": 3, 
+                    "second": 28, 
+                    "microsecond": 486000, 
+                    "year": 2017, 
+                    "day": 28, 
+                    "minute": 55
+                }, 
+                "Size": 8
+            }, 
+            {
+                "AvailabilityZone": "us-east-1b", 
+                "Attachments": [
+                    {
+                        "AttachTime": {
+                            "hour": 20, 
+                            "__class__": "datetime", 
+                            "month": 4, 
+                            "second": 21, 
+                            "microsecond": 0, 
+                            "year": 2017, 
+                            "day": 10, 
+                            "minute": 41
+                        }, 
+                        "InstanceId": "i-0be2fdca058278ea3", 
+                        "VolumeId": "vol-0252f61378ede9d01", 
+                        "State": "attached", 
+                        "DeleteOnTermination": true, 
+                        "Device": "/dev/sda1"
+                    }
+                ], 
+                "Tags": [
+                    {
+                        "Value": "FancyTestInstancePrivate", 
+                        "Key": "Name"
+                    }
+                ], 
+                "Encrypted": false, 
+                "VolumeType": "gp2", 
+                "VolumeId": "vol-0252f61378ede9d01", 
+                "State": "in-use", 
+                "Iops": 100, 
+                "SnapshotId": "snap-00762b50998e9113b", 
+                "CreateTime": {
+                    "hour": 20, 
+                    "__class__": "datetime", 
+                    "month": 4, 
+                    "second": 21, 
+                    "microsecond": 977000, 
+                    "year": 2017, 
+                    "day": 10, 
+                    "minute": 41
+                }, 
+                "Size": 8
+            }, 
+            {
+                "AvailabilityZone": "us-east-1d", 
+                "Attachments": [
+                    {
+                        "AttachTime": {
+                            "hour": 1, 
+                            "__class__": "datetime", 
+                            "month": 4, 
+                            "second": 40, 
+                            "microsecond": 0, 
+                            "year": 2017, 
+                            "day": 3, 
+                            "minute": 34
+                        }, 
+                        "InstanceId": "i-0aace5764d21d3105", 
+                        "VolumeId": "vol-0b5bc31549f55bdd2", 
+                        "State": "attached", 
+                        "DeleteOnTermination": true, 
+                        "Device": "/dev/xvda"
+                    }
+                ], 
+                "Encrypted": false, 
+                "VolumeType": "gp2", 
+                "VolumeId": "vol-0b5bc31549f55bdd2", 
+                "State": "in-use", 
+                "Iops": 100, 
+                "SnapshotId": "snap-037f1f9e6c8ea4d65", 
+                "CreateTime": {
+                    "hour": 1, 
+                    "__class__": "datetime", 
+                    "month": 4, 
+                    "second": 40, 
+                    "microsecond": 677000, 
+                    "year": 2017, 
+                    "day": 3, 
+                    "minute": 34
+                }, 
+                "Size": 8
+            }
+        ]
+    }
+}
\ No newline at end of file
diff --git a/tests/test_ebs.py b/tests/test_ebs.py
--- a/tests/test_ebs.py
+++ b/tests/test_ebs.py
@@ -168,6 +168,31 @@ def test_copy_instance_tags(self):
         self.assertEqual(tags['Name'], 'CompileLambda')
 
 
+class VolumeSnapshotTest(BaseTest):
+
+    def test_volume_snapshot(self):
+        factory = self.replay_flight_data('test_ebs_snapshot')
+        policy = self.load_policy(
+            {
+                'name': 'test-ebs-snapshot',
+                'resource': 'ebs',
+                'filters': [{'VolumeId': 'vol-01adbb6a4f175941d'}],
+                'actions': ['snapshot'],
+            },
+            session_factory=factory,
+        )
+        resources = policy.run()
+        snapshot_data = factory().client('ec2').describe_snapshots(
+            Filters=[
+                {
+                    'Name': 'volume-id',
+                    'Values': ['vol-01adbb6a4f175941d'],
+                },
+            ]
+        )
+        self.assertEqual(len(snapshot_data['Snapshots']), 1)
+        
+
 class VolumeDeleteTest(BaseTest):
 
     def test_volume_delete_force(self):