diff --git a/samcli/hook_packages/terraform/hooks/prepare/hook.py b/samcli/hook_packages/terraform/hooks/prepare/hook.py index 22422d4017..72575a611b 100644 --- a/samcli/hook_packages/terraform/hooks/prepare/hook.py +++ b/samcli/hook_packages/terraform/hooks/prepare/hook.py @@ -15,6 +15,7 @@ import hashlib import logging import shutil +import uuid from samcli.lib.hook.exceptions import PrepareHookException, InvalidSamMetadataPropertiesException from samcli.lib.utils import osutils @@ -46,6 +47,7 @@ PropertyBuilderMapping = Dict[str, PropertyBuilder] TERRAFORM_BUILD_SCRIPT = "copy_terraform_built_artifacts.py" +TF_BACKEND_OVERRIDE_FILENAME = "z_samcli_backend_override" CFN_CODE_PROPERTIES = { CFN_AWS_LAMBDA_FUNCTION: "Code", @@ -802,6 +804,9 @@ def _generate_makefile( if not os.path.exists(output_directory_path): os.makedirs(output_directory_path, exist_ok=True) + # create z_samcli_backend_override.tf in output directory + _generate_backend_override_file(output_directory_path) + # copy copy_terraform_built_artifacts.py script into output directory copy_terraform_built_artifacts_script_path = os.path.join( Path(os.path.dirname(__file__)).parent.parent, TERRAFORM_BUILD_SCRIPT @@ -814,6 +819,22 @@ def _generate_makefile( makefile.writelines(makefile_rules) +def _generate_backend_override_file(output_directory_path: str): + """ + Generates an override tf file to use a temporary backend + + Parameters + ---------- + output_directory_path: str + the output directory path to write the generated makefile + """ + statefile_filename = f"{uuid.uuid4()}.tfstate" + override_content = "terraform {\n" ' backend "local" {\n' f' path = "./{statefile_filename}"\n' " }\n" "}\n" + override_file_path = os.path.join(output_directory_path, TF_BACKEND_OVERRIDE_FILENAME) + with open(override_file_path, "w+") as f: + f.write(override_content) + + def _get_relevant_cfn_resource( sam_metadata_resource: SamMetadataResource, cfn_resources: Dict[str, Dict] ) -> Tuple[Dict, str]: @@ -919,6 +940,12 @@ def _generate_makefile_rule_for_lambda_resource( """ target = _get_makefile_build_target(logical_id) resource_address = sam_metadata_resource.resource.get("address", "") + move_override_recipe = _format_makefile_recipe( + "cp " + f"{Path(output_dir, TF_BACKEND_OVERRIDE_FILENAME).relative_to(terraform_application_dir)} " + f"./{TF_BACKEND_OVERRIDE_FILENAME}.tf" + ) + init_command_recipe = _format_makefile_recipe("terraform init -reconfigure") apply_command_template = "terraform apply -target {resource_address} -replace {resource_address} -auto-approve" apply_command_recipe = _format_makefile_recipe(apply_command_template.format(resource_address=resource_address)) show_command_recipe = _format_makefile_recipe( @@ -926,7 +953,13 @@ def _generate_makefile_rule_for_lambda_resource( python_command_name, output_dir, resource_address, sam_metadata_resource, terraform_application_dir ) ) - return f"{target}{apply_command_recipe}{show_command_recipe}" + return ( + f"{target}" + f"{move_override_recipe}" + f"{init_command_recipe}" + f"{apply_command_recipe}" + f"{show_command_recipe}" + ) def _build_show_command( 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 172a67b97a..5aa4ef1b91 100644 --- a/tests/unit/hook_packages/terraform/hooks/prepare/test_hook.py +++ b/tests/unit/hook_packages/terraform/hooks/prepare/test_hook.py @@ -2499,10 +2499,15 @@ def test_generate_makefile( ): mock_os.path.exists.return_value = output_dir_exists + mock_copy_tf_backend_override_file_path = Mock() mock_copy_terraform_built_artifacts_script_path = Mock() mock_makefile_path = Mock() mock_os.path.dirname.return_value = "" - mock_os.path.join.side_effect = [mock_copy_terraform_built_artifacts_script_path, mock_makefile_path] + mock_os.path.join.side_effect = [ + mock_copy_tf_backend_override_file_path, + mock_copy_terraform_built_artifacts_script_path, + mock_makefile_path, + ] mock_makefile = Mock() mock_open.return_value.__enter__.return_value = mock_makefile @@ -2605,6 +2610,8 @@ def test_get_python_command_name_python_not_found(self, mock_run_side_effect, mo @patch("samcli.hook_packages.terraform.hooks.prepare.hook._format_makefile_recipe") def test_generate_makefile_rule_for_lambda_resource(self, format_recipe_mock, get_build_target_mock): format_recipe_mock.side_effect = [ + "\tcp .aws-sam-iacs/iacs_metadata/z_samcli_backend_override.tf ./z_samcli_backend_override.tf\n", + "\tterraform init -reconfigure\n", "\tterraform apply -target null_resource.sam_metadata_aws_lambda_function -auto-approve\n", "\tterraform show -json | python3 .aws-sam/iacs_metadata/copy_terraform_built_artifacts.py --expression " '"|values|root_module|resources|[?address=="null_resource.sam_metadata_aws_lambda_function"]' @@ -2624,6 +2631,8 @@ def test_generate_makefile_rule_for_lambda_resource(self, format_recipe_mock, ge ) expected_makefile_rule = ( "build-function_logical_id:\n" + "\tcp .aws-sam-iacs/iacs_metadata/z_samcli_backend_override.tf ./z_samcli_backend_override.tf\n" + "\tterraform init -reconfigure\n" "\tterraform apply -target null_resource.sam_metadata_aws_lambda_function -auto-approve\n" "\tterraform show -json | python3 .aws-sam/iacs_metadata/copy_terraform_built_artifacts.py " '--expression "|values|root_module|resources|[?address=="null_resource.sam_metadata_aws_lambda_function"]'