Repository: cloud-custodian/cloud-custodian

Issue:
Ability to copy tags from instance to snapshot
It would be great to be able to specify certain tags which would be copied from an EC2 instance to a newly created snapshot when the `ec2` resource `snapshot` action is applied.



Discussion:

Resolving patch:
diff --git a/c7n/resources/ec2.py b/c7n/resources/ec2.py
--- a/c7n/resources/ec2.py
+++ b/c7n/resources/ec2.py
@@ -361,7 +361,9 @@ def process_instance(i):
 @actions.register('snapshot')
 class Snapshot(BaseAction):
 
-    schema = type_schema('snapshot')
+    schema = type_schema(
+        'snapshot',
+        **{'copy-tags': {'type': 'array', 'items': {'type': 'string'}}})
 
     def process(self, resources):
         for resource in resources:
@@ -386,15 +388,34 @@ def process_volume_set(self, resource):
                 DryRun=self.manager.config.dryrun,
                 VolumeId=block_device['Ebs']['VolumeId'],
                 Description=description)
+
+            tags = [
+                {'Key': 'Name', 'Value': block_device['Ebs']['VolumeId']},
+                {'Key': 'InstanceId', 'Value': resource['InstanceId']},
+                {'Key': 'DeviceName', 'Value': block_device['DeviceName']}
+            ]
+
+            copy_keys = self.data.get('copy-tags', [])
+            copy_tags = []
+            if copy_keys:
+                for t in resource.get('Tags', []):
+                    if t['Key'] in copy_keys:
+                        copy_tags.append(t)
+
+            if len(copy_tags) + len(tags) > 10:
+                log.warning(
+                    "action:%s volume:%s too many tags to copy" % (
+                        self.__class__.__name__.lower(),
+                        block_device['Ebs']['VolumeId']))
+                copy_tags = []
+
+            tags.extend(copy_tags)
+
             c.create_tags(
                 DryRun=self.manager.config.dryrun,
                 Resources=[
                     response['SnapshotId']],
-                Tags=[
-                    {'Key': 'Name', 'Value': block_device['Ebs']['VolumeId']},
-                    {'Key': 'InstanceId', 'Value': resource['InstanceId']},
-                    {'Key': 'DeviceName', 'Value': block_device['DeviceName']}
-                ])
+                Tags=tags)
 
 
 # Valid EC2 Query Filters


Test patch:
diff --git a/tests/test_ec2.py b/tests/test_ec2.py
--- a/tests/test_ec2.py
+++ b/tests/test_ec2.py
@@ -36,7 +36,7 @@ def test_ec2_attached_ebs_filter(self):
         resources = policy.run()
         self.assertEqual(len(resources), 1)
 
-    # DISABLED / Re-record flight data on public account        
+    # DISABLED / Re-record flight data on public account
     def test_ec2_attached_volume_skip_block(self):
         session_factory = self.replay_flight_data(
             'test_ec2_attached_ebs_filter')
@@ -141,7 +141,7 @@ def test_ec2_stop(self):
 
 class TestSnapshot(BaseTest):
 
-    def test_ec2_snapshot(self):
+    def test_ec2_snapshot_no_copy_tags(self):
         session_factory = self.replay_flight_data(
             'test_ec2_snapshot')
         policy = self.load_policy({
@@ -155,6 +155,20 @@ def test_ec2_snapshot(self):
         resources = policy.run()
         self.assertEqual(len(resources), 1)
 
+    def test_ec2_snapshot_copy_tags(self):
+        session_factory = self.replay_flight_data(
+            'test_ec2_snapshot')
+        policy = self.load_policy({
+            'name': 'ec2-test-snapshot',
+            'resource': 'ec2',
+            'filters': [
+                {'tag:Name': 'CompileLambda'}],
+            'actions': [
+                {'type': 'snapshot', 'copy-tags': ['ASV' 'Testing123']}]},
+            session_factory=session_factory)
+        resources = policy.run()
+        self.assertEqual(len(resources), 1)
+
 
 class TestEC2QueryFilter(unittest.TestCase):
 
@@ -165,7 +179,7 @@ def test_parse(self):
         self.assertEqual(
             x[0].query(),
             {'Name': 'instance-state-name', 'Values': ['running']})
-        
+
         self.assertTrue(
             isinstance(
                 QueryFilter.parse(
@@ -176,7 +190,7 @@ def test_parse(self):
             ValueError,
             QueryFilter.parse,
             [{'tag:ASV': None}])
-        
+
 
 class TestActions(unittest.TestCase):
 
@@ -192,7 +206,4 @@ def test_action_construction(self):
 
         self.assertIsInstance(
             actions.factory('terminate', None),
-            ec2.Terminate)        
-
-        
-
+            ec2.Terminate)