Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 26 additions & 3 deletions samcli/commands/_utils/options.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@


_TEMPLATE_OPTION_DEFAULT_VALUE = "template.[yaml|yml]"
DEFAULT_STACK_NAME = "sam-app"


LOG = logging.getLogger(__name__)
Expand Down Expand Up @@ -56,6 +57,28 @@ def get_or_default_template_file_name(ctx, param, provided_value, include_build)
return result


def guided_deploy_stack_name(ctx, param, provided_value):
"""
Provide a default value for stack name if invoked with a guided deploy.
:param ctx: Click Context
:param param: Param name
:param provided_value: Value provided by Click, it would be the value provided by the user.
:return: Actual value to be used in the CLI
"""

guided = ctx.params.get("guided", False) or ctx.params.get("g", False)

if not guided and not provided_value:
raise click.BadOptionUsage(
option_name=param.name,
ctx=ctx,
message="Missing option '--stack-name', 'sam deploy –guided' can "
"be used to provide and save needed parameters for future deploys.",
)

return provided_value if provided_value else DEFAULT_STACK_NAME


def template_common_option(f):
"""
Common ClI option for template
Expand Down Expand Up @@ -127,9 +150,9 @@ def parameter_override_click_option():
cls=OptionNargs,
type=CfnParameterOverridesType(),
default={},
help="Optional. A string that contains CloudFormation parameter overrides encoded as key=value "
"pairs. Use the same format as the AWS CLI, e.g. 'ParameterKey=KeyPairName,"
"ParameterValue=MyKey ParameterKey=InstanceType,ParameterValue=t1.micro'",
help="Optional. A string that contains AWS CloudFormation parameter overrides encoded as key=value pairs."
"For example, 'ParameterKey=KeyPairName,ParameterValue=MyKey ParameterKey=InstanceType,"
"ParameterValue=t1.micro' or KeyPairName=MyKey InstanceType=t1.micro",
)


Expand Down
35 changes: 20 additions & 15 deletions samcli/commands/deploy/command.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
template_click_option,
metadata_override_option,
_space_separated_list_func_type,
guided_deploy_stack_name,
)
from samcli.commands._utils.template import get_template_parameters
from samcli.commands.deploy.exceptions import GuidedDeployFailedError
Expand Down Expand Up @@ -47,10 +48,19 @@
help=HELP_TEXT,
)
@configuration_option(provider=TomlProvider(section=CONFIG_SECTION))
@click.option(
"--guided",
"-g",
required=False,
is_flag=True,
is_eager=True,
help="Specify this flag to allow SAM CLI to guide you through the deployment using guided prompts.",
)
@template_click_option(include_build=True)
@click.option(
"--stack-name",
required=True,
required=False,
callback=guided_deploy_stack_name,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice! Should roll this into init too sometime, possibly.

help="The name of the AWS CloudFormation stack you're deploying to. "
"If you specify an existing stack, the command updates the stack. "
"If you specify a new stack, the command creates it.",
Expand Down Expand Up @@ -121,14 +131,6 @@
help="Indicates whether to use JSON as the format for "
"the output AWS CloudFormation template. YAML is used by default.",
)
@click.option(
"--guided",
"-g",
required=False,
is_flag=True,
is_eager=True,
help="Specify this flag to allow SAM CLI to guide you through the deployment using guided prompts.",
)
@metadata_override_option
@notification_arns_override_option
@tags_override_option
Expand Down Expand Up @@ -213,6 +215,7 @@ def do_cli(
_parameter_overrides = None
guided_stack_name = None
guided_s3_bucket = None
guided_s3_prefix = None
guided_region = None

if guided:
Expand All @@ -221,7 +224,7 @@ def do_cli(

_parameter_override_keys = get_template_parameters(template_file=template_file)

guided_stack_name, guided_s3_bucket, guided_region, guided_profile, changeset_decision, _capabilities, _parameter_overrides, save_to_config = guided_deploy(
guided_stack_name, guided_s3_bucket, guided_s3_prefix, guided_region, guided_profile, changeset_decision, _capabilities, _parameter_overrides, save_to_config = guided_deploy(
stack_name, s3_bucket, region, profile, confirm_changeset, _parameter_override_keys, parameter_overrides
)

Expand All @@ -230,6 +233,7 @@ def do_cli(
template_file,
stack_name=guided_stack_name,
s3_bucket=guided_s3_bucket,
s3_prefix=guided_s3_prefix,
region=guided_region,
profile=guided_profile,
confirm_changeset=changeset_decision,
Expand All @@ -251,7 +255,7 @@ def do_cli(
with PackageContext(
template_file=template_file,
s3_bucket=guided_s3_bucket if guided else s3_bucket,
s3_prefix=s3_prefix,
s3_prefix=guided_s3_prefix if guided else s3_prefix,
output_template_file=output_template_file.name,
kms_key_id=kms_key_id,
use_json=use_json,
Expand All @@ -268,7 +272,7 @@ def do_cli(
stack_name=guided_stack_name if guided else stack_name,
s3_bucket=guided_s3_bucket if guided else s3_bucket,
force_upload=force_upload,
s3_prefix=s3_prefix,
s3_prefix=guided_s3_prefix if guided else s3_prefix,
kms_key_id=kms_key_id,
parameter_overrides=sanitize_parameter_overrides(_parameter_overrides) if guided else parameter_overrides,
capabilities=_capabilities if guided else capabilities,
Expand Down Expand Up @@ -301,6 +305,7 @@ def guided_deploy(
)

stack_name = click.prompt(f"\t{start_bold}Stack Name{end_bold}", default=default_stack_name, type=click.STRING)
s3_prefix = stack_name
region = click.prompt(f"\t{start_bold}AWS Region{end_bold}", default=default_region, type=click.STRING)
input_parameter_overrides = prompt_parameters(parameter_override_keys, start_bold, end_bold)

Expand All @@ -327,6 +332,7 @@ def guided_deploy(
return (
stack_name,
s3_bucket,
s3_prefix,
region,
profile,
confirm_changeset,
Expand All @@ -347,11 +353,10 @@ def prompt_parameters(parameter_override_keys, start_bold, end_bold):
)
_prompted_param_overrides[parameter_key] = {"Value": parameter, "Hidden": True}
else:
# Make sure the default is casted to a string.
parameter = click.prompt(
f"\t{start_bold}Parameter {parameter_key}{end_bold}",
default=_prompted_param_overrides.get(
parameter_key, parameter_properties.get("Default", "No default specified")
),
default=_prompted_param_overrides.get(parameter_key, str(parameter_properties.get("Default", ""))),
type=click.STRING,
)
_prompted_param_overrides[parameter_key] = {"Value": parameter, "Hidden": False}
Expand Down
23 changes: 22 additions & 1 deletion samcli/lib/bootstrap/bootstrap.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
from samcli.commands.exceptions import UserException, CredentialsError, RegionError


SAM_CLI_STACK_NAME = "aws-sam-cli-managed-stack"
SAM_CLI_STACK_NAME = "aws-sam-cli-managed-default"


def manage_stack(profile, region):
Expand Down Expand Up @@ -120,6 +120,27 @@ def _get_stack_template():
- Key: ManagedStackSource
Value: AwsSamCli

SamCliSourceBucketBucketPolicy:
Type: AWS::S3::BucketPolicy
Properties:
Bucket: !Ref SamCliSourceBucket
PolicyDocument:
Statement:
-
Action:
- "s3:GetObject"
Effect: "Allow"
Resource:
Fn::Join:
- ""
-
- "arn:aws:s3:::"
-
!Ref SamCliSourceBucket
Comment on lines +138 to +139
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this looks bit weird, should this be - !Ref SamCliSourceBucket?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it could also be !Sub "arn:aws:s3:::${SamCliSourceBucket}"

- "/*"
Principal:
Service: serverlessrepo.amazonaws.com

Outputs:
SourceBucket:
Value: !Ref SamCliSourceBucket
Expand Down
5 changes: 4 additions & 1 deletion samcli/lib/deploy/deployer.py
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,6 @@ def create_changeset(
:param tags: Array of tags passed to CloudFormation
:return:
"""

if not self.has_stack(stack_name):
changeset_type = "CREATE"
# When creating a new stack, UsePreviousValue=True is invalid.
Expand Down Expand Up @@ -178,12 +177,16 @@ def create_changeset(
kwargs["RoleARN"] = role_arn
if notification_arns is not None:
kwargs["NotificationARNs"] = notification_arns
return self._create_change_set(stack_name=stack_name, changeset_type=changeset_type, **kwargs)

def _create_change_set(self, stack_name, changeset_type, **kwargs):
try:
resp = self._client.create_change_set(**kwargs)
return resp, changeset_type
except botocore.exceptions.ClientError as ex:
if "The bucket you are attempting to access must be addressed using the specified endpoint" in str(ex):
raise DeployBucketInDifferentRegionError(f"Failed to create/update stack {stack_name}")
raise ChangeSetError(stack_name=stack_name, msg=str(ex))

except Exception as ex:
LOG.debug("Unable to create changeset", exc_info=ex)
Expand Down
2 changes: 2 additions & 0 deletions samcli/lib/package/s3_uploader.py
Original file line number Diff line number Diff line change
Expand Up @@ -205,3 +205,5 @@ def on_progress(self, bytes_transferred, **kwargs):
"\rUploading to %s %s / %s (%.2f%%)" % (self._remote_path, self._seen_so_far, self._size, percentage)
)
sys.stderr.flush()
if int(percentage) == 100:
sys.stderr.write("\n")
2 changes: 1 addition & 1 deletion tests/integration/deploy/test_deploy_command.py
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,7 @@ def test_deploy_without_stack_name(self, template_file):
deploy_process_execute = Popen(deploy_command_list, stdout=PIPE, stderr=PIPE)
deploy_process_execute.wait()
# Error no stack name present
self.assertEqual(deploy_process_execute.returncode, 1)
self.assertEqual(deploy_process_execute.returncode, 2)

@parameterized.expand(["aws-serverless-function.yaml"])
def test_deploy_without_capabilities(self, template_file):
Expand Down
4 changes: 3 additions & 1 deletion tests/unit/cli/test_cli_config_file.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,11 @@


class MockContext:
def __init__(self, info_name, parent):
def __init__(self, info_name, parent, params=None, command=None):
self.info_name = info_name
self.parent = parent
self.params = params
self.command = command


class TestTomlProvider(TestCase):
Expand Down
54 changes: 52 additions & 2 deletions tests/unit/commands/_utils/test_options.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,16 @@
import os

from unittest import TestCase
from unittest.mock import patch
from samcli.commands._utils.options import get_or_default_template_file_name, _TEMPLATE_OPTION_DEFAULT_VALUE
from unittest.mock import patch, MagicMock

import click

from samcli.commands._utils.options import (
get_or_default_template_file_name,
_TEMPLATE_OPTION_DEFAULT_VALUE,
guided_deploy_stack_name,
)
from tests.unit.cli.test_cli_config_file import MockContext


class Mock:
Expand Down Expand Up @@ -71,3 +79,45 @@ def test_verify_ctx(self, os_mock):
self.assertEqual(result, "a/b/c/absPath")
self.assertEqual(ctx.samconfig_dir, "a/b/c")
os_mock.path.abspath.assert_called_with(expected)


class TestGuidedDeployStackName(TestCase):
def test_must_return_provided_value_guided(self):
stack_name = "provided-stack"
mock_params = MagicMock()
mock_params.get = MagicMock(return_value=True)
result = guided_deploy_stack_name(
ctx=MockContext(info_name="test", parent=None, params=mock_params),
param=MagicMock(),
provided_value=stack_name,
)
self.assertEqual(result, stack_name)

def test_must_return_default_value_guided(self):
stack_name = None
mock_params = MagicMock()
mock_params.get = MagicMock(return_value=True)
result = guided_deploy_stack_name(
ctx=MockContext(info_name="test", parent=None, params=mock_params),
param=MagicMock(),
provided_value=stack_name,
)
self.assertEqual(result, "sam-app")

def test_must_return_provided_value_non_guided(self):
stack_name = "provided-stack"
mock_params = MagicMock()
mock_params.get = MagicMock(return_value=False)
result = guided_deploy_stack_name(ctx=MagicMock(), param=MagicMock(), provided_value=stack_name)
self.assertEqual(result, "provided-stack")

def test_exception_missing_parameter_no_value_non_guided(self):
stack_name = None
mock_params = MagicMock()
mock_params.get = MagicMock(return_value=False)
with self.assertRaises(click.BadOptionUsage):
guided_deploy_stack_name(
ctx=MockContext(info_name="test", parent=None, params=mock_params),
param=MagicMock(),
provided_value=stack_name,
)
12 changes: 7 additions & 5 deletions tests/unit/commands/deploy/test_command.py
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ def test_all_args_guided(
stack_name="sam-app",
s3_bucket="managed-s3-bucket",
force_upload=self.force_upload,
s3_prefix=self.s3_prefix,
s3_prefix="sam-app",
kms_key_id=self.kms_key_id,
parameter_overrides={"Myparameter": "guidedParameter", "MyNoEchoParameter": "secure"},
capabilities=self.capabilities,
Expand All @@ -169,6 +169,7 @@ def test_all_args_guided(
region="us-east-1",
s3_bucket="managed-s3-bucket",
stack_name="sam-app",
s3_prefix="sam-app",
parameter_overrides={
"Myparameter": {"Value": "guidedParameter", "Hidden": False},
"MyNoEchoParameter": {"Value": "secure", "Hidden": True},
Expand Down Expand Up @@ -238,7 +239,7 @@ def test_all_args_guided_no_save_echo_param_to_config(
stack_name="sam-app",
s3_bucket="managed-s3-bucket",
force_upload=self.force_upload,
s3_prefix=self.s3_prefix,
s3_prefix="sam-app",
kms_key_id=self.kms_key_id,
parameter_overrides={"Myparameter": "guidedParameter", "MyNoEchoParameter": "secure"},
capabilities=self.capabilities,
Expand All @@ -256,12 +257,13 @@ def test_all_args_guided_no_save_echo_param_to_config(
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_count, 7)
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", "s3_prefix", "sam-app"),
call(["deploy"], "parameters", "region", "us-east-1"),
call(["deploy"], "parameters", "confirm_changeset", True),
call(["deploy"], "parameters", "capabilities", "CAPABILITY_IAM"),
Expand Down Expand Up @@ -325,7 +327,7 @@ def test_all_args_guided_no_params_save_config(
stack_name="sam-app",
s3_bucket="managed-s3-bucket",
force_upload=self.force_upload,
s3_prefix=self.s3_prefix,
s3_prefix="sam-app",
kms_key_id=self.kms_key_id,
parameter_overrides=self.parameter_overrides,
capabilities=self.capabilities,
Expand Down Expand Up @@ -401,7 +403,7 @@ def test_all_args_guided_no_params_no_save_config(
stack_name="sam-app",
s3_bucket="managed-s3-bucket",
force_upload=self.force_upload,
s3_prefix=self.s3_prefix,
s3_prefix="sam-app",
kms_key_id=self.kms_key_id,
parameter_overrides=self.parameter_overrides,
capabilities=self.capabilities,
Expand Down
4 changes: 2 additions & 2 deletions tests/unit/lib/bootstrap/test_bootstrap.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ def test_new_stack(self):
"ChangeSetType": "CREATE",
"ChangeSetName": "InitialCreation",
}
ccs_resp = {"Id": "id", "StackId": "aws-sam-cli-managed-stack"}
ccs_resp = {"Id": "id", "StackId": "aws-sam-cli-managed-default"}
stubber.add_response("create_change_set", ccs_resp, ccs_params)
# describe change set creation status for waiter
dcs_params = {"ChangeSetName": "InitialCreation", "StackName": SAM_CLI_STACK_NAME}
Expand Down Expand Up @@ -189,7 +189,7 @@ def test_change_set_execution_fails(self):
"ChangeSetType": "CREATE",
"ChangeSetName": "InitialCreation",
}
ccs_resp = {"Id": "id", "StackId": "aws-sam-cli-managed-stack"}
ccs_resp = {"Id": "id", "StackId": "aws-sam-cli-managed-default"}
stubber.add_response("create_change_set", ccs_resp, ccs_params)
# describe change set creation status for waiter
dcs_params = {"ChangeSetName": "InitialCreation", "StackName": SAM_CLI_STACK_NAME}
Expand Down
Loading