From 874470353b884de8aa8116087b4d28d7a9597040 Mon Sep 17 00:00:00 2001 From: Vicky Wang Date: Thu, 21 Feb 2019 00:42:34 -0800 Subject: [PATCH 1/5] feat(publish): Add --semantic-version option to sam publish command --- samcli/commands/publish/command.py | 19 ++++-- .../publish/publish_app_integ_base.py | 5 +- .../integration/publish/test_command_integ.py | 21 ++++++ tests/unit/commands/publish/test_command.py | 67 ++++++++++++++++--- 4 files changed, 96 insertions(+), 16 deletions(-) diff --git a/samcli/commands/publish/command.py b/samcli/commands/publish/command.py index 77eebedab2..3b64f63517 100644 --- a/samcli/commands/publish/command.py +++ b/samcli/commands/publish/command.py @@ -7,12 +7,14 @@ from botocore.exceptions import ClientError from serverlessrepo import publish_application from serverlessrepo.publish import CREATE_APPLICATION +from serverlessrepo.parser import METADATA, SERVERLESS_REPO_APPLICATION from serverlessrepo.exceptions import ServerlessRepoError from samcli.cli.main import pass_context, common_options as cli_framework_options, aws_creds_options from samcli.commands._utils.options import template_common_option from samcli.commands._utils.template import get_template_data from samcli.commands.exceptions import UserException +from samcli.yamlhelper import yaml_dump HELP_TEXT = """ Use this command to publish a packaged AWS SAM template to @@ -31,20 +33,23 @@ """ SHORT_HELP = "Publish a packaged AWS SAM template to the AWS Serverless Application Repository." SERVERLESSREPO_CONSOLE_URL = "https://console.aws.amazon.com/serverlessrepo/home?region={}#/published-applications/{}" +SEMANTIC_VERSION_HELP = "Optional. The value provided here overwrites SemanticVersion in the template metadata." +SEMANTIC_VERSION = 'SemanticVersion' @click.command("publish", help=HELP_TEXT, short_help=SHORT_HELP) @template_common_option +@click.option('--semantic-version', help=SEMANTIC_VERSION_HELP) @aws_creds_options @cli_framework_options @pass_context -def cli(ctx, template): +def cli(ctx, template, semantic_version): # All logic must be implemented in the ``do_cli`` method. This helps with easy unit testing - do_cli(ctx, template) # pragma: no cover + do_cli(ctx, template, semantic_version) # pragma: no cover -def do_cli(ctx, template): +def do_cli(ctx, template, semantic_version): """Publish the application based on command line inputs.""" try: template_data = get_template_data(template) @@ -52,10 +57,16 @@ def do_cli(ctx, template): click.secho("Publish Failed", fg='red') raise UserException(str(ex)) + # Overwrite SemanticVersion in template metadata when provided in command input + if semantic_version and SERVERLESS_REPO_APPLICATION in template_data.get(METADATA, {}): + template_data.get(METADATA).get(SERVERLESS_REPO_APPLICATION)[SEMANTIC_VERSION] = semantic_version + with open(template, 'w') as fp: + fp.write(yaml_dump(template_data)) + try: publish_output = publish_application(template_data) click.secho("Publish Succeeded", fg="green") - click.secho(_gen_success_message(publish_output), fg="yellow") + click.secho(_gen_success_message(publish_output)) except ServerlessRepoError as ex: click.secho("Publish Failed", fg='red') raise UserException(str(ex)) diff --git a/tests/integration/publish/publish_app_integ_base.py b/tests/integration/publish/publish_app_integ_base.py index 7c93de99d9..d2c689fb0d 100644 --- a/tests/integration/publish/publish_app_integ_base.py +++ b/tests/integration/publish/publish_app_integ_base.py @@ -90,7 +90,7 @@ def base_command(self): return command - def get_command_list(self, template_path=None, region=None, profile=None): + def get_command_list(self, template_path=None, region=None, profile=None, semantic_version=None): command_list = [self.base_command(), "publish"] if template_path: @@ -102,4 +102,7 @@ def get_command_list(self, template_path=None, region=None, profile=None): if profile: command_list = command_list + ["--profile", profile] + if semantic_version: + command_list = command_list + ["--semantic-version", semantic_version] + return command_list diff --git a/tests/integration/publish/test_command_integ.py b/tests/integration/publish/test_command_integ.py index d50c58ba15..5d0da4e041 100644 --- a/tests/integration/publish/test_command_integ.py +++ b/tests/integration/publish/test_command_integ.py @@ -5,6 +5,7 @@ from unittest import skipIf +from samcli.commands.publish.command import SEMANTIC_VERSION from .publish_app_integ_base import PublishAppIntegBase # Publish tests require credentials and Travis will only add credentials to the env if the PR is from the same repo. @@ -59,6 +60,26 @@ def test_create_application_version(self): app_metadata = json.loads(app_metadata_text) self.assert_metadata_details(app_metadata, process_stdout.decode('utf-8')) + def test_create_application_version_overwrite_template_semantic_version(self): + template_path = self.temp_dir.joinpath("template_create_app_version.yaml") + command_list = self.get_command_list( + template_path=template_path, region=self.region_name, semantic_version='0.1.0') + + process = Popen(command_list, stdout=PIPE) + process.wait() + process_stdout = b"".join(process.stdout.readlines()).strip() + + overwritten_template_text = template_path.read_text() + self.assertIn('{}: 0.1.0'.format(SEMANTIC_VERSION), overwritten_template_text) + + expected_msg = 'The following metadata of application "{}" has been updated:'.format(self.application_id) + self.assertIn(expected_msg, process_stdout.decode('utf-8')) + + app_metadata_text = self.temp_dir.joinpath("metadata_create_app_version.json").read_text() + app_metadata = json.loads(app_metadata_text) + app_metadata[SEMANTIC_VERSION] = '0.1.0' + self.assert_metadata_details(app_metadata, process_stdout.decode('utf-8')) + @skipIf(SKIP_PUBLISH_TESTS, "Skip publish tests in Travis only") class TestPublishNewApp(PublishAppIntegBase): diff --git a/tests/unit/commands/publish/test_command.py b/tests/unit/commands/publish/test_command.py index 72bdbc6623..6d8f0f6ef1 100644 --- a/tests/unit/commands/publish/test_command.py +++ b/tests/unit/commands/publish/test_command.py @@ -1,13 +1,14 @@ """Test sam publish CLI.""" import json from unittest import TestCase -from mock import patch, call, Mock +from mock import patch, call, Mock, mock_open from botocore.exceptions import ClientError from serverlessrepo.exceptions import ServerlessRepoError from serverlessrepo.publish import CREATE_APPLICATION, UPDATE_APPLICATION +from serverlessrepo.parser import METADATA, SERVERLESS_REPO_APPLICATION -from samcli.commands.publish.command import do_cli as publish_cli +from samcli.commands.publish.command import do_cli as publish_cli, SEMANTIC_VERSION from samcli.commands.exceptions import UserException @@ -26,7 +27,7 @@ def setUp(self): def test_must_raise_if_value_error(self, click_mock, get_template_data_mock): get_template_data_mock.side_effect = ValueError("Template not found") with self.assertRaises(UserException) as context: - publish_cli(self.ctx_mock, self.template) + publish_cli(self.ctx_mock, self.template, None) message = str(context.exception) self.assertEqual("Template not found", message) @@ -38,7 +39,7 @@ def test_must_raise_if_value_error(self, click_mock, get_template_data_mock): def test_must_raise_if_serverlessrepo_error(self, click_mock, publish_application_mock): publish_application_mock.side_effect = ServerlessRepoError() with self.assertRaises(UserException): - publish_cli(self.ctx_mock, self.template) + publish_cli(self.ctx_mock, self.template, None) click_mock.secho.assert_called_with("Publish Failed", fg="red") @@ -56,7 +57,7 @@ def test_must_raise_if_s3_uri_error(self, click_mock, publish_application_mock): 'create_application' ) with self.assertRaises(UserException) as context: - publish_cli(self.ctx_mock, self.template) + publish_cli(self.ctx_mock, self.template, None) message = str(context.exception) self.assertIn("Please make sure that you have uploaded application artifacts " @@ -72,7 +73,7 @@ def test_must_raise_if_not_s3_uri_error(self, click_mock, publish_application_mo 'other_operation' ) with self.assertRaises(ClientError): - publish_cli(self.ctx_mock, self.template) + publish_cli(self.ctx_mock, self.template, None) click_mock.secho.assert_called_with("Publish Failed", fg="red") @@ -86,7 +87,7 @@ def test_must_succeed_to_create_application(self, click_mock, publish_applicatio 'actions': [CREATE_APPLICATION] } - publish_cli(self.ctx_mock, self.template) + publish_cli(self.ctx_mock, self.template, None) details_str = json.dumps({'attr1': 'value1'}, indent=2) expected_msg = "Created new application with the following metadata:\n{}" expected_link = self.console_link.format( @@ -95,7 +96,7 @@ def test_must_succeed_to_create_application(self, click_mock, publish_applicatio ) click_mock.secho.assert_has_calls([ call("Publish Succeeded", fg="green"), - call(expected_msg.format(details_str), fg="yellow"), + call(expected_msg.format(details_str)), call(expected_link, fg="yellow") ]) @@ -109,7 +110,7 @@ def test_must_succeed_to_update_application(self, click_mock, publish_applicatio 'actions': [UPDATE_APPLICATION] } - publish_cli(self.ctx_mock, self.template) + publish_cli(self.ctx_mock, self.template, None) details_str = json.dumps({'attr1': 'value1'}, indent=2) expected_msg = 'The following metadata of application "{}" has been updated:\n{}' expected_link = self.console_link.format( @@ -118,7 +119,7 @@ def test_must_succeed_to_update_application(self, click_mock, publish_applicatio ) click_mock.secho.assert_has_calls([ call("Publish Succeeded", fg="green"), - call(expected_msg.format(self.application_id, details_str), fg="yellow"), + call(expected_msg.format(self.application_id, details_str)), call(expected_link, fg="yellow") ]) @@ -139,9 +140,53 @@ def test_print_console_link_if_context_region_not_set(self, click_mock, boto3_mo session_mock.region_name = "us-west-1" boto3_mock.Session.return_value = session_mock - publish_cli(self.ctx_mock, self.template) + publish_cli(self.ctx_mock, self.template, None) expected_link = self.console_link.format( session_mock.region_name, self.application_id.replace('/', '~') ) click_mock.secho.assert_called_with(expected_link, fg="yellow") + + @patch('samcli.commands.publish.command.get_template_data') + @patch('samcli.commands.publish.command.publish_application') + def test_must_use_template_semantic_version(self, publish_application_mock, + get_template_data_mock): + template_data = { + METADATA: { + SERVERLESS_REPO_APPLICATION: {SEMANTIC_VERSION: '0.1'} + } + } + get_template_data_mock.return_value = template_data + publish_application_mock.return_value = { + 'application_id': self.application_id, + 'details': {}, 'actions': {} + } + publish_cli(self.ctx_mock, self.template, None) + publish_application_mock.assert_called_with(template_data) + + @patch('samcli.commands.publish.command.get_template_data') + @patch('samcli.commands.publish.command.publish_application') + def test_must_overwrite_semantic_version_if_provided(self, publish_application_mock, + get_template_data_mock): + template_data = { + METADATA: { + SERVERLESS_REPO_APPLICATION: {SEMANTIC_VERSION: '0.1'} + } + } + get_template_data_mock.return_value = template_data + publish_application_mock.return_value = { + 'application_id': self.application_id, + 'details': {}, 'actions': {} + } + + m = mock_open() + with patch("samcli.commands.publish.command.open", m): + publish_cli(self.ctx_mock, self.template, '0.2') + + m.assert_called_with(self.template, 'w') + expected_template_data = { + METADATA: { + SERVERLESS_REPO_APPLICATION: {SEMANTIC_VERSION: '0.2'} + } + } + publish_application_mock.assert_called_with(expected_template_data) From e087eff0c68816ed73a0a44f5c3e617e84c14517 Mon Sep 17 00:00:00 2001 From: Vicky Wang Date: Wed, 27 Feb 2019 14:28:11 -0800 Subject: [PATCH 2/5] Address feedback --- samcli/commands/publish/command.py | 21 ++++++----- .../integration/publish/test_command_integ.py | 12 +++++++ tests/unit/commands/publish/test_command.py | 36 ++++++++++++++++++- 3 files changed, 60 insertions(+), 9 deletions(-) diff --git a/samcli/commands/publish/command.py b/samcli/commands/publish/command.py index 3b64f63517..c4d5be770a 100644 --- a/samcli/commands/publish/command.py +++ b/samcli/commands/publish/command.py @@ -72,7 +72,7 @@ def do_cli(ctx, template, semantic_version): raise UserException(str(ex)) except ClientError as ex: click.secho("Publish Failed", fg='red') - raise _wrap_s3_uri_exception(ex) + raise _wrap_exception(ex) application_id = publish_output.get('application_id') _print_console_link(ctx.region, application_id) @@ -121,9 +121,9 @@ def _print_console_link(region, application_id): click.secho(msg, fg="yellow") -def _wrap_s3_uri_exception(ex): +def _wrap_exception(ex): """ - Wrap invalid S3 URI exception with a better error message. + Wrap exception with a better error message. Parameters ---------- @@ -133,14 +133,19 @@ def _wrap_s3_uri_exception(ex): Returns ------- Exception - UserException if found invalid S3 URI or ClientError + UserException if found invalid S3 URI or SemVer, else ClientError """ error_code = ex.response.get('Error').get('Code') message = ex.response.get('Error').get('Message') - if error_code == 'BadRequestException' and "Invalid S3 URI" in message: - return UserException( - "Your SAM template contains invalid S3 URIs. Please make sure that you have uploaded application " - "artifacts to S3 by packaging the template: 'sam package --template-file '.") + if error_code == 'BadRequestException': + if "Invalid S3 URI" in message: + return UserException( + "Your SAM template contains invalid S3 URIs. Please make sure that you have uploaded application " + "artifacts to S3 by packaging the template: 'sam package --template-file '.") + if "version name is a not valid SemVer" in message: + return UserException( + "The provided SemanticVersion is not a valid version number. Please follow the Semantic Versioning " + "scheme proposed in https://semver.org/") return ex diff --git a/tests/integration/publish/test_command_integ.py b/tests/integration/publish/test_command_integ.py index 5d0da4e041..afed5c7b42 100644 --- a/tests/integration/publish/test_command_integ.py +++ b/tests/integration/publish/test_command_integ.py @@ -80,6 +80,18 @@ def test_create_application_version_overwrite_template_semantic_version(self): app_metadata[SEMANTIC_VERSION] = '0.1.0' self.assert_metadata_details(app_metadata, process_stdout.decode('utf-8')) + def test_create_application_version_with_invalid_semantic_version(self): + template_path = self.temp_dir.joinpath("template_create_app_version.yaml") + command_list = self.get_command_list( + template_path=template_path, region=self.region_name, semantic_version='1') + + process = Popen(command_list, stderr=PIPE) + process.wait() + process_stderr = b"".join(process.stderr.readlines()).strip() + + expected_msg = 'The provided SemanticVersion is not a valid version number' + self.assertIn(expected_msg, process_stderr.decode('utf-8')) + @skipIf(SKIP_PUBLISH_TESTS, "Skip publish tests in Travis only") class TestPublishNewApp(PublishAppIntegBase): diff --git a/tests/unit/commands/publish/test_command.py b/tests/unit/commands/publish/test_command.py index 6d8f0f6ef1..d939c7966a 100644 --- a/tests/unit/commands/publish/test_command.py +++ b/tests/unit/commands/publish/test_command.py @@ -67,7 +67,41 @@ def test_must_raise_if_s3_uri_error(self, click_mock, publish_application_mock): @patch('samcli.commands.publish.command.get_template_data', Mock(return_value={})) @patch('samcli.commands.publish.command.publish_application') @patch('samcli.commands.publish.command.click') - def test_must_raise_if_not_s3_uri_error(self, click_mock, publish_application_mock): + def test_must_raise_if_invalid_semver_error(self, click_mock, publish_application_mock): + publish_application_mock.side_effect = ClientError( + { + 'Error': { + 'Code': 'BadRequestException', + 'Message': 'version name is a not valid SemVer' + } + }, + 'create_application' + ) + with self.assertRaises(UserException) as context: + publish_cli(self.ctx_mock, self.template, None) + + message = str(context.exception) + self.assertIn("The provided SemanticVersion is not a valid version number. Please " + "follow the Semantic Versioning scheme proposed in https://semver.org/", message) + click_mock.secho.assert_called_with("Publish Failed", fg="red") + + @patch('samcli.commands.publish.command.get_template_data', Mock(return_value={})) + @patch('samcli.commands.publish.command.publish_application') + @patch('samcli.commands.publish.command.click') + def test_must_raise_if_other_bad_request(self, click_mock, publish_application_mock): + publish_application_mock.side_effect = ClientError( + {'Error': {'Code': 'BadRequestException', 'Message': 'BadRequestMessage'}}, + 'other_operation' + ) + with self.assertRaises(ClientError): + publish_cli(self.ctx_mock, self.template, None) + + click_mock.secho.assert_called_with("Publish Failed", fg="red") + + @patch('samcli.commands.publish.command.get_template_data', Mock(return_value={})) + @patch('samcli.commands.publish.command.publish_application') + @patch('samcli.commands.publish.command.click') + def test_must_raise_if_other_client_error(self, click_mock, publish_application_mock): publish_application_mock.side_effect = ClientError( {'Error': {'Code': 'OtherError', 'Message': 'OtherMessage'}}, 'other_operation' From 0f6a665fdbdc021adc52079ad7808c59d0414e8c Mon Sep 17 00:00:00 2001 From: Vicky Wang Date: Wed, 27 Feb 2019 14:46:57 -0800 Subject: [PATCH 3/5] Update sam publish design doc --- designs/sam_publish_cmd.rst | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/designs/sam_publish_cmd.rst b/designs/sam_publish_cmd.rst index 45b2de3dd2..12d831afbc 100644 --- a/designs/sam_publish_cmd.rst +++ b/designs/sam_publish_cmd.rst @@ -132,6 +132,11 @@ Create new version of an existing SAR application Click the link below to view your application in AWS console: https://console.aws.amazon.com/serverlessrepo/home?region=#/published-applications/ + Alternatively, you can provide the new version number through the --semantic-version option without manually modifying + the template. The command will update the specified template and publish a new application version using it. + + >>> sam publish -t ./packaged.yaml --semantic-version 0.0.2 + Update the metadata of an existing application without creating new version Keep SemanticVersion unchanged, then modify metadata fields like Description or ReadmeUrl, and run ``sam publish -t ./packaged.yaml``. SAM CLI prints application metadata updated message, values of updated @@ -184,13 +189,15 @@ CLI Changes $ sam publish -t packaged.yaml --region Options: - -t, --template PATH AWS SAM template file [default: template.[yaml|yml]] - --profile TEXT Select a specific profile from your credential file to - get AWS credentials. - --region TEXT Set the AWS Region of the service (e.g. us-east-1). - --debug Turn on debug logging to print debug message generated - by SAM CLI. - --help Show this message and exit. + -t, --template PATH AWS SAM template file [default: template.[yaml|yml]] + --semantic-version TEXT Optional. The value provided here overwrites SemanticVersion + in the template metadata. + --profile TEXT Select a specific profile from your credential file to + get AWS credentials. + --region TEXT Set the AWS Region of the service (e.g. us-east-1). + --debug Turn on debug logging to print debug message generated + by SAM CLI. + --help Show this message and exit. 2. Update ``sam package`` (``aws cloudformation package``) command to support uploading locally referenced readme and license files to S3. From ad7fc53c638e77d03cfdb8f4432215555089c37e Mon Sep 17 00:00:00 2001 From: Vicky Wang Date: Tue, 5 Mar 2019 18:29:59 -0800 Subject: [PATCH 4/5] Remove error message parsing and do not write semver to template --- designs/sam_publish_cmd.rst | 4 +- requirements/base.txt | 2 +- samcli/commands/publish/command.py | 57 ++++----------- .../integration/publish/test_command_integ.py | 15 ---- tests/unit/commands/publish/test_command.py | 73 ++----------------- 5 files changed, 25 insertions(+), 126 deletions(-) diff --git a/designs/sam_publish_cmd.rst b/designs/sam_publish_cmd.rst index 12d831afbc..305ba8f5ad 100644 --- a/designs/sam_publish_cmd.rst +++ b/designs/sam_publish_cmd.rst @@ -133,7 +133,7 @@ Create new version of an existing SAR application https://console.aws.amazon.com/serverlessrepo/home?region=#/published-applications/ Alternatively, you can provide the new version number through the --semantic-version option without manually modifying - the template. The command will update the specified template and publish a new application version using it. + the template. The command will publish a new application version using the specified value. >>> sam publish -t ./packaged.yaml --semantic-version 0.0.2 @@ -190,7 +190,7 @@ CLI Changes Options: -t, --template PATH AWS SAM template file [default: template.[yaml|yml]] - --semantic-version TEXT Optional. The value provided here overwrites SemanticVersion + --semantic-version TEXT Optional. The value provided here overrides SemanticVersion in the template metadata. --profile TEXT Select a specific profile from your credential file to get AWS credentials. diff --git a/requirements/base.txt b/requirements/base.txt index 65bbf5ee59..f51e0f9984 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -13,4 +13,4 @@ python-dateutil~=2.6 pathlib2~=2.3.2; python_version<"3.4" requests==2.20.1 aws_lambda_builders==0.1.0 -serverlessrepo==0.1.5 +serverlessrepo==0.1.8 diff --git a/samcli/commands/publish/command.py b/samcli/commands/publish/command.py index c4d5be770a..7355dac68f 100644 --- a/samcli/commands/publish/command.py +++ b/samcli/commands/publish/command.py @@ -1,21 +1,24 @@ """CLI command for "publish" command.""" import json +import logging import click import boto3 -from botocore.exceptions import ClientError from serverlessrepo import publish_application from serverlessrepo.publish import CREATE_APPLICATION from serverlessrepo.parser import METADATA, SERVERLESS_REPO_APPLICATION -from serverlessrepo.exceptions import ServerlessRepoError +from serverlessrepo.exceptions import ServerlessRepoError, InvalidS3UriError from samcli.cli.main import pass_context, common_options as cli_framework_options, aws_creds_options from samcli.commands._utils.options import template_common_option from samcli.commands._utils.template import get_template_data from samcli.commands.exceptions import UserException -from samcli.yamlhelper import yaml_dump +LOG = logging.getLogger(__name__) + +SAM_PUBLISH_DOC = "https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-template-publishing-applications.html" # noqa +SAM_PACKAGE_DOC = "https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-cli-command-reference-sam-package.html" # noqa HELP_TEXT = """ Use this command to publish a packaged AWS SAM template to the AWS Serverless Application Repository to share within your team, @@ -24,13 +27,13 @@ This command expects the template's Metadata section to contain an AWS::ServerlessRepo::Application section with application metadata for publishing. For more details on this metadata section, see -https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-template-publishing-applications.html +{} \b Examples -------- To publish an application $ sam publish -t packaged.yaml --region -""" +""".format(SAM_PUBLISH_DOC) SHORT_HELP = "Publish a packaged AWS SAM template to the AWS Serverless Application Repository." SERVERLESSREPO_CONSOLE_URL = "https://console.aws.amazon.com/serverlessrepo/home?region={}#/published-applications/{}" SEMANTIC_VERSION_HELP = "Optional. The value provided here overwrites SemanticVersion in the template metadata." @@ -60,19 +63,21 @@ def do_cli(ctx, template, semantic_version): # Overwrite SemanticVersion in template metadata when provided in command input if semantic_version and SERVERLESS_REPO_APPLICATION in template_data.get(METADATA, {}): template_data.get(METADATA).get(SERVERLESS_REPO_APPLICATION)[SEMANTIC_VERSION] = semantic_version - with open(template, 'w') as fp: - fp.write(yaml_dump(template_data)) try: publish_output = publish_application(template_data) click.secho("Publish Succeeded", fg="green") click.secho(_gen_success_message(publish_output)) - except ServerlessRepoError as ex: + except InvalidS3UriError: click.secho("Publish Failed", fg='red') - raise UserException(str(ex)) - except ClientError as ex: + raise UserException( + "Your SAM template contains invalid S3 URIs. Please make sure that you have uploaded application " + "artifacts to S3 by packaging the template. See more details in {}".format(SAM_PACKAGE_DOC)) + except ServerlessRepoError as ex: click.secho("Publish Failed", fg='red') - raise _wrap_exception(ex) + LOG.debug("Failed to publish application to serverlessrepo", exc_info=True) + error_msg = '{}\nPlease follow the instructions in {}'.format(str(ex), SAM_PUBLISH_DOC) + raise UserException(error_msg) application_id = publish_output.get('application_id') _print_console_link(ctx.region, application_id) @@ -119,33 +124,3 @@ def _print_console_link(region, application_id): console_link = SERVERLESSREPO_CONSOLE_URL.format(region, application_id.replace('/', '~')) msg = "Click the link below to view your application in AWS console:\n{}".format(console_link) click.secho(msg, fg="yellow") - - -def _wrap_exception(ex): - """ - Wrap exception with a better error message. - - Parameters - ---------- - ex : ClientError - boto3 exception - - Returns - ------- - Exception - UserException if found invalid S3 URI or SemVer, else ClientError - """ - error_code = ex.response.get('Error').get('Code') - message = ex.response.get('Error').get('Message') - - if error_code == 'BadRequestException': - if "Invalid S3 URI" in message: - return UserException( - "Your SAM template contains invalid S3 URIs. Please make sure that you have uploaded application " - "artifacts to S3 by packaging the template: 'sam package --template-file '.") - if "version name is a not valid SemVer" in message: - return UserException( - "The provided SemanticVersion is not a valid version number. Please follow the Semantic Versioning " - "scheme proposed in https://semver.org/") - - return ex diff --git a/tests/integration/publish/test_command_integ.py b/tests/integration/publish/test_command_integ.py index afed5c7b42..ea0e712c80 100644 --- a/tests/integration/publish/test_command_integ.py +++ b/tests/integration/publish/test_command_integ.py @@ -69,9 +69,6 @@ def test_create_application_version_overwrite_template_semantic_version(self): process.wait() process_stdout = b"".join(process.stdout.readlines()).strip() - overwritten_template_text = template_path.read_text() - self.assertIn('{}: 0.1.0'.format(SEMANTIC_VERSION), overwritten_template_text) - expected_msg = 'The following metadata of application "{}" has been updated:'.format(self.application_id) self.assertIn(expected_msg, process_stdout.decode('utf-8')) @@ -80,18 +77,6 @@ def test_create_application_version_overwrite_template_semantic_version(self): app_metadata[SEMANTIC_VERSION] = '0.1.0' self.assert_metadata_details(app_metadata, process_stdout.decode('utf-8')) - def test_create_application_version_with_invalid_semantic_version(self): - template_path = self.temp_dir.joinpath("template_create_app_version.yaml") - command_list = self.get_command_list( - template_path=template_path, region=self.region_name, semantic_version='1') - - process = Popen(command_list, stderr=PIPE) - process.wait() - process_stderr = b"".join(process.stderr.readlines()).strip() - - expected_msg = 'The provided SemanticVersion is not a valid version number' - self.assertIn(expected_msg, process_stderr.decode('utf-8')) - @skipIf(SKIP_PUBLISH_TESTS, "Skip publish tests in Travis only") class TestPublishNewApp(PublishAppIntegBase): diff --git a/tests/unit/commands/publish/test_command.py b/tests/unit/commands/publish/test_command.py index d939c7966a..787bb5da73 100644 --- a/tests/unit/commands/publish/test_command.py +++ b/tests/unit/commands/publish/test_command.py @@ -1,10 +1,9 @@ """Test sam publish CLI.""" import json from unittest import TestCase -from mock import patch, call, Mock, mock_open +from mock import patch, call, Mock -from botocore.exceptions import ClientError -from serverlessrepo.exceptions import ServerlessRepoError +from serverlessrepo.exceptions import ServerlessRepoError, InvalidS3UriError from serverlessrepo.publish import CREATE_APPLICATION, UPDATE_APPLICATION from serverlessrepo.parser import METADATA, SERVERLESS_REPO_APPLICATION @@ -46,69 +45,13 @@ def test_must_raise_if_serverlessrepo_error(self, click_mock, publish_applicatio @patch('samcli.commands.publish.command.get_template_data', Mock(return_value={})) @patch('samcli.commands.publish.command.publish_application') @patch('samcli.commands.publish.command.click') - def test_must_raise_if_s3_uri_error(self, click_mock, publish_application_mock): - publish_application_mock.side_effect = ClientError( - { - 'Error': { - 'Code': 'BadRequestException', - 'Message': 'Invalid S3 URI' - } - }, - 'create_application' - ) - with self.assertRaises(UserException) as context: - publish_cli(self.ctx_mock, self.template, None) - - message = str(context.exception) - self.assertIn("Please make sure that you have uploaded application artifacts " - "to S3 by packaging the template", message) - click_mock.secho.assert_called_with("Publish Failed", fg="red") - - @patch('samcli.commands.publish.command.get_template_data', Mock(return_value={})) - @patch('samcli.commands.publish.command.publish_application') - @patch('samcli.commands.publish.command.click') - def test_must_raise_if_invalid_semver_error(self, click_mock, publish_application_mock): - publish_application_mock.side_effect = ClientError( - { - 'Error': { - 'Code': 'BadRequestException', - 'Message': 'version name is a not valid SemVer' - } - }, - 'create_application' - ) + def test_must_raise_if_invalid_S3_uri_error(self, click_mock, publish_application_mock): + publish_application_mock.side_effect = InvalidS3UriError(message="") with self.assertRaises(UserException) as context: publish_cli(self.ctx_mock, self.template, None) message = str(context.exception) - self.assertIn("The provided SemanticVersion is not a valid version number. Please " - "follow the Semantic Versioning scheme proposed in https://semver.org/", message) - click_mock.secho.assert_called_with("Publish Failed", fg="red") - - @patch('samcli.commands.publish.command.get_template_data', Mock(return_value={})) - @patch('samcli.commands.publish.command.publish_application') - @patch('samcli.commands.publish.command.click') - def test_must_raise_if_other_bad_request(self, click_mock, publish_application_mock): - publish_application_mock.side_effect = ClientError( - {'Error': {'Code': 'BadRequestException', 'Message': 'BadRequestMessage'}}, - 'other_operation' - ) - with self.assertRaises(ClientError): - publish_cli(self.ctx_mock, self.template, None) - - click_mock.secho.assert_called_with("Publish Failed", fg="red") - - @patch('samcli.commands.publish.command.get_template_data', Mock(return_value={})) - @patch('samcli.commands.publish.command.publish_application') - @patch('samcli.commands.publish.command.click') - def test_must_raise_if_other_client_error(self, click_mock, publish_application_mock): - publish_application_mock.side_effect = ClientError( - {'Error': {'Code': 'OtherError', 'Message': 'OtherMessage'}}, - 'other_operation' - ) - with self.assertRaises(ClientError): - publish_cli(self.ctx_mock, self.template, None) - + self.assertTrue("Your SAM template contains invalid S3 URIs" in message) click_mock.secho.assert_called_with("Publish Failed", fg="red") @patch('samcli.commands.publish.command.get_template_data', Mock(return_value={})) @@ -213,11 +156,7 @@ def test_must_overwrite_semantic_version_if_provided(self, publish_application_m 'details': {}, 'actions': {} } - m = mock_open() - with patch("samcli.commands.publish.command.open", m): - publish_cli(self.ctx_mock, self.template, '0.2') - - m.assert_called_with(self.template, 'w') + publish_cli(self.ctx_mock, self.template, '0.2') expected_template_data = { METADATA: { SERVERLESS_REPO_APPLICATION: {SEMANTIC_VERSION: '0.2'} From f452d2e0fb415c879ee3694c0c89a9732804f7d0 Mon Sep 17 00:00:00 2001 From: Vicky Wang Date: Tue, 5 Mar 2019 21:18:36 -0800 Subject: [PATCH 5/5] Use override in help text --- samcli/commands/publish/command.py | 4 ++-- tests/integration/publish/test_command_integ.py | 2 +- tests/unit/commands/publish/test_command.py | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/samcli/commands/publish/command.py b/samcli/commands/publish/command.py index 7355dac68f..9587f0c863 100644 --- a/samcli/commands/publish/command.py +++ b/samcli/commands/publish/command.py @@ -36,7 +36,7 @@ """.format(SAM_PUBLISH_DOC) SHORT_HELP = "Publish a packaged AWS SAM template to the AWS Serverless Application Repository." SERVERLESSREPO_CONSOLE_URL = "https://console.aws.amazon.com/serverlessrepo/home?region={}#/published-applications/{}" -SEMANTIC_VERSION_HELP = "Optional. The value provided here overwrites SemanticVersion in the template metadata." +SEMANTIC_VERSION_HELP = "Optional. The value provided here overrides SemanticVersion in the template metadata." SEMANTIC_VERSION = 'SemanticVersion' @@ -60,7 +60,7 @@ def do_cli(ctx, template, semantic_version): click.secho("Publish Failed", fg='red') raise UserException(str(ex)) - # Overwrite SemanticVersion in template metadata when provided in command input + # Override SemanticVersion in template metadata when provided in command input if semantic_version and SERVERLESS_REPO_APPLICATION in template_data.get(METADATA, {}): template_data.get(METADATA).get(SERVERLESS_REPO_APPLICATION)[SEMANTIC_VERSION] = semantic_version diff --git a/tests/integration/publish/test_command_integ.py b/tests/integration/publish/test_command_integ.py index ea0e712c80..b724d2c8bc 100644 --- a/tests/integration/publish/test_command_integ.py +++ b/tests/integration/publish/test_command_integ.py @@ -60,7 +60,7 @@ def test_create_application_version(self): app_metadata = json.loads(app_metadata_text) self.assert_metadata_details(app_metadata, process_stdout.decode('utf-8')) - def test_create_application_version_overwrite_template_semantic_version(self): + def test_create_application_version_with_semantic_version_option(self): template_path = self.temp_dir.joinpath("template_create_app_version.yaml") command_list = self.get_command_list( template_path=template_path, region=self.region_name, semantic_version='0.1.0') diff --git a/tests/unit/commands/publish/test_command.py b/tests/unit/commands/publish/test_command.py index 787bb5da73..f0f82c1c4d 100644 --- a/tests/unit/commands/publish/test_command.py +++ b/tests/unit/commands/publish/test_command.py @@ -143,8 +143,8 @@ def test_must_use_template_semantic_version(self, publish_application_mock, @patch('samcli.commands.publish.command.get_template_data') @patch('samcli.commands.publish.command.publish_application') - def test_must_overwrite_semantic_version_if_provided(self, publish_application_mock, - get_template_data_mock): + def test_must_override_template_semantic_version(self, publish_application_mock, + get_template_data_mock): template_data = { METADATA: { SERVERLESS_REPO_APPLICATION: {SEMANTIC_VERSION: '0.1'}