示例#1
0
    def test_stack_template_url(self):
        """Test stack template url."""
        context = mock_context("mynamespace")
        blueprint = MockBlueprint(name="myblueprint", context=context)

        region = "us-east-1"
        endpoint = "https://example.com"
        session = get_session(region)
        provider = Provider(session)
        action = BaseAction(
            context=context,
            provider_builder=MockProviderBuilder(provider, region=region),
        )

        with patch(
                "runway.cfngin.actions.base.get_s3_endpoint",
                autospec=True,
                return_value=endpoint,
        ):
            self.assertEqual(
                action.stack_template_url(blueprint),
                "%s/%s/stack_templates/%s/%s-%s.json" % (
                    endpoint,
                    "stacker-mynamespace",
                    "mynamespace-myblueprint",
                    "myblueprint",
                    MOCK_VERSION,
                ),
            )
示例#2
0
    def setUp(self):
        """Run before tests."""
        self.region = "us-east-1"
        self.session = get_session(self.region)
        self.provider = Provider(self.session)

        self.config_no_persist = {
            "stacks": [{
                "name": "stack1"
            }, {
                "name": "stack2",
                "requires": ["stack1"]
            }]
        }

        self.config_persist = {
            "persistent_graph_key":
            "test.json",
            "stacks": [{
                "name": "stack1"
            }, {
                "name": "stack2",
                "requires": ["stack1"]
            }],
        }
示例#3
0
 def test_ensure_cfn_bucket_does_not_exist_us_west(self):
     """Test ensure cfn bucket does not exist us west."""
     session = get_session("us-west-1")
     provider = Provider(session)
     action = BaseAction(
         context=mock_context("mynamespace"),
         provider_builder=MockProviderBuilder(provider, region="us-west-1"),
     )
     stubber = Stubber(action.s3_conn)
     stubber.add_client_error(
         "head_bucket",
         service_error_code="NoSuchBucket",
         service_message="Not Found",
         http_status_code=404,
     )
     stubber.add_response(
         "create_bucket",
         service_response={},
         expected_params={
             "Bucket": ANY,
             "CreateBucketConfiguration": {
                 "LocationConstraint": "us-west-1"
             },
         },
     )
     with stubber:
         action.ensure_cfn_bucket()
示例#4
0
def update(
        context,  # pylint: disable=unused-argument
        provider,
        **kwargs):  # noqa: E124
    # type: (Context, BaseProvider, Optional[Dict[str, Any]]) -> bool
    """Update the callback urls for the User Pool Client.

    Required to match the redirect_uri being sent which contains
    our distribution and alternate domain names.

    Args:
        context (:class:`runway.cfngin.context.Context`): The context
            instance.
        provider (:class:`runway.cfngin.providers.base.BaseProvider`):
            The provider instance

    Keyword Args:
        alternate_domains (List[str]): A list of any alternate domains
            that need to be listed with the primary distribution domain
        redirect_path_sign_in (str): The redirect path after sign in
        redirect_path_sign_out (str): The redirect path after sign out
        oauth_scopes (List[str]): A list of all available validation
            scopes for oauth
    """
    session = get_session(provider.region)
    cognito_client = session.client('cognito-idp')

    # Combine alternate domains with main distribution
    redirect_domains = kwargs['alternate_domains'] + [
        'https://' + kwargs['distribution_domain']
    ]

    # Create a list of all domains with their redirect paths
    redirect_uris_sign_in = [
        "%s%s" % (domain, kwargs['redirect_path_sign_in'])
        for domain in redirect_domains
    ]
    redirect_uris_sign_out = [
        "%s%s" % (domain, kwargs['redirect_path_sign_out'])
        for domain in redirect_domains
    ]
    # Update the user pool client
    try:
        cognito_client.update_user_pool_client(
            AllowedOAuthScopes=kwargs['oauth_scopes'],
            AllowedOAuthFlows=['code'],
            SupportedIdentityProviders=kwargs['supported_identity_providers'],
            AllowedOAuthFlowsUserPoolClient=True,
            ClientId=kwargs['client_id'],
            CallbackURLs=redirect_uris_sign_in,
            LogoutURLs=redirect_uris_sign_out,
            UserPoolId=context.hook_data['aae_user_pool_id_retriever']['id'],
        )
        return True
    except Exception as err:  # pylint: disable=broad-except
        LOGGER.error('Was not able to update the callback urls on '
                     'the user pool client')
        LOGGER.error(err)
        return False
示例#5
0
 def setUp(self):
     """Run before tests."""
     region = "us-east-1"
     self.session = get_session(region=region)
     self.provider = Provider(self.session,
                              interactive=True,
                              recreate_failed=True)
     self.stubber = Stubber(self.provider.cloudformation)
示例#6
0
    def setUp(self) -> None:
        """Run before tests."""
        self.context = self._get_context()
        self.session = get_session(region=None)
        self.provider = Provider(self.session,
                                 interactive=False,
                                 recreate_failed=False)
        provider_builder = MockProviderBuilder(provider=self.provider)
        self.deploy_action = deploy.Action(
            self.context,
            provider_builder=provider_builder,
            cancel=MockThreadingEvent(),  # type: ignore
        )

        self.stack = MagicMock()
        self.stack.region = None
        self.stack.name = "vpc"
        self.stack.fqn = "vpc"
        self.stack.blueprint.rendered = "{}"
        self.stack.locked = False
        self.stack_status = None

        plan = cast(
            Plan, self.deploy_action._Action__generate_plan())  # type: ignore
        self.step = plan.steps[0]
        self.step.stack = self.stack

        def patch_object(*args: Any, **kwargs: Any) -> None:
            mock_object = patch.object(*args, **kwargs)
            self.addCleanup(mock_object.stop)
            mock_object.start()

        def get_stack(name: str, *_args: Any,
                      **_kwargs: Any) -> Dict[str, Any]:
            if name != self.stack.name or not self.stack_status:
                raise StackDoesNotExist(name)

            return {
                "StackName": self.stack.name,
                "StackStatus": self.stack_status,
                "Outputs": [],
                "Tags": [],
            }

        def get_events(name: str, *_args: Any,
                       **_kwargs: Any) -> List[Dict[str, str]]:
            return [{
                "ResourceStatus": "ROLLBACK_IN_PROGRESS",
                "ResourceStatusReason": "CFN fail",
            }]

        patch_object(self.provider, "get_stack", side_effect=get_stack)
        patch_object(self.provider, "update_stack")
        patch_object(self.provider, "create_stack")
        patch_object(self.provider, "destroy_stack")
        patch_object(self.provider, "get_events", side_effect=get_events)

        patch_object(self.deploy_action, "s3_stack_push")
示例#7
0
def get_principal_arn(provider):
    """Return ARN of current session principle."""
    # looking up caller identity
    session = get_session(provider.region)
    sts_client = session.client('sts')
    caller_identity_arn = sts_client.get_caller_identity()['Arn']
    if caller_identity_arn.split(':')[2] == 'iam' and (
            caller_identity_arn.split(':')[5].startswith('user/')):
        return caller_identity_arn  # user arn
    return assumed_role_to_principle(caller_identity_arn)
示例#8
0
def get_principal_arn(provider):
    """Return ARN of current session principle."""
    # looking up caller identity
    session = get_session(provider.region)
    sts_client = session.client("sts")
    caller_identity_arn = sts_client.get_caller_identity()["Arn"]
    if caller_identity_arn.split(":")[2] == "iam" and (
            caller_identity_arn.split(":")[5].startswith("user/")):
        return caller_identity_arn  # user arn
    return assumed_role_to_principle(caller_identity_arn)
示例#9
0
def update(
    context,  # type: Context # pylint: disable=unused-import
    provider,  # type: BaseProvider
    **kwargs  # type: Optional[Dict[str, Any]]
):  # noqa: E124
    # type: (...) -> Union[Dict[str, Any], bool]
    """Retrieve/Update the domain name of the specified client.

    A domain name is required in order to make authorization and token
    requests. This prehook ensures we have one available, and if not
    we create one based on the user pool and client ids.

    Args:
        context (:class:`runway.cfngin.context.Context`): The context
            instance.
        provider (:class:`runway.cfngin.providers.base.BaseProvider`):
            The provider instance

    Keyword Args:
        user_pool_id (str): The ID of the Cognito User Pool
        client_id (str): The ID of the Cognito User Pool Client
    """
    session = get_session(provider.region)
    cognito_client = session.client('cognito-idp')

    context_dict = {}

    user_pool_id = context.hook_data['aae_user_pool_id_retriever']['id']
    client_id = kwargs['client_id']
    user_pool = cognito_client.describe_user_pool(
        UserPoolId=user_pool_id).get('UserPool')
    (user_pool_region, user_pool_hash) = user_pool_id.split('_')

    domain_prefix = user_pool.get('CustomDomain') or user_pool.get('Domain')

    # Return early if we already have a domain
    if domain_prefix:
        context_dict['domain'] = get_user_pool_domain(domain_prefix,
                                                      user_pool_region)
        return context_dict

    try:
        domain_prefix = ('%s-%s' % (user_pool_hash, client_id)).lower()

        cognito_client.create_user_pool_domain(Domain=domain_prefix,
                                               UserPoolId=user_pool_id)
        context_dict['domain'] = get_user_pool_domain(domain_prefix,
                                                      user_pool_region)
        return context_dict
    except Exception as err:  # pylint: disable=broad-except
        LOGGER.error('Could not update user pool domain for user pool id %s.',
                     user_pool_id)
        LOGGER.error(err)
        return False
示例#10
0
    def setUp(self):
        """Run before tests."""
        self.context = self._get_context()
        self.session = get_session(region=None)
        self.provider = Provider(self.session,
                                 interactive=False,
                                 recreate_failed=False)
        provider_builder = MockProviderBuilder(self.provider)
        self.build_action = build.Action(self.context,
                                         provider_builder=provider_builder,
                                         cancel=MockThreadingEvent())

        self.stack = MagicMock()
        self.stack.region = None
        self.stack.name = 'vpc'
        self.stack.fqn = 'vpc'
        self.stack.blueprint.rendered = '{}'
        self.stack.locked = False
        self.stack_status = None

        plan = self.build_action._Action__generate_plan()
        self.step = plan.steps[0]
        self.step.stack = self.stack

        def patch_object(*args, **kwargs):
            mock_object = patch.object(*args, **kwargs)
            self.addCleanup(mock_object.stop)
            mock_object.start()

        def get_stack(name, *args, **kwargs):
            if name != self.stack.name or not self.stack_status:
                raise StackDoesNotExist(name)

            return {
                'StackName': self.stack.name,
                'StackStatus': self.stack_status,
                'Outputs': [],
                'Tags': []
            }

        def get_events(name, *args, **kwargs):
            return [{
                'ResourceStatus': 'ROLLBACK_IN_PROGRESS',
                'ResourceStatusReason': 'CFN fail'
            }]

        patch_object(self.provider, 'get_stack', side_effect=get_stack)
        patch_object(self.provider, 'update_stack')
        patch_object(self.provider, 'create_stack')
        patch_object(self.provider, 'destroy_stack')
        patch_object(self.provider, 'get_events', side_effect=get_events)

        patch_object(self.build_action, "s3_stack_push")
示例#11
0
 def test_ensure_cfn_bucket_exists(self):
     """Test ensure cfn bucket exists."""
     session = get_session("us-east-1")
     provider = Provider(session)
     action = BaseAction(
         context=mock_context("mynamespace"),
         provider_builder=MockProviderBuilder(provider),
     )
     stubber = Stubber(action.s3_conn)
     stubber.add_response("head_bucket",
                          service_response={},
                          expected_params={"Bucket": ANY})
     with stubber:
         action.ensure_cfn_bucket()
示例#12
0
 def test_ensure_cfn_forbidden(self):
     """Test ensure cfn forbidden."""
     session = get_session("us-west-1")
     provider = Provider(session)
     action = BaseAction(context=mock_context("mynamespace"),
                         provider_builder=MockProviderBuilder(provider))
     stubber = Stubber(action.s3_conn)
     stubber.add_client_error(
         "head_bucket",
         service_error_code="AccessDenied",
         service_message="Forbidden",
         http_status_code=403,
     )
     with stubber:
         with self.assertRaises(botocore.exceptions.ClientError):
             action.ensure_cfn_bucket()
示例#13
0
def execute(
    context,  # type: Context # pylint: disable=unused-argument
    provider,  # type: BaseProvider
    **kwargs  # type: Optional[Dict[str, Any]]
):  # noqa: E124
    # type: (...) -> Union[Dict[str, Any], bool]
    """Execute the cleanup process.

    A StateMachine will be executed that stays active after the main and
    dependency stacks have been deleted. This will keep attempting to
    delete the Replicated functions that were created as part of the main
    stack. Once it has deleted all the Lambdas supplied it will self
    destruct its own stack.

    Args:
        context (:class:`runway.cfngin.context.Context`): The context
            instance.
        provider (:class:`runway.cfngin.providers.base.BaseProvider`):
            The provider instance

    Keyword Args:
        function_arns (List[str]): The arns of all the Replicated functions to
            delete
        state_machine_arn (str): The ARN of the State Machine to execute
        stack_name (str): The name of the Cleanup stack to delete
    """
    session = get_session(provider.region)
    step_functions_client = session.client('stepfunctions')

    try:
        step_functions_client.start_execution(
            stateMachineArn=kwargs['state_machine_arn'],
            input=json.dumps({
                "SelfDestruct": {
                    "StateMachineArn": kwargs['state_machine_arn'],
                    "StackName": kwargs['stack_name'],
                },
                "FunctionArns": kwargs['function_arns']
            }))
        return True
    except Exception as err:  # pylint: disable=broad-except
        LOGGER.error('Could not execute cleanup process.')
        LOGGER.error(err)
        return False
示例#14
0
def delete(
    context,  # type: Context # pylint: disable=unused-import
    provider,  # type: BaseProvider
    **kwargs  # type: Optional[Dict[str, Any]]
):  # noqa: E124
    # type: (...) -> Union[Dict[str, Any], bool]
    """Delete the domain if the user pool was created by Runway.

    If a User Pool was created by Runway, and populated with a domain, that
    domain must be deleted prior to the User Pool itself being deleted or an
    error will occur. This process ensures that our generated domain name is
    deleted, or skips if not able to find one.

    Args:
        context (:class:`runway.cfngin.context.Context`): The context
            instance.
        provider (:class:`runway.cfngin.providers.base.BaseProvider`):
            The provider instance

    Keyword Args:
        client_id (str): The ID of the Cognito User Pool Client
    """
    session = get_session(provider.region)
    cognito_client = session.client('cognito-idp')

    user_pool_id = context.hook_data['aae_user_pool_id_retriever']['id']
    client_id = kwargs['client_id']
    (_, user_pool_hash) = user_pool_id.split('_')
    domain_prefix = ('%s-%s' % (user_pool_hash, client_id)).lower()

    try:
        cognito_client.delete_user_pool_domain(UserPoolId=user_pool_id,
                                               Domain=domain_prefix)
        return True
    except cognito_client.exceptions.InvalidParameterException:
        LOGGER.info('No domain found with prefix %s. Skipping deletion.',
                    domain_prefix)
        return True
    except Exception as err:  # pylint: disable=broad-except
        LOGGER.error('Could not delete the User Pool Domain.')
        LOGGER.error(err)
        return False
示例#15
0
 def test_ensure_cfn_bucket_does_not_exist_us_east(self) -> None:
     """Test ensure cfn bucket does not exist us east."""
     session = get_session("us-east-1")
     provider = Provider(session)
     action = BaseAction(
         context=mock_context("mynamespace"),
         provider_builder=MockProviderBuilder(provider=provider),
     )
     stubber = Stubber(action.s3_conn)
     stubber.add_client_error(
         "head_bucket",
         service_error_code="NoSuchBucket",
         service_message="Not Found",
         http_status_code=404,
     )
     stubber.add_response("create_bucket",
                          service_response={},
                          expected_params={"Bucket": ANY})
     with stubber:
         action.ensure_cfn_bucket()
示例#16
0
    def setUp(self):
        """Run before tests."""
        self.region = 'us-east-1'
        self.session = get_session(self.region)
        self.provider = Provider(self.session)

        self.config_no_persist = {
            'stacks': [
                {'name': 'stack1'},
                {'name': 'stack2',
                 'requires': ['stack1']}
            ]
        }

        self.config_persist = {
            'persistent_graph_key': 'test.json',
            'stacks': [
                {'name': 'stack1'},
                {'name': 'stack2',
                 'requires': ['stack1']}
            ]
        }
示例#17
0
def get(
        context,  # pylint: disable=unused-argument
        provider,
        **kwargs):  # noqa: E124
    # type: (Context, BaseProvider, Optional[Dict[str, Any]]) -> Dict
    """Retrieve the callback URLs for User Pool Client Creation.

    When the User Pool is created a Callback URL is required. During a post
    hook entitled ``client_updater`` these Callback URLs are updated to that
    of the Distribution. Before then we need to ensure that if a Client
    already exists that the URLs for that client are used to prevent any
    interuption of service during deploy.

    Args:
        context (:class:`runway.cfngin.context.Context`): The context
            instance.
        provider (:class:`runway.cfngin.providers.base.BaseProvider`):
            The provider instance

    Keyword Args:
        user_pool_id (str): The ID of the User Pool to check for a client
        stack_name (str) The name of the stack to check against
    """
    session = get_session(provider.region)
    cloudformation_client = session.client('cloudformation')
    cognito_client = session.client('cognito-idp')

    context_dict = {}
    context_dict['callback_urls'] = ['https://example.tmp']

    try:
        # Return the current stack if one exists
        stack_desc = cloudformation_client.describe_stacks(
            StackName=kwargs['stack_name'])
        # Get the client_id from the outputs
        outputs = stack_desc['Stacks'][0]['Outputs']

        if kwargs['user_pool_arn']:
            user_pool_id = kwargs['user_pool_arn'].split('/')[-1:][0]
        else:
            user_pool_id = [
                o['OutputValue'] for o in outputs
                if o['OutputKey'] == 'AuthAtEdgeUserPoolId'
            ][0]

        client_id = [
            o['OutputValue'] for o in outputs
            if o['OutputKey'] == 'AuthAtEdgeClient'
        ][0]

        # Poll the user pool client information
        resp = cognito_client.describe_user_pool_client(
            UserPoolId=user_pool_id, ClientId=client_id)

        # Retrieve the callbacks
        callbacks = resp['UserPoolClient']['CallbackURLs']

        if callbacks:
            context_dict['callback_urls'] = callbacks
        return context_dict
    except Exception:  # pylint: disable=broad-except
        return context_dict
示例#18
0
 def setUp(self) -> None:
     """Run before tests."""
     region = "us-east-1"
     self.session = get_session(region=region)
     self.provider = Provider(self.session, region=region, recreate_failed=False)
     self.stubber = Stubber(self.provider.cloudformation)