From 1a5dc661e89aa863c2dcc08374d79e1853efe46b Mon Sep 17 00:00:00 2001 From: Benjamin Bennett Date: Mon, 15 Jan 2024 10:08:36 +0000 Subject: [PATCH 1/4] Fix generation of archives when using exclude_symlink_directories (#296) --- .../provider/data_source_archive_file_test.go | 98 +++++++++++++++---- internal/provider/zip_archiver.go | 26 ++--- internal/provider/zip_archiver_test.go | 15 +-- 3 files changed, 96 insertions(+), 43 deletions(-) diff --git a/internal/provider/data_source_archive_file_test.go b/internal/provider/data_source_archive_file_test.go index 8ec32870..ddfec65e 100644 --- a/internal/provider/data_source_archive_file_test.go +++ b/internal/provider/data_source_archive_file_test.go @@ -960,8 +960,8 @@ func TestAccArchiveFile_SymlinkFile_Absolute_ExcludeSymlinkDirectories(t *testin }) } -// TestAccArchiveFile_SymlinkDirectory_Relative_ExcludeSymlinkDirectories verifies that an error is generated when -// trying to use a symlink to a directory. +// TestAccArchiveFile_SymlinkDirectory_Relative_ExcludeSymlinkDirectories verifies that an empty archive +// is generated when trying to archive a directory which only contains a symlink to a directory. func TestAccArchiveFile_SymlinkDirectory_Relative_ExcludeSymlinkDirectories(t *testing.T) { td := t.TempDir() @@ -980,14 +980,19 @@ func TestAccArchiveFile_SymlinkDirectory_Relative_ExcludeSymlinkDirectories(t *t exclude_symlink_directories = true } `, filepath.ToSlash("test-fixtures/test-symlink-dir"), filepath.ToSlash(f)), - ExpectError: regexp.MustCompile(`.*error creating archive: error archiving directory: error reading file for`), + Check: r.ComposeTestCheckFunc( + r.TestCheckResourceAttrWith("data.archive_file.foo", "output_path", func(value string) error { + ensureContents(t, value, map[string][]byte{}) + return nil + }), + ), }, }, }) } -// TestAccArchiveFile_SymlinkDirectory_Absolute_ExcludeSymlinkDirectories verifies that an error is generated when -// trying to use a symlink to a directory. +// TestAccArchiveFile_SymlinkDirectory_Absolute_ExcludeSymlinkDirectories verifies that an empty archive +// is generated when trying to archive a directory which only contains a symlink to a directory. func TestAccArchiveFile_SymlinkDirectory_Absolute_ExcludeSymlinkDirectories(t *testing.T) { td := t.TempDir() @@ -1011,7 +1016,12 @@ func TestAccArchiveFile_SymlinkDirectory_Absolute_ExcludeSymlinkDirectories(t *t exclude_symlink_directories = true } `, filepath.ToSlash(symlinkDirWithRegularFilesAbs), filepath.ToSlash(f)), - ExpectError: regexp.MustCompile(`.*error creating archive: error archiving directory: error reading file for`), + Check: r.ComposeTestCheckFunc( + r.TestCheckResourceAttrWith("data.archive_file.foo", "output_path", func(value string) error { + ensureContents(t, value, map[string][]byte{}) + return nil + }), + ), }, }, }) @@ -1181,8 +1191,8 @@ func TestAccArchiveFile_SymlinkDirectoryWithSymlinkFile_Absolute_ExcludeSymlinkD }) } -// TestAccArchiveFile_DirectoryWithSymlinkDirectory_Relative_ExcludeSymlinkDirectories verifies that an error is -// generated when trying to a directory which contains a symlink to a directory. +// TestAccArchiveFile_DirectoryWithSymlinkDirectory_Relative_ExcludeSymlinkDirectories verifies that an empty archive +// is generated when trying to archive a directory which only contains a symlink to a directory. func TestAccArchiveFile_DirectoryWithSymlinkDirectory_Relative_ExcludeSymlinkDirectories(t *testing.T) { td := t.TempDir() @@ -1197,18 +1207,22 @@ func TestAccArchiveFile_DirectoryWithSymlinkDirectory_Relative_ExcludeSymlinkDir type = "zip" source_dir = "%s" output_path = "%s" - output_file_mode = "0666" exclude_symlink_directories = true } `, filepath.ToSlash("test-fixtures/test-dir-with-symlink-dir"), filepath.ToSlash(f)), - ExpectError: regexp.MustCompile(`.*error creating archive: error archiving directory: error reading file for`), + Check: r.ComposeTestCheckFunc( + r.TestCheckResourceAttrWith("data.archive_file.foo", "output_path", func(value string) error { + ensureContents(t, value, map[string][]byte{}) + return nil + }), + ), }, }, }) } -// TestAccArchiveFile_IncludeDirectoryWithSymlinkDirectory_Absolute_ExcludeSymlinkDirectories verifies that an error is -// generated when trying to a directory which contains a symlink to a directory. +// TestAccArchiveFile_IncludeDirectoryWithSymlinkDirectory_Absolute_ExcludeSymlinkDirectories verifies that an empty archive +// is generated when trying to archive a directory which only contains a symlink to a directory. func TestAccArchiveFile_IncludeDirectoryWithSymlinkDirectory_Absolute_ExcludeSymlinkDirectories(t *testing.T) { td := t.TempDir() @@ -1228,23 +1242,29 @@ func TestAccArchiveFile_IncludeDirectoryWithSymlinkDirectory_Absolute_ExcludeSym type = "zip" source_dir = "%s" output_path = "%s" - output_file_mode = "0666" exclude_symlink_directories = true } `, filepath.ToSlash(symlinkDirInRegularDirAbs), filepath.ToSlash(f)), - ExpectError: regexp.MustCompile(`.*error creating archive: error archiving directory: error reading file for`), + Check: r.ComposeTestCheckFunc( + r.TestCheckResourceAttrWith("data.archive_file.foo", "output_path", func(value string) error { + ensureContents(t, value, map[string][]byte{}) + return nil + }), + ), }, }, }) } -// TestAccArchiveFile_Multiple_Relative_ExcludeSymlinkDirectories verifies that an error is -// generated when trying to a directory which contains a symlink to a directory. +// TestAccArchiveFile_Multiple_Relative_ExcludeSymlinkDirectories verifies that +// symlinked directories are excluded. func TestAccArchiveFile_Multiple_Relative_ExcludeSymlinkDirectories(t *testing.T) { td := t.TempDir() f := filepath.Join(td, "zip_file_acc_test.zip") + var fileSize string + r.ParallelTest(t, r.TestCase{ ProtoV5ProviderFactories: protoV5ProviderFactories(), Steps: []r.TestStep{ @@ -1258,14 +1278,32 @@ func TestAccArchiveFile_Multiple_Relative_ExcludeSymlinkDirectories(t *testing.T exclude_symlink_directories = true } `, filepath.ToSlash("test-fixtures"), filepath.ToSlash(f)), - ExpectError: regexp.MustCompile(`.*error creating archive: error archiving directory: error reading file for`), + Check: r.ComposeTestCheckFunc( + testAccArchiveFileSize(f, &fileSize), + r.TestCheckResourceAttrPtr("data.archive_file.foo", "output_size", &fileSize), + r.TestCheckResourceAttrWith("data.archive_file.foo", "output_path", func(value string) error { + ensureContents(t, value, map[string][]byte{ + "test-dir/test-dir1/file1.txt": []byte("This is file 1"), + "test-dir/test-dir1/file2.txt": []byte("This is file 2"), + "test-dir/test-dir1/file3.txt": []byte("This is file 3"), + "test-dir/test-dir2/file1.txt": []byte("This is file 1"), + "test-dir/test-dir2/file2.txt": []byte("This is file 2"), + "test-dir/test-dir2/file3.txt": []byte("This is file 3"), + "test-dir/test-file.txt": []byte("This is test content"), + "test-dir-with-symlink-file/test-file.txt": []byte("This is test content"), + "test-dir-with-symlink-file/test-symlink.txt": []byte("This is test content"), + }) + ensureFileMode(t, value, "0666") + return nil + }), + ), }, }, }) } -// TestAccArchiveFile_Multiple_Absolute_ExcludeSymlinkDirectories verifies that an error is -// generated when trying to a directory which contains a symlink to a directory. +// TestAccArchiveFile_Multiple_Relative_ExcludeSymlinkDirectories verifies that +// symlinked directories are excluded. func TestAccArchiveFile_Multiple_Absolute_ExcludeSymlinkDirectories(t *testing.T) { td := t.TempDir() @@ -1276,6 +1314,8 @@ func TestAccArchiveFile_Multiple_Absolute_ExcludeSymlinkDirectories(t *testing.T t.Fatal(err) } + var fileSize string + r.ParallelTest(t, r.TestCase{ ProtoV5ProviderFactories: protoV5ProviderFactories(), Steps: []r.TestStep{ @@ -1289,7 +1329,25 @@ func TestAccArchiveFile_Multiple_Absolute_ExcludeSymlinkDirectories(t *testing.T exclude_symlink_directories = true } `, filepath.ToSlash(multipleDirsAndFilesAbs), filepath.ToSlash(f)), - ExpectError: regexp.MustCompile(`.*error creating archive: error archiving directory: error reading file for`), + Check: r.ComposeTestCheckFunc( + testAccArchiveFileSize(f, &fileSize), + r.TestCheckResourceAttrPtr("data.archive_file.foo", "output_size", &fileSize), + r.TestCheckResourceAttrWith("data.archive_file.foo", "output_path", func(value string) error { + ensureContents(t, value, map[string][]byte{ + "test-dir/test-dir1/file1.txt": []byte("This is file 1"), + "test-dir/test-dir1/file2.txt": []byte("This is file 2"), + "test-dir/test-dir1/file3.txt": []byte("This is file 3"), + "test-dir/test-dir2/file1.txt": []byte("This is file 1"), + "test-dir/test-dir2/file2.txt": []byte("This is file 2"), + "test-dir/test-dir2/file3.txt": []byte("This is file 3"), + "test-dir/test-file.txt": []byte("This is test content"), + "test-dir-with-symlink-file/test-file.txt": []byte("This is test content"), + "test-dir-with-symlink-file/test-symlink.txt": []byte("This is test content"), + }) + ensureFileMode(t, value, "0666") + return nil + }), + ), }, }, }) diff --git a/internal/provider/zip_archiver.go b/internal/provider/zip_archiver.go index 9b149529..c5248444 100644 --- a/internal/provider/zip_archiver.go +++ b/internal/provider/zip_archiver.go @@ -146,23 +146,25 @@ func (a *ZipArchiver) createWalkFunc(basePath string, indirname string, opts Arc } if info.Mode()&os.ModeSymlink == os.ModeSymlink { - if !opts.ExcludeSymlinkDirectories { - realPath, err := filepath.EvalSymlinks(path) - if err != nil { - return err - } + realPath, err := filepath.EvalSymlinks(path) + if err != nil { + return err + } - realInfo, err := os.Stat(realPath) - if err != nil { - return err - } + realInfo, err := os.Stat(realPath) + if err != nil { + return err + } - if realInfo.IsDir() { + if realInfo.IsDir() { + if !opts.ExcludeSymlinkDirectories { return filepath.Walk(realPath, a.createWalkFunc(archivePath, realPath, opts)) + } else { + return filepath.SkipDir } - - info = realInfo } + + info = realInfo } fh, err := zip.FileInfoHeader(info) diff --git a/internal/provider/zip_archiver_test.go b/internal/provider/zip_archiver_test.go index 3d45f324..a46de0d8 100644 --- a/internal/provider/zip_archiver_test.go +++ b/internal/provider/zip_archiver_test.go @@ -9,7 +9,6 @@ import ( "io" "os" "path/filepath" - "regexp" "strconv" "testing" "time" @@ -218,11 +217,8 @@ func TestZipArchiver_Dir_ExcludeSymlinkDirectories(t *testing.T) { ExcludeSymlinkDirectories: true, }) - regex := regexp.MustCompile(`error reading file for archival: read test-fixtures(\/|\\)test-dir-with-symlink-dir(\/|\\)test-symlink-dir: `) - found := regex.Match([]byte(err.Error())) - - if !found { - t.Fatalf("expedted error to match %q, got: %s", regex.String(), err.Error()) + if err != nil { + t.Errorf("expected no error: %s", err) } } @@ -270,11 +266,8 @@ func TestZipArchiver_Dir_Exclude_ExcludeSymlinkDirectories(t *testing.T) { ExcludeSymlinkDirectories: true, }) - regex := regexp.MustCompile(`error reading file for archival: read test-fixtures(\/|\\)test-dir-with-symlink-dir(\/|\\)test-symlink-dir: `) - found := regex.Match([]byte(err.Error())) - - if !found { - t.Fatalf("expedted error to match %q, got: %s", regex.String(), err.Error()) + if err != nil { + t.Errorf("expected no error: %s", err) } } From 437d7278e3b5a9a0bbc279ea045bfbba7ec8a880 Mon Sep 17 00:00:00 2001 From: Benjamin Bennett Date: Mon, 15 Jan 2024 10:16:07 +0000 Subject: [PATCH 2/4] Adding change log entries (#296) --- .changes/unreleased/BUG FIXES-20240115-101358.yaml | 7 +++++++ .changes/unreleased/BUG FIXES-20240115-101510.yaml | 6 ++++++ 2 files changed, 13 insertions(+) create mode 100644 .changes/unreleased/BUG FIXES-20240115-101358.yaml create mode 100644 .changes/unreleased/BUG FIXES-20240115-101510.yaml diff --git a/.changes/unreleased/BUG FIXES-20240115-101358.yaml b/.changes/unreleased/BUG FIXES-20240115-101358.yaml new file mode 100644 index 00000000..9571d833 --- /dev/null +++ b/.changes/unreleased/BUG FIXES-20240115-101358.yaml @@ -0,0 +1,7 @@ +kind: BUG FIXES +body: 'data-source/archive_file: Prevent error when generating archive from source + containing symbolically linked directories, and `exclude_symlink_directories` + is set to true' +time: 2024-01-15T10:13:58.177253Z +custom: + Issue: "298" diff --git a/.changes/unreleased/BUG FIXES-20240115-101510.yaml b/.changes/unreleased/BUG FIXES-20240115-101510.yaml new file mode 100644 index 00000000..96259e8b --- /dev/null +++ b/.changes/unreleased/BUG FIXES-20240115-101510.yaml @@ -0,0 +1,6 @@ +kind: BUG FIXES +body: 'resource/archive_file: Prevent error when generating archive from source containing + symbolically linked directories, and `exclude_symlink_directories` is set to true' +time: 2024-01-15T10:15:10.869072Z +custom: + Issue: "298" From b3ddb4c95094d1a6218ae7c5f042450096104d24 Mon Sep 17 00:00:00 2001 From: Benjamin Bennett Date: Wed, 17 Jan 2024 10:21:11 +0000 Subject: [PATCH 3/4] Return error when generating an archive would result in an empty archive (#296) --- .../unreleased/BUG FIXES-20240117-101851.yaml | 5 ++++ .../unreleased/BUG FIXES-20240117-101923.yaml | 5 ++++ .../provider/data_source_archive_file_test.go | 28 +++---------------- internal/provider/zip_archiver.go | 25 +++++++++++++++-- 4 files changed, 36 insertions(+), 27 deletions(-) create mode 100644 .changes/unreleased/BUG FIXES-20240117-101851.yaml create mode 100644 .changes/unreleased/BUG FIXES-20240117-101923.yaml diff --git a/.changes/unreleased/BUG FIXES-20240117-101851.yaml b/.changes/unreleased/BUG FIXES-20240117-101851.yaml new file mode 100644 index 00000000..99e4f247 --- /dev/null +++ b/.changes/unreleased/BUG FIXES-20240117-101851.yaml @@ -0,0 +1,5 @@ +kind: BUG FIXES +body: 'resource/archive_file: Return error when generated archive would be empty' +time: 2024-01-17T10:18:51.941981Z +custom: + Issue: "298" diff --git a/.changes/unreleased/BUG FIXES-20240117-101923.yaml b/.changes/unreleased/BUG FIXES-20240117-101923.yaml new file mode 100644 index 00000000..c51c97e1 --- /dev/null +++ b/.changes/unreleased/BUG FIXES-20240117-101923.yaml @@ -0,0 +1,5 @@ +kind: BUG FIXES +body: 'data-source/archive_file: Return error when generated archive would be empty' +time: 2024-01-17T10:19:23.907477Z +custom: + Issue: "298" diff --git a/internal/provider/data_source_archive_file_test.go b/internal/provider/data_source_archive_file_test.go index ddfec65e..3a78a59d 100644 --- a/internal/provider/data_source_archive_file_test.go +++ b/internal/provider/data_source_archive_file_test.go @@ -980,12 +980,7 @@ func TestAccArchiveFile_SymlinkDirectory_Relative_ExcludeSymlinkDirectories(t *t exclude_symlink_directories = true } `, filepath.ToSlash("test-fixtures/test-symlink-dir"), filepath.ToSlash(f)), - Check: r.ComposeTestCheckFunc( - r.TestCheckResourceAttrWith("data.archive_file.foo", "output_path", func(value string) error { - ensureContents(t, value, map[string][]byte{}) - return nil - }), - ), + ExpectError: regexp.MustCompile(`.*error creating archive: error archiving directory: archive has not been\ncreated as it would be empty`), }, }, }) @@ -1016,12 +1011,7 @@ func TestAccArchiveFile_SymlinkDirectory_Absolute_ExcludeSymlinkDirectories(t *t exclude_symlink_directories = true } `, filepath.ToSlash(symlinkDirWithRegularFilesAbs), filepath.ToSlash(f)), - Check: r.ComposeTestCheckFunc( - r.TestCheckResourceAttrWith("data.archive_file.foo", "output_path", func(value string) error { - ensureContents(t, value, map[string][]byte{}) - return nil - }), - ), + ExpectError: regexp.MustCompile(`.*error creating archive: error archiving directory: archive has not been\ncreated as it would be empty`), }, }, }) @@ -1210,12 +1200,7 @@ func TestAccArchiveFile_DirectoryWithSymlinkDirectory_Relative_ExcludeSymlinkDir exclude_symlink_directories = true } `, filepath.ToSlash("test-fixtures/test-dir-with-symlink-dir"), filepath.ToSlash(f)), - Check: r.ComposeTestCheckFunc( - r.TestCheckResourceAttrWith("data.archive_file.foo", "output_path", func(value string) error { - ensureContents(t, value, map[string][]byte{}) - return nil - }), - ), + ExpectError: regexp.MustCompile(`.*error creating archive: error archiving directory: archive has not been\ncreated as it would be empty`), }, }, }) @@ -1245,12 +1230,7 @@ func TestAccArchiveFile_IncludeDirectoryWithSymlinkDirectory_Absolute_ExcludeSym exclude_symlink_directories = true } `, filepath.ToSlash(symlinkDirInRegularDirAbs), filepath.ToSlash(f)), - Check: r.ComposeTestCheckFunc( - r.TestCheckResourceAttrWith("data.archive_file.foo", "output_path", func(value string) error { - ensureContents(t, value, map[string][]byte{}) - return nil - }), - ), + ExpectError: regexp.MustCompile(`.*error creating archive: error archiving directory: archive has not been\ncreated as it would be empty`), }, }, }) diff --git a/internal/provider/zip_archiver.go b/internal/provider/zip_archiver.go index c5248444..d4c00121 100644 --- a/internal/provider/zip_archiver.go +++ b/internal/provider/zip_archiver.go @@ -107,15 +107,28 @@ func (a *ZipArchiver) ArchiveDir(indirname string, opts ArchiveDirOpts) error { opts.Excludes[i] = filepath.FromSlash(opts.Excludes[i]) } + // Determine whether an empty archive would be generated. + isArchiveEmpty := true + + err = filepath.Walk(indirname, a.createWalkFunc("", indirname, opts, &isArchiveEmpty, true)) + if err != nil { + return err + } + + // Return an error if an empty archive would be generated. + if isArchiveEmpty { + return fmt.Errorf("archive has not been created as it would be empty") + } + if err := a.open(); err != nil { return err } defer a.close() - return filepath.Walk(indirname, a.createWalkFunc("", indirname, opts)) + return filepath.Walk(indirname, a.createWalkFunc("", indirname, opts, &isArchiveEmpty, false)) } -func (a *ZipArchiver) createWalkFunc(basePath string, indirname string, opts ArchiveDirOpts) func(path string, info os.FileInfo, err error) error { +func (a *ZipArchiver) createWalkFunc(basePath, indirname string, opts ArchiveDirOpts, isArchiveEmpty *bool, dryRun bool) func(path string, info os.FileInfo, err error) error { return func(path string, info os.FileInfo, err error) error { if err != nil { return fmt.Errorf("error encountered during file walk: %s", err) @@ -158,7 +171,7 @@ func (a *ZipArchiver) createWalkFunc(basePath string, indirname string, opts Arc if realInfo.IsDir() { if !opts.ExcludeSymlinkDirectories { - return filepath.Walk(realPath, a.createWalkFunc(archivePath, realPath, opts)) + return filepath.Walk(realPath, a.createWalkFunc(archivePath, realPath, opts, isArchiveEmpty, dryRun)) } else { return filepath.SkipDir } @@ -167,6 +180,12 @@ func (a *ZipArchiver) createWalkFunc(basePath string, indirname string, opts Arc info = realInfo } + *isArchiveEmpty = false + + if dryRun { + return nil + } + fh, err := zip.FileInfoHeader(info) if err != nil { return fmt.Errorf("error creating file header: %s", err) From 25ef85cade32a3c6b965e282e4ade0ab2524d27d Mon Sep 17 00:00:00 2001 From: Benjamin Bennett Date: Mon, 22 Jan 2024 14:38:49 +0000 Subject: [PATCH 4/4] Adding equivalent test coverage for archive_file resource (#296) --- .../provider/resource_archive_file_test.go | 373 ++++++++++++++++++ 1 file changed, 373 insertions(+) diff --git a/internal/provider/resource_archive_file_test.go b/internal/provider/resource_archive_file_test.go index 9e9ce08e..84f6c71d 100644 --- a/internal/provider/resource_archive_file_test.go +++ b/internal/provider/resource_archive_file_test.go @@ -1041,6 +1041,379 @@ func TestResource_ArchiveFile_SymlinkFile_Absolute_ExcludeSymlinkDirectories(t * }) } +// TestResource_ArchiveFile_SymlinkDirectory_Relative_ExcludeSymlinkDirectories verifies that an empty archive +// is generated when trying to archive a directory which only contains a symlink to a directory. +func TestResource_ArchiveFile_SymlinkDirectory_Relative_ExcludeSymlinkDirectories(t *testing.T) { + td := t.TempDir() + + f := filepath.Join(td, "zip_file_acc_test.zip") + + r.ParallelTest(t, r.TestCase{ + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Steps: []r.TestStep{ + { + Config: fmt.Sprintf(` + resource "archive_file" "foo" { + type = "zip" + source_dir = "%s" + output_path = "%s" + output_file_mode = "0666" + exclude_symlink_directories = true + } + `, filepath.ToSlash("test-fixtures/test-symlink-dir"), filepath.ToSlash(f)), + ExpectError: regexp.MustCompile(`.*error creating archive: error archiving directory: archive has not been\ncreated as it would be empty`), + }, + }, + }) +} + +// TestResource_ArchiveFile_SymlinkDirectory_Absolute_ExcludeSymlinkDirectories verifies that an empty archive +// is generated when trying to archive a directory which only contains a symlink to a directory. +func TestResource_ArchiveFile_SymlinkDirectory_Absolute_ExcludeSymlinkDirectories(t *testing.T) { + td := t.TempDir() + + f := filepath.Join(td, "zip_file_acc_test.zip") + + symlinkDirWithRegularFilesAbs, err := filepath.Abs("test-fixtures/test-symlink-dir") + if err != nil { + t.Fatal(err) + } + + r.ParallelTest(t, r.TestCase{ + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Steps: []r.TestStep{ + { + Config: fmt.Sprintf(` + resource "archive_file" "foo" { + type = "zip" + source_dir = "%s" + output_path = "%s" + output_file_mode = "0666" + exclude_symlink_directories = true + } + `, filepath.ToSlash(symlinkDirWithRegularFilesAbs), filepath.ToSlash(f)), + ExpectError: regexp.MustCompile(`.*error creating archive: error archiving directory: archive has not been\ncreated as it would be empty`), + }, + }, + }) +} + +// TestResource_ArchiveFile_DirectoryWithSymlinkFile_Relative_ExcludeSymlinkDirectories verifies that a relative path to a +// directory containing a symlink file generates an archive which includes the files in the directory. +func TestResource_ArchiveFile_DirectoryWithSymlinkFile_Relative_ExcludeSymlinkDirectories(t *testing.T) { + td := t.TempDir() + + f := filepath.Join(td, "zip_file_acc_test.zip") + + var fileSize string + + r.ParallelTest(t, r.TestCase{ + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Steps: []r.TestStep{ + { + Config: fmt.Sprintf(` + resource "archive_file" "foo" { + type = "zip" + source_dir = "%s" + output_path = "%s" + output_file_mode = "0666" + exclude_symlink_directories = true + } + `, filepath.ToSlash("test-fixtures/test-dir-with-symlink-file"), filepath.ToSlash(f)), + Check: r.ComposeTestCheckFunc( + testAccArchiveFileSize(f, &fileSize), + r.TestCheckResourceAttrPtr("archive_file.foo", "output_size", &fileSize), + r.TestCheckResourceAttrWith("archive_file.foo", "output_path", func(value string) error { + ensureContents(t, value, map[string][]byte{ + "test-file.txt": []byte(`This is test content`), + "test-symlink.txt": []byte(`This is test content`), + }) + ensureFileMode(t, value, "0666") + return nil + }), + ), + }, + }, + }) +} + +// TestResource_ArchiveFile_DirectoryWithSymlinkFile_Absolute_ExcludeSymlinkDirectories verifies that an absolute path to a +// directory containing a symlink file generates an archive which includes the files in the directory. +func TestResource_ArchiveFile_DirectoryWithSymlinkFile_Absolute_ExcludeSymlinkDirectories(t *testing.T) { + td := t.TempDir() + + f := filepath.Join(td, "zip_file_acc_test.zip") + + symlinkDirWithSymlinkFilesAbs, err := filepath.Abs("test-fixtures/test-dir-with-symlink-file") + if err != nil { + t.Fatal(err) + } + + var fileSize string + + r.ParallelTest(t, r.TestCase{ + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Steps: []r.TestStep{ + { + Config: fmt.Sprintf(` + resource "archive_file" "foo" { + type = "zip" + source_dir = "%s" + output_path = "%s" + output_file_mode = "0666" + exclude_symlink_directories = true + } + `, filepath.ToSlash(symlinkDirWithSymlinkFilesAbs), filepath.ToSlash(f)), + Check: r.ComposeTestCheckFunc( + testAccArchiveFileSize(f, &fileSize), + r.TestCheckResourceAttrPtr("archive_file.foo", "output_size", &fileSize), + r.TestCheckResourceAttrWith("archive_file.foo", "output_path", func(value string) error { + ensureContents(t, value, map[string][]byte{ + "test-file.txt": []byte(`This is test content`), + "test-symlink.txt": []byte(`This is test content`), + }) + ensureFileMode(t, value, "0666") + return nil + }), + ), + }, + }, + }) +} + +// TestResource_ArchiveFile_SymlinkDirectoryWithSymlinkFile_Relative_ExcludeSymlinkDirectories verifies that a relative path +// to a symlink file in a symlink directory generates an archive which includes the files in the directory. +func TestResource_ArchiveFile_SymlinkDirectoryWithSymlinkFile_Relative_ExcludeSymlinkDirectories(t *testing.T) { + td := t.TempDir() + + f := filepath.Join(td, "zip_file_acc_test.zip") + + var fileSize string + + r.ParallelTest(t, r.TestCase{ + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Steps: []r.TestStep{ + { + Config: fmt.Sprintf(` + resource "archive_file" "foo" { + type = "zip" + source_file = "%s" + output_path = "%s" + output_file_mode = "0666" + exclude_symlink_directories = true + } + `, filepath.ToSlash("test-fixtures/test-symlink-dir-with-symlink-file/test-symlink.txt"), filepath.ToSlash(f)), + Check: r.ComposeTestCheckFunc( + testAccArchiveFileSize(f, &fileSize), + r.TestCheckResourceAttrPtr("archive_file.foo", "output_size", &fileSize), + r.TestCheckResourceAttrWith("archive_file.foo", "output_path", func(value string) error { + ensureContents(t, value, map[string][]byte{ + "test-symlink.txt": []byte(`This is test content`), + }) + ensureFileMode(t, value, "0666") + return nil + }), + ), + }, + }, + }) +} + +// TestResource_ArchiveFile_SymlinkDirectoryWithSymlinkFile_Absolute_ExcludeSymlinkDirectories verifies that an absolute path +// to a symlink file in a symlink directory generates an archive which includes the files in the directory. +func TestResource_ArchiveFile_SymlinkDirectoryWithSymlinkFile_Absolute_ExcludeSymlinkDirectories(t *testing.T) { + td := t.TempDir() + + f := filepath.Join(td, "zip_file_acc_test.zip") + + symlinkFileInSymlinkDirAbs, err := filepath.Abs("test-fixtures/test-symlink-dir-with-symlink-file/test-symlink.txt") + if err != nil { + t.Fatal(err) + } + + var fileSize string + + r.ParallelTest(t, r.TestCase{ + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Steps: []r.TestStep{ + { + Config: fmt.Sprintf(` + resource "archive_file" "foo" { + type = "zip" + source_file = "%s" + output_path = "%s" + output_file_mode = "0666" + exclude_symlink_directories = true + } + `, filepath.ToSlash(symlinkFileInSymlinkDirAbs), filepath.ToSlash(f)), + Check: r.ComposeTestCheckFunc( + testAccArchiveFileSize(f, &fileSize), + r.TestCheckResourceAttrPtr("archive_file.foo", "output_size", &fileSize), + r.TestCheckResourceAttrWith("archive_file.foo", "output_path", func(value string) error { + ensureContents(t, value, map[string][]byte{ + "test-symlink.txt": []byte(`This is test content`), + }) + ensureFileMode(t, value, "0666") + return nil + }), + ), + }, + }, + }) +} + +// TestResource_ArchiveFile_DirectoryWithSymlinkDirectory_Relative_ExcludeSymlinkDirectories verifies that an empty archive +// is generated when trying to archive a directory which only contains a symlink to a directory. +func TestResource_ArchiveFile_DirectoryWithSymlinkDirectory_Relative_ExcludeSymlinkDirectories(t *testing.T) { + td := t.TempDir() + + f := filepath.Join(td, "zip_file_acc_test.zip") + + r.ParallelTest(t, r.TestCase{ + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Steps: []r.TestStep{ + { + Config: fmt.Sprintf(` + resource "archive_file" "foo" { + type = "zip" + source_dir = "%s" + output_path = "%s" + exclude_symlink_directories = true + } + `, filepath.ToSlash("test-fixtures/test-dir-with-symlink-dir"), filepath.ToSlash(f)), + ExpectError: regexp.MustCompile(`.*error creating archive: error archiving directory: archive has not been\ncreated as it would be empty`), + }, + }, + }) +} + +// TestResource_ArchiveFile_IncludeDirectoryWithSymlinkDirectory_Absolute_ExcludeSymlinkDirectories verifies that an empty archive +// is generated when trying to archive a directory which only contains a symlink to a directory. +func TestResource_ArchiveFile_IncludeDirectoryWithSymlinkDirectory_Absolute_ExcludeSymlinkDirectories(t *testing.T) { + td := t.TempDir() + + f := filepath.Join(td, "zip_file_acc_test.zip") + + symlinkDirInRegularDirAbs, err := filepath.Abs("test-fixtures/test-dir-with-symlink-dir") + if err != nil { + t.Fatal(err) + } + + r.ParallelTest(t, r.TestCase{ + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Steps: []r.TestStep{ + { + Config: fmt.Sprintf(` + resource "archive_file" "foo" { + type = "zip" + source_dir = "%s" + output_path = "%s" + exclude_symlink_directories = true + } + `, filepath.ToSlash(symlinkDirInRegularDirAbs), filepath.ToSlash(f)), + ExpectError: regexp.MustCompile(`.*error creating archive: error archiving directory: archive has not been\ncreated as it would be empty`), + }, + }, + }) +} + +// TestResource_ArchiveFile_Multiple_Relative_ExcludeSymlinkDirectories verifies that +// symlinked directories are excluded. +func TestResource_ArchiveFile_Multiple_Relative_ExcludeSymlinkDirectories(t *testing.T) { + td := t.TempDir() + + f := filepath.Join(td, "zip_file_acc_test.zip") + + var fileSize string + + r.ParallelTest(t, r.TestCase{ + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Steps: []r.TestStep{ + { + Config: fmt.Sprintf(` + resource "archive_file" "foo" { + type = "zip" + source_dir = "%s" + output_path = "%s" + output_file_mode = "0666" + exclude_symlink_directories = true + } + `, filepath.ToSlash("test-fixtures"), filepath.ToSlash(f)), + Check: r.ComposeTestCheckFunc( + testAccArchiveFileSize(f, &fileSize), + r.TestCheckResourceAttrPtr("archive_file.foo", "output_size", &fileSize), + r.TestCheckResourceAttrWith("archive_file.foo", "output_path", func(value string) error { + ensureContents(t, value, map[string][]byte{ + "test-dir/test-dir1/file1.txt": []byte("This is file 1"), + "test-dir/test-dir1/file2.txt": []byte("This is file 2"), + "test-dir/test-dir1/file3.txt": []byte("This is file 3"), + "test-dir/test-dir2/file1.txt": []byte("This is file 1"), + "test-dir/test-dir2/file2.txt": []byte("This is file 2"), + "test-dir/test-dir2/file3.txt": []byte("This is file 3"), + "test-dir/test-file.txt": []byte("This is test content"), + "test-dir-with-symlink-file/test-file.txt": []byte("This is test content"), + "test-dir-with-symlink-file/test-symlink.txt": []byte("This is test content"), + }) + ensureFileMode(t, value, "0666") + return nil + }), + ), + }, + }, + }) +} + +// TestResource_ArchiveFile_Multiple_Relative_ExcludeSymlinkDirectories verifies that +// symlinked directories are excluded. +func TestResource_ArchiveFile_Multiple_Absolute_ExcludeSymlinkDirectories(t *testing.T) { + td := t.TempDir() + + f := filepath.Join(td, "zip_file_acc_test.zip") + + multipleDirsAndFilesAbs, err := filepath.Abs("test-fixtures") + if err != nil { + t.Fatal(err) + } + + var fileSize string + + r.ParallelTest(t, r.TestCase{ + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Steps: []r.TestStep{ + { + Config: fmt.Sprintf(` + resource "archive_file" "foo" { + type = "zip" + source_dir = "%s" + output_path = "%s" + output_file_mode = "0666" + exclude_symlink_directories = true + } + `, filepath.ToSlash(multipleDirsAndFilesAbs), filepath.ToSlash(f)), + Check: r.ComposeTestCheckFunc( + testAccArchiveFileSize(f, &fileSize), + r.TestCheckResourceAttrPtr("archive_file.foo", "output_size", &fileSize), + r.TestCheckResourceAttrWith("archive_file.foo", "output_path", func(value string) error { + ensureContents(t, value, map[string][]byte{ + "test-dir/test-dir1/file1.txt": []byte("This is file 1"), + "test-dir/test-dir1/file2.txt": []byte("This is file 2"), + "test-dir/test-dir1/file3.txt": []byte("This is file 3"), + "test-dir/test-dir2/file1.txt": []byte("This is file 1"), + "test-dir/test-dir2/file2.txt": []byte("This is file 2"), + "test-dir/test-dir2/file3.txt": []byte("This is file 3"), + "test-dir/test-file.txt": []byte("This is test content"), + "test-dir-with-symlink-file/test-file.txt": []byte("This is test content"), + "test-dir-with-symlink-file/test-symlink.txt": []byte("This is test content"), + }) + ensureFileMode(t, value, "0666") + return nil + }), + ), + }, + }, + }) +} + func alterFileContents(content, path string) { f, err := os.Create(path) if err != nil {