-
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
Interactive Init #1454
Interactive Init #1454
Changes from 11 commits
45e75b6
33eb157
5aba829
01206f1
911dc86
80f8307
58a6e42
ad09dc3
6cf331b
535544b
f4b528b
b6d7cf6
64b45ea
8993087
bf2f2b3
8c53066
8066ae8
b09929f
694a0be
9eb8167
0378bcc
7438d9f
2900324
c238d82
6df7b0a
c4025a3
d34ea10
7061eb6
7cf2e35
cc3cba0
5053efa
0ec8bb2
7624b20
efcc51f
4b76385
b3594a0
3382135
f8d7977
7255c88
957e802
216b802
8fe22cc
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 |
---|---|---|
|
@@ -2,24 +2,36 @@ | |
""" | ||
Init command to scaffold a project app from a template | ||
""" | ||
import itertools | ||
import json | ||
import logging | ||
import os | ||
import subprocess | ||
|
||
import click | ||
|
||
from samcli.cli.main import pass_context, common_options | ||
from samcli.cli.main import pass_context, common_options, global_cfg | ||
from samcli.commands.exceptions import UserException | ||
from samcli.local.common.runtime_template import RUNTIMES, SUPPORTED_DEP_MANAGERS | ||
from samcli.lib.telemetry.metrics import track_command | ||
from samcli.local.common.runtime_template import INIT_RUNTIMES, SUPPORTED_DEP_MANAGERS, DEFAULT_RUNTIME | ||
from samcli.commands.init.init_generator import do_generate | ||
from samcli.commands.init.init_templates import InitTemplates | ||
from samcli.commands.init.interactive_init_flow import do_interactive | ||
|
||
LOG = logging.getLogger(__name__) | ||
|
||
|
||
@click.command( | ||
"init", short_help="Init an AWS SAM application.", context_settings=dict(help_option_names=[u"-h", u"--help"]) | ||
) | ||
@click.option("-l", "--location", help="Template location (git, mercurial, http(s), zip, path)") | ||
@click.option( | ||
"-r", "--runtime", type=click.Choice(INIT_RUNTIMES), default=DEFAULT_RUNTIME, help="Lambda Runtime of your app" | ||
"--no-interactive", | ||
is_flag=True, | ||
default=False, | ||
help="Disable interactive prompting for values, and fail if any required values are missing.", | ||
) | ||
@click.option("-l", "--location", help="Template location (git, mercurial, http(s), zip, path)") | ||
@click.option("-r", "--runtime", type=click.Choice(RUNTIMES), help="Lambda Runtime of your app") | ||
@click.option( | ||
"-d", | ||
"--dependency-manager", | ||
|
@@ -28,8 +40,12 @@ | |
help="Dependency manager of your Lambda runtime", | ||
required=False, | ||
) | ||
@click.option("-o", "--output-dir", default=".", type=click.Path(), help="Where to output the initialized app into") | ||
@click.option("-n", "--name", default="sam-app", help="Name of your project to be generated as a folder") | ||
@click.option("-o", "--output-dir", type=click.Path(), help="Where to output the initialized app into") | ||
@click.option("-n", "--name", help="Name of your project to be generated as a folder") | ||
@click.option( | ||
"--app-template", | ||
help="Identifier of the managed application template you want to use. If not sure, call 'sam init' without options for an interactive workflow.", | ||
) | ||
@click.option( | ||
"--no-input", | ||
is_flag=True, | ||
|
@@ -39,107 +55,57 @@ | |
@common_options | ||
@pass_context | ||
@track_command | ||
def cli(ctx, location, runtime, dependency_manager, output_dir, name, no_input): | ||
""" \b | ||
Initialize a serverless application with a SAM template, folder | ||
structure for your Lambda functions, connected to an event source such as APIs, | ||
S3 Buckets or DynamoDB Tables. This application includes everything you need to | ||
get started with serverless and eventually grow into a production scale application. | ||
\b | ||
This command can initialize a boilerplate serverless app. If you want to create your own | ||
template as well as use a custom location please take a look at our official documentation. | ||
|
||
\b | ||
Common usage: | ||
|
||
\b | ||
Initializes a new SAM project using Python 3.6 default template runtime | ||
\b | ||
$ sam init --runtime python3.6 | ||
\b | ||
Initializes a new SAM project using Java 8 and Gradle dependency manager | ||
\b | ||
$ sam init --runtime java8 --dependency-manager gradle | ||
\b | ||
Initializes a new SAM project using custom template in a Git/Mercurial repository | ||
\b | ||
# gh being expanded to github url | ||
$ sam init --location gh:aws-samples/cookiecutter-aws-sam-python | ||
\b | ||
$ sam init --location git+ssh://git@github.com/aws-samples/cookiecutter-aws-sam-python.git | ||
\b | ||
$ sam init --location hg+ssh://hg@bitbucket.org/repo/template-name | ||
|
||
\b | ||
Initializes a new SAM project using custom template in a Zipfile | ||
\b | ||
$ sam init --location /path/to/template.zip | ||
\b | ||
$ sam init --location https://example.com/path/to/template.zip | ||
|
||
\b | ||
Initializes a new SAM project using custom template in a local path | ||
\b | ||
$ sam init --location /path/to/template/folder | ||
|
||
""" | ||
# All logic must be implemented in the `do_cli` method. This helps ease unit tests | ||
do_cli(ctx, location, runtime, dependency_manager, output_dir, name, no_input) # pragma: no cover | ||
|
||
|
||
def do_cli(ctx, location, runtime, dependency_manager, output_dir, name, no_input): | ||
""" | ||
Implementation of the ``cli`` method, just separated out for unit testing purposes | ||
""" | ||
from samcli.commands.exceptions import UserException | ||
from samcli.local.init import generate_project | ||
from samcli.local.init.exceptions import GenerateProjectFailedError | ||
|
||
LOG.debug("Init command") | ||
click.secho("[+] Initializing project structure...", fg="green") | ||
|
||
no_build_msg = """ | ||
Project generated: {output_dir}/{name} | ||
|
||
Steps you can take next within the project folder | ||
=================================================== | ||
[*] Invoke Function: sam local invoke HelloWorldFunction --event event.json | ||
[*] Start API Gateway locally: sam local start-api | ||
""".format( | ||
output_dir=output_dir, name=name | ||
) | ||
|
||
build_msg = """ | ||
Project generated: {output_dir}/{name} | ||
|
||
Steps you can take next within the project folder | ||
=================================================== | ||
[*] Install dependencies | ||
[*] Invoke Function: sam local invoke HelloWorldFunction --event event.json | ||
[*] Start API Gateway locally: sam local start-api | ||
""".format( | ||
output_dir=output_dir, name=name | ||
) | ||
|
||
no_build_step_required = ( | ||
"python", | ||
"python3.7", | ||
"python3.6", | ||
"python2.7", | ||
"nodejs", | ||
"nodejs4.3", | ||
"nodejs6.10", | ||
"nodejs8.10", | ||
"nodejs10.x", | ||
"ruby2.5", | ||
) | ||
next_step_msg = no_build_msg if runtime in no_build_step_required else build_msg | ||
|
||
try: | ||
generate_project(location, runtime, dependency_manager, output_dir, name, no_input) | ||
if not location: | ||
click.secho(next_step_msg, bold=True) | ||
click.secho("Read {name}/README.md for further instructions\n".format(name=name), bold=True) | ||
click.secho("[*] Project initialization is now complete", fg="green") | ||
except GenerateProjectFailedError as e: | ||
raise UserException(str(e)) | ||
def cli(ctx, no_interactive, location, runtime, dependency_manager, output_dir, name, app_template, no_input): | ||
do_cli( | ||
ctx, no_interactive, location, runtime, dependency_manager, output_dir, name, app_template, no_input | ||
) # pragma: no cover | ||
|
||
|
||
def do_cli( | ||
ctx, | ||
no_interactive, | ||
location, | ||
runtime, | ||
dependency_manager, | ||
output_dir, | ||
name, | ||
app_template, | ||
no_input, | ||
auto_clone=True, | ||
): | ||
# check for mutually exclusive parameters | ||
if location and app_template: | ||
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. can potentially lift exception cases into decorators on 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. That turned out to be a bit problematic when I tried it, as we don't always move to failure - rather, in some cases where we have missing required parameters we move to interactive, in other cases we fail. It's difficult to lift a partial of this out without making the code look more confusing to me. |
||
msg = """ | ||
You must not provide both the --location and --app-template parameters. | ||
|
||
You can run 'sam init' without any options for an interactive initialization flow, or you can provide one of the following required parameter combinations: | ||
--name and --runtime and --app-template | ||
--location | ||
""" | ||
raise UserException(msg) | ||
# check for required parameters | ||
if location or (name and runtime and dependency_manager and app_template): | ||
# need to turn app_template into a location before we generate | ||
extra_context = None | ||
if app_template: | ||
templates = InitTemplates(no_interactive, auto_clone) | ||
location = templates.location_from_app_template(runtime, dependency_manager, app_template) | ||
no_input = True | ||
extra_context = {"project_name": name, "runtime": runtime} | ||
if not output_dir: | ||
output_dir = "." # default - should I lift this to overall options and seed default? | ||
do_generate(location, runtime, dependency_manager, output_dir, name, no_input, extra_context) | ||
elif no_interactive: | ||
error_msg = """ | ||
ERROR: Missing required parameters, with --no-interactive set. | ||
|
||
Must provide one of the following required parameter combinations: | ||
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. this is not clear on what constitues a combination, I would put valid combinations in a list, so that its easy to grok for the user. 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. There are only two, what format did you suggest? 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. [--name--runtime , --dependency-manager, --app-template] or [--location]. Just having all the |
||
--name and --runtime and --dependency-manager and --app-template | ||
--location | ||
|
||
You can also re-run without the --no-interactive flag to be prompted for required values. | ||
""" | ||
raise UserException(error_msg) | ||
else: | ||
# proceed to interactive state machine, which will call do_generate | ||
do_interactive(location, runtime, dependency_manager, output_dir, name, app_template, no_input) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
""" | ||
Cookiecutter-based generation logic for project templates. | ||
""" | ||
import click | ||
|
||
from samcli.commands.exceptions import UserException | ||
from samcli.local.init import generate_project | ||
from samcli.local.init.exceptions import GenerateProjectFailedError | ||
|
||
|
||
def do_generate(location, runtime, dependency_manager, output_dir, name, no_input, extra_context): | ||
no_build_msg = """ | ||
Project generated: {output_dir}/{name} | ||
|
||
Steps you can take next within the project folder | ||
=================================================== | ||
[*] Invoke Function: sam local invoke HelloWorldFunction --event event.json | ||
[*] Start API Gateway locally: sam local start-api | ||
""".format( | ||
output_dir=output_dir, name=name | ||
) | ||
|
||
build_msg = """ | ||
Project generated: {output_dir}/{name} | ||
|
||
Steps you can take next within the project folder | ||
=================================================== | ||
[*] Install dependencies | ||
[*] Invoke Function: sam local invoke HelloWorldFunction --event event.json | ||
[*] Start API Gateway locally: sam local start-api | ||
""".format( | ||
output_dir=output_dir, name=name | ||
) | ||
|
||
no_build_step_required = ( | ||
"python", | ||
"python3.7", | ||
"python3.6", | ||
"python2.7", | ||
"nodejs", | ||
"nodejs4.3", | ||
"nodejs6.10", | ||
"nodejs8.10", | ||
"nodejs10.x", | ||
"ruby2.5", | ||
) | ||
next_step_msg = no_build_msg if runtime in no_build_step_required else build_msg | ||
try: | ||
generate_project(location, runtime, dependency_manager, output_dir, name, no_input, extra_context) | ||
if not location: | ||
click.secho(next_step_msg, bold=True) | ||
click.secho("Read {name}/README.md for further instructions\n".format(name=name), bold=True) | ||
click.secho("[*] Project initialization is now complete", fg="green") | ||
except GenerateProjectFailedError as e: | ||
raise UserException(str(e)) |
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.
Some of these imports should be pushed down into the
do_cli
function. This is to keep--help
text load time low, otherwise we will be loading all of these modules.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.
Which imports?
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.
This seems to create other problems, we can discuss in private.