Skip to content

Commit

Permalink
feat: Deploy Help Text Overhaul (#4941)
Browse files Browse the repository at this point in the history
* feat: Update sam deploy help text

* Fix column spacing

* Update formatting

* Add tests

* Change secondary required options to interactive options

* Add test to check params
  • Loading branch information
mildaniel authored Mar 31, 2023
1 parent 450d953 commit b89b8e1
Show file tree
Hide file tree
Showing 10 changed files with 342 additions and 22 deletions.
6 changes: 2 additions & 4 deletions samcli/commands/_utils/options.py
Original file line number Diff line number Diff line change
Expand Up @@ -296,9 +296,7 @@ def parameter_override_click_option():
cls=OptionNargs,
type=CfnParameterOverridesType(),
default={},
help="String that contains AWS CloudFormation parameter overrides encoded as key=value pairs."
"\n\nExample: 'ParameterKey=KeyPairName,ParameterValue=MyKey ParameterKey=InstanceType,"
"ParameterValue=t1.micro' or KeyPairName=MyKey InstanceType=t1.micro",
help="String that contains AWS CloudFormation parameter overrides encoded as key=value pairs.",
)


Expand Down Expand Up @@ -326,7 +324,7 @@ def signing_profiles_click_option():
cls=OptionNargs,
type=SigningProfilesOptionType(),
default={},
help="Optional. A string that contains Code Sign configuration parameters as "
help="A string that contains Code Sign configuration parameters as "
"FunctionOrLayerNameToSign=SigningProfileName:SigningProfileOwner "
"Since signing profile owner is optional, it could also be written as "
"FunctionOrLayerNameToSign=SigningProfileName",
Expand Down
40 changes: 22 additions & 18 deletions samcli/commands/deploy/command.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
template_click_option,
use_json_option,
)
from samcli.commands.deploy.core.command import DeployCommand
from samcli.commands.deploy.utils import sanitize_parameter_overrides
from samcli.lib.bootstrap.bootstrap import manage_stack
from samcli.lib.bootstrap.companion_stack.companion_stack_manager import sync_ecr_stack
Expand All @@ -44,16 +45,16 @@
SHORT_HELP = "Deploy an AWS SAM application."


HELP_TEXT = """The sam deploy command creates a Cloudformation Stack and deploys your resources.
HELP_TEXT = """The sam deploy command creates a Cloudformation Stack and deploys your resources."""

\b
Set SAM_CLI_POLL_DELAY Environment Variable with a value of seconds in your shell to configure
how often SAM CLI checks the Stack state, which is useful when seeing throttling from CloudFormation.
\b
e.g. sam deploy --template-file packaged.yaml --stack-name sam-app --capabilities CAPABILITY_IAM
\b
DESCRIPTION = """
To turn on the guided interactive mode, specify the --guided option. This mode shows you the parameters
required for deployment, provides default options, and optionally saves these options in a configuration
file in your project directory. When you perform subsequent deployments of your application using sam deploy,
the AWS SAM CLI retrieves the required parameters from the configuration file.
Set SAM_CLI_POLL_DELAY Environment Variable with a value of seconds in your shell to configure
how often SAM CLI checks the Stack state, which is useful when seeing throttling from CloudFormation.
"""

CONFIG_SECTION = "parameters"
Expand All @@ -63,8 +64,16 @@
@click.command(
"deploy",
short_help=SHORT_HELP,
context_settings={"ignore_unknown_options": False, "allow_interspersed_args": True, "allow_extra_args": True},
context_settings={
"ignore_unknown_options": False,
"allow_interspersed_args": True,
"allow_extra_args": True,
"max_content_width": 120,
},
cls=DeployCommand,
help=HELP_TEXT,
description=DESCRIPTION,
requires_credentials=True,
)
@configuration_option(provider=TomlProvider(section=CONFIG_SECTION))
@click.option(
Expand All @@ -81,20 +90,15 @@
required=False,
is_flag=True,
help="Indicates whether to execute the change set. "
"Specify this flag if you want to view your stack changes "
"before executing the change set. The command creates an AWS CloudFormation "
"change set and then exits without executing the change set. if "
"the changeset looks satisfactory, the stack changes can be made by "
"running the same command without specifying `--no-execute-changeset`",
"Specify this flag to view stack changes before executing the change set.",
)
@click.option(
"--fail-on-empty-changeset/--no-fail-on-empty-changeset",
default=True,
required=False,
is_flag=True,
help="Specify if the CLI should return a non-zero exit code if there are no "
"changes to be made to the stack. The default behavior is to return a "
"non-zero exit code.",
help="Specify whether AWS SAM CLI should return a non-zero exit code if there are no "
"changes to be made to the stack. Defaults to a non-zero exit code.",
)
@click.option(
"--confirm-changeset/--no-confirm-changeset",
Expand Down
Empty file.
126 changes: 126 additions & 0 deletions samcli/commands/deploy/core/command.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
from typing import List

from click import Context, style

from samcli.cli.core.command import CoreCommand
from samcli.cli.row_modifiers import RowDefinition, ShowcaseRowModifier
from samcli.commands.deploy.core.formatters import DeployCommandHelpTextFormatter
from samcli.commands.deploy.core.options import OPTIONS_INFO

COL_SIZE_MODIFIER = 50


class DeployCommand(CoreCommand):
class CustomFormatterContext(Context):
formatter_class = DeployCommandHelpTextFormatter

context_class = CustomFormatterContext

@staticmethod
def format_examples(ctx: Context, formatter: DeployCommandHelpTextFormatter):
with formatter.indented_section(name="Examples", extra_indents=1):
formatter.write_rd(
[
RowDefinition(
text="\n",
),
RowDefinition(
name=style(f"$ {ctx.command_path} --guided"), extra_row_modifiers=[ShowcaseRowModifier()]
),
RowDefinition(
name=style(
f"$ {ctx.command_path} --template-file packaged.yaml --stack-name "
f"sam-app --capabilities CAPABILITY_IAM"
),
extra_row_modifiers=[ShowcaseRowModifier()],
),
RowDefinition(
name=style(
f"$ {ctx.command_path} --parameter-overrides "
f"'ParameterKey=InstanceType,ParameterValue=t1.micro'"
),
extra_row_modifiers=[ShowcaseRowModifier()],
),
RowDefinition(
name=style(
f"$ {ctx.command_path} --parameter-overrides KeyPairName=MyKey InstanceType=t1.micro"
),
extra_row_modifiers=[ShowcaseRowModifier()],
),
],
col_max=COL_SIZE_MODIFIER,
)

@staticmethod
def format_acronyms(formatter: DeployCommandHelpTextFormatter):
with formatter.indented_section(name="Acronyms", extra_indents=1):
formatter.write_rd(
[
RowDefinition(
text="\n",
),
RowDefinition(
name="IAM",
text="Identity and Access Management",
extra_row_modifiers=[ShowcaseRowModifier()],
),
RowDefinition(
name="ARN",
text="Amazon Resource Name",
extra_row_modifiers=[ShowcaseRowModifier()],
),
RowDefinition(
name="S3",
text="Simple Storage Service",
extra_row_modifiers=[ShowcaseRowModifier()],
),
RowDefinition(
name="SNS",
text="Simple Notification Service",
extra_row_modifiers=[ShowcaseRowModifier()],
),
RowDefinition(
name="ECR",
text="Elastic Container Registry",
extra_row_modifiers=[ShowcaseRowModifier()],
),
RowDefinition(
name="KMS",
text="Key Management Service",
extra_row_modifiers=[ShowcaseRowModifier()],
),
],
col_max=COL_SIZE_MODIFIER,
)

def format_options(self, ctx: Context, formatter: DeployCommandHelpTextFormatter) -> None: # type:ignore
# `ignore` is put in place here for mypy even though it is the correct behavior,
# as the `formatter_class` can be set in subclass of Command. If ignore is not set,
# mypy raises argument needs to be HelpFormatter as super class defines it.

self.format_description(formatter)
DeployCommand.format_examples(ctx, formatter)
DeployCommand.format_acronyms(formatter)

for option_heading, options in OPTIONS_INFO.items():
opts: List[RowDefinition] = sorted(
[
CoreCommand.convert_param_to_row_definition(
ctx=ctx, param=param, rank=options.get("option_names", {}).get(param.name, {}).get("rank", 0)
)
for param in self.get_params(ctx)
if param.name in options.get("option_names", {}).keys()
],
key=lambda row_def: row_def.rank,
)
with formatter.indented_section(name=option_heading, extra_indents=1):
formatter.write_rd(options.get("extras", [RowDefinition()]), col_max=COL_SIZE_MODIFIER)
formatter.write_rd(
[RowDefinition(name="", text="\n")]
+ [
opt
for options in zip(opts, [RowDefinition(name="", text="\n")] * (len(opts)))
for opt in options
],
col_max=COL_SIZE_MODIFIER,
)
19 changes: 19 additions & 0 deletions samcli/commands/deploy/core/formatters.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
from samcli.cli.formatters import RootCommandHelpTextFormatter
from samcli.cli.row_modifiers import BaseLineRowModifier
from samcli.commands.deploy.core.options import ALL_OPTIONS


class DeployCommandHelpTextFormatter(RootCommandHelpTextFormatter):
# Picked an additive constant that gives an aesthetically pleasing look.
ADDITIVE_JUSTIFICATION = 15

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# Add Additional space after determining the longest option.
# However, do not justify with padding for more than half the width of
# the terminal to retain aesthetics.
self.left_justification_length = min(
max([len(option) for option in ALL_OPTIONS]) + self.ADDITIVE_JUSTIFICATION,
self.width // 2 - self.indent_increment,
)
self.modifiers = [BaseLineRowModifier()]
89 changes: 89 additions & 0 deletions samcli/commands/deploy/core/options.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
"""
Deploy Command Options related Datastructures for formatting.
"""
from typing import Dict, List

from samcli.cli.row_modifiers import RowDefinition

# The ordering of the option lists matter, they are the order in which options will be displayed.

REQUIRED_OPTIONS: List[str] = ["stack_name", "capabilities", "resolve_s3"]

# Can be used instead of the options in the first list
INTERACTIVE_OPTIONS: List[str] = ["guided"]

AWS_CREDENTIAL_OPTION_NAMES: List[str] = ["region", "profile"]

INFRASTRUCTURE_OPTION_NAMES: List[str] = [
"parameter_overrides",
"s3_bucket",
"s3_prefix",
"resolve_image_repos",
"image_repository",
"image_repositories",
"role_arn",
"kms_key_id",
"notification_arns",
"tags",
"metadata",
]

DEPLOYMENT_OPTIONS: List[str] = [
"no_execute_changeset",
"fail_on_empty_changeset",
"confirm_changeset",
"disable_rollback",
"on_failure",
"force_upload",
]

CONFIGURATION_OPTION_NAMES: List[str] = ["config_env", "config_file"]

ADDITIONAL_OPTIONS: List[str] = [
"no_progressbar",
"signing_profiles",
"template_file",
"use_json",
]

OTHER_OPTIONS: List[str] = ["debug"]

ALL_OPTIONS: List[str] = (
REQUIRED_OPTIONS
+ INTERACTIVE_OPTIONS
+ AWS_CREDENTIAL_OPTION_NAMES
+ INFRASTRUCTURE_OPTION_NAMES
+ DEPLOYMENT_OPTIONS
+ CONFIGURATION_OPTION_NAMES
+ ADDITIONAL_OPTIONS
+ OTHER_OPTIONS
)

OPTIONS_INFO: Dict[str, Dict] = {
"Required Options": {"option_names": {opt: {"rank": idx} for idx, opt in enumerate(REQUIRED_OPTIONS)}},
"Interactive Options": {
"option_names": {opt: {"rank": idx} for idx, opt in enumerate(INTERACTIVE_OPTIONS)},
"extras": [
RowDefinition(name="Use the guided flag for a step-by-step flow instead of using the required options. ")
],
},
"AWS Credential Options": {
"option_names": {opt: {"rank": idx} for idx, opt in enumerate(AWS_CREDENTIAL_OPTION_NAMES)}
},
"Infrastructure Options": {
"option_names": {opt: {"rank": idx} for idx, opt in enumerate(INFRASTRUCTURE_OPTION_NAMES)}
},
"Deployment Options": {"option_names": {opt: {"rank": idx} for idx, opt in enumerate(DEPLOYMENT_OPTIONS)}},
"Configuration Options": {
"option_names": {opt: {"rank": idx} for idx, opt in enumerate(CONFIGURATION_OPTION_NAMES)},
"extras": [
RowDefinition(name="Learn more about configuration files at:"),
RowDefinition(
name="https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli"
"-config.html. "
),
],
},
"Additional Options": {"option_names": {opt: {"rank": idx} for idx, opt in enumerate(ADDITIONAL_OPTIONS)}},
"Other Options": {"option_names": {opt: {"rank": idx} for idx, opt in enumerate(OTHER_OPTIONS)}},
}
Empty file.
61 changes: 61 additions & 0 deletions tests/unit/commands/deploy/core/test_command.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import unittest
from unittest.mock import Mock, patch
from samcli.commands.deploy.core.command import DeployCommand
from samcli.commands.deploy.command import DESCRIPTION
from tests.unit.cli.test_command import MockFormatter


class MockParams:
def __init__(self, rv, name):
self.rv = rv
self.name = name

def get_help_record(self, ctx):
return self.rv


class TestDeployCommand(unittest.TestCase):
@patch.object(DeployCommand, "get_params")
def test_get_options_deploy_command_text(self, mock_get_params):
ctx = Mock()
ctx.command_path = "sam deploy"
ctx.parent.command_path = "sam"
formatter = MockFormatter(scrub_text=True)
# NOTE(sriram-mv): One option per option section.
mock_get_params.return_value = [
MockParams(rv=("--region", "Region"), name="region"),
MockParams(rv=("--debug", ""), name="debug"),
MockParams(rv=("--config-file", ""), name="config_file"),
MockParams(rv=("--s3-bucket", ""), name="s3_bucket"),
MockParams(rv=("--signing-profiles", ""), name="signing_profiles"),
MockParams(rv=("--stack-name", ""), name="stack_name"),
MockParams(rv=("--no-execute-changeset", ""), name="no_execute_changeset"),
MockParams(rv=("--guided", ""), name="guided"),
]

cmd = DeployCommand(name="deploy", requires_credentials=False, description=DESCRIPTION)
expected_output = {
"AWS Credential Options": [("", ""), ("--region", ""), ("", "")],
"Additional Options": [("", ""), ("--signing-profiles", ""), ("", "")],
"Deployment Options": [("", ""), ("--no-execute-changeset", ""), ("", "")],
"Configuration Options": [("", ""), ("--config-file", ""), ("", "")],
"Other Options": [("", ""), ("--debug", ""), ("", "")],
"Required Options": [("", ""), ("--stack-name", ""), ("", "")],
"Infrastructure Options": [("", ""), ("--s3-bucket", ""), ("", "")],
"Interactive Options": [("", ""), ("--guided", ""), ("", "")],
"Description": [(cmd.description + cmd.description_addendum, "")],
"Acronyms": [("", ""), ("IAM", ""), ("ARN", ""), ("S3", ""), ("SNS", ""), ("ECR", ""), ("KMS", "")],
"Examples": [
("", ""),
("$ sam deploy --guided\x1b[0m", ""),
(
"$ sam deploy --template-file packaged.yaml --stack-name sam-app --capabilities CAPABILITY_IAM\x1b[0m",
"",
),
("$ sam deploy --parameter-overrides 'ParameterKey=InstanceType,ParameterValue=t1.micro'\x1b[0m", ""),
("$ sam deploy --parameter-overrides KeyPairName=MyKey InstanceType=t1.micro\x1b[0m", ""),
],
}

cmd.format_options(ctx, formatter)
self.assertEqual(formatter.data, expected_output)
Loading

0 comments on commit b89b8e1

Please sign in to comment.