Repository: cloud-custodian/cloud-custodian

Issue:
WIP - Add API Gateway (rest-api resource) Enable CW Logging filter and action.
Some Cloud Team peers and I will be working to get this code put in with a possibility of a little help from Kapil and maybe Alfred.  The use case being that any time a new API is created or a c7n poll scan is ran it can identify API Gateway APIs which have Stages without CloudWatch Logging enabled or where the Logging level is set to OFF or ERROR.  Then the c7n policy would need to check and verify that a CW Role is specified in the API Gateway Settings for that account and region and if the CW role is missing the role from the policy is extracted and applied.  Once proper CW role is applied to the API Gateway settings it would enable CW logging on all stages from the filter set and turn logging level to users choice (INFO in our case).  I imagine the policy looking something like the following:

```
- name: api-gateway-enable-cw-logging-poll
  resource: rest-api
  filters:
     - or:
         - LoggingLevel: OFF
         - LoggingLevel: ERROR
         - LoggingEnabled: false
  actions:
     - type: enable-logging
       enabled: true
       logging-level: "INFO"
       logging-role: "arn:aws:iam::{{ account }}:role/APIGatewayLoggingRole"



- name: api-gateway-enable-cw-logging-realtime
  resource: rest-api
  mode:
     type: cloudtrail
     events:
        - source: aws.apigateway
          event: UpdateStage
          ids: "requestParameters.restApiId"
        - source: aws.apigateway
          event: CreateDeployment
          ids: "requestParameters.restApiId"
  filters:
     - or:
         - LoggingLevel: OFF
         - LoggingLevel: ERROR
         - LoggingEnabled: false
  actions:
     - type: enable-logging
       enabled: true
       logging-level: "INFO"
       logging-role: "arn:aws:iam::{{ account }}:role/APIGatewayLoggingRole"

```

The Python API calls are as follows:
 
Check to see if a CW Logging Role is set (Required to enable logging)

```
response = restclient.get_account()
```

Response Syntax:

```
{
    'cloudwatchRoleArn': 'string',
    'throttleSettings': {
        'burstLimit': 123,
        'rateLimit': 123.0
    },
    'features': [
        'string',
    ],
    'apiKeyVersion': 'string'
}
```

Then if the role is missing it would need to grab the logging-role specified in the policy and set it to a variable for ex: loggingrole  and then apply it with:

```
response = restclient.update_account(
    patchOperations=[
        {
            'op': 'replace',
            'path': '/cloudwatchRoleArn',
            'value':  loggingrole,
        },
    ]
)
```

Once the CW Logging Role has been confirmed or set it will need to see if logging is currently enabled for the stages associated with each API with the following command:

```
response = restclient.get_stages(
    restApiId=id,
)
```

With Response Syntax (reduced to applicable items):

```
{
    'item': [
        {
            'deploymentId': 'string',
            'stageName': 'string',
            'description': 'string',
            'methodSettings': {
                    'loggingLevel': 'string',
                    'dataTraceEnabled': True|False,
                }
            },
            'tags': {
                'string': 'string'
            },
        },
    ]
}
```

Then with the response it needs to pull in:

```
LoggingEnabled = item[]['methodSettings']['*/*']['dataTraceEnabled']
LoggingLevel = item[]['methodSettings']['*/*']['loggingLevel']
```

to see if logging is enabled (true|false) and to see what level (OFF | ERROR | INFO)
Then to apply different logging settings (Enable Logging and set level to INFO in the following example) the command would be:

```
            response = restclient.update_stage(
                restApiId=id,
                stageName=stageName,
                patchOperations=[
                    {
                        'op': 'replace',
                        'path': '/*/*/logging/dataTrace',
                        'value': 'true'
                    },
                    {
                        'op': 'replace',
                        'path': '/*/*/logging/loglevel',
                        'value': 'info'
                    },
                ]
            )
```



Discussion:

Resolving patch:
diff --git a/c7n/query.py b/c7n/query.py
--- a/c7n/query.py
+++ b/c7n/query.py
@@ -243,9 +243,14 @@ def augment(self, resources):
 @sources.register('describe-child')
 class ChildDescribeSource(DescribeSource):
 
+    resource_query_factory = ChildResourceQuery
+
     def __init__(self, manager):
         self.manager = manager
-        self.query = ChildResourceQuery(
+        self.query = self.get_query()
+
+    def get_query(self):
+        return self.resource_query_factory(
             self.manager.session_factory, self.manager)
 
 
@@ -464,13 +469,18 @@ def generate_arn(self):
 
 class ChildResourceManager(QueryResourceManager):
 
+    child_source = 'describe-child'
+
     @property
     def source_type(self):
-        source = self.data.get('source', 'describe-child')
+        source = self.data.get('source', self.child_source)
         if source == 'describe':
-            source = 'describe-child'
+            source = self.child_source
         return source
 
+    def get_parent_manager(self):
+        return self.get_resource_manager(self.resource_type.parent_spec[0])
+
 
 def _batch_augment(manager, model, detail_spec, resource_set):
     detail_op, param_name, param_key, detail_path = detail_spec
diff --git a/c7n/resources/apigw.py b/c7n/resources/apigw.py
--- a/c7n/resources/apigw.py
+++ b/c7n/resources/apigw.py
@@ -13,12 +13,76 @@
 # limitations under the License.
 from __future__ import absolute_import, division, print_function, unicode_literals
 
-from c7n.manager import resources
-from c7n.query import QueryResourceManager
+from concurrent.futures import as_completed
+
+from c7n.actions import ActionRegistry, BaseAction
+from c7n.filters import FilterRegistry, ValueFilter
+from c7n.manager import resources, ResourceManager
+from c7n import query, utils
+
+
+@resources.register('rest-account')
+class RestAccount(ResourceManager):
+
+    filter_registry = FilterRegistry('rest-account.filters')
+    action_registry = ActionRegistry('rest-account.actions')
+
+    class resource_type(object):
+        service = 'apigateway'
+        name = id = 'account_id'
+        dimensions = None
+
+    @classmethod
+    def get_permissions(cls):
+        return ('apigateway:GET',)
+
+    def get_model(self):
+        return self.resource_type
+
+    def _get_account(self):
+        client = utils.local_session(self.session_factory).client('apigateway')
+        account = client.get_account()
+        account.pop('ResponseMetadata', None)
+        account['account_id'] = 'apigw-settings'
+        return account
+
+    def resources(self):
+        return self.filter_resources([self._get_account()])
+
+    def get_resources(self, resource_ids):
+        return [self._get_account()]
+
+
+OP_SCHEMA = {
+    'type': 'object',
+    'required': ['op', 'path'],
+    'additonalProperties': False,
+    'properties': {
+        'op': {'enum': ['add', 'remove', 'update', 'copy', 'replace', 'test']},
+        'path': {'type': 'string'},
+        'value': {'type': 'string'},
+        'from': {'type': 'string'}
+    }
+}
+
+
+@RestAccount.action_registry.register('update')
+class UpdateAccount(BaseAction):
+
+    permissions = ('apigateway:PATCH',)
+    schema = utils.type_schema(
+        'update',
+        patch={'type': 'array', 'items': OP_SCHEMA},
+        required=['patch'])
+
+    def process(self, resources):
+        client = utils.local_session(
+            self.manager.session_factory).client('apigateway')
+        client.update_account(patchOperations=self.data['patch'])
 
 
 @resources.register('rest-api')
-class RestAPI(QueryResourceManager):
+class RestAPI(query.QueryResourceManager):
 
     class resource_type(object):
         service = 'apigateway'
@@ -29,3 +93,190 @@ class resource_type(object):
         name = 'name'
         date = 'createdDate'
         dimension = 'GatewayName'
+
+
+@resources.register('rest-stage')
+class RestStage(query.ChildResourceManager):
+
+    child_source = 'describe-rest-stage'
+
+    class resource_type(object):
+        service = 'apigateway'
+        parent_spec = ('rest-api', 'restApiId')
+        enum_spec = ('get_stages', 'item', None)
+        name = id = 'stageName'
+        date = 'createdDate'
+        dimension = None
+
+
+@query.sources.register('describe-rest-stage')
+class DescribeRestStage(query.ChildDescribeSource):
+
+    def get_query(self):
+        query = super(DescribeRestStage, self).get_query()
+        query.capture_parent_id = True
+        return query
+
+    def augment(self, resources):
+        results = []
+        # Using capture parent, changes the protocol
+        for parent_id, r in resources:
+            r['restApiId'] = parent_id
+            tags = r.setdefault('Tags', [])
+            for k, v in r.pop('tags', {}).items():
+                tags.append({
+                    'Key': k,
+                    'Value': v})
+            results.append(r)
+        return results
+
+
+@RestStage.action_registry.register('update')
+class UpdateStage(BaseAction):
+
+    permissions = ('apigateway:PATCH',)
+    schema = utils.type_schema(
+        'update',
+        patch={'type': 'array', 'items': OP_SCHEMA},
+        required=['patch'])
+
+    def process(self, resources):
+        client = utils.local_session(
+            self.manager.session_factory).client('apigateway')
+        for r in resources:
+            client.update_stage(
+                restApiId=r['restApiId'],
+                stageName=r['stageName'],
+                patchOperations=self.data['patch'])
+
+
+@resources.register('rest-resource')
+class RestResource(query.ChildResourceManager):
+
+    child_source = 'describe-rest-resource'
+
+    class resource_type(object):
+        service = 'apigateway'
+        parent_spec = ('rest-api', 'restApiId')
+        enum_spec = ('get_resources', 'items', None)
+        id = 'id'
+        name = 'path'
+        dimension = None
+
+
+@query.sources.register('describe-rest-resource')
+class DescribeRestResource(query.ChildDescribeSource):
+
+    def get_query(self):
+        query = super(DescribeRestResource, self).get_query()
+        query.capture_parent_id = True
+        return query
+
+    def augment(self, resources):
+        results = []
+        # Using capture parent id, changes the protocol
+        for parent_id, r in resources:
+            r['restApiId'] = parent_id
+            results.append(r)
+        return results
+
+
+ANNOTATION_KEY = 'c7n-matched-resource-methods'
+
+
+@RestResource.filter_registry.register('rest-method')
+class FilterRestMethod(ValueFilter):
+
+    schema = utils.type_schema(
+        'rest-method',
+        method={'type': 'string', 'enum': [
+            'all', 'ANY', 'PUT', 'GET', "POST",
+            "DELETE", "OPTIONS", "HEAD", "PATCH"]},
+        rinherit=ValueFilter.schema)
+    permissions = ('apigateway:GET',)
+
+    def process(self, resources, event=None):
+        method_set = self.data.get('method', 'all')
+        # 10 req/s with burst to 40
+        client = utils.local_session(
+            self.manager.session_factory).client('apigateway')
+
+        # uniqueness constraint validity across apis?
+        resource_map = {r['id']: r for r in resources}
+
+        futures = {}
+        results = set()
+
+        with self.executor_factory(max_workers=2) as w:
+            tasks = []
+            for r in resources:
+                r_method_set = method_set
+                if method_set == 'all':
+                    r_method_set = r.get('resourceMethods', {}).keys()
+                for m in r_method_set:
+                    tasks.append((r, m))
+            for task_set in utils.chunks(tasks, 20):
+                futures[w.submit(
+                    self.process_task_set, client, task_set)] = task_set
+
+            for f in as_completed(futures):
+                task_set = futures[f]
+                if f.exception():
+                    self.manager.log.warning(
+                        "Error retrieving methods on resources %s",
+                        ["%s:%s" % (r['restApiId'], r['path'])
+                         for r, mt in task_set])
+                    continue
+                for m in f.result():
+                    if self.match(m):
+                        results.add(m['resourceId'])
+                        resource_map[m['resourceId']].setdefault(
+                            ANNOTATION_KEY, []).append(m)
+        return [resource_map[rid] for rid in results]
+
+    def process_task_set(self, client, task_set):
+        results = []
+        for r, m in task_set:
+            method = client.get_method(
+                restApiId=r['restApiId'],
+                resourceId=r['id'],
+                httpMethod=m)
+            method.pop('ResponseMetadata', None)
+            method['restApiId'] = r['restApiId']
+            method['resourceId'] = r['id']
+            results.append(method)
+        return results
+
+
+@RestResource.action_registry.register('update-method')
+class UpdateRestMethod(BaseAction):
+
+    schema = utils.type_schema(
+        'update-method',
+        patch={'type': 'array', 'items': OP_SCHEMA},
+        required=['patch'])
+    permissions = ('apigateway:GET',)
+
+    def validate(self):
+        found = False
+        for f in self.manager.filters:
+            if isinstance(f, FilterRestMethod):
+                found = True
+                break
+        if not found:
+            raise ValueError(
+                ("update-method action requires ",
+                 "rest-method filter usage in policy"))
+        return self
+
+    def process(self, resources):
+        client = utils.local_session(
+            self.manager.session_factory).client('apigateway')
+        ops = self.data['patch']
+        for r in resources:
+            for m in r.get(ANNOTATION_KEY, []):
+                client.update_method(
+                    restApiId=m['restApiId'],
+                    resourceId=m['resourceId'],
+                    httpMethod=m['httpMethod'],
+                    patchOperations=ops)
diff --git a/c7n/resources/vpc.py b/c7n/resources/vpc.py
--- a/c7n/resources/vpc.py
+++ b/c7n/resources/vpc.py
@@ -876,7 +876,6 @@ def __call__(self, resource):
         matched = []
         sg_id = resource['GroupId']
         match_op = self.data.get('match-operator', 'and') == 'and' and all or any
-
         for perm in self.expand_permissions(resource[self.ip_permissions_key]):
             perm_matches = {}
             for idx, f in enumerate(self.vfilters):
@@ -884,7 +883,6 @@ def __call__(self, resource):
             perm_matches['ports'] = self.process_ports(perm)
             perm_matches['cidrs'] = self.process_cidrs(perm)
             perm_matches['self-refs'] = self.process_self_reference(perm, sg_id)
-
             perm_match_values = list(filter(
                 lambda x: x is not None, perm_matches.values()))
 


Test patch:
diff --git a/tests/data/placebo/test_rest_account_update/apigateway.GetAccount_1.json b/tests/data/placebo/test_rest_account_update/apigateway.GetAccount_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_rest_account_update/apigateway.GetAccount_1.json
@@ -0,0 +1,26 @@
+{
+    "status_code": 200,
+    "data": {
+        "ResponseMetadata": {
+            "RequestId": "f73d7a74-fd58-11e7-86bb-91cba7ad0737",
+            "HTTPStatusCode": 200,
+            "HTTPHeaders": {
+                "date": "Fri, 19 Jan 2018 20:40:20 GMT",
+                "content-type": "application/json",
+                "content-length": "172",
+                "connection": "keep-alive",
+                "x-amzn-requestid": "f73d7a74-fd58-11e7-86bb-91cba7ad0737"
+            },
+            "RetryAttempts": 0
+        },
+        "cloudwatchRoleArn": "arn:aws:iam::644160558196:role/ApiGwLogger",
+        "throttleSettings": {
+            "burstLimit": 5000,
+            "rateLimit": 10000.0
+        },
+        "features": [
+            "UsagePlans"
+        ],
+        "apiKeyVersion": "4"
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_rest_account_update/apigateway.GetAccount_2.json b/tests/data/placebo/test_rest_account_update/apigateway.GetAccount_2.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_rest_account_update/apigateway.GetAccount_2.json
@@ -0,0 +1,26 @@
+{
+    "status_code": 200,
+    "data": {
+        "ResponseMetadata": {
+            "RequestId": "f7918d8d-fd58-11e7-b9b5-59e88fede609",
+            "HTTPStatusCode": 200,
+            "HTTPHeaders": {
+                "date": "Fri, 19 Jan 2018 20:40:21 GMT",
+                "content-type": "application/json",
+                "content-length": "172",
+                "connection": "keep-alive",
+                "x-amzn-requestid": "f7918d8d-fd58-11e7-b9b5-59e88fede609"
+            },
+            "RetryAttempts": 0
+        },
+        "cloudwatchRoleArn": "arn:aws:iam::644160558196:role/ApiGwLogger",
+        "throttleSettings": {
+            "burstLimit": 5000,
+            "rateLimit": 10000.0
+        },
+        "features": [
+            "UsagePlans"
+        ],
+        "apiKeyVersion": "4"
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_rest_account_update/apigateway.GetAccount_3.json b/tests/data/placebo/test_rest_account_update/apigateway.GetAccount_3.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_rest_account_update/apigateway.GetAccount_3.json
@@ -0,0 +1,26 @@
+{
+    "status_code": 200,
+    "data": {
+        "ResponseMetadata": {
+            "RequestId": "f89430de-fd58-11e7-af05-d7b9e8636ed0",
+            "HTTPStatusCode": 200,
+            "HTTPHeaders": {
+                "date": "Fri, 19 Jan 2018 20:40:22 GMT",
+                "content-type": "application/json",
+                "content-length": "182",
+                "connection": "keep-alive",
+                "x-amzn-requestid": "f89430de-fd58-11e7-af05-d7b9e8636ed0"
+            },
+            "RetryAttempts": 0
+        },
+        "cloudwatchRoleArn": "arn:aws:iam::644160558196:role/OtherApiGatewayLogger",
+        "throttleSettings": {
+            "burstLimit": 5000,
+            "rateLimit": 10000.0
+        },
+        "features": [
+            "UsagePlans"
+        ],
+        "apiKeyVersion": "4"
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_rest_account_update/apigateway.UpdateAccount_1.json b/tests/data/placebo/test_rest_account_update/apigateway.UpdateAccount_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_rest_account_update/apigateway.UpdateAccount_1.json
@@ -0,0 +1,26 @@
+{
+    "status_code": 200,
+    "data": {
+        "ResponseMetadata": {
+            "RequestId": "f7e3f359-fd58-11e7-b9b5-59e88fede609",
+            "HTTPStatusCode": 200,
+            "HTTPHeaders": {
+                "date": "Fri, 19 Jan 2018 20:40:22 GMT",
+                "content-type": "application/json",
+                "content-length": "182",
+                "connection": "keep-alive",
+                "x-amzn-requestid": "f7e3f359-fd58-11e7-b9b5-59e88fede609"
+            },
+            "RetryAttempts": 0
+        },
+        "cloudwatchRoleArn": "arn:aws:iam::644160558196:role/OtherApiGatewayLogger",
+        "throttleSettings": {
+            "burstLimit": 5000,
+            "rateLimit": 10000.0
+        },
+        "features": [
+            "UsagePlans"
+        ],
+        "apiKeyVersion": "4"
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_rest_resource_method_update/apigateway.GetMethod_1.json b/tests/data/placebo/test_rest_resource_method_update/apigateway.GetMethod_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_rest_resource_method_update/apigateway.GetMethod_1.json
@@ -0,0 +1,33 @@
+{
+    "status_code": 200,
+    "data": {
+        "ResponseMetadata": {
+            "RequestId": "826e4042-ff96-11e7-a372-3fc0eeca9a36",
+            "HTTPStatusCode": 200,
+            "HTTPHeaders": {
+                "date": "Mon, 22 Jan 2018 17:05:55 GMT",
+                "content-type": "application/json",
+                "content-length": "490",
+                "connection": "keep-alive",
+                "x-amzn-requestid": "826e4042-ff96-11e7-a372-3fc0eeca9a36"
+            },
+            "RetryAttempts": 0
+        },
+        "httpMethod": "ANY",
+        "authorizationType": "AWS_IAM",
+        "apiKeyRequired": false,
+        "requestParameters": {},
+        "requestModels": {},
+        "methodIntegration": {
+            "type": "AWS_PROXY",
+            "httpMethod": "POST",
+            "uri": "arn:aws:apigateway:us-east-1:lambda:path/2015-03-31/functions/arn:aws:lambda:us-east-1:644160558196:function:sphere7-dev-ApiFunction-7K03QNRVA8OD/invocations",
+            "requestParameters": {},
+            "requestTemplates": {},
+            "passthroughBehavior": "NEVER",
+            "timeoutInMillis": 29000,
+            "cacheNamespace": "none",
+            "cacheKeyParameters": []
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_rest_resource_method_update/apigateway.GetMethod_2.json b/tests/data/placebo/test_rest_resource_method_update/apigateway.GetMethod_2.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_rest_resource_method_update/apigateway.GetMethod_2.json
@@ -0,0 +1,33 @@
+{
+    "status_code": 200,
+    "data": {
+        "ResponseMetadata": {
+            "RequestId": "8275ba31-ff96-11e7-8290-fba8696b2fbb",
+            "HTTPStatusCode": 200,
+            "HTTPHeaders": {
+                "date": "Mon, 22 Jan 2018 17:05:55 GMT",
+                "content-type": "application/json",
+                "content-length": "490",
+                "connection": "keep-alive",
+                "x-amzn-requestid": "8275ba31-ff96-11e7-8290-fba8696b2fbb"
+            },
+            "RetryAttempts": 0
+        },
+        "httpMethod": "ANY",
+        "authorizationType": "AWS_IAM",
+        "apiKeyRequired": false,
+        "requestParameters": {},
+        "requestModels": {},
+        "methodIntegration": {
+            "type": "AWS_PROXY",
+            "httpMethod": "POST",
+            "uri": "arn:aws:apigateway:us-east-1:lambda:path/2015-03-31/functions/arn:aws:lambda:us-east-1:644160558196:function:sphere7-dev-ApiFunction-7K03QNRVA8OD/invocations",
+            "requestParameters": {},
+            "requestTemplates": {},
+            "passthroughBehavior": "NEVER",
+            "timeoutInMillis": 29000,
+            "cacheNamespace": "none",
+            "cacheKeyParameters": []
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_rest_resource_method_update/apigateway.GetMethod_3.json b/tests/data/placebo/test_rest_resource_method_update/apigateway.GetMethod_3.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_rest_resource_method_update/apigateway.GetMethod_3.json
@@ -0,0 +1,41 @@
+{
+    "status_code": 200,
+    "data": {
+        "ResponseMetadata": {
+            "RequestId": "827d5ae2-ff96-11e7-a239-c1dc20443a92",
+            "HTTPStatusCode": 200,
+            "HTTPHeaders": {
+                "date": "Mon, 22 Jan 2018 17:05:55 GMT",
+                "content-type": "application/json",
+                "content-length": "528",
+                "connection": "keep-alive",
+                "x-amzn-requestid": "827d5ae2-ff96-11e7-a239-c1dc20443a92"
+            },
+            "RetryAttempts": 0
+        },
+        "httpMethod": "ANY",
+        "authorizationType": "NONE",
+        "apiKeyRequired": false,
+        "methodResponses": {
+            "200": {
+                "statusCode": "200",
+                "responseModels": {}
+            }
+        },
+        "methodIntegration": {
+            "type": "AWS_PROXY",
+            "httpMethod": "POST",
+            "uri": "arn:aws:apigateway:us-east-1:lambda:path/2015-03-31/functions/arn:aws:lambda:us-east-1:644160558196:function:glenns_test/invocations",
+            "passthroughBehavior": "WHEN_NO_MATCH",
+            "timeoutInMillis": 29000,
+            "cacheNamespace": "rygwz7",
+            "cacheKeyParameters": [],
+            "integrationResponses": {
+                "200": {
+                    "statusCode": "200",
+                    "selectionPattern": ".*"
+                }
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_rest_resource_method_update/apigateway.GetMethod_4.json b/tests/data/placebo/test_rest_resource_method_update/apigateway.GetMethod_4.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_rest_resource_method_update/apigateway.GetMethod_4.json
@@ -0,0 +1,41 @@
+{
+    "status_code": 200,
+    "data": {
+        "ResponseMetadata": {
+            "RequestId": "22359c9e-ff98-11e7-b9b5-59e88fede609",
+            "HTTPStatusCode": 200,
+            "HTTPHeaders": {
+                "date": "Mon, 22 Jan 2018 17:17:33 GMT",
+                "content-type": "application/json",
+                "content-length": "531",
+                "connection": "keep-alive",
+                "x-amzn-requestid": "22359c9e-ff98-11e7-b9b5-59e88fede609"
+            },
+            "RetryAttempts": 0
+        },
+        "httpMethod": "ANY",
+        "authorizationType": "AWS_IAM",
+        "apiKeyRequired": false,
+        "methodResponses": {
+            "200": {
+                "statusCode": "200",
+                "responseModels": {}
+            }
+        },
+        "methodIntegration": {
+            "type": "AWS_PROXY",
+            "httpMethod": "POST",
+            "uri": "arn:aws:apigateway:us-east-1:lambda:path/2015-03-31/functions/arn:aws:lambda:us-east-1:644160558196:function:glenns_test/invocations",
+            "passthroughBehavior": "WHEN_NO_MATCH",
+            "timeoutInMillis": 29000,
+            "cacheNamespace": "rygwz7",
+            "cacheKeyParameters": [],
+            "integrationResponses": {
+                "200": {
+                    "statusCode": "200",
+                    "selectionPattern": ".*"
+                }
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_rest_resource_method_update/apigateway.GetResources_1.json b/tests/data/placebo/test_rest_resource_method_update/apigateway.GetResources_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_rest_resource_method_update/apigateway.GetResources_1.json
@@ -0,0 +1,35 @@
+{
+    "status_code": 200,
+    "data": {
+        "ResponseMetadata": {
+            "RequestId": "823f8e9b-ff96-11e7-a190-a392413faa8a",
+            "HTTPStatusCode": 200,
+            "HTTPHeaders": {
+                "date": "Mon, 22 Jan 2018 17:05:55 GMT",
+                "content-type": "application/json",
+                "content-length": "181",
+                "connection": "keep-alive",
+                "x-amzn-requestid": "823f8e9b-ff96-11e7-a190-a392413faa8a"
+            },
+            "RetryAttempts": 0
+        },
+        "items": [
+            {
+                "id": "5m14zq",
+                "parentId": "yxv4xe21ka",
+                "pathPart": "{proxy+}",
+                "path": "/{proxy+}",
+                "resourceMethods": {
+                    "ANY": {}
+                }
+            },
+            {
+                "id": "yxv4xe21ka",
+                "path": "/",
+                "resourceMethods": {
+                    "ANY": {}
+                }
+            }
+        ]
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_rest_resource_method_update/apigateway.GetResources_2.json b/tests/data/placebo/test_rest_resource_method_update/apigateway.GetResources_2.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_rest_resource_method_update/apigateway.GetResources_2.json
@@ -0,0 +1,32 @@
+{
+    "status_code": 200,
+    "data": {
+        "ResponseMetadata": {
+            "RequestId": "8247cc1a-ff96-11e7-9092-55b4e57b1bc6",
+            "HTTPStatusCode": 200,
+            "HTTPHeaders": {
+                "date": "Mon, 22 Jan 2018 17:05:55 GMT",
+                "content-type": "application/json",
+                "content-length": "158",
+                "connection": "keep-alive",
+                "x-amzn-requestid": "8247cc1a-ff96-11e7-9092-55b4e57b1bc6"
+            },
+            "RetryAttempts": 0
+        },
+        "items": [
+            {
+                "id": "g7vd3d1wzl",
+                "path": "/"
+            },
+            {
+                "id": "rygwz7",
+                "parentId": "g7vd3d1wzl",
+                "pathPart": "glenns_test",
+                "path": "/glenns_test",
+                "resourceMethods": {
+                    "ANY": {}
+                }
+            }
+        ]
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_rest_resource_method_update/apigateway.GetRestApis_1.json b/tests/data/placebo/test_rest_resource_method_update/apigateway.GetRestApis_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_rest_resource_method_update/apigateway.GetRestApis_1.json
@@ -0,0 +1,61 @@
+{
+    "status_code": 200,
+    "data": {
+        "ResponseMetadata": {
+            "RequestId": "8217469c-ff96-11e7-a372-3fc0eeca9a36",
+            "HTTPStatusCode": 200,
+            "HTTPHeaders": {
+                "date": "Mon, 22 Jan 2018 17:05:55 GMT",
+                "content-type": "application/json",
+                "content-length": "364",
+                "connection": "keep-alive",
+                "x-amzn-requestid": "8217469c-ff96-11e7-a372-3fc0eeca9a36"
+            },
+            "RetryAttempts": 0
+        },
+        "items": [
+            {
+                "id": "5xhc1cnb7h",
+                "name": "sphere11",
+                "description": "The volume goes to 11",
+                "createdDate": {
+                    "__class__": "datetime",
+                    "year": 2017,
+                    "month": 1,
+                    "day": 22,
+                    "hour": 6,
+                    "minute": 27,
+                    "second": 33,
+                    "microsecond": 0
+                },
+                "apiKeySource": "HEADER",
+                "endpointConfiguration": {
+                    "types": [
+                        "EDGE"
+                    ]
+                }
+            },
+            {
+                "id": "rtmgxfiay5",
+                "name": "testapi",
+                "description": "for demo only",
+                "createdDate": {
+                    "__class__": "datetime",
+                    "year": 2017,
+                    "month": 4,
+                    "day": 5,
+                    "hour": 10,
+                    "minute": 34,
+                    "second": 55,
+                    "microsecond": 0
+                },
+                "apiKeySource": "HEADER",
+                "endpointConfiguration": {
+                    "types": [
+                        "EDGE"
+                    ]
+                }
+            }
+        ]
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_rest_resource_method_update/apigateway.UpdateMethod_1.json b/tests/data/placebo/test_rest_resource_method_update/apigateway.UpdateMethod_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_rest_resource_method_update/apigateway.UpdateMethod_1.json
@@ -0,0 +1,41 @@
+{
+    "status_code": 200,
+    "data": {
+        "ResponseMetadata": {
+            "RequestId": "82a38113-ff96-11e7-a372-3fc0eeca9a36",
+            "HTTPStatusCode": 200,
+            "HTTPHeaders": {
+                "date": "Mon, 22 Jan 2018 17:05:56 GMT",
+                "content-type": "application/json",
+                "content-length": "531",
+                "connection": "keep-alive",
+                "x-amzn-requestid": "82a38113-ff96-11e7-a372-3fc0eeca9a36"
+            },
+            "RetryAttempts": 0
+        },
+        "httpMethod": "ANY",
+        "authorizationType": "AWS_IAM",
+        "apiKeyRequired": false,
+        "methodResponses": {
+            "200": {
+                "statusCode": "200",
+                "responseModels": {}
+            }
+        },
+        "methodIntegration": {
+            "type": "AWS_PROXY",
+            "httpMethod": "POST",
+            "uri": "arn:aws:apigateway:us-east-1:lambda:path/2015-03-31/functions/arn:aws:lambda:us-east-1:644160558196:function:glenns_test/invocations",
+            "passthroughBehavior": "WHEN_NO_MATCH",
+            "timeoutInMillis": 29000,
+            "cacheNamespace": "rygwz7",
+            "cacheKeyParameters": [],
+            "integrationResponses": {
+                "200": {
+                    "statusCode": "200",
+                    "selectionPattern": ".*"
+                }
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_rest_resource_resource/apigateway.GetResources_1.json b/tests/data/placebo/test_rest_resource_resource/apigateway.GetResources_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_rest_resource_resource/apigateway.GetResources_1.json
@@ -0,0 +1,35 @@
+{
+    "status_code": 200,
+    "data": {
+        "ResponseMetadata": {
+            "RequestId": "422a2e71-ff95-11e7-8290-fba8696b2fbb",
+            "HTTPStatusCode": 200,
+            "HTTPHeaders": {
+                "date": "Mon, 22 Jan 2018 16:56:58 GMT",
+                "content-type": "application/json",
+                "content-length": "181",
+                "connection": "keep-alive",
+                "x-amzn-requestid": "422a2e71-ff95-11e7-8290-fba8696b2fbb"
+            },
+            "RetryAttempts": 0
+        },
+        "items": [
+            {
+                "id": "5m14zq",
+                "parentId": "yxv4xe21ka",
+                "pathPart": "{proxy+}",
+                "path": "/{proxy+}",
+                "resourceMethods": {
+                    "ANY": {}
+                }
+            },
+            {
+                "id": "yxv4xe21ka",
+                "path": "/",
+                "resourceMethods": {
+                    "ANY": {}
+                }
+            }
+        ]
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_rest_resource_resource/apigateway.GetResources_2.json b/tests/data/placebo/test_rest_resource_resource/apigateway.GetResources_2.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_rest_resource_resource/apigateway.GetResources_2.json
@@ -0,0 +1,32 @@
+{
+    "status_code": 200,
+    "data": {
+        "ResponseMetadata": {
+            "RequestId": "42363c83-ff95-11e7-b9b5-59e88fede609",
+            "HTTPStatusCode": 200,
+            "HTTPHeaders": {
+                "date": "Mon, 22 Jan 2018 16:56:58 GMT",
+                "content-type": "application/json",
+                "content-length": "158",
+                "connection": "keep-alive",
+                "x-amzn-requestid": "42363c83-ff95-11e7-b9b5-59e88fede609"
+            },
+            "RetryAttempts": 0
+        },
+        "items": [
+            {
+                "id": "g7vd3d1wzl",
+                "path": "/"
+            },
+            {
+                "id": "rygwz7",
+                "parentId": "g7vd3d1wzl",
+                "pathPart": "glenns_test",
+                "path": "/glenns_test",
+                "resourceMethods": {
+                    "ANY": {}
+                }
+            }
+        ]
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_rest_resource_resource/apigateway.GetRestApis_1.json b/tests/data/placebo/test_rest_resource_resource/apigateway.GetRestApis_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_rest_resource_resource/apigateway.GetRestApis_1.json
@@ -0,0 +1,61 @@
+{
+    "status_code": 200,
+    "data": {
+        "ResponseMetadata": {
+            "RequestId": "42031eda-ff95-11e7-a372-3fc0eeca9a36",
+            "HTTPStatusCode": 200,
+            "HTTPHeaders": {
+                "date": "Mon, 22 Jan 2018 16:56:58 GMT",
+                "content-type": "application/json",
+                "content-length": "364",
+                "connection": "keep-alive",
+                "x-amzn-requestid": "42031eda-ff95-11e7-a372-3fc0eeca9a36"
+            },
+            "RetryAttempts": 0
+        },
+        "items": [
+            {
+                "id": "5xhc1cnb7h",
+                "name": "sphere11",
+                "description": "The volume goes to 11",
+                "createdDate": {
+                    "__class__": "datetime",
+                    "year": 2017,
+                    "month": 1,
+                    "day": 22,
+                    "hour": 6,
+                    "minute": 27,
+                    "second": 33,
+                    "microsecond": 0
+                },
+                "apiKeySource": "HEADER",
+                "endpointConfiguration": {
+                    "types": [
+                        "EDGE"
+                    ]
+                }
+            },
+            {
+                "id": "rtmgxfiay5",
+                "name": "testapi",
+                "description": "for demo only",
+                "createdDate": {
+                    "__class__": "datetime",
+                    "year": 2017,
+                    "month": 4,
+                    "day": 5,
+                    "hour": 10,
+                    "minute": 34,
+                    "second": 55,
+                    "microsecond": 0
+                },
+                "apiKeySource": "HEADER",
+                "endpointConfiguration": {
+                    "types": [
+                        "EDGE"
+                    ]
+                }
+            }
+        ]
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_rest_stage/apigateway.GetRestApis_1.json b/tests/data/placebo/test_rest_stage/apigateway.GetRestApis_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_rest_stage/apigateway.GetRestApis_1.json
@@ -0,0 +1,61 @@
+{
+    "status_code": 200,
+    "data": {
+        "ResponseMetadata": {
+            "RequestId": "9baab075-fd38-11e7-b9b5-59e88fede609",
+            "HTTPStatusCode": 200,
+            "HTTPHeaders": {
+                "date": "Fri, 19 Jan 2018 16:48:43 GMT",
+                "content-type": "application/json",
+                "content-length": "364",
+                "connection": "keep-alive",
+                "x-amzn-requestid": "9baab075-fd38-11e7-b9b5-59e88fede609"
+            },
+            "RetryAttempts": 0
+        },
+        "items": [
+            {
+                "id": "5xhc1cnb7h",
+                "name": "sphere11",
+                "description": "The volume goes to 11",
+                "createdDate": {
+                    "__class__": "datetime",
+                    "year": 2017,
+                    "month": 1,
+                    "day": 22,
+                    "hour": 6,
+                    "minute": 27,
+                    "second": 33,
+                    "microsecond": 0
+                },
+                "apiKeySource": "HEADER",
+                "endpointConfiguration": {
+                    "types": [
+                        "EDGE"
+                    ]
+                }
+            },
+            {
+                "id": "rtmgxfiay5",
+                "name": "testapi",
+                "description": "for demo only",
+                "createdDate": {
+                    "__class__": "datetime",
+                    "year": 2017,
+                    "month": 4,
+                    "day": 5,
+                    "hour": 10,
+                    "minute": 34,
+                    "second": 55,
+                    "microsecond": 0
+                },
+                "apiKeySource": "HEADER",
+                "endpointConfiguration": {
+                    "types": [
+                        "EDGE"
+                    ]
+                }
+            }
+        ]
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_rest_stage/apigateway.GetStages_1.json b/tests/data/placebo/test_rest_stage/apigateway.GetStages_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_rest_stage/apigateway.GetStages_1.json
@@ -0,0 +1,91 @@
+{
+    "status_code": 200,
+    "data": {
+        "ResponseMetadata": {
+            "RequestId": "9bd45876-fd38-11e7-9092-55b4e57b1bc6",
+            "HTTPStatusCode": 200,
+            "HTTPHeaders": {
+                "date": "Fri, 19 Jan 2018 16:48:43 GMT",
+                "content-type": "application/json",
+                "content-length": "764",
+                "connection": "keep-alive",
+                "x-amzn-requestid": "9bd45876-fd38-11e7-9092-55b4e57b1bc6"
+            },
+            "RetryAttempts": 0
+        },
+        "item": [
+            {
+                "deploymentId": "49ijxa",
+                "stageName": "latest",
+                "cacheClusterEnabled": false,
+                "cacheClusterSize": "0.5",
+                "cacheClusterStatus": "NOT_AVAILABLE",
+                "methodSettings": {
+                    "*/*": {
+                        "metricsEnabled": false,
+                        "loggingLevel": "INFO",
+                        "dataTraceEnabled": true,
+                        "throttlingBurstLimit": 2000,
+                        "throttlingRateLimit": 1000.0,
+                        "cachingEnabled": false,
+                        "cacheTtlInSeconds": 300,
+                        "cacheDataEncrypted": false,
+                        "requireAuthorizationForCacheControl": true,
+                        "unauthorizedCacheControlHeaderStrategy": "SUCCEED_WITH_RESPONSE_HEADER"
+                    }
+                },
+                "variables": {},
+                "tags": {
+                    "ENV": "DEV"
+                },
+                "createdDate": {
+                    "__class__": "datetime",
+                    "year": 2017,
+                    "month": 1,
+                    "day": 22,
+                    "hour": 6,
+                    "minute": 28,
+                    "second": 5,
+                    "microsecond": 0
+                },
+                "lastUpdatedDate": {
+                    "__class__": "datetime",
+                    "year": 2018,
+                    "month": 1,
+                    "day": 19,
+                    "hour": 11,
+                    "minute": 33,
+                    "second": 41,
+                    "microsecond": 0
+                }
+            },
+            {
+                "deploymentId": "49ijxa",
+                "stageName": "noop",
+                "cacheClusterEnabled": false,
+                "cacheClusterStatus": "NOT_AVAILABLE",
+                "methodSettings": {},
+                "createdDate": {
+                    "__class__": "datetime",
+                    "year": 2017,
+                    "month": 1,
+                    "day": 22,
+                    "hour": 6,
+                    "minute": 28,
+                    "second": 2,
+                    "microsecond": 0
+                },
+                "lastUpdatedDate": {
+                    "__class__": "datetime",
+                    "year": 2017,
+                    "month": 1,
+                    "day": 22,
+                    "hour": 6,
+                    "minute": 28,
+                    "second": 2,
+                    "microsecond": 0
+                }
+            }
+        ]
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_rest_stage/apigateway.GetStages_2.json b/tests/data/placebo/test_rest_stage/apigateway.GetStages_2.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_rest_stage/apigateway.GetStages_2.json
@@ -0,0 +1,47 @@
+{
+    "status_code": 200,
+    "data": {
+        "ResponseMetadata": {
+            "RequestId": "9be03f7a-fd38-11e7-a239-c1dc20443a92",
+            "HTTPStatusCode": 200,
+            "HTTPHeaders": {
+                "date": "Fri, 19 Jan 2018 16:48:43 GMT",
+                "content-type": "application/json",
+                "content-length": "233",
+                "connection": "keep-alive",
+                "x-amzn-requestid": "9be03f7a-fd38-11e7-a239-c1dc20443a92"
+            },
+            "RetryAttempts": 0
+        },
+        "item": [
+            {
+                "deploymentId": "r2pi2v",
+                "stageName": "prod",
+                "description": "Created by AWS Lambda",
+                "cacheClusterEnabled": false,
+                "cacheClusterStatus": "NOT_AVAILABLE",
+                "methodSettings": {},
+                "createdDate": {
+                    "__class__": "datetime",
+                    "year": 2017,
+                    "month": 4,
+                    "day": 5,
+                    "hour": 10,
+                    "minute": 35,
+                    "second": 32,
+                    "microsecond": 0
+                },
+                "lastUpdatedDate": {
+                    "__class__": "datetime",
+                    "year": 2017,
+                    "month": 4,
+                    "day": 5,
+                    "hour": 10,
+                    "minute": 35,
+                    "second": 32,
+                    "microsecond": 0
+                }
+            }
+        ]
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_rest_stage_update/apigateway.GetRestApis_1.json b/tests/data/placebo/test_rest_stage_update/apigateway.GetRestApis_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_rest_stage_update/apigateway.GetRestApis_1.json
@@ -0,0 +1,61 @@
+{
+    "status_code": 200,
+    "data": {
+        "ResponseMetadata": {
+            "RequestId": "9c2f3819-fdfe-11e7-9092-55b4e57b1bc6",
+            "HTTPStatusCode": 200,
+            "HTTPHeaders": {
+                "date": "Sat, 20 Jan 2018 16:26:04 GMT",
+                "content-type": "application/json",
+                "content-length": "364",
+                "connection": "keep-alive",
+                "x-amzn-requestid": "9c2f3819-fdfe-11e7-9092-55b4e57b1bc6"
+            },
+            "RetryAttempts": 0
+        },
+        "items": [
+            {
+                "id": "5xhc1cnb7h",
+                "name": "sphere11",
+                "description": "The volume goes to 11",
+                "createdDate": {
+                    "__class__": "datetime",
+                    "year": 2017,
+                    "month": 1,
+                    "day": 22,
+                    "hour": 6,
+                    "minute": 27,
+                    "second": 33,
+                    "microsecond": 0
+                },
+                "apiKeySource": "HEADER",
+                "endpointConfiguration": {
+                    "types": [
+                        "EDGE"
+                    ]
+                }
+            },
+            {
+                "id": "rtmgxfiay5",
+                "name": "testapi",
+                "description": "for demo only",
+                "createdDate": {
+                    "__class__": "datetime",
+                    "year": 2017,
+                    "month": 4,
+                    "day": 5,
+                    "hour": 10,
+                    "minute": 34,
+                    "second": 55,
+                    "microsecond": 0
+                },
+                "apiKeySource": "HEADER",
+                "endpointConfiguration": {
+                    "types": [
+                        "EDGE"
+                    ]
+                }
+            }
+        ]
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_rest_stage_update/apigateway.GetStage_1.json b/tests/data/placebo/test_rest_stage_update/apigateway.GetStage_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_rest_stage_update/apigateway.GetStage_1.json
@@ -0,0 +1,55 @@
+{
+    "status_code": 200,
+    "data": {
+        "ResponseMetadata": {
+            "RequestId": "9d2c8357-fdfe-11e7-8290-fba8696b2fbb",
+            "HTTPStatusCode": 200,
+            "HTTPHeaders": {
+                "date": "Sat, 20 Jan 2018 16:26:05 GMT",
+                "content-type": "application/json",
+                "content-length": "507",
+                "connection": "keep-alive",
+                "x-amzn-requestid": "9d2c8357-fdfe-11e7-8290-fba8696b2fbb"
+            },
+            "RetryAttempts": 0
+        },
+        "deploymentId": "49ijxa",
+        "stageName": "noop",
+        "cacheClusterEnabled": false,
+        "cacheClusterStatus": "NOT_AVAILABLE",
+        "methodSettings": {
+            "*/*": {
+                "metricsEnabled": false,
+                "loggingLevel": "INFO",
+                "dataTraceEnabled": true,
+                "throttlingBurstLimit": 5000,
+                "throttlingRateLimit": 10000.0,
+                "cachingEnabled": false,
+                "cacheTtlInSeconds": 300,
+                "cacheDataEncrypted": false,
+                "requireAuthorizationForCacheControl": true,
+                "unauthorizedCacheControlHeaderStrategy": "SUCCEED_WITH_RESPONSE_HEADER"
+            }
+        },
+        "createdDate": {
+            "__class__": "datetime",
+            "year": 2017,
+            "month": 1,
+            "day": 22,
+            "hour": 6,
+            "minute": 28,
+            "second": 2,
+            "microsecond": 0
+        },
+        "lastUpdatedDate": {
+            "__class__": "datetime",
+            "year": 2018,
+            "month": 1,
+            "day": 20,
+            "hour": 11,
+            "minute": 26,
+            "second": 5,
+            "microsecond": 0
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_rest_stage_update/apigateway.GetStages_1.json b/tests/data/placebo/test_rest_stage_update/apigateway.GetStages_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_rest_stage_update/apigateway.GetStages_1.json
@@ -0,0 +1,91 @@
+{
+    "status_code": 200,
+    "data": {
+        "ResponseMetadata": {
+            "RequestId": "9c5ab442-fdfe-11e7-a239-c1dc20443a92",
+            "HTTPStatusCode": 200,
+            "HTTPHeaders": {
+                "date": "Sat, 20 Jan 2018 16:26:04 GMT",
+                "content-type": "application/json",
+                "content-length": "764",
+                "connection": "keep-alive",
+                "x-amzn-requestid": "9c5ab442-fdfe-11e7-a239-c1dc20443a92"
+            },
+            "RetryAttempts": 0
+        },
+        "item": [
+            {
+                "deploymentId": "49ijxa",
+                "stageName": "latest",
+                "cacheClusterEnabled": false,
+                "cacheClusterSize": "0.5",
+                "cacheClusterStatus": "NOT_AVAILABLE",
+                "methodSettings": {
+                    "*/*": {
+                        "metricsEnabled": false,
+                        "loggingLevel": "INFO",
+                        "dataTraceEnabled": true,
+                        "throttlingBurstLimit": 2000,
+                        "throttlingRateLimit": 1000.0,
+                        "cachingEnabled": false,
+                        "cacheTtlInSeconds": 300,
+                        "cacheDataEncrypted": false,
+                        "requireAuthorizationForCacheControl": true,
+                        "unauthorizedCacheControlHeaderStrategy": "SUCCEED_WITH_RESPONSE_HEADER"
+                    }
+                },
+                "variables": {},
+                "tags": {
+                    "ENV": "DEV"
+                },
+                "createdDate": {
+                    "__class__": "datetime",
+                    "year": 2017,
+                    "month": 1,
+                    "day": 22,
+                    "hour": 6,
+                    "minute": 28,
+                    "second": 5,
+                    "microsecond": 0
+                },
+                "lastUpdatedDate": {
+                    "__class__": "datetime",
+                    "year": 2018,
+                    "month": 1,
+                    "day": 19,
+                    "hour": 11,
+                    "minute": 33,
+                    "second": 41,
+                    "microsecond": 0
+                }
+            },
+            {
+                "deploymentId": "49ijxa",
+                "stageName": "noop",
+                "cacheClusterEnabled": false,
+                "cacheClusterStatus": "NOT_AVAILABLE",
+                "methodSettings": {},
+                "createdDate": {
+                    "__class__": "datetime",
+                    "year": 2017,
+                    "month": 1,
+                    "day": 22,
+                    "hour": 6,
+                    "minute": 28,
+                    "second": 2,
+                    "microsecond": 0
+                },
+                "lastUpdatedDate": {
+                    "__class__": "datetime",
+                    "year": 2017,
+                    "month": 1,
+                    "day": 22,
+                    "hour": 6,
+                    "minute": 28,
+                    "second": 2,
+                    "microsecond": 0
+                }
+            }
+        ]
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_rest_stage_update/apigateway.GetStages_2.json b/tests/data/placebo/test_rest_stage_update/apigateway.GetStages_2.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_rest_stage_update/apigateway.GetStages_2.json
@@ -0,0 +1,47 @@
+{
+    "status_code": 200,
+    "data": {
+        "ResponseMetadata": {
+            "RequestId": "9c758f9b-fdfe-11e7-a190-a392413faa8a",
+            "HTTPStatusCode": 200,
+            "HTTPHeaders": {
+                "date": "Sat, 20 Jan 2018 16:26:04 GMT",
+                "content-type": "application/json",
+                "content-length": "233",
+                "connection": "keep-alive",
+                "x-amzn-requestid": "9c758f9b-fdfe-11e7-a190-a392413faa8a"
+            },
+            "RetryAttempts": 0
+        },
+        "item": [
+            {
+                "deploymentId": "r2pi2v",
+                "stageName": "prod",
+                "description": "Created by AWS Lambda",
+                "cacheClusterEnabled": false,
+                "cacheClusterStatus": "NOT_AVAILABLE",
+                "methodSettings": {},
+                "createdDate": {
+                    "__class__": "datetime",
+                    "year": 2017,
+                    "month": 4,
+                    "day": 5,
+                    "hour": 10,
+                    "minute": 35,
+                    "second": 32,
+                    "microsecond": 0
+                },
+                "lastUpdatedDate": {
+                    "__class__": "datetime",
+                    "year": 2017,
+                    "month": 4,
+                    "day": 5,
+                    "hour": 10,
+                    "minute": 35,
+                    "second": 32,
+                    "microsecond": 0
+                }
+            }
+        ]
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_rest_stage_update/apigateway.UpdateStage_1.json b/tests/data/placebo/test_rest_stage_update/apigateway.UpdateStage_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_rest_stage_update/apigateway.UpdateStage_1.json
@@ -0,0 +1,55 @@
+{
+    "status_code": 200,
+    "data": {
+        "ResponseMetadata": {
+            "RequestId": "9ca85f6e-fdfe-11e7-a190-a392413faa8a",
+            "HTTPStatusCode": 200,
+            "HTTPHeaders": {
+                "date": "Sat, 20 Jan 2018 16:26:05 GMT",
+                "content-type": "application/json",
+                "content-length": "507",
+                "connection": "keep-alive",
+                "x-amzn-requestid": "9ca85f6e-fdfe-11e7-a190-a392413faa8a"
+            },
+            "RetryAttempts": 0
+        },
+        "deploymentId": "49ijxa",
+        "stageName": "noop",
+        "cacheClusterEnabled": false,
+        "cacheClusterStatus": "NOT_AVAILABLE",
+        "methodSettings": {
+            "*/*": {
+                "metricsEnabled": false,
+                "loggingLevel": "INFO",
+                "dataTraceEnabled": true,
+                "throttlingBurstLimit": 5000,
+                "throttlingRateLimit": 10000.0,
+                "cachingEnabled": false,
+                "cacheTtlInSeconds": 300,
+                "cacheDataEncrypted": false,
+                "requireAuthorizationForCacheControl": true,
+                "unauthorizedCacheControlHeaderStrategy": "SUCCEED_WITH_RESPONSE_HEADER"
+            }
+        },
+        "createdDate": {
+            "__class__": "datetime",
+            "year": 2017,
+            "month": 1,
+            "day": 22,
+            "hour": 6,
+            "minute": 28,
+            "second": 2,
+            "microsecond": 0
+        },
+        "lastUpdatedDate": {
+            "__class__": "datetime",
+            "year": 2018,
+            "month": 1,
+            "day": 20,
+            "hour": 11,
+            "minute": 26,
+            "second": 5,
+            "microsecond": 0
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_rest_stage_update/apigateway.UpdateStage_2.json b/tests/data/placebo/test_rest_stage_update/apigateway.UpdateStage_2.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_rest_stage_update/apigateway.UpdateStage_2.json
@@ -0,0 +1,56 @@
+{
+    "status_code": 200,
+    "data": {
+        "ResponseMetadata": {
+            "RequestId": "9cd7858f-fdfe-11e7-8894-0512a6e285b7",
+            "HTTPStatusCode": 200,
+            "HTTPHeaders": {
+                "date": "Sat, 20 Jan 2018 16:26:05 GMT",
+                "content-type": "application/json",
+                "content-length": "545",
+                "connection": "keep-alive",
+                "x-amzn-requestid": "9cd7858f-fdfe-11e7-8894-0512a6e285b7"
+            },
+            "RetryAttempts": 0
+        },
+        "deploymentId": "r2pi2v",
+        "stageName": "prod",
+        "description": "Created by AWS Lambda",
+        "cacheClusterEnabled": false,
+        "cacheClusterStatus": "NOT_AVAILABLE",
+        "methodSettings": {
+            "*/*": {
+                "metricsEnabled": false,
+                "loggingLevel": "INFO",
+                "dataTraceEnabled": true,
+                "throttlingBurstLimit": 5000,
+                "throttlingRateLimit": 10000.0,
+                "cachingEnabled": false,
+                "cacheTtlInSeconds": 300,
+                "cacheDataEncrypted": false,
+                "requireAuthorizationForCacheControl": true,
+                "unauthorizedCacheControlHeaderStrategy": "SUCCEED_WITH_RESPONSE_HEADER"
+            }
+        },
+        "createdDate": {
+            "__class__": "datetime",
+            "year": 2017,
+            "month": 4,
+            "day": 5,
+            "hour": 10,
+            "minute": 35,
+            "second": 32,
+            "microsecond": 0
+        },
+        "lastUpdatedDate": {
+            "__class__": "datetime",
+            "year": 2018,
+            "month": 1,
+            "day": 20,
+            "hour": 11,
+            "minute": 26,
+            "second": 5,
+            "microsecond": 0
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/test_apigw.py b/tests/test_apigw.py
new file mode 100644
--- /dev/null
+++ b/tests/test_apigw.py
@@ -0,0 +1,147 @@
+# Copyright 2018 Capital One Services, LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+from __future__ import absolute_import, division, print_function, unicode_literals
+
+from .common import BaseTest
+
+
+class TestRestAccount(BaseTest):
+
+    def test_rest_api_update(self):
+        session_factory = self.replay_flight_data('test_rest_account_update')
+        log_role = 'arn:aws:iam::644160558196:role/OtherApiGatewayLogger'
+        p = self.load_policy({
+            'name': 'update-account',
+            'resource': 'rest-account',
+            'actions': [
+                {'type': 'update',
+                 'patch': [
+                     {'op': 'replace',
+                      'path': '/cloudwatchRoleArn',
+                      'value': log_role}
+                 ]}]}, session_factory=session_factory)
+        before_account = p.resource_manager._get_account()
+        self.assertEqual(
+            before_account['cloudwatchRoleArn'],
+            'arn:aws:iam::644160558196:role/ApiGwLogger')
+
+        resources = p.run()
+        self.assertEqual(len(resources), 1)
+
+        after_account = p.resource_manager._get_account()
+        self.assertEqual(
+            after_account['cloudwatchRoleArn'], log_role)
+
+
+class TestRestResource(BaseTest):
+
+    def test_rest_resource_query(self):
+        session_factory = self.replay_flight_data(
+            'test_rest_resource_resource')
+        p = self.load_policy({
+            'name': 'all-rest-resources',
+            'resource': 'rest-resource',
+        }, session_factory=session_factory)
+        resources = p.run()
+        self.assertEqual(len(resources), 4)
+        self.assertEqual(
+            sorted([(r['restApiId'], r['path']) for r in resources]),
+            [('5xhc1cnb7h', '/'),
+             ('5xhc1cnb7h', '/{proxy+}'),
+             ('rtmgxfiay5', '/'),
+             ('rtmgxfiay5', '/glenns_test')])
+
+    def test_rest_resource_method_update(self):
+        session_factory = self.replay_flight_data(
+            'test_rest_resource_method_update')
+        p = self.load_policy({
+            'name': 'rest-method-iam',
+            'resource': 'rest-resource',
+            'filters': [
+                {'type': 'rest-method',
+                 'key': 'authorizationType',
+                 'value': 'AWS_IAM',
+                 'op': 'not-equal'}],
+            'actions': [{
+                'type': 'update-method',
+                'patch': [{
+                    'op': 'replace',
+                    'path': '/authorizationType',
+                    'value': 'AWS_IAM'},
+                ]}],
+        }, session_factory=session_factory)
+        resources = p.run()
+        self.assertEqual(len(resources), 1)
+        methods = []
+        for r in resources:
+            methods.extend(r['c7n-matched-resource-methods'])
+        resource = resources.pop()
+
+        m = methods.pop()
+        client = session_factory().client('apigateway')
+
+        method = client.get_method(
+            restApiId=m['restApiId'],
+            resourceId=m['resourceId'],
+            httpMethod=m['httpMethod'])
+        self.assertEqual(method['authorizationType'], 'AWS_IAM')
+
+
+class TestRestStage(BaseTest):
+
+    def test_rest_stage_resource(self):
+        session_factory = self.replay_flight_data('test_rest_stage')
+        p = self.load_policy({
+            'name': 'all-rest-stages',
+            'resource': 'rest-stage',
+            'filters': [
+                {'tag:ENV': "DEV"}
+                ]}, session_factory=session_factory)
+        resources = p.run()
+        self.assertEqual(len(resources), 1)
+        self.assertEqual(resources[0]['stageName'], 'latest')
+
+    def test_rest_stage_update(self):
+        session_factory = self.replay_flight_data('test_rest_stage_update')
+        p = self.load_policy({
+            'name': 'rest-stage-update',
+            'resource': 'rest-stage',
+            'filters': [
+                {'methodSettings."*/*".loggingLevel': 'absent'}],
+            'actions': [
+                {'type': 'update',
+                 'patch': [
+                     {'op': 'replace',
+                      'path': '/*/*/logging/dataTrace',
+                      'value': 'true'},
+                     {'op': 'replace',
+                      'path': '/*/*/logging/loglevel',
+                      'value': 'info'}
+                 ]},
+            ]}, session_factory=session_factory)
+
+        resources = p.run()
+        self.assertEqual(len(resources), 2)
+        client = session_factory().client('apigateway')
+
+        stage = client.get_stage(
+            restApiId=resources[0]['restApiId'],
+            stageName=resources[0]['stageName'])
+
+        found = False
+        for k, m in stage.get('methodSettings', {}).items():
+            found = True
+            self.assertEqual(m['loggingLevel'], 'INFO')
+            self.assertEqual(m['dataTraceEnabled'], True)
+        self.assertTrue(found)
diff --git a/tests/zpill.py b/tests/zpill.py
--- a/tests/zpill.py
+++ b/tests/zpill.py
@@ -240,10 +240,10 @@ def assertJmes(self, expr, instance, expected):
     def cleanUp(self):
         pass
 
-    def record_flight_data(self, test_case, zdata=False):
+    def record_flight_data(self, test_case, zdata=False, augment=False):
         self.recording = True
-        if not zdata:
-            test_dir = os.path.join(self.placebo_dir, test_case)
+        test_dir = os.path.join(self.placebo_dir, test_case)
+        if not (zdata or augment):
             if os.path.exists(test_dir):
                 shutil.rmtree(test_dir)
             os.makedirs(test_dir)