|
18 | 18 | from chalice.utils import ( |
19 | 19 | OSUtils, UI, serialize_to_json, to_cfn_resource_name |
20 | 20 | ) |
21 | | -from chalice.awsclient import TypedAWSClient # noqa |
| 21 | +from chalice.awsclient import TypedAWSClient # noqa |
22 | 22 | from chalice.config import Config # noqa |
23 | 23 | from chalice.deploy import models |
24 | 24 | from chalice.deploy.appgraph import ApplicationGraphBuilder, DependencyBuilder |
@@ -122,7 +122,6 @@ def construct_resources(self, config, chalice_stage_name): |
122 | 122 |
|
123 | 123 |
|
124 | 124 | class TemplateGenerator(object): |
125 | | - |
126 | 125 | template_file = None # type: str |
127 | 126 |
|
128 | 127 | def __init__(self, config, options): |
@@ -858,17 +857,202 @@ def _generate_managediamrole(self, resource, template): |
858 | 857 | 'role': '${aws_iam_role.%s.id}' % resource.resource_name, |
859 | 858 | } |
860 | 859 |
|
| 860 | + def _add_websocket_lambda_integration( |
| 861 | + self, websocket_api_id, websocket_handler, template): |
| 862 | + # type: (str, str, Dict[str, Any]) -> None |
| 863 | + websocket_handler_function_name = \ |
| 864 | + "${aws_lambda_function.%s.function_name}" % websocket_handler |
| 865 | + resource_definition = { |
| 866 | + 'api_id': websocket_api_id, |
| 867 | + 'connection_type': 'INTERNET', |
| 868 | + 'content_handling_strategy': 'CONVERT_TO_TEXT', |
| 869 | + 'integration_type': 'AWS_PROXY', |
| 870 | + 'integration_uri': self._arnref( |
| 871 | + "arn:%(partition)s:apigateway:%(region)s" |
| 872 | + ":lambda:path/2015-03-31/functions/arn" |
| 873 | + ":%(partition)s:lambda:%(region)s" |
| 874 | + ":%(account_id)s:function" |
| 875 | + ":%(websocket_handler_function_name)s/invocations", |
| 876 | + websocket_handler_function_name=websocket_handler_function_name |
| 877 | + ) |
| 878 | + } |
| 879 | + template['resource'].setdefault( |
| 880 | + 'aws_apigatewayv2_integration', {} |
| 881 | + )['%s_api_integration' % websocket_handler] = resource_definition |
| 882 | + |
| 883 | + def _add_websocket_lambda_invoke_permission( |
| 884 | + self, websocket_api_id, websocket_handler, template): |
| 885 | + # type: (str, str, Dict[str, Any]) -> None |
| 886 | + websocket_handler_function_name = \ |
| 887 | + "${aws_lambda_function.%s.function_name}" % websocket_handler |
| 888 | + resource_definition = { |
| 889 | + "function_name": websocket_handler_function_name, |
| 890 | + "action": "lambda:InvokeFunction", |
| 891 | + "principal": self._options.service_principal('apigateway'), |
| 892 | + "source_arn": self._arnref( |
| 893 | + "arn:%(partition)s:execute-api" |
| 894 | + ":%(region)s:%(account_id)s" |
| 895 | + ":%(websocket_api_id)s/*", |
| 896 | + websocket_api_id=websocket_api_id |
| 897 | + ) |
| 898 | + } |
| 899 | + template['resource'].setdefault( |
| 900 | + 'aws_lambda_permission', {} |
| 901 | + )['%s_invoke_permission' % websocket_handler] = resource_definition |
| 902 | + |
| 903 | + def _add_websockets_route(self, websocket_api_id, route_key, template): |
| 904 | + # type: (str, str, Dict[str, Any]) -> str |
| 905 | + integration_target = { |
| 906 | + '$connect': 'integrations/${aws_apigatewayv2_integration' |
| 907 | + '.websocket_connect_api_integration.id}', |
| 908 | + '$disconnect': 'integrations/${aws_apigatewayv2_integration' |
| 909 | + '.websocket_disconnect_api_integration.id}', |
| 910 | + }.get(route_key, |
| 911 | + 'integrations/${aws_apigatewayv2_integration' |
| 912 | + '.websocket_message_api_integration.id}') |
| 913 | + |
| 914 | + route_resource_name = { |
| 915 | + '$connect': 'websocket_connect_route', |
| 916 | + '$disconnect': 'websocket_disconnect_route', |
| 917 | + '$default': 'websocket_message_route', |
| 918 | + }.get(route_key, 'message') |
| 919 | + |
| 920 | + template['resource'].setdefault( |
| 921 | + 'aws_apigatewayv2_route', {} |
| 922 | + )[route_resource_name] = { |
| 923 | + "api_id": websocket_api_id, |
| 924 | + "route_key": route_key, |
| 925 | + "target": integration_target |
| 926 | + } |
| 927 | + return route_resource_name |
| 928 | + |
| 929 | + def _add_websocket_domain_name(self, websocket_api_id, resource, template): |
| 930 | + # type: (str, models.WebsocketAPI, Dict[str, Any]) -> None |
| 931 | + if resource.domain_name is None: |
| 932 | + return |
| 933 | + domain_name = resource.domain_name |
| 934 | + |
| 935 | + ws_domain_name_definition = { |
| 936 | + "domain_name": domain_name.domain_name, |
| 937 | + "domain_name_configuration": { |
| 938 | + 'certificate_arn': domain_name.certificate_arn, |
| 939 | + 'endpoint_type': 'REGIONAL', |
| 940 | + }, |
| 941 | + } |
| 942 | + |
| 943 | + if domain_name.tags: |
| 944 | + ws_domain_name_definition['tags'] = domain_name.tags |
| 945 | + |
| 946 | + template['resource'].setdefault( |
| 947 | + 'aws_apigatewayv2_domain_name', {} |
| 948 | + )[domain_name.resource_name] = ws_domain_name_definition |
| 949 | + |
| 950 | + template['resource'].setdefault( |
| 951 | + 'aws_apigatewayv2_api_mapping', {} |
| 952 | + )[domain_name.resource_name + '_mapping'] = { |
| 953 | + "api_id": websocket_api_id, |
| 954 | + "domain_name": "${aws_apigatewayv2_domain_name.%s.id}" % |
| 955 | + domain_name.resource_name, |
| 956 | + "stage": "${aws_apigatewayv2_stage.websocket_api_stage.id}", |
| 957 | + } |
| 958 | + |
| 959 | + def _inject_websocketapi_outputs(self, websocket_api_id, template): |
| 960 | + # type: (str, Dict[str, Any]) -> None |
| 961 | + aws_lambda_functions = template['resource']['aws_lambda_function'] |
| 962 | + stage_name = \ |
| 963 | + template['resource']['aws_apigatewayv2_stage'][ |
| 964 | + 'websocket_api_stage'][ |
| 965 | + 'name'] |
| 966 | + output = template.setdefault('output', {}) |
| 967 | + output['WebsocketAPIId'] = {"value": websocket_api_id} |
| 968 | + |
| 969 | + if 'websocket_connect' in aws_lambda_functions: |
| 970 | + output['WebsocketConnectHandlerArn'] = { |
| 971 | + "value": "${aws_lambda_function.websocket_connect.arn}"} |
| 972 | + output['WebsocketConnectHandlerName'] = { |
| 973 | + "value": ( |
| 974 | + "${aws_lambda_function.websocket_connect.function_name}")} |
| 975 | + if 'websocket_message' in aws_lambda_functions: |
| 976 | + output['WebsocketMessageHandlerArn'] = { |
| 977 | + "value": "${aws_lambda_function.websocket_message.arn}"} |
| 978 | + output['WebsocketMessageHandlerName'] = { |
| 979 | + "value": ( |
| 980 | + "${aws_lambda_function.websocket_message.function_name}")} |
| 981 | + if 'websocket_disconnect' in aws_lambda_functions: |
| 982 | + output['WebsocketDisconnectHandlerArn'] = { |
| 983 | + "value": "${aws_lambda_function.websocket_disconnect.arn}"} |
| 984 | + output['WebsocketDisconnectHandlerName'] = { |
| 985 | + "value": ( |
| 986 | + "${aws_lambda_function.websocket_disconnect" |
| 987 | + ".function_name}")} |
| 988 | + |
| 989 | + output['WebsocketConnectEndpointURL'] = { |
| 990 | + "value": ( |
| 991 | + 'wss://%(websocket_api_id)s.execute-api' |
| 992 | + # The api_gateway_stage is filled in when |
| 993 | + # the template is built. |
| 994 | + '.${data.aws_region.chalice.name}' |
| 995 | + '.amazonaws.com/%(stage_name)s/' |
| 996 | + ) % { |
| 997 | + "stage_name": stage_name, |
| 998 | + "websocket_api_id": websocket_api_id |
| 999 | + } |
| 1000 | + } |
| 1001 | + |
861 | 1002 | def _generate_websocketapi(self, resource, template): |
862 | 1003 | # type: (models.WebsocketAPI, Dict[str, Any]) -> None |
863 | 1004 |
|
864 | | - message = ( |
865 | | - "Unable to package chalice apps that use experimental " |
866 | | - "Websocket decorators. Terraform AWS Provider " |
867 | | - "support for websocket is pending see " |
868 | | - "https://git.io/fj9X8 for details and progress. " |
869 | | - "You can deploy this app using `chalice deploy`." |
870 | | - ) |
871 | | - raise NotImplementedError(message) |
| 1005 | + ws_definition = { |
| 1006 | + 'name': resource.name, |
| 1007 | + 'route_selection_expression': '$request.body.action', |
| 1008 | + 'protocol_type': 'WEBSOCKET', |
| 1009 | + } |
| 1010 | + |
| 1011 | + template['resource'].setdefault('aws_apigatewayv2_api', {})[ |
| 1012 | + resource.resource_name] = ws_definition |
| 1013 | + |
| 1014 | + websocket_api_id = "${aws_apigatewayv2_api.%s.id}" % \ |
| 1015 | + resource.resource_name |
| 1016 | + |
| 1017 | + websocket_handlers = [ |
| 1018 | + 'websocket_connect', |
| 1019 | + 'websocket_message', |
| 1020 | + 'websocket_disconnect', |
| 1021 | + ] |
| 1022 | + |
| 1023 | + for handler in websocket_handlers: |
| 1024 | + if handler in template['resource']['aws_lambda_function']: |
| 1025 | + self._add_websocket_lambda_integration(websocket_api_id, |
| 1026 | + handler, template) |
| 1027 | + self._add_websocket_lambda_invoke_permission(websocket_api_id, |
| 1028 | + handler, template) |
| 1029 | + |
| 1030 | + route_resource_names = [] |
| 1031 | + for route_key in resource.routes: |
| 1032 | + route_resource_name = self._add_websockets_route(websocket_api_id, |
| 1033 | + route_key, |
| 1034 | + template) |
| 1035 | + route_resource_names.append(route_resource_name) |
| 1036 | + |
| 1037 | + template['resource'].setdefault( |
| 1038 | + 'aws_apigatewayv2_deployment', {} |
| 1039 | + )['websocket_api_deployment'] = { |
| 1040 | + "api_id": websocket_api_id, |
| 1041 | + "depends_on": ["aws_apigatewayv2_route.%s" % name for name in |
| 1042 | + route_resource_names] |
| 1043 | + } |
| 1044 | + |
| 1045 | + template['resource'].setdefault( |
| 1046 | + 'aws_apigatewayv2_stage', {} |
| 1047 | + )['websocket_api_stage'] = { |
| 1048 | + "api_id": websocket_api_id, |
| 1049 | + "deployment_id": ("${aws_apigatewayv2_deployment" |
| 1050 | + ".websocket_api_deployment.id}"), |
| 1051 | + "name": resource.api_gateway_stage |
| 1052 | + } |
| 1053 | + |
| 1054 | + self._add_websocket_domain_name(websocket_api_id, resource, template) |
| 1055 | + self._inject_websocketapi_outputs(websocket_api_id, template) |
872 | 1056 |
|
873 | 1057 | def _generate_s3bucketnotification(self, resource, template): |
874 | 1058 | # type: (models.S3BucketNotification, Dict[str, Any]) -> None |
@@ -1026,9 +1210,9 @@ def _generate_lambdalayer(self, resource, template): |
1026 | 1210 | template['resource'].setdefault( |
1027 | 1211 | "aws_lambda_layer_version", {})[ |
1028 | 1212 | resource.resource_name] = { |
1029 | | - 'layer_name': resource.layer_name, |
1030 | | - 'compatible_runtimes': [resource.runtime], |
1031 | | - 'filename': resource.deployment_package.filename, |
| 1213 | + 'layer_name': resource.layer_name, |
| 1214 | + 'compatible_runtimes': [resource.runtime], |
| 1215 | + 'filename': resource.deployment_package.filename, |
1032 | 1216 | } |
1033 | 1217 | self._chalice_layer = resource.resource_name |
1034 | 1218 |
|
|
0 commit comments