github.com/mponton/terratest@v0.44.0/modules/test-structure/validate_struct.go (about) 1 package test_structure 2 3 import ( 4 "fmt" 5 "path" 6 "path/filepath" 7 8 go_commons_collections "github.com/gruntwork-io/go-commons/collections" 9 "github.com/mattn/go-zglob" 10 "github.com/mponton/terratest/modules/collections" 11 "github.com/mponton/terratest/modules/files" 12 ) 13 14 // ValidateFileType is the underlying module type to search for when performing validation. Either Terraform or Terragrunt 15 // files are targeted during a given validation sweep 16 type ValidateFileType string 17 18 const ( 19 // TF represents repositories that contain Terraform code 20 TF = "*.tf" 21 // TG represents repositories that contain Terragrunt code 22 TG = "terragrunt.hcl" 23 ) 24 25 // ValidationOptions represent the configuration for a given validation sweep of a target repo 26 type ValidationOptions struct { 27 // The target directory to recursively search for all Terraform directories (those that contain .tf files) 28 // If you provide RootDir and do not pass entries in either IncludeDirs or ExcludeDirs, then all Terraform directories 29 // From the RootDir, recursively, will be validated 30 RootDir string 31 FileType ValidateFileType 32 // If you only want to include certain sub directories of RootDir, add the absolute paths here. For example, if the 33 // RootDir is /home/project and you want to only include /home/project/examples, add /home/project/examples here 34 // Note that while the struct requires full paths, you can pass relative paths to the NewValidationOptions function 35 // which will build the full paths based on the supplied RootDir 36 IncludeDirs []string 37 // If you want to explicitly exclude certain sub directories of RootDir, add the absolute paths here. For example, if the 38 // RootDir is /home/project and you want to include everything EXCEPT /home/project/modules, add 39 // /home/project/modules to this slice. Note that ExcludeDirs is only considered when IncludeDirs is not passed 40 // Note that while the struct requires full paths, you can pass relative paths to the NewValidationOptions function 41 // which will build the full paths based on the supplied RootDir 42 ExcludeDirs []string 43 } 44 45 // configureBaseValidationOptions returns a pointer to a ValidationOptions struct configured with sane, override-able defaults 46 // Note that the ValidationOptions's fields IncludeDirs and ExcludeDirs must be absolute paths, but this method will accept relative paths 47 // and build the absolute paths when instantiating the ValidationOptions struct, making it the preferred means of configuring 48 // ValidationOptions. 49 // 50 // For example, if your RootDir is /home/project/ and you want to exclude "modules" and "test" you need 51 // only pass the relative paths in your excludeDirs slice like so: 52 // opts, err := NewValidationOptions("/home/project", []string{}, []string{"modules", "test"}) 53 func configureBaseValidationOptions(rootDir string, includeDirs, excludeDirs []string) (*ValidationOptions, error) { 54 vo := &ValidationOptions{ 55 RootDir: "", 56 IncludeDirs: []string{}, 57 ExcludeDirs: []string{}, 58 } 59 60 if rootDir == "" { 61 return nil, ValidationUndefinedRootDirErr{} 62 } 63 64 if !filepath.IsAbs(rootDir) { 65 rootDirAbs, err := filepath.Abs(rootDir) 66 if err != nil { 67 return nil, ValidationAbsolutePathErr{rootDir: rootDir} 68 } 69 rootDir = rootDirAbs 70 } 71 72 vo.RootDir = filepath.Clean(rootDir) 73 74 if len(includeDirs) > 0 { 75 vo.IncludeDirs = buildFullPathsFromRelative(vo.RootDir, includeDirs) 76 } 77 78 if len(excludeDirs) > 0 { 79 vo.ExcludeDirs = buildFullPathsFromRelative(vo.RootDir, excludeDirs) 80 } 81 82 return vo, nil 83 } 84 85 // NewValidationOptions returns a ValidationOptions struct, with override-able sane defaults, configured to find 86 // and process all directories containing .tf files 87 func NewValidationOptions(rootDir string, includeDirs, excludeDirs []string) (*ValidationOptions, error) { 88 opts, err := configureBaseValidationOptions(rootDir, includeDirs, excludeDirs) 89 if err != nil { 90 return opts, err 91 } 92 opts.FileType = TF 93 return opts, nil 94 } 95 96 // NewTerragruntValidationOptions returns a ValidationOptions struct, with override-able sane defaults, configured to find 97 // and process all directories containing .hcl files. 98 func NewTerragruntValidationOptions(rootDir string, includeDirs, excludeDirs []string) (*ValidationOptions, error) { 99 opts, err := configureBaseValidationOptions(rootDir, includeDirs, excludeDirs) 100 if err != nil { 101 return opts, err 102 } 103 opts.FileType = TG 104 return opts, nil 105 } 106 107 func buildFullPathsFromRelative(rootDir string, relativePaths []string) []string { 108 var fullPaths []string 109 for _, maybeRelativePath := range relativePaths { 110 // If the relativePath is already an absolute path, don't modify it 111 if filepath.IsAbs(maybeRelativePath) { 112 fullPaths = append(fullPaths, filepath.Clean(maybeRelativePath)) 113 } else { 114 fullPaths = append(fullPaths, filepath.Clean(filepath.Join(rootDir, maybeRelativePath))) 115 } 116 } 117 return fullPaths 118 } 119 120 // FindTerraformModulePathsInRootE returns a slice strings representing the filepaths for all valid Terraform / Terragrunt 121 // modules in the given RootDir, subject to the include / exclude filters. 122 func FindTerraformModulePathsInRootE(opts *ValidationOptions) ([]string, error) { 123 // Find all Terraform / Terragrunt files (as specified by opts.FileType) from the configured RootDir 124 pattern := fmt.Sprintf("%s/**/%s", opts.RootDir, opts.FileType) 125 matches, err := zglob.Glob(pattern) 126 if err != nil { 127 return matches, err 128 } 129 // Keep a unique set of the base dirs that contain Terraform / Terragrunt files 130 terraformDirSet := make(map[string]string) 131 for _, match := range matches { 132 // The glob match returns all full paths to every target file, whereas we're only interested in their root 133 // directories for the purposes of running Terraform validate / terragrunt validate-inputs 134 rootDir := path.Dir(match) 135 // Don't include hidden .terraform directories when finding paths to validate 136 if !files.PathContainsHiddenFileOrFolder(rootDir) { 137 terraformDirSet[rootDir] = "exists" 138 } 139 } 140 141 // Retrieve just the unique paths to each Terraform module directory from the map we're using as a set 142 terraformDirs := go_commons_collections.Keys(terraformDirSet) 143 144 if len(opts.IncludeDirs) > 0 { 145 terraformDirs = collections.ListIntersection(terraformDirs, opts.IncludeDirs) 146 } 147 148 if len(opts.ExcludeDirs) > 0 { 149 terraformDirs = collections.ListSubtract(terraformDirs, opts.ExcludeDirs) 150 } 151 152 // Filter out any filepaths that were explicitly included in opts.ExcludeDirs 153 return terraformDirs, nil 154 } 155 156 // Custom error types 157 158 // ValidationAbsolutePathErr is returned when NewValidationOptions was unable to convert a non-absolute RootDir to 159 // an absolute path 160 type ValidationAbsolutePathErr struct { 161 rootDir string 162 } 163 164 func (e ValidationAbsolutePathErr) Error() string { 165 return fmt.Sprintf("Could not convert RootDir: %s to absolute path", e.rootDir) 166 } 167 168 // ValidationUndefinedRootDirErr is returned when NewValidationOptions is called without a RootDir argument 169 type ValidationUndefinedRootDirErr struct{} 170 171 func (e ValidationUndefinedRootDirErr) Error() string { 172 return "RootDir must be defined in ValidationOptions passed to ValidateAllTerraformModules" 173 }