Repository: cloud-custodian/cloud-custodian

Issue:
aws - asg marked invalid incorrectly
Sample policy:
```
- policies:
    - name: filter-invalid-asgs
       resource: asg
       filters:
         - type: invalid
```
Looks like this policy marks valid Asgs, as invalid. On further investigation I found that those instances are spot instances. 
I suspect that the asgs that are running spot instances with launch template, do not return a `LaunchTemplate` top level key with describe_auto_scaling_groups api call. They seem to have LaunchTemplate details under `MixedInstancesPolicy` top level key which does not get checked in the code line shown below. 
https://github.com/cloud-custodian/cloud-custodian/blob/master/c7n/resources/ec2.py#L1888
Hence, it thinks there are no launch templates attached to the Asg, marking it invalid


Discussion:
can you provide a sanitized json for the asgs in question?
i've been digging into this, it does look a bit more substantial to resolve fully. i'd still like to see the sanitized json if you have one handy. 
i've put up a pr, please read the description of the changes there, and test in your environment and provide feedback.
Hello @kapilt , thanks for the PR. I'm putting below a sanitized json.
```
  {
    "AutoScalingGroupName": "asg-name",
    "AutoScalingGroupARN": "arn:aws:autoscaling:us-east-1:XXXXXXX",
    "MixedInstancesPolicy": {
      "LaunchTemplate": {
        "LaunchTemplateSpecification": {
          "LaunchTemplateId": "lt-01234567",
          "LaunchTemplateName": "xxx-abc-lt",
          "Version": "1"
        },
        "Overrides": [
          {
            "InstanceType": "m5.large"
          },
          {
            "InstanceType": "m5a.large"
          },
          {
            "InstanceType": "m4.large"
          }
        ]
      },
      "InstancesDistribution": {
        "OnDemandAllocationStrategy": "prioritized",
        "OnDemandBaseCapacity": 0,
        "OnDemandPercentageAboveBaseCapacity": 0,
        "SpotAllocationStrategy": "lowest-price",
        "SpotInstancePools": 2
      }
    },
    "MinSize": 0,
    "MaxSize": 100,
    "DesiredCapacity": 0,
    "DefaultCooldown": 300,
    "AvailabilityZones": [
      "us-east-1a",
      "us-east-1b",
      "us-east-1d"
    ],
    "LoadBalancerNames": [],
    "TargetGroupARNs": [],
    "HealthCheckType": "EC2",
    "HealthCheckGracePeriod": 600,
    "Instances": [],
    "CreatedTime": "XXXXX",
    "SuspendedProcesses": [
      {
        "ProcessName": "HealthCheck",
        "SuspensionReason": "User suspended at 2019-10-29T00:01:08Z"
      },
      {
        "ProcessName": "ScheduledActions",
        "SuspensionReason": "User suspended at 2019-10-29T00:01:08Z"
      },
      {
        "ProcessName": "AZRebalance",
        "SuspensionReason": "User suspended at 2019-10-29T00:01:08Z"
      },
      {
        "ProcessName": "AlarmNotification",
        "SuspensionReason": "User suspended at 2019-10-29T00:01:08Z"
      },
      {
        "ProcessName": "Launch",
        "SuspensionReason": "User suspended at 2019-10-29T00:01:08Z"
      },
      {
        "ProcessName": "ReplaceUnhealthy",
        "SuspensionReason": "User suspended at 2019-10-29T00:01:08Z"
      },
      {
        "ProcessName": "Terminate",
        "SuspensionReason": "User suspended at 2019-10-29T00:01:08Z"
      },
      {
        "ProcessName": "AddToLoadBalancer",
        "SuspensionReason": "User suspended at 2019-10-29T00:01:08Z"
      },
      {
        "ProcessName": "RemoveFromLoadBalancerLowPriority",
        "SuspensionReason": "User suspended at 2019-10-29T00:01:08Z"
      }
    ],
    "VPCZoneIdentifier": "subnet-XXXXXXX",
    "Tags": [],
    "TerminationPolicies": [
      "Default"
    ],
    "NewInstancesProtectedFromScaleIn": false,
    "ServiceLinkedRoleARN": "arn:aws:iam::X",
    "Invalid": true
  }
```
From what I understood, the response contains either `LaunchTemplate` or `MixedInstancesPolicy` as top level key. Although, I test your PR today and provide a feedback. Thanks!
hmm.. perhaps i misunderstood this then,  i need to walk back on some of the pr, i thought the mixedn instances policy template only applied to the spot instances, but it looks like it applies to the whole asg then. 
so confirmed its all mutually exclusive, an asg only gets a single template or config,  An error occurred (ValidationError) when calling the CreateAutoScalingGroup operation: Valid requests must contain either LaunchTemplate, LaunchConfigurationName, InstanceId or MixedInstancesPolicy parameter.                             

Got it, that's what my thought was. For spot instance types launch template, it just gets MixedInstancesPolicy. 

Resolving patch:
diff --git a/c7n/resources/asg.py b/c7n/resources/asg.py
--- a/c7n/resources/asg.py
+++ b/c7n/resources/asg.py
@@ -125,6 +125,11 @@ def get_launch_id(self, asg):
         if lid is not None:
             return (lid['LaunchTemplateId'], lid['Version'])
 
+        if 'MixedInstancesPolicy' in asg:
+            mip_spec = asg['MixedInstancesPolicy'][
+                'LaunchTemplate']['LaunchTemplateSpecification']
+            return (mip_spec['LaunchTemplateId'], mip_spec['Version'])
+
         # we've noticed some corner cases where the asg name is the lc name, but not
         # explicitly specified as launchconfiguration attribute.
         lid = asg['AutoScalingGroupName']
diff --git a/c7n/resources/ec2.py b/c7n/resources/ec2.py
--- a/c7n/resources/ec2.py
+++ b/c7n/resources/ec2.py
@@ -1888,12 +1888,17 @@ def get_resources(self, rids, cache=True):
     def get_asg_templates(self, asgs):
         templates = {}
         for a in asgs:
-            if 'LaunchTemplate' not in a:
+            t = None
+            if 'LaunchTemplate' in a:
+                t = a['LaunchTemplate']
+            elif 'MixedInstancesPolicy' in a:
+                t = a['MixedInstancesPolicy'][
+                    'LaunchTemplate']['LaunchTemplateSpecification']
+            if t is None:
                 continue
-            t = a['LaunchTemplate']
             templates.setdefault(
-                (t['LaunchTemplateId'], t['Version']), []).append(
-                    a['AutoScalingGroupName'])
+                (t['LaunchTemplateId'],
+                 t['Version']), []).append(a['AutoScalingGroupName'])
         return templates
 
 


Test patch:
diff --git a/tests/test_asg.py b/tests/test_asg.py
--- a/tests/test_asg.py
+++ b/tests/test_asg.py
@@ -18,6 +18,8 @@
 
 from .common import BaseTest
 
+from c7n.resources.asg import LaunchInfo
+
 
 class LaunchConfigTest(BaseTest):
 
@@ -73,6 +75,45 @@ def test_regex_filter(self):
         self.assertGreater(len(resources), 0)
 
 
+class AutoScalingTemplateTest(BaseTest):
+
+    def test_asg_mixed_instance_templates(self):
+        d = {
+            "AutoScalingGroupName": "devx",
+            "MixedInstancesPolicy": {
+                "LaunchTemplate": {
+                    "LaunchTemplateSpecification": {
+                        "LaunchTemplateId": "lt-0877401c93c294001",
+                        "LaunchTemplateName": "test",
+                        "Version": "4"},
+                    "Overrides": [{"InstanceType": "t1.micro"},
+                                  {"InstanceType": "t2.small"}]
+                },
+                "InstancesDistribution": {
+                    "OnDemandAllocationStrategy": "prioritized",
+                    "OnDemandBaseCapacity": 1,
+                    "OnDemandPercentageAboveBaseCapacity": 0,
+                    "SpotAllocationStrategy": "capacity-optimized"
+                }
+            },
+            "MinSize": 1,
+            "MaxSize": 1,
+            "DesiredCapacity": 1,
+            "DefaultCooldown": 300,
+            "AvailabilityZones": ["us-east-1d", "us-east-1e"],
+            "HealthCheckType": "EC2",
+            "HealthCheckGracePeriod": 300,
+            "VPCZoneIdentifier": "subnet-3a334610,subnet-e3b194de"}
+
+        p = self.load_policy({"name": "mixed-instance", "resource": "asg"})
+        self.assertEqual(
+            list(p.resource_manager.get_resource_manager(
+                'launch-template-version').get_asg_templates([d]).keys()),
+            [("lt-0877401c93c294001", "4")])
+        self.assertEqual(
+            LaunchInfo(p.resource_manager).get_launch_id(d), ("lt-0877401c93c294001", "4"))
+
+
 class AutoScalingTest(BaseTest):
 
     def get_ec2_tags(self, ec2, instance_id):