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
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), ], )
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)
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), ], )
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)
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), ], )
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"])
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), ], )
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
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
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
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), ], )
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)
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
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, )
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
def test_normalize_resource_path_windows(self, stack_location, path, normalized_path): self.assertEqual( SamLocalStackProvider.normalize_resource_path( stack_location, path), normalized_path)
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))
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