diff --git a/awscli/customizations/cloudformation/artifact_exporter.py b/awscli/customizations/cloudformation/artifact_exporter.py index d011bb468194..a1fde9ea1547 100644 --- a/awscli/customizations/cloudformation/artifact_exporter.py +++ b/awscli/customizations/cloudformation/artifact_exporter.py @@ -190,7 +190,8 @@ def make_zip(filename, source_root): zip_file = zipfile.ZipFile(f, 'w', zipfile.ZIP_DEFLATED) with contextlib.closing(zip_file) as zf: for root, dirs, files in os.walk(source_root, followlinks=True): - for filename in files: + dirs.sort() + for filename in sorted(files): full_path = os.path.join(root, filename) relative_path = os.path.relpath(full_path, source_root) zf.write(full_path, relative_path) diff --git a/tests/unit/customizations/cloudformation/test_artifact_exporter.py b/tests/unit/customizations/cloudformation/test_artifact_exporter.py index 166e284a4afb..30e29ee78169 100644 --- a/tests/unit/customizations/cloudformation/test_artifact_exporter.py +++ b/tests/unit/customizations/cloudformation/test_artifact_exporter.py @@ -1640,6 +1640,44 @@ def test_make_zip(self): os.remove(zipfile_name) test_file_creator.remove_all() + def test_make_zip_deterministic_order(self): + test_file_creator = FileCreator() + test_file_creator.append_file('b/y.txt', 'y') + test_file_creator.append_file('a/x.txt', 'x') + test_file_creator.append_file('b/a.txt', 'a') + test_file_creator.append_file('a/z.txt', 'z') + test_file_creator.append_file('c.txt', 'c') + test_file_creator.append_file('a.txt', 'a') + + dirname = test_file_creator.rootdir + 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(outfile, dirname) + + test_zip_file = zipfile.ZipFile(zipfile_name, "r") + with closing(test_zip_file) as zf: + names = zf.namelist() + + expected_order = [ + 'a.txt', + 'c.txt', + os.path.join('a', 'x.txt'), + os.path.join('a', 'z.txt'), + os.path.join('b', 'a.txt'), + os.path.join('b', 'y.txt'), + ] + self.assertEqual(names, expected_order) + + finally: + if zipfile_name: + os.remove(zipfile_name) + test_file_creator.remove_all() + @mock.patch("shutil.copy") @mock.patch("tempfile.mkdtemp") def test_copy_to_temp_dir(self, mkdtemp_mock, copyfile_mock):