Skip to content

Commit 0451b59

Browse files
committed
Package and deploy in one command (#1532)
* feat: beginnings of package and deploy together * fix: plumb through configuration - need to add additional parameters over from package. * fix: conditional check to print text on package during deploy * fix: exceptions for `package` * lint: local variables rule - `samcli/commands/deploy/command.py` has high number of local variables, because of the nature of command, it has high number of arguments.
1 parent 5854f2a commit 0451b59

File tree

11 files changed

+93
-56
lines changed

11 files changed

+93
-56
lines changed

.pylintrc

+1-1
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ confidence=
5959
# --enable=similarities". If you want to run only the classes checker, but have
6060
# no Warning level messages displayed, use"--disable=all --enable=classes
6161
# --disable=W"
62-
disable=R0201,W0613,W0640,I0021,I0020,W1618,W1619,R0902,R0903,W0231,W0611,R0913,W0703,C0330,R0204,I0011,R0904,C0301
62+
disable=R0201,W0613,W0640,I0021,I0020,W1618,W1619,R0902,R0903,W0231,W0611,R0913,W0703,C0330,R0204,I0011,R0904,R0914,C0301
6363

6464

6565
[REPORTS]

samcli/cli/types.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,9 @@ class CfnParameterOverridesType(click.ParamType):
2222
# https://regex101.com/r/xqfSjW/2
2323
# https://regex101.com/r/xqfSjW/5
2424

25-
# If Both ParameterKey pattern and KeyPairName=MyKey, should not be fixed. if they are it can
26-
# result in unpredicatable behavior.
25+
# If Both ParameterKey pattern and KeyPairName=MyKey should not be present
26+
# while adding parameter overrides, if they are, it
27+
# can result in unpredicatable behavior.
2728
KEY_REGEX = '([A-Za-z0-9\\"]+)'
2829
VALUE_REGEX = '(\\"(?:\\\\.|[^\\"\\\\]+)*\\"|(?:\\\\.|[^ \\"\\\\]+)+))'
2930

samcli/commands/_utils/options.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,7 @@ def parameter_override_click_option():
122122
"--parameter-overrides",
123123
cls=OptionNargs,
124124
type=CfnParameterOverridesType(),
125+
default={},
125126
help="Optional. A string that contains CloudFormation parameter overrides encoded as key=value "
126127
"pairs. Use the same format as the AWS CLI, e.g. 'ParameterKey=KeyPairName,"
127128
"ParameterValue=MyKey ParameterKey=InstanceType,ParameterValue=t1.micro'",
@@ -148,8 +149,7 @@ def capabilities_click_option():
148149
return click.option(
149150
"--capabilities",
150151
cls=OptionNargs,
151-
type=FuncParamType(lambda value: value.split(" ")),
152-
required=True,
152+
type=FuncParamType(lambda value: value.split(" ") if not isinstance(value, tuple) else value),
153153
help="A list of capabilities that you must specify"
154154
"before AWS Cloudformation can create certain stacks. Some stack tem-"
155155
"plates might include resources that can affect permissions in your AWS"
@@ -187,7 +187,7 @@ def notification_arns_click_option():
187187
return click.option(
188188
"--notification-arns",
189189
cls=OptionNargs,
190-
type=FuncParamType(lambda value: value.split(" ")),
190+
type=FuncParamType(lambda value: value.split(" ") if not isinstance(value, tuple) else value),
191191
required=False,
192192
help="Amazon Simple Notification Service topic"
193193
"Amazon Resource Names (ARNs) that AWS CloudFormation associates with"

samcli/commands/deploy/command.py

+56-21
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,19 @@
22
CLI command for "deploy" command
33
"""
44

5+
import tempfile
6+
57
import click
68

7-
from samcli.cli.cli_config_file import configuration_option, TomlProvider
89
from samcli.commands._utils.options import (
910
parameter_override_option,
1011
capabilities_override_option,
1112
tags_override_option,
1213
notification_arns_override_option,
1314
template_click_option,
15+
metadata_override_option,
1416
)
17+
from samcli.cli.cli_config_file import configuration_option, TomlProvider
1518
from samcli.cli.main import pass_context, common_options, aws_creds_options
1619
from samcli.lib.telemetry.metrics import track_command
1720

@@ -28,14 +31,14 @@
2831
"""
2932

3033

31-
@configuration_option(provider=TomlProvider(section="parameters"))
3234
@click.command(
3335
"deploy",
3436
short_help=SHORT_HELP,
3537
context_settings={"ignore_unknown_options": False, "allow_interspersed_args": True, "allow_extra_args": True},
3638
help=HELP_TEXT,
3739
)
38-
@template_click_option(include_build=False)
40+
@configuration_option(provider=TomlProvider(section="parameters"))
41+
@template_click_option(include_build=True)
3942
@click.option(
4043
"--stack-name",
4144
required=True,
@@ -96,6 +99,14 @@
9699
"changes to be made to the stack. The default behavior is to return a"
97100
"non-zero exit code.",
98101
)
102+
@click.option(
103+
"--use-json",
104+
required=False,
105+
is_flag=True,
106+
help="Indicates whether to use JSON as the format for "
107+
"the output AWS CloudFormation template. YAML is used by default.",
108+
)
109+
@metadata_override_option
99110
@notification_arns_override_option
100111
@tags_override_option
101112
@parameter_override_option
@@ -118,7 +129,9 @@ def cli(
118129
role_arn,
119130
notification_arns,
120131
fail_on_empty_changeset,
132+
use_json,
121133
tags,
134+
metadata,
122135
):
123136

124137
# All logic must be implemented in the ``do_cli`` method. This helps with easy unit testing
@@ -135,7 +148,9 @@ def cli(
135148
role_arn,
136149
notification_arns,
137150
fail_on_empty_changeset,
151+
use_json,
138152
tags,
153+
metadata,
139154
ctx.region,
140155
ctx.profile,
141156
) # pragma: no cover
@@ -154,27 +169,47 @@ def do_cli(
154169
role_arn,
155170
notification_arns,
156171
fail_on_empty_changeset,
172+
use_json,
157173
tags,
174+
metadata,
158175
region,
159176
profile,
160177
):
178+
from samcli.commands.package.package_context import PackageContext
161179
from samcli.commands.deploy.deploy_context import DeployContext
162180

163-
with DeployContext(
164-
template_file=template_file,
165-
stack_name=stack_name,
166-
s3_bucket=s3_bucket,
167-
force_upload=force_upload,
168-
s3_prefix=s3_prefix,
169-
kms_key_id=kms_key_id,
170-
parameter_overrides=parameter_overrides,
171-
capabilities=capabilities,
172-
no_execute_changeset=no_execute_changeset,
173-
role_arn=role_arn,
174-
notification_arns=notification_arns,
175-
fail_on_empty_changeset=fail_on_empty_changeset,
176-
tags=tags,
177-
region=region,
178-
profile=profile,
179-
) as deploy_context:
180-
deploy_context.run()
181+
with tempfile.NamedTemporaryFile() as output_template_file:
182+
183+
with PackageContext(
184+
template_file=template_file,
185+
s3_bucket=s3_bucket,
186+
s3_prefix=s3_prefix,
187+
output_template_file=output_template_file.name,
188+
kms_key_id=kms_key_id,
189+
use_json=use_json,
190+
force_upload=force_upload,
191+
metadata=metadata,
192+
on_deploy=True,
193+
region=region,
194+
profile=profile,
195+
) as package_context:
196+
package_context.run()
197+
198+
with DeployContext(
199+
template_file=output_template_file.name,
200+
stack_name=stack_name,
201+
s3_bucket=s3_bucket,
202+
force_upload=force_upload,
203+
s3_prefix=s3_prefix,
204+
kms_key_id=kms_key_id,
205+
parameter_overrides=parameter_overrides,
206+
capabilities=capabilities,
207+
no_execute_changeset=no_execute_changeset,
208+
role_arn=role_arn,
209+
notification_arns=notification_arns,
210+
fail_on_empty_changeset=fail_on_empty_changeset,
211+
tags=tags,
212+
region=region,
213+
profile=profile,
214+
) as deploy_context:
215+
deploy_context.run()

samcli/commands/package/command.py

+3-9
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,12 @@
11
"""
22
CLI command for "package" command
33
"""
4-
from functools import partial
5-
64
import click
75

8-
from samcli.cli.cli_config_file import TomlProvider, configuration_option
6+
7+
from samcli.cli.cli_config_file import configuration_option, TomlProvider
98
from samcli.cli.main import pass_context, common_options, aws_creds_options
10-
from samcli.commands._utils.options import (
11-
metadata_override_option,
12-
_TEMPLATE_OPTION_DEFAULT_VALUE,
13-
get_or_default_template_file_name,
14-
template_click_option,
15-
)
9+
from samcli.commands._utils.options import metadata_override_option, template_click_option
1610
from samcli.commands._utils.resources import resources_generator
1711
from samcli.lib.telemetry.metrics import track_command
1812

samcli/commands/package/exceptions.py

+9
Original file line numberDiff line numberDiff line change
@@ -72,3 +72,12 @@ def __init__(self, template_file, ex):
7272
super(PackageFailedError, self).__init__(
7373
message=message_fmt.format(template_file=self.template_file, ex=self.ex)
7474
)
75+
76+
77+
class NoSuchBucketError(UserException):
78+
def __init__(self, **kwargs):
79+
self.kwargs = kwargs
80+
81+
message_fmt = "\n S3 Bucket does not exist."
82+
83+
super(NoSuchBucketError, self).__init__(message=message_fmt.format(**self.kwargs))

samcli/commands/package/package_context.py

+4-2
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434
class PackageContext:
3535

3636
MSG_PACKAGED_TEMPLATE_WRITTEN = (
37-
"Successfully packaged artifacts and wrote output template "
37+
"\nSuccessfully packaged artifacts and wrote output template "
3838
"to file {output_file_name}."
3939
"\n"
4040
"Execute the following command to deploy the packaged template"
@@ -56,6 +56,7 @@ def __init__(
5656
metadata,
5757
region,
5858
profile,
59+
on_deploy=False,
5960
):
6061
self.template_file = template_file
6162
self.s3_bucket = s3_bucket
@@ -67,6 +68,7 @@ def __init__(
6768
self.metadata = metadata
6869
self.region = region
6970
self.profile = profile
71+
self.on_deploy = on_deploy
7072
self.s3_uploader = None
7173

7274
def __enter__(self):
@@ -91,7 +93,7 @@ def run(self):
9193

9294
self.write_output(self.output_template_file, exported_str)
9395

94-
if self.output_template_file:
96+
if self.output_template_file and not self.on_deploy:
9597
msg = self.MSG_PACKAGED_TEMPLATE_WRITTEN.format(
9698
output_file_name=self.output_template_file,
9799
output_file_path=os.path.abspath(self.output_template_file),

samcli/lib/package/artifact_exporter.py

-2
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,6 @@ def zip_folder(folder_path):
171171
:param folder_path:
172172
:return: Name of the zipfile
173173
"""
174-
175174
filename = os.path.join(tempfile.gettempdir(), "data-" + uuid.uuid4().hex)
176175

177176
zipfile_name = make_zip(filename, folder_path)
@@ -539,7 +538,6 @@ def __init__(
539538
"""
540539
Reads the template and makes it ready for export
541540
"""
542-
543541
if not (is_local_folder(parent_dir) and os.path.isabs(parent_dir)):
544542
raise ValueError("parent_dir parameter must be " "an absolute path to a folder {0}".format(parent_dir))
545543

samcli/lib/package/s3_uploader.py

+1-10
Original file line numberDiff line numberDiff line change
@@ -27,19 +27,11 @@
2727

2828
from boto3.s3 import transfer
2929

30+
from samcli.commands.package.exceptions import NoSuchBucketError
3031

3132
LOG = logging.getLogger(__name__)
3233

3334

34-
class NoSuchBucketError(Exception):
35-
def __init__(self, **kwargs):
36-
msg = self.fmt.format(**kwargs)
37-
Exception.__init__(self, msg)
38-
self.kwargs = kwargs
39-
40-
fmt = "S3 Bucket does not exist. " "Execute the command to create a new bucket" "\n" "aws s3 mb s3://{bucket_name}"
41-
42-
4335
class S3Uploader:
4436
"""
4537
Class to upload objects to S3 bucket that use versioning. If bucket
@@ -125,7 +117,6 @@ def upload_with_dedup(self, file_name, extension=None):
125117
# uploads of same object. Uploader will check if the file exists in S3
126118
# and re-upload only if necessary. So the template points to same file
127119
# in multiple places, this will upload only once
128-
129120
filemd5 = self.file_checksum(file_name)
130121
remote_path = filemd5
131122
if extension:

tests/unit/commands/deploy/test_command.py

+11-5
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
from unittest import TestCase
2-
from unittest.mock import patch, Mock
2+
from unittest.mock import patch, Mock, ANY
33

44
from samcli.commands.deploy.command import do_cli
55

@@ -23,13 +23,17 @@ def setUp(self):
2323
self.metadata = {"abc": "def"}
2424
self.region = None
2525
self.profile = None
26+
self.use_json = True
27+
self.metadata = {}
2628

29+
@patch("samcli.commands.package.command.click")
30+
@patch("samcli.commands.package.package_context.PackageContext")
2731
@patch("samcli.commands.deploy.command.click")
2832
@patch("samcli.commands.deploy.deploy_context.DeployContext")
29-
def test_all_args(self, deploy_command_context, click_mock):
33+
def test_all_args(self, mock_deploy_context, mock_deploy_click, mock_package_context, mock_package_click):
3034

3135
context_mock = Mock()
32-
deploy_command_context.return_value.__enter__.return_value = context_mock
36+
mock_deploy_context.return_value.__enter__.return_value = context_mock
3337

3438
do_cli(
3539
template_file=self.template_file,
@@ -47,10 +51,12 @@ def test_all_args(self, deploy_command_context, click_mock):
4751
tags=self.tags,
4852
region=self.region,
4953
profile=self.profile,
54+
use_json=self.use_json,
55+
metadata=self.metadata,
5056
)
5157

52-
deploy_command_context.assert_called_with(
53-
template_file=self.template_file,
58+
mock_deploy_context.assert_called_with(
59+
template_file=ANY,
5460
stack_name=self.stack_name,
5561
s3_bucket=self.s3_bucket,
5662
force_upload=self.force_upload,

tests/unit/lib/package/test_s3_uploader.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@
77
from pathlib import Path
88
from botocore.exceptions import ClientError
99

10-
from samcli.lib.package.s3_uploader import S3Uploader, NoSuchBucketError
10+
from samcli.commands.package.exceptions import NoSuchBucketError
11+
from samcli.lib.package.s3_uploader import S3Uploader
1112

1213

1314
class TestS3Uploader(TestCase):

0 commit comments

Comments
 (0)