diff --git a/.gitignore b/.gitignore index 5389aa8947..524356dc67 100644 --- a/.gitignore +++ b/.gitignore @@ -380,5 +380,7 @@ $RECYCLE.BIN/ # Temporary scratch directory used by the tests tests/integration/buildcmd/scratch +tests/integration/testdata/buildcmd/Dotnetcore2.0/bin +tests/integration/testdata/buildcmd/Dotnetcore2.0/obj # End of https://www.gitignore.io/api/osx,node,macos,linux,python,windows,pycharm,intellij,sublimetext,visualstudiocode diff --git a/samcli/commands/build/command.py b/samcli/commands/build/command.py index a8c3660eb3..1184689a17 100644 --- a/samcli/commands/build/command.py +++ b/samcli/commands/build/command.py @@ -11,7 +11,8 @@ from samcli.commands._utils.options import template_option_without_build, docker_common_options, \ parameter_override_option from samcli.commands.build.build_context import BuildContext -from samcli.lib.build.app_builder import ApplicationBuilder, BuildError, UnsupportedBuilderLibraryVersionError +from samcli.lib.build.app_builder import ApplicationBuilder, BuildError, UnsupportedBuilderLibraryVersionError, \ + ContainerBuildNotSupported from samcli.lib.build.workflow_config import UnsupportedRuntimeException from samcli.commands._utils.template import move_template @@ -31,9 +32,10 @@ Supported Runtimes ------------------ 1. Python 2.7, 3.6, 3.7 using PIP\n -4. Nodejs 8.10, 6.10 using NPM -4. Ruby 2.5 using Bundler -5. Java 8 using Gradle +2. Nodejs 8.10, 6.10 using NPM\n +3. Ruby 2.5 using Bundler\n +4. Java 8 using Gradle\n +5. Dotnetcore2.0 and 2.1 using Dotnet CLI (without --use-container flag)\n \b Examples -------- @@ -147,8 +149,9 @@ def do_cli(template, # pylint: disable=too-many-locals click.secho(msg, fg="yellow") - except (UnsupportedRuntimeException, BuildError, UnsupportedBuilderLibraryVersionError) as ex: - click.secho("Build Failed", fg="red") + except (UnsupportedRuntimeException, BuildError, UnsupportedBuilderLibraryVersionError, + ContainerBuildNotSupported) as ex: + click.secho("\nBuild Failed", fg="red") raise UserException(str(ex)) diff --git a/samcli/lib/build/app_builder.py b/samcli/lib/build/app_builder.py index d147b14562..ab6d1af1f6 100644 --- a/samcli/lib/build/app_builder.py +++ b/samcli/lib/build/app_builder.py @@ -19,7 +19,7 @@ from aws_lambda_builders.builder import LambdaBuilder from aws_lambda_builders.exceptions import LambdaBuilderError from aws_lambda_builders import RPC_PROTOCOL_VERSION as lambda_builders_protocol_version -from .workflow_config import get_workflow_config +from .workflow_config import get_workflow_config, supports_build_in_container LOG = logging.getLogger(__name__) @@ -33,6 +33,10 @@ def __init__(self, container_name, error_msg): Exception.__init__(self, msg.format(container_name=container_name, error_msg=error_msg)) +class ContainerBuildNotSupported(Exception): + pass + + class BuildError(Exception): pass @@ -224,6 +228,10 @@ def _build_function_on_container(self, # pylint: disable=too-many-locals if not self._container_manager.is_docker_reachable: raise BuildError("Docker is unreachable. Docker needs to be running to build inside a container.") + container_build_supported, reason = supports_build_in_container(config) + if not container_build_supported: + raise ContainerBuildNotSupported(reason) + # If we are printing debug logs in SAM CLI, the builder library should also print debug logs log_level = LOG.getEffectiveLevel() diff --git a/samcli/lib/build/workflow_config.py b/samcli/lib/build/workflow_config.py index 0fe9b1d31d..9223239619 100644 --- a/samcli/lib/build/workflow_config.py +++ b/samcli/lib/build/workflow_config.py @@ -125,6 +125,41 @@ def get_workflow_config(runtime, code_dir, project_dir): .format(runtime, str(ex))) +def supports_build_in_container(config): + """ + Given a workflow config, this method provides a boolean on whether the workflow can run within a container or not. + + Parameters + ---------- + config namedtuple(Capability) + Config specifying the particular build workflow + + Returns + ------- + tuple(bool, str) + True, if this workflow can be built inside a container. False, along with a reason message if it cannot be. + """ + + def _key(c): + return str(c.language) + str(c.dependency_manager) + str(c.application_framework) + + # This information could have beeen bundled inside the Workflow Config object. But we this way because + # ultimately the workflow's implementation dictates whether it can run within a container or not. + # A "workflow config" is like a primary key to identify the workflow. So we use the config as a key in the + # map to identify which workflows can support building within a container. + + unsupported = { + _key(DOTNET_CLIPACKAGE_CONFIG): "We do not support building .NET Core Lambda functions within a container. " + "Try building without the container. Most .NET Core functions will build " + "successfully.", + } + + if _key(config) in unsupported: + return False, unsupported[config] + + return True, None + + class BasicWorkflowSelector(object): """ Basic workflow selector that returns the first available configuration in the given list of configurations diff --git a/tests/integration/buildcmd/test_build_cmd.py b/tests/integration/buildcmd/test_build_cmd.py index fe6b6dd549..c15d51ac5b 100644 --- a/tests/integration/buildcmd/test_build_cmd.py +++ b/tests/integration/buildcmd/test_build_cmd.py @@ -356,6 +356,24 @@ def test_with_dotnetcore(self, runtime, code_uri, use_container): self.verify_docker_container_cleanedup(runtime) + @parameterized.expand([ + ("dotnetcore2.0", "Dotnetcore2.0"), + ("dotnetcore2.1", "Dotnetcore2.1"), + ]) + def test_must_fail_with_container(self, runtime, code_uri): + use_container = True + overrides = {"Runtime": runtime, "CodeUri": code_uri, + "Handler": "HelloWorld::HelloWorld.Function::FunctionHandler"} + cmdlist = self.get_command_list(use_container=use_container, + parameter_overrides=overrides) + + LOG.info("Running Command: {}".format(cmdlist)) + process = subprocess.Popen(cmdlist, cwd=self.working_dir) + process.wait() + + # Must error out, because container builds are not supported + self.assertEquals(process.returncode, 1) + def _verify_built_artifact(self, build_dir, function_logical_id, expected_files): self.assertTrue(build_dir.exists(), "Build directory should be created") diff --git a/tests/unit/lib/build_module/test_app_builder.py b/tests/unit/lib/build_module/test_app_builder.py index e0e769dbb5..05dae4d8e7 100644 --- a/tests/unit/lib/build_module/test_app_builder.py +++ b/tests/unit/lib/build_module/test_app_builder.py @@ -8,7 +8,7 @@ from samcli.lib.build.app_builder import ApplicationBuilder,\ UnsupportedBuilderLibraryVersionError, BuildError, \ - LambdaBuilderError + LambdaBuilderError, ContainerBuildNotSupported class TestApplicationBuilder_build(TestCase): @@ -325,6 +325,23 @@ def test_must_raise_on_docker_not_running(self): self.assertEquals(str(ctx.exception), "Docker is unreachable. Docker needs to be running to build inside a container.") + @patch("samcli.lib.build.app_builder.supports_build_in_container") + def test_must_raise_on_unsupported_container_build(self, supports_build_in_container_mock): + config = Mock() + + reason = "my reason" + supports_build_in_container_mock.return_value = (False, reason) + + with self.assertRaises(ContainerBuildNotSupported) as ctx: + self.builder._build_function_on_container(config, + "source_dir", + "artifacts_dir", + "scratch_dir", + "manifest_path", + "runtime") + + self.assertEquals(str(ctx.exception), reason) + class TestApplicationBuilder_parse_builder_response(TestCase):