Skip to content

Commit

Permalink
Merge branch 'eaglepointpartners/master' into master
Browse files Browse the repository at this point in the history
PR #1928

* eaglepointpartners/master:
  Add changelog for tf websocket support
  Revert formating changes
  Use function name as output for handler names
  Fixed prchecks (#1)
  Added tests for websockets_api terraform packaging
  Removed incorrect comment from TerraformGenerator
  Fixed terraform package websockets lambda invoke permission resource definition
  Fixed integration_uri
  Adds terraform packaging support for websocketapi
  • Loading branch information
jamesls committed May 17, 2022
2 parents 8e51ee6 + 896f1aa commit d6a9d8d
Show file tree
Hide file tree
Showing 3 changed files with 368 additions and 20 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"type": "enhancement",
"category": "Websockets",
"description": "Add support for WebSockets API Terraform packaging (#1670)"
}
210 changes: 197 additions & 13 deletions chalice/package.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
from chalice.utils import (
OSUtils, UI, serialize_to_json, to_cfn_resource_name
)
from chalice.awsclient import TypedAWSClient # noqa
from chalice.awsclient import TypedAWSClient # noqa
from chalice.config import Config # noqa
from chalice.deploy import models
from chalice.deploy.appgraph import ApplicationGraphBuilder, DependencyBuilder
Expand Down Expand Up @@ -122,7 +122,6 @@ def construct_resources(self, config, chalice_stage_name):


class TemplateGenerator(object):

template_file = None # type: str

def __init__(self, config, options):
Expand Down Expand Up @@ -858,17 +857,202 @@ def _generate_managediamrole(self, resource, template):
'role': '${aws_iam_role.%s.id}' % resource.resource_name,
}

def _add_websocket_lambda_integration(
self, websocket_api_id, websocket_handler, template):
# type: (str, str, Dict[str, Any]) -> None
websocket_handler_function_name = \
"${aws_lambda_function.%s.function_name}" % websocket_handler
resource_definition = {
'api_id': websocket_api_id,
'connection_type': 'INTERNET',
'content_handling_strategy': 'CONVERT_TO_TEXT',
'integration_type': 'AWS_PROXY',
'integration_uri': self._arnref(
"arn:%(partition)s:apigateway:%(region)s"
":lambda:path/2015-03-31/functions/arn"
":%(partition)s:lambda:%(region)s"
":%(account_id)s:function"
":%(websocket_handler_function_name)s/invocations",
websocket_handler_function_name=websocket_handler_function_name
)
}
template['resource'].setdefault(
'aws_apigatewayv2_integration', {}
)['%s_api_integration' % websocket_handler] = resource_definition

def _add_websocket_lambda_invoke_permission(
self, websocket_api_id, websocket_handler, template):
# type: (str, str, Dict[str, Any]) -> None
websocket_handler_function_name = \
"${aws_lambda_function.%s.function_name}" % websocket_handler
resource_definition = {
"function_name": websocket_handler_function_name,
"action": "lambda:InvokeFunction",
"principal": self._options.service_principal('apigateway'),
"source_arn": self._arnref(
"arn:%(partition)s:execute-api"
":%(region)s:%(account_id)s"
":%(websocket_api_id)s/*",
websocket_api_id=websocket_api_id
)
}
template['resource'].setdefault(
'aws_lambda_permission', {}
)['%s_invoke_permission' % websocket_handler] = resource_definition

def _add_websockets_route(self, websocket_api_id, route_key, template):
# type: (str, str, Dict[str, Any]) -> str
integration_target = {
'$connect': 'integrations/${aws_apigatewayv2_integration'
'.websocket_connect_api_integration.id}',
'$disconnect': 'integrations/${aws_apigatewayv2_integration'
'.websocket_disconnect_api_integration.id}',
}.get(route_key,
'integrations/${aws_apigatewayv2_integration'
'.websocket_message_api_integration.id}')

route_resource_name = {
'$connect': 'websocket_connect_route',
'$disconnect': 'websocket_disconnect_route',
'$default': 'websocket_message_route',
}.get(route_key, 'message')

template['resource'].setdefault(
'aws_apigatewayv2_route', {}
)[route_resource_name] = {
"api_id": websocket_api_id,
"route_key": route_key,
"target": integration_target
}
return route_resource_name

def _add_websocket_domain_name(self, websocket_api_id, resource, template):
# type: (str, models.WebsocketAPI, Dict[str, Any]) -> None
if resource.domain_name is None:
return
domain_name = resource.domain_name

ws_domain_name_definition = {
"domain_name": domain_name.domain_name,
"domain_name_configuration": {
'certificate_arn': domain_name.certificate_arn,
'endpoint_type': 'REGIONAL',
},
}

if domain_name.tags:
ws_domain_name_definition['tags'] = domain_name.tags

template['resource'].setdefault(
'aws_apigatewayv2_domain_name', {}
)[domain_name.resource_name] = ws_domain_name_definition

template['resource'].setdefault(
'aws_apigatewayv2_api_mapping', {}
)[domain_name.resource_name + '_mapping'] = {
"api_id": websocket_api_id,
"domain_name": "${aws_apigatewayv2_domain_name.%s.id}" %
domain_name.resource_name,
"stage": "${aws_apigatewayv2_stage.websocket_api_stage.id}",
}

def _inject_websocketapi_outputs(self, websocket_api_id, template):
# type: (str, Dict[str, Any]) -> None
aws_lambda_functions = template['resource']['aws_lambda_function']
stage_name = \
template['resource']['aws_apigatewayv2_stage'][
'websocket_api_stage'][
'name']
output = template.setdefault('output', {})
output['WebsocketAPIId'] = {"value": websocket_api_id}

if 'websocket_connect' in aws_lambda_functions:
output['WebsocketConnectHandlerArn'] = {
"value": "${aws_lambda_function.websocket_connect.arn}"}
output['WebsocketConnectHandlerName'] = {
"value": (
"${aws_lambda_function.websocket_connect.function_name}")}
if 'websocket_message' in aws_lambda_functions:
output['WebsocketMessageHandlerArn'] = {
"value": "${aws_lambda_function.websocket_message.arn}"}
output['WebsocketMessageHandlerName'] = {
"value": (
"${aws_lambda_function.websocket_message.function_name}")}
if 'websocket_disconnect' in aws_lambda_functions:
output['WebsocketDisconnectHandlerArn'] = {
"value": "${aws_lambda_function.websocket_disconnect.arn}"}
output['WebsocketDisconnectHandlerName'] = {
"value": (
"${aws_lambda_function.websocket_disconnect"
".function_name}")}

output['WebsocketConnectEndpointURL'] = {
"value": (
'wss://%(websocket_api_id)s.execute-api'
# The api_gateway_stage is filled in when
# the template is built.
'.${data.aws_region.chalice.name}'
'.amazonaws.com/%(stage_name)s/'
) % {
"stage_name": stage_name,
"websocket_api_id": websocket_api_id
}
}

def _generate_websocketapi(self, resource, template):
# type: (models.WebsocketAPI, Dict[str, Any]) -> None

message = (
"Unable to package chalice apps that use experimental "
"Websocket decorators. Terraform AWS Provider "
"support for websocket is pending see "
"https://git.io/fj9X8 for details and progress. "
"You can deploy this app using `chalice deploy`."
)
raise NotImplementedError(message)
ws_definition = {
'name': resource.name,
'route_selection_expression': '$request.body.action',
'protocol_type': 'WEBSOCKET',
}

template['resource'].setdefault('aws_apigatewayv2_api', {})[
resource.resource_name] = ws_definition

websocket_api_id = "${aws_apigatewayv2_api.%s.id}" % \
resource.resource_name

websocket_handlers = [
'websocket_connect',
'websocket_message',
'websocket_disconnect',
]

for handler in websocket_handlers:
if handler in template['resource']['aws_lambda_function']:
self._add_websocket_lambda_integration(websocket_api_id,
handler, template)
self._add_websocket_lambda_invoke_permission(websocket_api_id,
handler, template)

route_resource_names = []
for route_key in resource.routes:
route_resource_name = self._add_websockets_route(websocket_api_id,
route_key,
template)
route_resource_names.append(route_resource_name)

template['resource'].setdefault(
'aws_apigatewayv2_deployment', {}
)['websocket_api_deployment'] = {
"api_id": websocket_api_id,
"depends_on": ["aws_apigatewayv2_route.%s" % name for name in
route_resource_names]
}

template['resource'].setdefault(
'aws_apigatewayv2_stage', {}
)['websocket_api_stage'] = {
"api_id": websocket_api_id,
"deployment_id": ("${aws_apigatewayv2_deployment"
".websocket_api_deployment.id}"),
"name": resource.api_gateway_stage
}

self._add_websocket_domain_name(websocket_api_id, resource, template)
self._inject_websocketapi_outputs(websocket_api_id, template)

def _generate_s3bucketnotification(self, resource, template):
# type: (models.S3BucketNotification, Dict[str, Any]) -> None
Expand Down Expand Up @@ -1026,9 +1210,9 @@ def _generate_lambdalayer(self, resource, template):
template['resource'].setdefault(
"aws_lambda_layer_version", {})[
resource.resource_name] = {
'layer_name': resource.layer_name,
'compatible_runtimes': [resource.runtime],
'filename': resource.deployment_package.filename,
'layer_name': resource.layer_name,
'compatible_runtimes': [resource.runtime],
'filename': resource.deployment_package.filename,
}
self._chalice_layer = resource.resource_name

Expand Down
Loading

0 comments on commit d6a9d8d

Please sign in to comment.