From 9e70f88de1b97a66c6da29ecd0f4155d44722b09 Mon Sep 17 00:00:00 2001 From: Scott Heath Date: Tue, 1 Feb 2022 12:03:16 -0600 Subject: [PATCH 1/4] add support for providing a dest folder for tf copy --- modules/files/files.go | 69 ++++++++++++++++++++++++ modules/files/files_test.go | 34 ++++++++++++ modules/test-structure/test_structure.go | 58 ++++++++++++++++++++ 3 files changed, 161 insertions(+) diff --git a/modules/files/files.go b/modules/files/files.go index 2c928bb4a..5a1526a0a 100644 --- a/modules/files/files.go +++ b/modules/files/files.go @@ -64,6 +64,32 @@ func CopyTerraformFolderToTemp(folderPath string, tempFolderPrefix string) (stri return destFolder, nil } +// CopyTerraformFolderToDest creates a copy of the given folder and all its contents in a specified folder with a unique name and the given prefix. +// This is useful when running multiple tests in parallel against the same set of Terraform files to ensure the +// tests don't overwrite each other's .terraform working directory and terraform.tfstate files. This method returns +// the path to the dest folder with the copied contents. Hidden files and folders (with the exception of the `.terraform-version` files used +// by the [tfenv tool](https://github.com/tfutils/tfenv)), Terraform state files, and terraform.tfvars files are not copied to this temp folder, +// as you typically don't want them interfering with your tests. +// This method is useful when running through a build tool so the files are copied to a destination that is cleaned on each run of the pipeline. +func CopyTerraformFolderToDest(destRootFolder string, folderPath string, tempFolderPrefix string) (string, error) { + filter := func(path string) bool { + if PathIsTerraformVersionFile(path) { + return true + } + if PathContainsHiddenFileOrFolder(path) || PathContainsTerraformStateOrVars(path) { + return false + } + return true + } + + destFolder, err := CopyFolderToDest(destRootFolder, folderPath, tempFolderPrefix, filter) + if err != nil { + return "", err + } + + return destFolder, nil +} + // CopyTerragruntFolderToTemp creates a copy of the given folder and all its contents in a temp folder with a unique name and the given prefix. // Since terragrunt uses tfvars files to specify modules, they are copied to the temporary directory as well. // Terraform state files are excluded as well as .terragrunt-cache to avoid overwriting contents. @@ -115,6 +141,49 @@ func CopyFolderToTemp(folderPath string, tempFolderPrefix string, filter func(pa return destFolder, nil } +// CopyFolderToTemp creates a copy of the given folder and all its filtered contents in a temp folder +// with a unique name and the given prefix. +func CopyFolderToDest(destRootFolder string, folderPath string, tempFolderPrefix string, filter func(path string) bool) (string, error) { + destRootExists, err := FileExistsE(destRootFolder) + if err != nil { + return "", err + } + if !destRootExists { + return "", DirNotFoundError{Directory: destRootFolder} + } + + exists, err := FileExistsE(folderPath) + if err != nil { + return "", err + } + if !exists { + return "", DirNotFoundError{Directory: folderPath} + } + + tmpDir, err := ioutil.TempDir(destRootFolder, tempFolderPrefix) + if err != nil { + return "", err + } + + // Inside of the temp folder, we create a subfolder that preserves the name of the folder we're copying from. + absFolderPath, err := filepath.Abs(folderPath) + if err != nil { + return "", err + } + folderName := filepath.Base(absFolderPath) + destFolder := filepath.Join(tmpDir, folderName) + + if err := os.MkdirAll(destFolder, 0777); err != nil { + return "", err + } + + if err := CopyFolderContentsWithFilter(folderPath, destFolder, filter); err != nil { + return "", err + } + + return destFolder, nil +} + // CopyFolderContents copies all the files and folders within the given source folder to the destination folder. func CopyFolderContents(source string, destination string) error { return CopyFolderContentsWithFilter(source, destination, func(path string) bool { diff --git a/modules/files/files_test.go b/modules/files/files_test.go index 520567479..ef5229cee 100644 --- a/modules/files/files_test.go +++ b/modules/files/files_test.go @@ -68,6 +68,27 @@ func TestCopyFolderToTemp(t *testing.T) { assert.NoError(t, err) } +func TestCopyFolderToDest(t *testing.T) { + t.Parallel() + + tempFolderPrefix := "someprefix" + destFolder := os.TempDir() + tmpDir, err := ioutil.TempDir("", "TestCopyFolderContents") + require.NoError(t, err) + + filter := func(path string) bool { + return !PathContainsHiddenFileOrFolder(path) && !PathContainsTerraformState(path) + } + + folder, err := CopyFolderToDest(destFolder, "/not/a/real/path", tempFolderPrefix, filter) + require.Error(t, err) + assert.False(t, FileExists(folder)) + + folder, err = CopyFolderToDest(destFolder, tmpDir, tempFolderPrefix, filter) + assert.DirExists(t, folder) + assert.NoError(t, err) +} + func TestCopyFolderContents(t *testing.T) { t.Parallel() @@ -161,6 +182,19 @@ func TestCopyTerraformFolderToTemp(t *testing.T) { requireDirectoriesEqual(t, expectedDir, tmpDir) } +func TestCopyTerraformFolderToDest(t *testing.T) { + t.Parallel() + + originalDir := filepath.Join(copyFolderContentsFixtureRoot, "original") + expectedDir := filepath.Join(copyFolderContentsFixtureRoot, "no-hidden-files-no-terraform-files") + destFolder := os.TempDir() + + tmpDir, err := CopyTerraformFolderToDest(destFolder, originalDir, "TestCopyTerraformFolderToTemp") + require.NoError(t, err) + + requireDirectoriesEqual(t, expectedDir, tmpDir) +} + func TestCopyTerragruntFolderToTemp(t *testing.T) { t.Parallel() diff --git a/modules/test-structure/test_structure.go b/modules/test-structure/test_structure.go index dcc5678e7..4ddab0e52 100644 --- a/modules/test-structure/test_structure.go +++ b/modules/test-structure/test_structure.go @@ -97,6 +97,64 @@ func CopyTerraformFolderToTemp(t testing.TestingT, rootFolder string, terraformM return tmpTestFolder } +// CopyTerraformFolderToDest copies the given root folder to a randomly-named temp folder and return the path to the +// given terraform modules folder within the new temp root folder. This is useful when running multiple tests in +// parallel against the same set of Terraform files to ensure the tests don't overwrite each other's .terraform working +// directory and terraform.tfstate files. To ensure relative paths work, we copy over the entire root folder to a temp +// folder, and then return the path within that temp folder to the given terraform module dir, which is where the actual +// test will be running. +// For example, suppose you had the target terraform folder you want to test in "/examples/terraform-aws-example" +// relative to the repo root. If your tests reside in the "/test" relative to the root, then you will use this as +// follows: +// +// // Destination for the copy of the files. In this example we are using the Azure Dev Ops variable +// // for the folder that is cleaned after each pipeline job. +// destRootFolder := os.Getenv("AGENT_TEMPDIRECTORY") +// +// // Root folder where terraform files should be (relative to the test folder) +// rootFolder := ".." +// +// // Relative path to terraform module being tested from the root folder +// terraformFolderRelativeToRoot := "examples/terraform-aws-example" +// +// // Copy the terraform folder to a temp folder +// tempTestFolder := test_structure.CopyTerraformFolderToTemp(t, destRootFolder, rootFolder, terraformFolderRelativeToRoot) +// +// // Make sure to use the temp test folder in the terraform options +// terraformOptions := &terraform.Options{ +// TerraformDir: tempTestFolder, +// } +// +// Note that if any of the SKIP_ environment variables is set, we assume this is a test in the local dev where +// there are no other concurrent tests running and we want to be able to cache test data between test stages, so in that +// case, we do NOT copy anything to a temp folder, and return the path to the original terraform module folder instead. +func CopyTerraformFolderToDest(t testing.TestingT, destRootFolder string, rootFolder string, terraformModuleFolder string) string { + if SkipStageEnvVarSet() { + logger.Logf(t, "A SKIP_XXX environment variable is set. Using original examples folder rather than a temp folder so we can cache data between stages for faster local testing.") + return filepath.Join(rootFolder, terraformModuleFolder) + } + + fullTerraformModuleFolder := filepath.Join(rootFolder, terraformModuleFolder) + + exists, err := files.FileExistsE(fullTerraformModuleFolder) + require.NoError(t, err) + if !exists { + t.Fatal(files.DirNotFoundError{Directory: fullTerraformModuleFolder}) + } + + tmpRootFolder, err := files.CopyTerraformFolderToDest(destRootFolder, rootFolder, cleanName(t.Name())) + if err != nil { + t.Fatal(err) + } + + tmpTestFolder := filepath.Join(tmpRootFolder, terraformModuleFolder) + + // Log temp folder so we can see it + logger.Logf(t, "Copied terraform folder %s to %s", fullTerraformModuleFolder, tmpTestFolder) + + return tmpTestFolder +} + func cleanName(originalName string) string { parts := strings.Split(originalName, "/") return parts[len(parts)-1] From 82b8f8b947655510597e715cdb2bccdd8b0f63ed Mon Sep 17 00:00:00 2001 From: Scott Heath Date: Wed, 2 Feb 2022 07:13:28 -0600 Subject: [PATCH 2/4] reordered params; added CopyTerragruntFolderToDest --- modules/files/files.go | 24 ++++++++++++++++++++---- modules/files/files_test.go | 19 ++++++++++++++++--- modules/test-structure/test_structure.go | 4 ++-- 3 files changed, 38 insertions(+), 9 deletions(-) diff --git a/modules/files/files.go b/modules/files/files.go index 5a1526a0a..e882cd0d5 100644 --- a/modules/files/files.go +++ b/modules/files/files.go @@ -71,7 +71,7 @@ func CopyTerraformFolderToTemp(folderPath string, tempFolderPrefix string) (stri // by the [tfenv tool](https://github.com/tfutils/tfenv)), Terraform state files, and terraform.tfvars files are not copied to this temp folder, // as you typically don't want them interfering with your tests. // This method is useful when running through a build tool so the files are copied to a destination that is cleaned on each run of the pipeline. -func CopyTerraformFolderToDest(destRootFolder string, folderPath string, tempFolderPrefix string) (string, error) { +func CopyTerraformFolderToDest(folderPath string, destRootFolder string, tempFolderPrefix string) (string, error) { filter := func(path string) bool { if PathIsTerraformVersionFile(path) { return true @@ -82,7 +82,7 @@ func CopyTerraformFolderToDest(destRootFolder string, folderPath string, tempFol return true } - destFolder, err := CopyFolderToDest(destRootFolder, folderPath, tempFolderPrefix, filter) + destFolder, err := CopyFolderToDest(folderPath, destRootFolder, tempFolderPrefix, filter) if err != nil { return "", err } @@ -106,6 +106,22 @@ func CopyTerragruntFolderToTemp(folderPath string, tempFolderPrefix string) (str return destFolder, nil } +// CopyTerragruntFolderToDest creates a copy of the given folder and all its contents in a specified folder with a unique name and the given prefix. +// Since terragrunt uses tfvars files to specify modules, they are copied to the directory as well. +// Terraform state files are excluded as well as .terragrunt-cache to avoid overwriting contents. +func CopyTerragruntFolderToDest(folderPath string, destRootFolder string, tempFolderPrefix string) (string, error) { + filter := func(path string) bool { + return !PathContainsHiddenFileOrFolder(path) && !PathContainsTerraformState(path) + } + + destFolder, err := CopyFolderToDest(folderPath, destRootFolder, tempFolderPrefix, filter) + if err != nil { + return "", err + } + + return destFolder, nil +} + // CopyFolderToTemp creates a copy of the given folder and all its filtered contents in a temp folder // with a unique name and the given prefix. func CopyFolderToTemp(folderPath string, tempFolderPrefix string, filter func(path string) bool) (string, error) { @@ -141,9 +157,9 @@ func CopyFolderToTemp(folderPath string, tempFolderPrefix string, filter func(pa return destFolder, nil } -// CopyFolderToTemp creates a copy of the given folder and all its filtered contents in a temp folder +// CopyFolderToDest creates a copy of the given folder and all its filtered contents in a temp folder // with a unique name and the given prefix. -func CopyFolderToDest(destRootFolder string, folderPath string, tempFolderPrefix string, filter func(path string) bool) (string, error) { +func CopyFolderToDest(folderPath string, destRootFolder string, tempFolderPrefix string, filter func(path string) bool) (string, error) { destRootExists, err := FileExistsE(destRootFolder) if err != nil { return "", err diff --git a/modules/files/files_test.go b/modules/files/files_test.go index ef5229cee..ccda6b943 100644 --- a/modules/files/files_test.go +++ b/modules/files/files_test.go @@ -80,11 +80,11 @@ func TestCopyFolderToDest(t *testing.T) { return !PathContainsHiddenFileOrFolder(path) && !PathContainsTerraformState(path) } - folder, err := CopyFolderToDest(destFolder, "/not/a/real/path", tempFolderPrefix, filter) + folder, err := CopyFolderToDest("/not/a/real/path", destFolder, tempFolderPrefix, filter) require.Error(t, err) assert.False(t, FileExists(folder)) - folder, err = CopyFolderToDest(destFolder, tmpDir, tempFolderPrefix, filter) + folder, err = CopyFolderToDest(tmpDir, destFolder, tempFolderPrefix, filter) assert.DirExists(t, folder) assert.NoError(t, err) } @@ -189,7 +189,7 @@ func TestCopyTerraformFolderToDest(t *testing.T) { expectedDir := filepath.Join(copyFolderContentsFixtureRoot, "no-hidden-files-no-terraform-files") destFolder := os.TempDir() - tmpDir, err := CopyTerraformFolderToDest(destFolder, originalDir, "TestCopyTerraformFolderToTemp") + tmpDir, err := CopyTerraformFolderToDest(originalDir, destFolder, "TestCopyTerraformFolderToTemp") require.NoError(t, err) requireDirectoriesEqual(t, expectedDir, tmpDir) @@ -207,6 +207,19 @@ func TestCopyTerragruntFolderToTemp(t *testing.T) { requireDirectoriesEqual(t, expectedDir, tmpDir) } +func TestCopyTerragruntFolderToDest(t *testing.T) { + t.Parallel() + + originalDir := filepath.Join(copyFolderContentsFixtureRoot, "terragrunt-files") + expectedDir := filepath.Join(copyFolderContentsFixtureRoot, "no-state-files") + destFolder := os.TempDir() + + tmpDir, err := CopyTerragruntFolderToDest(originalDir, destFolder, t.Name()) + require.NoError(t, err) + + requireDirectoriesEqual(t, expectedDir, tmpDir) +} + func TestPathContainsTerraformStateOrVars(t *testing.T) { var data = []struct { desc string diff --git a/modules/test-structure/test_structure.go b/modules/test-structure/test_structure.go index 4ddab0e52..bd9706794 100644 --- a/modules/test-structure/test_structure.go +++ b/modules/test-structure/test_structure.go @@ -128,7 +128,7 @@ func CopyTerraformFolderToTemp(t testing.TestingT, rootFolder string, terraformM // Note that if any of the SKIP_ environment variables is set, we assume this is a test in the local dev where // there are no other concurrent tests running and we want to be able to cache test data between test stages, so in that // case, we do NOT copy anything to a temp folder, and return the path to the original terraform module folder instead. -func CopyTerraformFolderToDest(t testing.TestingT, destRootFolder string, rootFolder string, terraformModuleFolder string) string { +func CopyTerraformFolderToDest(t testing.TestingT, rootFolder string, terraformModuleFolder string, destRootFolder string) string { if SkipStageEnvVarSet() { logger.Logf(t, "A SKIP_XXX environment variable is set. Using original examples folder rather than a temp folder so we can cache data between stages for faster local testing.") return filepath.Join(rootFolder, terraformModuleFolder) @@ -142,7 +142,7 @@ func CopyTerraformFolderToDest(t testing.TestingT, destRootFolder string, rootFo t.Fatal(files.DirNotFoundError{Directory: fullTerraformModuleFolder}) } - tmpRootFolder, err := files.CopyTerraformFolderToDest(destRootFolder, rootFolder, cleanName(t.Name())) + tmpRootFolder, err := files.CopyTerraformFolderToDest(rootFolder, cleanName(t.Name()), destRootFolder) if err != nil { t.Fatal(err) } From 14d1cadd0f2e9de157cf8b895d4df90a9af97ce7 Mon Sep 17 00:00:00 2001 From: Scott Heath Date: Wed, 2 Feb 2022 21:34:16 -0600 Subject: [PATCH 3/4] switch to using CopyFolderToDest --- modules/files/files.go | 41 ++++--------------------------------- modules/files/files_test.go | 20 ------------------ 2 files changed, 4 insertions(+), 57 deletions(-) diff --git a/modules/files/files.go b/modules/files/files.go index e882cd0d5..35f3a48b3 100644 --- a/modules/files/files.go +++ b/modules/files/files.go @@ -56,7 +56,8 @@ func CopyTerraformFolderToTemp(folderPath string, tempFolderPrefix string) (stri return true } - destFolder, err := CopyFolderToTemp(folderPath, tempFolderPrefix, filter) + destRootFolder := os.TempDir() + destFolder, err := CopyFolderToDest(folderPath, destRootFolder, tempFolderPrefix, filter) if err != nil { return "", err } @@ -98,7 +99,8 @@ func CopyTerragruntFolderToTemp(folderPath string, tempFolderPrefix string) (str return !PathContainsHiddenFileOrFolder(path) && !PathContainsTerraformState(path) } - destFolder, err := CopyFolderToTemp(folderPath, tempFolderPrefix, filter) + destRootFolder := os.TempDir() + destFolder, err := CopyFolderToDest(folderPath, destRootFolder, tempFolderPrefix, filter) if err != nil { return "", err } @@ -122,41 +124,6 @@ func CopyTerragruntFolderToDest(folderPath string, destRootFolder string, tempFo return destFolder, nil } -// CopyFolderToTemp creates a copy of the given folder and all its filtered contents in a temp folder -// with a unique name and the given prefix. -func CopyFolderToTemp(folderPath string, tempFolderPrefix string, filter func(path string) bool) (string, error) { - exists, err := FileExistsE(folderPath) - if err != nil { - return "", err - } - if !exists { - return "", DirNotFoundError{Directory: folderPath} - } - - tmpDir, err := ioutil.TempDir("", tempFolderPrefix) - if err != nil { - return "", err - } - - // Inside of the temp folder, we create a subfolder that preserves the name of the folder we're copying from. - absFolderPath, err := filepath.Abs(folderPath) - if err != nil { - return "", err - } - folderName := filepath.Base(absFolderPath) - destFolder := filepath.Join(tmpDir, folderName) - - if err := os.MkdirAll(destFolder, 0777); err != nil { - return "", err - } - - if err := CopyFolderContentsWithFilter(folderPath, destFolder, filter); err != nil { - return "", err - } - - return destFolder, nil -} - // CopyFolderToDest creates a copy of the given folder and all its filtered contents in a temp folder // with a unique name and the given prefix. func CopyFolderToDest(folderPath string, destRootFolder string, tempFolderPrefix string, filter func(path string) bool) (string, error) { diff --git a/modules/files/files_test.go b/modules/files/files_test.go index ccda6b943..cf81d2f2e 100644 --- a/modules/files/files_test.go +++ b/modules/files/files_test.go @@ -48,26 +48,6 @@ func TestIsExistingDir(t *testing.T) { assert.True(t, IsExistingDir(currentFileDir)) } -func TestCopyFolderToTemp(t *testing.T) { - t.Parallel() - - tempFolderPrefix := "someprefix" - tmpDir, err := ioutil.TempDir("", "TestCopyFolderContents") - require.NoError(t, err) - - filter := func(path string) bool { - return !PathContainsHiddenFileOrFolder(path) && !PathContainsTerraformState(path) - } - - folder, err := CopyFolderToTemp("/not/a/real/path", tempFolderPrefix, filter) - require.Error(t, err) - assert.False(t, FileExists(folder)) - - folder, err = CopyFolderToTemp(tmpDir, tempFolderPrefix, filter) - assert.DirExists(t, folder) - assert.NoError(t, err) -} - func TestCopyFolderToDest(t *testing.T) { t.Parallel() From fc8f13fd5789f2a7e5d7477c4e1071481b840ea2 Mon Sep 17 00:00:00 2001 From: Scott Heath Date: Fri, 4 Feb 2022 21:41:41 -0600 Subject: [PATCH 4/4] Make CopyXToTemp funcs wrappers for CopyXToDest --- modules/files/files.go | 54 ++++++++++-------------------------------- 1 file changed, 13 insertions(+), 41 deletions(-) diff --git a/modules/files/files.go b/modules/files/files.go index 35f3a48b3..a93b2ac5f 100644 --- a/modules/files/files.go +++ b/modules/files/files.go @@ -39,32 +39,6 @@ func IsExistingDir(path string) bool { return err == nil && fileInfo.IsDir() } -// CopyTerraformFolderToTemp creates a copy of the given folder and all its contents in a temp folder with a unique name and the given prefix. -// This is useful when running multiple tests in parallel against the same set of Terraform files to ensure the -// tests don't overwrite each other's .terraform working directory and terraform.tfstate files. This method returns -// the path to the temp folder with the copied contents. Hidden files and folders (with the exception of the `.terraform-version` files used -// by the [tfenv tool](https://github.com/tfutils/tfenv)), Terraform state files, and terraform.tfvars files are not copied to this temp folder, -// as you typically don't want them interfering with your tests. -func CopyTerraformFolderToTemp(folderPath string, tempFolderPrefix string) (string, error) { - filter := func(path string) bool { - if PathIsTerraformVersionFile(path) { - return true - } - if PathContainsHiddenFileOrFolder(path) || PathContainsTerraformStateOrVars(path) { - return false - } - return true - } - - destRootFolder := os.TempDir() - destFolder, err := CopyFolderToDest(folderPath, destRootFolder, tempFolderPrefix, filter) - if err != nil { - return "", err - } - - return destFolder, nil -} - // CopyTerraformFolderToDest creates a copy of the given folder and all its contents in a specified folder with a unique name and the given prefix. // This is useful when running multiple tests in parallel against the same set of Terraform files to ensure the // tests don't overwrite each other's .terraform working directory and terraform.tfstate files. This method returns @@ -91,21 +65,9 @@ func CopyTerraformFolderToDest(folderPath string, destRootFolder string, tempFol return destFolder, nil } -// CopyTerragruntFolderToTemp creates a copy of the given folder and all its contents in a temp folder with a unique name and the given prefix. -// Since terragrunt uses tfvars files to specify modules, they are copied to the temporary directory as well. -// Terraform state files are excluded as well as .terragrunt-cache to avoid overwriting contents. -func CopyTerragruntFolderToTemp(folderPath string, tempFolderPrefix string) (string, error) { - filter := func(path string) bool { - return !PathContainsHiddenFileOrFolder(path) && !PathContainsTerraformState(path) - } - - destRootFolder := os.TempDir() - destFolder, err := CopyFolderToDest(folderPath, destRootFolder, tempFolderPrefix, filter) - if err != nil { - return "", err - } - - return destFolder, nil +// CopyTerraformFolderToTemp calls CopyTerraformFolderToDest, passing os.TempDir() as the root destination folder. +func CopyTerraformFolderToTemp(folderPath string, tempFolderPrefix string) (string, error) { + return CopyTerraformFolderToDest(folderPath, os.TempDir(), tempFolderPrefix) } // CopyTerragruntFolderToDest creates a copy of the given folder and all its contents in a specified folder with a unique name and the given prefix. @@ -124,6 +86,11 @@ func CopyTerragruntFolderToDest(folderPath string, destRootFolder string, tempFo return destFolder, nil } +// CopyTerragruntFolderToTemp calls CopyTerragruntFolderToDest, passing os.TempDir() as the root destination folder. +func CopyTerragruntFolderToTemp(folderPath string, tempFolderPrefix string) (string, error) { + return CopyTerragruntFolderToDest(folderPath, os.TempDir(), tempFolderPrefix) +} + // CopyFolderToDest creates a copy of the given folder and all its filtered contents in a temp folder // with a unique name and the given prefix. func CopyFolderToDest(folderPath string, destRootFolder string, tempFolderPrefix string, filter func(path string) bool) (string, error) { @@ -167,6 +134,11 @@ func CopyFolderToDest(folderPath string, destRootFolder string, tempFolderPrefix return destFolder, nil } +// CopyFolderToTemp calls CopyFolderToDest, passing os.TempDir() as the root destination folder. +func CopyFolderToTemp(folderPath string, tempFolderPrefix string, filter func(path string) bool) (string, error) { + return CopyFolderToDest(folderPath, os.TempDir(), tempFolderPrefix, filter) +} + // CopyFolderContents copies all the files and folders within the given source folder to the destination folder. func CopyFolderContents(source string, destination string) error { return CopyFolderContentsWithFilter(source, destination, func(path string) bool {