예제 #1
0
 def _inject_websocket_integrations(self, configs):
     # type: (Dict[str, Any]) -> Sequence[InstructionMsg]
     instructions = []  # type: List[InstructionMsg]
     for key, config in configs.items():
         instructions.append(
             models.StoreValue(
                 name='websocket-%s-integration-lambda-path' % key,
                 value=StringFormat(
                     'arn:aws:apigateway:{region_name}:lambda:path/'
                     '2015-03-31/functions/arn:aws:lambda:{region_name}:'
                     '{account_id}:function:%s/'
                     'invocations' % config['name'],
                     ['region_name', 'account_id'],
                 ),
             ), )
         instructions.append(
             models.APICall(
                 method_name='create_websocket_integration',
                 params={
                     'api_id':
                     Variable('websocket_api_id'),
                     'lambda_function':
                     Variable('websocket-%s-integration-lambda-path' % key),
                     'handler_type':
                     key,
                 },
                 output_var='%s-integration-id' % key,
             ), )
     return instructions
예제 #2
0
 def test_can_update_managed_role(self):
     role = models.ManagedIAMRole(
         resource_name='resource_name',
         role_name='myrole',
         trust_policy={},
         policy=models.AutoGenIAMPolicy(document={'role': 'policy'}),
     )
     self.remote_state.declare_resource_exists(role, role_arn='myrole:arn')
     plan = self.determine_plan(role)
     assert plan[0] == models.StoreValue(name='myrole_role_arn',
                                         value='myrole:arn')
     self.assert_apicall_equals(
         plan[1],
         models.APICall(
             method_name='put_role_policy',
             params={
                 'role_name': 'myrole',
                 'policy_name': 'myrole',
                 'policy_document': {
                     'role': 'policy'
                 }
             },
         ))
     assert plan[-2].variable_name == 'myrole_role_arn'
     assert plan[-1].value == 'myrole'
     assert list(self.last_plan.messages.values()) == [
         'Updating policy for IAM role: myrole\n'
     ]
예제 #3
0
 def _plan_managediamrole(self, resource):
     # type: (models.ManagedIAMRole) -> Sequence[InstructionMsg]
     document = resource.policy.document
     role_exists = self._remote_state.resource_exists(resource)
     varname = '%s_role_arn' % resource.role_name
     if not role_exists:
         return [(models.APICall(
             method_name='create_role',
             params={
                 'name': resource.role_name,
                 'trust_policy': resource.trust_policy,
                 'policy': document
             },
             output_var=varname,
         ), "Creating IAM role: %s\n" % resource.role_name),
                 models.RecordResourceVariable(
                     resource_type='iam_role',
                     resource_name=resource.resource_name,
                     name='role_arn',
                     variable_name=varname,
                 ),
                 models.RecordResourceValue(
                     resource_type='iam_role',
                     resource_name=resource.resource_name,
                     name='role_name',
                     value=resource.role_name,
                 )]
     role_arn = self._remote_state.resource_deployed_values(
         resource)['role_arn']
     return [
         models.StoreValue(name=varname, value=role_arn),
         (models.APICall(
             method_name='put_role_policy',
             params={
                 'role_name': resource.role_name,
                 'policy_name': resource.role_name,
                 'policy_document': document
             },
         ), "Updating policy for IAM role: %s\n" % resource.role_name),
         models.RecordResourceVariable(
             resource_type='iam_role',
             resource_name=resource.resource_name,
             name='role_arn',
             variable_name=varname,
         ),
         models.RecordResourceValue(
             resource_type='iam_role',
             resource_name=resource.resource_name,
             name='role_name',
             value=resource.role_name,
         )
     ]
예제 #4
0
 def test_can_plan_rest_api(self):
     function = create_function_resource('function_name')
     rest_api = models.RestAPI(
         resource_name='rest_api',
         swagger_doc={'swagger': '2.0'},
         api_gateway_stage='api',
         lambda_function=function,
     )
     plan = self.determine_plan(rest_api)
     self.assert_loads_needed_variables(plan)
     assert plan[4:] == [
         models.APICall(
             method_name='import_rest_api',
             params={'swagger_document': {
                 'swagger': '2.0'
             }},
             output_var='rest_api_id',
         ),
         models.RecordResourceVariable(
             resource_type='rest_api',
             resource_name='rest_api',
             name='rest_api_id',
             variable_name='rest_api_id',
         ),
         models.APICall(method_name='deploy_rest_api',
                        params={
                            'rest_api_id': Variable('rest_api_id'),
                            'api_gateway_stage': 'api'
                        }),
         models.APICall(
             method_name='add_permission_for_apigateway_if_needed',
             params={
                 'function_name': 'appname-dev-function_name',
                 'region_name': Variable('region_name'),
                 'account_id': Variable('account_id'),
                 'rest_api_id': Variable('rest_api_id'),
             }),
         models.StoreValue(
             name='rest_api_url',
             value=StringFormat(
                 'https://{rest_api_id}.execute-api.{region_name}'
                 '.amazonaws.com/api/',
                 ['rest_api_id', 'region_name'],
             ),
         ),
         models.RecordResourceVariable(resource_type='rest_api',
                                       resource_name='rest_api',
                                       name='rest_api_url',
                                       variable_name='rest_api_url'),
     ]
     assert list(
         self.last_plan.messages.values()) == ['Creating Rest API\n']
예제 #5
0
 def test_can_update_file_based_policy(self):
     role = models.ManagedIAMRole(
         resource_name='resource_name',
         role_name='myrole',
         trust_policy={},
         policy=models.FileBasedIAMPolicy(filename='foo.json',
                                          document={'iam': 'policy'}),
     )
     self.remote_state.declare_resource_exists(role, role_arn='myrole:arn')
     plan = self.determine_plan(role)
     assert plan[0] == models.StoreValue(name='myrole_role_arn',
                                         value='myrole:arn')
     self.assert_apicall_equals(
         plan[1],
         models.APICall(
             method_name='put_role_policy',
             params={
                 'role_name': 'myrole',
                 'policy_name': 'myrole',
                 'policy_document': {
                     'iam': 'policy'
                 }
             },
         ))
예제 #6
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
예제 #7
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
예제 #8
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,
         ),
     ]
예제 #9
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,
            ),
        ]
예제 #10
0
 def test_can_update_rest_api(self):
     function = create_function_resource('function_name')
     rest_api = models.RestAPI(
         resource_name='rest_api',
         swagger_doc={'swagger': '2.0'},
         api_gateway_stage='api',
         lambda_function=function,
     )
     self.remote_state.declare_resource_exists(rest_api)
     self.remote_state.deployed_values['rest_api'] = {
         'rest_api_id': 'my_rest_api_id',
     }
     plan = self.determine_plan(rest_api)
     self.assert_loads_needed_variables(plan)
     assert plan[4:] == [
         models.StoreValue(name='rest_api_id', value='my_rest_api_id'),
         models.RecordResourceVariable(
             resource_type='rest_api',
             resource_name='rest_api',
             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': {
                     'swagger': '2.0'
                 },
             },
         ),
         models.APICall(
             method_name='deploy_rest_api',
             params={
                 'rest_api_id': Variable('rest_api_id'),
                 'api_gateway_stage': 'api'
             },
         ),
         models.APICall(
             method_name='add_permission_for_apigateway_if_needed',
             params={
                 'function_name': 'appname-dev-function_name',
                 'region_name': Variable('region_name'),
                 'account_id': Variable('account_id'),
                 'rest_api_id': Variable('rest_api_id')
             },
         ),
         models.APICall(
             method_name='add_permission_for_apigateway_if_needed',
             params={
                 'rest_api_id': Variable("rest_api_id"),
                 'region_name': Variable("region_name"),
                 'account_id': Variable("account_id"),
                 'function_name': 'appname-dev-function_name'
             },
             output_var=None),
         models.StoreValue(
             name='rest_api_url',
             value=StringFormat(
                 'https://{rest_api_id}.execute-api.{region_name}'
                 '.amazonaws.com/api/',
                 ['rest_api_id', 'region_name'],
             ),
         ),
         models.RecordResourceVariable(resource_type='rest_api',
                                       resource_name='rest_api',
                                       name='rest_api_url',
                                       variable_name='rest_api_url'),
     ]