diff --git a/samcli/commands/build/build_context.py b/samcli/commands/build/build_context.py index 0cb547f0ca..5892b8a6f8 100644 --- a/samcli/commands/build/build_context.py +++ b/samcli/commands/build/build_context.py @@ -16,6 +16,7 @@ from samcli.commands._utils.template import get_template_data from samcli.commands.exceptions import UserException from samcli.local.lambdafn.exceptions import FunctionNotFound +from samcli.commands.build.exceptions import InvalidBuildDirException LOG = logging.getLogger(__name__) @@ -83,6 +84,10 @@ def __exit__(self, *args): def _setup_build_dir(build_dir, clean): build_path = pathlib.Path(build_dir) + if os.path.abspath(str(build_path)) == os.path.abspath(str(pathlib.Path.cwd())): + exception_message = "Failing build: Running a build with build-dir as current working directory is extremely dangerous since the build-dir contents is first removed. This is no longer supported, please remove the '--build-dir' option from the command to allow the build artifacts to be placed in the directory your template is in." + raise InvalidBuildDirException(exception_message) + if build_path.exists() and os.listdir(build_dir) and clean: # build folder contains something inside. Clear everything. shutil.rmtree(build_dir) diff --git a/samcli/commands/build/command.py b/samcli/commands/build/command.py index a84cf804de..d7549a0074 100644 --- a/samcli/commands/build/command.py +++ b/samcli/commands/build/command.py @@ -63,7 +63,7 @@ @click.option('--build-dir', '-b', default=DEFAULT_BUILD_DIR, type=click.Path(file_okay=False, dir_okay=True, writable=True), # Must be a directory - help="Path to a folder where the built artifacts will be stored") + help="Path to a folder where the built artifacts will be stored. This directory will be first removed before starting a build.") @click.option("--base-dir", "-s", default=None, type=click.Path(dir_okay=True, file_okay=False), # Must be a directory diff --git a/samcli/commands/build/exceptions.py b/samcli/commands/build/exceptions.py new file mode 100644 index 0000000000..783a928803 --- /dev/null +++ b/samcli/commands/build/exceptions.py @@ -0,0 +1,10 @@ +"""Build exceptions""" + +from samcli.commands.exceptions import UserException + + +class InvalidBuildDirException(UserException): + """ + Value provided to --build-dir is invalid + """ + pass diff --git a/tests/unit/commands/buildcmd/test_build_context.py b/tests/unit/commands/buildcmd/test_build_context.py index 21e1b57436..7d087336b7 100644 --- a/tests/unit/commands/buildcmd/test_build_context.py +++ b/tests/unit/commands/buildcmd/test_build_context.py @@ -3,6 +3,7 @@ from mock import patch, Mock from samcli.commands.build.build_context import BuildContext +from samcli.commands.build.exceptions import InvalidBuildDirException class TestBuildContext__enter__(TestCase): @@ -121,6 +122,8 @@ class TestBuildContext_setup_build_dir(TestCase): def test_build_dir_exists_with_non_empty_dir(self, pathlib_patch, os_patch, shutil_patch): path_mock = Mock() pathlib_patch.Path.return_value = path_mock + os_patch.path.abspath.side_effect = ["/somepath", "/cwd/path"] + path_mock.cwd.return_value = "/cwd/path" os_patch.listdir.return_value = True path_mock.resolve.return_value = "long/full/path" path_mock.exists.return_value = True @@ -129,12 +132,14 @@ def test_build_dir_exists_with_non_empty_dir(self, pathlib_patch, os_patch, shut full_build_path = BuildContext._setup_build_dir(build_dir, True) self.assertEqual(full_build_path, "long/full/path") + self.assertEqual(os_patch.path.abspath.call_count, 2) os_patch.listdir.assert_called_once() path_mock.exists.assert_called_once() path_mock.mkdir.assert_called_once_with(mode=0o755, parents=True, exist_ok=True) pathlib_patch.Path.assert_called_once_with(build_dir) shutil_patch.rmtree.assert_called_once_with(build_dir) + pathlib_patch.Path.cwd.assert_called_once() @patch("samcli.commands.build.build_context.shutil") @patch("samcli.commands.build.build_context.os") @@ -143,6 +148,8 @@ def test_build_dir_exists_with_empty_dir(self, pathlib_patch, os_patch, shutil_p path_mock = Mock() pathlib_patch.Path.return_value = path_mock os_patch.listdir.return_value = False + os_patch.path.abspath.side_effect = ["/somepath", "/cwd/path"] + path_mock.cwd.return_value = "/cwd/path" path_mock.resolve.return_value = "long/full/path" path_mock.exists.return_value = True build_dir = "/somepath" @@ -150,12 +157,14 @@ def test_build_dir_exists_with_empty_dir(self, pathlib_patch, os_patch, shutil_p full_build_path = BuildContext._setup_build_dir(build_dir, True) self.assertEqual(full_build_path, "long/full/path") + self.assertEqual(os_patch.path.abspath.call_count, 2) os_patch.listdir.assert_called_once() path_mock.exists.assert_called_once() path_mock.mkdir.assert_called_once_with(mode=0o755, parents=True, exist_ok=True) pathlib_patch.Path.assert_called_once_with(build_dir) shutil_patch.rmtree.assert_not_called() + pathlib_patch.Path.cwd.assert_called_once() @patch("samcli.commands.build.build_context.shutil") @patch("samcli.commands.build.build_context.os") @@ -163,6 +172,8 @@ def test_build_dir_exists_with_empty_dir(self, pathlib_patch, os_patch, shutil_p def test_build_dir_does_not_exist(self, pathlib_patch, os_patch, shutil_patch): path_mock = Mock() pathlib_patch.Path.return_value = path_mock + os_patch.path.abspath.side_effect = ["/somepath", "/cwd/path"] + path_mock.cwd.return_value = "/cwd/path" path_mock.resolve.return_value = "long/full/path" path_mock.exists.return_value = False build_dir = "/somepath" @@ -170,12 +181,14 @@ def test_build_dir_does_not_exist(self, pathlib_patch, os_patch, shutil_patch): full_build_path = BuildContext._setup_build_dir(build_dir, True) self.assertEqual(full_build_path, "long/full/path") + self.assertEqual(os_patch.path.abspath.call_count, 2) os_patch.listdir.assert_not_called() path_mock.exists.assert_called_once() path_mock.mkdir.assert_called_once_with(mode=0o755, parents=True, exist_ok=True) pathlib_patch.Path.assert_called_once_with(build_dir) shutil_patch.rmtree.assert_not_called() + pathlib_patch.Path.cwd.assert_called_once() @patch("samcli.commands.build.build_context.shutil") @patch("samcli.commands.build.build_context.os") @@ -183,6 +196,8 @@ def test_build_dir_does_not_exist(self, pathlib_patch, os_patch, shutil_patch): def test_non_clean_build_when_dir_exists_with_non_empty_dir(self, pathlib_patch, os_patch, shutil_patch): path_mock = Mock() pathlib_patch.Path.return_value = path_mock + os_patch.path.abspath.side_effect = ["/somepath", "/cwd/path"] + path_mock.cwd.return_value = "/cwd/path" os_patch.listdir.return_value = True path_mock.resolve.return_value = "long/full/path" path_mock.exists.return_value = True @@ -191,9 +206,33 @@ def test_non_clean_build_when_dir_exists_with_non_empty_dir(self, pathlib_patch, full_build_path = BuildContext._setup_build_dir(build_dir, False) self.assertEqual(full_build_path, "long/full/path") + self.assertEqual(os_patch.path.abspath.call_count, 2) os_patch.listdir.assert_called_once() path_mock.exists.assert_called_once() path_mock.mkdir.assert_called_once_with(mode=0o755, parents=True, exist_ok=True) pathlib_patch.Path.assert_called_once_with(build_dir) shutil_patch.rmtree.assert_not_called() + pathlib_patch.Path.cwd.assert_called_once() + + @patch("samcli.commands.build.build_context.shutil") + @patch("samcli.commands.build.build_context.os") + @patch("samcli.commands.build.build_context.pathlib") + def test_when_build_dir_is_cwd_raises_exception(self, pathlib_patch, os_patch, shutil_patch): + path_mock = Mock() + pathlib_patch.Path.return_value = path_mock + os_patch.path.abspath.side_effect = ["/somepath", "/somepath"] + path_mock.cwd.return_value = "/somepath" + build_dir = "/somepath" + + with self.assertRaises(InvalidBuildDirException): + BuildContext._setup_build_dir(build_dir, True) + + self.assertEqual(os_patch.path.abspath.call_count, 2) + + os_patch.listdir.assert_not_called() + path_mock.exists.assert_not_called() + path_mock.mkdir.assert_not_called() + pathlib_patch.Path.assert_called_once_with(build_dir) + shutil_patch.rmtree.assert_not_called() + pathlib_patch.Path.cwd.assert_called_once()