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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 16 additions & 16 deletions samcli/commands/pipeline/bootstrap/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
from samcli.cli.context import get_cmd_names
from samcli.cli.main import pass_context, common_options, aws_creds_options, print_cmdline_args
from samcli.lib.config.samconfig import SamConfig
from samcli.lib.pipeline.bootstrap.stage import Stage
from samcli.lib.pipeline.bootstrap.environment import Environment
from samcli.lib.telemetry.metric import track_command
from samcli.lib.utils.version_checker import check_newer_version
from .guided_context import GuidedContext
Expand Down Expand Up @@ -38,8 +38,8 @@
help="Disable interactive prompting for bootstrap parameters, and fail if any required arguments are missing.",
)
@click.option(
"--stage-name",
help="The name of the corresponding pipeline stage. It is used as a suffix for the created resources.",
"--environment",
help="The name of the corresponding environment. It is used as a suffix for the created resources.",
required=False,
)
@click.option(
Expand All @@ -51,7 +51,7 @@
)
@click.option(
"--pipeline-execution-role",
help="The ARN of an IAM role to be assumed by the pipeline user to operate on this stage. "
help="The ARN of an IAM role to be assumed by the pipeline user to operate on this environment. "
"Provide it only if you want to user your own role, otherwise, the command will create one",
required=False,
)
Expand Down Expand Up @@ -101,7 +101,7 @@
def cli(
ctx: Any,
interactive: bool,
stage_name: Optional[str],
environment: Optional[str],
pipeline_user: Optional[str],
pipeline_execution_role: Optional[str],
cloudformation_execution_role: Optional[str],
Expand All @@ -120,7 +120,7 @@ def cli(
region=ctx.region,
profile=ctx.profile,
interactive=interactive,
stage_name=stage_name,
environment_name=environment,
pipeline_user_arn=pipeline_user,
pipeline_execution_role_arn=pipeline_execution_role,
cloudformation_execution_role_arn=cloudformation_execution_role,
Expand All @@ -138,7 +138,7 @@ def do_cli(
region: Optional[str],
profile: Optional[str],
interactive: bool,
stage_name: Optional[str],
environment_name: Optional[str],
pipeline_user_arn: Optional[str],
pipeline_execution_role_arn: Optional[str],
cloudformation_execution_role_arn: Optional[str],
Expand All @@ -158,7 +158,7 @@ def do_cli(

if interactive:
guided_context = GuidedContext(
stage_name=stage_name,
environment_name=environment_name,
pipeline_user_arn=pipeline_user_arn,
pipeline_execution_role_arn=pipeline_execution_role_arn,
cloudformation_execution_role_arn=cloudformation_execution_role_arn,
Expand All @@ -168,7 +168,7 @@ def do_cli(
pipeline_ip_range=pipeline_ip_range,
)
guided_context.run()
stage_name = guided_context.stage_name
environment_name = guided_context.environment_name
pipeline_user_arn = guided_context.pipeline_user_arn
pipeline_execution_role_arn = guided_context.pipeline_execution_role_arn
pipeline_ip_range = guided_context.pipeline_ip_range
Expand All @@ -177,11 +177,11 @@ def do_cli(
create_ecr_repo = guided_context.create_ecr_repo
ecr_repo_arn = guided_context.ecr_repo_arn

if not stage_name:
raise click.UsageError("Missing required parameter '--stage-name'")
if not environment_name:
raise click.UsageError("Missing required parameter '--environment'")

stage: Stage = Stage(
name=stage_name,
environment: Environment = Environment(
name=environment_name,
aws_profile=profile,
aws_region=region,
pipeline_user_arn=pipeline_user_arn,
Expand All @@ -193,12 +193,12 @@ def do_cli(
ecr_repo_arn=ecr_repo_arn,
)

bootstrapped: bool = stage.bootstrap(confirm_changeset=confirm_changeset)
bootstrapped: bool = environment.bootstrap(confirm_changeset=confirm_changeset)

if bootstrapped:
stage.print_resources_summary()
environment.print_resources_summary()

stage.save_config_safe(
environment.save_config_safe(
config_dir=PIPELINE_CONFIG_DIR, filename=PIPELINE_CONFIG_FILENAME, cmd_names=_get_command_names()
)

Expand Down
16 changes: 8 additions & 8 deletions samcli/commands/pipeline/bootstrap/guided_context.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
"""
An interactive flow that prompt the user for required information to bootstrap the AWS account of a pipeline stage
An interactive flow that prompt the user for required information to bootstrap the AWS account of an environment
with the required infrastructure
"""
from typing import Optional
Expand All @@ -10,7 +10,7 @@
class GuidedContext:
def __init__(
self,
stage_name: Optional[str] = None,
environment_name: Optional[str] = None,
pipeline_user_arn: Optional[str] = None,
pipeline_execution_role_arn: Optional[str] = None,
cloudformation_execution_role_arn: Optional[str] = None,
Expand All @@ -19,7 +19,7 @@ def __init__(
ecr_repo_arn: Optional[str] = None,
pipeline_ip_range: Optional[str] = None,
) -> None:
self.stage_name = stage_name
self.environment_name = environment_name
self.pipeline_user_arn = pipeline_user_arn
self.pipeline_execution_role_arn = pipeline_execution_role_arn
self.cloudformation_execution_role_arn = cloudformation_execution_role_arn
Expand All @@ -34,13 +34,13 @@ 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
"""
if not self.stage_name:
self.stage_name = click.prompt("Stage Name", type=click.STRING)
if not self.environment_name:
self.environment_name = click.prompt("Environment Name", type=click.STRING)

if not self.pipeline_user_arn:
click.echo(
"\nThere must be exactly one pipeline user across all of the pipeline stages. "
"If you have ran this command before to bootstrap a previous pipeline stage, please "
"\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 provider."
)
Expand All @@ -50,7 +50,7 @@ def run(self) -> None:

if not self.pipeline_execution_role_arn:
self.pipeline_execution_role_arn = click.prompt(
"\nPipeline execution role (an IAM role assumed by the pipeline user to operate on this stage) "
"\nPipeline execution role (an IAM role assumed by the pipeline user to operate on this environment) "
"[leave blank to create one]",
default="",
type=click.STRING,
Expand Down
12 changes: 6 additions & 6 deletions samcli/commands/pipeline/init/pipeline_templates_manifest.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,15 @@
- displayName:Github Actions
id: github-actions
templates:
- displayName: jenkins-two-stages-pipeline
- displayName: jenkins-two-environments-pipeline
provider: Jenkins
location: templates/cookiecutter-jenkins-two-stages-pipeline
- displayName: gitlab-two-stages-pipeline
location: templates/cookiecutter-jenkins-two-environments-pipeline
- displayName: gitlab-two-environments-pipeline
provider: Gitlab
location: templates/cookiecutter-gitlab-two-stages-pipeline
- displayName: Github-Actions-two-stages-pipeline
location: templates/cookiecutter-gitlab-two-environments-pipeline
- displayName: Github-Actions-two-environments-pipeline
provider: Github Actions
location: templates/cookiecutter-github-actions-two-stages-pipeline
location: templates/cookiecutter-github-actions-two-environments-pipeline
"""
from pathlib import Path
from typing import Dict, List
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
""" Pipeline stage"""
""" Application Environment """
import os
import pathlib
import re
Expand All @@ -12,8 +12,8 @@

CFN_TEMPLATE_PATH = str(pathlib.Path(os.path.dirname(__file__)))
STACK_NAME_PREFIX = "aws-sam-cli-managed"
STAGE_RESOURCES_STACK_NAME_SUFFIX = "pipeline-resources"
STAGE_RESOURCES_CFN_TEMPLATE = "stage_resources.yaml"
ENVIRONMENT_RESOURCES_STACK_NAME_SUFFIX = "pipeline-resources"
ENVIRONMENT_RESOURCES_CFN_TEMPLATE = "environment_resources.yaml"
PIPELINE_USER = "pipeline_user"
PIPELINE_EXECUTION_ROLE = "pipeline_execution_role"
CLOUDFORMATION_EXECUTION_ROLE = "cloudformation_execution_role"
Expand All @@ -22,18 +22,18 @@
REGION = "region"


class Stage:
class Environment:
"""
Represents a pipeline stage
Represents an application environment: Beta, Gamma, Prod ...etc

Attributes
----------
name: str
The name of the stage
The name of the environment
aws_profile: Optional[str]
The named AWS profile(in user's machine) of the AWS account to deploy this stage to.
The named AWS profile (in user's machine) of the AWS account to deploy this environment to.
aws_region: Optional[str]
The AWS region to deploy this stage to.
The AWS region to deploy this environment to.
pipeline_user: IAMUser
The IAM User having its AccessKeyId and SecretAccessKey credentials shared with the CI/CD provider
pipeline_execution_role: Resource
Expand All @@ -55,14 +55,14 @@ class Stage:
Methods:
--------
did_user_provide_all_required_resources(self) -> bool:
checks if all of the stage required resources(pipeline_user, pipeline_execution_role,
checks if all of the environment's required resources (pipeline_user, pipeline_execution_role,
cloudformation_execution_role, artifacts_bucket and ecr_repo) are provided by the user.
bootstrap(self, confirm_changeset: bool = True) -> None:
deploys the CFN template ./stage_resources.yaml to the AWS account identified by aws_profile and aws_region
member fields. if aws_profile is not provided, it will fallback to default boto3 credentials' resolving.
Note that ./stage_resources.yaml template accepts the ARNs of already existing resources(if any) as parameters
and it will skip the creation of those resources but will use the ARNs to set the proper permissions of other
missing resources(resources created by the template)
deploys the CFN template ./environment_resources.yaml to the AWS account identified by aws_profile and
aws_region member fields. if aws_profile is not provided, it will fallback to default boto3 credentials'
resolving. Note that ./environment_resources.yaml template accepts the ARNs of already existing resources(if
any) as parameters and it will skip the creation of those resources but will use the ARNs to set the proper
permissions of other missing resources(resources created by the template)
save_config(self, config_dir: str, filename: str, cmd_names: List[str]):
save the Artifacts bucket name, ECR repo URI and ARNs of pipeline_user, pipeline_execution_role and
cloudformation_execution_role to the "pipelineconfig.toml" file so that it can be auto-filled during
Expand Down Expand Up @@ -96,7 +96,7 @@ def __init__(
self.ecr_repo: ECRRepo = ECRRepo(arn=ecr_repo_arn)

def did_user_provide_all_required_resources(self) -> bool:
"""Check if the user provided all of the stage resources or not"""
"""Check if the user provided all of the environment resources or not"""
return all(resource.is_user_provided for resource in self._get_resources())

def _get_non_user_provided_resources_msg(self) -> str:
Expand All @@ -115,13 +115,13 @@ def _get_non_user_provided_resources_msg(self) -> str:

def bootstrap(self, confirm_changeset: bool = True) -> bool:
"""
Deploys the CFN template(./stage_resources.yaml) which deploys:
Deploys the CFN template(./environment_resources.yaml) which deploys:
* Pipeline IAM User
* Pipeline execution IAM role
* CloudFormation execution IAM role
* Artifacts' S3 Bucket
* ECR Repo
to the AWS account associated with the given stage. It will not redeploy the stack if already exists.
to the AWS account associated with the given environment. It will not redeploy the stack if already exists.
This CFN template accepts the ARNs of the resources as parameters and will not create a resource if already
provided, this way we can conditionally create a resource only if the user didn't provide it

Expand All @@ -130,33 +130,36 @@ def bootstrap(self, confirm_changeset: bool = True) -> bool:
Parameters
----------
confirm_changeset: bool
if set to false, the stage_resources.yaml CFN template will directly be deployed, otherwise, the user will
be prompted for confirmation
if set to false, the environment_resources.yaml CFN template will directly be deployed, otherwise,
the user will be prompted for confirmation

Returns True if bootstrapped, otherwise False
"""

if self.did_user_provide_all_required_resources():
click.secho(f"\nAll required resources for the {self.name} stage exist, skipping creation.", fg="yellow")
click.secho(
f"\nAll required resources for the {self.name} environment exist, skipping creation.", fg="yellow"
)
return True

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} stage: {missing_resources_msg}"
f"This will create the following required resources for the '{self.name}' environment: "
f"{missing_resources_msg}"
)
if confirm_changeset:
confirmed: bool = click.confirm("Should we proceed with the creation?")
if not confirmed:
return False

sanitized_stage_name: str = re.sub("[^0-9a-zA-Z]+", "-", self.name)
stack_name: str = f"{STACK_NAME_PREFIX}-{sanitized_stage_name}-{STAGE_RESOURCES_STACK_NAME_SUFFIX}"
stage_resources_template_body = Stage._read_template(STAGE_RESOURCES_CFN_TEMPLATE)
sanitized_environment_name: str = re.sub("[^0-9a-zA-Z]+", "-", self.name)
stack_name: str = f"{STACK_NAME_PREFIX}-{sanitized_environment_name}-{ENVIRONMENT_RESOURCES_STACK_NAME_SUFFIX}"
environment_resources_template_body = Environment._read_template(ENVIRONMENT_RESOURCES_CFN_TEMPLATE)
output: StackOutput = manage_stack(
stack_name=stack_name,
region=self.aws_region,
profile=self.aws_profile,
template_body=stage_resources_template_body,
template_body=environment_resources_template_body,
parameter_overrides={
"PipelineUserArn": self.pipeline_user.arn or "",
"PipelineExecutionRoleArn": self.pipeline_execution_role.arn or "",
Expand Down Expand Up @@ -219,15 +222,15 @@ def save_config(self, config_dir: str, filename: str, cmd_names: List[str]) -> N
except ValueError:
ecr_repo_uri = ""

stage_specific_configs: Dict[str, Optional[str]] = {
environment_specific_configs: Dict[str, Optional[str]] = {
PIPELINE_EXECUTION_ROLE: self.pipeline_execution_role.arn,
CLOUDFORMATION_EXECUTION_ROLE: self.cloudformation_execution_role.arn,
ARTIFACTS_BUCKET: artifacts_bucket_name,
ECR_REPO: ecr_repo_uri,
REGION: self.aws_region,
}

for key, value in stage_specific_configs.items():
for key, value in environment_specific_configs.items():
if value:
samconfig.put(
cmd_names=cmd_names,
Expand Down Expand Up @@ -278,7 +281,8 @@ def print_resources_summary(self) -> None:
if provided_resources:
click.secho(
"\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/stage_resources.yaml",
"https://github.com/aws/aws-sam-cli/blob/develop/"
"samcli/lib/pipeline/bootstrap/environment_resources.yaml",
fg="green",
)
for resource in provided_resources:
Expand Down
Loading