From d358573abfedb1f5d585f654ef6302addeca0c7c Mon Sep 17 00:00:00 2001 From: Sam Liu Date: Tue, 6 Jul 2021 13:01:22 -0700 Subject: [PATCH 01/23] Add intro paragraph to bootstrap --- .../commands/pipeline/bootstrap/guided_context.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/samcli/commands/pipeline/bootstrap/guided_context.py b/samcli/commands/pipeline/bootstrap/guided_context.py index 982203285eb..3bbf9e7898e 100644 --- a/samcli/commands/pipeline/bootstrap/guided_context.py +++ b/samcli/commands/pipeline/bootstrap/guided_context.py @@ -2,6 +2,7 @@ An interactive flow that prompt the user for required information to bootstrap the AWS account of an environment with the required infrastructure """ +from textwrap import dedent from typing import Optional import click @@ -39,6 +40,18 @@ def run(self) -> None: for the pipeline to work. Users can provide all, none or some resources' ARNs and leave the remaining empty and it will be created by the bootstrap command """ + click.secho( + dedent( + """\ + SAM Pipeline Bootstrap generates the necessary AWS resources to connect your + CI/CD pipeline tool. We will ask for [1] account details, [2] stage definition, + and [3] references to existing resources in order to bootstrap these pipeline + resources. You can also add optional security parameters. + """ + ), + fg="cyan", + ) + account_id = get_current_account_id() if not self.environment_name: self.environment_name = click.prompt( From 91a459f6e612dde6fbb13cf628b7a74beeed46dc Mon Sep 17 00:00:00 2001 From: Sam Liu Date: Tue, 6 Jul 2021 13:04:05 -0700 Subject: [PATCH 02/23] Add switch account prompt --- samcli/commands/pipeline/bootstrap/guided_context.py | 7 +++++++ samcli/commands/pipeline/external_links.py | 1 + 2 files changed, 8 insertions(+) create mode 100644 samcli/commands/pipeline/external_links.py diff --git a/samcli/commands/pipeline/bootstrap/guided_context.py b/samcli/commands/pipeline/bootstrap/guided_context.py index 3bbf9e7898e..25e1d616fd0 100644 --- a/samcli/commands/pipeline/bootstrap/guided_context.py +++ b/samcli/commands/pipeline/bootstrap/guided_context.py @@ -6,6 +6,8 @@ from typing import Optional import click + +from samcli.commands.pipeline.external_links import CONFIG_AWS_CRED_DOC_URL from samcli.lib.bootstrap.bootstrap import get_current_account_id from samcli.lib.utils.defaults import get_default_aws_region @@ -53,6 +55,11 @@ def run(self) -> None: ) account_id = get_current_account_id() + click.secho("[1] Account details", bold=True) + if click.confirm(f"You are bootstrapping resources in Account {account_id}. Do you want to switch accounts?"): + click.echo(f"Please refer to this page about configuring credentials: {CONFIG_AWS_CRED_DOC_URL}.") + exit(0) + if not self.environment_name: self.environment_name = click.prompt( f"Environment name (a descriptive name for the environment which will be deployed" diff --git a/samcli/commands/pipeline/external_links.py b/samcli/commands/pipeline/external_links.py new file mode 100644 index 00000000000..6182e61382c --- /dev/null +++ b/samcli/commands/pipeline/external_links.py @@ -0,0 +1 @@ +CONFIG_AWS_CRED_DOC_URL = "https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-files.html" From d48c71279c2b1a96f7a22ce53470940a137ba122 Mon Sep 17 00:00:00 2001 From: Sam Liu Date: Tue, 6 Jul 2021 13:07:39 -0700 Subject: [PATCH 03/23] Revamp stage definition prompt --- .../commands/pipeline/bootstrap/guided_context.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/samcli/commands/pipeline/bootstrap/guided_context.py b/samcli/commands/pipeline/bootstrap/guided_context.py index 25e1d616fd0..d995a6734e1 100644 --- a/samcli/commands/pipeline/bootstrap/guided_context.py +++ b/samcli/commands/pipeline/bootstrap/guided_context.py @@ -60,16 +60,22 @@ def run(self) -> None: click.echo(f"Please refer to this page about configuring credentials: {CONFIG_AWS_CRED_DOC_URL}.") exit(0) - if not self.environment_name: + click.secho("[2] Stage definition", bold=True) + if self.environment_name: + click.echo(f"Stage name: {self.environment_name}") + else: + click.echo( + "Enter a name for the stage you want to bootstrap. This will be referenced later " + "when generating a Pipeline Config File with Pipeline Init." + ) self.environment_name = click.prompt( - f"Environment name (a descriptive name for the environment which will be deployed" - f" to AWS account {account_id})", + "Stage name", type=click.STRING, ) if not self.region: self.region = click.prompt( - "\nAWS region (the AWS region where the environment infrastructure resources will be deployed to)", + "Enter the region you want these resources to create", type=click.STRING, default=get_default_aws_region(), ) From 9fb33e4a1016788c0be5998575be8dd3bc8b6cc0 Mon Sep 17 00:00:00 2001 From: Sam Liu Date: Tue, 6 Jul 2021 13:25:29 -0700 Subject: [PATCH 04/23] Revamp existing resources prompt --- .../pipeline/bootstrap/guided_context.py | 66 ++++++++++--------- 1 file changed, 35 insertions(+), 31 deletions(-) diff --git a/samcli/commands/pipeline/bootstrap/guided_context.py b/samcli/commands/pipeline/bootstrap/guided_context.py index d995a6734e1..c0e0d058dfd 100644 --- a/samcli/commands/pipeline/bootstrap/guided_context.py +++ b/samcli/commands/pipeline/bootstrap/guided_context.py @@ -80,55 +80,59 @@ def run(self) -> None: default=get_default_aws_region(), ) - if not self.pipeline_user_arn: - click.echo( - "\nThere must be exactly one pipeline user across all of the environments. " - "If you have ran this command before to bootstrap a previous environment, please " - "provide the ARN of the created pipeline user, otherwise, we will create a new user for you. " - "Please make sure to store the credentials safely with the CI/CD system." - ) + click.secho("[3] Reference existing resources", bold=True) + if self.pipeline_user_arn: + click.echo(f"Pipeline IAM User ARN: {self.pipeline_user_arn}") + else: self.pipeline_user_arn = click.prompt( - "Pipeline user [leave blank to create one]", default="", type=click.STRING + "Enter the Pipeline IAM User ARN if you have previously created one, or we will create one for you", + default="", + type=click.STRING, ) - if not self.pipeline_execution_role_arn: + if self.pipeline_execution_role_arn: + click.echo(f"Pipeline execution role ARN: {self.pipeline_execution_role_arn}") + else: self.pipeline_execution_role_arn = click.prompt( - "\nPipeline execution role (an IAM role assumed by the pipeline user to operate on this environment) " - "[leave blank to create one]", + "Enter the Pipeline execution role ARN if you have previously created one, " + "or we will create one for you", default="", type=click.STRING, ) - if not self.cloudformation_execution_role_arn: + if self.cloudformation_execution_role_arn: + click.echo(f"CloudFormation execution role ARN: {self.cloudformation_execution_role_arn}") + else: self.cloudformation_execution_role_arn = click.prompt( - "\nCloudFormation execution role (an IAM role assumed by CloudFormation to deploy " - "the application's stack) [leave blank to create one]", + "Enter the CloudFormation execution role ARN if you have previously created one, " + "or we will create one for you", default="", type=click.STRING, ) - if not self.artifacts_bucket_arn: + if self.artifacts_bucket_arn: + click.echo(f"Artifacts bucket ARN: {self.cloudformation_execution_role_arn}") + else: self.artifacts_bucket_arn = click.prompt( - "\nArtifacts bucket (S3 bucket to hold the AWS SAM build artifacts) [leave blank to create one]", + "Please enter the bucket artifact ARN for your Lambda function. " + "If you do not have a bucket, we will create one for you", default="", type=click.STRING, ) - if not self.image_repository_arn: - click.echo( - "\nIf your SAM template includes (or going to include) Lambda functions of Image package type, " - "then an ECR image repository is required. Should we create one?" - ) - click.echo("\t1 - No, My SAM template won't include Lambda functions of Image package type") - click.echo("\t2 - Yes, I need help creating one") - click.echo("\t3 - I already have an ECR image repository") - choice = click.prompt(text="Choice", show_choices=False, type=click.Choice(["1", "2", "3"])) - if choice == "1": - self.create_image_repository = False - elif choice == "2": - self.create_image_repository = True - else: # choice == "3" + + if self.image_repository_arn: + click.echo(f"ECR image repository ARN: {self.image_repository_arn}") + else: + if click.confirm("Does your application contain any IMAGE type Lambda functions?"): + self.image_repository_arn = click.prompt( + "Please enter the ECR image repository ARN(s) for your IMAGE type function(s)." + "If you do not yet have a repostiory, we will create one for you", + default="", + type=click.STRING, + ) + self.create_image_repository = not bool(self.image_repository_arn) + else: self.create_image_repository = False - self.image_repository_arn = click.prompt("ECR image repository", type=click.STRING) if not self.pipeline_ip_range: click.echo("\nWe can deny requests not coming from a recognized IP address range.") From 33aa065347da714ce71194afe1d7f9430fa275c5 Mon Sep 17 00:00:00 2001 From: Sam Liu Date: Tue, 6 Jul 2021 13:36:27 -0700 Subject: [PATCH 05/23] Revamp security prompt --- samcli/commands/pipeline/bootstrap/guided_context.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/samcli/commands/pipeline/bootstrap/guided_context.py b/samcli/commands/pipeline/bootstrap/guided_context.py index c0e0d058dfd..6586717cecd 100644 --- a/samcli/commands/pipeline/bootstrap/guided_context.py +++ b/samcli/commands/pipeline/bootstrap/guided_context.py @@ -134,10 +134,13 @@ def run(self) -> None: else: self.create_image_repository = False - if not self.pipeline_ip_range: - click.echo("\nWe can deny requests not coming from a recognized IP address range.") + click.secho("[4] Security definition - OPTIONAL", bold=True) + if self.pipeline_ip_range: + click.echo(f"Pipeline IP address range: {self.pipeline_ip_range}") + else: self.pipeline_ip_range = click.prompt( - "Pipeline IP address range (using CIDR notation) [leave blank if you don't know]", + "For added security, you can define the permitted Pipeline IP range. " + "Enter the IP addresses to restrict access to", default="", type=click.STRING, ) From 999c23a27df4d820b31d60c0a44bd40cfeb6525c Mon Sep 17 00:00:00 2001 From: Sam Liu Date: Tue, 6 Jul 2021 14:36:32 -0700 Subject: [PATCH 06/23] Allow answers to be changed later --- .../pipeline/bootstrap/guided_context.py | 188 +++++++++++++----- 1 file changed, 136 insertions(+), 52 deletions(-) diff --git a/samcli/commands/pipeline/bootstrap/guided_context.py b/samcli/commands/pipeline/bootstrap/guided_context.py index 6586717cecd..368118c48c4 100644 --- a/samcli/commands/pipeline/bootstrap/guided_context.py +++ b/samcli/commands/pipeline/bootstrap/guided_context.py @@ -3,7 +3,7 @@ with the required infrastructure """ from textwrap import dedent -from typing import Optional +from typing import Optional, List, Tuple, Callable import click @@ -36,6 +36,117 @@ def __init__( self.pipeline_ip_range = pipeline_ip_range self.region = region + def _prompt_stage_name(self) -> None: + click.echo( + "Enter a name for the stage you want to bootstrap. This will be referenced later " + "when generating a Pipeline Config File with Pipeline Init." + ) + self.environment_name = click.prompt( + "Stage name", + default=self.environment_name, + type=click.STRING, + ) + + def _prompt_region_name(self) -> None: + self.region = click.prompt( + "Enter the region you want these resources to create", + type=click.STRING, + default=get_default_aws_region(), + ) + + def _prompt_pipeline_user(self) -> None: + self.pipeline_user_arn = click.prompt( + "Enter the Pipeline IAM User ARN if you have previously created one, or we will create one for you", + default="", + type=click.STRING, + ) + + def _prompt_pipeline_execution_role(self) -> None: + self.pipeline_execution_role_arn = click.prompt( + "Enter the Pipeline execution role ARN if you have previously created one, " + "or we will create one for you", + default="", + type=click.STRING, + ) + + def _prompt_cloudformation_execution_role(self) -> None: + self.cloudformation_execution_role_arn = click.prompt( + "Enter the CloudFormation execution role ARN if you have previously created one, " + "or we will create one for you", + default="", + type=click.STRING, + ) + + def _prompt_artifacts_bucket(self) -> None: + self.artifacts_bucket_arn = click.prompt( + "Please enter the bucket artifact ARN for your Lambda function. " + "If you do not have a bucket, we will create one for you", + default="", + type=click.STRING, + ) + + def _prompt_image_repository(self) -> None: + if click.confirm("Does your application contain any IMAGE type Lambda functions?"): + self.image_repository_arn = click.prompt( + "Please enter the ECR image repository ARN(s) for your IMAGE type function(s)." + "If you do not yet have a repostiory, we will create one for you", + default="", + type=click.STRING, + ) + self.create_image_repository = not bool(self.image_repository_arn) + else: + self.create_image_repository = False + + def _prompt_ip_range(self) -> None: + self.pipeline_ip_range = click.prompt( + "For added security, you can define the permitted Pipeline IP range. " + "Enter the IP addresses to restrict access to", + default="", + type=click.STRING, + ) + + def _get_user_inputs(self) -> List[Tuple[str, Callable[[], None]]]: + return [ + (f"Stage name: {self.environment_name}", self._prompt_stage_name), + (f"Region: {self.region}", self._prompt_region_name), + ( + f"Pipeline user ARN: {self.pipeline_user_arn}" + if self.pipeline_user_arn + else "Pipeline user: to be created", + self._prompt_pipeline_user, + ), + ( + f"Pipeline execution role ARN: {self.pipeline_execution_role_arn}" + if self.pipeline_execution_role_arn + else "Pipeline execution role: to be created", + self._prompt_pipeline_execution_role, + ), + ( + f"CloudFormation execution role ARN: {self.cloudformation_execution_role_arn}" + if self.cloudformation_execution_role_arn + else "CloudFormation execution role: to be created", + self._prompt_cloudformation_execution_role, + ), + ( + f"Artifacts bucket ARN: {self.artifacts_bucket_arn}" + if self.artifacts_bucket_arn + else "Artifacts bucket: to be created", + self._prompt_artifacts_bucket, + ), + ( + f"ECR image repository ARN: {self.image_repository_arn}" + if self.image_repository_arn + else f"ECR image repository: {'to be created' if self.create_image_repository else 'skipped'}", + self._prompt_image_repository, + ), + ( + f"Pipeline IP address range: {self.pipeline_ip_range}" + if self.pipeline_ip_range + else "Pipeline IP address range: none", + self._prompt_ip_range, + ), + ] + def run(self) -> None: """ Runs an interactive questionnaire to prompt the user for the ARNs of the AWS resources(infrastructure) required @@ -64,83 +175,56 @@ def run(self) -> None: if self.environment_name: click.echo(f"Stage name: {self.environment_name}") else: - click.echo( - "Enter a name for the stage you want to bootstrap. This will be referenced later " - "when generating a Pipeline Config File with Pipeline Init." - ) - self.environment_name = click.prompt( - "Stage name", - type=click.STRING, - ) + self._prompt_stage_name() if not self.region: - self.region = click.prompt( - "Enter the region you want these resources to create", - type=click.STRING, - default=get_default_aws_region(), - ) + self._prompt_region_name() click.secho("[3] Reference existing resources", bold=True) if self.pipeline_user_arn: click.echo(f"Pipeline IAM User ARN: {self.pipeline_user_arn}") else: - self.pipeline_user_arn = click.prompt( - "Enter the Pipeline IAM User ARN if you have previously created one, or we will create one for you", - default="", - type=click.STRING, - ) + self._prompt_pipeline_user() if self.pipeline_execution_role_arn: click.echo(f"Pipeline execution role ARN: {self.pipeline_execution_role_arn}") else: - self.pipeline_execution_role_arn = click.prompt( - "Enter the Pipeline execution role ARN if you have previously created one, " - "or we will create one for you", - default="", - type=click.STRING, - ) + self._prompt_pipeline_execution_role() if self.cloudformation_execution_role_arn: click.echo(f"CloudFormation execution role ARN: {self.cloudformation_execution_role_arn}") else: - self.cloudformation_execution_role_arn = click.prompt( - "Enter the CloudFormation execution role ARN if you have previously created one, " - "or we will create one for you", - default="", - type=click.STRING, - ) + self._prompt_cloudformation_execution_role() if self.artifacts_bucket_arn: click.echo(f"Artifacts bucket ARN: {self.cloudformation_execution_role_arn}") else: - self.artifacts_bucket_arn = click.prompt( - "Please enter the bucket artifact ARN for your Lambda function. " - "If you do not have a bucket, we will create one for you", - default="", - type=click.STRING, - ) + self._prompt_artifacts_bucket() if self.image_repository_arn: click.echo(f"ECR image repository ARN: {self.image_repository_arn}") else: - if click.confirm("Does your application contain any IMAGE type Lambda functions?"): - self.image_repository_arn = click.prompt( - "Please enter the ECR image repository ARN(s) for your IMAGE type function(s)." - "If you do not yet have a repostiory, we will create one for you", - default="", - type=click.STRING, - ) - self.create_image_repository = not bool(self.image_repository_arn) - else: - self.create_image_repository = False + self._prompt_image_repository() click.secho("[4] Security definition - OPTIONAL", bold=True) if self.pipeline_ip_range: click.echo(f"Pipeline IP address range: {self.pipeline_ip_range}") else: - self.pipeline_ip_range = click.prompt( - "For added security, you can define the permitted Pipeline IP range. " - "Enter the IP addresses to restrict access to", - default="", - type=click.STRING, + self._prompt_ip_range() + + # Ask customers to confirm the inputs + while True: + inputs = self._get_user_inputs() + for i, (text, _) in enumerate(inputs): + click.secho(f"{i + 1}. {text}", fg="cyan") + edit_input = click.prompt( + text="Press enter to confirm the values above, or select an item to edit the value", + default="0", + show_choices=False, + show_default=False, + type=click.Choice(["0"] + [str(i + 1) for i in range(len(inputs))]), ) + if int(edit_input): + inputs[int(edit_input) - 1][1]() + else: + break From fc001a69f31020349bdf5c8b399b54bebfe805c7 Mon Sep 17 00:00:00 2001 From: Sam Liu Date: Tue, 6 Jul 2021 16:18:11 -0700 Subject: [PATCH 07/23] Add exit message for bootstrap --- samcli/commands/pipeline/bootstrap/cli.py | 23 +++++++++++++++----- samcli/lib/pipeline/bootstrap/environment.py | 7 +----- samcli/lib/utils/colors.py | 4 ++++ 3 files changed, 23 insertions(+), 11 deletions(-) diff --git a/samcli/commands/pipeline/bootstrap/cli.py b/samcli/commands/pipeline/bootstrap/cli.py index aaeef3cfda8..8b2738974b5 100644 --- a/samcli/commands/pipeline/bootstrap/cli.py +++ b/samcli/commands/pipeline/bootstrap/cli.py @@ -2,6 +2,7 @@ CLI command for "pipeline bootstrap", which sets up the require pipeline infrastructure resources """ import os +from textwrap import dedent from typing import Any, Dict, List, Optional import click @@ -206,14 +207,26 @@ def do_cli( ) click.secho( - Colored().green( - "\nThe ARNs of created resources have been written to " - f"{os.path.join(PIPELINE_CONFIG_DIR, PIPELINE_CONFIG_FILENAME)}.\n" - f"It will be used next time you run `sam pipeline bootstrap` or " - f"`sam pipeline init` in this directory." + dedent( + f"""\ + View the definition in {os.path.join(PIPELINE_CONFIG_DIR, PIPELINE_CONFIG_FILENAME)}, + run {Colored().bold("sam pipeline bootstrap")} to generate another set of resources, or proceed to + {Colored().bold("sam pipeline init")} to create your Pipeline Config file. + """ ) ) + if not environment.pipeline_user.is_user_provided: + click.secho( + dedent( + """\ + Before running sam pipeline init, we recommend first setting up AWS credentials + in your CI/CD account. Read more about how to do so with your provider in + [DOCS-LINK]. + """ + ) + ) + def _load_saved_pipeline_user_arn() -> Optional[str]: samconfig: SamConfig = SamConfig(config_dir=PIPELINE_CONFIG_DIR, filename=PIPELINE_CONFIG_FILENAME) diff --git a/samcli/lib/pipeline/bootstrap/environment.py b/samcli/lib/pipeline/bootstrap/environment.py index afad534c65f..191e32a3478 100644 --- a/samcli/lib/pipeline/bootstrap/environment.py +++ b/samcli/lib/pipeline/bootstrap/environment.py @@ -323,12 +323,7 @@ def print_resources_summary(self) -> None: click.secho(self.color.green(f"\t{resource.arn}")) if not self.pipeline_user.is_user_provided: - click.secho( - self.color.green( - "Please configure your CI/CD project with the following pipeline user credentials and " - "make sure to periodically rotate it:", - ) - ) + click.secho(self.color.green(f"Pipeline IAM user credential:")) click.secho(self.color.green(f"\tAWS_ACCESS_KEY_ID: {self.pipeline_user.access_key_id}")) click.secho(self.color.green(f"\tAWS_SECRET_ACCESS_KEY: {self.pipeline_user.secret_access_key}")) diff --git a/samcli/lib/utils/colors.py b/samcli/lib/utils/colors.py index 84e3cbdbd7e..84767f0fec6 100644 --- a/samcli/lib/utils/colors.py +++ b/samcli/lib/utils/colors.py @@ -58,6 +58,10 @@ def underline(self, msg): """Underline the input""" return click.style(msg, underline=True) if self.colorize else msg + def bold(self, msg): + """Bold the input""" + return click.style(msg, bold=True) if self.colorize else msg + def _color(self, msg, color): """Internal helper method to add colors to input""" kwargs = {"fg": color} From dcbc48b25aba0047010fb6686a45740c9596e0ce Mon Sep 17 00:00:00 2001 From: Sam Liu Date: Tue, 6 Jul 2021 16:34:30 -0700 Subject: [PATCH 08/23] Add exit message for bootstrap (1) --- samcli/lib/pipeline/bootstrap/environment.py | 60 ++++++++++---------- samcli/lib/pipeline/bootstrap/resource.py | 21 ++++--- 2 files changed, 43 insertions(+), 38 deletions(-) diff --git a/samcli/lib/pipeline/bootstrap/environment.py b/samcli/lib/pipeline/bootstrap/environment.py index 191e32a3478..b6887360788 100644 --- a/samcli/lib/pipeline/bootstrap/environment.py +++ b/samcli/lib/pipeline/bootstrap/environment.py @@ -3,6 +3,7 @@ import os import pathlib import re +from itertools import chain from typing import Dict, List, Optional, Tuple import boto3 @@ -91,13 +92,19 @@ def __init__( self.name: str = name self.aws_profile: Optional[str] = aws_profile self.aws_region: Optional[str] = aws_region - self.pipeline_user: IAMUser = IAMUser(arn=pipeline_user_arn) - self.pipeline_execution_role: Resource = Resource(arn=pipeline_execution_role_arn) + self.pipeline_user: IAMUser = IAMUser(arn=pipeline_user_arn, comment="Pipeline IAM user") + self.pipeline_execution_role: Resource = Resource( + arn=pipeline_execution_role_arn, comment="Pipeline execution role" + ) self.pipeline_ip_range: Optional[str] = pipeline_ip_range - self.cloudformation_execution_role: Resource = Resource(arn=cloudformation_execution_role_arn) - self.artifacts_bucket: Resource = Resource(arn=artifacts_bucket_arn) + self.cloudformation_execution_role: Resource = Resource( + arn=cloudformation_execution_role_arn, comment="CloudFormation execution role" + ) + self.artifacts_bucket: Resource = Resource(arn=artifacts_bucket_arn, comment="Artifact bucket") self.create_image_repository: bool = create_image_repository - self.image_repository: ECRImageRepository = ECRImageRepository(arn=image_repository_arn) + self.image_repository: ECRImageRepository = ECRImageRepository( + arn=image_repository_arn, comment="ECR image repository" + ) self.color = Colored() def did_user_provide_all_required_resources(self) -> bool: @@ -105,18 +112,20 @@ def did_user_provide_all_required_resources(self) -> bool: return all(resource.is_user_provided for resource in self._get_resources()) def _get_non_user_provided_resources_msg(self) -> str: - missing_resources_msg = "" - if not self.pipeline_user.is_user_provided: - missing_resources_msg += "\n\tPipeline user" - if not self.pipeline_execution_role.is_user_provided: - missing_resources_msg += "\n\tPipeline execution role." - if not self.cloudformation_execution_role.is_user_provided: - missing_resources_msg += "\n\tCloudFormation execution role." - if not self.artifacts_bucket.is_user_provided: - missing_resources_msg += "\n\tArtifacts bucket." - if self.create_image_repository and not self.image_repository.is_user_provided: - missing_resources_msg += "\n\tECR image repository." - return missing_resources_msg + resource_comments = chain.from_iterable( + [ + [] if self.pipeline_user.is_user_provided else [self.pipeline_user.comment], + [] if self.pipeline_execution_role.is_user_provided else [self.pipeline_execution_role.comment], + [] + if self.cloudformation_execution_role.is_user_provided + else [self.cloudformation_execution_role.comment], + [] if self.artifacts_bucket.is_user_provided else [self.artifacts_bucket.comment], + [] + if self.image_repository.is_user_provided or not self.create_image_repository + else [self.image_repository.comment], + ] + ) + return "\n".join([f" - {comment}" for comment in resource_comments]) def bootstrap(self, confirm_changeset: bool = True) -> bool: """ @@ -149,7 +158,7 @@ def bootstrap(self, confirm_changeset: bool = True) -> bool: missing_resources_msg: str = self._get_non_user_provided_resources_msg() click.echo( - f"This will create the following required resources for the '{self.name}' environment: " + f"This will create the following required resources for the '{self.name}' environment: \n" f"{missing_resources_msg}" ) if confirm_changeset: @@ -307,20 +316,9 @@ def print_resources_summary(self) -> None: created_resources.append(resource) if created_resources: - click.secho(self.color.green("\nWe have created the following resources:")) + click.secho(self.color.green("The following resources were created in your account:")) for resource in created_resources: - click.secho(f"\t{resource.arn}", fg="green") - - if provided_resources: - click.secho( - self.color.green( - "\nYou provided the following resources. Please make sure it has the required permissions " - "as shown at https://github.com/aws/aws-sam-cli/blob/develop/" - "samcli/lib/pipeline/bootstrap/environment_resources.yaml", - ) - ) - for resource in provided_resources: - click.secho(self.color.green(f"\t{resource.arn}")) + click.secho(self.color.green(f" - {resource.comment}")) if not self.pipeline_user.is_user_provided: click.secho(self.color.green(f"Pipeline IAM user credential:")) diff --git a/samcli/lib/pipeline/bootstrap/resource.py b/samcli/lib/pipeline/bootstrap/resource.py index a2bd3cb2845..a7b39dd9657 100644 --- a/samcli/lib/pipeline/bootstrap/resource.py +++ b/samcli/lib/pipeline/bootstrap/resource.py @@ -44,6 +44,8 @@ class Resource: ---------- arn: str the ARN of the resource + comment: str + the comment of the resource is_user_provided: bool True if the user provided the ARN of the resource during the initialization. It indicates whether this pipeline- resource is provided by the user or created by SAM during `sam pipeline bootstrap` @@ -54,8 +56,9 @@ class Resource: extracts and returns the resource name from its ARN """ - def __init__(self, arn: Optional[str]) -> None: + def __init__(self, arn: Optional[str], comment: Optional[str]) -> None: self.arn: Optional[str] = arn + self.comment: Optional[str] = comment self.is_user_provided: bool = bool(arn) def name(self) -> Optional[str]: @@ -83,11 +86,15 @@ class IAMUser(Resource): """ def __init__( - self, arn: Optional[str], access_key_id: Optional[str] = None, secret_access_key: Optional[str] = None + self, + arn: Optional[str], + comment: Optional[str], + access_key_id: Optional[str] = None, + secret_access_key: Optional[str] = None, ) -> None: self.access_key_id: Optional[str] = access_key_id self.secret_access_key: Optional[str] = secret_access_key - super().__init__(arn=arn) + super().__init__(arn=arn, comment=comment) class S3Bucket(Resource): @@ -99,16 +106,16 @@ class S3Bucket(Resource): The ARN of the KMS key used in encrypting this S3Bucket, if any. """ - def __init__(self, arn: Optional[str], kms_key_arn: Optional[str] = None) -> None: + def __init__(self, arn: Optional[str], comment: Optional[str], kms_key_arn: Optional[str] = None) -> None: self.kms_key_arn: Optional[str] = kms_key_arn - super().__init__(arn=arn) + super().__init__(arn=arn, comment=comment) class ECRImageRepository(Resource): """ Represents an AWS ECR image repository resource """ - def __init__(self, arn: Optional[str]) -> None: - super().__init__(arn=arn) + def __init__(self, arn: Optional[str], comment: Optional[str]) -> None: + super().__init__(arn=arn, comment=comment) def get_uri(self) -> Optional[str]: """ From 061300875f6144f6bc82fef202c7f246238c3f22 Mon Sep 17 00:00:00 2001 From: Sam Liu Date: Tue, 6 Jul 2021 16:34:58 -0700 Subject: [PATCH 09/23] Add indentation to review values --- samcli/commands/pipeline/bootstrap/guided_context.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samcli/commands/pipeline/bootstrap/guided_context.py b/samcli/commands/pipeline/bootstrap/guided_context.py index 368118c48c4..22061105732 100644 --- a/samcli/commands/pipeline/bootstrap/guided_context.py +++ b/samcli/commands/pipeline/bootstrap/guided_context.py @@ -216,7 +216,7 @@ def run(self) -> None: while True: inputs = self._get_user_inputs() for i, (text, _) in enumerate(inputs): - click.secho(f"{i + 1}. {text}", fg="cyan") + click.secho(f" {i + 1}. {text}", fg="cyan") edit_input = click.prompt( text="Press enter to confirm the values above, or select an item to edit the value", default="0", From 5cbc4cc6e94cbf15d94088c837cc1487d81c5eba Mon Sep 17 00:00:00 2001 From: Sam Liu Date: Tue, 6 Jul 2021 16:37:31 -0700 Subject: [PATCH 10/23] Add "Below is the summary of the answers:" --- samcli/commands/pipeline/bootstrap/guided_context.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/samcli/commands/pipeline/bootstrap/guided_context.py b/samcli/commands/pipeline/bootstrap/guided_context.py index 22061105732..c90fcfa4527 100644 --- a/samcli/commands/pipeline/bootstrap/guided_context.py +++ b/samcli/commands/pipeline/bootstrap/guided_context.py @@ -9,6 +9,7 @@ from samcli.commands.pipeline.external_links import CONFIG_AWS_CRED_DOC_URL from samcli.lib.bootstrap.bootstrap import get_current_account_id +from samcli.lib.utils.colors import Colored from samcli.lib.utils.defaults import get_default_aws_region @@ -35,6 +36,7 @@ def __init__( self.image_repository_arn = image_repository_arn self.pipeline_ip_range = pipeline_ip_range self.region = region + self.color = Colored() def _prompt_stage_name(self) -> None: click.echo( @@ -215,8 +217,9 @@ def run(self) -> None: # Ask customers to confirm the inputs while True: inputs = self._get_user_inputs() + click.secho(self.color.cyan("Below is the summary of the answers:")) for i, (text, _) in enumerate(inputs): - click.secho(f" {i + 1}. {text}", fg="cyan") + click.secho(self.color.cyan(f" {i + 1}. {text}")) edit_input = click.prompt( text="Press enter to confirm the values above, or select an item to edit the value", default="0", From f5e0bb447d718f758a26a6c4a09059a83d14937a Mon Sep 17 00:00:00 2001 From: Sam Liu Date: Wed, 7 Jul 2021 09:49:38 -0700 Subject: [PATCH 11/23] Sweep pylint errors --- samcli/commands/pipeline/bootstrap/cli.py | 2 +- samcli/commands/pipeline/bootstrap/guided_context.py | 9 +++++---- samcli/commands/pipeline/external_links.py | 4 ++++ samcli/lib/pipeline/bootstrap/environment.py | 2 +- 4 files changed, 11 insertions(+), 6 deletions(-) diff --git a/samcli/commands/pipeline/bootstrap/cli.py b/samcli/commands/pipeline/bootstrap/cli.py index 8b2738974b5..fd5e6019cf4 100644 --- a/samcli/commands/pipeline/bootstrap/cli.py +++ b/samcli/commands/pipeline/bootstrap/cli.py @@ -220,7 +220,7 @@ def do_cli( click.secho( dedent( """\ - Before running sam pipeline init, we recommend first setting up AWS credentials + Before running sam pipeline init, we recommend first setting up AWS credentials in your CI/CD account. Read more about how to do so with your provider in [DOCS-LINK]. """ diff --git a/samcli/commands/pipeline/bootstrap/guided_context.py b/samcli/commands/pipeline/bootstrap/guided_context.py index c90fcfa4527..a3f800bc546 100644 --- a/samcli/commands/pipeline/bootstrap/guided_context.py +++ b/samcli/commands/pipeline/bootstrap/guided_context.py @@ -2,6 +2,7 @@ An interactive flow that prompt the user for required information to bootstrap the AWS account of an environment with the required infrastructure """ +import sys from textwrap import dedent from typing import Optional, List, Tuple, Callable @@ -149,7 +150,7 @@ def _get_user_inputs(self) -> List[Tuple[str, Callable[[], None]]]: ), ] - def run(self) -> None: + def run(self) -> None: # pylint: disable=too-many-branches """ Runs an interactive questionnaire to prompt the user for the ARNs of the AWS resources(infrastructure) required for the pipeline to work. Users can provide all, none or some resources' ARNs and leave the remaining empty @@ -159,8 +160,8 @@ def run(self) -> None: dedent( """\ SAM Pipeline Bootstrap generates the necessary AWS resources to connect your - CI/CD pipeline tool. We will ask for [1] account details, [2] stage definition, - and [3] references to existing resources in order to bootstrap these pipeline + CI/CD pipeline tool. We will ask for [1] account details, [2] stage definition, + and [3] references to existing resources in order to bootstrap these pipeline resources. You can also add optional security parameters. """ ), @@ -171,7 +172,7 @@ def run(self) -> None: click.secho("[1] Account details", bold=True) if click.confirm(f"You are bootstrapping resources in Account {account_id}. Do you want to switch accounts?"): click.echo(f"Please refer to this page about configuring credentials: {CONFIG_AWS_CRED_DOC_URL}.") - exit(0) + sys.exit(0) click.secho("[2] Stage definition", bold=True) if self.environment_name: diff --git a/samcli/commands/pipeline/external_links.py b/samcli/commands/pipeline/external_links.py index 6182e61382c..cb70320c00b 100644 --- a/samcli/commands/pipeline/external_links.py +++ b/samcli/commands/pipeline/external_links.py @@ -1 +1,5 @@ +""" +The module to store external links. Put them in a centralized place so that we can verify their +validity automatically. +""" CONFIG_AWS_CRED_DOC_URL = "https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-files.html" diff --git a/samcli/lib/pipeline/bootstrap/environment.py b/samcli/lib/pipeline/bootstrap/environment.py index b6887360788..260842e7c99 100644 --- a/samcli/lib/pipeline/bootstrap/environment.py +++ b/samcli/lib/pipeline/bootstrap/environment.py @@ -321,7 +321,7 @@ def print_resources_summary(self) -> None: click.secho(self.color.green(f" - {resource.comment}")) if not self.pipeline_user.is_user_provided: - click.secho(self.color.green(f"Pipeline IAM user credential:")) + click.secho(self.color.green("Pipeline IAM user credential:")) click.secho(self.color.green(f"\tAWS_ACCESS_KEY_ID: {self.pipeline_user.access_key_id}")) click.secho(self.color.green(f"\tAWS_SECRET_ACCESS_KEY: {self.pipeline_user.secret_access_key}")) From 129a0a532b32ba33e987c5226d68354057fc546c Mon Sep 17 00:00:00 2001 From: Sam Liu Date: Wed, 7 Jul 2021 10:45:48 -0700 Subject: [PATCH 12/23] Update unit tests --- .../pipeline/bootstrap/guided_context.py | 2 +- .../pipeline/bootstrap/test_guided_context.py | 36 ++++++++++++------- .../pipeline/bootstrap/test_environment.py | 3 -- .../lib/pipeline/bootstrap/test_resource.py | 24 ++++++++----- 4 files changed, 40 insertions(+), 25 deletions(-) diff --git a/samcli/commands/pipeline/bootstrap/guided_context.py b/samcli/commands/pipeline/bootstrap/guided_context.py index a3f800bc546..7b490931991 100644 --- a/samcli/commands/pipeline/bootstrap/guided_context.py +++ b/samcli/commands/pipeline/bootstrap/guided_context.py @@ -82,7 +82,7 @@ def _prompt_cloudformation_execution_role(self) -> None: def _prompt_artifacts_bucket(self) -> None: self.artifacts_bucket_arn = click.prompt( - "Please enter the bucket artifact ARN for your Lambda function. " + "Please enter the artifact bucket ARN for your Lambda function. " "If you do not have a bucket, we will create one for you", default="", type=click.STRING, diff --git a/tests/unit/commands/pipeline/bootstrap/test_guided_context.py b/tests/unit/commands/pipeline/bootstrap/test_guided_context.py index 65db1edd96c..2d40e5454c9 100644 --- a/tests/unit/commands/pipeline/bootstrap/test_guided_context.py +++ b/tests/unit/commands/pipeline/bootstrap/test_guided_context.py @@ -1,5 +1,5 @@ from unittest import TestCase -from unittest.mock import patch +from unittest.mock import patch, Mock from samcli.commands.pipeline.bootstrap.guided_context import GuidedContext @@ -19,6 +19,8 @@ class TestGuidedContext(TestCase): @patch("samcli.commands.pipeline.bootstrap.guided_context.click") def test_guided_context_will_not_prompt_for_fields_that_are_already_provided(self, click_mock, account_id_mock): account_id_mock.return_value = "1234567890" + click_mock.confirm.return_value = False + click_mock.prompt = Mock(return_value="0") gc: GuidedContext = GuidedContext( environment_name=ANY_ENVIRONMENT_NAME, pipeline_user_arn=ANY_PIPELINE_USER_ARN, @@ -31,23 +33,26 @@ def test_guided_context_will_not_prompt_for_fields_that_are_already_provided(sel region=ANY_REGION, ) gc.run() - click_mock.prompt.assert_not_called() + # there should only one prompt to ask what values customers want to change + click_mock.prompt.assert_called_once() @patch("samcli.commands.pipeline.bootstrap.guided_context.get_current_account_id") @patch("samcli.commands.pipeline.bootstrap.guided_context.click") def test_guided_context_will_prompt_for_fields_that_are_not_provided(self, click_mock, account_id_mock): account_id_mock.return_value = "1234567890" + click_mock.confirm.return_value = False + click_mock.prompt = Mock(return_value="0") gc: GuidedContext = GuidedContext( image_repository_arn=ANY_IMAGE_REPOSITORY_ARN # Exclude ECR repo, it has its own detailed test below ) gc.run() - self.assertTrue(self.did_prompt_text_like("Environment Name", click_mock.prompt)) - self.assertTrue(self.did_prompt_text_like("Pipeline user", click_mock.prompt)) + self.assertTrue(self.did_prompt_text_like("Stage Name", click_mock.prompt)) + self.assertTrue(self.did_prompt_text_like("Pipeline IAM user", click_mock.prompt)) self.assertTrue(self.did_prompt_text_like("Pipeline execution role", click_mock.prompt)) self.assertTrue(self.did_prompt_text_like("CloudFormation execution role", click_mock.prompt)) - self.assertTrue(self.did_prompt_text_like("Artifacts bucket", click_mock.prompt)) - self.assertTrue(self.did_prompt_text_like("AWS region", click_mock.prompt)) - self.assertTrue(self.did_prompt_text_like("Pipeline IP address range", click_mock.prompt)) + self.assertTrue(self.did_prompt_text_like("Artifact bucket", click_mock.prompt)) + self.assertTrue(self.did_prompt_text_like("region", click_mock.prompt)) + self.assertTrue(self.did_prompt_text_like("Pipeline IP range", click_mock.prompt)) @patch("samcli.commands.pipeline.bootstrap.guided_context.get_current_account_id") @patch("samcli.commands.pipeline.bootstrap.guided_context.click") @@ -70,22 +75,27 @@ def test_guided_context_will_not_prompt_for_not_provided_image_repository_if_no_ self.assertIsNone(gc_without_ecr_info.image_repository_arn) - click_mock.prompt.return_value = "1" # the user chose to not CREATE an ECR Image repository + click_mock.confirm.side_effect = [False, False] # the user chose to not CREATE an ECR Image repository + click_mock.prompt.return_value = "0" gc_without_ecr_info.run() self.assertIsNone(gc_without_ecr_info.image_repository_arn) self.assertFalse(gc_without_ecr_info.create_image_repository) - self.assertFalse(self.did_prompt_text_like("ECR image repository", click_mock.prompt)) + self.assertFalse(self.did_prompt_text_like("Please enter the ECR image repository", click_mock.prompt)) - click_mock.prompt.return_value = "2" # the user chose to CREATE an ECR Image repository + click_mock.confirm.side_effect = [False, True] # the user chose to CREATE an ECR Image repository + click_mock.prompt.side_effect = [None, "0"] gc_without_ecr_info.run() self.assertIsNone(gc_without_ecr_info.image_repository_arn) self.assertTrue(gc_without_ecr_info.create_image_repository) - self.assertFalse(self.did_prompt_text_like("ECR image repository", click_mock.prompt)) + self.assertTrue(self.did_prompt_text_like("Please enter the ECR image repository", click_mock.prompt)) - click_mock.prompt.side_effect = ["3", ANY_IMAGE_REPOSITORY_ARN] # the user already has a repo + click_mock.confirm.side_effect = [False, True] # the user already has a repo + click_mock.prompt.side_effect = [ANY_IMAGE_REPOSITORY_ARN, "0"] gc_without_ecr_info.run() self.assertFalse(gc_without_ecr_info.create_image_repository) - self.assertTrue(self.did_prompt_text_like("ECR image repository", click_mock.prompt)) # we've asked about it + self.assertTrue( + self.did_prompt_text_like("Please enter the ECR image repository", click_mock.prompt) + ) # we've asked about it self.assertEqual(gc_without_ecr_info.image_repository_arn, ANY_IMAGE_REPOSITORY_ARN) @staticmethod diff --git a/tests/unit/lib/pipeline/bootstrap/test_environment.py b/tests/unit/lib/pipeline/bootstrap/test_environment.py index ca19c1ce523..9b1b70bae50 100644 --- a/tests/unit/lib/pipeline/bootstrap/test_environment.py +++ b/tests/unit/lib/pipeline/bootstrap/test_environment.py @@ -340,7 +340,6 @@ def test_print_resources_summary_when_no_resources_provided_by_the_user(self, cl environment: Environment = Environment(name=ANY_ENVIRONMENT_NAME) environment.print_resources_summary() self.assert_summary_has_a_message_like("We have created the following resources", click_mock.secho) - self.assert_summary_does_not_have_a_message_like("You provided the following resources", click_mock.secho) @patch("samcli.lib.pipeline.bootstrap.environment.click") def test_print_resources_summary_when_all_resources_are_provided_by_the_user(self, click_mock): @@ -355,7 +354,6 @@ def test_print_resources_summary_when_all_resources_are_provided_by_the_user(sel ) environment.print_resources_summary() self.assert_summary_does_not_have_a_message_like("We have created the following resources", click_mock.secho) - self.assert_summary_has_a_message_like("You provided the following resources", click_mock.secho) @patch("samcli.lib.pipeline.bootstrap.environment.click") def test_print_resources_summary_when_some_resources_are_provided_by_the_user(self, click_mock): @@ -368,7 +366,6 @@ def test_print_resources_summary_when_some_resources_are_provided_by_the_user(se ) environment.print_resources_summary() self.assert_summary_has_a_message_like("We have created the following resources", click_mock.secho) - self.assert_summary_has_a_message_like("You provided the following resources", click_mock.secho) @patch("samcli.lib.pipeline.bootstrap.environment.click") def test_print_resources_summary_prints_the_credentials_of_the_pipeline_user_iff_not_provided_by_the_user( diff --git a/tests/unit/lib/pipeline/bootstrap/test_resource.py b/tests/unit/lib/pipeline/bootstrap/test_resource.py index 7e3ef507dae..f7dcab50f2f 100644 --- a/tests/unit/lib/pipeline/bootstrap/test_resource.py +++ b/tests/unit/lib/pipeline/bootstrap/test_resource.py @@ -23,18 +23,18 @@ def test_arn_parts_of_invalid_arn(self): class TestResource(TestCase): def test_resource(self): - resource = Resource(arn=VALID_ARN) + resource = Resource(arn=VALID_ARN, comment="") self.assertEqual(resource.arn, VALID_ARN) self.assertTrue(resource.is_user_provided) self.assertEqual(resource.name(), "resource-id") - resource = Resource(arn=INVALID_ARN) + resource = Resource(arn=INVALID_ARN, comment="") self.assertEqual(resource.arn, INVALID_ARN) self.assertTrue(resource.is_user_provided) with self.assertRaises(ValueError): resource.name() - resource = Resource(arn=None) + resource = Resource(arn=None, comment="") self.assertIsNone(resource.arn) self.assertFalse(resource.is_user_provided) self.assertIsNone(resource.name()) @@ -42,13 +42,20 @@ def test_resource(self): class TestIAMUser(TestCase): def test_create_iam_user(self): - user: IAMUser = IAMUser(arn=VALID_ARN) + user: IAMUser = IAMUser(arn=VALID_ARN, comment="user") self.assertEquals(user.arn, VALID_ARN) + self.assertEquals(user.comment, "user") self.assertIsNone(user.access_key_id) self.assertIsNone(user.secret_access_key) - user = IAMUser(arn=INVALID_ARN, access_key_id="any_access_key_id", secret_access_key="any_secret_access_key") + user = IAMUser( + arn=INVALID_ARN, + access_key_id="any_access_key_id", + secret_access_key="any_secret_access_key", + comment="user", + ) self.assertEquals(user.arn, INVALID_ARN) + self.assertEquals(user.comment, "user") self.assertEquals(user.access_key_id, "any_access_key_id") self.assertEquals(user.secret_access_key, "any_secret_access_key") @@ -56,11 +63,12 @@ def test_create_iam_user(self): class TestECRImageRepository(TestCase): def test_get_uri_with_valid_ecr_arn(self): valid_ecr_arn = "arn:partition:service:region:account-id:repository/repository-name" - repo: ECRImageRepository = ECRImageRepository(arn=valid_ecr_arn) + repo: ECRImageRepository = ECRImageRepository(arn=valid_ecr_arn, comment="ecr") self.assertEqual(repo.get_uri(), "account-id.dkr.ecr.region.amazonaws.com/repository-name") + self.assertEquals("ecr", repo.comment) def test_get_uri_with_invalid_ecr_arn(self): - repo = ECRImageRepository(arn=INVALID_ARN) + repo = ECRImageRepository(arn=INVALID_ARN, comment="ecr") with self.assertRaises(ValueError): repo.get_uri() @@ -68,6 +76,6 @@ def test_get_uri_with_valid_aws_arn_that_is_invalid_ecr_arn(self): ecr_arn_missing_repository_prefix = ( "arn:partition:service:region:account-id:repository-name-without-repository/-prefix" ) - repo = ECRImageRepository(arn=ecr_arn_missing_repository_prefix) + repo = ECRImageRepository(arn=ecr_arn_missing_repository_prefix, comment="ecr") with self.assertRaises(ValueError): repo.get_uri() From c0dc0a2061d7c4f22998eae5aff7d569ef1c1dda Mon Sep 17 00:00:00 2001 From: _sam <3804518+aahung@users.noreply.github.com> Date: Wed, 7 Jul 2021 12:07:52 -0700 Subject: [PATCH 13/23] Update samcli/commands/pipeline/bootstrap/guided_context.py Co-authored-by: Chris Rehn --- samcli/commands/pipeline/bootstrap/guided_context.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samcli/commands/pipeline/bootstrap/guided_context.py b/samcli/commands/pipeline/bootstrap/guided_context.py index 7b490931991..9f5b5bc6b05 100644 --- a/samcli/commands/pipeline/bootstrap/guided_context.py +++ b/samcli/commands/pipeline/bootstrap/guided_context.py @@ -185,7 +185,7 @@ def run(self) -> None: # pylint: disable=too-many-branches click.secho("[3] Reference existing resources", bold=True) if self.pipeline_user_arn: - click.echo(f"Pipeline IAM User ARN: {self.pipeline_user_arn}") + click.echo(f"Pipeline IAM user ARN: {self.pipeline_user_arn}") else: self._prompt_pipeline_user() From a35cc35b39bf22aaf458accd3b8cd50529df4c9a Mon Sep 17 00:00:00 2001 From: _sam <3804518+aahung@users.noreply.github.com> Date: Wed, 7 Jul 2021 12:07:58 -0700 Subject: [PATCH 14/23] Update samcli/commands/pipeline/bootstrap/guided_context.py Co-authored-by: Chris Rehn --- samcli/commands/pipeline/bootstrap/guided_context.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samcli/commands/pipeline/bootstrap/guided_context.py b/samcli/commands/pipeline/bootstrap/guided_context.py index 9f5b5bc6b05..3b76405070f 100644 --- a/samcli/commands/pipeline/bootstrap/guided_context.py +++ b/samcli/commands/pipeline/bootstrap/guided_context.py @@ -170,7 +170,7 @@ def run(self) -> None: # pylint: disable=too-many-branches account_id = get_current_account_id() click.secho("[1] Account details", bold=True) - if click.confirm(f"You are bootstrapping resources in Account {account_id}. Do you want to switch accounts?"): + if click.confirm(f"You are bootstrapping resources in account {account_id}. Do you want to switch accounts?"): click.echo(f"Please refer to this page about configuring credentials: {CONFIG_AWS_CRED_DOC_URL}.") sys.exit(0) From d6b5325a64b730b43b7c5e6d40679f2d31792887 Mon Sep 17 00:00:00 2001 From: _sam <3804518+aahung@users.noreply.github.com> Date: Wed, 7 Jul 2021 12:08:04 -0700 Subject: [PATCH 15/23] Update samcli/commands/pipeline/bootstrap/guided_context.py Co-authored-by: Chris Rehn --- samcli/commands/pipeline/bootstrap/guided_context.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samcli/commands/pipeline/bootstrap/guided_context.py b/samcli/commands/pipeline/bootstrap/guided_context.py index 3b76405070f..6d74b799f26 100644 --- a/samcli/commands/pipeline/bootstrap/guided_context.py +++ b/samcli/commands/pipeline/bootstrap/guided_context.py @@ -160,7 +160,7 @@ def run(self) -> None: # pylint: disable=too-many-branches dedent( """\ SAM Pipeline Bootstrap generates the necessary AWS resources to connect your - CI/CD pipeline tool. We will ask for [1] account details, [2] stage definition, + CI/CD system. We will ask for [1] account details, [2] stage definition, and [3] references to existing resources in order to bootstrap these pipeline resources. You can also add optional security parameters. """ From 2eda796a0c0d740a7390b1ec1819416919cb73ff Mon Sep 17 00:00:00 2001 From: _sam <3804518+aahung@users.noreply.github.com> Date: Wed, 7 Jul 2021 12:08:10 -0700 Subject: [PATCH 16/23] Update samcli/commands/pipeline/bootstrap/guided_context.py Co-authored-by: Chris Rehn --- samcli/commands/pipeline/bootstrap/guided_context.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samcli/commands/pipeline/bootstrap/guided_context.py b/samcli/commands/pipeline/bootstrap/guided_context.py index 6d74b799f26..e15e9ba0432 100644 --- a/samcli/commands/pipeline/bootstrap/guided_context.py +++ b/samcli/commands/pipeline/bootstrap/guided_context.py @@ -159,7 +159,7 @@ def run(self) -> None: # pylint: disable=too-many-branches click.secho( dedent( """\ - SAM Pipeline Bootstrap generates the necessary AWS resources to connect your + sam pipeline bootstrap generates the necessary AWS resources to connect your CI/CD system. We will ask for [1] account details, [2] stage definition, and [3] references to existing resources in order to bootstrap these pipeline resources. You can also add optional security parameters. From b119bc8a18a4d824ba3b1e35b36312e6ef1291ed Mon Sep 17 00:00:00 2001 From: _sam <3804518+aahung@users.noreply.github.com> Date: Wed, 7 Jul 2021 12:08:16 -0700 Subject: [PATCH 17/23] Update samcli/commands/pipeline/bootstrap/guided_context.py Co-authored-by: Chris Rehn --- samcli/commands/pipeline/bootstrap/guided_context.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samcli/commands/pipeline/bootstrap/guided_context.py b/samcli/commands/pipeline/bootstrap/guided_context.py index e15e9ba0432..8207810f9b5 100644 --- a/samcli/commands/pipeline/bootstrap/guided_context.py +++ b/samcli/commands/pipeline/bootstrap/guided_context.py @@ -102,7 +102,7 @@ def _prompt_image_repository(self) -> None: def _prompt_ip_range(self) -> None: self.pipeline_ip_range = click.prompt( - "For added security, you can define the permitted Pipeline IP range. " + "For added security, you can define the permitted pipeline IP range. " "Enter the IP addresses to restrict access to", default="", type=click.STRING, From 8ac06f877cf849fa84e97133b4622654bfcd6c7f Mon Sep 17 00:00:00 2001 From: _sam <3804518+aahung@users.noreply.github.com> Date: Wed, 7 Jul 2021 12:08:22 -0700 Subject: [PATCH 18/23] Update samcli/commands/pipeline/bootstrap/guided_context.py Co-authored-by: Chris Rehn --- samcli/commands/pipeline/bootstrap/guided_context.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samcli/commands/pipeline/bootstrap/guided_context.py b/samcli/commands/pipeline/bootstrap/guided_context.py index 8207810f9b5..2fabd5999cd 100644 --- a/samcli/commands/pipeline/bootstrap/guided_context.py +++ b/samcli/commands/pipeline/bootstrap/guided_context.py @@ -91,7 +91,7 @@ def _prompt_artifacts_bucket(self) -> None: def _prompt_image_repository(self) -> None: if click.confirm("Does your application contain any IMAGE type Lambda functions?"): self.image_repository_arn = click.prompt( - "Please enter the ECR image repository ARN(s) for your IMAGE type function(s)." + "Please enter the ECR image repository ARN(s) for your Image type function(s)." "If you do not yet have a repostiory, we will create one for you", default="", type=click.STRING, From eca4bb6cf7376cda42f5f5c1ad94e2be1c2707b1 Mon Sep 17 00:00:00 2001 From: _sam <3804518+aahung@users.noreply.github.com> Date: Wed, 7 Jul 2021 12:08:28 -0700 Subject: [PATCH 19/23] Update samcli/commands/pipeline/bootstrap/guided_context.py Co-authored-by: Chris Rehn --- samcli/commands/pipeline/bootstrap/guided_context.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samcli/commands/pipeline/bootstrap/guided_context.py b/samcli/commands/pipeline/bootstrap/guided_context.py index 2fabd5999cd..a26a45c69b6 100644 --- a/samcli/commands/pipeline/bootstrap/guided_context.py +++ b/samcli/commands/pipeline/bootstrap/guided_context.py @@ -66,7 +66,7 @@ def _prompt_pipeline_user(self) -> None: def _prompt_pipeline_execution_role(self) -> None: self.pipeline_execution_role_arn = click.prompt( - "Enter the Pipeline execution role ARN if you have previously created one, " + "Enter the pipeline execution role ARN if you have previously created one, " "or we will create one for you", default="", type=click.STRING, From c196d99618f3e247c041a745cc201578502762f1 Mon Sep 17 00:00:00 2001 From: _sam <3804518+aahung@users.noreply.github.com> Date: Wed, 7 Jul 2021 12:08:33 -0700 Subject: [PATCH 20/23] Update samcli/commands/pipeline/bootstrap/guided_context.py Co-authored-by: Chris Rehn --- samcli/commands/pipeline/bootstrap/guided_context.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samcli/commands/pipeline/bootstrap/guided_context.py b/samcli/commands/pipeline/bootstrap/guided_context.py index a26a45c69b6..a8c2ab30dac 100644 --- a/samcli/commands/pipeline/bootstrap/guided_context.py +++ b/samcli/commands/pipeline/bootstrap/guided_context.py @@ -59,7 +59,7 @@ def _prompt_region_name(self) -> None: def _prompt_pipeline_user(self) -> None: self.pipeline_user_arn = click.prompt( - "Enter the Pipeline IAM User ARN if you have previously created one, or we will create one for you", + "Enter the pipeline IAM user ARN if you have previously created one, or we will create one for you", default="", type=click.STRING, ) From 518b40ce78bd566f50940768119f3abbbfbfd1c4 Mon Sep 17 00:00:00 2001 From: _sam <3804518+aahung@users.noreply.github.com> Date: Wed, 7 Jul 2021 12:08:38 -0700 Subject: [PATCH 21/23] Update samcli/commands/pipeline/bootstrap/cli.py Co-authored-by: Chris Rehn --- samcli/commands/pipeline/bootstrap/cli.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samcli/commands/pipeline/bootstrap/cli.py b/samcli/commands/pipeline/bootstrap/cli.py index fd5e6019cf4..c927fc7210b 100644 --- a/samcli/commands/pipeline/bootstrap/cli.py +++ b/samcli/commands/pipeline/bootstrap/cli.py @@ -211,7 +211,7 @@ def do_cli( f"""\ View the definition in {os.path.join(PIPELINE_CONFIG_DIR, PIPELINE_CONFIG_FILENAME)}, run {Colored().bold("sam pipeline bootstrap")} to generate another set of resources, or proceed to - {Colored().bold("sam pipeline init")} to create your Pipeline Config file. + {Colored().bold("sam pipeline init")} to create your pipeline configuration file. """ ) ) From 2353ea25af4b2b70d6ededcad8ebf2d88cdb7512 Mon Sep 17 00:00:00 2001 From: Sam Liu Date: Wed, 7 Jul 2021 12:31:01 -0700 Subject: [PATCH 22/23] Update unit tests --- tests/unit/lib/pipeline/bootstrap/test_environment.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/tests/unit/lib/pipeline/bootstrap/test_environment.py b/tests/unit/lib/pipeline/bootstrap/test_environment.py index 9b1b70bae50..09df6bdc2e5 100644 --- a/tests/unit/lib/pipeline/bootstrap/test_environment.py +++ b/tests/unit/lib/pipeline/bootstrap/test_environment.py @@ -339,7 +339,7 @@ def test_save_config_safe(self, save_config_mock): def test_print_resources_summary_when_no_resources_provided_by_the_user(self, click_mock): environment: Environment = Environment(name=ANY_ENVIRONMENT_NAME) environment.print_resources_summary() - self.assert_summary_has_a_message_like("We have created the following resources", click_mock.secho) + self.assert_summary_has_a_message_like("The following resources were created in your account", click_mock.secho) @patch("samcli.lib.pipeline.bootstrap.environment.click") def test_print_resources_summary_when_all_resources_are_provided_by_the_user(self, click_mock): @@ -353,7 +353,9 @@ def test_print_resources_summary_when_all_resources_are_provided_by_the_user(sel image_repository_arn=ANY_IMAGE_REPOSITORY_ARN, ) environment.print_resources_summary() - self.assert_summary_does_not_have_a_message_like("We have created the following resources", click_mock.secho) + self.assert_summary_does_not_have_a_message_like( + "The following resources were created in your account", click_mock.secho + ) @patch("samcli.lib.pipeline.bootstrap.environment.click") def test_print_resources_summary_when_some_resources_are_provided_by_the_user(self, click_mock): @@ -365,7 +367,7 @@ def test_print_resources_summary_when_some_resources_are_provided_by_the_user(se image_repository_arn=ANY_IMAGE_REPOSITORY_ARN, ) environment.print_resources_summary() - self.assert_summary_has_a_message_like("We have created the following resources", click_mock.secho) + self.assert_summary_has_a_message_like("The following resources were created in your account", click_mock.secho) @patch("samcli.lib.pipeline.bootstrap.environment.click") def test_print_resources_summary_prints_the_credentials_of_the_pipeline_user_iff_not_provided_by_the_user( From 64c3a1ed4e683c41411c8167a9aced3d587db799 Mon Sep 17 00:00:00 2001 From: Sam Liu Date: Wed, 7 Jul 2021 13:31:45 -0700 Subject: [PATCH 23/23] Add bold to other literals --- samcli/commands/pipeline/bootstrap/cli.py | 4 ++-- samcli/commands/pipeline/bootstrap/guided_context.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/samcli/commands/pipeline/bootstrap/cli.py b/samcli/commands/pipeline/bootstrap/cli.py index c927fc7210b..b8f1996683d 100644 --- a/samcli/commands/pipeline/bootstrap/cli.py +++ b/samcli/commands/pipeline/bootstrap/cli.py @@ -219,8 +219,8 @@ def do_cli( if not environment.pipeline_user.is_user_provided: click.secho( dedent( - """\ - Before running sam pipeline init, we recommend first setting up AWS credentials + f"""\ + Before running {Colored().bold("sam pipeline init")}, we recommend first setting up AWS credentials in your CI/CD account. Read more about how to do so with your provider in [DOCS-LINK]. """ diff --git a/samcli/commands/pipeline/bootstrap/guided_context.py b/samcli/commands/pipeline/bootstrap/guided_context.py index a8c2ab30dac..d7f21044b2f 100644 --- a/samcli/commands/pipeline/bootstrap/guided_context.py +++ b/samcli/commands/pipeline/bootstrap/guided_context.py @@ -158,8 +158,8 @@ def run(self) -> None: # pylint: disable=too-many-branches """ click.secho( dedent( - """\ - sam pipeline bootstrap generates the necessary AWS resources to connect your + f"""\ + {Colored().bold("sam pipeline bootstrap")} generates the necessary AWS resources to connect your CI/CD system. We will ask for [1] account details, [2] stage definition, and [3] references to existing resources in order to bootstrap these pipeline resources. You can also add optional security parameters.