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  }