-
Notifications
You must be signed in to change notification settings - Fork 9.4k
/
import.go
224 lines (188 loc) · 6.56 KB
/
import.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
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package configs
import (
"github.com/hashicorp/hcl/v2"
"github.com/hashicorp/hcl/v2/hclsyntax"
hcljson "github.com/hashicorp/hcl/v2/json"
"github.com/hashicorp/terraform/internal/addrs"
"github.com/hashicorp/terraform/internal/tfdiags"
"github.com/zclconf/go-cty/cty"
)
type Import struct {
ID hcl.Expression
To hcl.Expression
// The To address may not be resolvable immediately if it contains dynamic
// index expressions, so we will extract the ConfigResource address and
// store it here for reference.
ToResource addrs.ConfigResource
ForEach hcl.Expression
ProviderConfigRef *ProviderConfigRef
Provider addrs.Provider
DeclRange hcl.Range
ProviderDeclRange hcl.Range
}
func decodeImportBlock(block *hcl.Block) (*Import, hcl.Diagnostics) {
var diags hcl.Diagnostics
imp := &Import{
DeclRange: block.DefRange,
}
content, moreDiags := block.Body.Content(importBlockSchema)
diags = append(diags, moreDiags...)
if attr, exists := content.Attributes["id"]; exists {
imp.ID = attr.Expr
}
if attr, exists := content.Attributes["to"]; exists {
toExpr, jsDiags := unwrapJSONRefExpr(attr.Expr)
diags = diags.Extend(jsDiags)
if diags.HasErrors() {
return imp, diags
}
imp.To = toExpr
addr, toDiags := parseConfigResourceFromExpression(imp.To)
diags = diags.Extend(toDiags.ToHCL())
if addr.Resource.Mode != addrs.ManagedResourceMode {
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Invalid import address",
Detail: "Only managed resources can be imported.",
Subject: attr.Range.Ptr(),
})
}
imp.ToResource = addr
}
if attr, exists := content.Attributes["for_each"]; exists {
imp.ForEach = attr.Expr
}
if attr, exists := content.Attributes["provider"]; exists {
if len(imp.ToResource.Module) > 0 {
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Invalid import provider argument",
Detail: "The provider argument can only be specified in import blocks that will generate configuration.\n\nUse the providers argument within the module block to configure providers for all resources within a module, including imported resources.",
Subject: attr.Range.Ptr(),
})
}
var providerDiags hcl.Diagnostics
imp.ProviderConfigRef, providerDiags = decodeProviderConfigRef(attr.Expr, "provider")
imp.ProviderDeclRange = attr.Range
diags = append(diags, providerDiags...)
}
return imp, diags
}
var importBlockSchema = &hcl.BodySchema{
Attributes: []hcl.AttributeSchema{
{
Name: "provider",
},
{
Name: "for_each",
},
{
Name: "id",
Required: true,
},
{
Name: "to",
Required: true,
},
},
}
// parseResourceInstanceFromExpression takes an arbitrary expression
// representing a resource instance, and parses out the static ConfigResource
// skipping an variable index expressions. This is used to connect an import
// block's "to" to the configuration address before the full instance
// expressions are evaluated.
func parseConfigResourceFromExpression(expr hcl.Expression) (addrs.ConfigResource, tfdiags.Diagnostics) {
traversal, hcdiags := exprToResourceTraversal(expr)
if hcdiags.HasErrors() {
return addrs.ConfigResource{}, tfdiags.Diagnostics(nil).Append(hcdiags)
}
addr, diags := addrs.ParseAbsResourceInstance(traversal)
if diags.HasErrors() {
return addrs.ConfigResource{}, diags
}
return addr.ConfigResource(), diags
}
// unwrapJSONRefExpr takes a string expression from a JSON configuration,
// and re-evaluates the string as HCL. If the expression is not JSON, the
// original expression is returned directly.
func unwrapJSONRefExpr(expr hcl.Expression) (hcl.Expression, hcl.Diagnostics) {
if !hcljson.IsJSONExpression(expr) {
return expr, nil
}
// We can abuse the hcl json api and rely on the fact that calling
// Value on a json expression with no EvalContext will return the
// raw string. We can then parse that as normal hcl syntax, and
// continue with the decoding.
v, diags := expr.Value(nil)
if diags.HasErrors() {
return nil, diags
}
// the JSON representation can only be a string
if v.Type() != cty.String {
diags = diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Invalid reference expression",
Detail: "A single reference string is required.",
Subject: expr.Range().Ptr(),
})
return nil, diags
}
rng := expr.Range()
expr, ds := hclsyntax.ParseExpression([]byte(v.AsString()), rng.Filename, rng.Start)
diags = diags.Extend(ds)
return expr, diags
}
// exprToResourceTraversal is used to parse the import block's to expression,
// which must be a resource instance, but may contain limited variables with
// index expressions. Since we only need the ConfigResource to connect the
// import to the configuration, we skip any index expressions.
func exprToResourceTraversal(expr hcl.Expression) (hcl.Traversal, hcl.Diagnostics) {
var trav hcl.Traversal
var diags hcl.Diagnostics
switch e := expr.(type) {
case *hclsyntax.RelativeTraversalExpr:
t, d := exprToResourceTraversal(e.Source)
diags = diags.Extend(d)
trav = append(trav, t...)
trav = append(trav, e.Traversal...)
case *hclsyntax.ScopeTraversalExpr:
// a static reference, we can just append the traversal
trav = append(trav, e.Traversal...)
case *hclsyntax.IndexExpr:
// Get the collection from the index expression, we don't need the
// index for a ConfigResource
t, d := exprToResourceTraversal(e.Collection)
diags = diags.Extend(d)
if diags.HasErrors() {
return nil, diags
}
trav = append(trav, t...)
default:
// if we don't recognise the expression type (which means we are likely
// dealing with a test mock), try and interpret this as an absolute
// traversal
t, d := hcl.AbsTraversalForExpr(e)
diags = diags.Extend(d)
trav = append(trav, t...)
}
return trav, diags
}
// parseImportToStatic attempts to parse the To address of an import block
// statically to get the resource address. This returns false when the address
// cannot be parsed, which is usually a result of dynamic index expressions
// using for_each.
func parseImportToStatic(expr hcl.Expression) (addrs.AbsResourceInstance, bool) {
// we may have a nil expression in some error cases, which we can just
// false to avoid the parsing
if expr == nil {
return addrs.AbsResourceInstance{}, false
}
var toDiags tfdiags.Diagnostics
traversal, hd := hcl.AbsTraversalForExpr(expr)
toDiags = toDiags.Append(hd)
to, td := addrs.ParseAbsResourceInstance(traversal)
toDiags = toDiags.Append(td)
return to, !toDiags.HasErrors()
}