Skip to content
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

Merged
merged 42 commits into from
Oct 29, 2019
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
45e75b6
WIP: Interactive Init Command
awood45 Sep 13, 2019
33eb157
WIP: Mutually Exclusive and Required Params
awood45 Sep 24, 2019
5aba829
WIP: Stubbed Out Interactive Flow
awood45 Sep 27, 2019
01206f1
WIP: Working Flow
awood45 Oct 2, 2019
911dc86
WIP: Working Init Flow
awood45 Oct 7, 2019
80f8307
WIP: Existing Init Unit Tests Pass
awood45 Oct 7, 2019
58a6e42
WIP: Clean Up Lint Warnings, Add Go Dep Manager
awood45 Oct 8, 2019
ad09dc3
Linting Improvements
awood45 Oct 9, 2019
6cf331b
Merge branch 'develop' into interactive-init
awood45 Oct 9, 2019
535544b
Add Docstrings, Fix Branch Indentation
awood45 Oct 9, 2019
f4b528b
Merge branch 'interactive-init' of github.com:awood45/aws-sam-cli int…
awood45 Oct 9, 2019
b6d7cf6
Remove Planning Comment
awood45 Oct 9, 2019
64b45ea
Check for Multiple Git Executables
awood45 Oct 10, 2019
8993087
Add Click Command Unit Tests for Init
awood45 Oct 10, 2019
bf2f2b3
Fix _git_executable Function
awood45 Oct 10, 2019
8c53066
Fix Git Templates Function Call
awood45 Oct 10, 2019
8066ae8
Use Filter for Template Selection
awood45 Oct 15, 2019
b09929f
Unit Test the Template Class
awood45 Oct 15, 2019
694a0be
Additional Test Coverage
awood45 Oct 15, 2019
9eb8167
Remove Unused Variable
awood45 Oct 15, 2019
0378bcc
Address Init PR Comments
awood45 Oct 15, 2019
7438d9f
Update Init Integ Tests
awood45 Oct 17, 2019
2900324
Run of Black Reformatting
awood45 Oct 23, 2019
c238d82
Merge branch 'develop' into interactive-init
awood45 Oct 23, 2019
6df7b0a
Merge develop into interactive-init
awood45 Oct 28, 2019
c4025a3
Improvements to fix linter issues in init
awood45 Oct 28, 2019
d34ea10
Update Mock Import
awood45 Oct 28, 2019
7061eb6
Try batch file for git
awood45 Oct 28, 2019
7cf2e35
Make Unit Test for Init Windows-Compatible
awood45 Oct 28, 2019
cc3cba0
Switch execname order
awood45 Oct 28, 2019
5053efa
Catch exceptions when checking git executables
awood45 Oct 28, 2019
0ec8bb2
Add explicit exception for missing git
awood45 Oct 28, 2019
7624b20
Add git executable unit test
awood45 Oct 28, 2019
efcc51f
Add git executable check
awood45 Oct 28, 2019
4b76385
Add Exception Info when Git Fails
awood45 Oct 28, 2019
b3594a0
Fix Warning Method
awood45 Oct 28, 2019
3382135
Run git in Appveyor test stage
awood45 Oct 29, 2019
f8d7977
WIP: Create Shared Directory
awood45 Oct 29, 2019
7255c88
Create Shared Dir if Necessary
awood45 Oct 29, 2019
957e802
Add Shared Dir Unit Tests
awood45 Oct 29, 2019
216b802
Add One More Test
awood45 Oct 29, 2019
8fe22cc
Move Local Imports into do_cli
awood45 Oct 29, 2019
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
186 changes: 76 additions & 110 deletions samcli/commands/init/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Copy link
Contributor

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.

Copy link
Member Author

Choose a reason for hiding this comment

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

Which imports?

Copy link
Member Author

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.

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",
Expand All @@ -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,
Expand All @@ -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:
Copy link
Contributor

Choose a reason for hiding this comment

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

can potentially lift exception cases into decorators on do_cli

Copy link
Member Author

Choose a reason for hiding this comment

The 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:
Copy link
Contributor

Choose a reason for hiding this comment

The 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.

Copy link
Member Author

Choose a reason for hiding this comment

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

There are only two, what format did you suggest?

Copy link
Contributor

Choose a reason for hiding this comment

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

[--name--runtime , --dependency-manager, --app-template] or [--location]. Just having all the ands together in --name and --runtime and --dependency-manager and --app-template made it difficult for me to spot the combination.

--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)
55 changes: 55 additions & 0 deletions samcli/commands/init/init_generator.py
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))
Loading