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
16 changes: 15 additions & 1 deletion samcli/commands/pipeline/init/interactive_init_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,11 +96,25 @@ def _load_pipeline_bootstrap_context() -> Dict:

config = SamConfig(PIPELINE_CONFIG_DIR, PIPELINE_CONFIG_FILENAME)
if not config.exists():
context[str(["environment_names_message"])] = ""
return context
for env in config.get_env_names():

# config.get_env_names() will return the list of
# bootstrapped env names and "default" which is used to store shared values
Copy link
Contributor

Choose a reason for hiding this comment

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

I thought it returns a version key as well.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

nope it doesn't

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It only returns keys with its value being a dictionary

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Check: samcli/lib/config/samconfig.py:44

Copy link
Contributor Author

Choose a reason for hiding this comment

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

def get_env_names(self):
self._read()
if isinstance(self.document, dict):
return [env for env, value in self.document.items() if isinstance(value, dict)]
return []

# we don't want to include "default" here.
env_names = [env_name for env_name in config.get_env_names() if env_name != "default"]
for env in env_names:
for key, value in config.get_all(bootstrap_command_names, section, env).items():
context[str([env, key])] = value

# pre-load the list of env names detected from pipelineconfig.toml
environment_names_message = (
"Here are the environment names detected "
+ f"in {os.path.join(PIPELINE_CONFIG_DIR, PIPELINE_CONFIG_FILENAME)}:\n"
+ "\n".join([f"\t- {env_name}" for env_name in env_names])
)
context[str(["environment_names_message"])] = environment_names_message

return context


Expand Down
40 changes: 24 additions & 16 deletions samcli/lib/cookiecutter/question.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ def ask(self, context: Optional[Dict] = None) -> Any:
# 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)
return click.prompt(text=self._resolve_text(context), default=resolved_default_answer)

def get_next_question_key(self, answer: Any) -> Optional[str]:
# _next_question_map is a Dict[str(answer), str(next question key)]
Expand Down Expand Up @@ -154,51 +154,59 @@ def _resolve_key_path(self, key_path: List, context: Dict) -> List[str]:
raise ValueError(f'Invalid value "{unresolved_key}" in key path')
return resolved_key_path

def _resolve_default_answer(self, context: Optional[Dict] = None) -> Optional[Any]:
def _resolve_value_from_expression(self, expression: Any, context: Optional[Dict] = None) -> Optional[Any]:
"""
a question may have a default answer provided directly through the "default_answer" value
a question may have a value provided directly as string or number value
or indirectly from cookiecutter context using a key path

Parameters
----------
context
Cookiecutter context used to resolve default values and answered questions' answers.
Cookiecutter context used to resolve values.

Raises
------
KeyError
When default value depends on the answer to a non-existent question
When an expression depends on the answer to a non-existent question
ValueError
The default value is malformed
The expression is malformed

Returns
-------
Optional default answer, it might be resolved from cookiecutter context using specified key path.
Optional value, it might be resolved from cookiecutter context using specified key path.

"""
if isinstance(self._default_answer, dict):
if isinstance(expression, dict):
context = context if context else {}

# load value using key path from cookiecutter
if "keyPath" not in self._default_answer:
raise KeyError(f'Missing key "keyPath" in question default "{self._default_answer}".')
unresolved_key_path = self._default_answer.get("keyPath", [])
if "keyPath" not in expression:
raise KeyError(f'Missing key "keyPath" in "{expression}".')
unresolved_key_path = expression.get("keyPath", [])
if not isinstance(unresolved_key_path, list):
raise ValueError(f'Invalid default answer "{self._default_answer}" for question {self.key}')
raise ValueError(f'Invalid expression "{expression}" in question {self.key}')

return context.get(str(self._resolve_key_path(unresolved_key_path, context)))
return expression

def _resolve_text(self, context: Optional[Dict] = None) -> str:
resolved_text = self._resolve_value_from_expression(self._text, context)
if resolved_text is None:
raise ValueError(f"Cannot resolve value from expression: {self._text}")
return str(resolved_text)

return self._default_answer
def _resolve_default_answer(self, context: Optional[Dict] = None) -> Optional[Any]:
return self._resolve_value_from_expression(self._default_answer, context)


class Info(Question):
def ask(self, context: Optional[Dict] = None) -> None:
return click.echo(message=self._text)
return click.echo(message=self._resolve_text(context))


class Confirm(Question):
def ask(self, context: Optional[Dict] = None) -> bool:
return click.confirm(text=self._text)
return click.confirm(text=self._resolve_text(context))


class Choice(Question):
Expand All @@ -219,7 +227,7 @@ def __init__(

def ask(self, context: Optional[Dict] = None) -> str:
resolved_default_answer = self._resolve_default_answer(context)
click.echo(self._text)
click.echo(self._resolve_text(context))
for index, option in enumerate(self._options):
click.echo(f"\t{index + 1} - {option}")
options_indexes = self._get_options_indexes(base=1)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,8 @@ def test_generate_pipeline_configuration_file_from_app_pipeline_template_happy_c
{
str(["testing", "pipeline_execution_role"]): "arn:aws:iam::123456789012:role/execution-role",
str(["prod", "pipeline_execution_role"]): "arn:aws:iam::123456789012:role/execution-role",
str(["environment_names_message"]): "Here are the environment names detected "
f'in {os.path.join(".aws-sam", "pipeline", "pipelineconfig.toml")}:\n\t- testing\n\t- prod',
}
)
cookiecutter_mock.assert_called_once_with(
Expand Down