github.com/muratcelep/terraform@v1.1.0-beta2-not-internal-4/not-internal/configs/parser_config_dir.go (about)

     1  package configs
     2  
     3  import (
     4  	"fmt"
     5  	"os"
     6  	"path/filepath"
     7  	"strings"
     8  
     9  	"github.com/hashicorp/hcl/v2"
    10  )
    11  
    12  // LoadConfigDir reads the .tf and .tf.json files in the given directory
    13  // as config files (using LoadConfigFile) and then combines these files into
    14  // a single Module.
    15  //
    16  // If this method returns nil, that indicates that the given directory does not
    17  // exist at all or could not be opened for some reason. Callers may wish to
    18  // detect this case and ignore the returned diagnostics so that they can
    19  // produce a more context-aware error message in that case.
    20  //
    21  // If this method returns a non-nil module while error diagnostics are returned
    22  // then the module may be incomplete but can be used carefully for static
    23  // analysis.
    24  //
    25  // This file does not consider a directory with no files to be an error, and
    26  // will simply return an empty module in that case. Callers should first call
    27  // Parser.IsConfigDir if they wish to recognize that situation.
    28  //
    29  // .tf files are parsed using the HCL native syntax while .tf.json files are
    30  // parsed using the HCL JSON syntax.
    31  func (p *Parser) LoadConfigDir(path string) (*Module, hcl.Diagnostics) {
    32  	primaryPaths, overridePaths, diags := p.dirFiles(path)
    33  	if diags.HasErrors() {
    34  		return nil, diags
    35  	}
    36  
    37  	primary, fDiags := p.loadFiles(primaryPaths, false)
    38  	diags = append(diags, fDiags...)
    39  	override, fDiags := p.loadFiles(overridePaths, true)
    40  	diags = append(diags, fDiags...)
    41  
    42  	mod, modDiags := NewModule(primary, override)
    43  	diags = append(diags, modDiags...)
    44  
    45  	mod.SourceDir = path
    46  
    47  	return mod, diags
    48  }
    49  
    50  // ConfigDirFiles returns lists of the primary and override files configuration
    51  // files in the given directory.
    52  //
    53  // If the given directory does not exist or cannot be read, error diagnostics
    54  // are returned. If errors are returned, the resulting lists may be incomplete.
    55  func (p Parser) ConfigDirFiles(dir string) (primary, override []string, diags hcl.Diagnostics) {
    56  	return p.dirFiles(dir)
    57  }
    58  
    59  // IsConfigDir determines whether the given path refers to a directory that
    60  // exists and contains at least one Terraform config file (with a .tf or
    61  // .tf.json extension.)
    62  func (p *Parser) IsConfigDir(path string) bool {
    63  	primaryPaths, overridePaths, _ := p.dirFiles(path)
    64  	return (len(primaryPaths) + len(overridePaths)) > 0
    65  }
    66  
    67  func (p *Parser) loadFiles(paths []string, override bool) ([]*File, hcl.Diagnostics) {
    68  	var files []*File
    69  	var diags hcl.Diagnostics
    70  
    71  	for _, path := range paths {
    72  		var f *File
    73  		var fDiags hcl.Diagnostics
    74  		if override {
    75  			f, fDiags = p.LoadConfigFileOverride(path)
    76  		} else {
    77  			f, fDiags = p.LoadConfigFile(path)
    78  		}
    79  		diags = append(diags, fDiags...)
    80  		if f != nil {
    81  			files = append(files, f)
    82  		}
    83  	}
    84  
    85  	return files, diags
    86  }
    87  
    88  func (p *Parser) dirFiles(dir string) (primary, override []string, diags hcl.Diagnostics) {
    89  	infos, err := p.fs.ReadDir(dir)
    90  	if err != nil {
    91  		diags = append(diags, &hcl.Diagnostic{
    92  			Severity: hcl.DiagError,
    93  			Summary:  "Failed to read module directory",
    94  			Detail:   fmt.Sprintf("Module directory %s does not exist or cannot be read.", dir),
    95  		})
    96  		return
    97  	}
    98  
    99  	for _, info := range infos {
   100  		if info.IsDir() {
   101  			// We only care about files
   102  			continue
   103  		}
   104  
   105  		name := info.Name()
   106  		ext := fileExt(name)
   107  		if ext == "" || IsIgnoredFile(name) {
   108  			continue
   109  		}
   110  
   111  		baseName := name[:len(name)-len(ext)] // strip extension
   112  		isOverride := baseName == "override" || strings.HasSuffix(baseName, "_override")
   113  
   114  		fullPath := filepath.Join(dir, name)
   115  		if isOverride {
   116  			override = append(override, fullPath)
   117  		} else {
   118  			primary = append(primary, fullPath)
   119  		}
   120  	}
   121  
   122  	return
   123  }
   124  
   125  // fileExt returns the Terraform configuration extension of the given
   126  // path, or a blank string if it is not a recognized extension.
   127  func fileExt(path string) string {
   128  	if strings.HasSuffix(path, ".tf") {
   129  		return ".tf"
   130  	} else if strings.HasSuffix(path, ".tf.json") {
   131  		return ".tf.json"
   132  	} else {
   133  		return ""
   134  	}
   135  }
   136  
   137  // IsIgnoredFile returns true if the given filename (which must not have a
   138  // directory path ahead of it) should be ignored as e.g. an editor swap file.
   139  func IsIgnoredFile(name string) bool {
   140  	return strings.HasPrefix(name, ".") || // Unix-like hidden files
   141  		strings.HasSuffix(name, "~") || // vim
   142  		strings.HasPrefix(name, "#") && strings.HasSuffix(name, "#") // emacs
   143  }
   144  
   145  // IsEmptyDir returns true if the given filesystem path contains no Terraform
   146  // configuration files.
   147  //
   148  // Unlike the methods of the Parser type, this function always consults the
   149  // real filesystem, and thus it isn't appropriate to use when working with
   150  // configuration loaded from a plan file.
   151  func IsEmptyDir(path string) (bool, error) {
   152  	if _, err := os.Stat(path); err != nil && os.IsNotExist(err) {
   153  		return true, nil
   154  	}
   155  
   156  	p := NewParser(nil)
   157  	fs, os, diags := p.dirFiles(path)
   158  	if diags.HasErrors() {
   159  		return false, diags
   160  	}
   161  
   162  	return len(fs) == 0 && len(os) == 0, nil
   163  }