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  }