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_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 __enter__(self) -> "BuildContext": self._stacks = SamLocalStackProvider.get_stacks( self._template_file, parameter_overrides=self._parameter_overrides) 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) 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 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_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) 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 _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 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 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 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))