Exemplo n.º 1
0
 def assert_loads_needed_variables(self, plan):
     # Parse arn and store region/account id for future
     # API calls.
     assert plan[0:4] == [
         models.BuiltinFunction(
             'parse_arn',
             [Variable('function_name_lambda_arn')],
             output_var='parsed_lambda_arn',
         ),
         models.JPSearch('account_id',
                         input_var='parsed_lambda_arn',
                         output_var='account_id'),
         models.JPSearch('region',
                         input_var='parsed_lambda_arn',
                         output_var='region_name'),
         # Verify we copy the function arn as needed.
         models.CopyVariable(from_var='function_name_lambda_arn',
                             to_var='api_handler_lambda_arn'),
     ]
Exemplo n.º 2
0
 def _delete_s3_event(self, resource_values):
     # type: (Dict[str, Any]) -> ResourceValueType
     bucket = resource_values['bucket']
     function_arn = resource_values['lambda_arn']
     return {
         'instructions': (
             models.BuiltinFunction('parse_arn', [function_arn],
                                    output_var='parsed_lambda_arn'),
             models.JPSearch('account_id', input_var='parsed_lambda_arn',
                             output_var='account_id'),
             models.APICall(
                 method_name='disconnect_s3_bucket_from_lambda',
                 params={'bucket': bucket, 'function_arn': function_arn}
             ),
             models.APICall(
                 method_name='remove_permission_for_s3_event',
                 params={'bucket': bucket, 'function_arn': function_arn,
                         'account_id': Variable('account_id')}
             ),
         )
     }
Exemplo n.º 3
0
    def _plan_restapi(self, resource):
        # type: (models.RestAPI) -> Sequence[InstructionMsg]
        function = resource.lambda_function
        function_name = function.function_name
        varname = '%s_lambda_arn' % function.resource_name
        lambda_arn_var = Variable(varname)
        # There's a set of shared instructions that are needed
        # in both the update as well as the initial create case.
        # That's what this shared_plan_premable is for.
        shared_plan_preamble = [
            # The various API gateway API calls need
            # to know the region name and account id so
            # we'll take care of that up front and store
            # them in variables.
            models.BuiltinFunction(
                'parse_arn',
                [lambda_arn_var],
                output_var='parsed_lambda_arn',
            ),
            models.JPSearch('account_id',
                            input_var='parsed_lambda_arn',
                            output_var='account_id'),
            models.JPSearch('region',
                            input_var='parsed_lambda_arn',
                            output_var='region_name'),
            # The swagger doc uses the 'api_handler_lambda_arn'
            # var name so we need to make sure we populate this variable
            # before importing the rest API.
            models.CopyVariable(from_var=varname,
                                to_var='api_handler_lambda_arn'),
        ]  # type: List[InstructionMsg]
        # There's also a set of instructions that are needed
        # at the end of deploying a rest API that apply to both
        # the update and create case.
        shared_plan_patch_ops = [{
            'op': 'replace',
            'path': '/minimumCompressionSize',
            'value': resource.minimum_compression
        }]  # type: List[Dict]

        shared_plan_epilogue = [
            models.APICall(method_name='update_rest_api',
                           params={
                               'rest_api_id': Variable('rest_api_id'),
                               'patch_operations': shared_plan_patch_ops
                           }),
            models.APICall(
                method_name='add_permission_for_apigateway',
                params={
                    'function_name': function_name,
                    'region_name': Variable('region_name'),
                    'account_id': Variable('account_id'),
                    'rest_api_id': Variable('rest_api_id')
                },
            ),
            models.APICall(
                method_name='deploy_rest_api',
                params={
                    'rest_api_id': Variable('rest_api_id'),
                    'api_gateway_stage': resource.api_gateway_stage
                },
            ),
            models.StoreValue(
                name='rest_api_url',
                value=StringFormat(
                    'https://{rest_api_id}.execute-api.{region_name}'
                    '.amazonaws.com/%s/' % resource.api_gateway_stage,
                    ['rest_api_id', 'region_name'],
                ),
            ),
            models.RecordResourceVariable(
                resource_type='rest_api',
                resource_name=resource.resource_name,
                name='rest_api_url',
                variable_name='rest_api_url',
            ),
        ]  # type: List[InstructionMsg]
        for auth in resource.authorizers:
            shared_plan_epilogue.append(
                models.APICall(
                    method_name='add_permission_for_apigateway',
                    params={
                        'function_name': auth.function_name,
                        'region_name': Variable('region_name'),
                        'account_id': Variable('account_id'),
                        'rest_api_id': Variable('rest_api_id')
                    },
                ))
        if not self._remote_state.resource_exists(resource):
            plan = shared_plan_preamble + [
                (models.APICall(
                    method_name='import_rest_api',
                    params={
                        'swagger_document': resource.swagger_doc,
                        'endpoint_type': resource.endpoint_type
                    },
                    output_var='rest_api_id',
                ), "Creating Rest API\n"),
                models.RecordResourceVariable(
                    resource_type='rest_api',
                    resource_name=resource.resource_name,
                    name='rest_api_id',
                    variable_name='rest_api_id',
                ),
            ]
        else:
            deployed = self._remote_state.resource_deployed_values(resource)
            shared_plan_epilogue.insert(
                0,
                models.APICall(method_name='get_rest_api',
                               params={'rest_api_id': Variable('rest_api_id')},
                               output_var='rest_api'))
            shared_plan_patch_ops.append({
                'op':
                'replace',
                'path':
                StringFormat(
                    '/endpointConfiguration/types/%s' %
                    ('{rest_api[endpointConfiguration][types][0]}'),
                    ['rest_api']),
                'value':
                resource.endpoint_type
            })
            plan = shared_plan_preamble + [
                models.StoreValue(name='rest_api_id',
                                  value=deployed['rest_api_id']),
                models.RecordResourceVariable(
                    resource_type='rest_api',
                    resource_name=resource.resource_name,
                    name='rest_api_id',
                    variable_name='rest_api_id',
                ),
                (models.APICall(
                    method_name='update_api_from_swagger',
                    params={
                        'rest_api_id': Variable('rest_api_id'),
                        'swagger_document': resource.swagger_doc,
                    },
                ), "Updating rest API\n"),
            ]

        plan.extend(shared_plan_epilogue)
        return plan
Exemplo n.º 4
0
    def _plan_websocketapi(self, resource):
        # type: (models.WebsocketAPI) -> Sequence[InstructionMsg]
        configs = self._create_websocket_function_configs(resource)
        routes = resource.routes

        # Which lambda function we use here does not matter. We are only using
        # it to find the account id and the region.
        lambda_arn_var = list(configs.values())[0]['lambda_arn_var']
        shared_plan_preamble = [
            # The various API gateway API calls need
            # to know the region name and account id so
            # we'll take care of that up front and store
            # them in variables.
            models.BuiltinFunction(
                'parse_arn',
                [lambda_arn_var],
                output_var='parsed_lambda_arn',
            ),
            models.JPSearch('account_id',
                            input_var='parsed_lambda_arn',
                            output_var='account_id'),
            models.JPSearch('region',
                            input_var='parsed_lambda_arn',
                            output_var='region_name'),
        ]  # type: List[InstructionMsg]

        # There's also a set of instructions that are needed
        # at the end of deploying a websocket API that apply to both
        # the update and create case.
        shared_plan_epilogue = [
            models.StoreValue(
                name='websocket_api_url',
                value=StringFormat(
                    'wss://{websocket_api_id}.execute-api.{region_name}'
                    '.amazonaws.com/%s/' % resource.api_gateway_stage,
                    ['websocket_api_id', 'region_name'],
                ),
            ),
            models.RecordResourceVariable(
                resource_type='websocket_api',
                resource_name=resource.resource_name,
                name='websocket_api_url',
                variable_name='websocket_api_url',
            ),
            models.RecordResourceVariable(
                resource_type='websocket_api',
                resource_name=resource.resource_name,
                name='websocket_api_id',
                variable_name='websocket_api_id',
            ),
        ]  # type: List[InstructionMsg]

        shared_plan_epilogue += [
            models.APICall(
                method_name='add_permission_for_apigateway_v2',
                params={
                    'function_name': function_config['name'],
                    'region_name': Variable('region_name'),
                    'account_id': Variable('account_id'),
                    'api_id': Variable('websocket_api_id')
                },
            ) for function_config in configs.values()
        ]

        main_plan = []  # type: List[InstructionMsg]
        if not self._remote_state.resource_exists(resource):
            # The resource does not exist, we create it in full here.
            main_plan += [
                (models.APICall(
                    method_name='create_websocket_api',
                    params={'name': resource.name},
                    output_var='websocket_api_id',
                ), "Creating websocket api: %s\n" % resource.name),
                models.StoreValue(
                    name='routes',
                    value=[],
                ),
            ]
            main_plan += self._inject_websocket_integrations(configs)

            for route_key in routes:
                main_plan += [self._create_route_for_key(route_key)]
            main_plan += [
                models.APICall(
                    method_name='deploy_websocket_api',
                    params={
                        'api_id': Variable('websocket_api_id'),
                    },
                    output_var='deployment-id',
                ),
                models.APICall(method_name='create_stage',
                               params={
                                   'api_id': Variable('websocket_api_id'),
                                   'stage_name': resource.api_gateway_stage,
                                   'deployment_id': Variable('deployment-id'),
                               }),
            ]
        else:
            # Already exists. Need to sync up the routes, the easiest way to do
            # this is to delete them and their integrations and re-create them.
            # They will not work if the lambda function changes from under
            # them, and the logic for detecting that and making just the needed
            # changes is complex. There is an integration test to ensure there
            # no dropped messages during a redeployment.
            deployed = self._remote_state.resource_deployed_values(resource)
            main_plan += [
                models.StoreValue(name='websocket_api_id',
                                  value=deployed['websocket_api_id']),
                models.APICall(
                    method_name='get_websocket_routes',
                    params={'api_id': Variable('websocket_api_id')},
                    output_var='routes',
                ),
                models.APICall(
                    method_name='delete_websocket_routes',
                    params={
                        'api_id': Variable('websocket_api_id'),
                        'routes': Variable('routes'),
                    },
                ),
                models.APICall(method_name='get_websocket_integrations',
                               params={
                                   'api_id': Variable('websocket_api_id'),
                               },
                               output_var='integrations'),
                models.APICall(method_name='delete_websocket_integrations',
                               params={
                                   'api_id': Variable('websocket_api_id'),
                                   'integrations': Variable('integrations'),
                               })
            ]
            main_plan += self._inject_websocket_integrations(configs)
            for route_key in routes:
                main_plan += [self._create_route_for_key(route_key)]
        return shared_plan_preamble + main_plan + shared_plan_epilogue
Exemplo n.º 5
0
 def _plan_sqseventsource(self, resource):
     # type: (models.SQSEventSource) -> Sequence[InstructionMsg]
     queue_arn_varname = '%s_queue_arn' % resource.resource_name
     uuid_varname = '%s_uuid' % resource.resource_name
     function_arn = Variable('%s_lambda_arn' %
                             resource.lambda_function.resource_name)
     instruction_for_queue_arn = [
         models.BuiltinFunction(
             'parse_arn',
             [function_arn],
             output_var='parsed_lambda_arn',
         ),
         models.JPSearch('account_id',
                         input_var='parsed_lambda_arn',
                         output_var='account_id'),
         models.JPSearch('region',
                         input_var='parsed_lambda_arn',
                         output_var='region_name'),
         models.StoreValue(
             name=queue_arn_varname,
             value=StringFormat(
                 'arn:aws:sqs:{region_name}:{account_id}:%s' %
                 (resource.queue),
                 ['region_name', 'account_id'],
             ),
         ),
     ]  # type: List[InstructionMsg]
     if self._remote_state.resource_exists(resource):
         deployed = self._remote_state.resource_deployed_values(resource)
         uuid = deployed['event_uuid']
         return instruction_for_queue_arn + [
             models.APICall(method_name='update_sqs_event_source',
                            params={
                                'event_uuid': uuid,
                                'batch_size': resource.batch_size
                            }),
             models.RecordResourceValue(
                 resource_type='sqs_event',
                 resource_name=resource.resource_name,
                 name='queue_arn',
                 value=deployed['queue_arn'],
             ),
             models.RecordResourceValue(
                 resource_type='sqs_event',
                 resource_name=resource.resource_name,
                 name='event_uuid',
                 value=uuid,
             ),
             models.RecordResourceValue(
                 resource_type='sqs_event',
                 resource_name=resource.resource_name,
                 name='queue',
                 value=resource.queue,
             ),
             models.RecordResourceValue(
                 resource_type='sqs_event',
                 resource_name=resource.resource_name,
                 name='lambda_arn',
                 value=deployed['lambda_arn'],
             ),
         ]
     return instruction_for_queue_arn + [
         (models.APICall(
             method_name='create_sqs_event_source',
             params={
                 'queue_arn': Variable(queue_arn_varname),
                 'batch_size': resource.batch_size,
                 'function_name': function_arn
             },
             output_var=uuid_varname,
         ), 'Subscribing %s to SQS queue %s\n' %
          (resource.lambda_function.function_name, resource.queue)),
         models.RecordResourceVariable(
             resource_type='sqs_event',
             resource_name=resource.resource_name,
             name='queue_arn',
             variable_name=queue_arn_varname,
         ),
         # We record this because this is what's used to unsubscribe
         # lambda to the SQS queue.
         models.RecordResourceVariable(
             resource_type='sqs_event',
             resource_name=resource.resource_name,
             name='event_uuid',
             variable_name=uuid_varname,
         ),
         models.RecordResourceValue(
             resource_type='sqs_event',
             resource_name=resource.resource_name,
             name='queue',
             value=resource.queue,
         ),
         models.RecordResourceVariable(
             resource_type='sqs_event',
             resource_name=resource.resource_name,
             name='lambda_arn',
             variable_name=function_arn.name,
         ),
     ]
Exemplo n.º 6
0
    def _plan_snslambdasubscription(self, resource):
        # type: (models.SNSLambdaSubscription) -> Sequence[InstructionMsg]
        function_arn = Variable('%s_lambda_arn' %
                                resource.lambda_function.resource_name)
        topic_arn_varname = '%s_topic_arn' % resource.resource_name
        subscribe_varname = '%s_subscription_arn' % resource.resource_name

        instruction_for_topic_arn = []  # type: List[InstructionMsg]
        if resource.topic.startswith('arn:aws:sns:'):
            instruction_for_topic_arn += [
                models.StoreValue(
                    name=topic_arn_varname,
                    value=resource.topic,
                )
            ]
        else:
            # To keep the user API simple, we only require the topic
            # name and not the ARN.  However, the APIs require the topic
            # ARN so we need to reconstruct it here in the planner.
            instruction_for_topic_arn += [
                models.BuiltinFunction(
                    'parse_arn',
                    [function_arn],
                    output_var='parsed_lambda_arn',
                ),
                models.JPSearch('account_id',
                                input_var='parsed_lambda_arn',
                                output_var='account_id'),
                models.JPSearch('region',
                                input_var='parsed_lambda_arn',
                                output_var='region_name'),
                models.StoreValue(
                    name=topic_arn_varname,
                    value=StringFormat(
                        'arn:aws:sns:{region_name}:{account_id}:%s' %
                        (resource.topic),
                        ['region_name', 'account_id'],
                    ),
                ),
            ]
        if self._remote_state.resource_exists(resource):
            # Given there's nothing about an SNS subscription you can
            # configure for now, if the resource exists, we don't do
            # anything.  The resource sweeper will verify that if the
            # subscription doesn't actually apply that we should unsubscribe
            # from the topic.
            deployed = self._remote_state.resource_deployed_values(resource)
            subscription_arn = deployed['subscription_arn']
            return instruction_for_topic_arn + [
                models.RecordResourceValue(
                    resource_type='sns_event',
                    resource_name=resource.resource_name,
                    name='topic',
                    value=resource.topic,
                ),
                models.RecordResourceVariable(
                    resource_type='sns_event',
                    resource_name=resource.resource_name,
                    name='lambda_arn',
                    variable_name=function_arn.name,
                ),
                models.RecordResourceValue(
                    resource_type='sns_event',
                    resource_name=resource.resource_name,
                    name='subscription_arn',
                    value=subscription_arn,
                ),
                models.RecordResourceVariable(
                    resource_type='sns_event',
                    resource_name=resource.resource_name,
                    name='topic_arn',
                    variable_name=topic_arn_varname,
                ),
            ]
        return instruction_for_topic_arn + [
            models.APICall(
                method_name='add_permission_for_sns_topic',
                params={
                    'topic_arn': Variable(topic_arn_varname),
                    'function_arn': function_arn
                },
            ),
            (models.APICall(
                method_name='subscribe_function_to_topic',
                params={
                    'topic_arn': Variable(topic_arn_varname),
                    'function_arn': function_arn
                },
                output_var=subscribe_varname,
            ), 'Subscribing %s to SNS topic %s\n' %
             (resource.lambda_function.function_name, resource.topic)),
            models.RecordResourceValue(
                resource_type='sns_event',
                resource_name=resource.resource_name,
                name='topic',
                value=resource.topic,
            ),
            models.RecordResourceVariable(
                resource_type='sns_event',
                resource_name=resource.resource_name,
                name='lambda_arn',
                variable_name=function_arn.name,
            ),
            models.RecordResourceVariable(
                resource_type='sns_event',
                resource_name=resource.resource_name,
                name='subscription_arn',
                variable_name=subscribe_varname,
            ),
            models.RecordResourceVariable(
                resource_type='sns_event',
                resource_name=resource.resource_name,
                name='topic_arn',
                variable_name=topic_arn_varname,
            ),
        ]
Exemplo n.º 7
0
    def _plan_lambdafunction(self, resource):
        # type: (models.LambdaFunction) -> Sequence[InstructionMsg]
        role_arn = self._get_role_arn(resource.role)
        # Make mypy happy, it complains if we don't "declare" this upfront.
        params = {}  # type: Dict[str, Any]
        varname = '%s_lambda_arn' % resource.resource_name
        # Not sure the best way to express this via mypy, but we know
        # that in the build stage we replace the deployment package
        # name with the actual filename generated from the pip
        # packager.  For now we resort to a cast.
        filename = cast(str, resource.deployment_package.filename)

        if resource.reserved_concurrency is None:
            concurrency_api_call = models.APICall(
                method_name='delete_function_concurrency',
                params={'function_name': resource.function_name},
                output_var='reserved_concurrency_result')
        else:
            concurrency = resource.reserved_concurrency
            concurrency_api_call = (
                models.APICall(method_name='put_function_concurrency',
                               params={
                                   'function_name': resource.function_name,
                                   'reserved_concurrent_executions':
                                   concurrency,
                               },
                               output_var='reserved_concurrency_result'),
                "Updating lambda function concurrency limit: %s\n" %
                resource.function_name)

        api_calls = []  # type: List[InstructionMsg]
        if not self._remote_state.resource_exists(resource):
            params = {
                'function_name':
                resource.function_name,
                'role_arn':
                role_arn,
                'zip_contents':
                self._osutils.get_file_contents(filename, binary=True),
                'runtime':
                resource.runtime,
                'handler':
                resource.handler,
                'environment_variables':
                resource.environment_variables,
                'tags':
                resource.tags,
                'timeout':
                resource.timeout,
                'memory_size':
                resource.memory_size,
                'security_group_ids':
                resource.security_group_ids,
                'subnet_ids':
                resource.subnet_ids,
                'layers':
                resource.layers
            }
            api_calls.extend([(models.APICall(
                method_name='create_function',
                params=params,
                output_var=varname,
            ), "Creating lambda function: %s\n" % resource.function_name),
                              models.RecordResourceVariable(
                                  resource_type='lambda_function',
                                  resource_name=resource.resource_name,
                                  name='lambda_arn',
                                  variable_name=varname,
                              )])
        else:
            # TODO: Consider a smarter diff where we check if we even need
            # to do an update() API call.
            params = {
                'function_name':
                resource.function_name,
                'role_arn':
                role_arn,
                'zip_contents':
                self._osutils.get_file_contents(filename, binary=True),
                'runtime':
                resource.runtime,
                'environment_variables':
                resource.environment_variables,
                'tags':
                resource.tags,
                'timeout':
                resource.timeout,
                'memory_size':
                resource.memory_size,
                'security_group_ids':
                resource.security_group_ids,
                'subnet_ids':
                resource.subnet_ids,
                'layers':
                resource.layers
            }
            api_calls.extend([(models.APICall(
                method_name='update_function',
                params=params,
                output_var='update_function_result',
            ), "Updating lambda function: %s\n" % resource.function_name),
                              models.JPSearch(
                                  'FunctionArn',
                                  input_var='update_function_result',
                                  output_var=varname,
                              ),
                              models.RecordResourceVariable(
                                  resource_type='lambda_function',
                                  resource_name=resource.resource_name,
                                  name='lambda_arn',
                                  variable_name=varname,
                              )])
        api_calls.append(concurrency_api_call)
        return api_calls
Exemplo n.º 8
0
 def _plan_lambdafunction(self, resource):
     # type: (models.LambdaFunction) -> Sequence[_INSTRUCTION_MSG]
     role_arn = self._get_role_arn(resource.role)
     # Make mypy happy, it complains if we don't "declare" this upfront.
     params = {}  # type: Dict[str, Any]
     varname = '%s_lambda_arn' % resource.resource_name
     if not self._remote_state.resource_exists(resource):
         params = {
             'function_name':
             resource.function_name,
             'role_arn':
             role_arn,
             'zip_contents':
             self._osutils.get_file_contents(
                 resource.deployment_package.filename, binary=True),
             'runtime':
             resource.runtime,
             'handler':
             resource.handler,
             'environment_variables':
             resource.environment_variables,
             'tags':
             resource.tags,
             'timeout':
             resource.timeout,
             'memory_size':
             resource.memory_size,
             'security_group_ids':
             resource.security_group_ids,
             'subnet_ids':
             resource.subnet_ids,
         }
         return [(models.APICall(
             method_name='create_function',
             params=params,
             output_var=varname,
         ), "Creating lambda function: %s\n" % resource.function_name),
                 models.RecordResourceVariable(
                     resource_type='lambda_function',
                     resource_name=resource.resource_name,
                     name='lambda_arn',
                     variable_name=varname,
                 )]
     # TODO: Consider a smarter diff where we check if we even need
     # to do an update() API call.
     params = {
         'function_name':
         resource.function_name,
         'role_arn':
         role_arn,
         'zip_contents':
         self._osutils.get_file_contents(
             resource.deployment_package.filename, binary=True),
         'runtime':
         resource.runtime,
         'environment_variables':
         resource.environment_variables,
         'tags':
         resource.tags,
         'timeout':
         resource.timeout,
         'memory_size':
         resource.memory_size,
         'security_group_ids':
         resource.security_group_ids,
         'subnet_ids':
         resource.subnet_ids,
     }
     return [(models.APICall(
         method_name='update_function',
         params=params,
         output_var='update_function_result',
     ), "Updating lambda function: %s\n" % resource.function_name),
             models.JPSearch(
                 'FunctionArn',
                 input_var='update_function_result',
                 output_var=varname,
             ),
             models.RecordResourceVariable(
                 resource_type='lambda_function',
                 resource_name=resource.resource_name,
                 name='lambda_arn',
                 variable_name=varname,
             )]