kubeform.dev/terraform-backend-sdk@v0.0.0-20220310143633-45f07fe731c5/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 }