2020-04-22 00:48:07 +02:00
|
|
|
package cliconfig
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
|
|
|
|
"github.com/hashicorp/hcl"
|
|
|
|
hclast "github.com/hashicorp/hcl/hcl/ast"
|
|
|
|
"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-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-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
|
|
|
|
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,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
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)
|
|
|
|
}
|