From 7585e1154484783e1b52117ea937f5fa1dc44a3e Mon Sep 17 00:00:00 2001 From: Sriram Madapusi Vasudevan <3770774+sriram-mv@users.noreply.github.com> Date: Fri, 9 Dec 2022 16:18:47 -0800 Subject: [PATCH] fix: change permissions on package universally (#4462) * fix: change permissions on package universally - set permissions on directories to be 755 and files to be 644. * fix: only change permissions for zipping Lambda - Lambda specific resource zips alone adhere to https://aws.amazon.com/premiumsupport/knowledge-center/lambda-deployment-package-errors * fix: windows tests * fix: set zip permissions to ensure there are no regressions * fix: modify zip test to check permission-as-is - except for windows, this was escalated earlier in https://github.com/aws/aws-sam-cli/pull/2356/files deliberately. * tests: more explicit testing on `zip_method` per Resource. * cleanup: move control logic to function zip signature Co-authored-by: Mehmet Nuri Deveci <5735811+mndeveci@users.noreply.github.com> --- samcli/lib/package/utils.py | 74 +++++++--- .../flows/auto_dependency_layer_sync_flow.py | 4 +- samcli/lib/sync/flows/layer_sync_flow.py | 4 +- .../lib/sync/flows/zip_function_sync_flow.py | 4 +- samcli/lib/utils/resources.py | 7 + .../lib/package/test_artifact_exporter.py | 132 +++++++++++++++++- .../test_auto_dependency_layer_sync_flow.py | 2 +- .../lib/sync/flows/test_layer_sync_flow.py | 2 +- .../sync/flows/test_zip_function_sync_flow.py | 2 +- 9 files changed, 193 insertions(+), 38 deletions(-) diff --git a/samcli/lib/package/utils.py b/samcli/lib/package/utils.py index 69017ea891..c22b08afda 100644 --- a/samcli/lib/package/utils.py +++ b/samcli/lib/package/utils.py @@ -1,6 +1,7 @@ """ Utilities involved in Packaging. """ +import functools import logging import os import platform @@ -10,7 +11,7 @@ import zipfile import contextlib from contextlib import contextmanager -from typing import Dict, Optional, cast +from typing import Dict, Optional, Callable, cast import jmespath @@ -18,6 +19,7 @@ from samcli.lib.package.ecr_utils import is_ecr_url from samcli.lib.package.s3_uploader import S3Uploader from samcli.lib.utils.hash import dir_checksum +from samcli.lib.utils.resources import LAMBDA_LOCAL_RESOURCES LOG = logging.getLogger(__name__) @@ -166,9 +168,14 @@ def upload_local_artifacts( local_path = make_abs_path(parent_dir, local_path) - # Or, pointing to a folder. Zip the folder and upload + # Or, pointing to a folder. Zip the folder and upload (zip_method is changed based on resource type) if is_local_folder(local_path): - return zip_and_upload(local_path, uploader, extension) + return zip_and_upload( + local_path, + uploader, + extension, + zip_method=make_zip_with_lambda_permissions if resource_id in LAMBDA_LOCAL_RESOURCES else make_zip, + ) # Path could be pointing to a file. Upload the file if is_local_file(local_path): @@ -184,13 +191,13 @@ def resource_not_packageable(resource_dict): return False -def zip_and_upload(local_path: str, uploader: S3Uploader, extension: Optional[str]) -> str: - with zip_folder(local_path) as (zip_file, md5_hash): +def zip_and_upload(local_path: str, uploader: S3Uploader, extension: Optional[str], zip_method: Callable) -> str: + with zip_folder(local_path, zip_method=zip_method) as (zip_file, md5_hash): return uploader.upload_with_dedup(zip_file, precomputed_md5=md5_hash, extension=extension) @contextmanager -def zip_folder(folder_path): +def zip_folder(folder_path, zip_method): """ Zip the entire folder and return a file to the zip. Use this inside a "with" statement to cleanup the zipfile after it is used. @@ -199,6 +206,8 @@ def zip_folder(folder_path): ---------- folder_path : str The path of the folder to zip + zip_method : Callable + Callable function that takes in a file name and source_path and zips accordingly. Yields ------ @@ -210,7 +219,7 @@ def zip_folder(folder_path): md5hash = dir_checksum(folder_path, followlinks=True) filename = os.path.join(tempfile.gettempdir(), "data-" + md5hash) - zipfile_name = make_zip(filename, folder_path) + zipfile_name = zip_method(filename, folder_path) try: yield zipfile_name, md5hash finally: @@ -218,7 +227,7 @@ def zip_folder(folder_path): os.remove(zipfile_name) -def make_zip(file_name, source_root): +def make_zip_with_permissions(file_name, source_root, file_permissions, dir_permissions): """ Create a zip file from the source directory @@ -228,6 +237,10 @@ def make_zip(file_name, source_root): The basename of the zip file, without .zip source_root : str The path to the source directory + file_permissions : int + The permissions set for files within the source_root + dir_permissions : int + The permissions set for directories within the source_root Returns ------- str @@ -242,28 +255,45 @@ def make_zip(file_name, source_root): for filename in files: full_path = os.path.join(root, filename) relative_path = os.path.relpath(full_path, source_root) - if platform.system().lower() == "windows": - with open(full_path, "rb") as data: - file_bytes = data.read() - info = zipfile.ZipInfo(relative_path) - # Clear external attr set for Windows + with open(full_path, "rb") as data: + file_bytes = data.read() + info = zipfile.ZipInfo(relative_path) + # Context: Nov 2020 + # Set external attr with Unix 0755 permission + # Originally set to 0005 in the discussion below + # https://github.com/aws/aws-sam-cli/pull/2193#discussion_r513110608 + # Changed to 0755 due to a regression in https://github.com/aws/aws-sam-cli/issues/2344 + # Final PR: https://github.com/aws/aws-sam-cli/pull/2356/files + if file_permissions and dir_permissions: + # Clear external attr info.external_attr = 0 - # Set external attr with Unix 0755 permission - # Originally set to 0005 in the discussion below - # https://github.com/aws/aws-sam-cli/pull/2193#discussion_r513110608 - # Changed to 0755 due to a regression in https://github.com/aws/aws-sam-cli/issues/2344 - # Mimicking Unix permission bits and recommanded permission bits - # in the Lambda Trouble Shooting Docs - info.external_attr = 0o100755 << 16 # Set host OS to Unix info.create_system = 3 + info.external_attr = dir_permissions << 16 if info.is_dir() else file_permissions << 16 zf.writestr(info, file_bytes, compress_type=compression_type) - else: - zf.write(full_path, relative_path) + else: + zf.write(full_path, relative_path) return zipfile_name +make_zip = functools.partial( + make_zip_with_permissions, + file_permissions=0o100755 if platform.system().lower() == "windows" else None, + dir_permissions=0o100755 if platform.system().lower() == "windows" else None, +) +# Context: Nov 2022 +# NOTE(sriram-mv): Modify permissions regardless of the Operating system, since +# AWS Lambda requires following permissions as referenced in docs: +# https://aws.amazon.com/premiumsupport/knowledge-center/lambda-deployment-package-errors/ +# For backward compatibility with windows, setting the permissions to be 755. +make_zip_with_lambda_permissions = functools.partial( + make_zip_with_permissions, + file_permissions=0o100755 if platform.system().lower() == "windows" else 0o100644, + dir_permissions=0o100755, +) + + def copy_to_temp_dir(filepath): tmp_dir = tempfile.mkdtemp() dst = os.path.join(tmp_dir, os.path.basename(filepath)) diff --git a/samcli/lib/sync/flows/auto_dependency_layer_sync_flow.py b/samcli/lib/sync/flows/auto_dependency_layer_sync_flow.py index 04d58b3867..6c47e22d52 100644 --- a/samcli/lib/sync/flows/auto_dependency_layer_sync_flow.py +++ b/samcli/lib/sync/flows/auto_dependency_layer_sync_flow.py @@ -11,7 +11,7 @@ from samcli.lib.bootstrap.nested_stack.nested_stack_builder import NestedStackBuilder from samcli.lib.bootstrap.nested_stack.nested_stack_manager import NestedStackManager from samcli.lib.build.build_graph import BuildGraph -from samcli.lib.package.utils import make_zip +from samcli.lib.package.utils import make_zip_with_lambda_permissions from samcli.lib.providers.provider import Function, Stack from samcli.lib.providers.sam_function_provider import SamFunctionProvider from samcli.lib.sync.exceptions import ( @@ -85,7 +85,7 @@ def gather_resources(self) -> None: self._get_compatible_runtimes()[0], ) zip_file_path = os.path.join(tempfile.gettempdir(), "data-" + uuid.uuid4().hex) - self._zip_file = make_zip(zip_file_path, self._artifact_folder) + self._zip_file = make_zip_with_lambda_permissions(zip_file_path, self._artifact_folder) self._local_sha = file_checksum(cast(str, self._zip_file), hashlib.sha256()) def _get_dependent_functions(self) -> List[Function]: diff --git a/samcli/lib/sync/flows/layer_sync_flow.py b/samcli/lib/sync/flows/layer_sync_flow.py index be59543de6..4a0fabc9d9 100644 --- a/samcli/lib/sync/flows/layer_sync_flow.py +++ b/samcli/lib/sync/flows/layer_sync_flow.py @@ -11,7 +11,7 @@ from contextlib import ExitStack from samcli.lib.build.app_builder import ApplicationBuilder -from samcli.lib.package.utils import make_zip +from samcli.lib.package.utils import make_zip_with_lambda_permissions from samcli.lib.providers.provider import ResourceIdentifier, Stack, get_resource_by_id, Function, LayerVersion from samcli.lib.providers.sam_function_provider import SamFunctionProvider from samcli.lib.sync.exceptions import MissingPhysicalResourceError, NoLayerVersionsFoundError @@ -235,7 +235,7 @@ def gather_resources(self) -> None: self._artifact_folder = builder.build().artifacts.get(self._layer_identifier) zip_file_path = os.path.join(tempfile.gettempdir(), f"data-{uuid.uuid4().hex}") - self._zip_file = make_zip(zip_file_path, self._artifact_folder) + self._zip_file = make_zip_with_lambda_permissions(zip_file_path, self._artifact_folder) LOG.debug("%sCreated artifact ZIP file: %s", self.log_prefix, self._zip_file) self._local_sha = file_checksum(cast(str, self._zip_file), hashlib.sha256()) diff --git a/samcli/lib/sync/flows/zip_function_sync_flow.py b/samcli/lib/sync/flows/zip_function_sync_flow.py index 8987a23e56..ea573dfd7b 100644 --- a/samcli/lib/sync/flows/zip_function_sync_flow.py +++ b/samcli/lib/sync/flows/zip_function_sync_flow.py @@ -16,7 +16,7 @@ from samcli.lib.package.s3_uploader import S3Uploader from samcli.lib.utils.colors import Colored from samcli.lib.utils.hash import file_checksum -from samcli.lib.package.utils import make_zip +from samcli.lib.package.utils import make_zip_with_lambda_permissions from samcli.lib.build.app_builder import ApplicationBuilder from samcli.lib.sync.sync_flow import ResourceAPICall, ApiCallTypes @@ -99,7 +99,7 @@ def gather_resources(self) -> None: self._artifact_folder = build_result.artifacts.get(self._function_identifier) zip_file_path = os.path.join(tempfile.gettempdir(), "data-" + uuid.uuid4().hex) - self._zip_file = make_zip(zip_file_path, self._artifact_folder) + self._zip_file = make_zip_with_lambda_permissions(zip_file_path, self._artifact_folder) LOG.debug("%sCreated artifact ZIP file: %s", self.log_prefix, self._zip_file) self._local_sha = file_checksum(cast(str, self._zip_file), hashlib.sha256()) diff --git a/samcli/lib/utils/resources.py b/samcli/lib/utils/resources.py index c608725132..11aee8da91 100644 --- a/samcli/lib/utils/resources.py +++ b/samcli/lib/utils/resources.py @@ -86,6 +86,13 @@ AWS_CLOUDFORMATION_STACK: "TemplateURL", } +LAMBDA_LOCAL_RESOURCES = [ + AWS_LAMBDA_FUNCTION, + AWS_LAMBDA_LAYERVERSION, + AWS_SERVERLESS_FUNCTION, + AWS_SERVERLESS_LAYERVERSION, +] + def get_packageable_resource_paths(): """ diff --git a/tests/unit/lib/package/test_artifact_exporter.py b/tests/unit/lib/package/test_artifact_exporter.py index d7b0353167..11f7bd1271 100644 --- a/tests/unit/lib/package/test_artifact_exporter.py +++ b/tests/unit/lib/package/test_artifact_exporter.py @@ -1,4 +1,6 @@ +import functools import json +import platform import tempfile import os import string @@ -13,8 +15,9 @@ from samcli.commands.package.exceptions import ExportFailedError from samcli.lib.package.s3_uploader import S3Uploader from samcli.lib.package.uploaders import Destination -from samcli.lib.package.utils import zip_folder, make_zip +from samcli.lib.package.utils import zip_folder, make_zip, make_zip_with_lambda_permissions, make_zip_with_permissions from samcli.lib.utils.packagetype import ZIP, IMAGE +from samcli.lib.utils.resources import LAMBDA_LOCAL_RESOURCES, RESOURCES_WITH_LOCAL_PATHS from tests.testing_utils import FileCreator from samcli.commands.package import exceptions from samcli.lib.package.artifact_exporter import ( @@ -333,7 +336,66 @@ def test_upload_local_artifacts_local_folder(self, zip_and_upload_mock): absolute_artifact_path = make_abs_path(parent_dir, artifact_path) - zip_and_upload_mock.assert_called_once_with(absolute_artifact_path, mock.ANY, None) + zip_and_upload_mock.assert_called_once_with(absolute_artifact_path, mock.ANY, None, zip_method=make_zip) + + @patch("samcli.lib.package.utils.zip_and_upload") + def test_upload_local_artifacts_local_folder_lambda_resources(self, zip_and_upload_mock): + for resource_id in LAMBDA_LOCAL_RESOURCES: + property_name = "property" + expected_s3_url = "s3://foo/bar?versionId=baz" + + zip_and_upload_mock.return_value = expected_s3_url + # Artifact path is a Directory + with self.make_temp_dir() as artifact_path: + # Artifact is a file in the temporary directory + parent_dir = tempfile.gettempdir() + resource_dict = {property_name: artifact_path} + + result = upload_local_artifacts(resource_id, resource_dict, property_name, parent_dir, Mock()) + self.assertEqual(result, expected_s3_url) + + absolute_artifact_path = make_abs_path(parent_dir, artifact_path) + # zip_method will NOT be the generalized zip_method `make_zip` + + with self.assertRaises(AssertionError): + zip_and_upload_mock.assert_called_once_with( + absolute_artifact_path, mock.ANY, None, zip_method=make_zip + ) + + # zip_method will be lambda specific. + zip_and_upload_mock.assert_called_once_with( + absolute_artifact_path, mock.ANY, None, zip_method=make_zip_with_lambda_permissions + ) + zip_and_upload_mock.reset_mock() + + @patch("samcli.lib.package.utils.zip_and_upload") + def test_upload_local_artifacts_local_folder_non_lambda_resources(self, zip_and_upload_mock): + non_lambda_resources = RESOURCES_WITH_LOCAL_PATHS.keys() - LAMBDA_LOCAL_RESOURCES + for resource_id in non_lambda_resources: + property_name = "property" + expected_s3_url = "s3://foo/bar?versionId=baz" + + zip_and_upload_mock.return_value = expected_s3_url + # Artifact path is a Directory + with self.make_temp_dir() as artifact_path: + # Artifact is a file in the temporary directory + parent_dir = tempfile.gettempdir() + resource_dict = {property_name: artifact_path} + + result = upload_local_artifacts(resource_id, resource_dict, property_name, parent_dir, Mock()) + self.assertEqual(result, expected_s3_url) + + absolute_artifact_path = make_abs_path(parent_dir, artifact_path) + + # zip_method will NOT be the specialized zip_method `make_zip_with_lambda_permissions` + with self.assertRaises(AssertionError): + zip_and_upload_mock.assert_called_once_with( + absolute_artifact_path, mock.ANY, None, zip_method=make_zip_with_lambda_permissions + ) + + # zip_method will be the generalized zip_method `make_zip` + zip_and_upload_mock.assert_called_once_with(absolute_artifact_path, mock.ANY, None, zip_method=make_zip) + zip_and_upload_mock.reset_mock() @patch("samcli.lib.package.utils.zip_and_upload") def test_upload_local_artifacts_no_path(self, zip_and_upload_mock): @@ -350,7 +412,7 @@ def test_upload_local_artifacts_no_path(self, zip_and_upload_mock): result = upload_local_artifacts(resource_id, resource_dict, property_name, parent_dir, self.s3_uploader_mock) self.assertEqual(result, expected_s3_url) - zip_and_upload_mock.assert_called_once_with(parent_dir, mock.ANY, None) + zip_and_upload_mock.assert_called_once_with(parent_dir, mock.ANY, None, zip_method=make_zip) self.s3_uploader_mock.upload_with_dedup.assert_not_called() @patch("samcli.lib.package.utils.zip_and_upload") @@ -394,7 +456,7 @@ def test_zip_folder(self, make_zip_mock): make_zip_mock.return_value = zip_file_name with self.make_temp_dir() as dirname: - with zip_folder(dirname) as actual_zip_file_name: + with zip_folder(dirname, zip_method=make_zip_mock) as actual_zip_file_name: self.assertEqual(actual_zip_file_name, (zip_file_name, mock.ANY)) make_zip_mock.assert_called_once_with(mock.ANY, dirname) @@ -576,7 +638,7 @@ class MockResource(ResourceZip): resource.export(resource_id, resource_dict, parent_dir) - zip_and_upload_mock.assert_called_once_with(tmp_dir, mock.ANY, None) + zip_and_upload_mock.assert_called_once_with(tmp_dir, mock.ANY, None, zip_method=make_zip) rmtree_mock.assert_called_once_with(tmp_dir) is_zipfile_mock.assert_called_once_with(original_path) self.code_signer_mock.should_sign_package.assert_called_once_with(resource_id) @@ -1570,7 +1632,7 @@ def test_template_export_path_be_folder(self): Template(template_path, os.path.relpath(dirname), self.uploaders_mock, self.code_signer_mock) - def test_make_zip(self): + def test_make_zip_keep_permissions_as_is(self): test_file_creator = FileCreator() test_file_creator.append_file( "index.js", "exports handler = (event, context, callback) => {callback(null, event);}" @@ -1578,6 +1640,9 @@ def test_make_zip(self): dirname = test_file_creator.rootdir + file_permissions = os.stat(test_file_creator.full_path("index.js")).st_mode + dir_permissions = os.stat(test_file_creator.rootdir).st_mode + expected_files = {"index.js"} random_name = "".join(random.choice(string.ascii_letters) for _ in range(10)) @@ -1590,8 +1655,15 @@ def test_make_zip(self): test_zip_file = zipfile.ZipFile(zipfile_name, "r") with closing(test_zip_file) as zf: files_in_zip = set() + external_attr_mask = 65535 << 16 for info in zf.infolist(): files_in_zip.add(info.filename) + permission_bits = (info.external_attr & external_attr_mask) >> 16 + if platform.system().lower() != "windows": + if info.is_dir(): + self.assertEqual(permission_bits, dir_permissions) + else: + self.assertEqual(permission_bits, file_permissions) self.assertEqual(files_in_zip, expected_files) @@ -1603,6 +1675,12 @@ def test_make_zip(self): @patch("platform.system") def test_make_zip_windows(self, mock_system): mock_system.return_value = "Windows" + # Redefining `make_zip` as is in local scope so that arguments passed to functools partial are re-loaded. + windows_make_zip = functools.partial( + make_zip_with_permissions, + file_permissions=0o100755 if platform.system().lower() == "windows" else None, + dir_permissions=0o100755 if platform.system().lower() == "windows" else None, + ) test_file_creator = FileCreator() test_file_creator.append_file( @@ -1618,7 +1696,7 @@ def test_make_zip_windows(self, mock_system): zipfile_name = None try: - zipfile_name = make_zip(outfile, dirname) + zipfile_name = windows_make_zip(outfile, dirname) test_zip_file = zipfile.ZipFile(zipfile_name, "r") with closing(test_zip_file) as zf: @@ -1636,6 +1714,46 @@ def test_make_zip_windows(self, mock_system): os.remove(zipfile_name) test_file_creator.remove_all() + def test_make_zip_lambda_resources(self): + + test_file_creator = FileCreator() + test_file_creator.append_file( + "index.js", "exports handler = (event, context, callback) => {callback(null, event);}" + ) + + dirname = test_file_creator.rootdir + + expected_files = {"index.js"} + + random_name = "".join(random.choice(string.ascii_letters) for _ in range(10)) + outfile = os.path.join(tempfile.gettempdir(), random_name) + + zipfile_name = None + try: + zipfile_name = make_zip_with_lambda_permissions(outfile, dirname) + + test_zip_file = zipfile.ZipFile(zipfile_name, "r") + with closing(test_zip_file) as zf: + files_in_zip = set() + external_attr_mask = 65535 << 16 + for info in zf.infolist(): + files_in_zip.add(info.filename) + permission_bits = (info.external_attr & external_attr_mask) >> 16 + if not platform.system().lower() == "windows": + if info.is_dir(): + self.assertEqual(permission_bits, 0o100755) + else: + self.assertEqual(permission_bits, 0o100644) + else: + self.assertEqual(permission_bits, 0o100755) + + self.assertEqual(files_in_zip, expected_files) + + finally: + if zipfile_name: + os.remove(zipfile_name) + test_file_creator.remove_all() + @patch("shutil.copyfile") @patch("tempfile.mkdtemp") def test_copy_to_temp_dir(self, mkdtemp_mock, copyfile_mock): diff --git a/tests/unit/lib/sync/flows/test_auto_dependency_layer_sync_flow.py b/tests/unit/lib/sync/flows/test_auto_dependency_layer_sync_flow.py index bfa0190976..e288a25bce 100644 --- a/tests/unit/lib/sync/flows/test_auto_dependency_layer_sync_flow.py +++ b/tests/unit/lib/sync/flows/test_auto_dependency_layer_sync_flow.py @@ -73,7 +73,7 @@ def test_gather_resources_fail_when_no_runtime_defined_for_function(self, patche @patch("samcli.lib.sync.flows.auto_dependency_layer_sync_flow.uuid") @patch("samcli.lib.sync.flows.auto_dependency_layer_sync_flow.file_checksum") - @patch("samcli.lib.sync.flows.auto_dependency_layer_sync_flow.make_zip") + @patch("samcli.lib.sync.flows.auto_dependency_layer_sync_flow.make_zip_with_lambda_permissions") @patch("samcli.lib.sync.flows.auto_dependency_layer_sync_flow.tempfile") @patch("samcli.lib.sync.flows.auto_dependency_layer_sync_flow.NestedStackManager") def test_gather_resources( diff --git a/tests/unit/lib/sync/flows/test_layer_sync_flow.py b/tests/unit/lib/sync/flows/test_layer_sync_flow.py index f709f5e682..8045c03980 100644 --- a/tests/unit/lib/sync/flows/test_layer_sync_flow.py +++ b/tests/unit/lib/sync/flows/test_layer_sync_flow.py @@ -57,7 +57,7 @@ def test_setup_with_unknown_layer(self): @patch("samcli.lib.sync.flows.layer_sync_flow.ApplicationBuilder") @patch("samcli.lib.sync.flows.layer_sync_flow.tempfile") - @patch("samcli.lib.sync.flows.layer_sync_flow.make_zip") + @patch("samcli.lib.sync.flows.layer_sync_flow.make_zip_with_lambda_permissions") @patch("samcli.lib.sync.flows.layer_sync_flow.file_checksum") @patch("samcli.lib.sync.flows.layer_sync_flow.os") @patch("samcli.lib.sync.flows.layer_sync_flow.rmtree_if_exists") diff --git a/tests/unit/lib/sync/flows/test_zip_function_sync_flow.py b/tests/unit/lib/sync/flows/test_zip_function_sync_flow.py index 8e7365c970..3a8eb2cfe1 100644 --- a/tests/unit/lib/sync/flows/test_zip_function_sync_flow.py +++ b/tests/unit/lib/sync/flows/test_zip_function_sync_flow.py @@ -33,7 +33,7 @@ def test_set_up(self, session_mock, client_provider_mock): @patch("samcli.lib.sync.flows.zip_function_sync_flow.hashlib.sha256") @patch("samcli.lib.sync.flows.zip_function_sync_flow.uuid.uuid4") @patch("samcli.lib.sync.flows.zip_function_sync_flow.file_checksum") - @patch("samcli.lib.sync.flows.zip_function_sync_flow.make_zip") + @patch("samcli.lib.sync.flows.zip_function_sync_flow.make_zip_with_lambda_permissions") @patch("samcli.lib.sync.flows.zip_function_sync_flow.tempfile.gettempdir") @patch("samcli.lib.sync.flows.zip_function_sync_flow.ApplicationBuilder") @patch("samcli.lib.sync.flows.zip_function_sync_flow.rmtree_if_exists")