Esempio n. 1
0
    def __enter__(self) -> "BuildContext":

        self._stacks = SamLocalStackProvider.get_stacks(
            self._template_file, parameter_overrides=self._parameter_overrides
        )
        self._template_dict = SamLocalStackProvider.find_root_stack(self.stacks).template_dict

        self._function_provider = SamFunctionProvider(self.stacks)
        self._layer_provider = SamLayerProvider(self.stacks)

        if not self._base_dir:
            # Base directory, if not provided, is the directory containing the template
            self._base_dir = str(pathlib.Path(self._template_file).resolve().parent)

        self._build_dir = self._setup_build_dir(self._build_dir, self._clean)

        if self._cached:
            cache_path = pathlib.Path(self._cache_dir)
            cache_path.mkdir(mode=self._BUILD_DIR_PERMISSIONS, parents=True, exist_ok=True)
            self._cache_dir = str(cache_path.resolve())

        if self._use_container:
            self._container_manager = ContainerManager(
                docker_network_id=self._docker_network, skip_pull_image=self._skip_pull_image
            )

        return self
Esempio n. 2
0
 def test_remote_stack_is_skipped(self, resource_type,
                                  location_property_name):
     template = {
         "Resources": {
             "ChildStack": {
                 "Type": resource_type,
                 "Properties": {
                     location_property_name: "s3://bucket/key"
                 },
             }
         }
     }
     self.get_template_data_mock.side_effect = lambda t: {
         self.template_file: template,
     }.get(t)
     with patch.dict(
             os.environ,
         {SamLocalStackProvider.ENV_SAM_CLI_ENABLE_NESTED_STACK: "1"}):
         stacks = SamLocalStackProvider.get_stacks(
             self.template_file,
             "",
             "",
             parameter_overrides=None,
         )
     self.assertListEqual(
         stacks,
         [
             Stack("", "", self.template_file, None, template),
         ],
     )
Esempio n. 3
0
 def test_sam_nested_stack_template_path_can_be_resolved_if_root_template_is_not_in_working_dir(
         self, resource_type, location_property_name, child_location,
         child_location_path):
     template_file = "somedir/template.yaml"
     template = {
         "Resources": {
             "ChildStack": {
                 "Type": resource_type,
                 "Properties": {
                     location_property_name: child_location
                 },
             }
         }
     }
     self.get_template_data_mock.side_effect = lambda t: {
         template_file: template,
         child_location_path: LEAF_TEMPLATE,
     }.get(t)
     stacks, remote_stack_full_paths = SamLocalStackProvider.get_stacks(
         template_file,
         "",
         "",
         parameter_overrides=None,
     )
     self.assertListEqual(
         stacks,
         [
             Stack("", "", template_file, {}, template),
             Stack("", "ChildStack", child_location_path, {},
                   LEAF_TEMPLATE),
         ],
     )
     self.assertFalse(remote_stack_full_paths)
Esempio n. 4
0
    def test_global_parameter_overrides_can_be_passed_to_child_stacks(
        self, resource_type, location_property_name, child_location, child_location_path
    ):
        template_file = "somedir/template.yaml"
        template = {
            "Resources": {
                "ChildStack": {
                    "Type": resource_type,
                    "Properties": {location_property_name: child_location},
                }
            }
        }
        self.get_template_data_mock.side_effect = lambda t: {
            template_file: template,
            child_location_path: LEAF_TEMPLATE,
        }.get(t)

        global_parameter_overrides = {"AWS::Region": "custom_region"}

        stacks = SamLocalStackProvider.get_stacks(
            template_file, "", "", parameter_overrides=None, global_parameter_overrides=global_parameter_overrides
        )
        self.assertListEqual(
            stacks,
            [
                Stack("", "", template_file, global_parameter_overrides, template),
                Stack("", "ChildStack", child_location_path, global_parameter_overrides, LEAF_TEMPLATE),
            ],
        )
Esempio n. 5
0
 def test_sam_nested_stack_should_be_extracted(self, resource_type,
                                               location_property_name,
                                               child_location,
                                               child_location_path):
     template = {
         "Resources": {
             "ChildStack": {
                 "Type": resource_type,
                 "Properties": {
                     location_property_name: child_location
                 },
             }
         }
     }
     self.get_template_data_mock.side_effect = lambda t: {
         self.template_file: template,
         child_location_path: LEAF_TEMPLATE,
     }.get(t)
     stacks, remote_stack_full_paths = SamLocalStackProvider.get_stacks(
         self.template_file,
         "",
         "",
         parameter_overrides=None,
     )
     self.assertListEqual(
         stacks,
         [
             Stack("", "", self.template_file, {}, template),
             Stack("", "ChildStack", child_location_path, {},
                   LEAF_TEMPLATE),
         ],
     )
     self.assertFalse(remote_stack_full_paths)
Esempio n. 6
0
 def test_sam_nested_stack_template_path_can_be_resolved_if_root_template_is_not_in_working_dir(
         self, resource_type, location_property_name, child_location,
         child_location_path):
     template_file = "somedir/template.yaml"
     template = {
         "Resources": {
             "ChildStack": {
                 "Type": resource_type,
                 "Properties": {
                     location_property_name: child_location
                 },
             }
         }
     }
     self.get_template_data_mock.side_effect = lambda t: {
         template_file: template,
         child_location_path: LEAF_TEMPLATE,
     }.get(t)
     with patch.dict(
             os.environ,
         {SamLocalStackProvider.ENV_SAM_CLI_ENABLE_NESTED_STACK: "1"}):
         stacks = SamLocalStackProvider.get_stacks(
             template_file,
             "",
             "",
             parameter_overrides=None,
         )
     self.assertListEqual(
         stacks,
         [
             Stack("", "", template_file, None, template),
             Stack("", "ChildStack", child_location_path, None,
                   LEAF_TEMPLATE),
         ],
     )
Esempio n. 7
0
 def test_remote_stack_is_skipped(self, resource_type,
                                  location_property_name):
     template = {
         "Resources": {
             "ChildStack": {
                 "Type": resource_type,
                 "Properties": {
                     location_property_name: "s3://bucket/key"
                 },
             }
         }
     }
     self.get_template_data_mock.side_effect = lambda t: {
         self.template_file: template,
     }.get(t)
     stacks, remote_stack_full_paths = SamLocalStackProvider.get_stacks(
         self.template_file,
         "",
         "",
         parameter_overrides=None,
     )
     self.assertListEqual(
         stacks,
         [
             Stack("", "", self.template_file, {}, template),
         ],
     )
     self.assertEqual(remote_stack_full_paths, ["ChildStack"])
Esempio n. 8
0
 def test_sam_nested_stack_should_not_be_extracted_when_recursive_is_disabled(
         self, resource_type, location_property_name, child_location,
         child_location_path):
     template = {
         "Resources": {
             "ChildStack": {
                 "Type": resource_type,
                 "Properties": {
                     location_property_name: child_location
                 },
             }
         }
     }
     self.get_template_data_mock.side_effect = lambda t: {
         self.template_file: template,
         child_location_path: LEAF_TEMPLATE,
     }.get(t)
     with patch.dict(
             os.environ,
         {SamLocalStackProvider.ENV_SAM_CLI_ENABLE_NESTED_STACK: ""}):
         stacks = SamLocalStackProvider.get_stacks(
             self.template_file,
             "",
             "",
             parameter_overrides=None,
         )
     self.assertListEqual(
         stacks,
         [
             Stack("", "", self.template_file, None, template),
         ],
     )
Esempio n. 9
0
 def _get_stacks(self):
     try:
         return SamLocalStackProvider.get_stacks(
             self._template_file,
             parameter_overrides=self.parameter_overrides)
     except (TemplateNotFoundException,
             TemplateFailedParsingException) as ex:
         raise InvokeContextException(str(ex)) from ex
Esempio n. 10
0
 def _get_stacks(self) -> List[Stack]:
     try:
         stacks, _ = SamLocalStackProvider.get_stacks(
             self._template_file,
             parameter_overrides=self._parameter_overrides,
             global_parameter_overrides=self._global_parameter_overrides,
         )
         return stacks
     except (TemplateNotFoundException,
             TemplateFailedParsingException) as ex:
         raise InvokeContextException(str(ex)) from ex
Esempio n. 11
0
    def __enter__(self):
        """
        Performs some basic checks and returns itself when everything is ready to invoke a Lambda function.

        :returns InvokeContext: Returns this object
        """

        stacks = self._get_stacks()
        self._template_dict = SamLocalStackProvider.find_root_stack(
            stacks).template_dict
        self._function_provider = SamFunctionProvider(stacks)

        self._env_vars_value = self._get_env_vars_value(self._env_vars_file)
        self._container_env_vars_value = self._get_env_vars_value(
            self._container_env_vars_file)
        self._log_file_handle = self._setup_log_file(self._log_file)

        # in case of warm containers && debugging is enabled && if debug-function property is not provided, so
        # if the provided template only contains one lambda function, so debug-function will be set to this function
        # if the template contains multiple functions, a warning message "that the debugging option will be ignored"
        # will be printed
        if self._containers_mode == ContainersMode.WARM and self._debug_ports and not self._debug_function:
            if len(self._function_provider.functions) == 1:
                self._debug_function = list(
                    self._function_provider.functions.keys())[0]
            else:
                LOG.info(
                    "Warning: you supplied debugging options but you did not specify the --debug-function option."
                    " To specify which function you want to debug, please use the --debug-function <function-name>"
                )
                # skipp the debugging
                self._debug_ports = None

        self._debug_context = self._get_debug_context(
            self._debug_ports,
            self._debug_args,
            self._debugger_path,
            self._container_env_vars_value,
            self._debug_function,
        )

        self._container_manager = self._get_container_manager(
            self._docker_network, self._skip_pull_image, self._shutdown)

        if not self._container_manager.is_docker_reachable:
            raise InvokeContextException(
                "Running AWS SAM projects locally requires Docker. Have you got it installed and running?"
            )

        # initialize all lambda function containers upfront
        if self._containers_initializing_mode == ContainersInitializationMode.EAGER:
            self._initialize_all_functions_containers()

        return self
Esempio n. 12
0
 def test_sam_deep_nested_stack(self):
     child_template_file = "./child.yaml"
     grand_child_template_file = "./grand-child.yaml"
     template = {
         "Resources": {
             "ChildStack": {
                 "Type": AWS_SERVERLESS_APPLICATION,
                 "Properties": {
                     "Location": child_template_file
                 },
             }
         }
     }
     child_template = {
         "Resources": {
             "GrandChildStack": {
                 "Type": AWS_SERVERLESS_APPLICATION,
                 "Properties": {
                     "Location": grand_child_template_file
                 },
             }
         }
     }
     self.get_template_data_mock.side_effect = lambda t: {
         self.template_file: template,
         child_template_file: child_template,
         grand_child_template_file: LEAF_TEMPLATE,
     }.get(t)
     with patch.dict(
             os.environ,
         {SamLocalStackProvider.ENV_SAM_CLI_ENABLE_NESTED_STACK: "1"}):
         stacks = SamLocalStackProvider.get_stacks(
             self.template_file,
             "",
             "",
             parameter_overrides=None,
         )
     self.assertListEqual(
         stacks,
         [
             Stack("", "", self.template_file, None, template),
             Stack("", "ChildStack", child_template_file, None,
                   child_template),
             Stack("ChildStack", "GrandChildStack",
                   grand_child_template_file, None, LEAF_TEMPLATE),
         ],
     )
Esempio n. 13
0
 def test_sam_deep_nested_stack(self):
     child_template_file = "child.yaml"
     grand_child_template_file = "grand-child.yaml"
     template = {
         "Resources": {
             "ChildStack": {
                 "Type": AWS_SERVERLESS_APPLICATION,
                 "Properties": {
                     "Location": child_template_file
                 },
             }
         }
     }
     child_template = {
         "Resources": {
             "GrandChildStack": {
                 "Type": AWS_SERVERLESS_APPLICATION,
                 "Properties": {
                     "Location": grand_child_template_file
                 },
             }
         }
     }
     self.get_template_data_mock.side_effect = lambda t: {
         self.template_file: template,
         child_template_file: child_template,
         grand_child_template_file: LEAF_TEMPLATE,
     }.get(t)
     stacks, remote_stack_full_paths = SamLocalStackProvider.get_stacks(
         self.template_file,
         "",
         "",
         parameter_overrides=None,
     )
     self.assertListEqual(
         stacks,
         [
             Stack("", "", self.template_file, {}, template),
             Stack("", "ChildStack", child_template_file, {},
                   child_template),
             Stack("ChildStack", "GrandChildStack",
                   grand_child_template_file, {}, LEAF_TEMPLATE),
         ],
     )
     self.assertFalse(remote_stack_full_paths)
Esempio n. 14
0
    def __enter__(self) -> "BuildContext":

        self._stacks, remote_stack_full_paths = SamLocalStackProvider.get_stacks(
            self._template_file, parameter_overrides=self._parameter_overrides)

        if remote_stack_full_paths:
            LOG.warning(
                "Below nested stacks(s) specify non-local URL(s), which are unsupported:\n%s\n"
                "Skipping building resources inside these nested stacks.",
                "\n".join([
                    f"- {full_path}" for full_path in remote_stack_full_paths
                ]),
            )

        # Note(xinhol): self._use_raw_codeuri is added temporarily to fix issue #2717
        # when base_dir is provided, codeuri should not be resolved based on template file path.
        # we will refactor to make all path resolution inside providers intead of in multiple places
        self._function_provider = SamFunctionProvider(self.stacks,
                                                      self._use_raw_codeuri)
        self._layer_provider = SamLayerProvider(self.stacks,
                                                self._use_raw_codeuri)

        if not self._base_dir:
            # Base directory, if not provided, is the directory containing the template
            self._base_dir = str(
                pathlib.Path(self._template_file).resolve().parent)

        self._build_dir = self._setup_build_dir(self._build_dir, self._clean)

        if self._cached:
            cache_path = pathlib.Path(self._cache_dir)
            cache_path.mkdir(mode=self._BUILD_DIR_PERMISSIONS,
                             parents=True,
                             exist_ok=True)
            self._cache_dir = str(cache_path.resolve())

        if self._use_container:
            self._container_manager = ContainerManager(
                docker_network_id=self._docker_network,
                skip_pull_image=self._skip_pull_image)

        return self
Esempio n. 15
0
    def test_normalize_resource_path_symlink(self):
        """
        template: tmp_dir/some/path/template.yaml
        link1 (tmp_dir/symlinks/link1) -> ../some/path/template.yaml
        link2 (tmp_dir/symlinks/link1) -> tmp_dir/symlinks/link1
        resource_path (tmp_dir/some/path/src), raw path is "src"
        The final expected value is the actual value of resource_path, which is tmp_dir/some/path/src

        Skip the test on windows, due to symlink is not resolved consistently on Python:
        https://stackoverflow.com/questions/43333640/python-os-path-realpath-for-symlink-in-windows
        """
        with tempfile.TemporaryDirectory() as tmp_dir:
            Path(tmp_dir, "some", "path").mkdir(parents=True)
            Path(tmp_dir, "symlinks").mkdir(parents=True)

            link1 = os.path.join(tmp_dir, "symlinks", "link1")
            link2 = os.path.join(tmp_dir, "symlinks", "link2")

            resource_path = "src"

            # on mac, tmp_dir itself could be a symlink
            real_tmp_dir = os.path.realpath(tmp_dir)
            # SamLocalStackProvider.normalize_resource_path() always returns a relative path.
            # so expected is converted to relative path
            expected = os.path.relpath(
                os.path.join(real_tmp_dir, os.path.join("some", "path",
                                                        "src")))

            os.symlink(os.path.join("..", "some", "path", "template.yaml"),
                       link1)
            os.symlink("link1", link2)

            self.assertEqual(
                SamLocalStackProvider.normalize_resource_path(
                    link2, resource_path),
                expected,
            )
Esempio n. 16
0
    def guided_prompts(self, parameter_override_keys):
        default_stack_name = self.stack_name or "sam-app"
        default_region = self.region or get_session().get_config_variable(
            "region") or "us-east-1"
        default_capabilities = self.capabilities[0] or ("CAPABILITY_IAM", )
        default_config_env = self.config_env or DEFAULT_ENV
        default_config_file = self.config_file or DEFAULT_CONFIG_FILE_NAME
        input_capabilities = None
        config_env = None
        config_file = None

        click.echo(
            self.color.yellow(
                "\n\tSetting default arguments for 'sam deploy'\n\t========================================="
            ))

        stack_name = prompt(f"\t{self.start_bold}Stack Name{self.end_bold}",
                            default=default_stack_name,
                            type=click.STRING)
        region = prompt(f"\t{self.start_bold}AWS Region{self.end_bold}",
                        default=default_region,
                        type=click.STRING)
        input_parameter_overrides = self.prompt_parameters(
            parameter_override_keys, self.parameter_overrides_from_cmdline,
            self.start_bold, self.end_bold)
        stacks = SamLocalStackProvider.get_stacks(
            self.template_file,
            parameter_overrides=sanitize_parameter_overrides(
                input_parameter_overrides))
        image_repositories = self.prompt_image_repository(stacks)

        click.secho(
            "\t#Shows you resources changes to be deployed and require a 'Y' to initiate deploy"
        )
        confirm_changeset = confirm(
            f"\t{self.start_bold}Confirm changes before deploy{self.end_bold}",
            default=self.confirm_changeset)
        click.secho(
            "\t#SAM needs permission to be able to create roles to connect to the resources in your template"
        )
        capabilities_confirm = confirm(
            f"\t{self.start_bold}Allow SAM CLI IAM role creation{self.end_bold}",
            default=True)

        if not capabilities_confirm:
            input_capabilities = prompt(
                f"\t{self.start_bold}Capabilities{self.end_bold}",
                default=list(default_capabilities),
                type=FuncParamType(func=_space_separated_list_func_type),
            )

        self.prompt_authorization(stacks)
        self.prompt_code_signing_settings(stacks)

        save_to_config = confirm(
            f"\t{self.start_bold}Save arguments to configuration file{self.end_bold}",
            default=True)
        if save_to_config:
            config_file = prompt(
                f"\t{self.start_bold}SAM configuration file{self.end_bold}",
                default=default_config_file,
                type=click.STRING,
            )
            config_env = prompt(
                f"\t{self.start_bold}SAM configuration environment{self.end_bold}",
                default=default_config_env,
                type=click.STRING,
            )

        s3_bucket = manage_stack(profile=self.profile, region=region)
        click.echo(f"\n\t\tManaged S3 bucket: {s3_bucket}")
        click.echo(
            "\t\tA different default S3 bucket can be set in samconfig.toml")

        self.guided_stack_name = stack_name
        self.guided_s3_bucket = s3_bucket
        self.guided_image_repositories = image_repositories
        self.guided_s3_prefix = stack_name
        self.guided_region = region
        self.guided_profile = self.profile
        self._capabilities = input_capabilities if input_capabilities else default_capabilities
        self._parameter_overrides = (input_parameter_overrides
                                     if input_parameter_overrides else
                                     self.parameter_overrides_from_cmdline)
        self.save_to_config = save_to_config
        self.config_env = config_env if config_env else default_config_env
        self.config_file = config_file if config_file else default_config_file
        self.confirm_changeset = confirm_changeset
Esempio n. 17
0
 def test_normalize_resource_path_windows(self, stack_location, path,
                                          normalized_path):
     self.assertEqual(
         SamLocalStackProvider.normalize_resource_path(
             stack_location, path), normalized_path)
Esempio n. 18
0
    def deploy(
        self,
        stack_name,
        template_str,
        parameters,
        capabilities,
        no_execute_changeset,
        role_arn,
        notification_arns,
        s3_uploader,
        tags,
        region,
        fail_on_empty_changeset=True,
        confirm_changeset=False,
    ):
        """
        Deploy the stack to cloudformation.
        - if changeset needs confirmation, it will prompt for customers to confirm.
        - if no_execute_changeset is True, the changeset won't be executed.

        Parameters
        ----------
        stack_name : str
            name of the stack
        template_str : str
            the string content of the template
        parameters : List[Dict]
            List of parameters
        capabilities : List[str]
            List of capabilities
        no_execute_changeset : bool
            A bool indicating whether to execute changeset
        role_arn : str
            the Arn of the role to create changeset
        notification_arns : List[str]
            Arns for sending notifications
        s3_uploader : S3Uploader
            S3Uploader object to upload files to S3 buckets
        tags : List[str]
            List of tags passed to CloudFormation
        region : str
            AWS region to deploy the stack to
        fail_on_empty_changeset : bool
            Should fail when changeset is empty
        confirm_changeset : bool
            Should wait for customer's confirm before executing the changeset
        """
        stacks, _ = SamLocalStackProvider.get_stacks(
            self.template_file,
            parameter_overrides=sanitize_parameter_overrides(
                self.parameter_overrides))
        auth_required_per_resource = auth_per_resource(stacks)

        for resource, authorization_required in auth_required_per_resource:
            if not authorization_required:
                click.secho(f"{resource} may not have authorization defined.",
                            fg="yellow")

        try:
            result, changeset_type = self.deployer.create_and_wait_for_changeset(
                stack_name=stack_name,
                cfn_template=template_str,
                parameter_values=parameters,
                capabilities=capabilities,
                role_arn=role_arn,
                notification_arns=notification_arns,
                s3_uploader=s3_uploader,
                tags=tags,
            )
            click.echo(
                self.MSG_SHOWCASE_CHANGESET.format(changeset_id=result["Id"]))

            if no_execute_changeset:
                return

            if confirm_changeset:
                click.secho(self.MSG_CONFIRM_CHANGESET_HEADER, fg="yellow")
                click.secho("=" * len(self.MSG_CONFIRM_CHANGESET_HEADER),
                            fg="yellow")
                if not click.confirm(f"{self.MSG_CONFIRM_CHANGESET}",
                                     default=False):
                    return

            self.deployer.execute_changeset(result["Id"], stack_name)
            self.deployer.wait_for_execute(stack_name, changeset_type)
            click.echo(
                self.MSG_EXECUTE_SUCCESS.format(stack_name=stack_name,
                                                region=region))

        except deploy_exceptions.ChangeEmptyError as ex:
            if fail_on_empty_changeset:
                raise
            click.echo(str(ex))
Esempio n. 19
0
def do_cli(  # pylint: disable=too-many-locals, too-many-statements
    function_identifier: Optional[str],
    template: str,
    base_dir: Optional[str],
    build_dir: str,
    cache_dir: str,
    clean: bool,
    use_container: bool,
    cached: bool,
    parallel: bool,
    manifest_path: Optional[str],
    docker_network: Optional[str],
    skip_pull_image: bool,
    parameter_overrides: Dict,
    mode: Optional[str],
    container_env_var: Optional[Tuple[str]],
    container_env_var_file: Optional[str],
    build_image: Optional[Tuple[str]],
) -> None:
    """
    Implementation of the ``cli`` method
    """

    from samcli.commands.exceptions import UserException

    from samcli.commands.build.build_context import BuildContext
    from samcli.lib.build.app_builder import (
        ApplicationBuilder,
        BuildError,
        UnsupportedBuilderLibraryVersionError,
        ContainerBuildNotSupported,
    )
    from samcli.lib.build.workflow_config import UnsupportedRuntimeException
    from samcli.local.lambdafn.exceptions import FunctionNotFound
    from samcli.commands._utils.template import move_template
    from samcli.lib.build.build_graph import InvalidBuildGraphException

    LOG.debug("'build' command is called")
    if cached:
        LOG.info("Starting Build use cache")
    if use_container:
        LOG.info("Starting Build inside a container")

    processed_env_vars = _process_env_var(container_env_var)
    processed_build_images = _process_image_options(build_image)

    with BuildContext(
            function_identifier,
            template,
            base_dir,
            build_dir,
            cache_dir,
            cached,
            clean=clean,
            manifest_path=manifest_path,
            use_container=use_container,
            parameter_overrides=parameter_overrides,
            docker_network=docker_network,
            skip_pull_image=skip_pull_image,
            mode=mode,
            container_env_var=processed_env_vars,
            container_env_var_file=container_env_var_file,
            build_images=processed_build_images,
    ) as ctx:
        try:
            builder = ApplicationBuilder(
                ctx.resources_to_build,
                ctx.build_dir,
                ctx.base_dir,
                ctx.cache_dir,
                ctx.cached,
                ctx.is_building_specific_resource,
                manifest_path_override=ctx.manifest_path_override,
                container_manager=ctx.container_manager,
                mode=ctx.mode,
                parallel=parallel,
                container_env_var=processed_env_vars,
                container_env_var_file=container_env_var_file,
                build_images=processed_build_images,
            )
        except FunctionNotFound as ex:
            raise UserException(str(ex),
                                wrapped_from=ex.__class__.__name__) from ex

        try:
            artifacts = builder.build()

            stack_output_template_path_by_stack_path = {
                stack.stack_path: stack.get_output_template_path(ctx.build_dir)
                for stack in ctx.stacks
            }
            for stack in ctx.stacks:
                modified_template = builder.update_template(
                    stack,
                    artifacts,
                    stack_output_template_path_by_stack_path,
                )
                move_template(stack.location,
                              stack.get_output_template_path(ctx.build_dir),
                              modified_template)

            click.secho("\nBuild Succeeded", fg="green")

            # try to use relpath so the command is easier to understand, however,
            # under Windows, when SAM and (build_dir or output_template_path) are
            # on different drive, relpath() fails.
            root_stack = SamLocalStackProvider.find_root_stack(ctx.stacks)
            out_template_path = root_stack.get_output_template_path(
                ctx.build_dir)
            try:
                build_dir_in_success_message = os.path.relpath(ctx.build_dir)
                output_template_path_in_success_message = os.path.relpath(
                    out_template_path)
            except ValueError:
                LOG.debug(
                    "Failed to retrieve relpath - using the specified path as-is instead"
                )
                build_dir_in_success_message = ctx.build_dir
                output_template_path_in_success_message = out_template_path

            msg = gen_success_msg(
                build_dir_in_success_message,
                output_template_path_in_success_message,
                os.path.abspath(
                    ctx.build_dir) == os.path.abspath(DEFAULT_BUILD_DIR),
            )

            click.secho(msg, fg="yellow")

        except (
                UnsupportedRuntimeException,
                BuildError,
                BuildInsideContainerError,
                UnsupportedBuilderLibraryVersionError,
                ContainerBuildNotSupported,
                InvalidBuildGraphException,
        ) as ex:
            click.secho("\nBuild Failed", fg="red")

            # Some Exceptions have a deeper wrapped exception that needs to be surfaced
            # from deeper than just one level down.
            deep_wrap = getattr(ex, "wrapped_from", None)
            wrapped_from = deep_wrap if deep_wrap else ex.__class__.__name__
            raise UserException(str(ex), wrapped_from=wrapped_from) from ex