@ -6,7 +6,6 @@ package configs
import (
"fmt"
"os"
"path"
"path/filepath"
"strings"
@ -17,10 +16,19 @@ const (
DefaultTestDirectory = "tests"
)
// LoadConfigDir reads the .tf and .tf.js on files in the given directory
// LoadConfigDir reads the configurati on files in the given directory
// as config files (using LoadConfigFile) and then combines these files into
// a single Module.
//
// Main terraform configuration files (.tf and .tf.json) are loaded as the primary
// module, while override files (override.tf and *_override.tf) are loaded as
// overrides.
// Optionally, test files (.tftest.hcl and .tftest.json) can be loaded from
// a subdirectory of the given directory, which is specified by the
// MatchTestFiles option, or from the default test directory.
// If this option is not specified, test files will not be loaded.
// Query files (.tfquery.hcl) are also loaded from the given directory.
//
// 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
@ -36,21 +44,45 @@ const (
//
// .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 , "" )
func ( p * Parser ) LoadConfigDir ( path string , opts ... Option ) ( * Module , hcl . Diagnostics ) {
fileSet, diags := p . dirFileSet ( path , opts ... )
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 ... )
// Load the .tf configuration files
primary , fDiags := p . loadFiles ( fileSet . Primary , false )
diags = diags . Extend ( fDiags )
override , fDiags := p . loadFiles ( fileSet . Override , true )
diags = diags . Extend ( fDiags )
// Initialize the module
mod , modDiags := NewModule ( primary , override )
diags = append ( diags , modDiags ... )
diags = diags . Extend ( modDiags )
// Check if we need to load test files
if len ( fileSet . Tests ) > 0 {
testFiles , fDiags := p . loadTestFiles ( path , fileSet . Tests )
diags = diags . Extend ( fDiags )
if mod != nil {
mod . Tests = testFiles
}
}
// Check if we need to load query files
if len ( fileSet . Queries ) > 0 {
queryFiles , fDiags := p . loadQueryFiles ( path , fileSet . Queries )
diags = append ( diags , fDiags ... )
if mod != nil {
for _ , qf := range queryFiles {
diags = diags . Extend ( mod . appendQueryFile ( qf ) )
}
}
}
mod . SourceDir = path
if mod != nil {
mod . SourceDir = path
}
return mod , diags
}
@ -58,24 +90,7 @@ func (p *Parser) LoadConfigDir(path string) (*Module, hcl.Diagnostics) {
// LoadConfigDirWithTests matches LoadConfigDir, but the return Module also
// contains any relevant .tftest.hcl files.
func ( p * Parser ) LoadConfigDirWithTests ( path string , testDirectory string ) ( * Module , hcl . Diagnostics ) {
primaryPaths , overridePaths , testPaths , diags := p . dirFiles ( path , testDirectory )
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 ... )
tests , fDiags := p . loadTestFiles ( path , testPaths )
diags = append ( diags , fDiags ... )
mod , modDiags := NewModuleWithTests ( primary , override , tests )
diags = append ( diags , modDiags ... )
mod . SourceDir = path
return mod , diags
return p . LoadConfigDir ( path , MatchTestFiles ( testDirectory ) )
}
func ( p * Parser ) LoadMockDataDir ( dir string , useForPlanDefault bool , source hcl . Range ) ( * MockData , hcl . Diagnostics ) {
@ -129,24 +144,18 @@ func (p *Parser) LoadMockDataDir(dir string, useForPlanDefault bool, source hcl.
//
// 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 ) {
primary , override , _ , diags = p . dirFiles ( dir , "" )
return primary , override , diags
}
// ConfigDirFilesWithTests matches ConfigDirFiles except it also returns the
// paths to any test files within the module.
func ( p Parser ) ConfigDirFilesWithTests ( dir string , testDirectory string ) ( primary , override , tests [ ] string , diags hcl . Diagnostics ) {
return p . dirFiles ( dir , testDirectory )
func ( p Parser ) ConfigDirFiles ( dir string , opts ... Option ) ( primary , override [ ] string , diags hcl . Diagnostics ) {
fSet , diags := p . dirFileSet ( dir , opts ... )
return fSet . Primary , fSet . Override , diags
}
// 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.). Note, we explicitely exclude checking for tests here
// as tests must live alongside actual .tf config files.
// as tests must live alongside actual .tf config files. Same goes for query files.
func ( p * Parser ) IsConfigDir ( path string ) bool {
p rimaryPaths, overridePaths , _ , _ := p . dirFiles ( path , "" )
return ( len ( p rimaryPaths) + len ( overridePaths ) ) > 0
p athSet, _ := p . dirFileSet ( path )
return ( len ( p athSet. Primary ) + len ( pathSet . Override ) ) > 0
}
func ( p * Parser ) loadFiles ( paths [ ] string , override bool ) ( [ ] * File , hcl . Diagnostics ) {
@ -170,109 +179,6 @@ func (p *Parser) loadFiles(paths []string, override bool) ([]*File, hcl.Diagnost
return files , diags
}
// dirFiles finds Terraform configuration files within dir, splitting them into
// primary and override files based on the filename.
//
// If testsDir is not empty, dirFiles will also retrieve Terraform testing files
// both directly within dir and within testsDir as a subdirectory of dir. In
// this way, testsDir acts both as a direction to retrieve test files within the
// main direction and as the location for additional test files.
func ( p * Parser ) dirFiles ( dir string , testsDir string ) ( primary , override , tests [ ] string , diags hcl . Diagnostics ) {
includeTests := len ( testsDir ) > 0
if includeTests {
testPath := path . Join ( dir , testsDir )
infos , err := p . fs . ReadDir ( testPath )
if err != nil {
// Then we couldn't read from the testing directory for some reason.
if os . IsNotExist ( err ) {
// Then this means the testing directory did not exist.
// We won't actually stop loading the rest of the configuration
// for this, we will add a warning to explain to the user why
// test files weren't processed but leave it at that.
if testsDir != DefaultTestDirectory {
// We'll only add the warning if a directory other than the
// default has been requested. If the user is just loading
// the default directory then we have no expectation that
// it should actually exist.
diags = append ( diags , & hcl . Diagnostic {
Severity : hcl . DiagWarning ,
Summary : "Test directory does not exist" ,
Detail : fmt . Sprintf ( "Requested test directory %s does not exist." , testPath ) ,
} )
}
} else {
// Then there is some other reason we couldn't load. We will
// treat this as a full error.
diags = append ( diags , & hcl . Diagnostic {
Severity : hcl . DiagError ,
Summary : "Failed to read test directory" ,
Detail : fmt . Sprintf ( "Test directory %s could not be read: %v." , testPath , err ) ,
} )
// We'll also stop loading the rest of the config for this.
return
}
} else {
for _ , testInfo := range infos {
if testInfo . IsDir ( ) || IsIgnoredFile ( testInfo . Name ( ) ) {
continue
}
if strings . HasSuffix ( testInfo . Name ( ) , ".tftest.hcl" ) || strings . HasSuffix ( testInfo . Name ( ) , ".tftest.json" ) {
tests = append ( tests , filepath . Join ( testPath , testInfo . Name ( ) ) )
}
}
}
}
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 terraform configuration files.
continue
}
name := info . Name ( )
ext := fileExt ( name )
if ext == "" || IsIgnoredFile ( name ) {
continue
}
if ext == ".tftest.hcl" || ext == ".tftest.json" {
if includeTests {
tests = append ( tests , filepath . Join ( dir , 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
}
func ( p * Parser ) loadTestFiles ( basePath string , paths [ ] string ) ( map [ string ] * TestFile , hcl . Diagnostics ) {
var diags hcl . Diagnostics
@ -299,6 +205,21 @@ func (p *Parser) loadTestFiles(basePath string, paths []string) (map[string]*Tes
return tfs , diags
}
func ( p * Parser ) loadQueryFiles ( basePath string , paths [ ] string ) ( [ ] * QueryFile , hcl . Diagnostics ) {
files := make ( [ ] * QueryFile , 0 , len ( paths ) )
var diags hcl . Diagnostics
for _ , path := range paths {
f , fDiags := p . LoadQueryFile ( path )
diags = append ( diags , fDiags ... )
if f != nil {
files = append ( files , f )
}
}
return files , diags
}
// 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 {
@ -310,6 +231,10 @@ func fileExt(path string) string {
return ".tftest.hcl"
} else if strings . HasSuffix ( path , ".tftest.json" ) {
return ".tftest.json"
} else if strings . HasSuffix ( path , ".tfquery.hcl" ) {
return ".tfquery.hcl"
} else if strings . HasSuffix ( path , ".tfquery.json" ) {
return ".tfquery.json"
} else {
return ""
}
@ -335,10 +260,10 @@ func IsEmptyDir(path, testDir string) (bool, error) {
}
p := NewParser ( nil )
f s, os , tests , diags := p . dirFiles ( path , testDir )
f Set, diags := p . dirFileSet ( path , MatchTestFiles ( testDir ) )
if diags . HasErrors ( ) {
return false , diags
}
return len ( f s) == 0 && len ( os ) == 0 && len ( tests) == 0 , nil
return len ( f Set. Primary ) == 0 && len ( fSet . Override ) == 0 && len ( fSe t. T ests) == 0 , nil
}