mirror of
https://github.com/opentofu/opentofu.git
synced 2024-12-28 01:41:48 -06:00
b0215fcd0f
This loader uses the HCL2 parser and decoder to process a config file, and then transforms the result into the same shape as would be produced by the HCL config loader. To avoid making changes to the existing config structures (which are depended on across much of the codebase) we first decode into a set of HCL2-tailored structs and then process them into the public-facing structs that a loader is expected to return. This is a compromise to keep the config package API broadly unchanged for now. Once we're ready to remove the old HCL loader (which implies that we're ready to support HCL2 natively elsewhere in the codebase) we will be able to simplify this quite considerably. Due to some mismatches of abstraction between HCL/HIL and HCL2, some shimming is required to get the required result.
511 lines
16 KiB
Go
511 lines
16 KiB
Go
package config
|
|
|
|
import (
|
|
"reflect"
|
|
"testing"
|
|
|
|
"github.com/zclconf/go-cty/cty"
|
|
|
|
gohcl2 "github.com/hashicorp/hcl2/gohcl"
|
|
hcl2 "github.com/hashicorp/hcl2/hcl"
|
|
)
|
|
|
|
func TestHCL2ConfigurableConfigurable(t *testing.T) {
|
|
var _ configurable = new(hcl2Configurable)
|
|
}
|
|
|
|
func TestHCL2Basic(t *testing.T) {
|
|
loader := globalHCL2Loader
|
|
cbl, _, err := loader.loadFile("test-fixtures/basic-hcl2.tf")
|
|
if err != nil {
|
|
if diags, isDiags := err.(hcl2.Diagnostics); isDiags {
|
|
for _, diag := range diags {
|
|
t.Logf("- %s", diag.Error())
|
|
}
|
|
t.Fatalf("unexpected diagnostics in load")
|
|
} else {
|
|
t.Fatalf("unexpected error in load: %s", err)
|
|
}
|
|
}
|
|
|
|
cfg, err := cbl.Config()
|
|
if err != nil {
|
|
if diags, isDiags := err.(hcl2.Diagnostics); isDiags {
|
|
for _, diag := range diags {
|
|
t.Logf("- %s", diag.Error())
|
|
}
|
|
t.Fatalf("unexpected diagnostics in decode")
|
|
} else {
|
|
t.Fatalf("unexpected error in decode: %s", err)
|
|
}
|
|
}
|
|
|
|
// Unfortunately the config structure isn't DeepEqual-friendly because
|
|
// of all the nested RawConfig, etc structures, so we'll need to
|
|
// hand-assert each item.
|
|
|
|
// The "terraform" block
|
|
if cfg.Terraform == nil {
|
|
t.Fatalf("Terraform field is nil")
|
|
}
|
|
if got, want := cfg.Terraform.RequiredVersion, "foo"; got != want {
|
|
t.Errorf("wrong Terraform.RequiredVersion %q; want %q", got, want)
|
|
}
|
|
if cfg.Terraform.Backend == nil {
|
|
t.Fatalf("Terraform.Backend is nil")
|
|
}
|
|
if got, want := cfg.Terraform.Backend.Type, "baz"; got != want {
|
|
t.Errorf("wrong Terraform.Backend.Type %q; want %q", got, want)
|
|
}
|
|
if got, want := cfg.Terraform.Backend.RawConfig.Raw, map[string]interface{}{"something": "nothing"}; !reflect.DeepEqual(got, want) {
|
|
t.Errorf("wrong Terraform.Backend.RawConfig.Raw %#v; want %#v", got, want)
|
|
}
|
|
|
|
// The "atlas" block
|
|
if cfg.Atlas == nil {
|
|
t.Fatalf("Atlas field is nil")
|
|
}
|
|
if got, want := cfg.Atlas.Name, "example/foo"; got != want {
|
|
t.Errorf("wrong Atlas.Name %q; want %q", got, want)
|
|
}
|
|
|
|
// "module" blocks
|
|
if got, want := len(cfg.Modules), 1; got != want {
|
|
t.Errorf("Modules slice has wrong length %#v; want %#v", got, want)
|
|
} else {
|
|
m := cfg.Modules[0]
|
|
if got, want := m.Name, "child"; got != want {
|
|
t.Errorf("wrong Modules[0].Name %#v; want %#v", got, want)
|
|
}
|
|
if got, want := m.Source, "./baz"; got != want {
|
|
t.Errorf("wrong Modules[0].Source %#v; want %#v", got, want)
|
|
}
|
|
want := map[string]string{"toasty": "true"}
|
|
var got map[string]string
|
|
gohcl2.DecodeBody(m.RawConfig.Body, nil, &got)
|
|
if !reflect.DeepEqual(got, want) {
|
|
t.Errorf("wrong Modules[0].RawConfig.Body %#v; want %#v", got, want)
|
|
}
|
|
}
|
|
|
|
// "resource" blocks
|
|
if got, want := len(cfg.Resources), 5; got != want {
|
|
t.Errorf("Resources slice has wrong length %#v; want %#v", got, want)
|
|
} else {
|
|
{
|
|
r := cfg.Resources[0]
|
|
|
|
if got, want := r.Id(), "aws_security_group.firewall"; got != want {
|
|
t.Errorf("wrong Resources[0].Id() %#v; want %#v", got, want)
|
|
}
|
|
|
|
wantConfig := map[string]string{}
|
|
var gotConfig map[string]string
|
|
gohcl2.DecodeBody(r.RawConfig.Body, nil, &gotConfig)
|
|
if !reflect.DeepEqual(gotConfig, wantConfig) {
|
|
t.Errorf("wrong Resources[0].RawConfig.Body %#v; want %#v", gotConfig, wantConfig)
|
|
}
|
|
|
|
wantCount := map[string]string{"count": "5"}
|
|
var gotCount map[string]string
|
|
gohcl2.DecodeBody(r.RawCount.Body, nil, &gotCount)
|
|
if !reflect.DeepEqual(gotCount, wantCount) {
|
|
t.Errorf("wrong Resources[0].RawCount.Body %#v; want %#v", gotCount, wantCount)
|
|
}
|
|
if got, want := r.RawCount.Key, "count"; got != want {
|
|
t.Errorf("wrong Resources[0].RawCount.Key %#v; want %#v", got, want)
|
|
}
|
|
|
|
if got, want := len(r.Provisioners), 0; got != want {
|
|
t.Errorf("wrong Resources[0].Provisioners length %#v; want %#v", got, want)
|
|
}
|
|
if got, want := len(r.DependsOn), 0; got != want {
|
|
t.Errorf("wrong Resources[0].DependsOn length %#v; want %#v", got, want)
|
|
}
|
|
if got, want := r.Provider, "another"; got != want {
|
|
t.Errorf("wrong Resources[0].Provider %#v; want %#v", got, want)
|
|
}
|
|
if got, want := r.Lifecycle, (ResourceLifecycle{}); !reflect.DeepEqual(got, want) {
|
|
t.Errorf("wrong Resources[0].Lifecycle %#v; want %#v", got, want)
|
|
}
|
|
}
|
|
{
|
|
r := cfg.Resources[1]
|
|
|
|
if got, want := r.Id(), "aws_instance.web"; got != want {
|
|
t.Errorf("wrong Resources[1].Id() %#v; want %#v", got, want)
|
|
}
|
|
if got, want := r.Provider, ""; got != want {
|
|
t.Errorf("wrong Resources[1].Provider %#v; want %#v", got, want)
|
|
}
|
|
|
|
if got, want := len(r.Provisioners), 1; got != want {
|
|
t.Errorf("wrong Resources[1].Provisioners length %#v; want %#v", got, want)
|
|
} else {
|
|
p := r.Provisioners[0]
|
|
|
|
if got, want := p.Type, "file"; got != want {
|
|
t.Errorf("wrong Resources[1].Provisioners[0].Type %#v; want %#v", got, want)
|
|
}
|
|
|
|
wantConfig := map[string]string{
|
|
"source": "foo",
|
|
"destination": "bar",
|
|
}
|
|
var gotConfig map[string]string
|
|
gohcl2.DecodeBody(p.RawConfig.Body, nil, &gotConfig)
|
|
if !reflect.DeepEqual(gotConfig, wantConfig) {
|
|
t.Errorf("wrong Resources[1].Provisioners[0].RawConfig.Body %#v; want %#v", gotConfig, wantConfig)
|
|
}
|
|
|
|
wantConn := map[string]string{
|
|
"default": "true",
|
|
}
|
|
var gotConn map[string]string
|
|
gohcl2.DecodeBody(p.ConnInfo.Body, nil, &gotConn)
|
|
if !reflect.DeepEqual(gotConn, wantConn) {
|
|
t.Errorf("wrong Resources[1].Provisioners[0].ConnInfo.Body %#v; want %#v", gotConn, wantConn)
|
|
}
|
|
}
|
|
|
|
// We'll use these throwaway structs to more easily decode and
|
|
// compare the main config body.
|
|
type instanceNetworkInterface struct {
|
|
DeviceIndex int `hcl:"device_index"`
|
|
Description string `hcl:"description"`
|
|
}
|
|
type instanceConfig struct {
|
|
AMI string `hcl:"ami"`
|
|
SecurityGroups []string `hcl:"security_groups"`
|
|
NetworkInterface instanceNetworkInterface `hcl:"network_interface,block"`
|
|
}
|
|
var gotConfig instanceConfig
|
|
wantConfig := instanceConfig{
|
|
AMI: "ami-abc123",
|
|
SecurityGroups: []string{"foo", "sg-firewall"},
|
|
NetworkInterface: instanceNetworkInterface{
|
|
DeviceIndex: 0,
|
|
Description: "Main network interface",
|
|
},
|
|
}
|
|
ctx := &hcl2.EvalContext{
|
|
Variables: map[string]cty.Value{
|
|
"var": cty.ObjectVal(map[string]cty.Value{
|
|
"foo": cty.StringVal("ami-abc123"),
|
|
}),
|
|
"aws_security_group": cty.ObjectVal(map[string]cty.Value{
|
|
"firewall": cty.ObjectVal(map[string]cty.Value{
|
|
"foo": cty.StringVal("sg-firewall"),
|
|
}),
|
|
}),
|
|
},
|
|
}
|
|
diags := gohcl2.DecodeBody(r.RawConfig.Body, ctx, &gotConfig)
|
|
if len(diags) != 0 {
|
|
t.Errorf("unexpected diagnostics decoding Resources[1].RawConfig.Body")
|
|
for _, diag := range diags {
|
|
t.Logf("- %s", diag.Error())
|
|
}
|
|
}
|
|
if !reflect.DeepEqual(gotConfig, wantConfig) {
|
|
t.Errorf("wrong Resources[1].RawConfig.Body %#v; want %#v", gotConfig, wantConfig)
|
|
}
|
|
|
|
}
|
|
{
|
|
r := cfg.Resources[2]
|
|
|
|
if got, want := r.Id(), "aws_instance.db"; got != want {
|
|
t.Errorf("wrong Resources[2].Id() %#v; want %#v", got, want)
|
|
}
|
|
if got, want := r.DependsOn, []string{"aws_instance.web"}; !reflect.DeepEqual(got, want) {
|
|
t.Errorf("wrong Resources[2].DependsOn %#v; want %#v", got, want)
|
|
}
|
|
|
|
if got, want := len(r.Provisioners), 1; got != want {
|
|
t.Errorf("wrong Resources[2].Provisioners length %#v; want %#v", got, want)
|
|
} else {
|
|
p := r.Provisioners[0]
|
|
|
|
if got, want := p.Type, "file"; got != want {
|
|
t.Errorf("wrong Resources[2].Provisioners[0].Type %#v; want %#v", got, want)
|
|
}
|
|
|
|
wantConfig := map[string]string{
|
|
"source": "here",
|
|
"destination": "there",
|
|
}
|
|
var gotConfig map[string]string
|
|
gohcl2.DecodeBody(p.RawConfig.Body, nil, &gotConfig)
|
|
if !reflect.DeepEqual(gotConfig, wantConfig) {
|
|
t.Errorf("wrong Resources[2].Provisioners[0].RawConfig.Body %#v; want %#v", gotConfig, wantConfig)
|
|
}
|
|
|
|
wantConn := map[string]string{
|
|
"default": "false",
|
|
}
|
|
var gotConn map[string]string
|
|
gohcl2.DecodeBody(p.ConnInfo.Body, nil, &gotConn)
|
|
if !reflect.DeepEqual(gotConn, wantConn) {
|
|
t.Errorf("wrong Resources[2].Provisioners[0].ConnInfo.Body %#v; want %#v", gotConn, wantConn)
|
|
}
|
|
}
|
|
}
|
|
{
|
|
r := cfg.Resources[3]
|
|
|
|
if got, want := r.Id(), "data.do.simple"; got != want {
|
|
t.Errorf("wrong Resources[3].Id() %#v; want %#v", got, want)
|
|
}
|
|
if got, want := r.DependsOn, []string(nil); !reflect.DeepEqual(got, want) {
|
|
t.Errorf("wrong Resources[3].DependsOn %#v; want %#v", got, want)
|
|
}
|
|
if got, want := r.Provider, "do.foo"; got != want {
|
|
t.Errorf("wrong Resources[3].Provider %#v; want %#v", got, want)
|
|
}
|
|
|
|
wantConfig := map[string]string{
|
|
"foo": "baz",
|
|
}
|
|
var gotConfig map[string]string
|
|
gohcl2.DecodeBody(r.RawConfig.Body, nil, &gotConfig)
|
|
if !reflect.DeepEqual(gotConfig, wantConfig) {
|
|
t.Errorf("wrong Resources[3].RawConfig.Body %#v; want %#v", gotConfig, wantConfig)
|
|
}
|
|
}
|
|
{
|
|
r := cfg.Resources[4]
|
|
|
|
if got, want := r.Id(), "data.do.depends"; got != want {
|
|
t.Errorf("wrong Resources[4].Id() %#v; want %#v", got, want)
|
|
}
|
|
if got, want := r.DependsOn, []string{"data.do.simple"}; !reflect.DeepEqual(got, want) {
|
|
t.Errorf("wrong Resources[4].DependsOn %#v; want %#v", got, want)
|
|
}
|
|
if got, want := r.Provider, ""; got != want {
|
|
t.Errorf("wrong Resources[4].Provider %#v; want %#v", got, want)
|
|
}
|
|
|
|
wantConfig := map[string]string{}
|
|
var gotConfig map[string]string
|
|
gohcl2.DecodeBody(r.RawConfig.Body, nil, &gotConfig)
|
|
if !reflect.DeepEqual(gotConfig, wantConfig) {
|
|
t.Errorf("wrong Resources[4].RawConfig.Body %#v; want %#v", gotConfig, wantConfig)
|
|
}
|
|
}
|
|
}
|
|
|
|
// "variable" blocks
|
|
if got, want := len(cfg.Variables), 3; got != want {
|
|
t.Errorf("Variables slice has wrong length %#v; want %#v", got, want)
|
|
} else {
|
|
{
|
|
v := cfg.Variables[0]
|
|
|
|
if got, want := v.Name, "foo"; got != want {
|
|
t.Errorf("wrong Variables[0].Name %#v; want %#v", got, want)
|
|
}
|
|
if got, want := v.Default, "bar"; got != want {
|
|
t.Errorf("wrong Variables[0].Default %#v; want %#v", got, want)
|
|
}
|
|
if got, want := v.Description, "barbar"; got != want {
|
|
t.Errorf("wrong Variables[0].Description %#v; want %#v", got, want)
|
|
}
|
|
if got, want := v.DeclaredType, ""; got != want {
|
|
t.Errorf("wrong Variables[0].DeclaredType %#v; want %#v", got, want)
|
|
}
|
|
}
|
|
{
|
|
v := cfg.Variables[1]
|
|
|
|
if got, want := v.Name, "bar"; got != want {
|
|
t.Errorf("wrong Variables[1].Name %#v; want %#v", got, want)
|
|
}
|
|
if got, want := v.Default, interface{}(nil); got != want {
|
|
t.Errorf("wrong Variables[1].Default %#v; want %#v", got, want)
|
|
}
|
|
if got, want := v.Description, ""; got != want {
|
|
t.Errorf("wrong Variables[1].Description %#v; want %#v", got, want)
|
|
}
|
|
if got, want := v.DeclaredType, "string"; got != want {
|
|
t.Errorf("wrong Variables[1].DeclaredType %#v; want %#v", got, want)
|
|
}
|
|
}
|
|
{
|
|
v := cfg.Variables[2]
|
|
|
|
if got, want := v.Name, "baz"; got != want {
|
|
t.Errorf("wrong Variables[2].Name %#v; want %#v", got, want)
|
|
}
|
|
if got, want := v.Default, map[string]interface{}{"key": "value"}; !reflect.DeepEqual(got, want) {
|
|
t.Errorf("wrong Variables[2].Default %#v; want %#v", got, want)
|
|
}
|
|
if got, want := v.Description, ""; got != want {
|
|
t.Errorf("wrong Variables[2].Description %#v; want %#v", got, want)
|
|
}
|
|
if got, want := v.DeclaredType, "map"; got != want {
|
|
t.Errorf("wrong Variables[2].DeclaredType %#v; want %#v", got, want)
|
|
}
|
|
}
|
|
}
|
|
|
|
// "output" blocks
|
|
if got, want := len(cfg.Outputs), 2; got != want {
|
|
t.Errorf("Outputs slice has wrong length %#v; want %#v", got, want)
|
|
} else {
|
|
{
|
|
o := cfg.Outputs[0]
|
|
|
|
if got, want := o.Name, "web_ip"; got != want {
|
|
t.Errorf("wrong Outputs[0].Name %#v; want %#v", got, want)
|
|
}
|
|
if got, want := o.DependsOn, []string(nil); !reflect.DeepEqual(got, want) {
|
|
t.Errorf("wrong Outputs[0].DependsOn %#v; want %#v", got, want)
|
|
}
|
|
if got, want := o.Description, ""; got != want {
|
|
t.Errorf("wrong Outputs[0].Description %#v; want %#v", got, want)
|
|
}
|
|
if got, want := o.Sensitive, true; got != want {
|
|
t.Errorf("wrong Outputs[0].Sensitive %#v; want %#v", got, want)
|
|
}
|
|
|
|
wantConfig := map[string]string{
|
|
"value": "312.213.645.123",
|
|
}
|
|
var gotConfig map[string]string
|
|
ctx := &hcl2.EvalContext{
|
|
Variables: map[string]cty.Value{
|
|
"aws_instance": cty.ObjectVal(map[string]cty.Value{
|
|
"web": cty.ObjectVal(map[string]cty.Value{
|
|
"private_ip": cty.StringVal("312.213.645.123"),
|
|
}),
|
|
}),
|
|
},
|
|
}
|
|
gohcl2.DecodeBody(o.RawConfig.Body, ctx, &gotConfig)
|
|
if !reflect.DeepEqual(gotConfig, wantConfig) {
|
|
t.Errorf("wrong Outputs[0].RawConfig.Body %#v; want %#v", gotConfig, wantConfig)
|
|
}
|
|
}
|
|
{
|
|
o := cfg.Outputs[1]
|
|
|
|
if got, want := o.Name, "web_id"; got != want {
|
|
t.Errorf("wrong Outputs[1].Name %#v; want %#v", got, want)
|
|
}
|
|
if got, want := o.DependsOn, []string{"aws_instance.db"}; !reflect.DeepEqual(got, want) {
|
|
t.Errorf("wrong Outputs[1].DependsOn %#v; want %#v", got, want)
|
|
}
|
|
if got, want := o.Description, "The ID"; got != want {
|
|
t.Errorf("wrong Outputs[1].Description %#v; want %#v", got, want)
|
|
}
|
|
if got, want := o.Sensitive, false; got != want {
|
|
t.Errorf("wrong Outputs[1].Sensitive %#v; want %#v", got, want)
|
|
}
|
|
}
|
|
}
|
|
|
|
// "provider" blocks
|
|
if got, want := len(cfg.ProviderConfigs), 2; got != want {
|
|
t.Errorf("ProviderConfigs slice has wrong length %#v; want %#v", got, want)
|
|
} else {
|
|
{
|
|
p := cfg.ProviderConfigs[0]
|
|
|
|
if got, want := p.Name, "aws"; got != want {
|
|
t.Errorf("wrong ProviderConfigs[0].Name %#v; want %#v", got, want)
|
|
}
|
|
if got, want := p.Alias, ""; got != want {
|
|
t.Errorf("wrong ProviderConfigs[0].Alias %#v; want %#v", got, want)
|
|
}
|
|
if got, want := p.Version, "1.0.0"; got != want {
|
|
t.Errorf("wrong ProviderConfigs[0].Version %#v; want %#v", got, want)
|
|
}
|
|
|
|
wantConfig := map[string]string{
|
|
"access_key": "foo",
|
|
"secret_key": "bar",
|
|
}
|
|
var gotConfig map[string]string
|
|
gohcl2.DecodeBody(p.RawConfig.Body, nil, &gotConfig)
|
|
if !reflect.DeepEqual(gotConfig, wantConfig) {
|
|
t.Errorf("wrong ProviderConfigs[0].RawConfig.Body %#v; want %#v", gotConfig, wantConfig)
|
|
}
|
|
|
|
}
|
|
{
|
|
p := cfg.ProviderConfigs[1]
|
|
|
|
if got, want := p.Name, "do"; got != want {
|
|
t.Errorf("wrong ProviderConfigs[1].Name %#v; want %#v", got, want)
|
|
}
|
|
if got, want := p.Alias, "fum"; got != want {
|
|
t.Errorf("wrong ProviderConfigs[1].Alias %#v; want %#v", got, want)
|
|
}
|
|
if got, want := p.Version, ""; got != want {
|
|
t.Errorf("wrong ProviderConfigs[1].Version %#v; want %#v", got, want)
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
// "locals" definitions
|
|
if got, want := len(cfg.Locals), 5; got != want {
|
|
t.Errorf("Locals slice has wrong length %#v; want %#v", got, want)
|
|
} else {
|
|
{
|
|
l := cfg.Locals[0]
|
|
|
|
if got, want := l.Name, "security_group_ids"; got != want {
|
|
t.Errorf("wrong Locals[0].Name %#v; want %#v", got, want)
|
|
}
|
|
|
|
wantConfig := map[string][]string{
|
|
"value": []string{"sg-abc123"},
|
|
}
|
|
var gotConfig map[string][]string
|
|
ctx := &hcl2.EvalContext{
|
|
Variables: map[string]cty.Value{
|
|
"aws_security_group": cty.ObjectVal(map[string]cty.Value{
|
|
"firewall": cty.ObjectVal(map[string]cty.Value{
|
|
"id": cty.StringVal("sg-abc123"),
|
|
}),
|
|
}),
|
|
},
|
|
}
|
|
gohcl2.DecodeBody(l.RawConfig.Body, ctx, &gotConfig)
|
|
if !reflect.DeepEqual(gotConfig, wantConfig) {
|
|
t.Errorf("wrong Locals[0].RawConfig.Body %#v; want %#v", gotConfig, wantConfig)
|
|
}
|
|
}
|
|
{
|
|
l := cfg.Locals[1]
|
|
|
|
if got, want := l.Name, "web_ip"; got != want {
|
|
t.Errorf("wrong Locals[1].Name %#v; want %#v", got, want)
|
|
}
|
|
}
|
|
{
|
|
l := cfg.Locals[2]
|
|
|
|
if got, want := l.Name, "literal"; got != want {
|
|
t.Errorf("wrong Locals[2].Name %#v; want %#v", got, want)
|
|
}
|
|
}
|
|
{
|
|
l := cfg.Locals[3]
|
|
|
|
if got, want := l.Name, "literal_list"; got != want {
|
|
t.Errorf("wrong Locals[3].Name %#v; want %#v", got, want)
|
|
}
|
|
}
|
|
{
|
|
l := cfg.Locals[4]
|
|
|
|
if got, want := l.Name, "literal_map"; got != want {
|
|
t.Errorf("wrong Locals[4].Name %#v; want %#v", got, want)
|
|
}
|
|
}
|
|
}
|
|
}
|