github.com/mponton/terratest@v0.44.0/modules/files/files.go (about) 1 // Package files allows to interact with files on a file system. 2 package files 3 4 import ( 5 "fmt" 6 "io/ioutil" 7 "os" 8 "path/filepath" 9 "strings" 10 11 "github.com/mattn/go-zglob" 12 ) 13 14 // FileExists returns true if the given file exists. 15 func FileExists(path string) bool { 16 _, err := os.Stat(path) 17 return err == nil 18 } 19 20 // FileExistsE returns true if the given file exists 21 // It will return an error if os.Stat error is not an ErrNotExist 22 func FileExistsE(path string) (bool, error) { 23 _, err := os.Stat(path) 24 if err != nil && !os.IsNotExist(err) { 25 return false, err 26 } 27 return err == nil, nil 28 } 29 30 // IsExistingFile returns true if the path exists and is a file. 31 func IsExistingFile(path string) bool { 32 fileInfo, err := os.Stat(path) 33 return err == nil && !fileInfo.IsDir() 34 } 35 36 // IsExistingDir returns true if the path exists and is a directory 37 func IsExistingDir(path string) bool { 38 fileInfo, err := os.Stat(path) 39 return err == nil && fileInfo.IsDir() 40 } 41 42 // CopyTerraformFolderToDest creates a copy of the given folder and all its contents in a specified folder with a unique name and the given prefix. 43 // This is useful when running multiple tests in parallel against the same set of Terraform files to ensure the 44 // tests don't overwrite each other's .terraform working directory and terraform.tfstate files. This method returns 45 // the path to the dest folder with the copied contents. Hidden files and folders (with the exception of the `.terraform-version` files used 46 // by the [tfenv tool](https://github.com/tfutils/tfenv) and `.terraform.lock.hcl` used by Terraform to lock providers versions), Terraform state 47 // files, and terraform.tfvars files are not copied to this temp folder, as you typically don't want them interfering with your tests. 48 // 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. 49 func CopyTerraformFolderToDest(folderPath string, destRootFolder string, tempFolderPrefix string) (string, error) { 50 filter := func(path string) bool { 51 if PathIsTerraformVersionFile(path) || PathIsTerraformLockFile(path) { 52 return true 53 } 54 if PathContainsHiddenFileOrFolder(path) || PathContainsTerraformStateOrVars(path) { 55 return false 56 } 57 return true 58 } 59 60 destFolder, err := CopyFolderToDest(folderPath, destRootFolder, tempFolderPrefix, filter) 61 if err != nil { 62 return "", err 63 } 64 65 return destFolder, nil 66 } 67 68 // CopyTerraformFolderToTemp calls CopyTerraformFolderToDest, passing os.TempDir() as the root destination folder. 69 func CopyTerraformFolderToTemp(folderPath string, tempFolderPrefix string) (string, error) { 70 return CopyTerraformFolderToDest(folderPath, os.TempDir(), tempFolderPrefix) 71 } 72 73 // CopyTerragruntFolderToDest creates a copy of the given folder and all its contents in a specified folder with a unique name and the given prefix. 74 // Since terragrunt uses tfvars files to specify modules, they are copied to the directory as well. 75 // Terraform state files are excluded as well as .terragrunt-cache to avoid overwriting contents. 76 func CopyTerragruntFolderToDest(folderPath string, destRootFolder string, tempFolderPrefix string) (string, error) { 77 filter := func(path string) bool { 78 return !PathContainsHiddenFileOrFolder(path) && !PathContainsTerraformState(path) 79 } 80 81 destFolder, err := CopyFolderToDest(folderPath, destRootFolder, tempFolderPrefix, filter) 82 if err != nil { 83 return "", err 84 } 85 86 return destFolder, nil 87 } 88 89 // CopyTerragruntFolderToTemp calls CopyTerragruntFolderToDest, passing os.TempDir() as the root destination folder. 90 func CopyTerragruntFolderToTemp(folderPath string, tempFolderPrefix string) (string, error) { 91 return CopyTerragruntFolderToDest(folderPath, os.TempDir(), tempFolderPrefix) 92 } 93 94 // CopyFolderToDest creates a copy of the given folder and all its filtered contents in a temp folder 95 // with a unique name and the given prefix. 96 func CopyFolderToDest(folderPath string, destRootFolder string, tempFolderPrefix string, filter func(path string) bool) (string, error) { 97 destRootExists, err := FileExistsE(destRootFolder) 98 if err != nil { 99 return "", err 100 } 101 if !destRootExists { 102 return "", DirNotFoundError{Directory: destRootFolder} 103 } 104 105 exists, err := FileExistsE(folderPath) 106 if err != nil { 107 return "", err 108 } 109 if !exists { 110 return "", DirNotFoundError{Directory: folderPath} 111 } 112 113 tmpDir, err := ioutil.TempDir(destRootFolder, tempFolderPrefix) 114 if err != nil { 115 return "", err 116 } 117 118 // Inside of the temp folder, we create a subfolder that preserves the name of the folder we're copying from. 119 absFolderPath, err := filepath.Abs(folderPath) 120 if err != nil { 121 return "", err 122 } 123 folderName := filepath.Base(absFolderPath) 124 destFolder := filepath.Join(tmpDir, folderName) 125 126 if err := os.MkdirAll(destFolder, 0777); err != nil { 127 return "", err 128 } 129 130 if err := CopyFolderContentsWithFilter(folderPath, destFolder, filter); err != nil { 131 return "", err 132 } 133 134 return destFolder, nil 135 } 136 137 // CopyFolderToTemp calls CopyFolderToDest, passing os.TempDir() as the root destination folder. 138 func CopyFolderToTemp(folderPath string, tempFolderPrefix string, filter func(path string) bool) (string, error) { 139 return CopyFolderToDest(folderPath, os.TempDir(), tempFolderPrefix, filter) 140 } 141 142 // CopyFolderContents copies all the files and folders within the given source folder to the destination folder. 143 func CopyFolderContents(source string, destination string) error { 144 return CopyFolderContentsWithFilter(source, destination, func(path string) bool { 145 return true 146 }) 147 } 148 149 // CopyFolderContentsWithFilter copies the files and folders within the given source folder that pass the given filter (return true) to the 150 // destination folder. 151 func CopyFolderContentsWithFilter(source string, destination string, filter func(path string) bool) error { 152 files, err := ioutil.ReadDir(source) 153 if err != nil { 154 return err 155 } 156 157 for _, file := range files { 158 src := filepath.Join(source, file.Name()) 159 dest := filepath.Join(destination, file.Name()) 160 161 if !filter(src) { 162 continue 163 } else if file.IsDir() { 164 if err := os.MkdirAll(dest, file.Mode()); err != nil { 165 return err 166 } 167 168 if err := CopyFolderContentsWithFilter(src, dest, filter); err != nil { 169 return err 170 } 171 172 } else if isSymLink(file) { 173 if err := copySymLink(src, dest); err != nil { 174 return err 175 } 176 } else { 177 if err := CopyFile(src, dest); err != nil { 178 return err 179 } 180 } 181 } 182 183 return nil 184 } 185 186 // PathContainsTerraformStateOrVars returns true if the path corresponds to a Terraform state file or .tfvars/.tfvars.json file. 187 func PathContainsTerraformStateOrVars(path string) bool { 188 filename := filepath.Base(path) 189 return filename == "terraform.tfstate" || filename == "terraform.tfstate.backup" || filename == "terraform.tfvars" || filename == "terraform.tfvars.json" 190 } 191 192 // PathContainsTerraformState returns true if the path corresponds to a Terraform state file. 193 func PathContainsTerraformState(path string) bool { 194 filename := filepath.Base(path) 195 return filename == "terraform.tfstate" || filename == "terraform.tfstate.backup" 196 } 197 198 // PathContainsHiddenFileOrFolder returns true if the given path contains a hidden file or folder. 199 func PathContainsHiddenFileOrFolder(path string) bool { 200 pathParts := strings.Split(path, string(filepath.Separator)) 201 for _, pathPart := range pathParts { 202 if strings.HasPrefix(pathPart, ".") && pathPart != "." && pathPart != ".." { 203 return true 204 } 205 } 206 return false 207 } 208 209 // PathIsTerraformVersionFile returns true if the given path is the special '.terraform-version' file used by the [tfenv](https://github.com/tfutils/tfenv) tool. 210 func PathIsTerraformVersionFile(path string) bool { 211 return filepath.Base(path) == ".terraform-version" 212 } 213 214 // PathIsTerraformLockFile return true if the given path is the special '.terraform.lock.hcl' file used by Terraform to lock providers versions 215 func PathIsTerraformLockFile(path string) bool { 216 return filepath.Base(path) == ".terraform.lock.hcl" 217 } 218 219 // CopyFile copies a file from source to destination. 220 func CopyFile(source string, destination string) error { 221 contents, err := ioutil.ReadFile(source) 222 if err != nil { 223 return err 224 } 225 226 return WriteFileWithSamePermissions(source, destination, contents) 227 } 228 229 // WriteFileWithSamePermissions writes a file to the given destination with the given contents using the same permissions as the file at source. 230 func WriteFileWithSamePermissions(source string, destination string, contents []byte) error { 231 fileInfo, err := os.Stat(source) 232 if err != nil { 233 return err 234 } 235 236 return ioutil.WriteFile(destination, contents, fileInfo.Mode()) 237 } 238 239 // isSymLink returns true if the given file is a symbolic link 240 // Per https://stackoverflow.com/a/18062079/2308858 241 func isSymLink(file os.FileInfo) bool { 242 return file.Mode()&os.ModeSymlink != 0 243 } 244 245 // copySymLink copies the source symbolic link to the given destination. 246 func copySymLink(source string, destination string) error { 247 symlinkPath, err := os.Readlink(source) 248 if err != nil { 249 return err 250 } 251 252 err = os.Symlink(symlinkPath, destination) 253 if err != nil { 254 return err 255 } 256 257 return nil 258 } 259 260 // FindTerraformSourceFilesInDir given a directory path, finds all the terraform source files contained in it. This will 261 // recursively search subdirectories, but will ignore any hidden files (which in turn ignores terraform data dirs like 262 // .terraform folder). 263 func FindTerraformSourceFilesInDir(dirPath string) ([]string, error) { 264 pattern := fmt.Sprintf("%s/**/*.tf", dirPath) 265 matches, err := zglob.Glob(pattern) 266 if err != nil { 267 return nil, err 268 } 269 270 tfFiles := []string{} 271 for _, match := range matches { 272 // Don't include hidden .terraform directories when finding paths to validate 273 if !PathContainsHiddenFileOrFolder(match) { 274 tfFiles = append(tfFiles, match) 275 } 276 } 277 return tfFiles, nil 278 }