/
loader.go
220 lines (193 loc) · 6.53 KB
/
loader.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
package terraform
import (
"fmt"
"log"
"os"
"path/filepath"
"github.com/hashicorp/go-version"
"github.com/hashicorp/hcl/v2"
"github.com/spf13/afero"
"github.com/terraform-linters/tflint/terraform/addrs"
)
// Loader is a fork of configload.Loader. The instance is the main entry-point
// for loading configurations via this package.
//
// It extends the general config-loading functionality in the Parser to support
// loading full configurations using modules and gathering input values from
// values files.
type Loader struct {
parser *Parser
modules moduleMgr
baseDir string
}
// NewLoader creates and returns a loader that reads configuration from the
// given filesystem.
//
// The loader has some internal state about the modules that are currently
// installed, which is read from disk as part of this function. Note that
// this will always read against the current directory unless TF_DATA_DIR
// is set.
//
// If an original working dir is passed, the paths of the loaded files will
// be relative to that directory.
func NewLoader(fs afero.Afero, originalWd string) (*Loader, error) {
log.Print("[INFO] Initialize new loader")
wd, err := os.Getwd()
if err != nil {
return nil, fmt.Errorf("failed to determine current working directory: %s", err)
}
baseDir, err := filepath.Rel(originalWd, wd)
if err != nil {
return nil, fmt.Errorf("failed to determine base dir: %s", err)
}
ret := &Loader{
parser: NewParser(fs),
modules: moduleMgr{
fs: fs,
manifest: moduleManifest{},
},
baseDir: baseDir,
}
err = ret.modules.readModuleManifest()
if err != nil {
return nil, fmt.Errorf("failed to read module manifest: %s", err)
}
return ret, nil
}
// LoadConfig reads the Terraform module in the given directory and uses it as the
// root module to build the static module tree that represents a configuration.
func (l *Loader) LoadConfig(dir string, callModuleType CallModuleType) (*Config, hcl.Diagnostics) {
mod, diags := l.parser.LoadConfigDir(l.baseDir, dir)
if diags.HasErrors() {
return nil, diags
}
var walker ModuleWalkerFunc
switch callModuleType {
case CallAllModule:
log.Print("[INFO] Building the root module while calling child modules...")
walker = l.moduleWalkerFunc(true, true)
case CallLocalModule:
log.Print("[INFO] Building the root module while calling local child modules...")
walker = l.moduleWalkerFunc(true, false)
case CallNoModule:
walker = l.moduleWalkerFunc(false, false)
default:
panic(fmt.Sprintf("unexpected module call type: %d", callModuleType))
}
cfg, diags := BuildConfig(mod, walker)
if diags.HasErrors() {
return nil, diags
}
return cfg, nil
}
func (l *Loader) moduleWalkerFunc(walkLocal, walkRemote bool) ModuleWalkerFunc {
return func(req *ModuleRequest) (*Module, *version.Version, hcl.Diagnostics) {
switch source := req.SourceAddr.(type) {
case nil:
// Case for no source attribute. This is usually invalid, but is ignored to prevent panic.
return nil, nil, nil
case addrs.ModuleSourceLocal:
if !walkLocal {
return nil, nil, nil
}
dir := filepath.ToSlash(filepath.Join(req.Parent.Module.SourceDir, source.String()))
log.Printf("[DEBUG] Trying to load the local module: name=%s dir=%s", req.Name, dir)
if !l.parser.Exists(dir) {
return nil, nil, hcl.Diagnostics{
{
Severity: hcl.DiagError,
Summary: fmt.Sprintf(`"%s" module is not found`, req.Name),
Detail: fmt.Sprintf(`The module directory "%s" does not exist or cannot be read.`, filepath.Join(l.baseDir, dir)),
Subject: &req.CallRange,
},
}
}
mod, diags := l.parser.LoadConfigDir(l.baseDir, dir)
return mod, nil, diags
case addrs.ModuleSourceRemote:
if !walkRemote {
return nil, nil, nil
}
// Since we're just loading here, we expect that all referenced modules
// will be already installed and described in our manifest. However, we
// do verify that the manifest and the configuration are in agreement
// so that we can prompt the user to run "terraform init" if not.
key := l.modules.manifest.moduleKey(req.Path)
record, exists := l.modules.manifest[key]
if !exists {
log.Printf(`[DEBUG] Failed to find "%s"`, key)
return nil, nil, hcl.Diagnostics{
{
Severity: hcl.DiagError,
Summary: fmt.Sprintf(`"%s" module is not found. Did you run "terraform init"?`, req.Name),
Subject: &req.CallRange,
},
}
}
log.Printf("[DEBUG] Trying to load the remote module: key=%s, version=%s, dir=%s", key, record.VersionStr, record.Dir)
mod, diags := l.parser.LoadConfigDir(l.baseDir, record.Dir)
return mod, record.Version, diags
default:
panic(fmt.Sprintf("unexpected module source type: %T", req.SourceAddr))
}
}
}
var defaultVarsFilename = "terraform.tfvars"
// LoadValuesFiles reads Terraform's autoloaded values files in the given directory
// and returns terraform.InputValues in order of priority.
//
// The second and subsequent arguments are given the paths of value files to be read
// manually. Argument order matches precedence.
func (l *Loader) LoadValuesFiles(dir string, files ...string) ([]InputValues, hcl.Diagnostics) {
values := []InputValues{}
diags := hcl.Diagnostics{}
autoLoadFiles, listDiags := l.parser.autoLoadValuesDirFiles(l.baseDir, dir)
diags = diags.Extend(listDiags)
if listDiags.HasErrors() {
return nil, diags
}
defaultVarsFile := filepath.Join(dir, defaultVarsFilename)
if _, err := os.Stat(defaultVarsFile); err == nil {
autoLoadFiles = append([]string{defaultVarsFile}, autoLoadFiles...)
}
for _, file := range autoLoadFiles {
vals, loadDiags := l.loadValuesFile(file)
diags = diags.Extend(loadDiags)
if !loadDiags.HasErrors() {
values = append(values, vals)
}
}
for _, file := range files {
vals, loadDiags := l.loadValuesFile(file)
diags = diags.Extend(loadDiags)
if !loadDiags.HasErrors() {
values = append(values, vals)
}
}
return values, diags
}
func (l *Loader) loadValuesFile(file string) (InputValues, hcl.Diagnostics) {
vals, diags := l.parser.LoadValuesFile(l.baseDir, file)
if diags.HasErrors() {
return nil, diags
}
ret := make(InputValues)
for k, v := range vals {
ret[k] = &InputValue{
Value: v,
}
}
return ret, nil
}
func (l *Loader) LoadConfigDirFiles(dir string) (map[string]*hcl.File, hcl.Diagnostics) {
return l.parser.LoadConfigDirFiles(l.baseDir, dir)
}
func (l *Loader) IsConfigDir(path string) bool {
return l.parser.IsConfigDir(l.baseDir, path)
}
func (l *Loader) Sources() map[string][]byte {
return l.parser.Sources()
}
func (l *Loader) Files() map[string]*hcl.File {
return l.parser.Files()
}