-
Notifications
You must be signed in to change notification settings - Fork 0
/
context_load.go
275 lines (246 loc) · 9.12 KB
/
context_load.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
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
package eval
import (
"fmt"
"path/filepath"
"strings"
"github.com/apparentlymart/awsup/addr"
"github.com/apparentlymart/awsup/config"
"github.com/hashicorp/hcl2/hcl"
"github.com/zclconf/go-cty/cty"
)
func newModuleContext(rctx *RootContext, parser *config.Parser, srcPath string, path addr.ModulePath, each EachState, inputConstants hcl.Attributes, root, parent *ModuleContext, callRange hcl.Range) (*ModuleContext, hcl.Diagnostics) {
cfg, diags := parser.ParseDirOrFile(srcPath)
mctx := &ModuleContext{
Global: rctx,
Path: path,
Config: cfg,
}
if diags.HasErrors() {
// If we failed during parsing then we'll just bail altogether,
// though giving the caller the option to poke around in the
// returned Config object if desired, since it may include some
// partial information for valid portions of the configuration.
// We'll use our call range as a synthetic Subject for any diagnostics
// that don't already have one, since at least that helps the user
// figure out which Module block led to the diagnostic.
for _, diag := range diags {
if (diag.Subject == nil && callRange != hcl.Range{}) {
diag.Subject = &callRange
}
}
return mctx, diags
}
if parent == nil {
// If parent is nil then we're the root module, and so we need to set
// ourselves as our root.
mctx.Root = mctx
root = mctx
} else {
mctx.Root = root
mctx.Parent = parent
}
constants, constsDiags := buildConstantsTable(cfg.Constants, inputConstants, parent, each, callRange)
diags = append(diags, constsDiags...)
mctx.Constants = constants
// Now that mctx.Constants is set, we can safely use mctx.EvalConstant from
// this point forward.
children := make(map[string]*ModuleEach)
if constsDiags.HasErrors() {
// We won't proceed further if we encountered errors building the
// constants table, since our various calls to EvalConstants below
// tend to cause confusing follow-up diagnostics as a result of
// required constants being absent from the table.
return mctx, diags
}
for name, mcfg := range cfg.Modules {
forEachVal, valDiags := mctx.EvalConstant(mcfg.ForEach, cty.DynamicPseudoType, NoEachState)
diags = append(diags, valDiags...)
if valDiags.HasErrors() {
// Can't process any further if we can't evaluate ForEach
continue
}
forEachType := forEachVal.Type()
path := path.AppendName(name)
switch {
case forEachVal.IsNull():
children[name] = newModuleEach(addr.NoEach)
childCtx, childDiags := mctx.childModuleContext(parser, path, mcfg, NoEachState)
diags = append(diags, childDiags...)
children[name].Modules[addr.NoEachIndex] = childCtx
case forEachType.IsSetType():
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Incorrect value type",
Detail: "A set value cannot be used as a ForEach interator.",
Subject: mcfg.ForEach.StartRange().Ptr(),
})
continue
case forEachType.IsCollectionType() || forEachType.IsObjectType() || forEachType.IsTupleType():
switch {
case forEachType.IsListType() || forEachType.IsTupleType():
children[name] = newModuleEach(addr.EachTypeInt)
case forEachType.IsMapType() || forEachType.IsObjectType():
children[name] = newModuleEach(addr.EachTypeString)
default:
// should never happen since we've now covered all of the
// collection types
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Incorrect value type",
Detail: fmt.Sprintf("A %s value cannot be used as a ForEach interator.", forEachType.FriendlyName()),
Subject: mcfg.ForEach.StartRange().Ptr(),
})
continue
}
for it := forEachVal.ElementIterator(); it.Next(); {
keyVal, val := it.Element()
key := addr.MakeEachIndex(keyVal)
path := path.AppendIndex(key)
each := EachState{
Key: key,
Value: val,
}
childCtx, childDiags := mctx.childModuleContext(parser, path, mcfg, each)
diags = append(diags, childDiags...)
if childCtx == nil {
// The content of the config block was so broken that we
// weren't able to construct any context.
continue
}
children[name].Modules[key] = childCtx
}
default:
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Incorrect value type",
Detail: fmt.Sprintf("A %s value cannot be used as a ForEach interator.", forEachType.FriendlyName()),
Subject: mcfg.ForEach.StartRange().Ptr(),
})
continue
}
}
mctx.Children = children
return mctx, diags
}
func (mctx *ModuleContext) childModuleContext(parser *config.Parser, path addr.ModulePath, cfg *config.ModuleCall, each EachState) (*ModuleContext, hcl.Diagnostics) {
// This method is called while mctx is still being constructed, so
// mctx.Config, mctx.Root, and mctx.Constantsare the only fields safe to
// access. mctx.EvalConstant uses only mctx.Constants and so it is also
// safe to use in here.
var diags hcl.Diagnostics
basePath := mctx.Config.SourceDir
if basePath == "" {
// An empty basePath indicates that a module was loaded from a
// synthetic source, such as an in-memory buffer (e.g. for unit testing).
// basePath should never be empty in normal CLI usage.
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Child modules not allowed",
Detail: "The current module was not loaded from an on-disk path, so child module references cannot be resolved.",
Subject: cfg.Source.Range().Ptr(),
})
return nil, diags
}
srcPathVal, srcDiags := mctx.EvalConstant(cfg.Source, cty.String, each)
diags = append(diags, srcDiags...)
if srcDiags.HasErrors() {
// We can't proceed any further if we don't have a valid source
return nil, diags
}
if srcPathVal.IsNull() {
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Unspecified module source",
Detail: "Child module declaration is missing the required attribute \"Source\".",
Subject: &cfg.DeclRange,
})
return nil, diags
}
srcPath := srcPathVal.AsString()
if !(strings.HasPrefix(srcPath, "./") || strings.HasPrefix(srcPath, "../")) {
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Invalid child module source path",
Detail: "A child module source must be a relative path beginning with either \"./\" or \"../\".",
Subject: cfg.Source.Range().Ptr(),
})
return nil, diags
}
srcPath = filepath.Join(basePath, srcPath)
childCtx, childDiags := newModuleContext(mctx.Global, parser, srcPath, path, each, cfg.Constants, mctx.Root, mctx, cfg.DeclRange)
diags = append(diags, childDiags...)
return childCtx, diags
}
func buildConstantsTable(cfgs map[string]*config.Constant, input hcl.Attributes, parent *ModuleContext, each EachState, callRange hcl.Range) (map[string]cty.Value, hcl.Diagnostics) {
table := map[string]cty.Value{}
var diags hcl.Diagnostics
// If we're working on the root module, this is signalled by our range
// being the zero value.
inRoot := callRange == hcl.Range{}
for name, cfg := range cfgs {
attr, isSet := input[name]
if !isSet {
val, valDiags := cfg.Default.Value(nil)
diags = append(diags, valDiags...)
if val.IsNull() {
if inRoot {
// Root constants are expected to come from the CLI and
// thus a different diagnostic message is warranted.
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Required root constant not set",
Detail: fmt.Sprintf("The root module requires a value for its named constant %q. Set it in a file passed with the --constants argument.", name),
})
} else {
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Missing required constant for module",
Detail: fmt.Sprintf("This module requires a value for its named constant %q.", name),
Subject: &callRange,
})
}
}
table[name] = val
continue
}
var val cty.Value
var valDiags hcl.Diagnostics
if parent != nil {
val, valDiags = parent.EvalConstant(attr.Expr, cty.DynamicPseudoType, each)
} else {
// For the root module we're evaluating expressions from a
// constants definition file provided on the command line, so
// we don't allow any variables here.
val, valDiags = attr.Expr.Value(nil)
}
diags = append(diags, valDiags...)
// Ensure we don't get unknown values in our table, even if an error
// causes Value to return DynamicVal; a constant value must always
// be known.
if !val.IsKnown() {
val = cty.NullVal(val.Type())
}
table[name] = val
}
// Detect any extraneous constants set in the input
for name, attr := range input {
if _, isAllowed := cfgs[name]; !isAllowed {
if inRoot {
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Unsupported root module constant",
Detail: fmt.Sprintf("The root module does not expect a constant named %q.", name),
Subject: &attr.NameRange,
})
} else {
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Unsupported module constant",
Detail: fmt.Sprintf("This child module does not expect a constant named %q.", name),
Subject: &attr.NameRange,
})
}
}
}
return table, diags
}