From a48fafa80827093b13e981735c8e7cc78083e780 Mon Sep 17 00:00:00 2001 From: Daniel Mil Date: Tue, 20 Jun 2023 14:24:58 -0700 Subject: [PATCH 1/6] Add parameter for plan file --- .../_utils/custom_options/hook_name_option.py | 3 +- samcli/commands/local/invoke/cli.py | 2 + .../terraform/hooks/prepare/hook.py | 49 +++++++++++-------- samcli/lib/hook/hook_wrapper.py | 3 ++ 4 files changed, 35 insertions(+), 22 deletions(-) diff --git a/samcli/commands/_utils/custom_options/hook_name_option.py b/samcli/commands/_utils/custom_options/hook_name_option.py index 745ab6c64a..73c02d6508 100644 --- a/samcli/commands/_utils/custom_options/hook_name_option.py +++ b/samcli/commands/_utils/custom_options/hook_name_option.py @@ -86,9 +86,10 @@ def _call_prepare_hook(self, iac_hook_wrapper, opts): aws_profile = opts.get("profile") aws_region = opts.get("region") skip_prepare_infra = opts.get("skip_prepare_infra", False) + plan_file = opts.get("plan_file") metadata_file = iac_hook_wrapper.prepare( - output_dir_path, iac_project_path, debug, aws_profile, aws_region, skip_prepare_infra + output_dir_path, iac_project_path, debug, aws_profile, aws_region, skip_prepare_infra, plan_file ) LOG.info("Prepare hook completed and metadata file generated at: %s", metadata_file) diff --git a/samcli/commands/local/invoke/cli.py b/samcli/commands/local/invoke/cli.py index a9a3fc9571..6d6316d689 100644 --- a/samcli/commands/local/invoke/cli.py +++ b/samcli/commands/local/invoke/cli.py @@ -44,6 +44,7 @@ context_settings={"max_content_width": 120}, ) @configuration_option(provider=TomlProvider(section="parameters")) +@click.option("--plan-file", type=click.Path()) @hook_name_click_option( force_prepare=False, invalid_coexist_options=["t", "template-file", "template", "parameter-overrides"] ) @@ -91,6 +92,7 @@ def cli( invoke_image, hook_name, skip_prepare_infra, + plan_file, ): """ `sam local invoke` command entry point diff --git a/samcli/hook_packages/terraform/hooks/prepare/hook.py b/samcli/hook_packages/terraform/hooks/prepare/hook.py index ccbd357f39..e495dd8e46 100644 --- a/samcli/hook_packages/terraform/hooks/prepare/hook.py +++ b/samcli/hook_packages/terraform/hooks/prepare/hook.py @@ -58,6 +58,8 @@ def prepare(params: dict) -> dict: skip_prepare_infra = params.get("SkipPrepareInfra") metadata_file_path = os.path.join(output_dir_path, TERRAFORM_METADATA_FILE) + plan_file = params.get("PlanFile") + if skip_prepare_infra and os.path.exists(metadata_file_path): LOG.info("Skipping preparation stage, the metadata file already exists at %s", metadata_file_path) else: @@ -71,33 +73,38 @@ def prepare(params: dict) -> dict: ) try: # initialize terraform application - LOG.info(log_msg) - invoke_subprocess_with_loading_pattern( - command_args={ - "args": ["terraform", "init", "-input=false"], - "cwd": terraform_application_dir, - } - ) - - # get json output of terraform plan - LOG.info("Creating terraform plan and getting JSON output") - with osutils.tempfile_platform_independent() as temp_file: + if not plan_file: + LOG.info(log_msg) invoke_subprocess_with_loading_pattern( - # input false to avoid SAM CLI to stuck in case if the - # Terraform project expects input, and customer does not provide it. command_args={ - "args": ["terraform", "plan", "-out", temp_file.name, "-input=false"], + "args": ["terraform", "init", "-input=false"], "cwd": terraform_application_dir, } ) - result = run( - ["terraform", "show", "-json", temp_file.name], - check=True, - capture_output=True, - cwd=terraform_application_dir, - ) - tf_json = json.loads(result.stdout) + # get json output of terraform plan + LOG.info("Creating terraform plan and getting JSON output") + with osutils.tempfile_platform_independent() as temp_file: + invoke_subprocess_with_loading_pattern( + # input false to avoid SAM CLI to stuck in case if the + # Terraform project expects input, and customer does not provide it. + command_args={ + "args": ["terraform", "plan", "-out", temp_file.name, "-input=false"], + "cwd": terraform_application_dir, + } + ) + + result = run( + ["terraform", "show", "-json", temp_file.name], + check=True, + capture_output=True, + cwd=terraform_application_dir, + ) + tf_json = json.loads(result.stdout) + else: + LOG.info(f"Using provided plan file: {plan_file}") + with open(plan_file, 'r') as f: + tf_json = json.load(f) # convert terraform to cloudformation LOG.info("Generating metadata file") diff --git a/samcli/lib/hook/hook_wrapper.py b/samcli/lib/hook/hook_wrapper.py index 2bcaa4bd2c..0f90f1a1fa 100644 --- a/samcli/lib/hook/hook_wrapper.py +++ b/samcli/lib/hook/hook_wrapper.py @@ -51,6 +51,7 @@ def prepare( aws_profile: Optional[str] = None, aws_region: Optional[str] = None, skip_prepare_infra: bool = False, + plan_file: str = False, ) -> str: """ Run the prepare hook to generate the IaC Metadata file. @@ -86,6 +87,8 @@ def prepare( params["Profile"] = aws_profile if aws_region: params["Region"] = aws_region + if plan_file: + params["PlanFile"] = plan_file output = self._execute("prepare", params) From 87c47b9d89b17dc278cee15092c4bb8786bf861d Mon Sep 17 00:00:00 2001 From: Daniel Mil Date: Wed, 9 Aug 2023 14:37:14 -0700 Subject: [PATCH 2/6] feat: Support custom plan files --- samcli/commands/_utils/options.py | 10 +++ samcli/commands/build/command.py | 3 + samcli/commands/build/core/options.py | 4 + samcli/commands/local/invoke/cli.py | 4 +- samcli/commands/local/invoke/core/options.py | 4 + samcli/commands/local/start_api/cli.py | 3 + .../commands/local/start_api/core/options.py | 4 + samcli/commands/local/start_lambda/cli.py | 3 + .../local/start_lambda/core/options.py | 4 + .../terraform/hooks/prepare/hook.py | 77 ++++++++++--------- samcli/lib/hook/hook_wrapper.py | 4 +- .../test_hook_package_id_option.py | 12 ++- .../commands/buildcmd/core/test_command.py | 2 + .../local/invoke/core/test_command.py | 2 + .../local/start_api/core/test_command.py | 2 + .../local/start_lambda/core/test_command.py | 2 + .../terraform/hooks/prepare/test_hook.py | 31 +++++++- 17 files changed, 127 insertions(+), 44 deletions(-) diff --git a/samcli/commands/_utils/options.py b/samcli/commands/_utils/options.py index aa57f65f2c..c5a8b6317e 100644 --- a/samcli/commands/_utils/options.py +++ b/samcli/commands/_utils/options.py @@ -789,6 +789,16 @@ def use_container_build_option(f): return use_container_build_click_option()(f) +def plan_file_click_option(): + return click.option( + "--plan-file", type=click.Path(), help="Used for passing a custom plan file when executing the Terraform hook." + ) + + +def plan_file_option(f): + return plan_file_click_option()(f) + + def build_image_click_option(cls): return click.option( "--build-image", diff --git a/samcli/commands/build/command.py b/samcli/commands/build/command.py index 17c177a1e1..f18f0a809e 100644 --- a/samcli/commands/build/command.py +++ b/samcli/commands/build/command.py @@ -22,6 +22,7 @@ use_container_build_option, build_image_option, hook_name_click_option, + plan_file_option, ) from samcli.commands._utils.option_value_processor import process_env_var, process_image_options from samcli.cli.main import pass_context, common_options as cli_framework_options, aws_creds_options, print_cmdline_args @@ -70,6 +71,7 @@ context_settings={"max_content_width": 120}, ) @configuration_option(provider=ConfigProvider(section="parameters")) +@plan_file_option @hook_name_click_option( force_prepare=True, invalid_coexist_options=["t", "template-file", "template", "parameter-overrides"], @@ -155,6 +157,7 @@ def cli( hook_name: Optional[str], skip_prepare_infra: bool, mount_with, + plan_file, ) -> None: """ `sam build` command entry point diff --git a/samcli/commands/build/core/options.py b/samcli/commands/build/core/options.py index 103808672c..e80590051d 100644 --- a/samcli/commands/build/core/options.py +++ b/samcli/commands/build/core/options.py @@ -37,6 +37,8 @@ TEMPLATE_OPTIONS: List[str] = ["parameter_overrides"] +HOOK_OPTIONS: List[str] = ["plan_file"] + ALL_OPTIONS: List[str] = ( REQUIRED_OPTIONS + TEMPLATE_OPTIONS @@ -47,6 +49,7 @@ + EXTENSION_OPTIONS + CONFIGURATION_OPTION_NAMES + ALL_COMMON_OPTIONS + + HOOK_OPTIONS ) OPTIONS_INFO: Dict[str, Dict] = { @@ -71,5 +74,6 @@ ), ], }, + "Hook Options": {"option_names": {opt: {"rank": idx} for idx, opt in enumerate(HOOK_OPTIONS)}}, } add_common_options_info(OPTIONS_INFO) diff --git a/samcli/commands/local/invoke/cli.py b/samcli/commands/local/invoke/cli.py index 51b0f69bd8..764db32a1a 100644 --- a/samcli/commands/local/invoke/cli.py +++ b/samcli/commands/local/invoke/cli.py @@ -11,7 +11,7 @@ from samcli.cli.main import common_options as cli_framework_options from samcli.commands._utils.experimental import ExperimentalFlag, is_experimental_enabled from samcli.commands._utils.option_value_processor import process_image_options -from samcli.commands._utils.options import hook_name_click_option, skip_prepare_infra_option +from samcli.commands._utils.options import hook_name_click_option, plan_file_option, skip_prepare_infra_option from samcli.commands.local.cli_common.options import invoke_common_options, local_common_options from samcli.commands.local.invoke.core.command import InvokeCommand from samcli.commands.local.lib.exceptions import InvalidIntermediateImageError @@ -44,7 +44,7 @@ context_settings={"max_content_width": 120}, ) @configuration_option(provider=ConfigProvider(section="parameters")) -@click.option("--plan-file", type=click.Path()) +@plan_file_option @hook_name_click_option( force_prepare=False, invalid_coexist_options=["t", "template-file", "template", "parameter-overrides"] ) diff --git a/samcli/commands/local/invoke/core/options.py b/samcli/commands/local/invoke/core/options.py index b11f33eaa9..c5c3dc07f0 100644 --- a/samcli/commands/local/invoke/core/options.py +++ b/samcli/commands/local/invoke/core/options.py @@ -46,6 +46,8 @@ OTHER_OPTIONS: List[str] = ["debug"] +HOOK_OPTIONS: List[str] = ["plan_file"] + ALL_OPTIONS: List[str] = ( REQUIRED_OPTIONS + TEMPLATE_OPTIONS @@ -55,6 +57,7 @@ + EXTENSION_OPTIONS + CONFIGURATION_OPTION_NAMES + ALL_COMMON_OPTIONS + + HOOK_OPTIONS ) OPTIONS_INFO: Dict[str, Dict] = { @@ -78,6 +81,7 @@ ), ], }, + "Hook Options": {"option_names": {opt: {"rank": idx} for idx, opt in enumerate(HOOK_OPTIONS)}}, } add_common_options_info(OPTIONS_INFO) diff --git a/samcli/commands/local/start_api/cli.py b/samcli/commands/local/start_api/cli.py index 7c712c4561..42d89f4735 100644 --- a/samcli/commands/local/start_api/cli.py +++ b/samcli/commands/local/start_api/cli.py @@ -14,6 +14,7 @@ from samcli.commands._utils.options import ( generate_next_command_recommendation, hook_name_click_option, + plan_file_option, skip_prepare_infra_option, ) from samcli.commands.local.cli_common.options import ( @@ -59,6 +60,7 @@ context_settings={"max_content_width": 120}, ) @configuration_option(provider=ConfigProvider(section="parameters")) +@plan_file_option @hook_name_click_option( force_prepare=False, invalid_coexist_options=["t", "template-file", "template", "parameter-overrides"] ) @@ -109,6 +111,7 @@ def cli( invoke_image, hook_name, skip_prepare_infra, + plan_file, ): """ `sam local start-api` command entry point diff --git a/samcli/commands/local/start_api/core/options.py b/samcli/commands/local/start_api/core/options.py index 21bb1bf822..6eaf1500d5 100644 --- a/samcli/commands/local/start_api/core/options.py +++ b/samcli/commands/local/start_api/core/options.py @@ -47,6 +47,8 @@ "static_dir", ] +HOOK_OPTIONS: List[str] = ["plan_file"] + ALL_OPTIONS: List[str] = ( REQUIRED_OPTIONS + TEMPLATE_OPTIONS @@ -56,6 +58,7 @@ + CONFIGURATION_OPTION_NAMES + ALL_COMMON_OPTIONS + EXTENSION_OPTIONS + + HOOK_OPTIONS ) OPTIONS_INFO: Dict[str, Dict] = { @@ -79,6 +82,7 @@ ), ], }, + "Hook Options": {"option_names": {opt: {"rank": idx} for idx, opt in enumerate(HOOK_OPTIONS)}}, } add_common_options_info(OPTIONS_INFO) diff --git a/samcli/commands/local/start_lambda/cli.py b/samcli/commands/local/start_lambda/cli.py index 744f7f8fe5..698b98c598 100644 --- a/samcli/commands/local/start_lambda/cli.py +++ b/samcli/commands/local/start_lambda/cli.py @@ -14,6 +14,7 @@ from samcli.commands._utils.options import ( generate_next_command_recommendation, hook_name_click_option, + plan_file_option, skip_prepare_infra_option, ) from samcli.commands.local.cli_common.options import ( @@ -53,6 +54,7 @@ context_settings={"max_content_width": 120}, ) @configuration_option(provider=ConfigProvider(section="parameters")) +@plan_file_option @hook_name_click_option( force_prepare=False, invalid_coexist_options=["t", "template-file", "template", "parameter-overrides"] ) @@ -96,6 +98,7 @@ def cli( invoke_image, hook_name, skip_prepare_infra, + plan_file, ): """ `sam local start-lambda` command entry point diff --git a/samcli/commands/local/start_lambda/core/options.py b/samcli/commands/local/start_lambda/core/options.py index 6d6168c18f..4ef011f732 100644 --- a/samcli/commands/local/start_lambda/core/options.py +++ b/samcli/commands/local/start_lambda/core/options.py @@ -46,6 +46,8 @@ CONFIGURATION_OPTION_NAMES: List[str] = ["config_env", "config_file"] +HOOK_OPTIONS: List[str] = ["plan_file"] + ALL_OPTIONS: List[str] = ( REQUIRED_OPTIONS + TEMPLATE_OPTIONS @@ -55,6 +57,7 @@ + EXTENSION_OPTIONS + CONFIGURATION_OPTION_NAMES + ALL_COMMON_OPTIONS + + HOOK_OPTIONS ) OPTIONS_INFO: Dict[str, Dict] = { @@ -78,6 +81,7 @@ ), ], }, + "Hook Options": {"option_names": {opt: {"rank": idx} for idx, opt in enumerate(HOOK_OPTIONS)}}, } add_common_options_info(OPTIONS_INFO) diff --git a/samcli/hook_packages/terraform/hooks/prepare/hook.py b/samcli/hook_packages/terraform/hooks/prepare/hook.py index e495dd8e46..77d2f375f3 100644 --- a/samcli/hook_packages/terraform/hooks/prepare/hook.py +++ b/samcli/hook_packages/terraform/hooks/prepare/hook.py @@ -63,47 +63,14 @@ def prepare(params: dict) -> dict: if skip_prepare_infra and os.path.exists(metadata_file_path): LOG.info("Skipping preparation stage, the metadata file already exists at %s", metadata_file_path) else: - log_msg = ( - ( - "The option to skip infrastructure preparation was provided, but AWS SAM CLI could not find " - f"the metadata file. Preparing anyways.{os.linesep}Initializing Terraform application" - ) - if skip_prepare_infra - else "Initializing Terraform application" - ) + try: # initialize terraform application if not plan_file: - LOG.info(log_msg) - invoke_subprocess_with_loading_pattern( - command_args={ - "args": ["terraform", "init", "-input=false"], - "cwd": terraform_application_dir, - } - ) - - # get json output of terraform plan - LOG.info("Creating terraform plan and getting JSON output") - with osutils.tempfile_platform_independent() as temp_file: - invoke_subprocess_with_loading_pattern( - # input false to avoid SAM CLI to stuck in case if the - # Terraform project expects input, and customer does not provide it. - command_args={ - "args": ["terraform", "plan", "-out", temp_file.name, "-input=false"], - "cwd": terraform_application_dir, - } - ) - - result = run( - ["terraform", "show", "-json", temp_file.name], - check=True, - capture_output=True, - cwd=terraform_application_dir, - ) - tf_json = json.loads(result.stdout) + tf_json = _generate_plan_file(skip_prepare_infra, terraform_application_dir) else: LOG.info(f"Using provided plan file: {plan_file}") - with open(plan_file, 'r') as f: + with open(plan_file, "r") as f: tf_json = json.load(f) # convert terraform to cloudformation @@ -174,3 +141,41 @@ def _update_resources_paths(cfn_resources: Dict[str, Any], terraform_application original_path = resource.get("Properties", {}).get(attribute) if isinstance(original_path, str) and not os.path.isabs(original_path): resource["Properties"][attribute] = str(Path(terraform_application_dir).joinpath(original_path)) + + +def _generate_plan_file(skip_prepare_infra, terraform_application_dir) -> dict: + log_msg = ( + ( + "The option to skip infrastructure preparation was provided, but AWS SAM CLI could not find " + f"the metadata file. Preparing anyways.{os.linesep}Initializing Terraform application" + ) + if skip_prepare_infra + else "Initializing Terraform application" + ) + LOG.info(log_msg) + invoke_subprocess_with_loading_pattern( + command_args={ + "args": ["terraform", "init", "-input=false"], + "cwd": terraform_application_dir, + } + ) + + # get json output of terraform plan + LOG.info("Creating terraform plan and getting JSON output") + with osutils.tempfile_platform_independent() as temp_file: + invoke_subprocess_with_loading_pattern( + # input false to avoid SAM CLI to stuck in case if the + # Terraform project expects input, and customer does not provide it. + command_args={ + "args": ["terraform", "plan", "-out", temp_file.name, "-input=false"], + "cwd": terraform_application_dir, + } + ) + + result = run( + ["terraform", "show", "-json", temp_file.name], + check=True, + capture_output=True, + cwd=terraform_application_dir, + ) + return dict(json.loads(result.stdout)) diff --git a/samcli/lib/hook/hook_wrapper.py b/samcli/lib/hook/hook_wrapper.py index 0f90f1a1fa..47b0a2fc25 100644 --- a/samcli/lib/hook/hook_wrapper.py +++ b/samcli/lib/hook/hook_wrapper.py @@ -51,7 +51,7 @@ def prepare( aws_profile: Optional[str] = None, aws_region: Optional[str] = None, skip_prepare_infra: bool = False, - plan_file: str = False, + plan_file: Optional[str] = None, ) -> str: """ Run the prepare hook to generate the IaC Metadata file. @@ -70,6 +70,8 @@ def prepare( AWS region to use. Default is None (use default region) skip_prepare_infra: bool Flag to skip skip prepare hook if we already have the metadata file. Default is False. + plan_file: Optional[str] + Provided plan file to use instead of generating one from the hook Returns ------- diff --git a/tests/unit/commands/_utils/custom_options/test_hook_package_id_option.py b/tests/unit/commands/_utils/custom_options/test_hook_package_id_option.py index 807d10c7f0..8e7c607d97 100644 --- a/tests/unit/commands/_utils/custom_options/test_hook_package_id_option.py +++ b/tests/unit/commands/_utils/custom_options/test_hook_package_id_option.py @@ -92,7 +92,7 @@ def test_valid_hook_package_with_only_hook_id_option( args = [] hook_name_option.handle_parse_result(ctx, opts, args) self.iac_hook_wrapper_instance_mock.prepare.assert_called_once_with( - os.path.join(self.cwd_path, ".aws-sam-iacs", "iacs_metadata"), self.cwd_path, False, None, None, False + os.path.join(self.cwd_path, ".aws-sam-iacs", "iacs_metadata"), self.cwd_path, False, None, None, False, None ) self.assertEqual(opts.get("template_file"), self.metadata_path) @@ -129,6 +129,7 @@ def test_valid_hook_package_with_other_options( "test", "us-east-1", False, + None, ) self.assertEqual(opts.get("template_file"), self.metadata_path) @@ -276,6 +277,7 @@ def test_valid_hook_package_with_beta_feature_option( None, None, False, + None, ) self.assertEqual(opts.get("template_file"), self.metadata_path) @@ -323,6 +325,7 @@ def test_valid_hook_package_with_beta_feature_option_in_sam_config( None, None, False, + None, ) self.assertEqual(opts.get("template_file"), metadata_path) @@ -375,6 +378,7 @@ def test_valid_hook_package_with_beta_feature_option_in_environment_variable( None, None, False, + None, ) self.assertEqual(opts.get("template_file"), metadata_path) @@ -410,7 +414,7 @@ def test_valid_hook_package_with_skipping_prepare_hook_and_built_path_does_not_e args = [] hook_name_option.handle_parse_result(ctx, opts, args) self.iac_hook_wrapper_instance_mock.prepare.assert_called_once_with( - os.path.join(self.cwd_path, ".aws-sam-iacs", "iacs_metadata"), self.cwd_path, False, None, None, False + os.path.join(self.cwd_path, ".aws-sam-iacs", "iacs_metadata"), self.cwd_path, False, None, None, False, None ) self.assertEqual(opts.get("template_file"), self.metadata_path) @@ -449,7 +453,7 @@ def test_valid_hook_package_with_use_container_and_build_image( args = [] hook_name_option.handle_parse_result(ctx, opts, args) self.iac_hook_wrapper_instance_mock.prepare.assert_called_once_with( - os.path.join(self.cwd_path, ".aws-sam-iacs", "iacs_metadata"), self.cwd_path, False, None, None, False + os.path.join(self.cwd_path, ".aws-sam-iacs", "iacs_metadata"), self.cwd_path, False, None, None, False, None ) self.assertEqual(opts.get("template_file"), self.metadata_path) @@ -525,6 +529,6 @@ def test_valid_hook_package_with_use_container_false_and_no_build_image( args = [] hook_name_option.handle_parse_result(ctx, opts, args) self.iac_hook_wrapper_instance_mock.prepare.assert_called_once_with( - os.path.join(self.cwd_path, ".aws-sam-iacs", "iacs_metadata"), self.cwd_path, False, None, None, False + os.path.join(self.cwd_path, ".aws-sam-iacs", "iacs_metadata"), self.cwd_path, False, None, None, False, None ) self.assertEqual(opts.get("template_file"), self.metadata_path) diff --git a/tests/unit/commands/buildcmd/core/test_command.py b/tests/unit/commands/buildcmd/core/test_command.py index 2869e4d0b2..df0735ffc1 100644 --- a/tests/unit/commands/buildcmd/core/test_command.py +++ b/tests/unit/commands/buildcmd/core/test_command.py @@ -33,6 +33,7 @@ def test_get_options_build_command_text(self, mock_get_params): MockParams(rv=("--parameter-overrides", ""), name="parameter_overrides"), MockParams(rv=("--beta-features", ""), name="beta_features"), MockParams(rv=("--template-file", ""), name="template_file"), + MockParams(rv=("--plan-file", ""), name="plan_file"), ] cmd = BuildCommand(name="sync", requires_credentials=False, description=DESCRIPTION) @@ -41,6 +42,7 @@ def test_get_options_build_command_text(self, mock_get_params): "Container Options": [("", ""), ("--use-container", ""), ("", "")], "Configuration Options": [("", ""), ("--config-file", ""), ("", "")], "Extension Options": [("", ""), ("--hook-name", ""), ("", "")], + "Hook Options": [('', ''), ('--plan-file', ''), ('', '')], "Build Strategy Options": [("", ""), ("--parallel", ""), ("", "")], "Artifact Location Options": [("", ""), ("--build-dir", ""), ("", "")], "Template Options": [("", ""), ("--parameter-overrides", ""), ("", "")], diff --git a/tests/unit/commands/local/invoke/core/test_command.py b/tests/unit/commands/local/invoke/core/test_command.py index 78431388ac..0106e2d533 100644 --- a/tests/unit/commands/local/invoke/core/test_command.py +++ b/tests/unit/commands/local/invoke/core/test_command.py @@ -32,6 +32,7 @@ def test_get_options_local_invoke_command_text(self, mock_get_params): MockParams(rv=("--log-file", ""), name="log_file"), MockParams(rv=("--beta-features", ""), name="beta_features"), MockParams(rv=("--debug", ""), name="debug"), + MockParams(rv=("--plan-file", ""), name="plan_file"), ] cmd = InvokeCommand(name="local invoke", requires_credentials=False, description=DESCRIPTION) @@ -43,6 +44,7 @@ def test_get_options_local_invoke_command_text(self, mock_get_params): "Description": [(cmd.description + cmd.description_addendum, "")], "Examples": [], "Extension Options": [("", ""), ("--hook_name", ""), ("", "")], + "Hook Options": [('', ''), ('--plan-file', ''), ('', '')], "Beta Options": [("", ""), ("--beta-features", ""), ("", "")], "Invoke default lambda function with no event": [("", ""), ("$sam local invoke\x1b[0m", "")], "Invoke lambda function with stdin input": [ diff --git a/tests/unit/commands/local/start_api/core/test_command.py b/tests/unit/commands/local/start_api/core/test_command.py index 67eab70400..8d6fb10a35 100644 --- a/tests/unit/commands/local/start_api/core/test_command.py +++ b/tests/unit/commands/local/start_api/core/test_command.py @@ -32,6 +32,7 @@ def test_get_options_local_start_api_command(self, mock_get_params): MockParams(rv=("--beta-features", ""), name="beta_features"), MockParams(rv=("--log-file", ""), name="log_file"), MockParams(rv=("--debug", ""), name="debug"), + MockParams(rv=("--plan-file", ""), name="plan_file"), ] cmd = InvokeAPICommand(name="local start-api", requires_credentials=False, description=DESCRIPTION) @@ -43,6 +44,7 @@ def test_get_options_local_start_api_command(self, mock_get_params): "Description": [(cmd.description + cmd.description_addendum, "")], "Examples": [("", ""), ("$sam local start-api\x1b[0m", "")], "Extension Options": [("", ""), ("--hook_name", ""), ("", "")], + "Hook Options": [('', ''), ('--plan-file', ''), ('', '')], "Other Options": [("", ""), ("--debug", ""), ("", "")], "Beta Options": [("", ""), ("--beta-features", ""), ("", "")], "Required Options": [("", ""), ("--template-file", ""), ("", "")], diff --git a/tests/unit/commands/local/start_lambda/core/test_command.py b/tests/unit/commands/local/start_lambda/core/test_command.py index 631c95c0f1..0f48eb1a12 100644 --- a/tests/unit/commands/local/start_lambda/core/test_command.py +++ b/tests/unit/commands/local/start_lambda/core/test_command.py @@ -32,6 +32,7 @@ def test_get_options_local_start_lambda_command(self, mock_get_params): MockParams(rv=("--log-file", ""), name="log_file"), MockParams(rv=("--beta-features", ""), name="beta_features"), MockParams(rv=("--debug", ""), name="debug"), + MockParams(rv=("--plan-file", ""), name="plan_file"), ] cmd = InvokeLambdaCommand(name="local start-api", requires_credentials=False, description=DESCRIPTION) @@ -42,6 +43,7 @@ def test_get_options_local_start_lambda_command(self, mock_get_params): "Container Options": [("", ""), ("--port", ""), ("", "")], "Description": [(cmd.description + cmd.description_addendum, "")], "Examples": [], + "Hook Options": [('', ''), ('--plan-file', ''), ('', '')], "Setup": [("", ""), ("Start the local lambda endpoint.", ""), ("$sam local start-lambda\x1b[0m", "")], "Template Options": [("", ""), ("--parameter-overrides", ""), ("", "")], "Using AWS CLI": [ diff --git a/tests/unit/hook_packages/terraform/hooks/prepare/test_hook.py b/tests/unit/hook_packages/terraform/hooks/prepare/test_hook.py index aabba14f3e..7b7902343b 100644 --- a/tests/unit/hook_packages/terraform/hooks/prepare/test_hook.py +++ b/tests/unit/hook_packages/terraform/hooks/prepare/test_hook.py @@ -1,6 +1,6 @@ """Test Terraform prepare hook""" from subprocess import CalledProcessError -from unittest.mock import Mock, call, patch, MagicMock +from unittest.mock import Mock, call, patch, MagicMock, ANY from parameterized import parameterized from tests.unit.hook_packages.terraform.hooks.prepare.prepare_base import PrepareHookUnitBase @@ -352,3 +352,32 @@ def test_skip_prepare_infra_with_metadata_file(self, run_mock, os_mock): prepare(self.prepare_params) run_mock.assert_not_called() + + @patch("samcli.hook_packages.terraform.hooks.prepare.hook.invoke_subprocess_with_loading_pattern") + @patch("samcli.hook_packages.terraform.hooks.prepare.hook._update_resources_paths") + @patch("samcli.hook_packages.terraform.hooks.prepare.hook.translate_to_cfn") + @patch("builtins.open") + @patch("samcli.hook_packages.terraform.hooks.prepare.hook.osutils.tempfile_platform_independent") + @patch("samcli.hook_packages.terraform.hooks.prepare.hook.os") + @patch("samcli.hook_packages.terraform.hooks.prepare.hook.json") + @patch("samcli.hook_packages.terraform.hooks.prepare.hook.run") + def test_uses_custom_plan_file( + self, + mock_subprocess_run, + mock_json, + mock_os, + named_temporary_file_mock, + mock_open, + mock_translate_to_cfn, + mock_update_resources_paths, + mock_subprocess_loader, + ): + self.prepare_params["PlanFile"] = "my-custom-plan.json" + + file_mock = Mock() + mock_open.return_value.__enter__.return_value = file_mock + + prepare(self.prepare_params) + + mock_open.assert_has_calls([call("my-custom-plan.json", "r"), ANY]) + mock_json.load.assert_called_once_with(file_mock) From c4937e6b9d6bd281a2626759adf5a37bfb503864 Mon Sep 17 00:00:00 2001 From: Daniel Mil Date: Wed, 9 Aug 2023 15:05:23 -0700 Subject: [PATCH 3/6] Add docstrings and type hints --- .../terraform/hooks/prepare/hook.py | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/samcli/hook_packages/terraform/hooks/prepare/hook.py b/samcli/hook_packages/terraform/hooks/prepare/hook.py index 77d2f375f3..f55b8e02ce 100644 --- a/samcli/hook_packages/terraform/hooks/prepare/hook.py +++ b/samcli/hook_packages/terraform/hooks/prepare/hook.py @@ -143,7 +143,22 @@ def _update_resources_paths(cfn_resources: Dict[str, Any], terraform_application resource["Properties"][attribute] = str(Path(terraform_application_dir).joinpath(original_path)) -def _generate_plan_file(skip_prepare_infra, terraform_application_dir) -> dict: +def _generate_plan_file(skip_prepare_infra: bool, terraform_application_dir: str) -> dict: + """ + Call the relevant Terraform commands to generate, load and return the Terraform plan file + which the AWS SAM CLI will then parse to extract the fields required to run local emulators. + + Parameters + ---------- + skip_prepare_infra: bool + Flag to skip skip prepare hook if we already have the metadata file. Default is False. + terraform_application_dir: str + The path where the hook can find the TF application. + Returns + ------- + dict + The Terraform plan file in JSON format + """ log_msg = ( ( "The option to skip infrastructure preparation was provided, but AWS SAM CLI could not find " From 337393610d377bf0d1ef1b124096790565b76e22 Mon Sep 17 00:00:00 2001 From: Daniel Mil Date: Wed, 9 Aug 2023 16:06:19 -0700 Subject: [PATCH 4/6] Address comments --- .../_utils/custom_options/hook_name_option.py | 2 +- samcli/commands/_utils/options.py | 10 ++- samcli/commands/build/command.py | 6 +- samcli/commands/build/core/options.py | 6 +- samcli/commands/local/invoke/cli.py | 6 +- samcli/commands/local/invoke/core/options.py | 6 +- samcli/commands/local/start_api/cli.py | 6 +- .../commands/local/start_api/core/options.py | 6 +- samcli/commands/local/start_lambda/cli.py | 6 +- .../local/start_lambda/core/options.py | 6 +- .../terraform/hooks/prepare/hook.py | 79 ++++++++++--------- .../commands/buildcmd/core/test_command.py | 4 +- .../local/invoke/core/test_command.py | 4 +- .../local/start_api/core/test_command.py | 4 +- .../local/start_lambda/core/test_command.py | 4 +- 15 files changed, 80 insertions(+), 75 deletions(-) diff --git a/samcli/commands/_utils/custom_options/hook_name_option.py b/samcli/commands/_utils/custom_options/hook_name_option.py index 132d268a6b..57f53b9fc5 100644 --- a/samcli/commands/_utils/custom_options/hook_name_option.py +++ b/samcli/commands/_utils/custom_options/hook_name_option.py @@ -86,7 +86,7 @@ def _call_prepare_hook(self, iac_hook_wrapper, opts): aws_profile = opts.get("profile") aws_region = opts.get("region") skip_prepare_infra = opts.get("skip_prepare_infra", False) - plan_file = opts.get("plan_file") + plan_file = opts.get("terraform_plan_file") metadata_file = iac_hook_wrapper.prepare( output_dir_path, iac_project_path, debug, aws_profile, aws_region, skip_prepare_infra, plan_file diff --git a/samcli/commands/_utils/options.py b/samcli/commands/_utils/options.py index c5a8b6317e..045f408f9f 100644 --- a/samcli/commands/_utils/options.py +++ b/samcli/commands/_utils/options.py @@ -789,14 +789,16 @@ def use_container_build_option(f): return use_container_build_click_option()(f) -def plan_file_click_option(): +def terraform_plan_file_click_option(): return click.option( - "--plan-file", type=click.Path(), help="Used for passing a custom plan file when executing the Terraform hook." + "--terraform-plan-file", + type=click.Path(), + help="Used for passing a custom plan file when executing the Terraform hook.", ) -def plan_file_option(f): - return plan_file_click_option()(f) +def terraform_plan_file_option(f): + return terraform_plan_file_click_option()(f) def build_image_click_option(cls): diff --git a/samcli/commands/build/command.py b/samcli/commands/build/command.py index f18f0a809e..9e478f2aa8 100644 --- a/samcli/commands/build/command.py +++ b/samcli/commands/build/command.py @@ -22,7 +22,7 @@ use_container_build_option, build_image_option, hook_name_click_option, - plan_file_option, + terraform_plan_file_option, ) from samcli.commands._utils.option_value_processor import process_env_var, process_image_options from samcli.cli.main import pass_context, common_options as cli_framework_options, aws_creds_options, print_cmdline_args @@ -71,7 +71,7 @@ context_settings={"max_content_width": 120}, ) @configuration_option(provider=ConfigProvider(section="parameters")) -@plan_file_option +@terraform_plan_file_option @hook_name_click_option( force_prepare=True, invalid_coexist_options=["t", "template-file", "template", "parameter-overrides"], @@ -157,7 +157,7 @@ def cli( hook_name: Optional[str], skip_prepare_infra: bool, mount_with, - plan_file, + terraform_plan_file, ) -> None: """ `sam build` command entry point diff --git a/samcli/commands/build/core/options.py b/samcli/commands/build/core/options.py index e80590051d..2c525fdeca 100644 --- a/samcli/commands/build/core/options.py +++ b/samcli/commands/build/core/options.py @@ -37,7 +37,7 @@ TEMPLATE_OPTIONS: List[str] = ["parameter_overrides"] -HOOK_OPTIONS: List[str] = ["plan_file"] +TERRAFORM_HOOK_OPTIONS: List[str] = ["terraform_plan_file"] ALL_OPTIONS: List[str] = ( REQUIRED_OPTIONS @@ -49,7 +49,7 @@ + EXTENSION_OPTIONS + CONFIGURATION_OPTION_NAMES + ALL_COMMON_OPTIONS - + HOOK_OPTIONS + + TERRAFORM_HOOK_OPTIONS ) OPTIONS_INFO: Dict[str, Dict] = { @@ -74,6 +74,6 @@ ), ], }, - "Hook Options": {"option_names": {opt: {"rank": idx} for idx, opt in enumerate(HOOK_OPTIONS)}}, + "Terraform Hook Options": {"option_names": {opt: {"rank": idx} for idx, opt in enumerate(TERRAFORM_HOOK_OPTIONS)}}, } add_common_options_info(OPTIONS_INFO) diff --git a/samcli/commands/local/invoke/cli.py b/samcli/commands/local/invoke/cli.py index 764db32a1a..f8e87f4e27 100644 --- a/samcli/commands/local/invoke/cli.py +++ b/samcli/commands/local/invoke/cli.py @@ -11,7 +11,7 @@ from samcli.cli.main import common_options as cli_framework_options from samcli.commands._utils.experimental import ExperimentalFlag, is_experimental_enabled from samcli.commands._utils.option_value_processor import process_image_options -from samcli.commands._utils.options import hook_name_click_option, plan_file_option, skip_prepare_infra_option +from samcli.commands._utils.options import hook_name_click_option, skip_prepare_infra_option, terraform_plan_file_option from samcli.commands.local.cli_common.options import invoke_common_options, local_common_options from samcli.commands.local.invoke.core.command import InvokeCommand from samcli.commands.local.lib.exceptions import InvalidIntermediateImageError @@ -44,7 +44,7 @@ context_settings={"max_content_width": 120}, ) @configuration_option(provider=ConfigProvider(section="parameters")) -@plan_file_option +@terraform_plan_file_option @hook_name_click_option( force_prepare=False, invalid_coexist_options=["t", "template-file", "template", "parameter-overrides"] ) @@ -92,7 +92,7 @@ def cli( invoke_image, hook_name, skip_prepare_infra, - plan_file, + terraform_plan_file, ): """ `sam local invoke` command entry point diff --git a/samcli/commands/local/invoke/core/options.py b/samcli/commands/local/invoke/core/options.py index c5c3dc07f0..83bb246e3c 100644 --- a/samcli/commands/local/invoke/core/options.py +++ b/samcli/commands/local/invoke/core/options.py @@ -46,7 +46,7 @@ OTHER_OPTIONS: List[str] = ["debug"] -HOOK_OPTIONS: List[str] = ["plan_file"] +TERRAFORM_HOOK_OPTIONS: List[str] = ["terraform_plan_file"] ALL_OPTIONS: List[str] = ( REQUIRED_OPTIONS @@ -57,7 +57,7 @@ + EXTENSION_OPTIONS + CONFIGURATION_OPTION_NAMES + ALL_COMMON_OPTIONS - + HOOK_OPTIONS + + TERRAFORM_HOOK_OPTIONS ) OPTIONS_INFO: Dict[str, Dict] = { @@ -81,7 +81,7 @@ ), ], }, - "Hook Options": {"option_names": {opt: {"rank": idx} for idx, opt in enumerate(HOOK_OPTIONS)}}, + "Terraform Hook Options": {"option_names": {opt: {"rank": idx} for idx, opt in enumerate(TERRAFORM_HOOK_OPTIONS)}}, } add_common_options_info(OPTIONS_INFO) diff --git a/samcli/commands/local/start_api/cli.py b/samcli/commands/local/start_api/cli.py index 42d89f4735..9d3247eeef 100644 --- a/samcli/commands/local/start_api/cli.py +++ b/samcli/commands/local/start_api/cli.py @@ -14,8 +14,8 @@ from samcli.commands._utils.options import ( generate_next_command_recommendation, hook_name_click_option, - plan_file_option, skip_prepare_infra_option, + terraform_plan_file_option, ) from samcli.commands.local.cli_common.options import ( invoke_common_options, @@ -60,7 +60,7 @@ context_settings={"max_content_width": 120}, ) @configuration_option(provider=ConfigProvider(section="parameters")) -@plan_file_option +@terraform_plan_file_option @hook_name_click_option( force_prepare=False, invalid_coexist_options=["t", "template-file", "template", "parameter-overrides"] ) @@ -111,7 +111,7 @@ def cli( invoke_image, hook_name, skip_prepare_infra, - plan_file, + terraform_plan_file, ): """ `sam local start-api` command entry point diff --git a/samcli/commands/local/start_api/core/options.py b/samcli/commands/local/start_api/core/options.py index 6eaf1500d5..a71776269f 100644 --- a/samcli/commands/local/start_api/core/options.py +++ b/samcli/commands/local/start_api/core/options.py @@ -47,7 +47,7 @@ "static_dir", ] -HOOK_OPTIONS: List[str] = ["plan_file"] +TERRAFORM_HOOK_OPTIONS: List[str] = ["terraform_plan_file"] ALL_OPTIONS: List[str] = ( REQUIRED_OPTIONS @@ -58,7 +58,7 @@ + CONFIGURATION_OPTION_NAMES + ALL_COMMON_OPTIONS + EXTENSION_OPTIONS - + HOOK_OPTIONS + + TERRAFORM_HOOK_OPTIONS ) OPTIONS_INFO: Dict[str, Dict] = { @@ -82,7 +82,7 @@ ), ], }, - "Hook Options": {"option_names": {opt: {"rank": idx} for idx, opt in enumerate(HOOK_OPTIONS)}}, + "Terraform Hook Options": {"option_names": {opt: {"rank": idx} for idx, opt in enumerate(TERRAFORM_HOOK_OPTIONS)}}, } add_common_options_info(OPTIONS_INFO) diff --git a/samcli/commands/local/start_lambda/cli.py b/samcli/commands/local/start_lambda/cli.py index 698b98c598..8dd78dc2f9 100644 --- a/samcli/commands/local/start_lambda/cli.py +++ b/samcli/commands/local/start_lambda/cli.py @@ -14,8 +14,8 @@ from samcli.commands._utils.options import ( generate_next_command_recommendation, hook_name_click_option, - plan_file_option, skip_prepare_infra_option, + terraform_plan_file_option, ) from samcli.commands.local.cli_common.options import ( invoke_common_options, @@ -54,7 +54,7 @@ context_settings={"max_content_width": 120}, ) @configuration_option(provider=ConfigProvider(section="parameters")) -@plan_file_option +@terraform_plan_file_option @hook_name_click_option( force_prepare=False, invalid_coexist_options=["t", "template-file", "template", "parameter-overrides"] ) @@ -98,7 +98,7 @@ def cli( invoke_image, hook_name, skip_prepare_infra, - plan_file, + terraform_plan_file, ): """ `sam local start-lambda` command entry point diff --git a/samcli/commands/local/start_lambda/core/options.py b/samcli/commands/local/start_lambda/core/options.py index 4ef011f732..5ff5d4e115 100644 --- a/samcli/commands/local/start_lambda/core/options.py +++ b/samcli/commands/local/start_lambda/core/options.py @@ -46,7 +46,7 @@ CONFIGURATION_OPTION_NAMES: List[str] = ["config_env", "config_file"] -HOOK_OPTIONS: List[str] = ["plan_file"] +TERRAFORM_HOOK_OPTIONS: List[str] = ["terraform_plan_file"] ALL_OPTIONS: List[str] = ( REQUIRED_OPTIONS @@ -57,7 +57,7 @@ + EXTENSION_OPTIONS + CONFIGURATION_OPTION_NAMES + ALL_COMMON_OPTIONS - + HOOK_OPTIONS + + TERRAFORM_HOOK_OPTIONS ) OPTIONS_INFO: Dict[str, Dict] = { @@ -81,7 +81,7 @@ ), ], }, - "Hook Options": {"option_names": {opt: {"rank": idx} for idx, opt in enumerate(HOOK_OPTIONS)}}, + "Terraform Hook Options": {"option_names": {opt: {"rank": idx} for idx, opt in enumerate(TERRAFORM_HOOK_OPTIONS)}}, } add_common_options_info(OPTIONS_INFO) diff --git a/samcli/hook_packages/terraform/hooks/prepare/hook.py b/samcli/hook_packages/terraform/hooks/prepare/hook.py index f55b8e02ce..79553f3089 100644 --- a/samcli/hook_packages/terraform/hooks/prepare/hook.py +++ b/samcli/hook_packages/terraform/hooks/prepare/hook.py @@ -55,7 +55,7 @@ def prepare(params: dict) -> dict: output_dir_path = os.path.normpath(os.path.join(terraform_application_dir, output_dir_path)) LOG.debug("The normalized OutputDirPath value is %s", output_dir_path) - skip_prepare_infra = params.get("SkipPrepareInfra") + skip_prepare_infra = params.get("SkipPrepareInfra", False) metadata_file_path = os.path.join(output_dir_path, TERRAFORM_METADATA_FILE) plan_file = params.get("PlanFile") @@ -92,26 +92,7 @@ def prepare(params: dict) -> dict: LOG.info("Finished generating metadata file. Storing in %s", metadata_file_path) with open(metadata_file_path, "w+") as metadata_file: json.dump(cfn_dict, metadata_file) - except CalledProcessError as e: - stderr_output = str(e.stderr) - - # stderr can take on bytes or just be a plain string depending on terminal - if isinstance(e.stderr, bytes): - stderr_output = e.stderr.decode("utf-8") - - # one of the subprocess.run calls resulted in non-zero exit code or some OS error - LOG.debug( - "Error running terraform command: \n" "cmd: %s \n" "stdout: %s \n" "stderr: %s \n", - e.cmd, - e.stdout, - stderr_output, - ) - raise PrepareHookException( - f"There was an error while preparing the Terraform application.\n{stderr_output}" - ) from e - except LoadingPatternError as e: - raise PrepareHookException(f"Error occurred when invoking a process: {e}") from e except OSError as e: raise PrepareHookException(f"OSError: {e}") from e @@ -168,29 +149,51 @@ def _generate_plan_file(skip_prepare_infra: bool, terraform_application_dir: str else "Initializing Terraform application" ) LOG.info(log_msg) - invoke_subprocess_with_loading_pattern( - command_args={ - "args": ["terraform", "init", "-input=false"], - "cwd": terraform_application_dir, - } - ) - - # get json output of terraform plan - LOG.info("Creating terraform plan and getting JSON output") - with osutils.tempfile_platform_independent() as temp_file: + try: invoke_subprocess_with_loading_pattern( - # input false to avoid SAM CLI to stuck in case if the - # Terraform project expects input, and customer does not provide it. command_args={ - "args": ["terraform", "plan", "-out", temp_file.name, "-input=false"], + "args": ["terraform", "init", "-input=false"], "cwd": terraform_application_dir, } ) - result = run( - ["terraform", "show", "-json", temp_file.name], - check=True, - capture_output=True, - cwd=terraform_application_dir, + # get json output of terraform plan + LOG.info("Creating terraform plan and getting JSON output") + with osutils.tempfile_platform_independent() as temp_file: + invoke_subprocess_with_loading_pattern( + # input false to avoid SAM CLI to stuck in case if the + # Terraform project expects input, and customer does not provide it. + command_args={ + "args": ["terraform", "plan", "-out", temp_file.name, "-input=false"], + "cwd": terraform_application_dir, + } + ) + + result = run( + ["terraform", "show", "-json", temp_file.name], + check=True, + capture_output=True, + cwd=terraform_application_dir, + ) + except CalledProcessError as e: + stderr_output = str(e.stderr) + + # stderr can take on bytes or just be a plain string depending on terminal + if isinstance(e.stderr, bytes): + stderr_output = e.stderr.decode("utf-8") + + # one of the subprocess.run calls resulted in non-zero exit code or some OS error + LOG.debug( + "Error running terraform command: \n" "cmd: %s \n" "stdout: %s \n" "stderr: %s \n", + e.cmd, + e.stdout, + stderr_output, ) + + raise PrepareHookException( + f"There was an error while preparing the Terraform application.\n{stderr_output}" + ) from e + except LoadingPatternError as e: + raise PrepareHookException(f"Error occurred when invoking a process: {e}") from e + return dict(json.loads(result.stdout)) diff --git a/tests/unit/commands/buildcmd/core/test_command.py b/tests/unit/commands/buildcmd/core/test_command.py index df0735ffc1..0f2e191f7c 100644 --- a/tests/unit/commands/buildcmd/core/test_command.py +++ b/tests/unit/commands/buildcmd/core/test_command.py @@ -33,7 +33,7 @@ def test_get_options_build_command_text(self, mock_get_params): MockParams(rv=("--parameter-overrides", ""), name="parameter_overrides"), MockParams(rv=("--beta-features", ""), name="beta_features"), MockParams(rv=("--template-file", ""), name="template_file"), - MockParams(rv=("--plan-file", ""), name="plan_file"), + MockParams(rv=("--terraform-plan-file", ""), name="terraform_plan_file"), ] cmd = BuildCommand(name="sync", requires_credentials=False, description=DESCRIPTION) @@ -42,7 +42,7 @@ def test_get_options_build_command_text(self, mock_get_params): "Container Options": [("", ""), ("--use-container", ""), ("", "")], "Configuration Options": [("", ""), ("--config-file", ""), ("", "")], "Extension Options": [("", ""), ("--hook-name", ""), ("", "")], - "Hook Options": [('', ''), ('--plan-file', ''), ('', '')], + "Terraform Hook Options": [("", ""), ("--terraform-plan-file", ""), ("", "")], "Build Strategy Options": [("", ""), ("--parallel", ""), ("", "")], "Artifact Location Options": [("", ""), ("--build-dir", ""), ("", "")], "Template Options": [("", ""), ("--parameter-overrides", ""), ("", "")], diff --git a/tests/unit/commands/local/invoke/core/test_command.py b/tests/unit/commands/local/invoke/core/test_command.py index 0106e2d533..a7d77e0a95 100644 --- a/tests/unit/commands/local/invoke/core/test_command.py +++ b/tests/unit/commands/local/invoke/core/test_command.py @@ -32,7 +32,7 @@ def test_get_options_local_invoke_command_text(self, mock_get_params): MockParams(rv=("--log-file", ""), name="log_file"), MockParams(rv=("--beta-features", ""), name="beta_features"), MockParams(rv=("--debug", ""), name="debug"), - MockParams(rv=("--plan-file", ""), name="plan_file"), + MockParams(rv=("--terraform-plan-file", ""), name="terraform_plan_file"), ] cmd = InvokeCommand(name="local invoke", requires_credentials=False, description=DESCRIPTION) @@ -44,7 +44,7 @@ def test_get_options_local_invoke_command_text(self, mock_get_params): "Description": [(cmd.description + cmd.description_addendum, "")], "Examples": [], "Extension Options": [("", ""), ("--hook_name", ""), ("", "")], - "Hook Options": [('', ''), ('--plan-file', ''), ('', '')], + "Terraform Hook Options": [("", ""), ("--terraform-plan-file", ""), ("", "")], "Beta Options": [("", ""), ("--beta-features", ""), ("", "")], "Invoke default lambda function with no event": [("", ""), ("$sam local invoke\x1b[0m", "")], "Invoke lambda function with stdin input": [ diff --git a/tests/unit/commands/local/start_api/core/test_command.py b/tests/unit/commands/local/start_api/core/test_command.py index 8d6fb10a35..09a8a17edc 100644 --- a/tests/unit/commands/local/start_api/core/test_command.py +++ b/tests/unit/commands/local/start_api/core/test_command.py @@ -32,7 +32,7 @@ def test_get_options_local_start_api_command(self, mock_get_params): MockParams(rv=("--beta-features", ""), name="beta_features"), MockParams(rv=("--log-file", ""), name="log_file"), MockParams(rv=("--debug", ""), name="debug"), - MockParams(rv=("--plan-file", ""), name="plan_file"), + MockParams(rv=("--terraform-plan-file", ""), name="terraform_plan_file"), ] cmd = InvokeAPICommand(name="local start-api", requires_credentials=False, description=DESCRIPTION) @@ -44,7 +44,7 @@ def test_get_options_local_start_api_command(self, mock_get_params): "Description": [(cmd.description + cmd.description_addendum, "")], "Examples": [("", ""), ("$sam local start-api\x1b[0m", "")], "Extension Options": [("", ""), ("--hook_name", ""), ("", "")], - "Hook Options": [('', ''), ('--plan-file', ''), ('', '')], + "Terraform Hook Options": [("", ""), ("--terraform-plan-file", ""), ("", "")], "Other Options": [("", ""), ("--debug", ""), ("", "")], "Beta Options": [("", ""), ("--beta-features", ""), ("", "")], "Required Options": [("", ""), ("--template-file", ""), ("", "")], diff --git a/tests/unit/commands/local/start_lambda/core/test_command.py b/tests/unit/commands/local/start_lambda/core/test_command.py index 0f48eb1a12..c0ef4fa826 100644 --- a/tests/unit/commands/local/start_lambda/core/test_command.py +++ b/tests/unit/commands/local/start_lambda/core/test_command.py @@ -32,7 +32,7 @@ def test_get_options_local_start_lambda_command(self, mock_get_params): MockParams(rv=("--log-file", ""), name="log_file"), MockParams(rv=("--beta-features", ""), name="beta_features"), MockParams(rv=("--debug", ""), name="debug"), - MockParams(rv=("--plan-file", ""), name="plan_file"), + MockParams(rv=("--terraform-plan-file", ""), name="terraform_plan_file"), ] cmd = InvokeLambdaCommand(name="local start-api", requires_credentials=False, description=DESCRIPTION) @@ -43,7 +43,7 @@ def test_get_options_local_start_lambda_command(self, mock_get_params): "Container Options": [("", ""), ("--port", ""), ("", "")], "Description": [(cmd.description + cmd.description_addendum, "")], "Examples": [], - "Hook Options": [('', ''), ('--plan-file', ''), ('', '')], + "Terraform Hook Options": [("", ""), ("--terraform-plan-file", ""), ("", "")], "Setup": [("", ""), ("Start the local lambda endpoint.", ""), ("$sam local start-lambda\x1b[0m", "")], "Template Options": [("", ""), ("--parameter-overrides", ""), ("", "")], "Using AWS CLI": [ From 8b27bb6a0821282c7c999d2f5ceebef1f5da7530 Mon Sep 17 00:00:00 2001 From: Daniel Mil Date: Wed, 9 Aug 2023 16:19:34 -0700 Subject: [PATCH 5/6] Update schema --- schema/samcli.json | 28 ++++++++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/schema/samcli.json b/schema/samcli.json index cbe8ee4fce..f5daf84cbb 100644 --- a/schema/samcli.json +++ b/schema/samcli.json @@ -225,9 +225,14 @@ "properties": { "parameters": { "title": "Parameters for the build command", - "description": "Available parameters for the build command:\n* hook_name:\nHook package id to extend AWS SAM CLI commands functionality. \n\nExample: `terraform` to extend AWS SAM CLI commands functionality to support terraform applications. \n\nAvailable Hook Names: ['terraform']\n* skip_prepare_infra:\nSkip preparation stage when there are no infrastructure changes. Only used in conjunction with --hook-name.\n* use_container:\nBuild functions within an AWS Lambda-like container.\n* container_env_var:\nEnvironment variables to be passed into build containers\nResource format (FuncName.VarName=Value) or Global format (VarName=Value).\n\n Example: --container-env-var Func1.VAR1=value1 --container-env-var VAR2=value2\n* container_env_var_file:\nEnvironment variables json file (e.g., env_vars.json) to be passed to build containers.\n* build_image:\nContainer image URIs for building functions/layers. You can specify for all functions/layers with just the image URI (--build-image public.ecr.aws/sam/build-nodejs18.x:latest). You can specify for each individual function with (--build-image FunctionLogicalID=public.ecr.aws/sam/build-nodejs18.x:latest). A combination of the two can be used. If a function does not have build image specified or an image URI for all functions, the default SAM CLI build images will be used.\n* exclude:\nName of the resource(s) to exclude from AWS SAM CLI build.\n* parallel:\nEnable parallel builds for AWS SAM template's functions and layers.\n* mount_with:\nSpecify mount mode for building functions/layers inside container. If it is mounted with write permissions, some files in source code directory may be changed/added by the build process. By default the source code directory is read only.\n* build_dir:\nDirectory to store build artifacts.Note: This directory will be first removed before starting a build.\n* cache_dir:\nDirectory to store cached artifacts. The default cache directory is .aws-sam/cache\n* base_dir:\nResolve relative paths to function's source code with respect to this directory. Use this if SAM template and source code are not in same enclosing folder. By default, relative paths are resolved with respect to the SAM template's location.\n* manifest:\nPath to a custom dependency manifest. Example: custom-package.json\n* cached:\nEnable cached builds.Reuse build artifacts that have not changed from previous builds. \n\nAWS SAM CLI evaluates if files in your project directory have changed. \n\nNote: AWS SAM CLI does not evaluate changes made to third party modules that the project depends on.Example: Python function includes a requirements.txt file with the following entry requests=1.x and the latest request module version changes from 1.1 to 1.2, AWS SAM CLI will not pull the latest version until a non-cached build is run.\n* template_file:\nAWS SAM template file.\n* parameter_overrides:\nString that contains AWS CloudFormation parameter overrides encoded as key=value pairs.\n* skip_pull_image:\nSkip pulling down the latest Docker image for Lambda runtime.\n* docker_network:\nName or ID of an existing docker network for AWS Lambda docker containers to connect to, along with the default bridge network. If not specified, the Lambda containers will only connect to the default bridge docker network.\n* beta_features:\nEnable/Disable beta features.\n* debug:\nTurn on debug logging to print debug message generated by AWS SAM CLI and display timestamps.\n* profile:\nSelect a specific profile from your credential file to get AWS credentials.\n* region:\nSet the AWS Region of the service. (e.g. us-east-1)", + "description": "Available parameters for the build command:\n* terraform_plan_file:\nUsed for passing a custom plan file when executing the Terraform hook.\n* hook_name:\nHook package id to extend AWS SAM CLI commands functionality. \n\nExample: `terraform` to extend AWS SAM CLI commands functionality to support terraform applications. \n\nAvailable Hook Names: ['terraform']\n* skip_prepare_infra:\nSkip preparation stage when there are no infrastructure changes. Only used in conjunction with --hook-name.\n* use_container:\nBuild functions within an AWS Lambda-like container.\n* container_env_var:\nEnvironment variables to be passed into build containers\nResource format (FuncName.VarName=Value) or Global format (VarName=Value).\n\n Example: --container-env-var Func1.VAR1=value1 --container-env-var VAR2=value2\n* container_env_var_file:\nEnvironment variables json file (e.g., env_vars.json) to be passed to build containers.\n* build_image:\nContainer image URIs for building functions/layers. You can specify for all functions/layers with just the image URI (--build-image public.ecr.aws/sam/build-nodejs18.x:latest). You can specify for each individual function with (--build-image FunctionLogicalID=public.ecr.aws/sam/build-nodejs18.x:latest). A combination of the two can be used. If a function does not have build image specified or an image URI for all functions, the default SAM CLI build images will be used.\n* exclude:\nName of the resource(s) to exclude from AWS SAM CLI build.\n* parallel:\nEnable parallel builds for AWS SAM template's functions and layers.\n* mount_with:\nSpecify mount mode for building functions/layers inside container. If it is mounted with write permissions, some files in source code directory may be changed/added by the build process. By default the source code directory is read only.\n* build_dir:\nDirectory to store build artifacts.Note: This directory will be first removed before starting a build.\n* cache_dir:\nDirectory to store cached artifacts. The default cache directory is .aws-sam/cache\n* base_dir:\nResolve relative paths to function's source code with respect to this directory. Use this if SAM template and source code are not in same enclosing folder. By default, relative paths are resolved with respect to the SAM template's location.\n* manifest:\nPath to a custom dependency manifest. Example: custom-package.json\n* cached:\nEnable cached builds.Reuse build artifacts that have not changed from previous builds. \n\nAWS SAM CLI evaluates if files in your project directory have changed. \n\nNote: AWS SAM CLI does not evaluate changes made to third party modules that the project depends on.Example: Python function includes a requirements.txt file with the following entry requests=1.x and the latest request module version changes from 1.1 to 1.2, AWS SAM CLI will not pull the latest version until a non-cached build is run.\n* template_file:\nAWS SAM template file.\n* parameter_overrides:\nString that contains AWS CloudFormation parameter overrides encoded as key=value pairs.\n* skip_pull_image:\nSkip pulling down the latest Docker image for Lambda runtime.\n* docker_network:\nName or ID of an existing docker network for AWS Lambda docker containers to connect to, along with the default bridge network. If not specified, the Lambda containers will only connect to the default bridge docker network.\n* beta_features:\nEnable/Disable beta features.\n* debug:\nTurn on debug logging to print debug message generated by AWS SAM CLI and display timestamps.\n* profile:\nSelect a specific profile from your credential file to get AWS credentials.\n* region:\nSet the AWS Region of the service. (e.g. us-east-1)", "type": "object", "properties": { + "terraform_plan_file": { + "title": "terraform_plan_file", + "type": "string", + "description": "Used for passing a custom plan file when executing the Terraform hook." + }, "hook_name": { "title": "hook_name", "type": "string", @@ -365,9 +370,14 @@ "properties": { "parameters": { "title": "Parameters for the local invoke command", - "description": "Available parameters for the local invoke command:\n* hook_name:\nHook package id to extend AWS SAM CLI commands functionality. \n\nExample: `terraform` to extend AWS SAM CLI commands functionality to support terraform applications. \n\nAvailable Hook Names: ['terraform']\n* skip_prepare_infra:\nSkip preparation stage when there are no infrastructure changes. Only used in conjunction with --hook-name.\n* event:\nJSON file containing event data passed to the Lambda function during invoke. If this option is not specified, no event is assumed. Pass in the value '-' to input JSON via stdin\n* no_event:\nDEPRECATED: By default no event is assumed.\n* template_file:\nAWS SAM template which references built artifacts for resources in the template. (if applicable)\n* env_vars:\nJSON file containing values for Lambda function's environment variables.\n* parameter_overrides:\nString that contains AWS CloudFormation parameter overrides encoded as key=value pairs.\n* debug_port:\nWhen specified, Lambda function container will start in debug mode and will expose this port on localhost.\n* debugger_path:\nHost path to a debugger that will be mounted into the Lambda container.\n* debug_args:\nAdditional arguments to be passed to the debugger.\n* container_env_vars:\nJSON file containing environment variables to be set within the container environment\n* docker_volume_basedir:\nSpecify the location basedir where the SAM template exists. If Docker is running on a remote machine, Path of the SAM template must be mounted on the Docker machine and modified to match the remote machine.\n* log_file:\nFile to capture output logs.\n* layer_cache_basedir:\nSpecify the location basedir where the lambda layers used by the template will be downloaded to.\n* skip_pull_image:\nSkip pulling down the latest Docker image for Lambda runtime.\n* docker_network:\nName or ID of an existing docker network for AWS Lambda docker containers to connect to, along with the default bridge network. If not specified, the Lambda containers will only connect to the default bridge docker network.\n* force_image_build:\nForce rebuilding the image used for invoking functions with layers.\n* shutdown:\nEmulate a shutdown event after invoke completes, to test extension handling of shutdown behavior.\n* container_host:\nHost of locally emulated Lambda container. This option is useful when the container runs on a different host than AWS SAM CLI. For example, if one wants to run AWS SAM CLI in a Docker container on macOS, this option could specify `host.docker.internal`\n* container_host_interface:\nIP address of the host network interface that container ports should bind to. Use 0.0.0.0 to bind to all interfaces.\n* invoke_image:\nContainer image URIs for invoking functions or starting api and function. One can specify the image URI used for the local function invocation (--invoke-image public.ecr.aws/sam/build-nodejs14.x:latest). One can also specify for each individual function with (--invoke-image Function1=public.ecr.aws/sam/build-nodejs14.x:latest). If a function does not have invoke image specified, the default AWS SAM CLI emulation image will be used.\n* beta_features:\nEnable/Disable beta features.\n* debug:\nTurn on debug logging to print debug message generated by AWS SAM CLI and display timestamps.\n* profile:\nSelect a specific profile from your credential file to get AWS credentials.\n* region:\nSet the AWS Region of the service. (e.g. us-east-1)", + "description": "Available parameters for the local invoke command:\n* terraform_plan_file:\nUsed for passing a custom plan file when executing the Terraform hook.\n* hook_name:\nHook package id to extend AWS SAM CLI commands functionality. \n\nExample: `terraform` to extend AWS SAM CLI commands functionality to support terraform applications. \n\nAvailable Hook Names: ['terraform']\n* skip_prepare_infra:\nSkip preparation stage when there are no infrastructure changes. Only used in conjunction with --hook-name.\n* event:\nJSON file containing event data passed to the Lambda function during invoke. If this option is not specified, no event is assumed. Pass in the value '-' to input JSON via stdin\n* no_event:\nDEPRECATED: By default no event is assumed.\n* template_file:\nAWS SAM template which references built artifacts for resources in the template. (if applicable)\n* env_vars:\nJSON file containing values for Lambda function's environment variables.\n* parameter_overrides:\nString that contains AWS CloudFormation parameter overrides encoded as key=value pairs.\n* debug_port:\nWhen specified, Lambda function container will start in debug mode and will expose this port on localhost.\n* debugger_path:\nHost path to a debugger that will be mounted into the Lambda container.\n* debug_args:\nAdditional arguments to be passed to the debugger.\n* container_env_vars:\nJSON file containing environment variables to be set within the container environment\n* docker_volume_basedir:\nSpecify the location basedir where the SAM template exists. If Docker is running on a remote machine, Path of the SAM template must be mounted on the Docker machine and modified to match the remote machine.\n* log_file:\nFile to capture output logs.\n* layer_cache_basedir:\nSpecify the location basedir where the lambda layers used by the template will be downloaded to.\n* skip_pull_image:\nSkip pulling down the latest Docker image for Lambda runtime.\n* docker_network:\nName or ID of an existing docker network for AWS Lambda docker containers to connect to, along with the default bridge network. If not specified, the Lambda containers will only connect to the default bridge docker network.\n* force_image_build:\nForce rebuilding the image used for invoking functions with layers.\n* shutdown:\nEmulate a shutdown event after invoke completes, to test extension handling of shutdown behavior.\n* container_host:\nHost of locally emulated Lambda container. This option is useful when the container runs on a different host than AWS SAM CLI. For example, if one wants to run AWS SAM CLI in a Docker container on macOS, this option could specify `host.docker.internal`\n* container_host_interface:\nIP address of the host network interface that container ports should bind to. Use 0.0.0.0 to bind to all interfaces.\n* invoke_image:\nContainer image URIs for invoking functions or starting api and function. One can specify the image URI used for the local function invocation (--invoke-image public.ecr.aws/sam/build-nodejs14.x:latest). One can also specify for each individual function with (--invoke-image Function1=public.ecr.aws/sam/build-nodejs14.x:latest). If a function does not have invoke image specified, the default AWS SAM CLI emulation image will be used.\n* beta_features:\nEnable/Disable beta features.\n* debug:\nTurn on debug logging to print debug message generated by AWS SAM CLI and display timestamps.\n* profile:\nSelect a specific profile from your credential file to get AWS credentials.\n* region:\nSet the AWS Region of the service. (e.g. us-east-1)", "type": "object", "properties": { + "terraform_plan_file": { + "title": "terraform_plan_file", + "type": "string", + "description": "Used for passing a custom plan file when executing the Terraform hook." + }, "hook_name": { "title": "hook_name", "type": "string", @@ -516,9 +526,14 @@ "properties": { "parameters": { "title": "Parameters for the local start api command", - "description": "Available parameters for the local start api command:\n* hook_name:\nHook package id to extend AWS SAM CLI commands functionality. \n\nExample: `terraform` to extend AWS SAM CLI commands functionality to support terraform applications. \n\nAvailable Hook Names: ['terraform']\n* skip_prepare_infra:\nSkip preparation stage when there are no infrastructure changes. Only used in conjunction with --hook-name.\n* host:\nLocal hostname or IP address to bind to (default: '127.0.0.1')\n* port:\nLocal port number to listen on (default: '3000')\n* static_dir:\nAny static assets (e.g. CSS/Javascript/HTML) files located in this directory will be presented at /\n* template_file:\nAWS SAM template which references built artifacts for resources in the template. (if applicable)\n* env_vars:\nJSON file containing values for Lambda function's environment variables.\n* parameter_overrides:\nString that contains AWS CloudFormation parameter overrides encoded as key=value pairs.\n* debug_port:\nWhen specified, Lambda function container will start in debug mode and will expose this port on localhost.\n* debugger_path:\nHost path to a debugger that will be mounted into the Lambda container.\n* debug_args:\nAdditional arguments to be passed to the debugger.\n* container_env_vars:\nJSON file containing environment variables to be set within the container environment\n* docker_volume_basedir:\nSpecify the location basedir where the SAM template exists. If Docker is running on a remote machine, Path of the SAM template must be mounted on the Docker machine and modified to match the remote machine.\n* log_file:\nFile to capture output logs.\n* layer_cache_basedir:\nSpecify the location basedir where the lambda layers used by the template will be downloaded to.\n* skip_pull_image:\nSkip pulling down the latest Docker image for Lambda runtime.\n* docker_network:\nName or ID of an existing docker network for AWS Lambda docker containers to connect to, along with the default bridge network. If not specified, the Lambda containers will only connect to the default bridge docker network.\n* force_image_build:\nForce rebuilding the image used for invoking functions with layers.\n* warm_containers:\nOptional. Specifies how AWS SAM CLI manages \ncontainers for each function.\nTwo modes are available:\nEAGER: Containers for all functions are \nloaded at startup and persist between \ninvocations.\nLAZY: Containers are only loaded when each \nfunction is first invoked. Those containers \npersist for additional invocations.\n* debug_function:\nOptional. Specifies the Lambda Function logicalId to apply debug options to when --warm-containers is specified. This parameter applies to --debug-port, --debugger-path, and --debug-args.\n* shutdown:\nEmulate a shutdown event after invoke completes, to test extension handling of shutdown behavior.\n* container_host:\nHost of locally emulated Lambda container. This option is useful when the container runs on a different host than AWS SAM CLI. For example, if one wants to run AWS SAM CLI in a Docker container on macOS, this option could specify `host.docker.internal`\n* container_host_interface:\nIP address of the host network interface that container ports should bind to. Use 0.0.0.0 to bind to all interfaces.\n* invoke_image:\nContainer image URIs for invoking functions or starting api and function. One can specify the image URI used for the local function invocation (--invoke-image public.ecr.aws/sam/build-nodejs14.x:latest). One can also specify for each individual function with (--invoke-image Function1=public.ecr.aws/sam/build-nodejs14.x:latest). If a function does not have invoke image specified, the default AWS SAM CLI emulation image will be used.\n* beta_features:\nEnable/Disable beta features.\n* debug:\nTurn on debug logging to print debug message generated by AWS SAM CLI and display timestamps.\n* profile:\nSelect a specific profile from your credential file to get AWS credentials.\n* region:\nSet the AWS Region of the service. (e.g. us-east-1)", + "description": "Available parameters for the local start api command:\n* terraform_plan_file:\nUsed for passing a custom plan file when executing the Terraform hook.\n* hook_name:\nHook package id to extend AWS SAM CLI commands functionality. \n\nExample: `terraform` to extend AWS SAM CLI commands functionality to support terraform applications. \n\nAvailable Hook Names: ['terraform']\n* skip_prepare_infra:\nSkip preparation stage when there are no infrastructure changes. Only used in conjunction with --hook-name.\n* host:\nLocal hostname or IP address to bind to (default: '127.0.0.1')\n* port:\nLocal port number to listen on (default: '3000')\n* static_dir:\nAny static assets (e.g. CSS/Javascript/HTML) files located in this directory will be presented at /\n* template_file:\nAWS SAM template which references built artifacts for resources in the template. (if applicable)\n* env_vars:\nJSON file containing values for Lambda function's environment variables.\n* parameter_overrides:\nString that contains AWS CloudFormation parameter overrides encoded as key=value pairs.\n* debug_port:\nWhen specified, Lambda function container will start in debug mode and will expose this port on localhost.\n* debugger_path:\nHost path to a debugger that will be mounted into the Lambda container.\n* debug_args:\nAdditional arguments to be passed to the debugger.\n* container_env_vars:\nJSON file containing environment variables to be set within the container environment\n* docker_volume_basedir:\nSpecify the location basedir where the SAM template exists. If Docker is running on a remote machine, Path of the SAM template must be mounted on the Docker machine and modified to match the remote machine.\n* log_file:\nFile to capture output logs.\n* layer_cache_basedir:\nSpecify the location basedir where the lambda layers used by the template will be downloaded to.\n* skip_pull_image:\nSkip pulling down the latest Docker image for Lambda runtime.\n* docker_network:\nName or ID of an existing docker network for AWS Lambda docker containers to connect to, along with the default bridge network. If not specified, the Lambda containers will only connect to the default bridge docker network.\n* force_image_build:\nForce rebuilding the image used for invoking functions with layers.\n* warm_containers:\nOptional. Specifies how AWS SAM CLI manages \ncontainers for each function.\nTwo modes are available:\nEAGER: Containers for all functions are \nloaded at startup and persist between \ninvocations.\nLAZY: Containers are only loaded when each \nfunction is first invoked. Those containers \npersist for additional invocations.\n* debug_function:\nOptional. Specifies the Lambda Function logicalId to apply debug options to when --warm-containers is specified. This parameter applies to --debug-port, --debugger-path, and --debug-args.\n* shutdown:\nEmulate a shutdown event after invoke completes, to test extension handling of shutdown behavior.\n* container_host:\nHost of locally emulated Lambda container. This option is useful when the container runs on a different host than AWS SAM CLI. For example, if one wants to run AWS SAM CLI in a Docker container on macOS, this option could specify `host.docker.internal`\n* container_host_interface:\nIP address of the host network interface that container ports should bind to. Use 0.0.0.0 to bind to all interfaces.\n* invoke_image:\nContainer image URIs for invoking functions or starting api and function. One can specify the image URI used for the local function invocation (--invoke-image public.ecr.aws/sam/build-nodejs14.x:latest). One can also specify for each individual function with (--invoke-image Function1=public.ecr.aws/sam/build-nodejs14.x:latest). If a function does not have invoke image specified, the default AWS SAM CLI emulation image will be used.\n* beta_features:\nEnable/Disable beta features.\n* debug:\nTurn on debug logging to print debug message generated by AWS SAM CLI and display timestamps.\n* profile:\nSelect a specific profile from your credential file to get AWS credentials.\n* region:\nSet the AWS Region of the service. (e.g. us-east-1)", "type": "object", "properties": { + "terraform_plan_file": { + "title": "terraform_plan_file", + "type": "string", + "description": "Used for passing a custom plan file when executing the Terraform hook." + }, "hook_name": { "title": "hook_name", "type": "string", @@ -703,9 +718,14 @@ "properties": { "parameters": { "title": "Parameters for the local start lambda command", - "description": "Available parameters for the local start lambda command:\n* hook_name:\nHook package id to extend AWS SAM CLI commands functionality. \n\nExample: `terraform` to extend AWS SAM CLI commands functionality to support terraform applications. \n\nAvailable Hook Names: ['terraform']\n* skip_prepare_infra:\nSkip preparation stage when there are no infrastructure changes. Only used in conjunction with --hook-name.\n* host:\nLocal hostname or IP address to bind to (default: '127.0.0.1')\n* port:\nLocal port number to listen on (default: '3001')\n* template_file:\nAWS SAM template which references built artifacts for resources in the template. (if applicable)\n* env_vars:\nJSON file containing values for Lambda function's environment variables.\n* parameter_overrides:\nString that contains AWS CloudFormation parameter overrides encoded as key=value pairs.\n* debug_port:\nWhen specified, Lambda function container will start in debug mode and will expose this port on localhost.\n* debugger_path:\nHost path to a debugger that will be mounted into the Lambda container.\n* debug_args:\nAdditional arguments to be passed to the debugger.\n* container_env_vars:\nJSON file containing environment variables to be set within the container environment\n* docker_volume_basedir:\nSpecify the location basedir where the SAM template exists. If Docker is running on a remote machine, Path of the SAM template must be mounted on the Docker machine and modified to match the remote machine.\n* log_file:\nFile to capture output logs.\n* layer_cache_basedir:\nSpecify the location basedir where the lambda layers used by the template will be downloaded to.\n* skip_pull_image:\nSkip pulling down the latest Docker image for Lambda runtime.\n* docker_network:\nName or ID of an existing docker network for AWS Lambda docker containers to connect to, along with the default bridge network. If not specified, the Lambda containers will only connect to the default bridge docker network.\n* force_image_build:\nForce rebuilding the image used for invoking functions with layers.\n* warm_containers:\nOptional. Specifies how AWS SAM CLI manages \ncontainers for each function.\nTwo modes are available:\nEAGER: Containers for all functions are \nloaded at startup and persist between \ninvocations.\nLAZY: Containers are only loaded when each \nfunction is first invoked. Those containers \npersist for additional invocations.\n* debug_function:\nOptional. Specifies the Lambda Function logicalId to apply debug options to when --warm-containers is specified. This parameter applies to --debug-port, --debugger-path, and --debug-args.\n* shutdown:\nEmulate a shutdown event after invoke completes, to test extension handling of shutdown behavior.\n* container_host:\nHost of locally emulated Lambda container. This option is useful when the container runs on a different host than AWS SAM CLI. For example, if one wants to run AWS SAM CLI in a Docker container on macOS, this option could specify `host.docker.internal`\n* container_host_interface:\nIP address of the host network interface that container ports should bind to. Use 0.0.0.0 to bind to all interfaces.\n* invoke_image:\nContainer image URIs for invoking functions or starting api and function. One can specify the image URI used for the local function invocation (--invoke-image public.ecr.aws/sam/build-nodejs14.x:latest). One can also specify for each individual function with (--invoke-image Function1=public.ecr.aws/sam/build-nodejs14.x:latest). If a function does not have invoke image specified, the default AWS SAM CLI emulation image will be used.\n* beta_features:\nEnable/Disable beta features.\n* debug:\nTurn on debug logging to print debug message generated by AWS SAM CLI and display timestamps.\n* profile:\nSelect a specific profile from your credential file to get AWS credentials.\n* region:\nSet the AWS Region of the service. (e.g. us-east-1)", + "description": "Available parameters for the local start lambda command:\n* terraform_plan_file:\nUsed for passing a custom plan file when executing the Terraform hook.\n* hook_name:\nHook package id to extend AWS SAM CLI commands functionality. \n\nExample: `terraform` to extend AWS SAM CLI commands functionality to support terraform applications. \n\nAvailable Hook Names: ['terraform']\n* skip_prepare_infra:\nSkip preparation stage when there are no infrastructure changes. Only used in conjunction with --hook-name.\n* host:\nLocal hostname or IP address to bind to (default: '127.0.0.1')\n* port:\nLocal port number to listen on (default: '3001')\n* template_file:\nAWS SAM template which references built artifacts for resources in the template. (if applicable)\n* env_vars:\nJSON file containing values for Lambda function's environment variables.\n* parameter_overrides:\nString that contains AWS CloudFormation parameter overrides encoded as key=value pairs.\n* debug_port:\nWhen specified, Lambda function container will start in debug mode and will expose this port on localhost.\n* debugger_path:\nHost path to a debugger that will be mounted into the Lambda container.\n* debug_args:\nAdditional arguments to be passed to the debugger.\n* container_env_vars:\nJSON file containing environment variables to be set within the container environment\n* docker_volume_basedir:\nSpecify the location basedir where the SAM template exists. If Docker is running on a remote machine, Path of the SAM template must be mounted on the Docker machine and modified to match the remote machine.\n* log_file:\nFile to capture output logs.\n* layer_cache_basedir:\nSpecify the location basedir where the lambda layers used by the template will be downloaded to.\n* skip_pull_image:\nSkip pulling down the latest Docker image for Lambda runtime.\n* docker_network:\nName or ID of an existing docker network for AWS Lambda docker containers to connect to, along with the default bridge network. If not specified, the Lambda containers will only connect to the default bridge docker network.\n* force_image_build:\nForce rebuilding the image used for invoking functions with layers.\n* warm_containers:\nOptional. Specifies how AWS SAM CLI manages \ncontainers for each function.\nTwo modes are available:\nEAGER: Containers for all functions are \nloaded at startup and persist between \ninvocations.\nLAZY: Containers are only loaded when each \nfunction is first invoked. Those containers \npersist for additional invocations.\n* debug_function:\nOptional. Specifies the Lambda Function logicalId to apply debug options to when --warm-containers is specified. This parameter applies to --debug-port, --debugger-path, and --debug-args.\n* shutdown:\nEmulate a shutdown event after invoke completes, to test extension handling of shutdown behavior.\n* container_host:\nHost of locally emulated Lambda container. This option is useful when the container runs on a different host than AWS SAM CLI. For example, if one wants to run AWS SAM CLI in a Docker container on macOS, this option could specify `host.docker.internal`\n* container_host_interface:\nIP address of the host network interface that container ports should bind to. Use 0.0.0.0 to bind to all interfaces.\n* invoke_image:\nContainer image URIs for invoking functions or starting api and function. One can specify the image URI used for the local function invocation (--invoke-image public.ecr.aws/sam/build-nodejs14.x:latest). One can also specify for each individual function with (--invoke-image Function1=public.ecr.aws/sam/build-nodejs14.x:latest). If a function does not have invoke image specified, the default AWS SAM CLI emulation image will be used.\n* beta_features:\nEnable/Disable beta features.\n* debug:\nTurn on debug logging to print debug message generated by AWS SAM CLI and display timestamps.\n* profile:\nSelect a specific profile from your credential file to get AWS credentials.\n* region:\nSet the AWS Region of the service. (e.g. us-east-1)", "type": "object", "properties": { + "terraform_plan_file": { + "title": "terraform_plan_file", + "type": "string", + "description": "Used for passing a custom plan file when executing the Terraform hook." + }, "hook_name": { "title": "hook_name", "type": "string", From 2b0ee61bc232f87a6ad6462ce0b489ecd95ed3c4 Mon Sep 17 00:00:00 2001 From: Daniel Mil Date: Wed, 9 Aug 2023 17:05:32 -0700 Subject: [PATCH 6/6] Check to ensure hook name is provided --- samcli/commands/_utils/options.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/samcli/commands/_utils/options.py b/samcli/commands/_utils/options.py index 045f408f9f..79397d00d4 100644 --- a/samcli/commands/_utils/options.py +++ b/samcli/commands/_utils/options.py @@ -789,10 +789,32 @@ def use_container_build_option(f): return use_container_build_click_option()(f) +def terraform_plan_file_callback(ctx, param, provided_value): + """ + Callback for --terraform-plan-file to check if --hook-name is also specified + + Parameters + ---------- + ctx: click.core.Context + Click context + param: click.Option + Parameter properties + provided_value: bool + True if option was provided + """ + is_option_provided = provided_value or ctx.default_map.get("terraform_plan_file") + is_hook_provided = ctx.params.get("hook_name") or ctx.default_map.get("hook_name") + + if is_option_provided and not is_hook_provided: + raise click.BadOptionUsage(option_name=param.name, ctx=ctx, message="Missing option --hook-name") + + def terraform_plan_file_click_option(): return click.option( "--terraform-plan-file", type=click.Path(), + required=False, + callback=terraform_plan_file_callback, help="Used for passing a custom plan file when executing the Terraform hook.", )