/
provider_installation.go
337 lines (305 loc) · 12.5 KB
/
provider_installation.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
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
package cliconfig
import (
"fmt"
"path/filepath"
"github.com/hashicorp/hcl"
hclast "github.com/hashicorp/hcl/hcl/ast"
"github.com/hashicorp/terraform/internal/addrs"
"github.com/hashicorp/terraform/internal/getproviders"
"github.com/hashicorp/terraform/internal/tfdiags"
)
// ProviderInstallation is the structure of the "provider_installation"
// nested block within the CLI configuration.
type ProviderInstallation struct {
Methods []*ProviderInstallationMethod
// DevOverrides allows overriding the normal selection process for
// a particular subset of providers to force using a particular
// local directory and disregard version numbering altogether.
// This is here to allow provider developers to conveniently test
// local builds of their plugins in a development environment, without
// having to fuss with version constraints, dependency lock files, and
// so forth.
//
// This is _not_ intended for "production" use because it bypasses the
// usual version selection and checksum verification mechanisms for
// the providers in question. To make that intent/effect clearer, some
// Terraform commands emit warnings when overrides are present. Local
// mirror directories are a better way to distribute "released"
// providers, because they are still subject to version constraints and
// checksum verification.
DevOverrides map[addrs.Provider]getproviders.PackageLocalDir
}
// decodeProviderInstallationFromConfig uses the HCL AST API directly to
// decode "provider_installation" blocks from the given file.
//
// This uses the HCL AST directly, rather than HCL's decoder, because the
// intended configuration structure can't be represented using the HCL
// decoder's struct tags. This structure is intended as something that would
// be relatively easier to deal with in HCL 2 once we eventually migrate
// CLI config over to that, and so this function is stricter than HCL 1's
// decoder would be in terms of exactly what configuration shape it is
// expecting.
//
// Note that this function wants the top-level file object which might or
// might not contain provider_installation blocks, not a provider_installation
// block directly itself.
func decodeProviderInstallationFromConfig(hclFile *hclast.File) ([]*ProviderInstallation, tfdiags.Diagnostics) {
var ret []*ProviderInstallation
var diags tfdiags.Diagnostics
root := hclFile.Node.(*hclast.ObjectList)
// This is a rather odd hybrid: it's a HCL 2-like decode implemented using
// the HCL 1 AST API. That makes it a bit awkward in places, but it allows
// us to mimick the strictness of HCL 2 (making a later migration easier)
// and to support a block structure that the HCL 1 decoder can't represent.
for _, block := range root.Items {
if block.Keys[0].Token.Value() != "provider_installation" {
continue
}
// HCL only tracks whether the input was JSON or native syntax inside
// individual tokens, so we'll use our block type token to decide
// and assume that the rest of the block must be written in the same
// syntax, because syntax is a whole-file idea.
isJSON := block.Keys[0].Token.JSON
if block.Assign.Line != 0 && !isJSON {
// Seems to be an attribute rather than a block
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
"Invalid provider_installation block",
fmt.Sprintf("The provider_installation block at %s must not be introduced with an equals sign.", block.Pos()),
))
continue
}
if len(block.Keys) > 1 && !isJSON {
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
"Invalid provider_installation block",
fmt.Sprintf("The provider_installation block at %s must not have any labels.", block.Pos()),
))
}
pi := &ProviderInstallation{}
devOverrides := make(map[addrs.Provider]getproviders.PackageLocalDir)
body, ok := block.Val.(*hclast.ObjectType)
if !ok {
// We can't get in here with native HCL syntax because we
// already checked above that we're using block syntax, but
// if we're reading JSON then our value could potentially be
// anything.
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
"Invalid provider_installation block",
fmt.Sprintf("The provider_installation block at %s must not be introduced with an equals sign.", block.Pos()),
))
continue
}
for _, methodBlock := range body.List.Items {
if methodBlock.Assign.Line != 0 && !isJSON {
// Seems to be an attribute rather than a block
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
"Invalid provider_installation method block",
fmt.Sprintf("The items inside the provider_installation block at %s must all be blocks.", block.Pos()),
))
continue
}
if len(methodBlock.Keys) > 1 && !isJSON {
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
"Invalid provider_installation method block",
fmt.Sprintf("The blocks inside the provider_installation block at %s may not have any labels.", block.Pos()),
))
}
methodBody, ok := methodBlock.Val.(*hclast.ObjectType)
if !ok {
// We can't get in here with native HCL syntax because we
// already checked above that we're using block syntax, but
// if we're reading JSON then our value could potentially be
// anything.
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
"Invalid provider_installation method block",
fmt.Sprintf("The items inside the provider_installation block at %s must all be blocks.", block.Pos()),
))
continue
}
methodTypeStr := methodBlock.Keys[0].Token.Value().(string)
var location ProviderInstallationLocation
var include, exclude []string
switch methodTypeStr {
case "direct":
type BodyContent struct {
Include []string `hcl:"include"`
Exclude []string `hcl:"exclude"`
}
var bodyContent BodyContent
err := hcl.DecodeObject(&bodyContent, methodBody)
if err != nil {
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
"Invalid provider_installation method block",
fmt.Sprintf("Invalid %s block at %s: %s.", methodTypeStr, block.Pos(), err),
))
continue
}
location = ProviderInstallationDirect
include = bodyContent.Include
exclude = bodyContent.Exclude
case "filesystem_mirror":
type BodyContent struct {
Path string `hcl:"path"`
Include []string `hcl:"include"`
Exclude []string `hcl:"exclude"`
}
var bodyContent BodyContent
err := hcl.DecodeObject(&bodyContent, methodBody)
if err != nil {
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
"Invalid provider_installation method block",
fmt.Sprintf("Invalid %s block at %s: %s.", methodTypeStr, block.Pos(), err),
))
continue
}
if bodyContent.Path == "" {
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
"Invalid provider_installation method block",
fmt.Sprintf("Invalid %s block at %s: \"path\" argument is required.", methodTypeStr, block.Pos()),
))
continue
}
location = ProviderInstallationFilesystemMirror(bodyContent.Path)
include = bodyContent.Include
exclude = bodyContent.Exclude
case "network_mirror":
type BodyContent struct {
URL string `hcl:"url"`
Include []string `hcl:"include"`
Exclude []string `hcl:"exclude"`
}
var bodyContent BodyContent
err := hcl.DecodeObject(&bodyContent, methodBody)
if err != nil {
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
"Invalid provider_installation method block",
fmt.Sprintf("Invalid %s block at %s: %s.", methodTypeStr, block.Pos(), err),
))
continue
}
if bodyContent.URL == "" {
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
"Invalid provider_installation method block",
fmt.Sprintf("Invalid %s block at %s: \"url\" argument is required.", methodTypeStr, block.Pos()),
))
continue
}
location = ProviderInstallationNetworkMirror(bodyContent.URL)
include = bodyContent.Include
exclude = bodyContent.Exclude
case "dev_overrides":
if len(pi.Methods) > 0 {
// We require dev_overrides to appear first if it's present,
// because dev_overrides effectively bypass the normal
// selection process for a particular provider altogether,
// and so they don't participate in the usual
// include/exclude arguments and priority ordering.
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
"Invalid provider_installation method block",
fmt.Sprintf("The dev_overrides block at at %s must appear before all other installation methods, because development overrides always have the highest priority.", methodBlock.Pos()),
))
continue
}
// The content of a dev_overrides block is a mapping from
// provider source addresses to local filesystem paths. To get
// our decoding started, we'll use the normal HCL decoder to
// populate a map of strings and then decode further from
// that.
var rawItems map[string]string
err := hcl.DecodeObject(&rawItems, methodBody)
if err != nil {
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
"Invalid provider_installation method block",
fmt.Sprintf("Invalid %s block at %s: %s.", methodTypeStr, block.Pos(), err),
))
continue
}
for rawAddr, rawPath := range rawItems {
addr, moreDiags := addrs.ParseProviderSourceString(rawAddr)
if moreDiags.HasErrors() {
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
"Invalid provider installation dev overrides",
fmt.Sprintf("The entry %q in %s is not a valid provider source string.", rawAddr, block.Pos()),
))
continue
}
dirPath := filepath.Clean(rawPath)
devOverrides[addr] = getproviders.PackageLocalDir(dirPath)
}
continue // We won't add anything to pi.Methods for this one
default:
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
"Invalid provider_installation method block",
fmt.Sprintf("Unknown provider installation method %q at %s.", methodTypeStr, methodBlock.Pos()),
))
continue
}
pi.Methods = append(pi.Methods, &ProviderInstallationMethod{
Location: location,
Include: include,
Exclude: exclude,
})
}
if len(devOverrides) > 0 {
pi.DevOverrides = devOverrides
}
ret = append(ret, pi)
}
return ret, diags
}
// ProviderInstallationMethod represents an installation method block inside
// a provider_installation block.
type ProviderInstallationMethod struct {
Location ProviderInstallationLocation
Include []string `hcl:"include"`
Exclude []string `hcl:"exclude"`
}
// ProviderInstallationLocation is an interface type representing the
// different installation location types. The concrete implementations of
// this interface are:
//
// ProviderInstallationDirect: install from the provider's origin registry
// ProviderInstallationFilesystemMirror(dir): install from a local filesystem mirror
// ProviderInstallationNetworkMirror(host): install from a network mirror
type ProviderInstallationLocation interface {
providerInstallationLocation()
}
type providerInstallationDirect [0]byte
func (i providerInstallationDirect) providerInstallationLocation() {}
// ProviderInstallationDirect is a ProviderInstallationSourceLocation
// representing installation from a provider's origin registry.
var ProviderInstallationDirect ProviderInstallationLocation = providerInstallationDirect{}
func (i providerInstallationDirect) GoString() string {
return "cliconfig.ProviderInstallationDirect"
}
// ProviderInstallationFilesystemMirror is a ProviderInstallationSourceLocation
// representing installation from a particular local filesystem mirror. The
// string value is the filesystem path to the mirror directory.
type ProviderInstallationFilesystemMirror string
func (i ProviderInstallationFilesystemMirror) providerInstallationLocation() {}
func (i ProviderInstallationFilesystemMirror) GoString() string {
return fmt.Sprintf("cliconfig.ProviderInstallationFilesystemMirror(%q)", i)
}
// ProviderInstallationNetworkMirror is a ProviderInstallationSourceLocation
// representing installation from a particular local network mirror. The
// string value is the HTTP base URL exactly as written in the configuration,
// without any normalization.
type ProviderInstallationNetworkMirror string
func (i ProviderInstallationNetworkMirror) providerInstallationLocation() {}
func (i ProviderInstallationNetworkMirror) GoString() string {
return fmt.Sprintf("cliconfig.ProviderInstallationNetworkMirror(%q)", i)
}