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
6 changes: 6 additions & 0 deletions samcli/cli/context.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from typing import List, Optional, cast

import click
from rich.console import Console

from samcli.cli.formatters import RootCommandHelpTextFormatter
from samcli.commands.exceptions import AWSServiceClientError
Expand Down Expand Up @@ -44,6 +45,11 @@ def __init__(self):
self._session_id = str(uuid.uuid4())
self._experimental = False
self._exception = None
self._console = Console()

@property
def console(self):
return self._console

@property
def exception(self):
Expand Down
8 changes: 4 additions & 4 deletions samcli/commands/_utils/options.py
Original file line number Diff line number Diff line change
Expand Up @@ -326,7 +326,7 @@ def no_progressbar_click_option():
default=False,
required=False,
is_flag=True,
help="Does not showcase a progress bar when uploading artifacts to s3 and pushing docker images to ECR",
help="Does not showcase a progress bar when uploading artifacts to S3 and pushing docker images to ECR",
)


Expand Down Expand Up @@ -679,9 +679,9 @@ def resolve_s3_click_option(guided):
required=False,
is_flag=True,
callback=callback,
help="Automatically resolve s3 bucket for non-guided deployments. "
"Enabling this option will also create a managed default s3 bucket for you. "
"If you do not provide a --s3-bucket value, the managed bucket will be used. "
help="Automatically resolve AWS S3 bucket for non-guided deployments. "
"Enabling this option will also create a managed default AWS S3 bucket for you. "
"If one does not provide a --s3-bucket value, the managed bucket will be used. "
"Do not use --guided with this option.",
)

Expand Down
35 changes: 23 additions & 12 deletions samcli/commands/package/command.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
template_click_option,
use_json_option,
)
from samcli.commands.package.core.command import PackageCommand
from samcli.lib.bootstrap.bootstrap import manage_stack
from samcli.lib.cli_validation.image_repository_validation import image_repository_validation
from samcli.lib.telemetry.metric import track_command, track_template_warnings
Expand All @@ -42,20 +43,30 @@ def resources_and_properties_help_string():
)


HELP_TEXT = (
"""The SAM package command creates and uploads artifacts based on the package type of a given resource.
It uploads local images to ECR for `Image` package types.
It creates zip of your code and dependencies and uploads it to S3 for other package types.
The command returns a copy of your template, replacing references to local artifacts
with the AWS location where the command uploaded the artifacts.

The following resources and their property locations are supported.
"""
+ resources_and_properties_help_string()
)
DESCRIPTION = """
Creates and uploads artifacts based on the package type of a given resource.
It uploads local images to ECR for `Image` package types.
It creates a zip of code and dependencies and uploads it to S3 for `Zip` package types.

A new template is returned which replaces references to local artifacts
with the AWS location where the command uploaded the artifacts.
"""


@click.command("package", short_help=SHORT_HELP, help=HELP_TEXT, context_settings=dict(max_content_width=120))
@click.command(
"package",
short_help=SHORT_HELP,
context_settings={
"ignore_unknown_options": False,
"allow_interspersed_args": True,
"allow_extra_args": True,
"max_content_width": 120,
},
cls=PackageCommand,
help=SHORT_HELP,
description=DESCRIPTION,
requires_credentials=True,
)
@configuration_option(provider=TomlProvider(section="parameters"))
@template_click_option(include_build=True)
@click.option(
Expand Down
Empty file.
138 changes: 138 additions & 0 deletions samcli/commands/package/core/command.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
"""
`sam package` command class for help text visual layer.
"""
import click
from click import Context, style
from rich.table import Table

from samcli.cli.core.command import CoreCommand
from samcli.cli.row_modifiers import RowDefinition, ShowcaseRowModifier
from samcli.commands.package.core.formatters import PackageCommandHelpTextFormatter
from samcli.commands.package.core.options import OPTIONS_INFO
from samcli.lib.utils.resources import resources_generator

COL_SIZE_MODIFIER = 38


class PackageCommand(CoreCommand):
"""
`sam` package specific command class that specializes in the visual appearance
of `sam package` help text.
It hosts a custom formatter, examples, table for supported resources, acronyms
and how options are to be used in the CLI for `sam package`.
"""

class CustomFormatterContext(Context):
formatter_class = PackageCommandHelpTextFormatter

context_class = CustomFormatterContext

@staticmethod
def format_examples(ctx: Context, formatter: PackageCommandHelpTextFormatter):
with formatter.indented_section(name="Examples", extra_indents=1):
with formatter.indented_section(name="Automatic resolution of S3 buckets", extra_indents=1):
formatter.write_rd(
[
RowDefinition(
text="\n",
),
RowDefinition(
name=style(f"$ {ctx.command_path} --resolve-s3"),
extra_row_modifiers=[ShowcaseRowModifier()],
),
],
col_max=COL_SIZE_MODIFIER,
)
with formatter.indented_section(name="Get packaged template", extra_indents=1):
formatter.write_rd(
[
RowDefinition(
text="\n",
),
RowDefinition(
name=style(f"$ {ctx.command_path} --resolve-s3 --output-template-file packaged.yaml"),
extra_row_modifiers=[ShowcaseRowModifier()],
),
],
col_max=COL_SIZE_MODIFIER,
)
with formatter.indented_section(name="Customized location for uploading artifacts", extra_indents=1):
formatter.write_rd(
[
RowDefinition(
text="\n",
),
RowDefinition(
name=style(
f"$ {ctx.command_path} --s3-bucket S3_BUCKET --output-template-file packaged.yaml"
),
extra_row_modifiers=[ShowcaseRowModifier()],
),
],
col_max=COL_SIZE_MODIFIER,
)

@staticmethod
def format_table(formatter: PackageCommandHelpTextFormatter):
with formatter.section(name="Supported Resources"):
pass
ctx = click.get_current_context()
table = Table(width=ctx.max_content_width)
table.add_column("Resource")
table.add_column("Location")
for resource, location in resources_generator():
table.add_row(resource, location)
with ctx.obj.console.capture() as capture:
ctx.obj.console.print(table)
formatter.write_rd(
[
RowDefinition(name="\n"),
RowDefinition(name=capture.get()),
],
col_max=COL_SIZE_MODIFIER,
)

@staticmethod
def format_acronyms(formatter: PackageCommandHelpTextFormatter):
with formatter.indented_section(name="Acronyms", extra_indents=1):
formatter.write_rd(
[
RowDefinition(
text="\n",
),
RowDefinition(
name="S3",
text="Simple Storage 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: PackageCommandHelpTextFormatter) -> 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)
PackageCommand.format_examples(ctx, formatter)
PackageCommand.format_table(formatter)
PackageCommand.format_acronyms(formatter)

CoreCommand._format_options(
ctx=ctx,
params=self.get_params(ctx),
formatter=formatter,
formatting_options=OPTIONS_INFO,
write_rd_overrides={"col_max": COL_SIZE_MODIFIER},
)
19 changes: 19 additions & 0 deletions samcli/commands/package/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 PackageCommandHelpTextFormatter(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()]
68 changes: 68 additions & 0 deletions samcli/commands/package/core/options.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
"""
Package Command Options related Datastructures for formatting.
"""
from typing import Dict, List

from samcli.cli.core.options import ALL_COMMON_OPTIONS, add_common_options_info
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] = ["s3_bucket", "resolve_s3"]

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

INFRASTRUCTURE_OPTION_NAMES: List[str] = [
"s3_prefix",
Copy link
Contributor

Choose a reason for hiding this comment

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

How can we make sure these are not duplicated between the command itself and its help text? We could at least think about adding generic unit tests which will do a cross check if certain parameter is defined in one place and missed in other.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

"image_repository",
"image_repositories",
"kms_key_id",
"metadata",
]

DEPLOYMENT_OPTIONS: List[str] = [
"force_upload",
]

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

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

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

OPTIONS_INFO: Dict[str, Dict] = {
"Required Options": {"option_names": {opt: {"rank": idx} for idx, opt in enumerate(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)}
},
"Package Management 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)}},
}
add_common_options_info(OPTIONS_INFO)
6 changes: 6 additions & 0 deletions tests/unit/cli/test_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
from unittest import TestCase
from unittest.mock import patch, ANY

from rich.console import Console

from samcli.cli.context import Context
from samcli.lib.utils.sam_logging import (
SamCliLogger,
Expand All @@ -20,6 +22,10 @@ def test_must_initialize_with_defaults(self):

self.assertEqual(ctx.debug, False, "debug must default to False")

def test_must_have_console(self):
ctx = Context()
self.assertTrue(isinstance(ctx.console, Console))

def test_must_set_get_debug_flag(self):
ctx = Context()

Expand Down
Empty file.
Loading