command/cliconfig: handle provider_installation block in JSON syntax
The CLI config can be written in both native HCL and HCL JSON syntaxes, so the provider_installation block must be expressible using JSON too. Our previous checks to approximate HCL 2-level strictness were too strict for HCL JSON where things are more ambiguous even in HCL 2, so this includes some additional relaxations if we detect that we're decoding an AST produced from a JSON file. This is still subject to the quirky ways HCL 1 handles JSON though, so the JSON value must be structured in a way that doesn't trigger HCL's heuristics that try to guess what is a block and what is an attribute. (This is the issue that HCL 2 fixes by always decoding using a schema; there's more context on this in: https://log.martinatkins.me/2019/04/25/hcl-json/ )
This commit is contained in:
parent
f5012c12da
commit
c7fe6b9160
|
@ -42,7 +42,12 @@ func decodeProviderInstallationFromConfig(hclFile *hclast.File) ([]*ProviderInst
|
||||||
if block.Keys[0].Token.Value() != "provider_installation" {
|
if block.Keys[0].Token.Value() != "provider_installation" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if block.Assign.Line != 0 {
|
// 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
|
// Seems to be an attribute rather than a block
|
||||||
diags = diags.Append(tfdiags.Sourceless(
|
diags = diags.Append(tfdiags.Sourceless(
|
||||||
tfdiags.Error,
|
tfdiags.Error,
|
||||||
|
@ -51,7 +56,7 @@ func decodeProviderInstallationFromConfig(hclFile *hclast.File) ([]*ProviderInst
|
||||||
))
|
))
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if len(block.Keys) > 1 {
|
if len(block.Keys) > 1 && !isJSON {
|
||||||
diags = diags.Append(tfdiags.Sourceless(
|
diags = diags.Append(tfdiags.Sourceless(
|
||||||
tfdiags.Error,
|
tfdiags.Error,
|
||||||
"Invalid provider_installation block",
|
"Invalid provider_installation block",
|
||||||
|
@ -61,13 +66,22 @@ func decodeProviderInstallationFromConfig(hclFile *hclast.File) ([]*ProviderInst
|
||||||
|
|
||||||
pi := &ProviderInstallation{}
|
pi := &ProviderInstallation{}
|
||||||
|
|
||||||
// Because we checked block.Assign was unset above we can assume that
|
body, ok := block.Val.(*hclast.ObjectType)
|
||||||
// we're reading something produced with block syntax and therefore
|
if !ok {
|
||||||
// it will always be an hclast.ObjectType.
|
// We can't get in here with native HCL syntax because we
|
||||||
body := block.Val.(*hclast.ObjectType)
|
// 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 {
|
for _, methodBlock := range body.List.Items {
|
||||||
if methodBlock.Assign.Line != 0 {
|
if methodBlock.Assign.Line != 0 && !isJSON {
|
||||||
// Seems to be an attribute rather than a block
|
// Seems to be an attribute rather than a block
|
||||||
diags = diags.Append(tfdiags.Sourceless(
|
diags = diags.Append(tfdiags.Sourceless(
|
||||||
tfdiags.Error,
|
tfdiags.Error,
|
||||||
|
@ -76,7 +90,7 @@ func decodeProviderInstallationFromConfig(hclFile *hclast.File) ([]*ProviderInst
|
||||||
))
|
))
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if len(methodBlock.Keys) > 1 {
|
if len(methodBlock.Keys) > 1 && !isJSON {
|
||||||
diags = diags.Append(tfdiags.Sourceless(
|
diags = diags.Append(tfdiags.Sourceless(
|
||||||
tfdiags.Error,
|
tfdiags.Error,
|
||||||
"Invalid provider_installation method block",
|
"Invalid provider_installation method block",
|
||||||
|
@ -84,7 +98,19 @@ func decodeProviderInstallationFromConfig(hclFile *hclast.File) ([]*ProviderInst
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
methodBody := methodBlock.Val.(*hclast.ObjectType)
|
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)
|
methodTypeStr := methodBlock.Keys[0].Token.Value().(string)
|
||||||
var location ProviderInstallationLocation
|
var location ProviderInstallationLocation
|
||||||
|
|
|
@ -8,7 +8,9 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestLoadConfig_providerInstallation(t *testing.T) {
|
func TestLoadConfig_providerInstallation(t *testing.T) {
|
||||||
got, diags := loadConfigFile(filepath.Join(fixtureDir, "provider-installation"))
|
for _, configFile := range []string{"provider-installation", "provider-installation.json"} {
|
||||||
|
t.Run(configFile, func(t *testing.T) {
|
||||||
|
got, diags := loadConfigFile(filepath.Join(fixtureDir, configFile))
|
||||||
if diags.HasErrors() {
|
if diags.HasErrors() {
|
||||||
t.Errorf("unexpected diagnostics: %s", diags.Err().Error())
|
t.Errorf("unexpected diagnostics: %s", diags.Err().Error())
|
||||||
}
|
}
|
||||||
|
@ -41,6 +43,8 @@ func TestLoadConfig_providerInstallation(t *testing.T) {
|
||||||
if diff := cmp.Diff(want, got); diff != "" {
|
if diff := cmp.Diff(want, got); diff != "" {
|
||||||
t.Errorf("wrong result\n%s", diff)
|
t.Errorf("wrong result\n%s", diff)
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLoadConfig_providerInstallationErrors(t *testing.T) {
|
func TestLoadConfig_providerInstallationErrors(t *testing.T) {
|
||||||
|
|
|
@ -0,0 +1,19 @@
|
||||||
|
{
|
||||||
|
"provider_installation": {
|
||||||
|
"filesystem_mirror": [{
|
||||||
|
"path": "/tmp/example1",
|
||||||
|
"include": ["example.com/*/*"]
|
||||||
|
}],
|
||||||
|
"network_mirror": [{
|
||||||
|
"url": "https://tf-Mirror.example.com/",
|
||||||
|
"include": ["registry.terraform.io/*/*"],
|
||||||
|
"exclude": ["registry.Terraform.io/foobar/*"]
|
||||||
|
}],
|
||||||
|
"filesystem_mirror": [{
|
||||||
|
"path": "/tmp/example2"
|
||||||
|
}],
|
||||||
|
"direct": [{
|
||||||
|
"exclude": ["example.com/*/*"]
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue