Repository: cloud-custodian/cloud-custodian

Issue:
Schema error with `aws.network-addr`
**Describe the bug**
Schema error with `aws.network-addr`. 

**To Reproduce**
Attempt to get the schema, notice `aws.network-addr` is in the list. Next, try to get the schema for `aws.network-addr`, notice that custodian throws an error.

```bash
# custodian schema
resources:
... snipped...
- aws.network-acl
- aws.network-addr
- aws.ops-item
...
# custodian schema aws.network-addr
2020-05-14 13:50:11,216: custodian.commands:ERROR aws.network-addr is not a valid resource
```

**Expected behavior**
I would expect the schema to load.

**Background (please complete the following information):**
 - OS: ubuntu 18.0.4
 - Python Version: 3.7.5
 - Custodian Version: 0.9.1
 - Tool Version: [if applicable]
 - Cloud Provider: aws
 - Traceback: [if applicable, please exclude sensitive/account information]
 - `custodian version --debug` output:

```
# custodian version
0.9.1
(venv) root@ip-10-66-150-45:/opt/stickler# custodian version --debug

Please copy/paste the following info along with any bug reports:

Custodian:   0.9.1
Python:      3.7.5 (default, Nov  7 2019, 10:50:52)
             [GCC 8.3.0]
Platform:    posix.uname_result(sysname='Linux', nodename='ip-10-66-150-45', release='5.3.0-1017-aws', version='#18~18.04.1-Ubuntu SMP Wed Apr 8 15:12:16 UTC 2020', machine='x86_64')
Using venv:  True
Docker: False
Installed:

argcomplete==1.11.1
attrs==19.3.0
boto3==1.12.47
botocore==1.15.47
docutils==0.15.2
importlib-metadata==1.6.0
jmespath==0.9.5
jsonschema==3.2.0
pyrsistent==0.16.0
python-dateutil==2.8.1
pyyaml==5.3.1
s3transfer==0.3.3
setuptools==46.2.0
six==1.14.0
tabulate==0.8.7
urllib3==1.25.9
zipp==3.1.0
```

**Additional context**
Add any other context about the problem here.



Discussion:
thanks for filing an issue, we switched network-addr to an alias recently so that it was mainly known by elastic-ip as network-addr naming seemed to cause some confusion, but in either case both names should be resolvable in the schema command. 

Resolving patch:
diff --git a/c7n/commands.py b/c7n/commands.py
--- a/c7n/commands.py
+++ b/c7n/commands.py
@@ -379,14 +379,14 @@ def schema_cmd(options):
         components[0] = '%s.%s' % (cloud_provider, components[0])
         load_resources((components[0],))
         resource_mapping = schema.resource_vocabulary(
-            cloud_provider)
+            cloud_provider, aliases=True)
     elif components[0] == 'mode':
         load_available(resources=False)
         resource_mapping = schema.resource_vocabulary()
     else:  # compatibility, aws is default for provider
         components[0] = 'aws.%s' % components[0]
         load_resources((components[0],))
-        resource_mapping = schema.resource_vocabulary('aws')
+        resource_mapping = schema.resource_vocabulary('aws', aliases=True)
 
     #
     # Handle mode
@@ -413,18 +413,21 @@ def schema_cmd(options):
     # Handle resource
     #
     resource = components[0]
-    if resource not in resource_mapping:
+    resource_info = resource_mapping.get(resource, resource_mapping['aliases'].get(resource))
+    if resource_info is None:
         log.error('{} is not a valid resource'.format(resource))
         sys.exit(1)
 
     if len(components) == 1:
         docstring = ElementSchema.doc(
-            resource_mapping[resource]['classes']['resource'])
-        del(resource_mapping[resource]['classes'])
+            resource_info['classes']['resource'])
+        resource_info.pop('classes', None)
+        # de-alias to preferred resource name
+        resource = resource_info.pop('resource_type', resource)
         if docstring:
             print("\nHelp\n----\n")
             print(docstring + '\n')
-        output = {resource: resource_mapping[resource]}
+        output = {resource: resource_info}
         print(yaml_dump(output))
         return
 
@@ -438,9 +441,9 @@ def schema_cmd(options):
 
     if len(components) == 2:
         output = "No {} available for resource {}.".format(category, resource)
-        if category in resource_mapping[resource]:
+        if category in resource_info:
             output = {resource: {
-                category: resource_mapping[resource][category]}}
+                category: resource_info[category]}}
         print(yaml_dump(output))
         return
 
@@ -448,12 +451,12 @@ def schema_cmd(options):
     # Handle item
     #
     item = components[2]
-    if item not in resource_mapping[resource][category]:
+    if item not in resource_info[category]:
         log.error('{} is not in the {} list for resource {}'.format(item, category, resource))
         sys.exit(1)
 
     if len(components) == 3:
-        cls = resource_mapping[resource]['classes'][category][item]
+        cls = resource_info['classes'][category][item]
         _print_cls_schema(cls)
 
         return
diff --git a/c7n/schema.py b/c7n/schema.py
--- a/c7n/schema.py
+++ b/c7n/schema.py
@@ -438,10 +438,13 @@ def resource_outline(provider=None):
     return outline
 
 
-def resource_vocabulary(cloud_name=None, qualify_name=True):
+def resource_vocabulary(cloud_name=None, qualify_name=True, aliases=True):
     vocabulary = {}
     resources = {}
 
+    if aliases:
+        vocabulary['aliases'] = {}
+
     for cname, ctype in clouds.items():
         if cloud_name is not None and cloud_name != cname:
             continue
@@ -471,6 +474,15 @@ def resource_vocabulary(cloud_name=None, qualify_name=True):
             'classes': classes,
         }
 
+        if aliases and resource_type.type_aliases:
+            provider = type_name.split('.', 1)[0]
+            for type_alias in resource_type.type_aliases:
+                vocabulary['aliases'][
+                    "{}.{}".format(provider, type_alias)] = vocabulary[type_name]
+                if provider == 'aws':
+                    vocabulary['aliases'][type_alias] = vocabulary[type_name]
+            vocabulary[type_name]['resource_type'] = type_name
+
     vocabulary["mode"] = {}
     for mode_name, cls in execution.items():
         vocabulary["mode"][mode_name] = cls


Test patch:
diff --git a/tests/test_cli.py b/tests/test_cli.py
--- a/tests/test_cli.py
+++ b/tests/test_cli.py
@@ -178,6 +178,16 @@ def test_schema_outline(self):
             sorted(data['aws']['aws.ec2'].keys()), ['actions', 'filters'])
         self.assertTrue(len(data['aws']['aws.ec2']['actions']) > 10)
 
+    def test_schema_alias(self):
+        stdout, stderr = self.run_and_expect_success([
+            "custodian", "schema", "aws.network-addr"])
+        self.assertIn("aws.elastic-ip:", stdout)
+
+    def test_schema_alias_unqualified(self):
+        stdout, stderr = self.run_and_expect_success([
+            "custodian", "schema", "network-addr"])
+        self.assertIn("aws.elastic-ip:", stdout)
+
     def test_schema(self):
 
         # no options