Repository: cloud-custodian/cloud-custodian

Issue:
schema - custodian schema command should show the entire schema rather than the references
example:

the schema subcommand now outputs:
```

Help
----

Generic value filter using jmespath
    

Schema
------

properties:
  default:
    type: object
  key:
    type: string
  op:
    $ref: '#/definitions/filters_common/comparison_operators'
  type:
    enum:
    - value
  value:
    $ref: '#/definitions/filters_common/value'
  value_from:
    $ref: '#/definitions/filters_common/value_from'
  value_type:
    $ref: '#/definitions/filters_common/value_types'
required:
- type

```

instead of:
```
Help
----

Generic value filter using jmespath
    

Schema
------

{
    "additionalProperties": false, 
    "required": [
        "type"
    ], 
    "type": "object", 
    "properties": {
        "default": {
            "type": "object"
        }, 
        "value": {
            "oneOf": [
                {
                    "type": "array"
                }, 
                {
                    "type": "string"
                }, 
                {
                    "type": "boolean"
                }, 
                {
                    "type": "number"
                }, 
                {
                    "type": "null"
                }
            ]
        }, 
        "value_from": {
            "additionalProperties": "False", 
            "required": [
                "url"
            ], 
            "type": "object", 
            "properties": {
                "url": {
                    "type": "string"
                }, 
                "expr": {
                    "oneOf": [
                        {
                            "type": "integer"
                        }, 
                        {
                            "type": "string"
                        }
                    ]
                }, 
                "format": {
                    "enum": [
                        "csv", 
                        "json", 
                        "txt", 
                        "csv2dict"
                    ]
                }
            }
        }, 
        "value_type": {
            "enum": [
                "age", 
                "integer", 
                "expiration", 
                "normalize", 
                "size", 
                "cidr", 
                "cidr_size", 
                "swap", 
                "resource_count", 
                "expr", 
                "unique_size"
            ]
        }, 
        "key": {
            "type": "string"
        }, 
        "type": {
            "enum": [
                "value"
            ]
        }, 
        "op": {
            "enum": [
                "regex", 
                "ni", 
                "gt", 
                "lt", 
                "gte", 
                "glob", 
                "contains", 
                "not-equal", 
                "equal", 
                "greater-than", 
                "ne", 
                "ge", 
                "le", 
                "lte", 
                "in", 
                "intersect", 
                "less-than", 
                "difference", 
                "eq", 
                "not-in"
            ]
        }
    }
}
```


Discussion:

Resolving patch:
diff --git a/c7n/commands.py b/c7n/commands.py
--- a/c7n/commands.py
+++ b/c7n/commands.py
@@ -16,6 +16,7 @@
 from collections import Counter, defaultdict
 from datetime import timedelta, datetime
 from functools import wraps
+import jmespath
 import inspect
 import logging
 import os
@@ -30,6 +31,7 @@
 from c7n.exceptions import ClientError
 from c7n.provider import clouds
 from c7n.policy import Policy, PolicyCollection, load as policy_load
+from c7n.schema import generate
 from c7n.utils import dumps, load_file, local_session, SafeLoader
 from c7n.config import Bag, Config
 from c7n import provider
@@ -515,7 +517,9 @@ def _print_cls_schema(cls):
     # Print schema
     print("\nSchema\n------\n")
     if hasattr(cls, 'schema'):
+        definitions = generate()['definitions']
         component_schema = dict(cls.schema)
+        component_schema = _expand_schema(component_schema, definitions)
         component_schema.pop('additionalProperties', None)
         component_schema.pop('type', None)
         print(yaml.safe_dump(component_schema))
@@ -526,6 +530,18 @@ def _print_cls_schema(cls):
     return
 
 
+def _expand_schema(schema, definitions):
+    """Expand references in schema to their full schema"""
+    for k, v in list(schema.items()):
+        if k == '$ref':
+            # the value here is in the form of: '#/definitions/path/to/key'
+            path = '.'.join(v.split('/')[2:])
+            return jmespath.search(path, definitions)
+        if isinstance(v, dict):
+            schema[k] = _expand_schema(v, definitions)
+    return schema
+
+
 def _metrics_get_endpoints(options):
     """ Determine the start and end dates based on user-supplied options. """
     if bool(options.start) ^ bool(options.end):


Test patch:
diff --git a/tests/test_cli.py b/tests/test_cli.py
--- a/tests/test_cli.py
+++ b/tests/test_cli.py
@@ -18,10 +18,14 @@
 import sys
 
 from argparse import ArgumentTypeError
-from c7n import cli, version, commands
 from datetime import datetime, timedelta
 
+from c7n import cli, version, commands
+from c7n.resolver import ValuesFrom
+from c7n.commands import _expand_schema
 from c7n.resources import aws
+from c7n.schema import generate
+
 from .common import BaseTest, TextTestIO
 
 
@@ -225,6 +229,21 @@ def test_schema_output(self):
         output = self.get_output(["custodian", "schema", "ec2.filters.image"])
         self.assertIn("Help", output)
 
+    def test_schema_expand(self):
+        # refs should only ever exist in a dictionary by itself
+        test_schema = {
+            '$ref': '#/definitions/filters_common/value_from'
+        }
+        result = _expand_schema(test_schema, generate()['definitions'])
+        self.assertEquals(result, ValuesFrom.schema)
+
+    def test_schema_expand_not_found(self):
+        test_schema = {
+            '$ref': '#/definitions/filters_common/invalid_schema'
+        }
+        result = _expand_schema(test_schema, generate()['definitions'])
+        self.assertEquals(result, None)
+
 
 class ReportTest(CliTest):