Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
62 commits
Select commit Hold shift + click to select a range
8f6b8fb
Initial setup and work
viksrivat Jun 17, 2019
f457401
Add RestApi tests
viksrivat Jun 19, 2019
8112cf4
Remove Stage Name/Variable from pr
viksrivat Jun 20, 2019
5050dd8
Run make pr
viksrivat Jun 20, 2019
bf5a083
Add Rest Api integration test
viksrivat Jun 20, 2019
6eb226a
Remove AWS::Stage from current pr
viksrivat Jun 20, 2019
7c5823d
Update integration test to only use CloudFormation types
viksrivat Jun 20, 2019
4966513
Re-run Travis Ci
viksrivat Jun 24, 2019
2c38ede
Update Uri to BodyS3Location
viksrivat Jun 24, 2019
9d3d137
Add docstring to extract_swagger_api
viksrivat Jun 24, 2019
b6d9b62
Fix Merge conflicts with dev and branch
viksrivat Jun 24, 2019
f8ac623
Update AWS::ApiGateway::Stage tests
viksrivat Jun 24, 2019
54bda7f
Test local file in body fail and clean tests
viksrivat Jun 24, 2019
9b06696
Initial Attempt
viksrivat Jun 25, 2019
145162e
Refactor Sam API provider
viksrivat Jun 25, 2019
5615163
Cleanup Refactoring
viksrivat Jun 25, 2019
752fba8
Merge pull request #1 from viksrivat/feature/cloud_formation_api_prov…
viksrivat Jun 25, 2019
1157284
Update AWS::ApiGateway::Stage tests
viksrivat Jun 24, 2019
5d1dfc7
Fix merge conflict
viksrivat Jun 25, 2019
7ed267c
Remove .vscode
viksrivat Jun 25, 2019
681ede3
Re-run travis
viksrivat Jun 25, 2019
a0f5837
Restructure the information Api has the way it passed through
viksrivat Jun 26, 2019
a3ca29f
Merge pull request #2 from viksrivat/cleanup/update_common_stage_feat…
viksrivat Jun 26, 2019
0078786
Update common
viksrivat Jun 26, 2019
9879105
Merge branch 'feature/cloud_formation_stage_support' of github.com:vi…
viksrivat Jun 26, 2019
a9117ad
Update AbstractParserProvider documentation
viksrivat Jun 26, 2019
997845c
Initial Refactor refactor
viksrivat Jun 26, 2019
de65fd0
Fix tests and run make pr
viksrivat Jun 26, 2019
df243a7
Fix merge conflict
viksrivat Jun 27, 2019
4555f11
Reorganize classes
viksrivat Jun 27, 2019
420a842
Update docstrings
viksrivat Jun 27, 2019
53f4b7e
Update the SAMBaseProvider
viksrivat Jun 27, 2019
856326e
Fix merge conflict
viksrivat Jun 27, 2019
6803a0b
Restructure to seperate SAM and CF providers seperately + tests
viksrivat Jun 27, 2019
837137f
Remove transform flag in the rest-api
viksrivat Jun 27, 2019
4130ea2
Remove BaseProvider object
viksrivat Jun 27, 2019
ec833dc
Fix merge conflict
viksrivat Jun 27, 2019
693692b
Update with style
viksrivat Jun 27, 2019
19eaebe
Update tests and code with comments
viksrivat Jun 27, 2019
d226f53
Fix merge conflicts with comments from feature/cfn_rest_api
viksrivat Jun 27, 2019
23467de
Update tests with comments
viksrivat Jun 28, 2019
7e25c02
Fix merge conflict tests
viksrivat Jun 28, 2019
066b236
Trigger
viksrivat Jun 28, 2019
8987db2
Remove noapievent
viksrivat Jun 28, 2019
4a792bf
Update tests
viksrivat Jun 28, 2019
644b851
Update merge conflictz
viksrivat Jun 28, 2019
238742a
Fix bug with api
viksrivat Jun 29, 2019
7662f24
Fixed comments
viksrivat Jul 8, 2019
cc21095
Fix merge with rest_api
viksrivat Jul 9, 2019
b50e644
Remove Invalid Integration Test
viksrivat Jul 9, 2019
a01aee2
Run make pr
viksrivat Jul 9, 2019
ae97479
Merge branch 'develop' into feature/cloud_formation_stage_support
viksrivat Jul 11, 2019
fe706c7
Fix merge issues and run make pr
viksrivat Jul 11, 2019
9ea9a67
Update Route with comments
viksrivat Jul 15, 2019
8353724
Fix Route collector doc
viksrivat Jul 15, 2019
8b096d3
Update to run AWS::ApiGateway::Stage Integration tests
viksrivat Jul 15, 2019
fcfc10e
Update based on comments
viksrivat Jul 22, 2019
f78cbd0
Update RouteCollector to collect and create Api
viksrivat Jul 23, 2019
df8186f
Cleanup ApiCollector
viksrivat Jul 23, 2019
be44710
Update Based on Comments
viksrivat Jul 25, 2019
12ce392
Update tests
viksrivat Jul 25, 2019
e89cc33
Update ApiCollector description
viksrivat Jul 25, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
230 changes: 98 additions & 132 deletions samcli/commands/local/lib/api_collector.py
Original file line number Diff line number Diff line change
@@ -1,207 +1,173 @@
"""
Class to store the API configurations in the SAM Template. This class helps store both implicit and explicit
APIs in a standardized format
routes in a standardized format
"""

import logging
from collections import namedtuple
from collections import defaultdict

from six import string_types

from samcli.local.apigw.local_apigw_service import Route
from samcli.commands.local.lib.provider import Api

LOG = logging.getLogger(__name__)


class ApiCollector(object):
# Properties of each API. The structure is quite similar to the properties of AWS::Serverless::Api resource.
# This is intentional because it allows us to easily extend this class to support future properties on the API.
# We will store properties of Implicit APIs also in this format which converges the handling of implicit & explicit
# APIs.
Properties = namedtuple("Properties", ["apis", "binary_media_types", "cors", "stage_name", "stage_variables"])

def __init__(self):
# API properties stored per resource. Key is the LogicalId of the AWS::Serverless::Api resource and
# value is the properties
self.by_resource = {}
# Route properties stored per resource.
self._route_per_resource = defaultdict(list)

# processed values to be set before creating the api
self._routes = []
self.binary_media_types_set = set()
self.stage_name = None
self.stage_variables = None

def __iter__(self):
"""
Iterator to iterate through all the APIs stored in the collector. In each iteration, this yields the
LogicalId of the API resource and a list of APIs available in this resource.

Iterator to iterate through all the routes stored in the collector. In each iteration, this yields the
LogicalId of the route resource and a list of routes available in this resource.
Yields
-------
str
LogicalID of the AWS::Serverless::Api resource
LogicalID of the AWS::Serverless::Api or AWS::ApiGateway::RestApi resource
list samcli.commands.local.lib.provider.Api
List of the API available in this resource along with additional configuration like binary media types.
"""

for logical_id, _ in self.by_resource.items():
yield logical_id, self._get_apis_with_config(logical_id)
for logical_id, _ in self._route_per_resource.items():
yield logical_id, self._get_routes(logical_id)

def add_apis(self, logical_id, apis):
def add_routes(self, logical_id, routes):
"""
Stores the given APIs tagged under the given logicalId

Stores the given routes tagged under the given logicalId
Parameters
----------
logical_id : str
LogicalId of the AWS::Serverless::Api resource

apis : list of samcli.commands.local.lib.provider.Api
List of APIs available in this resource
LogicalId of the AWS::Serverless::Api or AWS::ApiGateway::RestApi resource
routes : list of samcli.commands.local.agiw.local_apigw_service.Route
List of routes available in this resource
"""
properties = self._get_properties(logical_id)
properties.apis.extend(apis)
self._get_routes(logical_id).extend(routes)

def add_binary_media_types(self, logical_id, binary_media_types):
def _get_routes(self, logical_id):
"""
Stores the binary media type configuration for the API with given logical ID

Returns the properties of resource with given logical ID. If a resource is not found, then it returns an
empty data.
Parameters
----------
logical_id : str
LogicalId of the AWS::Serverless::Api resource

binary_media_types : list of str
List of binary media types supported by this resource

"""
properties = self._get_properties(logical_id)

binary_media_types = binary_media_types or []
for value in binary_media_types:
normalized_value = self._normalize_binary_media_type(value)

# If the value is not supported, then just skip it.
if normalized_value:
properties.binary_media_types.add(normalized_value)
else:
LOG.debug("Unsupported data type of binary media type value of resource '%s'", logical_id)

def add_stage_name(self, logical_id, stage_name):
Logical ID of the resource
Returns
-------
samcli.commands.local.lib.Routes
Properties object for this resource.
"""
Stores the stage name for the API with the given local ID

Parameters
----------
logical_id : str
LogicalId of the AWS::Serverless::Api resource
return self._route_per_resource[logical_id]

stage_name : str
The stage_name string
@property
def routes(self):
return self._routes if self._routes else self.all_routes()

"""
properties = self._get_properties(logical_id)
properties = properties._replace(stage_name=stage_name)
self._set_properties(logical_id, properties)
@routes.setter
def routes(self, routes):
self._routes = routes

def add_stage_variables(self, logical_id, stage_variables):
def all_routes(self):
"""
Stores the stage variables for the API with the given local ID

Parameters
----------
logical_id : str
LogicalId of the AWS::Serverless::Api resource

stage_variables : dict
A dictionary containing stage variables.
Gets all the routes within the _route_per_resource

Return
-------
All the routes within the _route_per_resource
"""
properties = self._get_properties(logical_id)
properties = properties._replace(stage_variables=stage_variables)
self._set_properties(logical_id, properties)
routes = []
for logical_id in self._route_per_resource.keys():
routes.extend(self._get_routes(logical_id))
return routes

def _get_apis_with_config(self, logical_id):
def get_api(self):
"""
Returns the list of APIs in this resource along with other extra configuration such as binary media types,
cors etc. Additional configuration is merged directly into the API data because these properties, although
defined globally, actually apply to each API.
Creates the api using the parts from the ApiCollector. The routes are also deduped so that there is no
duplicate routes with the same function name, path, but different method.

Parameters
----------
logical_id : str
Logical ID of the resource to fetch data for
The normalised_routes are the routes that have been processed. By default, this will get all the routes.
However, it can be changed to override the default value of normalised routes such as in SamApiProvider

Returns
Return
-------
list of samcli.commands.local.lib.provider.Api
List of APIs with additional configurations for the resource with given logicalId. If there are no APIs,
then it returns an empty list
An Api object with all the properties
"""
api = Api()
api.routes = self.dedupe_function_routes(self.routes)
api.binary_media_types_set = self.binary_media_types_set
api.stage_name = self.stage_name
api.stage_variables = self.stage_variables
return api

properties = self._get_properties(logical_id)
@staticmethod
def dedupe_function_routes(routes):
"""
Remove duplicate routes that have the same function_name and method

# These configs need to be applied to each API
binary_media = sorted(list(properties.binary_media_types)) # Also sort the list to keep the ordering stable
cors = properties.cors
stage_name = properties.stage_name
stage_variables = properties.stage_variables
route: list(Route)
List of Routes

result = []
for api in properties.apis:
# Create a copy of the API with updated configuration
updated_api = api._replace(binary_media_types=binary_media,
cors=cors,
stage_name=stage_name,
stage_variables=stage_variables)
result.append(updated_api)
Return
-------
A list of routes without duplicate routes with the same function_name and method
"""
grouped_routes = {}

return result
for route in routes:
key = "{}-{}".format(route.function_name, route.path)
config = grouped_routes.get(key, None)
methods = route.methods
if config:
methods += config.methods
sorted_methods = sorted(methods)
grouped_routes[key] = Route(function_name=route.function_name, path=route.path, methods=sorted_methods)
return list(grouped_routes.values())

def _get_properties(self, logical_id):
def add_binary_media_types(self, logical_id, binary_media_types):
"""
Returns the properties of resource with given logical ID. If a resource is not found, then it returns an
empty data.

Stores the binary media type configuration for the API with given logical ID
Parameters
----------
logical_id : str
Logical ID of the resource

Returns
-------
samcli.commands.local.lib.sam_api_provider.ApiCollector.Properties
Properties object for this resource.
"""

if logical_id not in self.by_resource:
self.by_resource[logical_id] = self.Properties(apis=[],
# Use a set() to be able to easily de-dupe
binary_media_types=set(),
cors=None,
stage_name=None,
stage_variables=None)
logical_id : str
LogicalId of the AWS::Serverless::Api resource

return self.by_resource[logical_id]
api: samcli.commands.local.lib.provider.Api
Instance of the Api which will save all the api configurations

def _set_properties(self, logical_id, properties):
binary_media_types : list of str
List of binary media types supported by this resource
"""
Sets the properties of resource with given logical ID. If a resource is not found, it does nothing

Parameters
----------
logical_id : str
Logical ID of the resource
properties : samcli.commands.local.lib.sam_api_provider.ApiCollector.Properties
Properties object for this resource.
"""
binary_media_types = binary_media_types or []
for value in binary_media_types:
normalized_value = self.normalize_binary_media_type(value)

if logical_id in self.by_resource:
self.by_resource[logical_id] = properties
# If the value is not supported, then just skip it.
if normalized_value:
self.binary_media_types_set.add(normalized_value)
else:
LOG.debug("Unsupported data type of binary media type value of resource '%s'", logical_id)

@staticmethod
def _normalize_binary_media_type(value):
def normalize_binary_media_type(value):
"""
Converts binary media types values to the canonical format. Ex: image~1gif -> image/gif. If the value is not
a string, then this method just returns None

Parameters
----------
value : str
Value to be normalized

Returns
-------
str or None
Expand Down
35 changes: 17 additions & 18 deletions samcli/commands/local/lib/api_provider.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
"""Class that provides Apis from a SAM Template"""
"""Class that provides the Api with a list of routes from a Template"""

import logging

from samcli.commands.local.lib.cfn_base_api_provider import CfnBaseApiProvider
from samcli.commands.local.lib.api_collector import ApiCollector
from samcli.commands.local.lib.cfn_api_provider import CfnApiProvider
from samcli.commands.local.lib.cfn_base_api_provider import CfnBaseApiProvider
from samcli.commands.local.lib.provider import AbstractApiProvider
from samcli.commands.local.lib.sam_base_provider import SamBaseProvider
from samcli.commands.local.lib.sam_api_provider import SamApiProvider
from samcli.commands.local.lib.cfn_api_provider import CfnApiProvider
from samcli.commands.local.lib.sam_base_provider import SamBaseProvider

LOG = logging.getLogger(__name__)

Expand All @@ -16,7 +16,7 @@ class ApiProvider(AbstractApiProvider):

def __init__(self, template_dict, parameter_overrides=None, cwd=None):
"""
Initialize the class with SAM template data. The template_dict (SAM Templated) is assumed
Initialize the class with template data. The template_dict is assumed
to be valid, normalized and a dictionary. template_dict should be normalized by running any and all
pre-processing before passing to this class.
This class does not perform any syntactic validation of the template.
Expand All @@ -27,7 +27,7 @@ def __init__(self, template_dict, parameter_overrides=None, cwd=None):
Parameters
----------
template_dict : dict
SAM Template as a dictionary
Template as a dictionary

cwd : str
Optional working directory with respect to which we will resolve relative path to Swagger file
Expand All @@ -39,23 +39,22 @@ def __init__(self, template_dict, parameter_overrides=None, cwd=None):

# Store a set of apis
self.cwd = cwd
self.apis = self._extract_apis(self.resources)

LOG.debug("%d APIs found in the template", len(self.apis))
self.api = self._extract_api(self.resources)
self.routes = self.api.routes
LOG.debug("%d APIs found in the template", len(self.routes))

def get_all(self):
"""
Yields all the Lambda functions with Api Events available in the SAM Template.
Yields all the Apis in the current Provider

:yields Api: namedtuple containing the Api information
:yields api: an Api object with routes and properties
"""

for api in self.apis:
yield api
yield self.api

def _extract_apis(self, resources):
def _extract_api(self, resources):
"""
Extracts all the Apis by running through the one providers. The provider that has the first type matched
Extracts all the routes by running through the one providers. The provider that has the first type matched
will be run across all the resources

Parameters
Expand All @@ -64,12 +63,12 @@ def _extract_apis(self, resources):
The dictionary containing the different resources within the template
Returns
---------
list of Apis extracted from the resources
An Api from the parsed template
"""
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there is not modification in this method. It is just returning the api that is 'collected/parsed'

collector = ApiCollector()
provider = self.find_api_provider(resources)
apis = provider.extract_resource_api(resources, collector, cwd=self.cwd)
return self.normalize_apis(apis)
provider.extract_resources(resources, collector, cwd=self.cwd)
return collector.get_api()

@staticmethod
def find_api_provider(resources):
Expand Down
Loading