-
Notifications
You must be signed in to change notification settings - Fork 1.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: sam remote invoke help text and UX fixes #5366
Changes from all commits
d6399a1
3e8a3a7
1d48a0d
afbb3c5
beaa091
e821aa7
9ce5402
9374c42
96661ae
cc301b1
372856d
da7ff17
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,158 @@ | ||
""" | ||
Invoke Command Class. | ||
""" | ||
import json | ||
|
||
from click import Context, style | ||
|
||
from samcli.cli.core.command import CoreCommand | ||
from samcli.cli.row_modifiers import RowDefinition, ShowcaseRowModifier | ||
from samcli.commands.remote.invoke.core.formatters import RemoteInvokeCommandHelpTextFormatter | ||
from samcli.commands.remote.invoke.core.options import OPTIONS_INFO | ||
|
||
|
||
class RemoteInvokeCommand(CoreCommand): | ||
class CustomFormatterContext(Context): | ||
formatter_class = RemoteInvokeCommandHelpTextFormatter | ||
|
||
context_class = CustomFormatterContext | ||
|
||
@staticmethod | ||
def format_examples(ctx: Context, formatter: RemoteInvokeCommandHelpTextFormatter): | ||
with formatter.indented_section(name="Examples", extra_indents=1): | ||
with formatter.indented_section(name="Invoke default lambda function with empty event", extra_indents=1): | ||
formatter.write_rd( | ||
[ | ||
RowDefinition( | ||
text="\n", | ||
), | ||
RowDefinition( | ||
name=style(f"${ctx.command_path} --stack-name hello-world"), | ||
extra_row_modifiers=[ShowcaseRowModifier()], | ||
), | ||
] | ||
) | ||
with formatter.indented_section( | ||
name="Invoke default lambda function with event passed as text input", extra_indents=1 | ||
): | ||
formatter.write_rd( | ||
[ | ||
RowDefinition( | ||
text="\n", | ||
), | ||
RowDefinition( | ||
name=style( | ||
f"${ctx.command_path} --stack-name hello-world -e '{json.dumps({'message':'hello!'})}'" | ||
), | ||
extra_row_modifiers=[ShowcaseRowModifier()], | ||
), | ||
] | ||
) | ||
with formatter.indented_section(name="Invoke named lambda function with an event file", extra_indents=1): | ||
formatter.write_rd( | ||
[ | ||
RowDefinition( | ||
text="\n", | ||
), | ||
RowDefinition( | ||
name=style( | ||
f"${ctx.command_path} --stack-name " | ||
f"hello-world HelloWorldFunction --event-file event.json" | ||
), | ||
extra_row_modifiers=[ShowcaseRowModifier()], | ||
), | ||
] | ||
) | ||
with formatter.indented_section(name="Invoke lambda function with event as stdin input", extra_indents=1): | ||
formatter.write_rd( | ||
[ | ||
RowDefinition( | ||
text="\n", | ||
), | ||
RowDefinition( | ||
name=style( | ||
f"$ echo '{json.dumps({'message':'hello!'})}' | " | ||
f"{ctx.command_path} HelloWorldFunction --event-file -" | ||
), | ||
extra_row_modifiers=[ShowcaseRowModifier()], | ||
), | ||
] | ||
) | ||
with formatter.indented_section( | ||
name="Invoke lambda function using lambda ARN and get the full AWS API response", extra_indents=1 | ||
): | ||
formatter.write_rd( | ||
[ | ||
RowDefinition( | ||
text="\n", | ||
), | ||
RowDefinition( | ||
name=style( | ||
f"${ctx.command_path} arn:aws:lambda:us-west-2:123456789012:function:my-function -e <>" | ||
f" --output json" | ||
), | ||
extra_row_modifiers=[ShowcaseRowModifier()], | ||
), | ||
] | ||
) | ||
with formatter.indented_section( | ||
name="Asynchronously invoke lambda function with additional boto parameters", extra_indents=1 | ||
): | ||
formatter.write_rd( | ||
[ | ||
RowDefinition( | ||
text="\n", | ||
), | ||
RowDefinition( | ||
name=style( | ||
f"${ctx.command_path} HelloWorldFunction -e <> " | ||
f"--parameter InvocationType=Event --parameter Qualifier=MyQualifier" | ||
), | ||
extra_row_modifiers=[ShowcaseRowModifier()], | ||
), | ||
] | ||
) | ||
with formatter.indented_section( | ||
name="Dry invoke a lambda function to validate parameter values and user/role permissions", | ||
extra_indents=1, | ||
): | ||
formatter.write_rd( | ||
[ | ||
RowDefinition( | ||
text="\n", | ||
), | ||
RowDefinition( | ||
name=style( | ||
f"${ctx.command_path} HelloWorldFunction -e <> --output json " | ||
f"--parameter InvocationType=DryRun" | ||
), | ||
extra_row_modifiers=[ShowcaseRowModifier()], | ||
), | ||
] | ||
) | ||
|
||
@staticmethod | ||
def format_acronyms(formatter: RemoteInvokeCommandHelpTextFormatter): | ||
with formatter.indented_section(name="Acronyms", extra_indents=1): | ||
formatter.write_rd( | ||
[ | ||
RowDefinition( | ||
name="ARN", | ||
text="Amazon Resource Name", | ||
extra_row_modifiers=[ShowcaseRowModifier()], | ||
), | ||
] | ||
) | ||
|
||
def format_options(self, ctx: Context, formatter: RemoteInvokeCommandHelpTextFormatter) -> None: # type:ignore | ||
# NOTE: `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) | ||
RemoteInvokeCommand.format_examples(ctx, formatter) | ||
RemoteInvokeCommand.format_acronyms(formatter) | ||
|
||
CoreCommand._format_options( | ||
ctx=ctx, params=self.get_params(ctx), formatter=formatter, formatting_options=OPTIONS_INFO | ||
) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
""" | ||
Remote Invoke Command Formatter. | ||
""" | ||
from samcli.cli.formatters import RootCommandHelpTextFormatter | ||
from samcli.cli.row_modifiers import BaseLineRowModifier | ||
from samcli.commands.remote.invoke.core.options import ALL_OPTIONS | ||
|
||
|
||
class RemoteInvokeCommandHelpTextFormatter(RootCommandHelpTextFormatter): | ||
ADDITIVE_JUSTIFICATION = 17 | ||
|
||
def __init__(self, *args, **kwargs): | ||
super().__init__(*args, **kwargs) | ||
# NOTE: 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()] |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
""" | ||
Remote Invoke 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 | ||
|
||
# NOTE: The ordering of the option lists matter, they are the order | ||
# in which options will be displayed. | ||
|
||
INFRASTRUCTURE_OPTION_NAMES: List[str] = ["stack_name"] | ||
|
||
INPUT_EVENT_OPTIONS: List[str] = ["event", "event_file"] | ||
|
||
ADDITIONAL_OPTIONS: List[str] = ["parameter", "output"] | ||
|
||
AWS_CREDENTIAL_OPTION_NAMES: List[str] = ["region", "profile"] | ||
|
||
CONFIGURATION_OPTION_NAMES: List[str] = ["config_env", "config_file"] | ||
|
||
OTHER_OPTIONS: List[str] = ["debug"] | ||
|
||
ALL_OPTIONS: List[str] = ( | ||
INFRASTRUCTURE_OPTION_NAMES | ||
+ INPUT_EVENT_OPTIONS | ||
+ ADDITIONAL_OPTIONS | ||
+ AWS_CREDENTIAL_OPTION_NAMES | ||
+ CONFIGURATION_OPTION_NAMES | ||
+ ALL_COMMON_OPTIONS | ||
) | ||
|
||
OPTIONS_INFO: Dict[str, Dict] = { | ||
"Infrastructure Options": { | ||
"option_names": {opt: {"rank": idx} for idx, opt in enumerate(INFRASTRUCTURE_OPTION_NAMES)} | ||
}, | ||
"Input Event Options": {"option_names": {opt: {"rank": idx} for idx, opt in enumerate(INPUT_EVENT_OPTIONS)}}, | ||
"Additional Options": {"option_names": {opt: {"rank": idx} for idx, opt in enumerate(ADDITIONAL_OPTIONS)}}, | ||
"AWS Credential Options": { | ||
"option_names": {opt: {"rank": idx} for idx, opt in enumerate(AWS_CREDENTIAL_OPTION_NAMES)} | ||
}, | ||
"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. " | ||
), | ||
], | ||
}, | ||
} | ||
|
||
add_common_options_info(OPTIONS_INFO) |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,8 +4,6 @@ | |
import logging | ||
import sys | ||
from functools import wraps | ||
from io import TextIOWrapper | ||
from typing import cast | ||
|
||
import click | ||
|
||
|
@@ -17,7 +15,7 @@ | |
def event_and_event_file_options_validation(func): | ||
""" | ||
This function validates the cases when both --event and --event-file are provided and | ||
neither option is provided | ||
logs if "-" is provided for --event-file and event is read from stdin. | ||
|
||
Parameters | ||
---------- | ||
|
@@ -48,10 +46,9 @@ def wrapped(*args, **kwargs): | |
|
||
validator.validate() | ||
|
||
# if no event nor event_file arguments are given, read from stdin | ||
if not event and not event_file: | ||
LOG.debug("Neither --event nor --event-file options have been provided, reading from stdin") | ||
kwargs["event_file"] = cast(TextIOWrapper, sys.stdin) | ||
# If "-" is provided for --event-file, click uses it as a special file to refer to stdin. | ||
if event_file and event_file.fileno() == sys.stdin.fileno(): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. just checking: is this the same behavior as local? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, |
||
LOG.info("Reading event from stdin (you can also pass it from file with --event-file)") | ||
return func(*args, **kwargs) | ||
|
||
return wrapped | ||
|
@@ -83,7 +80,7 @@ def wrapped(*args, **kwargs): | |
exception=click.BadOptionUsage( | ||
option_name="--resource-id", | ||
ctx=ctx, | ||
message="Atleast 1 of --stack-name or --resource-id parameters should be provided.", | ||
message="At least 1 of --stack-name or --resource-id parameters should be provided.", | ||
), | ||
) | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not specific to this PR, I don't really don't like this as we need to keep parameter names in multiple places :(
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can consolidate for sure!