diff --git a/samcli/commands/deploy/command.py b/samcli/commands/deploy/command.py index b7a594009c..33eb24204c 100644 --- a/samcli/commands/deploy/command.py +++ b/samcli/commands/deploy/command.py @@ -271,7 +271,7 @@ def do_cli( force_upload=force_upload, s3_prefix=s3_prefix, kms_key_id=kms_key_id, - parameter_overrides=_parameter_overrides if guided else parameter_overrides, + parameter_overrides=sanitize_parameter_overrides(_parameter_overrides) if guided else parameter_overrides, capabilities=_capabilities if guided else capabilities, no_execute_changeset=no_execute_changeset, role_arn=role_arn, @@ -305,13 +305,21 @@ def guided_deploy( region = click.prompt(f"\t{start_bold}AWS Region{end_bold}", default=default_region, type=click.STRING) if parameter_override_keys: for parameter_key, parameter_properties in parameter_override_keys.items(): - input_parameter_overrides[parameter_key] = click.prompt( - f"\t{start_bold}Parameter {parameter_key}{end_bold}", - default=parameter_overrides.get( - parameter_key, parameter_properties.get("Default", "No default specified") - ), - type=click.STRING, - ) + no_echo = parameter_properties.get("NoEcho", False) + if no_echo: + parameter = click.prompt( + f"\t{start_bold}Parameter {parameter_key}{end_bold}", type=click.STRING, hide_input=True + ) + input_parameter_overrides[parameter_key] = {"Value": parameter, "Hidden": True} + else: + parameter = click.prompt( + f"\t{start_bold}Parameter {parameter_key}{end_bold}", + default=parameter_overrides.get( + parameter_key, parameter_properties.get("Default", "No default specified") + ), + type=click.STRING, + ) + input_parameter_overrides[parameter_key] = {"Value": parameter, "Hidden": False} click.secho("\t#Shows you resources changes to be deployed and require a 'Y' to initiate deploy") confirm_changeset = click.confirm( @@ -332,7 +340,7 @@ def guided_deploy( click.echo(color.yellow("\n\tS3 bucket for deployments\n\t=========================")) s3_bucket = manage_stack(profile=profile, region=region) click.echo(f"\tS3 bucket: {s3_bucket}") - click.echo("\tA different default S3 bucket can be set in /.samconfig.toml") + click.echo("\tA different default S3 bucket can be set in samconfig.toml") return ( stack_name, @@ -348,7 +356,11 @@ def guided_deploy( def print_deploy_args(stack_name, s3_bucket, region, capabilities, parameter_overrides, confirm_changeset): - param_overrides_string = parameter_overrides + _parameters = parameter_overrides.copy() + for key, value in _parameters.items(): + if isinstance(value, dict): + _parameters[key] = value.get("Value", value) if not value.get("Hidden") else "*" * len(value.get("Value")) + capabilities_string = json.dumps(capabilities) click.secho("\n\tDeploying with following values\n\t===============================", fg="yellow") @@ -357,7 +369,7 @@ def print_deploy_args(stack_name, s3_bucket, region, capabilities, parameter_ove click.echo(f"\tConfirm changeset : {confirm_changeset}") click.echo(f"\tDeployment s3 bucket : {s3_bucket}") click.echo(f"\tCapabilities : {capabilities_string}") - click.echo(f"\tParameter overrides : {param_overrides_string}") + click.echo(f"\tParameter overrides : {_parameters}") click.secho("\nInitiating deployment\n=====================", fg="yellow") @@ -394,8 +406,15 @@ def save_config(template_file, parameter_overrides, **kwargs): samconfig.put(cmd_names, section, key, value) if parameter_overrides: - parameter_overrides_value = " ".join([f"{key}={value}" for key, value in parameter_overrides.items()]) - samconfig.put(cmd_names, section, "parameter_overrides", parameter_overrides_value) + _params = [] + for key, value in parameter_overrides.items(): + if isinstance(value, dict): + if not value.get("Hidden"): + _params.append(f"{key}={value.get('Value')}") + else: + _params.append(f"{key}={value}") + if _params: + samconfig.put(cmd_names, section, "parameter_overrides", " ".join(_params)) samconfig.flush() @@ -413,3 +432,7 @@ def get_config_ctx(template_file): config_dir=samconfig_dir if samconfig_dir else SamConfig.config_dir(template_file_path=template_file) ) return ctx, samconfig + + +def sanitize_parameter_overrides(parameter_overrides): + return {key: value.get("Value") if isinstance(value, dict) else value for key, value in parameter_overrides.items()} diff --git a/samcli/lib/bootstrap/bootstrap.py b/samcli/lib/bootstrap/bootstrap.py index 758258b63b..a39489e82f 100644 --- a/samcli/lib/bootstrap/bootstrap.py +++ b/samcli/lib/bootstrap/bootstrap.py @@ -96,7 +96,7 @@ def _create_stack(cloudformation_client): def _get_stack_template(): gc = GlobalConfig() - info = {"version": __version__, "installationId": gc.installation_id} + info = {"version": __version__, "installationId": gc.installation_id if gc.installation_id else "unknown"} template = """ AWSTemplateFormatVersion : '2010-09-09' diff --git a/tests/unit/commands/deploy/test_command.py b/tests/unit/commands/deploy/test_command.py index 39162d72fc..3a17a4c7cd 100644 --- a/tests/unit/commands/deploy/test_command.py +++ b/tests/unit/commands/deploy/test_command.py @@ -1,7 +1,8 @@ from unittest import TestCase -from unittest.mock import patch, Mock, ANY, MagicMock +from unittest.mock import patch, Mock, ANY, MagicMock, call from samcli.commands.deploy.command import do_cli +from tests.unit.cli.test_cli_config_file import MockContext class TestDeployliCommand(TestCase): @@ -105,10 +106,13 @@ def test_all_args_guided( mock_sam_config = MagicMock() mock_sam_config.exists = MagicMock(return_value=True) mock_get_config_ctx.return_value = (None, mock_sam_config) - mock_get_template_parameters.return_value = {"Myparameter": {"Type": "String"}} + mock_get_template_parameters.return_value = { + "Myparameter": {"Type": "String"}, + "MyNoEchoParameter": {"Type": "String", "NoEcho": True}, + } mock_deploy_context.return_value.__enter__.return_value = context_mock mock_deploy_click.prompt = MagicMock( - side_effect=["sam-app", "us-east-1", "guidedParameter", ("CAPABILITY_IAM",)] + side_effect=["sam-app", "us-east-1", "guidedParameter", "secure", ("CAPABILITY_IAM",)] ) mock_deploy_click.confirm = MagicMock(side_effect=[True, False, True]) @@ -144,7 +148,7 @@ def test_all_args_guided( force_upload=self.force_upload, s3_prefix=self.s3_prefix, kms_key_id=self.kms_key_id, - parameter_overrides={"Myparameter": "guidedParameter"}, + parameter_overrides={"Myparameter": "guidedParameter", "MyNoEchoParameter": "secure"}, capabilities=self.capabilities, no_execute_changeset=self.no_execute_changeset, role_arn=self.role_arn, @@ -165,11 +169,106 @@ def test_all_args_guided( region="us-east-1", s3_bucket="managed-s3-bucket", stack_name="sam-app", - parameter_overrides={"Myparameter": "guidedParameter"}, + parameter_overrides={ + "Myparameter": {"Value": "guidedParameter", "Hidden": False}, + "MyNoEchoParameter": {"Value": "secure", "Hidden": True}, + }, ) mock_managed_stack.assert_called_with(profile=self.profile, region="us-east-1") self.assertEqual(context_mock.run.call_count, 1) + @patch("samcli.commands.package.command.click") + @patch("samcli.commands.package.package_context.PackageContext") + @patch("samcli.commands.deploy.command.click") + @patch("samcli.commands.deploy.deploy_context.DeployContext") + @patch("samcli.commands.deploy.command.manage_stack") + @patch("samcli.commands.deploy.command.get_template_parameters") + @patch("samcli.commands.deploy.command.get_config_ctx") + def test_all_args_guided_no_save_echo_param_to_config( + self, + mock_get_config_ctx, + mock_get_template_parameters, + mock_managed_stack, + mock_deploy_context, + mock_deploy_click, + mock_package_context, + mock_package_click, + ): + + context_mock = Mock() + mock_sam_config = MagicMock() + mock_sam_config.exists = MagicMock(return_value=True) + mock_get_config_ctx.return_value = (MockContext(info_name="deploy", parent=None), mock_sam_config) + mock_get_template_parameters.return_value = { + "Myparameter": {"Type": "String"}, + "MyNoEchoParameter": {"Type": "String", "NoEcho": True}, + } + mock_deploy_context.return_value.__enter__.return_value = context_mock + mock_deploy_click.prompt = MagicMock( + side_effect=["sam-app", "us-east-1", "guidedParameter", "secure", ("CAPABILITY_IAM",)] + ) + mock_deploy_click.confirm = MagicMock(side_effect=[True, False, True]) + + mock_managed_stack.return_value = "managed-s3-bucket" + + do_cli( + template_file=self.template_file, + stack_name=self.stack_name, + s3_bucket=None, + force_upload=self.force_upload, + s3_prefix=self.s3_prefix, + kms_key_id=self.kms_key_id, + parameter_overrides=self.parameter_overrides, + capabilities=self.capabilities, + no_execute_changeset=self.no_execute_changeset, + role_arn=self.role_arn, + notification_arns=self.notification_arns, + fail_on_empty_changeset=self.fail_on_empty_changset, + tags=self.tags, + region=self.region, + profile=self.profile, + use_json=self.use_json, + metadata=self.metadata, + guided=True, + confirm_changeset=True, + ) + + mock_deploy_context.assert_called_with( + template_file=ANY, + stack_name="sam-app", + s3_bucket="managed-s3-bucket", + force_upload=self.force_upload, + s3_prefix=self.s3_prefix, + kms_key_id=self.kms_key_id, + parameter_overrides={"Myparameter": "guidedParameter", "MyNoEchoParameter": "secure"}, + capabilities=self.capabilities, + no_execute_changeset=self.no_execute_changeset, + role_arn=self.role_arn, + notification_arns=self.notification_arns, + fail_on_empty_changeset=self.fail_on_empty_changset, + tags=self.tags, + region="us-east-1", + profile=self.profile, + confirm_changeset=True, + ) + + context_mock.run.assert_called_with() + mock_managed_stack.assert_called_with(profile=self.profile, region="us-east-1") + self.assertEqual(context_mock.run.call_count, 1) + + self.assertEqual(mock_sam_config.put.call_count, 6) + self.assertEqual( + mock_sam_config.put.call_args_list, + [ + call(["deploy"], "parameters", "stack_name", "sam-app"), + call(["deploy"], "parameters", "s3_bucket", "managed-s3-bucket"), + call(["deploy"], "parameters", "region", "us-east-1"), + call(["deploy"], "parameters", "confirm_changeset", True), + call(["deploy"], "parameters", "capabilities", "CAPABILITY_IAM"), + call(["deploy"], "parameters", "parameter_overrides", "Myparameter=guidedParameter"), + ], + ) + @patch("samcli.commands.package.command.click") @patch("samcli.commands.package.package_context.PackageContext") @patch("samcli.commands.deploy.command.click")