Repository: cloud-custodian/cloud-custodian

Issue:
aws - ami unused with missing launch template
Policy

```
policies:
- name: remove-outdated-amis
  resource: ami
  description: |
    Delete unused AMIs that are older than X days
  mode:
    type: periodic
    schedule: "rate(1 day)"
    role: ${role_arn}
    execution-options:
      dryrun: true
    tags:
      owner: ops
  filters:
    - key: Name
      type: value
      op: regex
      # additional backslash to escape due to yaml requirements
      value: "^golden-build-[\\d]+$"
    - key: CreationDate
      type: value
      value_type: age
      op: greater-than
      value: 90
    - type: unused
      value: true
    - "tag:custodian_exclude_delete": absent
  actions:
    - type: deregister
      delete-snapshots: true
```

Command

```
custodian run --cache-period=0 --dryrun --region us-east-1 policies/remove-old-ami-and-ebs.yml
```

Exception

```
2019-08-11 07:22:23,823: custodian.commands:ERROR Error while executing policy remove-outdated-amis, continuing
Traceback (most recent call last):
  File "/Users/user/.local/share/virtualenvs/cloudcustodian-ymoVWLAP/lib/python2.7/site-packages/c7n/commands.py", line 269, in run
    policy()
  File "/Users/user/.local/share/virtualenvs/cloudcustodian-ymoVWLAP/lib/python2.7/site-packages/c7n/policy.py", line 941, in __call__
    resources = PullMode(self).run()
  File "/Users/user/.local/share/virtualenvs/cloudcustodian-ymoVWLAP/lib/python2.7/site-packages/c7n/policy.py", line 229, in run
    resources = self.policy.resource_manager.resources()
  File "/Users/user/.local/share/virtualenvs/cloudcustodian-ymoVWLAP/lib/python2.7/site-packages/c7n/resources/ami.py", line 53, in resources
    return super(AMI, self).resources(query=query)
  File "/Users/user/.local/share/virtualenvs/cloudcustodian-ymoVWLAP/lib/python2.7/site-packages/c7n/query.py", line 460, in resources
    resources = self.filter_resources(resources)
  File "/Users/user/.local/share/virtualenvs/cloudcustodian-ymoVWLAP/lib/python2.7/site-packages/c7n/manager.py", line 108, in filter_resources
    resources = f.process(resources, event)
  File "/Users/user/.local/share/virtualenvs/cloudcustodian-ymoVWLAP/lib/python2.7/site-packages/c7n/resources/ami.py", line 305, in process
    images = self._pull_ec2_images().union(self._pull_asg_images())
  File "/Users/user/.local/share/virtualenvs/cloudcustodian-ymoVWLAP/lib/python2.7/site-packages/c7n/resources/ami.py", line 296, in _pull_asg_images
    list(tmpl_mgr.get_asg_templates(asgs).keys())):
  File "/Users/user/.local/share/virtualenvs/cloudcustodian-ymoVWLAP/lib/python2.7/site-packages/c7n/resources/ec2.py", line 1870, in get_resources
    LaunchTemplateId=tid, Versions=tversions).get(
  File "/Users/user/.local/share/virtualenvs/cloudcustodian-ymoVWLAP/lib/python2.7/site-packages/botocore/client.py", line 357, in _api_call
    return self._make_api_call(operation_name, kwargs)
  File "/Users/user/.local/share/virtualenvs/cloudcustodian-ymoVWLAP/lib/python2.7/site-packages/botocore/client.py", line 661, in _make_api_call
    raise error_class(parsed_response, operation_name)
ClientError: An error occurred (InvalidLaunchTemplateId.NotFound) when calling the DescribeLaunchTemplateVersions operation: The specified launch template, with template ID lt-0634c8a44afa8b953, does not exist.
```


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
@@ -1866,9 +1866,16 @@ def get_resources(self, rids, cache=True):
         results = []
         # We may end up fetching duplicates on $Latest and $Version
         for tid, tversions in t_versions.items():
-            ltv = client.describe_launch_template_versions(
-                LaunchTemplateId=tid, Versions=tversions).get(
-                    'LaunchTemplateVersions')
+            try:
+                ltv = client.describe_launch_template_versions(
+                    LaunchTemplateId=tid, Versions=tversions).get(
+                        'LaunchTemplateVersions')
+            except ClientError as e:
+                if e.response['Error']['Code'] == "InvalidLaunchTemplateId.NotFound":
+                    continue
+                if e.response['Error']['Code'] == "InvalidLaunchTemplateId.VersionNotFound":
+                    continue
+                raise
             if not tversions:
                 tversions = [str(t['VersionNumber']) for t in ltv]
             for tversion, t in zip(tversions, ltv):


Test patch:
diff --git a/tests/data/placebo/test_launch_template_id_not_found/ec2.DescribeLaunchTemplateVersions_1.json b/tests/data/placebo/test_launch_template_id_not_found/ec2.DescribeLaunchTemplateVersions_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_launch_template_id_not_found/ec2.DescribeLaunchTemplateVersions_1.json
@@ -0,0 +1,20 @@
+{
+    "status_code": 400,
+    "data": {
+        "Error": {
+            "Code": "InvalidLaunchTemplateId.NotFound",
+            "Message": "The specified launch template, with template ID lt-0a49586208137d8de, does not exist."
+        },
+        "ResponseMetadata": {
+            "RequestId": "02482169-3000-4f85-a230-3ea4fa786727",
+            "HTTPStatusCode": 400,
+            "HTTPHeaders": {
+                "transfer-encoding": "chunked",
+                "date": "Fri, 23 Aug 2019 00:23:20 GMT",
+                "connection": "close",
+                "server": "AmazonEC2"
+            },
+            "RetryAttempts": 0
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_launch_template_id_not_found/ec2.DescribeLaunchTemplateVersions_2.json b/tests/data/placebo/test_launch_template_id_not_found/ec2.DescribeLaunchTemplateVersions_2.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_launch_template_id_not_found/ec2.DescribeLaunchTemplateVersions_2.json
@@ -0,0 +1,43 @@
+{
+    "status_code": 200,
+    "data": {
+        "LaunchTemplateVersions": [
+            {
+                "LaunchTemplateId": "lt-0877401c93c294001",
+                "LaunchTemplateName": "test",
+                "VersionNumber": 3,
+                "CreateTime": {
+                    "__class__": "datetime",
+                    "year": 2019,
+                    "month": 2,
+                    "day": 27,
+                    "hour": 19,
+                    "minute": 24,
+                    "second": 34,
+                    "microsecond": 0
+                },
+                "CreatedBy": "arn:aws:iam::644160558196:user/Samson.Shi@capitalone.com",
+                "DefaultVersion": true,
+                "LaunchTemplateData": {
+                    "ImageId": "ami-04bfee437f38a691e",
+                    "InstanceType": "t1.micro",
+                    "SecurityGroupIds": [
+                        "sg-00cd07381bc482d51"
+                    ]
+                }
+            }
+        ],
+        "ResponseMetadata": {
+            "RequestId": "6d4c6484-8189-4db9-9776-942ce3cffadf",
+            "HTTPStatusCode": 200,
+            "HTTPHeaders": {
+                "content-type": "text/xml;charset=UTF-8",
+                "transfer-encoding": "chunked",
+                "vary": "accept-encoding",
+                "date": "Fri, 23 Aug 2019 00:23:20 GMT",
+                "server": "AmazonEC2"
+            },
+            "RetryAttempts": 0
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/test_ec2.py b/tests/test_ec2.py
--- a/tests/test_ec2.py
+++ b/tests/test_ec2.py
@@ -1650,6 +1650,17 @@ def test_launch_template_versions(self):
         self.assertEqual(len(resources), 8)
         self.assertTrue(all(['LaunchTemplateData' in r for r in resources]))
 
+    def test_launch_template_id_not_found(self):
+        factory = self.replay_flight_data("test_launch_template_id_not_found")
+        good_lt_id = 'lt-0877401c93c294001'
+        p = self.load_policy(
+            {'name': 'lt-missing', 'resource': 'launch-template-version'},
+            session_factory=factory)
+        resources = p.resource_manager.get_resources(
+            [('lt-0a49586208137d8de', '1'), ('lt-0877401c93c294001', '3')])
+        self.assertEqual(len(resources), 1)
+        self.assertEqual(resources[0]['LaunchTemplateId'], good_lt_id)
+
 
 class TestReservedInstance(BaseTest):