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
19 changes: 10 additions & 9 deletions samcli/commands/init/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ def wrapped(*args, **kwargs):
default=None,
help="Lambda Image of your app",
cls=Mutex,
not_required=["location", "app_template", "runtime"],
not_required=["location", "runtime"],
)
@click.option(
"-d",
Expand All @@ -182,7 +182,7 @@ def wrapped(*args, **kwargs):
help="Identifier of the managed application template you want to use. "
"If not sure, call 'sam init' without options for an interactive workflow.",
cls=Mutex,
not_required=["location", "base_image"],
not_required=["location"],
)
@click.option(
"--no-input",
Expand Down Expand Up @@ -277,13 +277,14 @@ def do_cli(
if package_type == IMAGE and image_bool:
base_image, runtime = _get_runtime_from_image(base_image)
Copy link
Contributor

Choose a reason for hiding this comment

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

are we now adding a requirement on both base image and app template always needing to be specified. Am I summarizing that correctly.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We are not changing the current working experience, for example, running
sam init --name testProject --dependency-manager bundler --package-type Image --base-image amazon/ruby2.7-base
will behave exactly as before.

The change affects the broken experience only (broke due to multiple managed project template instead of one for the given base-image like python3.8). Running
sam init --name testProject --dependency-manager pip --package-type Image --base-image amazon/python3.8-base
used to raise an exception

Error: Multiple lambda image application templates found. This should not be possible, please raise an issue.

Now it will raise

Error: Multiple lambda image application templates found. Please specify one using the --app-template parameter.

and the user can fix it by running
sam init --name testProject --dependency-manager pip --package-type Image --base-image amazon/python3.8-base --app-template hello-world-lambda-image

options = templates.init_options(package_type, runtime, base_image, dependency_manager)
if len(options) == 1:
app_template = options[0].get("appTemplate")
elif len(options) > 1:
raise LambdaImagesTemplateException(
"Multiple lambda image application templates found. "
"This should not be possible, please raise an issue."
)
if not app_template:
if len(options) == 1:
app_template = options[0].get("appTemplate")
elif len(options) > 1:
raise LambdaImagesTemplateException(
"Multiple lambda image application templates found. "
"Please specify one using the --app-template parameter."
)

if app_template and not location:
location = templates.location_from_app_template(
Expand Down
2 changes: 1 addition & 1 deletion samcli/commands/init/init_templates.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ def location_from_app_template(self, package_type, runtime, base_image, dependen
if template.get("init_location") is not None:
return template["init_location"]
if template.get("directory") is not None:
return os.path.join(self._git_repo.local_path, template["directory"])
return os.path.normpath(os.path.join(self._git_repo.local_path, template["directory"]))
raise InvalidInitTemplateError("Invalid template. This should not be possible, please raise an issue.")
except StopIteration as ex:
msg = "Can't find application template " + app_template + " - check valid values in interactive init."
Expand Down
251 changes: 250 additions & 1 deletion tests/unit/commands/init/test_cli.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import os
from pathlib import Path
from unittest import TestCase
from unittest.mock import patch, ANY
Expand All @@ -9,7 +10,7 @@
from samcli.commands.exceptions import UserException
from samcli.commands.init import cli as init_cmd
from samcli.commands.init import do_cli as init_cli
from samcli.commands.init.init_templates import InitTemplates, APP_TEMPLATES_REPO_URL, APP_TEMPLATES_REPO_NAME
from samcli.commands.init.init_templates import InitTemplates, APP_TEMPLATES_REPO_URL
from samcli.lib.init import GenerateProjectFailedError
from samcli.lib.utils.git_repo import GitRepo
from samcli.lib.utils.packagetype import IMAGE, ZIP
Expand Down Expand Up @@ -1101,3 +1102,251 @@ def test_init_cli_no_package_type(self, generate_project_patch, cd_mock):
True,
ANY,
)

@patch.object(InitTemplates, "__init__", MockInitTemplates.__init__)
@patch("samcli.commands.init.init_templates.InitTemplates._init_options_from_manifest")
def test_init_cli_image_pool_with_base_image_having_multiple_managed_template_but_no_app_template_provided(
self,
init_options_from_manifest_mock,
):
init_options_from_manifest_mock.return_value = [
{
"directory": "python3.8-image/cookiecutter-aws-sam-hello-python-lambda-image",
"displayName": "Hello World Lambda Image Example",
"dependencyManager": "pip",
"appTemplate": "hello-world-lambda-image",
"packageType": "Image",
},
{
"directory": "python3.8-image/cookiecutter-ml-apigw-pytorch",
"displayName": "PyTorch Machine Learning Inference API",
"dependencyManager": "pip",
"appTemplate": "ml-apigw-pytorch",
"packageType": "Image",
},
]
with self.assertRaises(UserException):
init_cli(
ctx=self.ctx,
no_interactive=self.no_interactive,
pt_explicit=self.pt_explicit,
package_type="Image",
base_image="amazon/python3.8-base",
dependency_manager="pip",
app_template=None,
name=self.name,
output_dir=self.output_dir,
location=None,
runtime=None,
no_input=self.no_input,
extra_context=self.extra_context,
)

@patch.object(InitTemplates, "__init__", MockInitTemplates.__init__)
@patch("samcli.commands.init.init_templates.InitTemplates._init_options_from_manifest")
def test_init_cli_image_pool_with_base_image_having_multiple_managed_template_and_provided_app_template_not_matching_any_managed_templates(
self,
init_options_from_manifest_mock,
):
init_options_from_manifest_mock.return_value = [
{
"directory": "python3.8-image/cookiecutter-aws-sam-hello-python-lambda-image",
"displayName": "Hello World Lambda Image Example",
"dependencyManager": "pip",
"appTemplate": "hello-world-lambda-image",
"packageType": "Image",
},
{
"directory": "python3.8-image/cookiecutter-ml-apigw-pytorch",
"displayName": "PyTorch Machine Learning Inference API",
"dependencyManager": "pip",
"appTemplate": "ml-apigw-pytorch",
"packageType": "Image",
},
]
with self.assertRaises(UserException):
init_cli(
ctx=self.ctx,
no_interactive=self.no_interactive,
pt_explicit=self.pt_explicit,
package_type="Image",
base_image="amazon/python3.8-base",
dependency_manager="pip",
app_template="Not-ml-apigw-pytorch", # different value than appTemplates shown in the manifest above
name=self.name,
output_dir=self.output_dir,
location=None,
runtime=None,
no_input=self.no_input,
extra_context=self.extra_context,
)

@patch.object(InitTemplates, "__init__", MockInitTemplates.__init__)
@patch("samcli.commands.init.init_templates.InitTemplates._init_options_from_manifest")
@patch("samcli.commands.init.init_generator.generate_project")
def test_init_cli_image_pool_with_base_image_having_multiple_managed_template_with_matching_app_template_provided(
self,
generate_project_patch,
init_options_from_manifest_mock,
):
init_options_from_manifest_mock.return_value = [
{
"directory": "python3.8-image/cookiecutter-aws-sam-hello-python-lambda-image",
"displayName": "Hello World Lambda Image Example",
"dependencyManager": "pip",
"appTemplate": "hello-world-lambda-image",
"packageType": "Image",
},
{
"directory": "python3.8-image/cookiecutter-ml-apigw-pytorch",
"displayName": "PyTorch Machine Learning Inference API",
"dependencyManager": "pip",
"appTemplate": "ml-apigw-pytorch",
"packageType": "Image",
},
]
init_cli(
ctx=self.ctx,
no_interactive=True,
pt_explicit=True,
package_type="Image",
base_image="amazon/python3.8-base",
dependency_manager="pip",
app_template="ml-apigw-pytorch", # same value as one appTemplate in the manifest above
name=self.name,
output_dir=None,
location=None,
runtime=None,
no_input=None,
extra_context=None,
)
generate_project_patch.assert_called_once_with(
os.path.normpath("repository/python3.8-image/cookiecutter-ml-apigw-pytorch"), # location
"Image", # package_type
"python3.8", # runtime
"pip", # dependency_manager
self.output_dir,
self.name,
True, # no_input
ANY,
)

@patch.object(InitTemplates, "__init__", MockInitTemplates.__init__)
@patch("samcli.commands.init.init_templates.InitTemplates._init_options_from_manifest")
@patch("samcli.commands.init.init_generator.generate_project")
def test_init_cli_image_pool_with_base_image_having_one_managed_template_does_not_need_app_template_argument(
self,
generate_project_patch,
init_options_from_manifest_mock,
):
init_options_from_manifest_mock.return_value = [
{
"directory": "python3.8-image/cookiecutter-ml-apigw-pytorch",
"displayName": "PyTorch Machine Learning Inference API",
"dependencyManager": "pip",
"appTemplate": "ml-apigw-pytorch",
"packageType": "Image",
},
]
init_cli(
ctx=self.ctx,
no_interactive=True,
pt_explicit=True,
package_type="Image",
base_image="amazon/python3.8-base",
dependency_manager="pip",
app_template=None,
name=self.name,
output_dir=None,
location=None,
runtime=None,
no_input=None,
extra_context=None,
)
generate_project_patch.assert_called_once_with(
os.path.normpath("repository/python3.8-image/cookiecutter-ml-apigw-pytorch"), # location
"Image", # package_type
"python3.8", # runtime
"pip", # dependency_manager
self.output_dir,
self.name,
True, # no_input
ANY,
)

@patch.object(InitTemplates, "__init__", MockInitTemplates.__init__)
@patch("samcli.commands.init.init_templates.InitTemplates._init_options_from_manifest")
@patch("samcli.commands.init.init_generator.generate_project")
def test_init_cli_image_pool_with_base_image_having_one_managed_template_with_provided_app_template_matching_the_managed_template(
self,
generate_project_patch,
init_options_from_manifest_mock,
):
init_options_from_manifest_mock.return_value = [
{
"directory": "python3.8-image/cookiecutter-ml-apigw-pytorch",
"displayName": "PyTorch Machine Learning Inference API",
"dependencyManager": "pip",
"appTemplate": "ml-apigw-pytorch",
"packageType": "Image",
},
]
init_cli(
ctx=self.ctx,
no_interactive=True,
pt_explicit=True,
package_type="Image",
base_image="amazon/python3.8-base",
dependency_manager="pip",
app_template="ml-apigw-pytorch", # same value as appTemplate indicated in the manifest above
name=self.name,
output_dir=None,
location=None,
runtime=None,
no_input=None,
extra_context=None,
)
generate_project_patch.assert_called_once_with(
os.path.normpath("repository/python3.8-image/cookiecutter-ml-apigw-pytorch"), # location
"Image", # package_type
"python3.8", # runtime
"pip", # dependency_manager
self.output_dir,
self.name,
True, # no_input
ANY,
)

@patch.object(InitTemplates, "__init__", MockInitTemplates.__init__)
@patch("samcli.commands.init.init_templates.InitTemplates._init_options_from_manifest")
@patch("samcli.commands.init.init_generator.generate_project")
def test_init_cli_image_pool_with_base_image_having_one_managed_template_with_provided_app_template_not_matching_the_managed_template(
self,
generate_project_patch,
init_options_from_manifest_mock,
):
init_options_from_manifest_mock.return_value = [
{
"directory": "python3.8-image/cookiecutter-ml-apigw-pytorch",
"displayName": "PyTorch Machine Learning Inference API",
"dependencyManager": "pip",
"appTemplate": "ml-apigw-pytorch",
"packageType": "Image",
},
]
with (self.assertRaises(UserException)):
init_cli(
ctx=self.ctx,
no_interactive=True,
pt_explicit=True,
package_type="Image",
base_image="amazon/python3.8-base",
dependency_manager="pip",
app_template="NOT-ml-apigw-pytorch", # different value than appTemplate shown in the manifest above
name=self.name,
output_dir=None,
location=None,
runtime=None,
no_input=None,
extra_context=None,
)