-
Notifications
You must be signed in to change notification settings - Fork 228
/
module_instance.go
239 lines (216 loc) · 7.26 KB
/
module_instance.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
package addrs
import (
"bytes"
"fmt"
"github.com/hashicorp/hcl/v2"
"github.com/hashicorp/hcl/v2/hclsyntax"
"github.com/zclconf/go-cty/cty"
"github.com/zclconf/go-cty/cty/gocty"
"github.com/hashicorp/terraform-plugin-sdk/v2/internal/tfdiags"
)
// ModuleInstance is an address for a particular module instance within the
// dynamic module tree. This is an extension of the static traversals
// represented by type Module that deals with the possibility of a single
// module call producing multiple instances via the "count" and "for_each"
// arguments.
//
// Although ModuleInstance is a slice, it should be treated as immutable after
// creation.
type ModuleInstance []ModuleInstanceStep
func parseModuleInstance(traversal hcl.Traversal) (ModuleInstance, tfdiags.Diagnostics) {
mi, remain, diags := parseModuleInstancePrefix(traversal)
if len(remain) != 0 {
if len(remain) == len(traversal) {
diags = append(diags, tfdiags.Diag(
tfdiags.Error,
"Invalid module instance address",
"A module instance address must begin with \"module.\".",
))
} else {
diags = append(diags, tfdiags.Diag(
tfdiags.Error,
"Invalid module instance address",
"The module instance address is followed by additional invalid content.",
))
}
}
return mi, diags
}
// ParseModuleInstanceStr is a helper wrapper around ParseModuleInstance
// that takes a string and parses it with the HCL native syntax traversal parser
// before interpreting it.
//
// This should be used only in specialized situations since it will cause the
// created references to not have any meaningful source location information.
// If a reference string is coming from a source that should be identified in
// error messages then the caller should instead parse it directly using a
// suitable function from the HCL API and pass the traversal itself to
// ParseProviderConfigCompact.
//
// Error diagnostics are returned if either the parsing fails or the analysis
// of the traversal fails. There is no way for the caller to distinguish the
// two kinds of diagnostics programmatically. If error diagnostics are returned
// then the returned address is invalid.
func ParseModuleInstanceStr(str string) (ModuleInstance, tfdiags.Diagnostics) {
var diags tfdiags.Diagnostics
traversal, parseDiags := hclsyntax.ParseTraversalAbs([]byte(str), "", hcl.Pos{Line: 1, Column: 1})
for _, err := range parseDiags.Errs() {
// ignore warnings, they don't matter in this case
diags = append(diags, tfdiags.FromError(err))
}
if parseDiags.HasErrors() {
return nil, diags
}
addr, addrDiags := parseModuleInstance(traversal)
diags = append(diags, addrDiags...)
return addr, diags
}
func parseModuleInstancePrefix(traversal hcl.Traversal) (ModuleInstance, hcl.Traversal, tfdiags.Diagnostics) {
remain := traversal
var mi ModuleInstance
var diags tfdiags.Diagnostics
for len(remain) > 0 {
var next string
switch tt := remain[0].(type) {
case hcl.TraverseRoot:
next = tt.Name
case hcl.TraverseAttr:
next = tt.Name
default:
diags = append(diags, tfdiags.Diag(
tfdiags.Error,
"Invalid address operator",
"Module address prefix must be followed by dot and then a name.",
))
}
if next != "module" {
break
}
remain = remain[1:]
// If we have the prefix "module" then we should be followed by an
// module call name, as an attribute, and then optionally an index step
// giving the instance key.
if len(remain) == 0 {
diags = append(diags, tfdiags.Diag(
tfdiags.Error,
"Invalid address operator",
"Prefix \"module.\" must be followed by a module name.",
))
break
}
var moduleName string
switch tt := remain[0].(type) {
case hcl.TraverseAttr:
moduleName = tt.Name
default:
diags = append(diags, tfdiags.Diag(
tfdiags.Error,
"Invalid address operator",
"Prefix \"module.\" must be followed by a module name.",
))
}
remain = remain[1:]
step := ModuleInstanceStep{
Name: moduleName,
}
if len(remain) > 0 {
if idx, ok := remain[0].(hcl.TraverseIndex); ok {
remain = remain[1:]
switch idx.Key.Type() {
case cty.String:
step.InstanceKey = stringKey(idx.Key.AsString())
case cty.Number:
var idxInt int
err := gocty.FromCtyValue(idx.Key, &idxInt)
if err == nil {
step.InstanceKey = intKey(idxInt)
} else {
diags = append(diags, tfdiags.Diag(
tfdiags.Error,
"Invalid address operator",
fmt.Sprintf("Invalid module index: %s.", err),
))
}
default:
// Should never happen, because no other types are allowed in traversal indices.
diags = append(diags, tfdiags.Diag(
tfdiags.Error,
"Invalid address operator",
"Invalid module key: must be either a string or an integer.",
))
}
}
}
mi = append(mi, step)
}
var retRemain hcl.Traversal
if len(remain) > 0 {
retRemain = make(hcl.Traversal, len(remain))
copy(retRemain, remain)
// The first element here might be either a TraverseRoot or a
// TraverseAttr, depending on whether we had a module address on the
// front. To make life easier for callers, we'll normalize to always
// start with a TraverseRoot.
if tt, ok := retRemain[0].(hcl.TraverseAttr); ok {
retRemain[0] = hcl.TraverseRoot{
Name: tt.Name,
SrcRange: tt.SrcRange,
}
}
}
return mi, retRemain, diags
}
// UnkeyedInstanceShim is a shim method for converting a Module address to the
// equivalent ModuleInstance address that assumes that no modules have
// keyed instances.
//
// This is a temporary allowance for the fact that Terraform does not presently
// support "count" and "for_each" on modules, and thus graph building code that
// derives graph nodes from configuration must just assume unkeyed modules
// in order to construct the graph. At a later time when "count" and "for_each"
// support is added for modules, all callers of this method will need to be
// reworked to allow for keyed module instances.
func (m Module) UnkeyedInstanceShim() ModuleInstance {
path := make(ModuleInstance, len(m))
for i, name := range m {
path[i] = ModuleInstanceStep{Name: name}
}
return path
}
// ModuleInstanceStep is a single traversal step through the dynamic module
// tree. It is used only as part of ModuleInstance.
type ModuleInstanceStep struct {
Name string
InstanceKey instanceKey
}
// RootModuleInstance is the module instance address representing the root
// module, which is also the zero value of ModuleInstance.
var RootModuleInstance ModuleInstance
// Child returns the address of a child module instance of the receiver,
// identified by the given name and key.
func (m ModuleInstance) Child(name string, key instanceKey) ModuleInstance {
ret := make(ModuleInstance, 0, len(m)+1)
ret = append(ret, m...)
return append(ret, ModuleInstanceStep{
Name: name,
InstanceKey: key,
})
}
// String returns a string representation of the receiver, in the format used
// within e.g. user-provided resource addresses.
//
// The address of the root module has the empty string as its representation.
func (m ModuleInstance) String() string {
var buf bytes.Buffer
sep := ""
for _, step := range m {
buf.WriteString(sep)
buf.WriteString("module.")
buf.WriteString(step.Name)
if step.InstanceKey != NoKey {
buf.WriteString(step.InstanceKey.String())
}
sep = "."
}
return buf.String()
}