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
46 changes: 41 additions & 5 deletions samcli/commands/init/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,12 @@
Init command to scaffold a project app from a template
"""
import logging
import json
from json import JSONDecodeError

import click

from samcli.commands.exceptions import UserException
from samcli.cli.main import pass_context, common_options, global_cfg
from samcli.local.common.runtime_template import RUNTIMES, SUPPORTED_DEP_MANAGERS
from samcli.lib.telemetry.metrics import track_command
Expand Down Expand Up @@ -86,12 +89,32 @@
default=False,
help="Disable Cookiecutter prompting and accept default values defined template config",
)
@click.option(
"--extra_context",
default=None,
help="Override any custom parameters in the template's cookiecutter.json configuration e.g. "
""
'{"customParam1": "customValue1", "customParam2":"customValue2"}'
""" """,
required=False,
)
@common_options
@pass_context
@track_command
def cli(ctx, no_interactive, location, runtime, dependency_manager, output_dir, name, app_template, no_input):
def cli(
ctx, no_interactive, location, runtime, dependency_manager, output_dir, name, app_template, no_input, extra_context
):
do_cli(
ctx, no_interactive, location, runtime, dependency_manager, output_dir, name, app_template, no_input
ctx,
no_interactive,
location,
runtime,
dependency_manager,
output_dir,
name,
app_template,
no_input,
extra_context,
) # pragma: no cover


Expand All @@ -106,9 +129,9 @@ def do_cli(
name,
app_template,
no_input,
extra_context,
auto_clone=True,
):
from samcli.commands.exceptions import UserException
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
Expand All @@ -126,12 +149,15 @@ def do_cli(
# 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}
default_context = {"project_name": name, "runtime": runtime}
if extra_context is None:
extra_context = default_context
else:
extra_context = _merge_extra_context(default_context, extra_context)
if not output_dir:
output_dir = "."
do_generate(location, runtime, dependency_manager, output_dir, name, no_input, extra_context)
Expand All @@ -149,3 +175,13 @@ def do_cli(
else:
# proceed to interactive state machine, which will call do_generate
do_interactive(location, runtime, dependency_manager, output_dir, name, app_template, no_input)


def _merge_extra_context(default_context, extra_context):
try:
extra_context_dict = json.loads(extra_context)
except JSONDecodeError:
raise UserException(
"Parse error reading the --extra-content parameter. The value of this parameter must be valid JSON."
)
return {**extra_context_dict, **default_context}
26 changes: 26 additions & 0 deletions tests/integration/init/test_init_command.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,32 @@ def test_init_command_java_gradle(self):
self.assertEqual(return_code, 0)
self.assertTrue(os.path.isdir(temp + "/sam-app-gradle"))

def test_init_command_with_extra_context_parameter(self):
with tempfile.TemporaryDirectory() as temp:
process = Popen(
[
TestBasicInitCommand._get_command(),
"init",
"--runtime",
"java8",
"--dependency-manager",
"maven",
"--app-template",
"hello-world",
"--name",
"sam-app-maven",
"--no-interactive",
"--extra_context",
'{"schema_name": "codedeploy", "schema_type": "aws"}',
"-o",
temp,
]
)
return_code = process.wait()

self.assertEqual(return_code, 0)
self.assertTrue(os.path.isdir(temp + "/sam-app-maven"))

@staticmethod
def _get_command():
command = "sam"
Expand Down
112 changes: 109 additions & 3 deletions tests/unit/commands/init/test_cli.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
from unittest import TestCase
from unittest.mock import patch, ANY

import click
from click.testing import CliRunner

from samcli.commands.init import cli as init_cmd
Expand All @@ -21,7 +20,8 @@ def setUp(self):
self.name = "testing project"
self.app_template = "hello-world"
self.no_input = False
self.extra_context = {"project_name": "testing project", "runtime": "python3.6"}
self.extra_context = '{"project_name": "testing project", "runtime": "python3.6"}'
self.extra_context_as_json = {"project_name": "testing project", "runtime": "python3.6"}

@patch("samcli.commands.init.init_templates.InitTemplates._shared_dir_check")
@patch("samcli.commands.init.init_generator.generate_project")
Expand All @@ -38,6 +38,7 @@ def test_init_cli(self, generate_project_patch, sd_mock):
name=self.name,
app_template=self.app_template,
no_input=self.no_input,
extra_context=None,
auto_clone=False,
)

Expand All @@ -50,7 +51,7 @@ def test_init_cli(self, generate_project_patch, sd_mock):
self.output_dir,
self.name,
True,
self.extra_context,
self.extra_context_as_json,
)

@patch("samcli.commands.init.init_templates.InitTemplates._shared_dir_check")
Expand All @@ -68,6 +69,7 @@ def test_init_fails_invalid_template(self, sd_mock):
name=self.name,
app_template="wrong-and-bad",
no_input=self.no_input,
extra_context=None,
auto_clone=False,
)

Expand All @@ -86,6 +88,7 @@ def test_init_fails_invalid_dep_mgr(self, sd_mock):
name=self.name,
app_template=self.app_template,
no_input=self.no_input,
extra_context=None,
auto_clone=False,
)

Expand Down Expand Up @@ -224,6 +227,7 @@ def test_init_cli_missing_params_fails(self):
name=None,
app_template=None,
no_input=True,
extra_context=None,
auto_clone=False,
)

Expand All @@ -241,6 +245,7 @@ def test_init_cli_mutually_exclusive_params_fails(self):
name=self.name,
app_template="fails-anyways",
no_input=self.no_input,
extra_context=None,
auto_clone=False,
)

Expand All @@ -266,9 +271,110 @@ def test_init_cli_generate_project_fails(self, generate_project_patch, sd_mock):
name=self.name,
app_template=None,
no_input=self.no_input,
extra_context=None,
auto_clone=False,
)

generate_project_patch.assert_called_with(
self.location, self.runtime, self.dependency_manager, self.output_dir, self.name, self.no_input
)

@patch("samcli.commands.init.init_generator.generate_project")
def test_init_cli_with_extra_context_parameter_not_passed(self, generate_project_patch):
# GIVEN generate_project successfully created a project
# WHEN a project name has been passed
init_cli(
ctx=self.ctx,
no_interactive=self.no_interactive,
location=self.location,
runtime=self.runtime,
dependency_manager=self.dependency_manager,
output_dir=self.output_dir,
name=self.name,
app_template=self.app_template,
no_input=self.no_input,
extra_context=None,
auto_clone=False,
)

# THEN we should receive no errors
generate_project_patch.assert_called_once_with(
ANY, self.runtime, self.dependency_manager, ".", self.name, True, self.extra_context_as_json
)

@patch("samcli.commands.init.init_generator.generate_project")
def test_init_cli_with_extra_context_parameter_passed(self, generate_project_patch):
# GIVEN generate_project successfully created a project
# WHEN a project name has been passed
init_cli(
ctx=self.ctx,
no_interactive=self.no_interactive,
location=self.location,
runtime=self.runtime,
dependency_manager=self.dependency_manager,
output_dir=self.output_dir,
name=self.name,
app_template=self.app_template,
no_input=self.no_input,
extra_context='{"schema_name":"events", "schema_type":"aws"}',
auto_clone=False,
)

# THEN we should receive no errors
generate_project_patch.assert_called_once_with(
ANY,
self.runtime,
self.dependency_manager,
".",
self.name,
True,
{"project_name": "testing project", "runtime": "python3.6", "schema_name": "events", "schema_type": "aws"},
)

@patch("samcli.commands.init.init_generator.generate_project")
def test_init_cli_with_extra_context_not_overriding_default_parameter(self, generate_project_patch):
# GIVEN generate_project successfully created a project
# WHEN a project name has been passed
init_cli(
ctx=self.ctx,
no_interactive=self.no_interactive,
location=self.location,
runtime=self.runtime,
dependency_manager=self.dependency_manager,
output_dir=self.output_dir,
name=self.name,
app_template=self.app_template,
no_input=self.no_input,
extra_context='{"project_name": "my_project", "runtime": "java8", "schema_name":"events", "schema_type": "aws"}',
auto_clone=False,
)

# THEN we should receive no errors
generate_project_patch.assert_called_once_with(
ANY,
self.runtime,
self.dependency_manager,
".",
self.name,
True,
{"project_name": "testing project", "runtime": "python3.6", "schema_name": "events", "schema_type": "aws"},
)
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we add some invalid input tests to confirm proper behavior?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

added test


@patch("samcli.commands.init.init_generator.generate_project")
def test_init_cli_with_extra_context_input_as_wrong_json_raises_exception(self, generate_project_patch):
# GIVEN generate_project successfully created a project
# WHEN a project name has been passed
with self.assertRaises(UserException):
init_cli(
ctx=self.ctx,
no_interactive=self.no_interactive,
location=self.location,
runtime=self.runtime,
dependency_manager=self.dependency_manager,
output_dir=self.output_dir,
name=self.name,
app_template=self.app_template,
no_input=self.no_input,
extra_context='{"project_name", "my_project", "runtime": "java8", "schema_name":"events", "schema_type": "aws"}',
auto_clone=False,
)