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
14 changes: 14 additions & 0 deletions samcli/commands/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
Class containing error conditions that are exposed to the user.
"""

import os

import click


Expand Down Expand Up @@ -72,3 +74,15 @@ class AppPipelineTemplateManifestException(UserException):
Exception class when SAM is not able to parse the "manifest.yaml" file located in the SAM pipeline templates
Git repo: "github.com/aws/aws-sam-cli-pipeline-init-templates.git
"""


class PipelineFileAlreadyExistsError(UserException):
Copy link
Contributor

Choose a reason for hiding this comment

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

Might add a brief comment here just like all exceptions above?

"""
Exception class when the files to-be-generated by the pipeline template already exists on the SAM project. Instead
of overriding, the user need to manually remove the old files.
"""

def __init__(self, file_path: os.PathLike) -> None:
super().__init__(
f'Pipeline file "{file_path}" already exists in project root directory, please remove it first.'
)
14 changes: 6 additions & 8 deletions samcli/commands/pipeline/init/interactive_init_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
import click

from samcli.cli.main import global_cfg
from samcli.commands.exceptions import PipelineTemplateCloneException
from samcli.commands.exceptions import PipelineTemplateCloneException, PipelineFileAlreadyExistsError
from samcli.lib.config.samconfig import SamConfig
from samcli.lib.cookiecutter.interactive_flow import InteractiveFlow
from samcli.lib.cookiecutter.interactive_flow_creator import InteractiveFlowCreator
Expand Down Expand Up @@ -225,6 +225,9 @@ def _prompt_cicd_provider(available_providers: List[Provider]) -> Provider:
Returns:
The chosen provider
"""
if len(available_providers) == 1:
return available_providers[0]

question_to_choose_provider = Choice(
key="provider",
text="CI/CD provider",
Expand All @@ -246,6 +249,8 @@ def _prompt_provider_pipeline_template(
Returns:
The chosen pipeline template manifest
"""
if len(provider_available_pipeline_templates_metadata) == 1:
return provider_available_pipeline_templates_metadata[0]
question_to_choose_pipeline_template = Choice(
key="pipeline-template",
text="Which pipeline template would you like to use?",
Expand Down Expand Up @@ -291,10 +296,3 @@ def _get_pipeline_template_interactive_flow(pipeline_template_dir: Path) -> Inte
"""
flow_definition_path: Path = pipeline_template_dir.joinpath("questions.json")
return InteractiveFlowCreator.create_flow(str(flow_definition_path))


class PipelineFileAlreadyExistsError(Exception):
def __init__(self, file_path: os.PathLike) -> None:
Exception.__init__(
self, f'Pipeline file "{file_path}" already exists in project root directory, please remove it first.'
)
4 changes: 4 additions & 0 deletions samcli/lib/cookiecutter/question.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,10 @@ def ask(self, context: Optional[Dict] = None) -> Any:
The user provided answer.
"""
resolved_default_answer = self._resolve_default_answer(context)
# if it is an optional question with no default answer,
# set an empty default answer to prevent click from keep asking for an answer
if not self._required and resolved_default_answer is None:
resolved_default_answer = ""
return click.prompt(text=self._text, default=resolved_default_answer)

def get_next_question_key(self, answer: Any) -> Optional[str]:
Expand Down
29 changes: 17 additions & 12 deletions samcli/lib/pipeline/bootstrap/environment.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import click

from samcli.lib.config.samconfig import SamConfig
from samcli.lib.utils.colors import Colored
from samcli.lib.utils.managed_cloudformation_stack import manage_stack, StackOutput
from .resource import Resource, IAMUser, ECRImageRepository

Expand Down Expand Up @@ -94,6 +95,7 @@ def __init__(
self.artifacts_bucket: Resource = Resource(arn=artifacts_bucket_arn)
self.create_image_repository: bool = create_image_repository
self.image_repository: ECRImageRepository = ECRImageRepository(arn=image_repository_arn)
self.color = Colored()

def did_user_provide_all_required_resources(self) -> bool:
"""Check if the user provided all of the environment resources or not"""
Expand Down Expand Up @@ -138,7 +140,7 @@ def bootstrap(self, confirm_changeset: bool = True) -> bool:

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

Choose a reason for hiding this comment

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

Just curious are there any differences between using self.color.yellow and fg="yellow"?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

No difference in the functionality. Just cleaner code.

)
return True

Expand All @@ -150,6 +152,7 @@ def bootstrap(self, confirm_changeset: bool = True) -> bool:
if confirm_changeset:
confirmed: bool = click.confirm("Should we proceed with the creation?")
if not confirmed:
click.secho(self.color.red("Canceling pipeline bootstrap creation."))
return False

environment_resources_template_body = Environment._read_template(ENVIRONMENT_RESOURCES_CFN_TEMPLATE)
Expand Down Expand Up @@ -272,28 +275,30 @@ def print_resources_summary(self) -> None:
created_resources.append(resource)

if created_resources:
click.secho("\nWe have created the following resources:", fg="green")
click.secho(self.color.green("\nWe have created the following resources:"))
for resource in created_resources:
click.secho(f"\t{resource.arn}", fg="green")

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/environment_resources.yaml",
fg="green",
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(f"\t{resource.arn}", fg="green")
click.secho(self.color.green(f"\t{resource.arn}"))

if not self.pipeline_user.is_user_provided:
click.secho(
"Please configure your CI/CD project with the following pipeline user credentials and "
"make sure to periodically rotate it:",
fg="green",
self.color.green(
"Please configure your CI/CD project with the following pipeline user credentials and "
"make sure to periodically rotate it:",
)
)
click.secho(f"\tACCESS_KEY_ID: {self.pipeline_user.access_key_id}", fg="green")
click.secho(f"\tSECRET_ACCESS_KEY: {self.pipeline_user.secret_access_key}", fg="green")
click.secho(self.color.green(f"\tACCESS_KEY_ID: {self.pipeline_user.access_key_id}"))
click.secho(self.color.green(f"\tSECRET_ACCESS_KEY: {self.pipeline_user.secret_access_key}"))

def _get_stack_name(self) -> str:
sanitized_environment_name: str = re.sub("[^0-9a-zA-Z]+", "-", self.name)
Expand Down
4 changes: 2 additions & 2 deletions tests/integration/pipeline/test_bootstrap_command.py
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,7 @@ def test_interactive_with_some_required_resources_provided(self):

def test_interactive_pipeline_user_only_created_once(self):
"""
Create 3 stages, only the first stage resource stack creates
Create 3 environments, only the first environment resource stack creates
a pipeline user, and the remaining two share the same pipeline user.
"""
env_names = []
Expand Down Expand Up @@ -236,7 +236,7 @@ def test_interactive_pipeline_user_only_created_once(self):
self.assertEqual(bootstrap_process_execute.process.returncode, 0)
stdout = bootstrap_process_execute.stdout.decode()

# only first stage creates pipeline user
# Only first environment creates pipeline user
if i == 0:
self.assertIn("We have created the following resources", stdout)
self.assertSetEqual(
Expand Down
1 change: 0 additions & 1 deletion tests/integration/pipeline/test_init_command.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
QUICK_START_JENKINS_INPUTS = [
"1", # quick start
"1", # jenkins, this depends on the template repo.
"1", # two stage pipeline, this depends on the template repo.
"credential-id",
"main",
"template.yaml",
Expand Down
36 changes: 36 additions & 0 deletions tests/unit/commands/pipeline/init/test_initeractive_init_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
APP_PIPELINE_TEMPLATES_REPO_LOCAL_NAME,
shared_path,
CUSTOM_PIPELINE_TEMPLATE_REPO_LOCAL_NAME,
_prompt_cicd_provider,
_prompt_provider_pipeline_template,
)
from samcli.commands.pipeline.init.pipeline_templates_manifest import AppPipelineTemplateManifestException
from samcli.lib.utils.git_repo import CloneRepoException
Expand Down Expand Up @@ -292,3 +294,37 @@ def test_generate_pipeline_configuration_file_from_custom_remote_pipeline_templa
extra_context=cookiecutter_context_mock,
overwrite_if_exists=True,
)

@patch("samcli.lib.cookiecutter.question.click")
def test_prompt_cicd_provider_will_not_prompt_if_the_list_of_providers_has_only_one_provider(self, click_mock):
gitlab_provider = Mock(id="gitlab", display_name="Gitlab CI/CD")
providers = [gitlab_provider]

chosen_provider = _prompt_cicd_provider(providers)
click_mock.prompt.assert_not_called()
self.assertEqual(chosen_provider, gitlab_provider)

jenkins_provider = Mock(id="jenkins", display_name="Jenkins")
providers.append(jenkins_provider)
click_mock.prompt.return_value = "2"
chosen_provider = _prompt_cicd_provider(providers)
click_mock.prompt.assert_called_once()
self.assertEqual(chosen_provider, jenkins_provider)

@patch("samcli.lib.cookiecutter.question.click")
def test_prompt_provider_pipeline_template_will_not_prompt_if_the_list_of_templatess_has_only_one_provider(
self, click_mock
):
template1 = Mock(display_name="anyName1", location="anyLocation1", provider="a provider")
template2 = Mock(display_name="anyName2", location="anyLocation2", provider="a provider")
templates = [template1]

chosen_template = _prompt_provider_pipeline_template(templates)
click_mock.prompt.assert_not_called()
self.assertEqual(chosen_template, template1)

templates.append(template2)
click_mock.prompt.return_value = "2"
chosen_template = _prompt_provider_pipeline_template(templates)
click_mock.prompt.assert_called_once()
self.assertEqual(chosen_template, template2)