github.com/graywolf-at-work-2/terraform-vendor@v1.4.5/internal/command/cliconfig/provider_installation.go (about) 1 package cliconfig 2 3 import ( 4 "fmt" 5 "path/filepath" 6 7 "github.com/hashicorp/hcl" 8 hclast "github.com/hashicorp/hcl/hcl/ast" 9 "github.com/hashicorp/terraform/internal/addrs" 10 "github.com/hashicorp/terraform/internal/getproviders" 11 "github.com/hashicorp/terraform/internal/tfdiags" 12 ) 13 14 // ProviderInstallation is the structure of the "provider_installation" 15 // nested block within the CLI configuration. 16 type ProviderInstallation struct { 17 Methods []*ProviderInstallationMethod 18 19 // DevOverrides allows overriding the normal selection process for 20 // a particular subset of providers to force using a particular 21 // local directory and disregard version numbering altogether. 22 // This is here to allow provider developers to conveniently test 23 // local builds of their plugins in a development environment, without 24 // having to fuss with version constraints, dependency lock files, and 25 // so forth. 26 // 27 // This is _not_ intended for "production" use because it bypasses the 28 // usual version selection and checksum verification mechanisms for 29 // the providers in question. To make that intent/effect clearer, some 30 // Terraform commands emit warnings when overrides are present. Local 31 // mirror directories are a better way to distribute "released" 32 // providers, because they are still subject to version constraints and 33 // checksum verification. 34 DevOverrides map[addrs.Provider]getproviders.PackageLocalDir 35 } 36 37 // decodeProviderInstallationFromConfig uses the HCL AST API directly to 38 // decode "provider_installation" blocks from the given file. 39 // 40 // This uses the HCL AST directly, rather than HCL's decoder, because the 41 // intended configuration structure can't be represented using the HCL 42 // decoder's struct tags. This structure is intended as something that would 43 // be relatively easier to deal with in HCL 2 once we eventually migrate 44 // CLI config over to that, and so this function is stricter than HCL 1's 45 // decoder would be in terms of exactly what configuration shape it is 46 // expecting. 47 // 48 // Note that this function wants the top-level file object which might or 49 // might not contain provider_installation blocks, not a provider_installation 50 // block directly itself. 51 func decodeProviderInstallationFromConfig(hclFile *hclast.File) ([]*ProviderInstallation, tfdiags.Diagnostics) { 52 var ret []*ProviderInstallation 53 var diags tfdiags.Diagnostics 54 55 root := hclFile.Node.(*hclast.ObjectList) 56 57 // This is a rather odd hybrid: it's a HCL 2-like decode implemented using 58 // the HCL 1 AST API. That makes it a bit awkward in places, but it allows 59 // us to mimick the strictness of HCL 2 (making a later migration easier) 60 // and to support a block structure that the HCL 1 decoder can't represent. 61 for _, block := range root.Items { 62 if block.Keys[0].Token.Value() != "provider_installation" { 63 continue 64 } 65 // HCL only tracks whether the input was JSON or native syntax inside 66 // individual tokens, so we'll use our block type token to decide 67 // and assume that the rest of the block must be written in the same 68 // syntax, because syntax is a whole-file idea. 69 isJSON := block.Keys[0].Token.JSON 70 if block.Assign.Line != 0 && !isJSON { 71 // Seems to be an attribute rather than a block 72 diags = diags.Append(tfdiags.Sourceless( 73 tfdiags.Error, 74 "Invalid provider_installation block", 75 fmt.Sprintf("The provider_installation block at %s must not be introduced with an equals sign.", block.Pos()), 76 )) 77 continue 78 } 79 if len(block.Keys) > 1 && !isJSON { 80 diags = diags.Append(tfdiags.Sourceless( 81 tfdiags.Error, 82 "Invalid provider_installation block", 83 fmt.Sprintf("The provider_installation block at %s must not have any labels.", block.Pos()), 84 )) 85 } 86 87 pi := &ProviderInstallation{} 88 devOverrides := make(map[addrs.Provider]getproviders.PackageLocalDir) 89 90 body, ok := block.Val.(*hclast.ObjectType) 91 if !ok { 92 // We can't get in here with native HCL syntax because we 93 // already checked above that we're using block syntax, but 94 // if we're reading JSON then our value could potentially be 95 // anything. 96 diags = diags.Append(tfdiags.Sourceless( 97 tfdiags.Error, 98 "Invalid provider_installation block", 99 fmt.Sprintf("The provider_installation block at %s must not be introduced with an equals sign.", block.Pos()), 100 )) 101 continue 102 } 103 104 for _, methodBlock := range body.List.Items { 105 if methodBlock.Assign.Line != 0 && !isJSON { 106 // Seems to be an attribute rather than a block 107 diags = diags.Append(tfdiags.Sourceless( 108 tfdiags.Error, 109 "Invalid provider_installation method block", 110 fmt.Sprintf("The items inside the provider_installation block at %s must all be blocks.", block.Pos()), 111 )) 112 continue 113 } 114 if len(methodBlock.Keys) > 1 && !isJSON { 115 diags = diags.Append(tfdiags.Sourceless( 116 tfdiags.Error, 117 "Invalid provider_installation method block", 118 fmt.Sprintf("The blocks inside the provider_installation block at %s may not have any labels.", block.Pos()), 119 )) 120 } 121 122 methodBody, ok := methodBlock.Val.(*hclast.ObjectType) 123 if !ok { 124 // We can't get in here with native HCL syntax because we 125 // already checked above that we're using block syntax, but 126 // if we're reading JSON then our value could potentially be 127 // anything. 128 diags = diags.Append(tfdiags.Sourceless( 129 tfdiags.Error, 130 "Invalid provider_installation method block", 131 fmt.Sprintf("The items inside the provider_installation block at %s must all be blocks.", block.Pos()), 132 )) 133 continue 134 } 135 136 methodTypeStr := methodBlock.Keys[0].Token.Value().(string) 137 var location ProviderInstallationLocation 138 var include, exclude []string 139 switch methodTypeStr { 140 case "direct": 141 type BodyContent struct { 142 Include []string `hcl:"include"` 143 Exclude []string `hcl:"exclude"` 144 } 145 var bodyContent BodyContent 146 err := hcl.DecodeObject(&bodyContent, methodBody) 147 if err != nil { 148 diags = diags.Append(tfdiags.Sourceless( 149 tfdiags.Error, 150 "Invalid provider_installation method block", 151 fmt.Sprintf("Invalid %s block at %s: %s.", methodTypeStr, block.Pos(), err), 152 )) 153 continue 154 } 155 location = ProviderInstallationDirect 156 include = bodyContent.Include 157 exclude = bodyContent.Exclude 158 case "filesystem_mirror": 159 type BodyContent struct { 160 Path string `hcl:"path"` 161 Include []string `hcl:"include"` 162 Exclude []string `hcl:"exclude"` 163 } 164 var bodyContent BodyContent 165 err := hcl.DecodeObject(&bodyContent, methodBody) 166 if err != nil { 167 diags = diags.Append(tfdiags.Sourceless( 168 tfdiags.Error, 169 "Invalid provider_installation method block", 170 fmt.Sprintf("Invalid %s block at %s: %s.", methodTypeStr, block.Pos(), err), 171 )) 172 continue 173 } 174 if bodyContent.Path == "" { 175 diags = diags.Append(tfdiags.Sourceless( 176 tfdiags.Error, 177 "Invalid provider_installation method block", 178 fmt.Sprintf("Invalid %s block at %s: \"path\" argument is required.", methodTypeStr, block.Pos()), 179 )) 180 continue 181 } 182 location = ProviderInstallationFilesystemMirror(bodyContent.Path) 183 include = bodyContent.Include 184 exclude = bodyContent.Exclude 185 case "network_mirror": 186 type BodyContent struct { 187 URL string `hcl:"url"` 188 Include []string `hcl:"include"` 189 Exclude []string `hcl:"exclude"` 190 } 191 var bodyContent BodyContent 192 err := hcl.DecodeObject(&bodyContent, methodBody) 193 if err != nil { 194 diags = diags.Append(tfdiags.Sourceless( 195 tfdiags.Error, 196 "Invalid provider_installation method block", 197 fmt.Sprintf("Invalid %s block at %s: %s.", methodTypeStr, block.Pos(), err), 198 )) 199 continue 200 } 201 if bodyContent.URL == "" { 202 diags = diags.Append(tfdiags.Sourceless( 203 tfdiags.Error, 204 "Invalid provider_installation method block", 205 fmt.Sprintf("Invalid %s block at %s: \"url\" argument is required.", methodTypeStr, block.Pos()), 206 )) 207 continue 208 } 209 location = ProviderInstallationNetworkMirror(bodyContent.URL) 210 include = bodyContent.Include 211 exclude = bodyContent.Exclude 212 case "dev_overrides": 213 if len(pi.Methods) > 0 { 214 // We require dev_overrides to appear first if it's present, 215 // because dev_overrides effectively bypass the normal 216 // selection process for a particular provider altogether, 217 // and so they don't participate in the usual 218 // include/exclude arguments and priority ordering. 219 diags = diags.Append(tfdiags.Sourceless( 220 tfdiags.Error, 221 "Invalid provider_installation method block", 222 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()), 223 )) 224 continue 225 } 226 227 // The content of a dev_overrides block is a mapping from 228 // provider source addresses to local filesystem paths. To get 229 // our decoding started, we'll use the normal HCL decoder to 230 // populate a map of strings and then decode further from 231 // that. 232 var rawItems map[string]string 233 err := hcl.DecodeObject(&rawItems, methodBody) 234 if err != nil { 235 diags = diags.Append(tfdiags.Sourceless( 236 tfdiags.Error, 237 "Invalid provider_installation method block", 238 fmt.Sprintf("Invalid %s block at %s: %s.", methodTypeStr, block.Pos(), err), 239 )) 240 continue 241 } 242 243 for rawAddr, rawPath := range rawItems { 244 addr, moreDiags := addrs.ParseProviderSourceString(rawAddr) 245 if moreDiags.HasErrors() { 246 diags = diags.Append(tfdiags.Sourceless( 247 tfdiags.Error, 248 "Invalid provider installation dev overrides", 249 fmt.Sprintf("The entry %q in %s is not a valid provider source string.\n\n%s", rawAddr, block.Pos(), moreDiags.Err().Error()), 250 )) 251 continue 252 } 253 dirPath := filepath.Clean(rawPath) 254 devOverrides[addr] = getproviders.PackageLocalDir(dirPath) 255 } 256 257 continue // We won't add anything to pi.Methods for this one 258 259 default: 260 diags = diags.Append(tfdiags.Sourceless( 261 tfdiags.Error, 262 "Invalid provider_installation method block", 263 fmt.Sprintf("Unknown provider installation method %q at %s.", methodTypeStr, methodBlock.Pos()), 264 )) 265 continue 266 } 267 268 pi.Methods = append(pi.Methods, &ProviderInstallationMethod{ 269 Location: location, 270 Include: include, 271 Exclude: exclude, 272 }) 273 } 274 275 if len(devOverrides) > 0 { 276 pi.DevOverrides = devOverrides 277 } 278 279 ret = append(ret, pi) 280 } 281 282 return ret, diags 283 } 284 285 // ProviderInstallationMethod represents an installation method block inside 286 // a provider_installation block. 287 type ProviderInstallationMethod struct { 288 Location ProviderInstallationLocation 289 Include []string `hcl:"include"` 290 Exclude []string `hcl:"exclude"` 291 } 292 293 // ProviderInstallationLocation is an interface type representing the 294 // different installation location types. The concrete implementations of 295 // this interface are: 296 // 297 // - [ProviderInstallationDirect]: install from the provider's origin registry 298 // - [ProviderInstallationFilesystemMirror] (dir): install from a local filesystem mirror 299 // - [ProviderInstallationNetworkMirror] (host): install from a network mirror 300 type ProviderInstallationLocation interface { 301 providerInstallationLocation() 302 } 303 304 type providerInstallationDirect [0]byte 305 306 func (i providerInstallationDirect) providerInstallationLocation() {} 307 308 // ProviderInstallationDirect is a ProviderInstallationSourceLocation 309 // representing installation from a provider's origin registry. 310 var ProviderInstallationDirect ProviderInstallationLocation = providerInstallationDirect{} 311 312 func (i providerInstallationDirect) GoString() string { 313 return "cliconfig.ProviderInstallationDirect" 314 } 315 316 // ProviderInstallationFilesystemMirror is a ProviderInstallationSourceLocation 317 // representing installation from a particular local filesystem mirror. The 318 // string value is the filesystem path to the mirror directory. 319 type ProviderInstallationFilesystemMirror string 320 321 func (i ProviderInstallationFilesystemMirror) providerInstallationLocation() {} 322 323 func (i ProviderInstallationFilesystemMirror) GoString() string { 324 return fmt.Sprintf("cliconfig.ProviderInstallationFilesystemMirror(%q)", i) 325 } 326 327 // ProviderInstallationNetworkMirror is a ProviderInstallationSourceLocation 328 // representing installation from a particular local network mirror. The 329 // string value is the HTTP base URL exactly as written in the configuration, 330 // without any normalization. 331 type ProviderInstallationNetworkMirror string 332 333 func (i ProviderInstallationNetworkMirror) providerInstallationLocation() {} 334 335 func (i ProviderInstallationNetworkMirror) GoString() string { 336 return fmt.Sprintf("cliconfig.ProviderInstallationNetworkMirror(%q)", i) 337 }