2020-04-22 00:48:07 +02:00
package cliconfig
import (
"fmt"
2020-10-15 03:00:23 +02:00
"path/filepath"
2020-04-22 00:48:07 +02:00
"github.com/hashicorp/hcl"
hclast "github.com/hashicorp/hcl/hcl/ast"
2020-10-15 03:00:23 +02:00
"github.com/hashicorp/terraform/addrs"
"github.com/hashicorp/terraform/internal/getproviders"
2020-04-22 00:48:07 +02:00
"github.com/hashicorp/terraform/tfdiags"
)
// ProviderInstallation is the structure of the "provider_installation"
// nested block within the CLI configuration.
type ProviderInstallation struct {
2020-04-23 01:28:06 +02:00
Methods [ ] * ProviderInstallationMethod
2020-10-15 03:00:23 +02:00
// 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
2020-04-22 00:48:07 +02:00
}
// 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
}
2020-04-23 02:12:33 +02:00
// 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 {
2020-04-22 00:48:07 +02:00
// 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
}
2020-04-23 02:12:33 +02:00
if len ( block . Keys ) > 1 && ! isJSON {
2020-04-22 00:48:07 +02:00
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 { }
2020-10-15 03:00:23 +02:00
devOverrides := make ( map [ addrs . Provider ] getproviders . PackageLocalDir )
2020-04-22 00:48:07 +02:00
2020-04-23 02:12:33 +02:00
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
}
2020-04-22 00:48:07 +02:00
2020-04-23 01:28:06 +02:00
for _ , methodBlock := range body . List . Items {
2020-04-23 02:12:33 +02:00
if methodBlock . Assign . Line != 0 && ! isJSON {
2020-04-22 00:48:07 +02:00
// Seems to be an attribute rather than a block
diags = diags . Append ( tfdiags . Sourceless (
tfdiags . Error ,
2020-04-23 01:28:06 +02:00
"Invalid provider_installation method block" ,
2020-04-22 00:48:07 +02:00
fmt . Sprintf ( "The items inside the provider_installation block at %s must all be blocks." , block . Pos ( ) ) ,
) )
continue
}
2020-04-23 02:12:33 +02:00
if len ( methodBlock . Keys ) > 1 && ! isJSON {
2020-04-22 00:48:07 +02:00
diags = diags . Append ( tfdiags . Sourceless (
tfdiags . Error ,
2020-04-23 01:28:06 +02:00
"Invalid provider_installation method block" ,
2020-04-22 00:48:07 +02:00
fmt . Sprintf ( "The blocks inside the provider_installation block at %s may not have any labels." , block . Pos ( ) ) ,
) )
}
2020-04-23 02:12:33 +02:00
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
}
2020-04-22 00:48:07 +02:00
2020-04-23 01:28:06 +02:00
methodTypeStr := methodBlock . Keys [ 0 ] . Token . Value ( ) . ( string )
var location ProviderInstallationLocation
2020-04-22 00:48:07 +02:00
var include , exclude [ ] string
2020-04-23 01:28:06 +02:00
switch methodTypeStr {
2020-04-22 00:48:07 +02:00
case "direct" :
type BodyContent struct {
Include [ ] string ` hcl:"include" `
Exclude [ ] string ` hcl:"exclude" `
}
var bodyContent BodyContent
2020-04-23 01:28:06 +02:00
err := hcl . DecodeObject ( & bodyContent , methodBody )
2020-04-22 00:48:07 +02:00
if err != nil {
diags = diags . Append ( tfdiags . Sourceless (
tfdiags . Error ,
2020-04-23 01:28:06 +02:00
"Invalid provider_installation method block" ,
fmt . Sprintf ( "Invalid %s block at %s: %s." , methodTypeStr , block . Pos ( ) , err ) ,
2020-04-22 00:48:07 +02:00
) )
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
2020-04-23 01:28:06 +02:00
err := hcl . DecodeObject ( & bodyContent , methodBody )
2020-04-22 00:48:07 +02:00
if err != nil {
diags = diags . Append ( tfdiags . Sourceless (
tfdiags . Error ,
2020-04-23 01:28:06 +02:00
"Invalid provider_installation method block" ,
fmt . Sprintf ( "Invalid %s block at %s: %s." , methodTypeStr , block . Pos ( ) , err ) ,
2020-04-22 00:48:07 +02:00
) )
continue
}
if bodyContent . Path == "" {
diags = diags . Append ( tfdiags . Sourceless (
tfdiags . Error ,
2020-04-23 01:28:06 +02:00
"Invalid provider_installation method block" ,
fmt . Sprintf ( "Invalid %s block at %s: \"path\" argument is required." , methodTypeStr , block . Pos ( ) ) ,
2020-04-22 00:48:07 +02:00
) )
continue
}
location = ProviderInstallationFilesystemMirror ( bodyContent . Path )
include = bodyContent . Include
exclude = bodyContent . Exclude
case "network_mirror" :
type BodyContent struct {
2020-04-23 00:58:40 +02:00
URL string ` hcl:"url" `
2020-04-22 00:48:07 +02:00
Include [ ] string ` hcl:"include" `
Exclude [ ] string ` hcl:"exclude" `
}
var bodyContent BodyContent
2020-04-23 01:28:06 +02:00
err := hcl . DecodeObject ( & bodyContent , methodBody )
2020-04-22 00:48:07 +02:00
if err != nil {
diags = diags . Append ( tfdiags . Sourceless (
tfdiags . Error ,
2020-04-23 01:28:06 +02:00
"Invalid provider_installation method block" ,
fmt . Sprintf ( "Invalid %s block at %s: %s." , methodTypeStr , block . Pos ( ) , err ) ,
2020-04-22 00:48:07 +02:00
) )
continue
}
2020-04-23 00:58:40 +02:00
if bodyContent . URL == "" {
2020-04-22 00:48:07 +02:00
diags = diags . Append ( tfdiags . Sourceless (
tfdiags . Error ,
2020-04-23 01:28:06 +02:00
"Invalid provider_installation method block" ,
fmt . Sprintf ( "Invalid %s block at %s: \"url\" argument is required." , methodTypeStr , block . Pos ( ) ) ,
2020-04-22 00:48:07 +02:00
) )
continue
}
2020-04-23 00:58:40 +02:00
location = ProviderInstallationNetworkMirror ( bodyContent . URL )
2020-04-22 00:48:07 +02:00
include = bodyContent . Include
exclude = bodyContent . Exclude
2020-10-15 03:00:23 +02:00
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
2020-04-22 00:48:07 +02:00
default :
diags = diags . Append ( tfdiags . Sourceless (
tfdiags . Error ,
2020-04-23 01:28:06 +02:00
"Invalid provider_installation method block" ,
fmt . Sprintf ( "Unknown provider installation method %q at %s." , methodTypeStr , methodBlock . Pos ( ) ) ,
2020-04-22 00:48:07 +02:00
) )
continue
}
2020-04-23 01:28:06 +02:00
pi . Methods = append ( pi . Methods , & ProviderInstallationMethod {
2020-04-22 00:48:07 +02:00
Location : location ,
Include : include ,
Exclude : exclude ,
} )
}
2020-10-15 03:00:23 +02:00
if len ( devOverrides ) > 0 {
pi . DevOverrides = devOverrides
}
2020-04-22 00:48:07 +02:00
ret = append ( ret , pi )
}
return ret , diags
}
2020-04-23 01:28:06 +02:00
// ProviderInstallationMethod represents an installation method block inside
2020-04-22 00:48:07 +02:00
// a provider_installation block.
2020-04-23 01:28:06 +02:00
type ProviderInstallationMethod struct {
Location ProviderInstallationLocation
2020-04-22 00:48:07 +02:00
Include [ ] string ` hcl:"include" `
Exclude [ ] string ` hcl:"exclude" `
}
2020-04-23 01:28:06 +02:00
// ProviderInstallationLocation is an interface type representing the
// different installation location types. The concrete implementations of
2020-04-22 00:48:07 +02:00
// 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
2020-04-23 01:28:06 +02:00
type ProviderInstallationLocation interface {
2020-04-22 00:48:07 +02:00
providerInstallationLocation ( )
}
2020-04-23 02:31:57 +02:00
type providerInstallationDirect [ 0 ] byte
2020-04-22 00:48:07 +02:00
2020-04-23 02:31:57 +02:00
func ( i providerInstallationDirect ) providerInstallationLocation ( ) { }
2020-04-22 00:48:07 +02:00
// ProviderInstallationDirect is a ProviderInstallationSourceLocation
// representing installation from a provider's origin registry.
2020-04-23 02:31:57 +02:00
var ProviderInstallationDirect ProviderInstallationLocation = providerInstallationDirect { }
func ( i providerInstallationDirect ) GoString ( ) string {
return "cliconfig.ProviderInstallationDirect"
}
2020-04-22 00:48:07 +02:00
// 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 ( ) { }
2020-04-23 02:31:57 +02:00
func ( i ProviderInstallationFilesystemMirror ) GoString ( ) string {
return fmt . Sprintf ( "cliconfig.ProviderInstallationFilesystemMirror(%q)" , i )
}
2020-04-22 00:48:07 +02:00
// ProviderInstallationNetworkMirror is a ProviderInstallationSourceLocation
// representing installation from a particular local network mirror. The
2020-04-23 00:58:40 +02:00
// string value is the HTTP base URL exactly as written in the configuration,
// without any normalization.
2020-04-22 00:48:07 +02:00
type ProviderInstallationNetworkMirror string
func ( i ProviderInstallationNetworkMirror ) providerInstallationLocation ( ) { }
2020-04-23 02:31:57 +02:00
func ( i ProviderInstallationNetworkMirror ) GoString ( ) string {
return fmt . Sprintf ( "cliconfig.ProviderInstallationNetworkMirror(%q)" , i )
}