mirror of
https://github.com/opentofu/opentofu.git
synced 2025-01-28 17:34:24 -06:00
39e609d5fd
Previously we were using the experimental HCL 2 repository, but now we'll shift over to the v2 import path within the main HCL repository as part of actually releasing HCL 2.0 as stable. This is a mechanical search/replace to the new import paths. It also switches to the v2.0.0 release of HCL, which includes some new code that Terraform didn't previously have but should not change any behavior that matters for Terraform's purposes. For the moment the experimental HCL2 repository is still an indirect dependency via terraform-config-inspect, so it remains in our go.sum and vendor directories for the moment. Because terraform-config-inspect uses a much smaller subset of the HCL2 functionality, this does still manage to prune the vendor directory a little. A subsequent release of terraform-config-inspect should allow us to completely remove that old repository in a future commit.
164 lines
4.7 KiB
Go
164 lines
4.7 KiB
Go
package configs
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"github.com/hashicorp/hcl/v2"
|
|
)
|
|
|
|
// LoadConfigDir reads the .tf and .tf.json files in the given directory
|
|
// as config files (using LoadConfigFile) and then combines these files into
|
|
// a single Module.
|
|
//
|
|
// If this method returns nil, that indicates that the given directory does not
|
|
// exist at all or could not be opened for some reason. Callers may wish to
|
|
// detect this case and ignore the returned diagnostics so that they can
|
|
// produce a more context-aware error message in that case.
|
|
//
|
|
// If this method returns a non-nil module while error diagnostics are returned
|
|
// then the module may be incomplete but can be used carefully for static
|
|
// analysis.
|
|
//
|
|
// This file does not consider a directory with no files to be an error, and
|
|
// will simply return an empty module in that case. Callers should first call
|
|
// Parser.IsConfigDir if they wish to recognize that situation.
|
|
//
|
|
// .tf files are parsed using the HCL native syntax while .tf.json files are
|
|
// parsed using the HCL JSON syntax.
|
|
func (p *Parser) LoadConfigDir(path string) (*Module, hcl.Diagnostics) {
|
|
primaryPaths, overridePaths, diags := p.dirFiles(path)
|
|
if diags.HasErrors() {
|
|
return nil, diags
|
|
}
|
|
|
|
primary, fDiags := p.loadFiles(primaryPaths, false)
|
|
diags = append(diags, fDiags...)
|
|
override, fDiags := p.loadFiles(overridePaths, true)
|
|
diags = append(diags, fDiags...)
|
|
|
|
mod, modDiags := NewModule(primary, override)
|
|
diags = append(diags, modDiags...)
|
|
|
|
mod.SourceDir = path
|
|
|
|
return mod, diags
|
|
}
|
|
|
|
// ConfigDirFiles returns lists of the primary and override files configuration
|
|
// files in the given directory.
|
|
//
|
|
// If the given directory does not exist or cannot be read, error diagnostics
|
|
// are returned. If errors are returned, the resulting lists may be incomplete.
|
|
func (p Parser) ConfigDirFiles(dir string) (primary, override []string, diags hcl.Diagnostics) {
|
|
return p.dirFiles(dir)
|
|
}
|
|
|
|
// IsConfigDir determines whether the given path refers to a directory that
|
|
// exists and contains at least one Terraform config file (with a .tf or
|
|
// .tf.json extension.)
|
|
func (p *Parser) IsConfigDir(path string) bool {
|
|
primaryPaths, overridePaths, _ := p.dirFiles(path)
|
|
return (len(primaryPaths) + len(overridePaths)) > 0
|
|
}
|
|
|
|
func (p *Parser) loadFiles(paths []string, override bool) ([]*File, hcl.Diagnostics) {
|
|
var files []*File
|
|
var diags hcl.Diagnostics
|
|
|
|
for _, path := range paths {
|
|
var f *File
|
|
var fDiags hcl.Diagnostics
|
|
if override {
|
|
f, fDiags = p.LoadConfigFileOverride(path)
|
|
} else {
|
|
f, fDiags = p.LoadConfigFile(path)
|
|
}
|
|
diags = append(diags, fDiags...)
|
|
if f != nil {
|
|
files = append(files, f)
|
|
}
|
|
}
|
|
|
|
return files, diags
|
|
}
|
|
|
|
func (p *Parser) dirFiles(dir string) (primary, override []string, diags hcl.Diagnostics) {
|
|
infos, err := p.fs.ReadDir(dir)
|
|
if err != nil {
|
|
diags = append(diags, &hcl.Diagnostic{
|
|
Severity: hcl.DiagError,
|
|
Summary: "Failed to read module directory",
|
|
Detail: fmt.Sprintf("Module directory %s does not exist or cannot be read.", dir),
|
|
})
|
|
return
|
|
}
|
|
|
|
for _, info := range infos {
|
|
if info.IsDir() {
|
|
// We only care about files
|
|
continue
|
|
}
|
|
|
|
name := info.Name()
|
|
ext := fileExt(name)
|
|
if ext == "" || IsIgnoredFile(name) {
|
|
continue
|
|
}
|
|
|
|
baseName := name[:len(name)-len(ext)] // strip extension
|
|
isOverride := baseName == "override" || strings.HasSuffix(baseName, "_override")
|
|
|
|
fullPath := filepath.Join(dir, name)
|
|
if isOverride {
|
|
override = append(override, fullPath)
|
|
} else {
|
|
primary = append(primary, fullPath)
|
|
}
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
// fileExt returns the Terraform configuration extension of the given
|
|
// path, or a blank string if it is not a recognized extension.
|
|
func fileExt(path string) string {
|
|
if strings.HasSuffix(path, ".tf") {
|
|
return ".tf"
|
|
} else if strings.HasSuffix(path, ".tf.json") {
|
|
return ".tf.json"
|
|
} else {
|
|
return ""
|
|
}
|
|
}
|
|
|
|
// IsIgnoredFile returns true if the given filename (which must not have a
|
|
// directory path ahead of it) should be ignored as e.g. an editor swap file.
|
|
func IsIgnoredFile(name string) bool {
|
|
return strings.HasPrefix(name, ".") || // Unix-like hidden files
|
|
strings.HasSuffix(name, "~") || // vim
|
|
strings.HasPrefix(name, "#") && strings.HasSuffix(name, "#") // emacs
|
|
}
|
|
|
|
// IsEmptyDir returns true if the given filesystem path contains no Terraform
|
|
// configuration files.
|
|
//
|
|
// Unlike the methods of the Parser type, this function always consults the
|
|
// real filesystem, and thus it isn't appropriate to use when working with
|
|
// configuration loaded from a plan file.
|
|
func IsEmptyDir(path string) (bool, error) {
|
|
if _, err := os.Stat(path); err != nil && os.IsNotExist(err) {
|
|
return true, nil
|
|
}
|
|
|
|
p := NewParser(nil)
|
|
fs, os, err := p.dirFiles(path)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
|
|
return len(fs) == 0 && len(os) == 0, nil
|
|
}
|