configs: finish deprecation of the config package by removing the remaining used functions into configs (#25996)

This commit is contained in:
Kristin Laemmert 2020-08-26 14:39:18 -04:00 committed by GitHub
parent 18bdd1a4f0
commit 23a8bdd522
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
180 changed files with 21 additions and 9143 deletions

View File

@ -1,92 +0,0 @@
package config
// Append appends one configuration to another.
//
// Append assumes that both configurations will not have
// conflicting variables, resources, etc. If they do, the
// problems will be caught in the validation phase.
//
// It is possible that c1, c2 on their own are not valid. For
// example, a resource in c2 may reference a variable in c1. But
// together, they would be valid.
func Append(c1, c2 *Config) (*Config, error) {
c := new(Config)
// Append unknown keys, but keep them unique since it is a set
unknowns := make(map[string]struct{})
for _, k := range c1.unknownKeys {
_, present := unknowns[k]
if !present {
unknowns[k] = struct{}{}
c.unknownKeys = append(c.unknownKeys, k)
}
}
for _, k := range c2.unknownKeys {
_, present := unknowns[k]
if !present {
unknowns[k] = struct{}{}
c.unknownKeys = append(c.unknownKeys, k)
}
}
c.Atlas = c1.Atlas
if c2.Atlas != nil {
c.Atlas = c2.Atlas
}
// merge Terraform blocks
if c1.Terraform != nil {
c.Terraform = c1.Terraform
if c2.Terraform != nil {
c.Terraform.Merge(c2.Terraform)
}
} else {
c.Terraform = c2.Terraform
}
if len(c1.Modules) > 0 || len(c2.Modules) > 0 {
c.Modules = make(
[]*Module, 0, len(c1.Modules)+len(c2.Modules))
c.Modules = append(c.Modules, c1.Modules...)
c.Modules = append(c.Modules, c2.Modules...)
}
if len(c1.Outputs) > 0 || len(c2.Outputs) > 0 {
c.Outputs = make(
[]*Output, 0, len(c1.Outputs)+len(c2.Outputs))
c.Outputs = append(c.Outputs, c1.Outputs...)
c.Outputs = append(c.Outputs, c2.Outputs...)
}
if len(c1.ProviderConfigs) > 0 || len(c2.ProviderConfigs) > 0 {
c.ProviderConfigs = make(
[]*ProviderConfig,
0, len(c1.ProviderConfigs)+len(c2.ProviderConfigs))
c.ProviderConfigs = append(c.ProviderConfigs, c1.ProviderConfigs...)
c.ProviderConfigs = append(c.ProviderConfigs, c2.ProviderConfigs...)
}
if len(c1.Resources) > 0 || len(c2.Resources) > 0 {
c.Resources = make(
[]*Resource,
0, len(c1.Resources)+len(c2.Resources))
c.Resources = append(c.Resources, c1.Resources...)
c.Resources = append(c.Resources, c2.Resources...)
}
if len(c1.Variables) > 0 || len(c2.Variables) > 0 {
c.Variables = make(
[]*Variable, 0, len(c1.Variables)+len(c2.Variables))
c.Variables = append(c.Variables, c1.Variables...)
c.Variables = append(c.Variables, c2.Variables...)
}
if len(c1.Locals) > 0 || len(c2.Locals) > 0 {
c.Locals = make([]*Local, 0, len(c1.Locals)+len(c2.Locals))
c.Locals = append(c.Locals, c1.Locals...)
c.Locals = append(c.Locals, c2.Locals...)
}
return c, nil
}

View File

@ -1,173 +0,0 @@
package config
import (
"fmt"
"reflect"
"testing"
"github.com/davecgh/go-spew/spew"
)
func TestAppend(t *testing.T) {
cases := []struct {
c1, c2, result *Config
err bool
}{
{
&Config{
Atlas: &AtlasConfig{
Name: "foo",
},
Modules: []*Module{
&Module{Name: "foo"},
},
Outputs: []*Output{
&Output{Name: "foo"},
},
ProviderConfigs: []*ProviderConfig{
&ProviderConfig{Name: "foo"},
},
Resources: []*Resource{
&Resource{Name: "foo"},
},
Variables: []*Variable{
&Variable{Name: "foo"},
},
Locals: []*Local{
&Local{Name: "foo"},
},
unknownKeys: []string{"foo"},
},
&Config{
Atlas: &AtlasConfig{
Name: "bar",
},
Modules: []*Module{
&Module{Name: "bar"},
},
Outputs: []*Output{
&Output{Name: "bar"},
},
ProviderConfigs: []*ProviderConfig{
&ProviderConfig{Name: "bar"},
},
Resources: []*Resource{
&Resource{Name: "bar"},
},
Variables: []*Variable{
&Variable{Name: "bar"},
},
Locals: []*Local{
&Local{Name: "bar"},
},
unknownKeys: []string{"bar"},
},
&Config{
Atlas: &AtlasConfig{
Name: "bar",
},
Modules: []*Module{
&Module{Name: "foo"},
&Module{Name: "bar"},
},
Outputs: []*Output{
&Output{Name: "foo"},
&Output{Name: "bar"},
},
ProviderConfigs: []*ProviderConfig{
&ProviderConfig{Name: "foo"},
&ProviderConfig{Name: "bar"},
},
Resources: []*Resource{
&Resource{Name: "foo"},
&Resource{Name: "bar"},
},
Variables: []*Variable{
&Variable{Name: "foo"},
&Variable{Name: "bar"},
},
Locals: []*Local{
&Local{Name: "foo"},
&Local{Name: "bar"},
},
unknownKeys: []string{"foo", "bar"},
},
false,
},
// Terraform block
{
&Config{
Terraform: &Terraform{
RequiredVersion: "A",
},
},
&Config{},
&Config{
Terraform: &Terraform{
RequiredVersion: "A",
},
},
false,
},
{
&Config{},
&Config{
Terraform: &Terraform{
RequiredVersion: "A",
},
},
&Config{
Terraform: &Terraform{
RequiredVersion: "A",
},
},
false,
},
// appending configs merges terraform blocks
{
&Config{
Terraform: &Terraform{
RequiredVersion: "A",
},
},
&Config{
Terraform: &Terraform{
Backend: &Backend{
Type: "test",
},
},
},
&Config{
Terraform: &Terraform{
RequiredVersion: "A",
Backend: &Backend{
Type: "test",
},
},
},
false,
},
}
for i, tc := range cases {
t.Run(fmt.Sprintf("%02d", i), func(t *testing.T) {
actual, err := Append(tc.c1, tc.c2)
if err != nil != tc.err {
t.Errorf("unexpected error: %s", err)
}
if !reflect.DeepEqual(actual, tc.result) {
t.Errorf("wrong result\ngot: %swant: %s", spew.Sdump(actual), spew.Sdump(tc.result))
}
})
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,378 +0,0 @@
package config
import (
"bytes"
"fmt"
"sort"
"strings"
)
// TestString is a Stringer-like function that outputs a string that can
// be used to easily compare multiple Config structures in unit tests.
//
// This function has no practical use outside of unit tests and debugging.
func (c *Config) TestString() string {
if c == nil {
return "<nil config>"
}
var buf bytes.Buffer
if len(c.Modules) > 0 {
buf.WriteString("Modules:\n\n")
buf.WriteString(modulesStr(c.Modules))
buf.WriteString("\n\n")
}
if len(c.Variables) > 0 {
buf.WriteString("Variables:\n\n")
buf.WriteString(variablesStr(c.Variables))
buf.WriteString("\n\n")
}
if len(c.ProviderConfigs) > 0 {
buf.WriteString("Provider Configs:\n\n")
buf.WriteString(providerConfigsStr(c.ProviderConfigs))
buf.WriteString("\n\n")
}
if len(c.Resources) > 0 {
buf.WriteString("Resources:\n\n")
buf.WriteString(resourcesStr(c.Resources))
buf.WriteString("\n\n")
}
if len(c.Outputs) > 0 {
buf.WriteString("Outputs:\n\n")
buf.WriteString(outputsStr(c.Outputs))
buf.WriteString("\n")
}
return strings.TrimSpace(buf.String())
}
func terraformStr(t *Terraform) string {
result := ""
if b := t.Backend; b != nil {
result += fmt.Sprintf("backend (%s)\n", b.Type)
keys := make([]string, 0, len(b.RawConfig.Raw))
for k, _ := range b.RawConfig.Raw {
keys = append(keys, k)
}
sort.Strings(keys)
for _, k := range keys {
result += fmt.Sprintf(" %s\n", k)
}
}
return strings.TrimSpace(result)
}
func modulesStr(ms []*Module) string {
result := ""
order := make([]int, 0, len(ms))
ks := make([]string, 0, len(ms))
mapping := make(map[string]int)
for i, m := range ms {
k := m.Id()
ks = append(ks, k)
mapping[k] = i
}
sort.Strings(ks)
for _, k := range ks {
order = append(order, mapping[k])
}
for _, i := range order {
m := ms[i]
result += fmt.Sprintf("%s\n", m.Id())
ks := make([]string, 0, len(m.RawConfig.Raw))
for k, _ := range m.RawConfig.Raw {
ks = append(ks, k)
}
sort.Strings(ks)
result += fmt.Sprintf(" source = %s\n", m.Source)
for _, k := range ks {
result += fmt.Sprintf(" %s\n", k)
}
}
return strings.TrimSpace(result)
}
func outputsStr(os []*Output) string {
ns := make([]string, 0, len(os))
m := make(map[string]*Output)
for _, o := range os {
ns = append(ns, o.Name)
m[o.Name] = o
}
sort.Strings(ns)
result := ""
for _, n := range ns {
o := m[n]
result += fmt.Sprintf("%s\n", n)
if len(o.DependsOn) > 0 {
result += fmt.Sprintf(" dependsOn\n")
for _, d := range o.DependsOn {
result += fmt.Sprintf(" %s\n", d)
}
}
if len(o.RawConfig.Variables) > 0 {
result += fmt.Sprintf(" vars\n")
for _, rawV := range o.RawConfig.Variables {
kind := "unknown"
str := rawV.FullKey()
switch rawV.(type) {
case *ResourceVariable:
kind = "resource"
case *UserVariable:
kind = "user"
}
result += fmt.Sprintf(" %s: %s\n", kind, str)
}
}
if o.Description != "" {
result += fmt.Sprintf(" description\n %s\n", o.Description)
}
}
return strings.TrimSpace(result)
}
func localsStr(ls []*Local) string {
ns := make([]string, 0, len(ls))
m := make(map[string]*Local)
for _, l := range ls {
ns = append(ns, l.Name)
m[l.Name] = l
}
sort.Strings(ns)
result := ""
for _, n := range ns {
l := m[n]
result += fmt.Sprintf("%s\n", n)
if len(l.RawConfig.Variables) > 0 {
result += fmt.Sprintf(" vars\n")
for _, rawV := range l.RawConfig.Variables {
kind := "unknown"
str := rawV.FullKey()
switch rawV.(type) {
case *ResourceVariable:
kind = "resource"
case *UserVariable:
kind = "user"
}
result += fmt.Sprintf(" %s: %s\n", kind, str)
}
}
}
return strings.TrimSpace(result)
}
// This helper turns a provider configs field into a deterministic
// string value for comparison in tests.
func providerConfigsStr(pcs []*ProviderConfig) string {
result := ""
ns := make([]string, 0, len(pcs))
m := make(map[string]*ProviderConfig)
for _, n := range pcs {
ns = append(ns, n.Name)
m[n.Name] = n
}
sort.Strings(ns)
for _, n := range ns {
pc := m[n]
result += fmt.Sprintf("%s\n", n)
keys := make([]string, 0, len(pc.RawConfig.Raw))
for k, _ := range pc.RawConfig.Raw {
keys = append(keys, k)
}
sort.Strings(keys)
for _, k := range keys {
result += fmt.Sprintf(" %s\n", k)
}
if len(pc.RawConfig.Variables) > 0 {
result += fmt.Sprintf(" vars\n")
for _, rawV := range pc.RawConfig.Variables {
kind := "unknown"
str := rawV.FullKey()
switch rawV.(type) {
case *ResourceVariable:
kind = "resource"
case *UserVariable:
kind = "user"
}
result += fmt.Sprintf(" %s: %s\n", kind, str)
}
}
}
return strings.TrimSpace(result)
}
// This helper turns a resources field into a deterministic
// string value for comparison in tests.
func resourcesStr(rs []*Resource) string {
result := ""
order := make([]int, 0, len(rs))
ks := make([]string, 0, len(rs))
mapping := make(map[string]int)
for i, r := range rs {
k := r.Id()
ks = append(ks, k)
mapping[k] = i
}
sort.Strings(ks)
for _, k := range ks {
order = append(order, mapping[k])
}
for _, i := range order {
r := rs[i]
result += fmt.Sprintf(
"%s (x%s)\n",
r.Id(),
r.RawCount.Value())
ks := make([]string, 0, len(r.RawConfig.Raw))
for k, _ := range r.RawConfig.Raw {
ks = append(ks, k)
}
sort.Strings(ks)
for _, k := range ks {
result += fmt.Sprintf(" %s\n", k)
}
if len(r.Provisioners) > 0 {
result += fmt.Sprintf(" provisioners\n")
for _, p := range r.Provisioners {
when := ""
if p.When != ProvisionerWhenCreate {
when = fmt.Sprintf(" (%s)", p.When.String())
}
result += fmt.Sprintf(" %s%s\n", p.Type, when)
if p.OnFailure != ProvisionerOnFailureFail {
result += fmt.Sprintf(" on_failure = %s\n", p.OnFailure.String())
}
ks := make([]string, 0, len(p.RawConfig.Raw))
for k, _ := range p.RawConfig.Raw {
ks = append(ks, k)
}
sort.Strings(ks)
for _, k := range ks {
result += fmt.Sprintf(" %s\n", k)
}
}
}
if len(r.DependsOn) > 0 {
result += fmt.Sprintf(" dependsOn\n")
for _, d := range r.DependsOn {
result += fmt.Sprintf(" %s\n", d)
}
}
if len(r.RawConfig.Variables) > 0 {
result += fmt.Sprintf(" vars\n")
ks := make([]string, 0, len(r.RawConfig.Variables))
for k, _ := range r.RawConfig.Variables {
ks = append(ks, k)
}
sort.Strings(ks)
for _, k := range ks {
rawV := r.RawConfig.Variables[k]
kind := "unknown"
str := rawV.FullKey()
switch rawV.(type) {
case *ResourceVariable:
kind = "resource"
case *UserVariable:
kind = "user"
}
result += fmt.Sprintf(" %s: %s\n", kind, str)
}
}
}
return strings.TrimSpace(result)
}
// This helper turns a variables field into a deterministic
// string value for comparison in tests.
func variablesStr(vs []*Variable) string {
result := ""
ks := make([]string, 0, len(vs))
m := make(map[string]*Variable)
for _, v := range vs {
ks = append(ks, v.Name)
m[v.Name] = v
}
sort.Strings(ks)
for _, k := range ks {
v := m[k]
required := ""
if v.Required() {
required = " (required)"
}
declaredType := ""
if v.DeclaredType != "" {
declaredType = fmt.Sprintf(" (%s)", v.DeclaredType)
}
if v.Default == nil || v.Default == "" {
v.Default = "<>"
}
if v.Description == "" {
v.Description = "<>"
}
result += fmt.Sprintf(
"%s%s%s\n %v\n %s\n",
k,
required,
declaredType,
v.Default,
v.Description)
}
return strings.TrimSpace(result)
}

View File

@ -1,117 +0,0 @@
package config
import (
"fmt"
"strings"
"github.com/hashicorp/go-version"
"github.com/mitchellh/hashstructure"
)
// Terraform is the Terraform meta-configuration that can be present
// in configuration files for configuring Terraform itself.
type Terraform struct {
RequiredVersion string `hcl:"required_version"` // Required Terraform version (constraint)
Backend *Backend // See Backend struct docs
}
// Validate performs the validation for just the Terraform configuration.
func (t *Terraform) Validate() []error {
var errs []error
if raw := t.RequiredVersion; raw != "" {
// Check that the value has no interpolations
rc, err := NewRawConfig(map[string]interface{}{
"root": raw,
})
if err != nil {
errs = append(errs, fmt.Errorf(
"terraform.required_version: %s", err))
} else if len(rc.Interpolations) > 0 {
errs = append(errs, fmt.Errorf(
"terraform.required_version: cannot contain interpolations"))
} else {
// Check it is valid
_, err := version.NewConstraint(raw)
if err != nil {
errs = append(errs, fmt.Errorf(
"terraform.required_version: invalid syntax: %s", err))
}
}
}
if t.Backend != nil {
errs = append(errs, t.Backend.Validate()...)
}
return errs
}
// Merge t with t2.
// Any conflicting fields are overwritten by t2.
func (t *Terraform) Merge(t2 *Terraform) {
if t2.RequiredVersion != "" {
t.RequiredVersion = t2.RequiredVersion
}
if t2.Backend != nil {
t.Backend = t2.Backend
}
}
// Backend is the configuration for the "backend" to use with Terraform.
// A backend is responsible for all major behavior of Terraform's core.
// The abstraction layer above the core (the "backend") allows for behavior
// such as remote operation.
type Backend struct {
Type string
RawConfig *RawConfig
// Hash is a unique hash code representing the original configuration
// of the backend. This won't be recomputed unless Rehash is called.
Hash uint64
}
// Rehash returns a unique content hash for this backend's configuration
// as a uint64 value.
func (b *Backend) Rehash() uint64 {
// If we have no backend, the value is zero
if b == nil {
return 0
}
// Use hashstructure to hash only our type with the config.
code, err := hashstructure.Hash(map[string]interface{}{
"type": b.Type,
"config": b.RawConfig.Raw,
}, nil)
// This should never happen since we have just some basic primitives
// so panic if there is an error.
if err != nil {
panic(err)
}
return code
}
func (b *Backend) Validate() []error {
if len(b.RawConfig.Interpolations) > 0 {
return []error{fmt.Errorf(strings.TrimSpace(errBackendInterpolations))}
}
return nil
}
const errBackendInterpolations = `
terraform.backend: configuration cannot contain interpolations
The backend configuration is loaded by Terraform extremely early, before
the core of Terraform can be initialized. This is necessary because the backend
dictates the behavior of that core. The core is what handles interpolation
processing. Because of this, interpolations cannot be used in backend
configuration.
If you'd like to parameterize backend configuration, we recommend using
partial configuration with the "-backend-config" flag to "terraform init".
`

View File

@ -1,55 +0,0 @@
package config
import (
"fmt"
"testing"
)
func TestBackendHash(t *testing.T) {
// WARNING: The codes below should _never_ change. If they change, it
// means that a future TF version may falsely recognize unchanged backend
// configuration as changed. Ultimately this should have no adverse
// affect but it is annoying for users and should be avoided if possible.
cases := []struct {
Name string
Fixture string
Code uint64
}{
{
"no backend config",
"backend-hash-empty",
0,
},
{
"backend config with only type",
"backend-hash-type-only",
17852588448730441876,
},
{
"backend config with type and config",
"backend-hash-basic",
10288498853650209002,
},
}
for i, tc := range cases {
t.Run(fmt.Sprintf("%d-%s", i, tc.Name), func(t *testing.T) {
c := testConfig(t, tc.Fixture)
err := c.Validate()
if err != nil {
t.Fatalf("err: %s", err)
}
var actual uint64
if c.Terraform != nil && c.Terraform.Backend != nil {
actual = c.Terraform.Backend.Hash
}
if actual != tc.Code {
t.Fatalf("bad: %d != %d", actual, tc.Code)
}
})
}
}

View File

@ -1,783 +0,0 @@
package config
import (
"flag"
"fmt"
"io/ioutil"
"log"
"os"
"path/filepath"
"reflect"
"strings"
"testing"
"github.com/hashicorp/hil/ast"
"github.com/hashicorp/terraform/helper/logging"
)
// This is the directory where our test fixtures are.
const fixtureDir = "./testdata"
func TestMain(m *testing.M) {
flag.Parse()
if testing.Verbose() {
// if we're verbose, use the logging requested by TF_LOG
logging.SetOutput()
} else {
// otherwise silence all logs
log.SetOutput(ioutil.Discard)
}
os.Exit(m.Run())
}
func TestConfigCopy(t *testing.T) {
c := testConfig(t, "copy-basic")
rOrig := c.Resources[0]
rCopy := rOrig.Copy()
if rCopy.Name != rOrig.Name {
t.Fatalf("Expected names to equal: %q <=> %q", rCopy.Name, rOrig.Name)
}
if rCopy.Type != rOrig.Type {
t.Fatalf("Expected types to equal: %q <=> %q", rCopy.Type, rOrig.Type)
}
origCount := rOrig.RawCount.Config()["count"]
rCopy.RawCount.Config()["count"] = "5"
if rOrig.RawCount.Config()["count"] != origCount {
t.Fatalf("Expected RawCount to be copied, but it behaves like a ref!")
}
rCopy.RawConfig.Config()["newfield"] = "hello"
if rOrig.RawConfig.Config()["newfield"] == "hello" {
t.Fatalf("Expected RawConfig to be copied, but it behaves like a ref!")
}
rCopy.Provisioners = append(rCopy.Provisioners, &Provisioner{})
if len(rOrig.Provisioners) == len(rCopy.Provisioners) {
t.Fatalf("Expected Provisioners to be copied, but it behaves like a ref!")
}
if rCopy.Provider != rOrig.Provider {
t.Fatalf("Expected providers to equal: %q <=> %q",
rCopy.Provider, rOrig.Provider)
}
rCopy.DependsOn[0] = "gotchya"
if rOrig.DependsOn[0] == rCopy.DependsOn[0] {
t.Fatalf("Expected DependsOn to be copied, but it behaves like a ref!")
}
rCopy.Lifecycle.IgnoreChanges[0] = "gotchya"
if rOrig.Lifecycle.IgnoreChanges[0] == rCopy.Lifecycle.IgnoreChanges[0] {
t.Fatalf("Expected Lifecycle to be copied, but it behaves like a ref!")
}
}
func TestConfigCount(t *testing.T) {
c := testConfig(t, "count-int")
actual, err := c.Resources[0].Count()
if err != nil {
t.Fatalf("err: %s", err)
}
if actual != 5 {
t.Fatalf("bad: %#v", actual)
}
}
func TestConfigCount_string(t *testing.T) {
c := testConfig(t, "count-string")
actual, err := c.Resources[0].Count()
if err != nil {
t.Fatalf("err: %s", err)
}
if actual != 5 {
t.Fatalf("bad: %#v", actual)
}
}
// Terraform GH-11800
func TestConfigCount_list(t *testing.T) {
c := testConfig(t, "count-list")
// The key is to interpolate so it doesn't fail parsing
c.Resources[0].RawCount.Interpolate(map[string]ast.Variable{
"var.list": ast.Variable{
Value: []ast.Variable{},
Type: ast.TypeList,
},
})
_, err := c.Resources[0].Count()
if err == nil {
t.Fatal("should error")
}
}
func TestConfigCount_var(t *testing.T) {
c := testConfig(t, "count-var")
_, err := c.Resources[0].Count()
if err == nil {
t.Fatalf("should error")
}
}
func TestConfig_emptyCollections(t *testing.T) {
c := testConfig(t, "empty-collections")
if len(c.Variables) != 3 {
t.Fatalf("bad: expected 3 variables, got %d", len(c.Variables))
}
for _, variable := range c.Variables {
switch variable.Name {
case "empty_string":
if variable.Default != "" {
t.Fatalf("bad: wrong default %q for variable empty_string", variable.Default)
}
case "empty_map":
if !reflect.DeepEqual(variable.Default, map[string]interface{}{}) {
t.Fatalf("bad: wrong default %#v for variable empty_map", variable.Default)
}
case "empty_list":
if !reflect.DeepEqual(variable.Default, []interface{}{}) {
t.Fatalf("bad: wrong default %#v for variable empty_list", variable.Default)
}
default:
t.Fatalf("Unexpected variable: %s", variable.Name)
}
}
}
// This table test is the preferred way to test validation of configuration.
// There are dozens of functions below which do not follow this that are
// there mostly historically. They should be converted at some point.
func TestConfigValidate_table(t *testing.T) {
cases := []struct {
Name string
Fixture string
Err bool
ErrString string
}{
{
"basic good",
"validate-good",
false,
"",
},
{
"depends on module",
"validate-depends-on-module",
false,
"",
},
{
"depends on non-existent module",
"validate-depends-on-bad-module",
true,
"non-existent module 'foo'",
},
{
"data source with provisioners",
"validate-data-provisioner",
true,
"data sources cannot have",
},
{
"basic provisioners",
"validate-basic-provisioners",
false,
"",
},
{
"backend config with interpolations",
"validate-backend-interpolate",
true,
"cannot contain interp",
},
{
"nested types in variable default",
"validate-var-nested",
false,
"",
},
{
"provider with valid version constraint",
"provider-version",
false,
"",
},
{
"provider with invalid version constraint",
"provider-version-invalid",
true,
"not a valid version constraint",
},
{
"invalid provider name in module block",
"validate-missing-provider",
true,
"cannot pass non-existent provider",
},
}
for i, tc := range cases {
t.Run(fmt.Sprintf("%d-%s", i, tc.Name), func(t *testing.T) {
c := testConfig(t, tc.Fixture)
diags := c.Validate()
if diags.HasErrors() != tc.Err {
t.Fatalf("err: %s", diags.Err().Error())
}
if diags.HasErrors() {
gotErr := diags.Err().Error()
if tc.ErrString != "" && !strings.Contains(gotErr, tc.ErrString) {
t.Fatalf("expected err to contain: %s\n\ngot: %s", tc.ErrString, gotErr)
}
return
}
})
}
}
func TestConfigValidate_tfVersion(t *testing.T) {
c := testConfig(t, "validate-tf-version")
if err := c.Validate(); err != nil {
t.Fatalf("err: %s", err)
}
}
func TestConfigValidate_tfVersionBad(t *testing.T) {
c := testConfig(t, "validate-bad-tf-version")
if err := c.Validate(); err == nil {
t.Fatal("should not be valid")
}
}
func TestConfigValidate_tfVersionInterpolations(t *testing.T) {
c := testConfig(t, "validate-tf-version-interp")
if err := c.Validate(); err == nil {
t.Fatal("should not be valid")
}
}
func TestConfigValidate_badDependsOn(t *testing.T) {
c := testConfig(t, "validate-bad-depends-on")
if err := c.Validate(); err == nil {
t.Fatal("should not be valid")
}
}
func TestConfigValidate_countInt(t *testing.T) {
c := testConfig(t, "validate-count-int")
if err := c.Validate(); err != nil {
t.Fatalf("err: %s", err)
}
}
func TestConfigValidate_countBadContext(t *testing.T) {
c := testConfig(t, "validate-count-bad-context")
diags := c.Validate()
expected := []string{
"output \"no_count_in_output\": count variables are only valid within resources",
"module \"no_count_in_module\": count variables are only valid within resources",
}
for _, exp := range expected {
errStr := diags.Err().Error()
if !strings.Contains(errStr, exp) {
t.Errorf("expected: %q,\nto contain: %q", errStr, exp)
}
}
}
func TestConfigValidate_countCountVar(t *testing.T) {
c := testConfig(t, "validate-count-count-var")
if err := c.Validate(); err == nil {
t.Fatal("should not be valid")
}
}
func TestConfigValidate_countNotInt(t *testing.T) {
c := testConfig(t, "validate-count-not-int")
if err := c.Validate(); err == nil {
t.Fatal("should not be valid")
}
}
func TestConfigValidate_countUserVar(t *testing.T) {
c := testConfig(t, "validate-count-user-var")
if err := c.Validate(); err != nil {
t.Fatalf("err: %s", err)
}
}
func TestConfigValidate_countLocalValue(t *testing.T) {
c := testConfig(t, "validate-local-value-count")
if err := c.Validate(); err != nil {
t.Fatalf("err: %s", err)
}
}
func TestConfigValidate_countVar(t *testing.T) {
c := testConfig(t, "validate-count-var")
if err := c.Validate(); err != nil {
t.Fatalf("err: %s", err)
}
}
func TestConfigValidate_countVarInvalid(t *testing.T) {
c := testConfig(t, "validate-count-var-invalid")
if err := c.Validate(); err == nil {
t.Fatal("should not be valid")
}
}
func TestConfigValidate_countVarUnknown(t *testing.T) {
c := testConfig(t, "validate-count-var-unknown")
if err := c.Validate(); err == nil {
t.Fatal("should not be valid")
}
}
func TestConfigValidate_dependsOnVar(t *testing.T) {
c := testConfig(t, "validate-depends-on-var")
if err := c.Validate(); err == nil {
t.Fatal("should not be valid")
}
}
func TestConfigValidate_dupModule(t *testing.T) {
c := testConfig(t, "validate-dup-module")
if err := c.Validate(); err == nil {
t.Fatal("should not be valid")
}
}
func TestConfigValidate_dupResource(t *testing.T) {
c := testConfig(t, "validate-dup-resource")
if err := c.Validate(); err == nil {
t.Fatal("should not be valid")
}
}
func TestConfigValidate_ignoreChanges(t *testing.T) {
c := testConfig(t, "validate-ignore-changes")
if err := c.Validate(); err != nil {
t.Fatalf("err: %s", err)
}
}
func TestConfigValidate_ignoreChangesBad(t *testing.T) {
c := testConfig(t, "validate-ignore-changes-bad")
if err := c.Validate(); err == nil {
t.Fatal("should not be valid")
}
}
func TestConfigValidate_ignoreChangesInterpolate(t *testing.T) {
c := testConfig(t, "validate-ignore-changes-interpolate")
if err := c.Validate(); err == nil {
t.Fatal("should not be valid")
}
}
func TestConfigValidate_moduleNameBad(t *testing.T) {
c := testConfig(t, "validate-module-name-bad")
if err := c.Validate(); err == nil {
t.Fatal("should not be valid")
}
}
func TestConfigValidate_moduleSourceVar(t *testing.T) {
c := testConfig(t, "validate-module-source-var")
if err := c.Validate(); err == nil {
t.Fatal("should not be valid")
}
}
func TestConfigValidate_moduleVarInt(t *testing.T) {
c := testConfig(t, "validate-module-var-int")
if err := c.Validate(); err != nil {
t.Fatalf("should be valid: %s", err)
}
}
func TestConfigValidate_moduleVarMap(t *testing.T) {
c := testConfig(t, "validate-module-var-map")
if err := c.Validate(); err != nil {
t.Fatalf("should be valid: %s", err)
}
}
func TestConfigValidate_moduleVarList(t *testing.T) {
c := testConfig(t, "validate-module-var-list")
if err := c.Validate(); err != nil {
t.Fatalf("should be valid: %s", err)
}
}
func TestConfigValidate_moduleVarSelf(t *testing.T) {
c := testConfig(t, "validate-module-var-self")
if err := c.Validate(); err == nil {
t.Fatal("should be invalid")
}
}
func TestConfigValidate_nil(t *testing.T) {
var c Config
if err := c.Validate(); err != nil {
t.Fatalf("err: %s", err)
}
}
func TestConfigValidate_outputBadField(t *testing.T) {
c := testConfig(t, "validate-output-bad-field")
if err := c.Validate(); err == nil {
t.Fatal("should not be valid")
}
}
func TestConfigValidate_outputDescription(t *testing.T) {
c := testConfig(t, "validate-output-description")
if err := c.Validate(); err != nil {
t.Fatalf("err: %s", err)
}
if len(c.Outputs) != 1 {
t.Fatalf("got %d outputs; want 1", len(c.Outputs))
}
if got, want := "Number 5", c.Outputs[0].Description; got != want {
t.Fatalf("got description %q; want %q", got, want)
}
}
func TestConfigValidate_outputDuplicate(t *testing.T) {
c := testConfig(t, "validate-output-dup")
if err := c.Validate(); err == nil {
t.Fatal("should not be valid")
}
}
func TestConfigValidate_pathVar(t *testing.T) {
c := testConfig(t, "validate-path-var")
if err := c.Validate(); err != nil {
t.Fatalf("err: %s", err)
}
}
func TestConfigValidate_pathVarInvalid(t *testing.T) {
c := testConfig(t, "validate-path-var-invalid")
if err := c.Validate(); err == nil {
t.Fatal("should not be valid")
}
}
func TestConfigValidate_providerMulti(t *testing.T) {
c := testConfig(t, "validate-provider-multi")
if err := c.Validate(); err == nil {
t.Fatal("should not be valid")
}
}
func TestConfigValidate_providerMultiGood(t *testing.T) {
c := testConfig(t, "validate-provider-multi-good")
if err := c.Validate(); err != nil {
t.Fatalf("should be valid: %s", err)
}
}
func TestConfigValidate_providerMultiRefGood(t *testing.T) {
c := testConfig(t, "validate-provider-multi-ref-good")
if err := c.Validate(); err != nil {
t.Fatalf("should be valid: %s", err)
}
}
func TestConfigValidate_provConnSplatOther(t *testing.T) {
c := testConfig(t, "validate-prov-conn-splat-other")
if err := c.Validate(); err != nil {
t.Fatalf("should be valid: %s", err)
}
}
func TestConfigValidate_provConnSplatSelf(t *testing.T) {
c := testConfig(t, "validate-prov-conn-splat-self")
if err := c.Validate(); err == nil {
t.Fatal("should not be valid")
}
}
func TestConfigValidate_provSplatOther(t *testing.T) {
c := testConfig(t, "validate-prov-splat-other")
if err := c.Validate(); err != nil {
t.Fatalf("should be valid: %s", err)
}
}
func TestConfigValidate_provSplatSelf(t *testing.T) {
c := testConfig(t, "validate-prov-splat-self")
if err := c.Validate(); err == nil {
t.Fatal("should not be valid")
}
}
func TestConfigValidate_resourceProvVarSelf(t *testing.T) {
c := testConfig(t, "validate-resource-prov-self")
if err := c.Validate(); err != nil {
t.Fatalf("should be valid: %s", err)
}
}
func TestConfigValidate_resourceVarSelf(t *testing.T) {
c := testConfig(t, "validate-resource-self")
if err := c.Validate(); err == nil {
t.Fatal("should not be valid")
}
}
func TestConfigValidate_unknownThing(t *testing.T) {
c := testConfig(t, "validate-unknownthing")
if err := c.Validate(); err == nil {
t.Fatal("should not be valid")
}
}
func TestConfigValidate_unknownResourceVar(t *testing.T) {
c := testConfig(t, "validate-unknown-resource-var")
if err := c.Validate(); err == nil {
t.Fatal("should not be valid")
}
}
func TestConfigValidate_unknownResourceVar_output(t *testing.T) {
c := testConfig(t, "validate-unknown-resource-var-output")
if err := c.Validate(); err == nil {
t.Fatal("should not be valid")
}
}
func TestConfigValidate_unknownVar(t *testing.T) {
c := testConfig(t, "validate-unknownvar")
if err := c.Validate(); err == nil {
t.Fatal("should not be valid")
}
}
func TestConfigValidate_unknownVarCount(t *testing.T) {
c := testConfig(t, "validate-unknownvar-count")
if err := c.Validate(); err == nil {
t.Fatal("should not be valid")
}
}
func TestConfigValidate_varDefault(t *testing.T) {
c := testConfig(t, "validate-var-default")
if err := c.Validate(); err != nil {
t.Fatalf("should be valid: %s", err)
}
}
func TestConfigValidate_varDefaultListType(t *testing.T) {
c := testConfig(t, "validate-var-default-list-type")
if err := c.Validate(); err != nil {
t.Fatalf("should be valid: %s", err)
}
}
func TestConfigValidate_varDefaultInterpolate(t *testing.T) {
c := testConfig(t, "validate-var-default-interpolate")
if err := c.Validate(); err == nil {
t.Fatal("should not be valid")
}
}
func TestConfigValidate_varDefaultInterpolateEscaped(t *testing.T) {
c := testConfig(t, "validate-var-default-interpolate-escaped")
if err := c.Validate(); err != nil {
t.Fatalf("should be valid, but got err: %s", err)
}
}
func TestConfigValidate_varDup(t *testing.T) {
c := testConfig(t, "validate-var-dup")
if err := c.Validate(); err == nil {
t.Fatal("should not be valid")
}
}
func TestConfigValidate_varMultiExactNonSlice(t *testing.T) {
c := testConfig(t, "validate-var-multi-exact-non-slice")
if err := c.Validate(); err != nil {
t.Fatalf("should be valid: %s", err)
}
}
func TestConfigValidate_varMultiFunctionCall(t *testing.T) {
c := testConfig(t, "validate-var-multi-func")
if err := c.Validate(); err != nil {
t.Fatalf("should be valid: %s", err)
}
}
func TestConfigValidate_varModule(t *testing.T) {
c := testConfig(t, "validate-var-module")
if err := c.Validate(); err != nil {
t.Fatalf("err: %s", err)
}
}
func TestConfigValidate_varModuleInvalid(t *testing.T) {
c := testConfig(t, "validate-var-module-invalid")
if err := c.Validate(); err == nil {
t.Fatal("should not be valid")
}
}
func TestConfigValidate_varProviderVersionInvalid(t *testing.T) {
c := testConfig(t, "validate-provider-version-invalid")
if err := c.Validate(); err == nil {
t.Fatal("should not be valid")
}
}
func TestNameRegexp(t *testing.T) {
cases := []struct {
Input string
Match bool
}{
{"hello", true},
{"foo-bar", true},
{"foo_bar", true},
{"_hello", true},
{"foo bar", false},
{"foo.bar", false},
}
for _, tc := range cases {
if NameRegexp.Match([]byte(tc.Input)) != tc.Match {
t.Fatalf("Input: %s\n\nExpected: %#v", tc.Input, tc.Match)
}
}
}
func TestConfigValidate_localValuesMultiFile(t *testing.T) {
c, err := LoadDir(filepath.Join(fixtureDir, "validate-local-multi-file"))
if err != nil {
t.Fatalf("unexpected error during load: %s", err)
}
if err := c.Validate(); err != nil {
t.Fatalf("unexpected error from validate: %s", err)
}
if len(c.Locals) != 1 {
t.Fatalf("got 0 locals; want 1")
}
if got, want := c.Locals[0].Name, "test"; got != want {
t.Errorf("wrong local name\ngot: %#v\nwant: %#v", got, want)
}
}
func TestProviderConfigName(t *testing.T) {
pcs := []*ProviderConfig{
&ProviderConfig{Name: "aw"},
&ProviderConfig{Name: "aws"},
&ProviderConfig{Name: "a"},
&ProviderConfig{Name: "gce_"},
}
n := ProviderConfigName("aws_instance", pcs)
if n != "aws" {
t.Fatalf("bad: %s", n)
}
}
func testConfig(t *testing.T, name string) *Config {
c, err := LoadFile(filepath.Join(fixtureDir, name, "main.tf"))
if err != nil {
t.Fatalf("file: %s\n\nerr: %s", name, err)
}
return c
}
func TestConfigDataCount(t *testing.T) {
c := testConfig(t, "data-count")
actual, err := c.Resources[0].Count()
if err != nil {
t.Fatalf("err: %s", err)
}
if actual != 5 {
t.Fatalf("bad: %#v", actual)
}
// we need to make sure "count" has been removed from the RawConfig, since
// it's not a real key and won't validate.
if _, ok := c.Resources[0].RawConfig.Raw["count"]; ok {
t.Fatal("count key still exists in RawConfig")
}
}
func TestConfigProviderVersion(t *testing.T) {
c := testConfig(t, "provider-version")
if len(c.ProviderConfigs) != 1 {
t.Fatal("expected 1 provider")
}
p := c.ProviderConfigs[0]
if p.Name != "aws" {
t.Fatalf("expected provider name 'aws', got %q", p.Name)
}
if p.Version != "0.0.1" {
t.Fatalf("expected providers version '0.0.1', got %q", p.Version)
}
if _, ok := p.RawConfig.Raw["version"]; ok {
t.Fatal("'version' should not exist in raw config")
}
}
func TestConfigModuleProviders(t *testing.T) {
c := testConfig(t, "module-providers")
if len(c.Modules) != 1 {
t.Fatalf("expected 1 module, got %d", len(c.Modules))
}
expected := map[string]string{
"aws": "aws.foo",
}
got := c.Modules[0].Providers
if !reflect.DeepEqual(expected, got) {
t.Fatalf("exptected providers %#v, got providers %#v", expected, got)
}
}
func TestValidateOutputErrorWarnings(t *testing.T) {
// TODO: remove this in 0.12
c := testConfig(t, "output-warnings")
diags := c.Validate()
if diags.HasErrors() {
t.Fatal("config should not have errors:", diags)
}
if len(diags) != 2 {
t.Fatalf("should have 2 warnings, got %d:\n%s", len(diags), diags)
}
// this fixture has no explicit count, and should have no warning
c = testConfig(t, "output-no-warnings")
if err := c.Validate(); err != nil {
t.Fatal("config should have no warnings or errors")
}
}

View File

@ -1,43 +0,0 @@
package config
// configTree represents a tree of configurations where the root is the
// first file and its children are the configurations it has imported.
type configTree struct {
Path string
Config *Config
Children []*configTree
}
// Flatten flattens the entire tree down to a single merged Config
// structure.
func (t *configTree) Flatten() (*Config, error) {
// No children is easy: we're already merged!
if len(t.Children) == 0 {
return t.Config, nil
}
// Depth-first, merge all the children first.
childConfigs := make([]*Config, len(t.Children))
for i, ct := range t.Children {
c, err := ct.Flatten()
if err != nil {
return nil, err
}
childConfigs[i] = c
}
// Merge all the children in order
config := childConfigs[0]
childConfigs = childConfigs[1:]
for _, config2 := range childConfigs {
var err error
config, err = Merge(config, config2)
if err != nil {
return nil, err
}
}
// Merge the final merged child config with our own
return Merge(config, t.Config)
}

View File

@ -1,151 +0,0 @@
package config
import (
"bufio"
"fmt"
"io"
"os"
"github.com/hashicorp/errwrap"
)
// configurable is an interface that must be implemented by any configuration
// formats of Terraform in order to return a *Config.
type configurable interface {
Config() (*Config, error)
}
// importTree is the result of the first-pass load of the configuration
// files. It is a tree of raw configurables and then any children (their
// imports).
//
// An importTree can be turned into a configTree.
type importTree struct {
Path string
Raw configurable
Children []*importTree
}
// This is the function type that must be implemented by the configuration
// file loader to turn a single file into a configurable and any additional
// imports.
type fileLoaderFunc func(path string) (configurable, []string, error)
// Set this to a non-empty value at link time to enable the HCL2 experiment.
// This is not currently enabled for release builds.
//
// For example:
// go install -ldflags="-X github.com/hashicorp/terraform/config.enableHCL2Experiment=true" github.com/hashicorp/terraform
var enableHCL2Experiment = ""
// loadTree takes a single file and loads the entire importTree for that
// file. This function detects what kind of configuration file it is an
// executes the proper fileLoaderFunc.
func loadTree(root string) (*importTree, error) {
var f fileLoaderFunc
// HCL2 experiment is currently activated at build time via the linker.
// See the comment on this variable for more information.
if enableHCL2Experiment == "" {
// Main-line behavior: always use the original HCL parser
switch ext(root) {
case ".tf", ".tf.json":
f = loadFileHcl
default:
}
} else {
// Experimental behavior: use the HCL2 parser if the opt-in comment
// is present.
switch ext(root) {
case ".tf":
// We need to sniff the file for the opt-in comment line to decide
// if the file is participating in the HCL2 experiment.
cf, err := os.Open(root)
if err != nil {
return nil, err
}
sc := bufio.NewScanner(cf)
for sc.Scan() {
if sc.Text() == "#terraform:hcl2" {
f = globalHCL2Loader.loadFile
}
}
if f == nil {
f = loadFileHcl
}
case ".tf.json":
f = loadFileHcl
default:
}
}
if f == nil {
return nil, fmt.Errorf(
"%s: unknown configuration format. Use '.tf' or '.tf.json' extension",
root)
}
c, imps, err := f(root)
if err != nil {
return nil, err
}
children := make([]*importTree, len(imps))
for i, imp := range imps {
t, err := loadTree(imp)
if err != nil {
return nil, err
}
children[i] = t
}
return &importTree{
Path: root,
Raw: c,
Children: children,
}, nil
}
// Close releases any resources we might be holding open for the importTree.
//
// This can safely be called even while ConfigTree results are alive. The
// importTree is not bound to these.
func (t *importTree) Close() error {
if c, ok := t.Raw.(io.Closer); ok {
c.Close()
}
for _, ct := range t.Children {
ct.Close()
}
return nil
}
// ConfigTree traverses the importTree and turns each node into a *Config
// object, ultimately returning a *configTree.
func (t *importTree) ConfigTree() (*configTree, error) {
config, err := t.Raw.Config()
if err != nil {
return nil, errwrap.Wrapf(fmt.Sprintf("Error loading %s: {{err}}", t.Path), err)
}
// Build our result
result := &configTree{
Path: t.Path,
Config: config,
}
// Build the config trees for the children
result.Children = make([]*configTree, len(t.Children))
for i, ct := range t.Children {
t, err := ct.ConfigTree()
if err != nil {
return nil, err
}
result.Children[i] = t
}
return result, nil
}

View File

@ -1,81 +0,0 @@
package config
import (
"testing"
)
func TestImportTreeHCL2Experiment(t *testing.T) {
// Can only run this test if we're built with the experiment enabled.
// Enable this test by passing the following option to "go test":
// -ldflags="-X github.com/hashicorp/terraform/config.enableHCL2Experiment=true"
// See the comment associated with this flag variable for more information.
if enableHCL2Experiment == "" {
t.Skip("HCL2 experiment is not enabled")
}
t.Run("HCL not opted in", func(t *testing.T) {
// .tf file without opt-in should use the old HCL parser
imp, err := loadTree("testdata/hcl2-experiment-switch/not-opted-in.tf")
if err != nil {
t.Fatal(err)
}
tree, err := imp.ConfigTree()
if err != nil {
t.Fatalf("unexpected error loading not-opted-in.tf: %s", err)
}
cfg := tree.Config
if got, want := len(cfg.Locals), 1; got != want {
t.Fatalf("wrong number of locals %#v; want %#v", got, want)
}
if cfg.Locals[0].RawConfig.Raw == nil {
// Having RawConfig.Raw indicates the old loader
t.Fatal("local has no RawConfig.Raw")
}
})
t.Run("HCL opted in", func(t *testing.T) {
// .tf file with opt-in should use the new HCL2 parser
imp, err := loadTree("testdata/hcl2-experiment-switch/opted-in.tf")
if err != nil {
t.Fatal(err)
}
tree, err := imp.ConfigTree()
if err != nil {
t.Fatalf("unexpected error loading opted-in.tf: %s", err)
}
cfg := tree.Config
if got, want := len(cfg.Locals), 1; got != want {
t.Fatalf("wrong number of locals %#v; want %#v", got, want)
}
if cfg.Locals[0].RawConfig.Body == nil {
// Having RawConfig.Body indicates the new loader
t.Fatal("local has no RawConfig.Body")
}
})
t.Run("JSON ineligible", func(t *testing.T) {
// .tf.json file should always use the old HCL parser
imp, err := loadTree("testdata/hcl2-experiment-switch/not-eligible.tf.json")
if err != nil {
t.Fatal(err)
}
tree, err := imp.ConfigTree()
if err != nil {
t.Fatalf("unexpected error loading not-eligible.tf.json: %s", err)
}
cfg := tree.Config
if got, want := len(cfg.Locals), 1; got != want {
t.Fatalf("wrong number of locals %#v; want %#v", got, want)
}
if cfg.Locals[0].RawConfig.Raw == nil {
// Having RawConfig.Raw indicates the old loader
t.Fatal("local has no RawConfig.Raw")
}
})
}

View File

@ -1,212 +0,0 @@
package config
import (
"encoding/json"
"fmt"
"io"
"os"
"path/filepath"
"sort"
"strings"
"github.com/hashicorp/hcl"
)
// ErrNoConfigsFound is the error returned by LoadDir if no
// Terraform configuration files were found in the given directory.
type ErrNoConfigsFound struct {
Dir string
}
func (e ErrNoConfigsFound) Error() string {
return fmt.Sprintf(
"No Terraform configuration files found in directory: %s",
e.Dir)
}
// LoadJSON loads a single Terraform configuration from a given JSON document.
//
// The document must be a complete Terraform configuration. This function will
// NOT try to load any additional modules so only the given document is loaded.
func LoadJSON(raw json.RawMessage) (*Config, error) {
obj, err := hcl.Parse(string(raw))
if err != nil {
return nil, fmt.Errorf(
"Error parsing JSON document as HCL: %s", err)
}
// Start building the result
hclConfig := &hclConfigurable{
Root: obj,
}
return hclConfig.Config()
}
// LoadFile loads the Terraform configuration from a given file.
//
// This file can be any format that Terraform recognizes, and import any
// other format that Terraform recognizes.
func LoadFile(path string) (*Config, error) {
importTree, err := loadTree(path)
if err != nil {
return nil, err
}
configTree, err := importTree.ConfigTree()
// Close the importTree now so that we can clear resources as quickly
// as possible.
importTree.Close()
if err != nil {
return nil, err
}
return configTree.Flatten()
}
// LoadDir loads all the Terraform configuration files in a single
// directory and appends them together.
//
// Special files known as "override files" can also be present, which
// are merged into the loaded configuration. That is, the non-override
// files are loaded first to create the configuration. Then, the overrides
// are merged into the configuration to create the final configuration.
//
// Files are loaded in lexical order.
func LoadDir(root string) (*Config, error) {
files, overrides, err := dirFiles(root)
if err != nil {
return nil, err
}
if len(files) == 0 && len(overrides) == 0 {
return nil, &ErrNoConfigsFound{Dir: root}
}
// Determine the absolute path to the directory.
rootAbs, err := filepath.Abs(root)
if err != nil {
return nil, err
}
var result *Config
// Sort the files and overrides so we have a deterministic order
sort.Strings(files)
sort.Strings(overrides)
// Load all the regular files, append them to each other.
for _, f := range files {
c, err := LoadFile(f)
if err != nil {
return nil, err
}
if result != nil {
result, err = Append(result, c)
if err != nil {
return nil, err
}
} else {
result = c
}
}
if len(files) == 0 {
result = &Config{}
}
// Load all the overrides, and merge them into the config
for _, f := range overrides {
c, err := LoadFile(f)
if err != nil {
return nil, err
}
result, err = Merge(result, c)
if err != nil {
return nil, err
}
}
// Mark the directory
result.Dir = rootAbs
return result, nil
}
// Ext returns the Terraform configuration extension of the given
// path, or a blank string if it is an invalid function.
func ext(path string) string {
if strings.HasSuffix(path, ".tf") {
return ".tf"
} else if strings.HasSuffix(path, ".tf.json") {
return ".tf.json"
} else {
return ""
}
}
func dirFiles(dir string) ([]string, []string, error) {
f, err := os.Open(dir)
if err != nil {
return nil, nil, err
}
defer f.Close()
fi, err := f.Stat()
if err != nil {
return nil, nil, err
}
if !fi.IsDir() {
return nil, nil, fmt.Errorf(
"configuration path must be a directory: %s",
dir)
}
var files, overrides []string
err = nil
for err != io.EOF {
var fis []os.FileInfo
fis, err = f.Readdir(128)
if err != nil && err != io.EOF {
return nil, nil, err
}
for _, fi := range fis {
// Ignore directories
if fi.IsDir() {
continue
}
// Only care about files that are valid to load
name := fi.Name()
extValue := ext(name)
if extValue == "" || IsIgnoredFile(name) {
continue
}
// Determine if we're dealing with an override
nameNoExt := name[:len(name)-len(extValue)]
override := nameNoExt == "override" ||
strings.HasSuffix(nameNoExt, "_override")
path := filepath.Join(dir, name)
if override {
overrides = append(overrides, path)
} else {
files = append(files, path)
}
}
}
return files, overrides, nil
}
// IsIgnoredFile returns true or false depending on whether the
// provided file name is a file that should be ignored.
func IsIgnoredFile(name string) bool {
return strings.HasPrefix(name, ".") || // Unix-like hidden files
strings.HasSuffix(name, "~") || // vim
strings.HasPrefix(name, "#") && strings.HasSuffix(name, "#") // emacs
}

File diff suppressed because it is too large Load Diff

View File

@ -1,473 +0,0 @@
package config
import (
"fmt"
"sort"
"strings"
hcl2 "github.com/hashicorp/hcl/v2"
gohcl2 "github.com/hashicorp/hcl/v2/gohcl"
hcl2parse "github.com/hashicorp/hcl/v2/hclparse"
"github.com/hashicorp/terraform/configs/hcl2shim"
"github.com/zclconf/go-cty/cty"
)
// hcl2Configurable is an implementation of configurable that knows
// how to turn a HCL Body into a *Config object.
type hcl2Configurable struct {
SourceFilename string
Body hcl2.Body
}
// hcl2Loader is a wrapper around a HCL parser that provides a fileLoaderFunc.
type hcl2Loader struct {
Parser *hcl2parse.Parser
}
// For the moment we'll just have a global loader since we don't have anywhere
// better to stash this.
// TODO: refactor the loader API so that it uses some sort of object we can
// stash the parser inside.
var globalHCL2Loader = newHCL2Loader()
// newHCL2Loader creates a new hcl2Loader containing a new HCL Parser.
//
// HCL parsers retain information about files that are loaded to aid in
// producing diagnostic messages, so all files within a single configuration
// should be loaded with the same parser to ensure the availability of
// full diagnostic information.
func newHCL2Loader() hcl2Loader {
return hcl2Loader{
Parser: hcl2parse.NewParser(),
}
}
// loadFile is a fileLoaderFunc that knows how to read a HCL2 file and turn it
// into a hcl2Configurable.
func (l hcl2Loader) loadFile(filename string) (configurable, []string, error) {
var f *hcl2.File
var diags hcl2.Diagnostics
if strings.HasSuffix(filename, ".json") {
f, diags = l.Parser.ParseJSONFile(filename)
} else {
f, diags = l.Parser.ParseHCLFile(filename)
}
if diags.HasErrors() {
// Return diagnostics as an error; callers may type-assert this to
// recover the original diagnostics, if it doesn't end up wrapped
// in another error.
return nil, nil, diags
}
return &hcl2Configurable{
SourceFilename: filename,
Body: f.Body,
}, nil, nil
}
func (t *hcl2Configurable) Config() (*Config, error) {
config := &Config{}
// these structs are used only for the initial shallow decoding; we'll
// expand this into the main, public-facing config structs afterwards.
type atlas struct {
Name string `hcl:"name"`
Include *[]string `hcl:"include"`
Exclude *[]string `hcl:"exclude"`
}
type provider struct {
Name string `hcl:"name,label"`
Alias *string `hcl:"alias,attr"`
Version *string `hcl:"version,attr"`
Config hcl2.Body `hcl:",remain"`
}
type module struct {
Name string `hcl:"name,label"`
Source string `hcl:"source,attr"`
Version *string `hcl:"version,attr"`
Providers *map[string]string `hcl:"providers,attr"`
Config hcl2.Body `hcl:",remain"`
}
type resourceLifecycle struct {
CreateBeforeDestroy *bool `hcl:"create_before_destroy,attr"`
PreventDestroy *bool `hcl:"prevent_destroy,attr"`
IgnoreChanges *[]string `hcl:"ignore_changes,attr"`
}
type connection struct {
Config hcl2.Body `hcl:",remain"`
}
type provisioner struct {
Type string `hcl:"type,label"`
When *string `hcl:"when,attr"`
OnFailure *string `hcl:"on_failure,attr"`
Connection *connection `hcl:"connection,block"`
Config hcl2.Body `hcl:",remain"`
}
type managedResource struct {
Type string `hcl:"type,label"`
Name string `hcl:"name,label"`
CountExpr hcl2.Expression `hcl:"count,attr"`
Provider *string `hcl:"provider,attr"`
DependsOn *[]string `hcl:"depends_on,attr"`
Lifecycle *resourceLifecycle `hcl:"lifecycle,block"`
Provisioners []provisioner `hcl:"provisioner,block"`
Connection *connection `hcl:"connection,block"`
Config hcl2.Body `hcl:",remain"`
}
type dataResource struct {
Type string `hcl:"type,label"`
Name string `hcl:"name,label"`
CountExpr hcl2.Expression `hcl:"count,attr"`
Provider *string `hcl:"provider,attr"`
DependsOn *[]string `hcl:"depends_on,attr"`
Config hcl2.Body `hcl:",remain"`
}
type variable struct {
Name string `hcl:"name,label"`
DeclaredType *string `hcl:"type,attr"`
Default *cty.Value `hcl:"default,attr"`
Description *string `hcl:"description,attr"`
Sensitive *bool `hcl:"sensitive,attr"`
}
type output struct {
Name string `hcl:"name,label"`
ValueExpr hcl2.Expression `hcl:"value,attr"`
DependsOn *[]string `hcl:"depends_on,attr"`
Description *string `hcl:"description,attr"`
Sensitive *bool `hcl:"sensitive,attr"`
}
type locals struct {
Definitions hcl2.Attributes `hcl:",remain"`
}
type backend struct {
Type string `hcl:"type,label"`
Config hcl2.Body `hcl:",remain"`
}
type terraform struct {
RequiredVersion *string `hcl:"required_version,attr"`
Backend *backend `hcl:"backend,block"`
}
type topLevel struct {
Atlas *atlas `hcl:"atlas,block"`
Datas []dataResource `hcl:"data,block"`
Modules []module `hcl:"module,block"`
Outputs []output `hcl:"output,block"`
Providers []provider `hcl:"provider,block"`
Resources []managedResource `hcl:"resource,block"`
Terraform *terraform `hcl:"terraform,block"`
Variables []variable `hcl:"variable,block"`
Locals []*locals `hcl:"locals,block"`
}
var raw topLevel
diags := gohcl2.DecodeBody(t.Body, nil, &raw)
if diags.HasErrors() {
// Do some minimal decoding to see if we can at least get the
// required Terraform version, which might help explain why we
// couldn't parse the rest.
if raw.Terraform != nil && raw.Terraform.RequiredVersion != nil {
config.Terraform = &Terraform{
RequiredVersion: *raw.Terraform.RequiredVersion,
}
}
// We return the diags as an implementation of error, which the
// caller than then type-assert if desired to recover the individual
// diagnostics.
// FIXME: The current API gives us no way to return warnings in the
// absence of any errors.
return config, diags
}
if raw.Terraform != nil {
var reqdVersion string
var backend *Backend
if raw.Terraform.RequiredVersion != nil {
reqdVersion = *raw.Terraform.RequiredVersion
}
if raw.Terraform.Backend != nil {
backend = new(Backend)
backend.Type = raw.Terraform.Backend.Type
// We don't permit interpolations or nested blocks inside the
// backend config, so we can decode the config early here and
// get direct access to the values, which is important for the
// config hashing to work as expected.
var config map[string]string
configDiags := gohcl2.DecodeBody(raw.Terraform.Backend.Config, nil, &config)
diags = append(diags, configDiags...)
raw := make(map[string]interface{}, len(config))
for k, v := range config {
raw[k] = v
}
var err error
backend.RawConfig, err = NewRawConfig(raw)
if err != nil {
diags = append(diags, &hcl2.Diagnostic{
Severity: hcl2.DiagError,
Summary: "Invalid backend configuration",
Detail: fmt.Sprintf("Error in backend configuration: %s", err),
})
}
}
config.Terraform = &Terraform{
RequiredVersion: reqdVersion,
Backend: backend,
}
}
if raw.Atlas != nil {
var include, exclude []string
if raw.Atlas.Include != nil {
include = *raw.Atlas.Include
}
if raw.Atlas.Exclude != nil {
exclude = *raw.Atlas.Exclude
}
config.Atlas = &AtlasConfig{
Name: raw.Atlas.Name,
Include: include,
Exclude: exclude,
}
}
for _, rawM := range raw.Modules {
m := &Module{
Name: rawM.Name,
Source: rawM.Source,
RawConfig: NewRawConfigHCL2(rawM.Config),
}
if rawM.Version != nil {
m.Version = *rawM.Version
}
if rawM.Providers != nil {
m.Providers = *rawM.Providers
}
config.Modules = append(config.Modules, m)
}
for _, rawV := range raw.Variables {
v := &Variable{
Name: rawV.Name,
}
if rawV.DeclaredType != nil {
v.DeclaredType = *rawV.DeclaredType
}
if rawV.Default != nil {
v.Default = hcl2shim.ConfigValueFromHCL2(*rawV.Default)
}
if rawV.Description != nil {
v.Description = *rawV.Description
}
config.Variables = append(config.Variables, v)
}
for _, rawO := range raw.Outputs {
o := &Output{
Name: rawO.Name,
}
if rawO.Description != nil {
o.Description = *rawO.Description
}
if rawO.DependsOn != nil {
o.DependsOn = *rawO.DependsOn
}
if rawO.Sensitive != nil {
o.Sensitive = *rawO.Sensitive
}
// The result is expected to be a map like map[string]interface{}{"value": something},
// so we'll fake that with our hcl2shim.SingleAttrBody shim.
o.RawConfig = NewRawConfigHCL2(hcl2shim.SingleAttrBody{
Name: "value",
Expr: rawO.ValueExpr,
})
config.Outputs = append(config.Outputs, o)
}
for _, rawR := range raw.Resources {
r := &Resource{
Mode: ManagedResourceMode,
Type: rawR.Type,
Name: rawR.Name,
}
if rawR.Lifecycle != nil {
var l ResourceLifecycle
if rawR.Lifecycle.CreateBeforeDestroy != nil {
l.CreateBeforeDestroy = *rawR.Lifecycle.CreateBeforeDestroy
}
if rawR.Lifecycle.PreventDestroy != nil {
l.PreventDestroy = *rawR.Lifecycle.PreventDestroy
}
if rawR.Lifecycle.IgnoreChanges != nil {
l.IgnoreChanges = *rawR.Lifecycle.IgnoreChanges
}
r.Lifecycle = l
}
if rawR.Provider != nil {
r.Provider = *rawR.Provider
}
if rawR.DependsOn != nil {
r.DependsOn = *rawR.DependsOn
}
var defaultConnInfo *RawConfig
if rawR.Connection != nil {
defaultConnInfo = NewRawConfigHCL2(rawR.Connection.Config)
}
for _, rawP := range rawR.Provisioners {
p := &Provisioner{
Type: rawP.Type,
}
switch {
case rawP.When == nil:
p.When = ProvisionerWhenCreate
case *rawP.When == "create":
p.When = ProvisionerWhenCreate
case *rawP.When == "destroy":
p.When = ProvisionerWhenDestroy
default:
p.When = ProvisionerWhenInvalid
}
switch {
case rawP.OnFailure == nil:
p.OnFailure = ProvisionerOnFailureFail
case *rawP.When == "fail":
p.OnFailure = ProvisionerOnFailureFail
case *rawP.When == "continue":
p.OnFailure = ProvisionerOnFailureContinue
default:
p.OnFailure = ProvisionerOnFailureInvalid
}
if rawP.Connection != nil {
p.ConnInfo = NewRawConfigHCL2(rawP.Connection.Config)
} else {
p.ConnInfo = defaultConnInfo
}
p.RawConfig = NewRawConfigHCL2(rawP.Config)
r.Provisioners = append(r.Provisioners, p)
}
// The old loader records the count expression as a weird RawConfig with
// a single-element map inside. Since the rest of the world is assuming
// that, we'll mimic it here.
{
countBody := hcl2shim.SingleAttrBody{
Name: "count",
Expr: rawR.CountExpr,
}
r.RawCount = NewRawConfigHCL2(countBody)
r.RawCount.Key = "count"
}
r.RawConfig = NewRawConfigHCL2(rawR.Config)
config.Resources = append(config.Resources, r)
}
for _, rawR := range raw.Datas {
r := &Resource{
Mode: DataResourceMode,
Type: rawR.Type,
Name: rawR.Name,
}
if rawR.Provider != nil {
r.Provider = *rawR.Provider
}
if rawR.DependsOn != nil {
r.DependsOn = *rawR.DependsOn
}
// The old loader records the count expression as a weird RawConfig with
// a single-element map inside. Since the rest of the world is assuming
// that, we'll mimic it here.
{
countBody := hcl2shim.SingleAttrBody{
Name: "count",
Expr: rawR.CountExpr,
}
r.RawCount = NewRawConfigHCL2(countBody)
r.RawCount.Key = "count"
}
r.RawConfig = NewRawConfigHCL2(rawR.Config)
config.Resources = append(config.Resources, r)
}
for _, rawP := range raw.Providers {
p := &ProviderConfig{
Name: rawP.Name,
}
if rawP.Alias != nil {
p.Alias = *rawP.Alias
}
if rawP.Version != nil {
p.Version = *rawP.Version
}
// The result is expected to be a map like map[string]interface{}{"value": something},
// so we'll fake that with our hcl2shim.SingleAttrBody shim.
p.RawConfig = NewRawConfigHCL2(rawP.Config)
config.ProviderConfigs = append(config.ProviderConfigs, p)
}
for _, rawL := range raw.Locals {
names := make([]string, 0, len(rawL.Definitions))
for n := range rawL.Definitions {
names = append(names, n)
}
sort.Strings(names)
for _, n := range names {
attr := rawL.Definitions[n]
l := &Local{
Name: n,
RawConfig: NewRawConfigHCL2(hcl2shim.SingleAttrBody{
Name: "value",
Expr: attr.Expr,
}),
}
config.Locals = append(config.Locals, l)
}
}
// FIXME: The current API gives us no way to return warnings in the
// absence of any errors.
var err error
if diags.HasErrors() {
err = diags
}
return config, err
}

View File

@ -1,510 +0,0 @@
package config
import (
"reflect"
"testing"
"github.com/zclconf/go-cty/cty"
hcl2 "github.com/hashicorp/hcl/v2"
gohcl2 "github.com/hashicorp/hcl/v2/gohcl"
)
func TestHCL2ConfigurableConfigurable(t *testing.T) {
var _ configurable = new(hcl2Configurable)
}
func TestHCL2Basic(t *testing.T) {
loader := globalHCL2Loader
cbl, _, err := loader.loadFile("testdata/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)
}
}
}
}

View File

@ -1,9 +0,0 @@
package config
import (
"testing"
)
func TestHCLConfigurableConfigurable(t *testing.T) {
var _ configurable = new(hclConfigurable)
}

File diff suppressed because it is too large Load Diff

View File

@ -1,204 +0,0 @@
package config
// Merge merges two configurations into a single configuration.
//
// Merge allows for the two configurations to have duplicate resources,
// because the resources will be merged. This differs from a single
// Config which must only have unique resources.
func Merge(c1, c2 *Config) (*Config, error) {
c := new(Config)
// Merge unknown keys
unknowns := make(map[string]struct{})
for _, k := range c1.unknownKeys {
_, present := unknowns[k]
if !present {
unknowns[k] = struct{}{}
c.unknownKeys = append(c.unknownKeys, k)
}
}
for _, k := range c2.unknownKeys {
_, present := unknowns[k]
if !present {
unknowns[k] = struct{}{}
c.unknownKeys = append(c.unknownKeys, k)
}
}
// Merge Atlas configuration. This is a dumb one overrides the other
// sort of merge.
c.Atlas = c1.Atlas
if c2.Atlas != nil {
c.Atlas = c2.Atlas
}
// Merge the Terraform configuration
if c1.Terraform != nil {
c.Terraform = c1.Terraform
if c2.Terraform != nil {
c.Terraform.Merge(c2.Terraform)
}
} else {
c.Terraform = c2.Terraform
}
// NOTE: Everything below is pretty gross. Due to the lack of generics
// in Go, there is some hoop-jumping involved to make this merging a
// little more test-friendly and less repetitive. Ironically, making it
// less repetitive involves being a little repetitive, but I prefer to
// be repetitive with things that are less error prone than things that
// are more error prone (more logic). Type conversions to an interface
// are pretty low-error.
var m1, m2, mresult []merger
// Modules
m1 = make([]merger, 0, len(c1.Modules))
m2 = make([]merger, 0, len(c2.Modules))
for _, v := range c1.Modules {
m1 = append(m1, v)
}
for _, v := range c2.Modules {
m2 = append(m2, v)
}
mresult = mergeSlice(m1, m2)
if len(mresult) > 0 {
c.Modules = make([]*Module, len(mresult))
for i, v := range mresult {
c.Modules[i] = v.(*Module)
}
}
// Outputs
m1 = make([]merger, 0, len(c1.Outputs))
m2 = make([]merger, 0, len(c2.Outputs))
for _, v := range c1.Outputs {
m1 = append(m1, v)
}
for _, v := range c2.Outputs {
m2 = append(m2, v)
}
mresult = mergeSlice(m1, m2)
if len(mresult) > 0 {
c.Outputs = make([]*Output, len(mresult))
for i, v := range mresult {
c.Outputs[i] = v.(*Output)
}
}
// Provider Configs
m1 = make([]merger, 0, len(c1.ProviderConfigs))
m2 = make([]merger, 0, len(c2.ProviderConfigs))
for _, v := range c1.ProviderConfigs {
m1 = append(m1, v)
}
for _, v := range c2.ProviderConfigs {
m2 = append(m2, v)
}
mresult = mergeSlice(m1, m2)
if len(mresult) > 0 {
c.ProviderConfigs = make([]*ProviderConfig, len(mresult))
for i, v := range mresult {
c.ProviderConfigs[i] = v.(*ProviderConfig)
}
}
// Resources
m1 = make([]merger, 0, len(c1.Resources))
m2 = make([]merger, 0, len(c2.Resources))
for _, v := range c1.Resources {
m1 = append(m1, v)
}
for _, v := range c2.Resources {
m2 = append(m2, v)
}
mresult = mergeSlice(m1, m2)
if len(mresult) > 0 {
c.Resources = make([]*Resource, len(mresult))
for i, v := range mresult {
c.Resources[i] = v.(*Resource)
}
}
// Variables
m1 = make([]merger, 0, len(c1.Variables))
m2 = make([]merger, 0, len(c2.Variables))
for _, v := range c1.Variables {
m1 = append(m1, v)
}
for _, v := range c2.Variables {
m2 = append(m2, v)
}
mresult = mergeSlice(m1, m2)
if len(mresult) > 0 {
c.Variables = make([]*Variable, len(mresult))
for i, v := range mresult {
c.Variables[i] = v.(*Variable)
}
}
// Local Values
// These are simpler than the other config elements because they are just
// flat values and so no deep merging is required.
if localsCount := len(c1.Locals) + len(c2.Locals); localsCount != 0 {
// Explicit length check above because we want c.Locals to remain
// nil if the result would be empty.
c.Locals = make([]*Local, 0, len(c1.Locals)+len(c2.Locals))
c.Locals = append(c.Locals, c1.Locals...)
c.Locals = append(c.Locals, c2.Locals...)
}
return c, nil
}
// merger is an interface that must be implemented by types that are
// merge-able. This simplifies the implementation of Merge for the various
// components of a Config.
type merger interface {
mergerName() string
mergerMerge(merger) merger
}
// mergeSlice merges a slice of mergers.
func mergeSlice(m1, m2 []merger) []merger {
r := make([]merger, len(m1), len(m1)+len(m2))
copy(r, m1)
m := map[string]struct{}{}
for _, v2 := range m2 {
// If we already saw it, just append it because its a
// duplicate and invalid...
name := v2.mergerName()
if _, ok := m[name]; ok {
r = append(r, v2)
continue
}
m[name] = struct{}{}
// Find an original to override
var original merger
originalIndex := -1
for i, v := range m1 {
if v.mergerName() == name {
originalIndex = i
original = v
break
}
}
var v merger
if original == nil {
v = v2
} else {
v = original.mergerMerge(v2)
}
if originalIndex == -1 {
r = append(r, v)
} else {
r[originalIndex] = v
}
}
return r
}

View File

@ -1,499 +0,0 @@
package config
import (
"fmt"
"reflect"
"testing"
"github.com/davecgh/go-spew/spew"
)
func TestMerge(t *testing.T) {
cases := []struct {
c1, c2, result *Config
err bool
}{
// Normal good case.
{
&Config{
Atlas: &AtlasConfig{
Name: "foo",
},
Modules: []*Module{
&Module{Name: "foo"},
},
Outputs: []*Output{
&Output{Name: "foo"},
},
ProviderConfigs: []*ProviderConfig{
&ProviderConfig{Name: "foo"},
},
Resources: []*Resource{
&Resource{Name: "foo"},
},
Variables: []*Variable{
&Variable{Name: "foo"},
},
Locals: []*Local{
&Local{Name: "foo"},
},
unknownKeys: []string{"foo"},
},
&Config{
Atlas: &AtlasConfig{
Name: "bar",
},
Modules: []*Module{
&Module{Name: "bar"},
},
Outputs: []*Output{
&Output{Name: "bar"},
},
ProviderConfigs: []*ProviderConfig{
&ProviderConfig{Name: "bar"},
},
Resources: []*Resource{
&Resource{Name: "bar"},
},
Variables: []*Variable{
&Variable{Name: "bar"},
},
Locals: []*Local{
&Local{Name: "bar"},
},
unknownKeys: []string{"bar"},
},
&Config{
Atlas: &AtlasConfig{
Name: "bar",
},
Modules: []*Module{
&Module{Name: "foo"},
&Module{Name: "bar"},
},
Outputs: []*Output{
&Output{Name: "foo"},
&Output{Name: "bar"},
},
ProviderConfigs: []*ProviderConfig{
&ProviderConfig{Name: "foo"},
&ProviderConfig{Name: "bar"},
},
Resources: []*Resource{
&Resource{Name: "foo"},
&Resource{Name: "bar"},
},
Variables: []*Variable{
&Variable{Name: "foo"},
&Variable{Name: "bar"},
},
Locals: []*Local{
&Local{Name: "foo"},
&Local{Name: "bar"},
},
unknownKeys: []string{"foo", "bar"},
},
false,
},
// Test that when merging duplicates, it merges into the
// first, but keeps the duplicates so that errors still
// happen.
{
&Config{
Outputs: []*Output{
&Output{Name: "foo"},
},
ProviderConfigs: []*ProviderConfig{
&ProviderConfig{Name: "foo"},
},
Resources: []*Resource{
&Resource{Name: "foo"},
},
Variables: []*Variable{
&Variable{Name: "foo", Default: "foo"},
&Variable{Name: "foo"},
},
Locals: []*Local{
&Local{Name: "foo"},
},
unknownKeys: []string{"foo"},
},
&Config{
Outputs: []*Output{
&Output{Name: "bar"},
},
ProviderConfigs: []*ProviderConfig{
&ProviderConfig{Name: "bar"},
},
Resources: []*Resource{
&Resource{Name: "bar"},
},
Variables: []*Variable{
&Variable{Name: "foo", Default: "bar"},
&Variable{Name: "bar"},
},
Locals: []*Local{
&Local{Name: "foo"},
},
unknownKeys: []string{"bar"},
},
&Config{
Outputs: []*Output{
&Output{Name: "foo"},
&Output{Name: "bar"},
},
ProviderConfigs: []*ProviderConfig{
&ProviderConfig{Name: "foo"},
&ProviderConfig{Name: "bar"},
},
Resources: []*Resource{
&Resource{Name: "foo"},
&Resource{Name: "bar"},
},
Variables: []*Variable{
&Variable{Name: "foo", Default: "bar"},
&Variable{Name: "foo"},
&Variable{Name: "bar"},
},
Locals: []*Local{
&Local{Name: "foo"},
&Local{Name: "foo"},
},
unknownKeys: []string{"foo", "bar"},
},
false,
},
// Terraform block
{
&Config{
Terraform: &Terraform{
RequiredVersion: "A",
},
},
&Config{},
&Config{
Terraform: &Terraform{
RequiredVersion: "A",
},
},
false,
},
{
&Config{},
&Config{
Terraform: &Terraform{
RequiredVersion: "A",
},
},
&Config{
Terraform: &Terraform{
RequiredVersion: "A",
},
},
false,
},
// Provider alias
{
&Config{
ProviderConfigs: []*ProviderConfig{
&ProviderConfig{Alias: "foo"},
},
},
&Config{},
&Config{
ProviderConfigs: []*ProviderConfig{
&ProviderConfig{Alias: "foo"},
},
},
false,
},
{
&Config{},
&Config{
ProviderConfigs: []*ProviderConfig{
&ProviderConfig{Alias: "foo"},
},
},
&Config{
ProviderConfigs: []*ProviderConfig{
&ProviderConfig{Alias: "foo"},
},
},
false,
},
{
&Config{
ProviderConfigs: []*ProviderConfig{
&ProviderConfig{Alias: "bar"},
},
},
&Config{
ProviderConfigs: []*ProviderConfig{
&ProviderConfig{Alias: "foo"},
},
},
&Config{
ProviderConfigs: []*ProviderConfig{
&ProviderConfig{Alias: "foo"},
},
},
false,
},
// Variable type
{
&Config{
Variables: []*Variable{
&Variable{DeclaredType: "foo"},
},
},
&Config{},
&Config{
Variables: []*Variable{
&Variable{DeclaredType: "foo"},
},
},
false,
},
{
&Config{},
&Config{
Variables: []*Variable{
&Variable{DeclaredType: "foo"},
},
},
&Config{
Variables: []*Variable{
&Variable{DeclaredType: "foo"},
},
},
false,
},
{
&Config{
Variables: []*Variable{
&Variable{DeclaredType: "bar"},
},
},
&Config{
Variables: []*Variable{
&Variable{DeclaredType: "foo"},
},
},
&Config{
Variables: []*Variable{
&Variable{DeclaredType: "foo"},
},
},
false,
},
// Output description
{
&Config{
Outputs: []*Output{
&Output{Description: "foo"},
},
},
&Config{},
&Config{
Outputs: []*Output{
&Output{Description: "foo"},
},
},
false,
},
{
&Config{},
&Config{
Outputs: []*Output{
&Output{Description: "foo"},
},
},
&Config{
Outputs: []*Output{
&Output{Description: "foo"},
},
},
false,
},
{
&Config{
Outputs: []*Output{
&Output{Description: "bar"},
},
},
&Config{
Outputs: []*Output{
&Output{Description: "foo"},
},
},
&Config{
Outputs: []*Output{
&Output{Description: "foo"},
},
},
false,
},
// Output depends_on
{
&Config{
Outputs: []*Output{
&Output{DependsOn: []string{"foo"}},
},
},
&Config{},
&Config{
Outputs: []*Output{
&Output{DependsOn: []string{"foo"}},
},
},
false,
},
{
&Config{},
&Config{
Outputs: []*Output{
&Output{DependsOn: []string{"foo"}},
},
},
&Config{
Outputs: []*Output{
&Output{DependsOn: []string{"foo"}},
},
},
false,
},
{
&Config{
Outputs: []*Output{
&Output{DependsOn: []string{"bar"}},
},
},
&Config{
Outputs: []*Output{
&Output{DependsOn: []string{"foo"}},
},
},
&Config{
Outputs: []*Output{
&Output{DependsOn: []string{"foo"}},
},
},
false,
},
// Output sensitive
{
&Config{
Outputs: []*Output{
&Output{Sensitive: true},
},
},
&Config{},
&Config{
Outputs: []*Output{
&Output{Sensitive: true},
},
},
false,
},
{
&Config{},
&Config{
Outputs: []*Output{
&Output{Sensitive: true},
},
},
&Config{
Outputs: []*Output{
&Output{Sensitive: true},
},
},
false,
},
{
&Config{
Outputs: []*Output{
&Output{Sensitive: false},
},
},
&Config{
Outputs: []*Output{
&Output{Sensitive: true},
},
},
&Config{
Outputs: []*Output{
&Output{Sensitive: true},
},
},
false,
},
// terraform blocks are merged, not overwritten
{
&Config{
Terraform: &Terraform{
RequiredVersion: "A",
},
},
&Config{
Terraform: &Terraform{
Backend: &Backend{
Type: "test",
},
},
},
&Config{
Terraform: &Terraform{
RequiredVersion: "A",
Backend: &Backend{
Type: "test",
},
},
},
false,
},
}
for i, tc := range cases {
t.Run(fmt.Sprintf("%02d", i), func(t *testing.T) {
actual, err := Merge(tc.c1, tc.c2)
if err != nil != tc.err {
t.Errorf("unexpected error: %s", err)
}
if !reflect.DeepEqual(actual, tc.result) {
t.Errorf("wrong result\ngot: %swant: %s", spew.Sdump(actual), spew.Sdump(tc.result))
}
})
}
}

View File

@ -1,61 +0,0 @@
package config
import "github.com/blang/semver"
// ProviderVersionConstraint presents a constraint for a particular
// provider, identified by its full name.
type ProviderVersionConstraint struct {
Constraint string
ProviderType string
}
// ProviderVersionConstraints is a map from provider full name to its associated
// ProviderVersionConstraint, as produced by Config.RequiredProviders.
type ProviderVersionConstraints map[string]ProviderVersionConstraint
// RequiredRanges returns a semver.Range for each distinct provider type in
// the constraint map. If the same provider type appears more than once
// (e.g. because aliases are in use) then their respective constraints are
// combined such that they must *all* apply.
//
// The result of this method can be passed to the
// PluginMetaSet.ConstrainVersions method within the plugin/discovery
// package in order to filter down the available plugins to those which
// satisfy the given constraints.
//
// This function will panic if any of the constraints within cannot be
// parsed as semver ranges. This is guaranteed to never happen for a
// constraint set that was built from a configuration that passed validation.
func (cons ProviderVersionConstraints) RequiredRanges() map[string]semver.Range {
ret := make(map[string]semver.Range, len(cons))
for _, con := range cons {
spec := semver.MustParseRange(con.Constraint)
if existing, exists := ret[con.ProviderType]; exists {
ret[con.ProviderType] = existing.AND(spec)
} else {
ret[con.ProviderType] = spec
}
}
return ret
}
// ProviderConfigsByFullName returns a map from provider full names (as
// returned by ProviderConfig.FullName()) to the corresponding provider
// configs.
//
// This function returns no new information than what's already in
// c.ProviderConfigs, but returns it in a more convenient shape. If there
// is more than one provider config with the same full name then the result
// is undefined, but that is guaranteed not to happen for any config that
// has passed validation.
func (c *Config) ProviderConfigsByFullName() map[string]*ProviderConfig {
ret := make(map[string]*ProviderConfig, len(c.ProviderConfigs))
for _, pc := range c.ProviderConfigs {
ret[pc.FullName()] = pc
}
return ret
}

View File

@ -1,40 +0,0 @@
package config
// ProvisionerWhen is an enum for valid values for when to run provisioners.
type ProvisionerWhen int
const (
ProvisionerWhenInvalid ProvisionerWhen = iota
ProvisionerWhenCreate
ProvisionerWhenDestroy
)
var provisionerWhenStrs = map[ProvisionerWhen]string{
ProvisionerWhenInvalid: "invalid",
ProvisionerWhenCreate: "create",
ProvisionerWhenDestroy: "destroy",
}
func (v ProvisionerWhen) String() string {
return provisionerWhenStrs[v]
}
// ProvisionerOnFailure is an enum for valid values for on_failure options
// for provisioners.
type ProvisionerOnFailure int
const (
ProvisionerOnFailureInvalid ProvisionerOnFailure = iota
ProvisionerOnFailureContinue
ProvisionerOnFailureFail
)
var provisionerOnFailureStrs = map[ProvisionerOnFailure]string{
ProvisionerOnFailureInvalid: "invalid",
ProvisionerOnFailureContinue: "continue",
ProvisionerOnFailureFail: "fail",
}
func (v ProvisionerOnFailure) String() string {
return provisionerOnFailureStrs[v]
}

View File

@ -1,9 +0,0 @@
package config
//go:generate go run golang.org/x/tools/cmd/stringer -type=ResourceMode -output=resource_mode_string.go resource_mode.go
type ResourceMode int
const (
ManagedResourceMode ResourceMode = iota
DataResourceMode
)

View File

@ -1,24 +0,0 @@
// Code generated by "stringer -type=ResourceMode -output=resource_mode_string.go resource_mode.go"; DO NOT EDIT.
package config
import "strconv"
func _() {
// An "invalid array index" compiler error signifies that the constant values have changed.
// Re-run the stringer command to generate them again.
var x [1]struct{}
_ = x[ManagedResourceMode-0]
_ = x[DataResourceMode-1]
}
const _ResourceMode_name = "ManagedResourceModeDataResourceMode"
var _ResourceMode_index = [...]uint8{0, 19, 35}
func (i ResourceMode) String() string {
if i < 0 || i >= ResourceMode(len(_ResourceMode_index)-1) {
return "ResourceMode(" + strconv.FormatInt(int64(i), 10) + ")"
}
return _ResourceMode_name[_ResourceMode_index[i]:_ResourceMode_index[i+1]]
}

View File

@ -1 +0,0 @@
windows-line-endings.tf eol=crlf

View File

@ -1,15 +0,0 @@
provider "cloudstack" {
api_url = "bla"
api_key = "bla"
secret_key = "bla"
}
resource "cloudstack_firewall" "test" {
ipaddress = "192.168.0.1"
rule {
source_cidr = "10.0.0.0/8"
protocol = "tcp"
ports = ["80", "1000-2000"]
}
}

View File

@ -1,27 +0,0 @@
{
"provider": {
"cloudstack": {
"api_url": "bla",
"api_key": "bla",
"secret_key": "bla"
}
},
"resource": {
"cloudstack_firewall": {
"test": {
"ipaddress": "192.168.0.1",
"rule": [
{
"source_cidr": "10.0.0.0/8",
"protocol": "tcp",
"ports": [
"80",
"1000-2000"
]
}
]
}
}
}
}

View File

@ -1,7 +0,0 @@
terraform {
backend "foo" {
foo = "bar"
bar = ["baz"]
map = { a = "b" }
}
}

View File

@ -1 +0,0 @@
terraform {}

View File

@ -1 +0,0 @@
# Empty

View File

@ -1,4 +0,0 @@
terraform {
backend "foo" {
}
}

View File

@ -1,3 +0,0 @@
variable "bad_type" {
type = "notatype"
}

View File

@ -1 +0,0 @@
variable "foo" {}

View File

@ -1,125 +0,0 @@
#terraform:hcl2
terraform {
required_version = "foo"
backend "baz" {
something = "nothing"
}
}
variable "foo" {
default = "bar"
description = "barbar"
}
variable "bar" {
type = "string"
}
variable "baz" {
type = "map"
default = {
key = "value"
}
}
provider "aws" {
access_key = "foo"
secret_key = "bar"
version = "1.0.0"
}
provider "do" {
api_key = var.foo
alias = "fum"
}
data "do" "simple" {
foo = "baz"
provider = "do.foo"
}
data "do" "depends" {
depends_on = ["data.do.simple"]
}
resource "aws_security_group" "firewall" {
count = 5
provider = "another"
}
resource "aws_instance" "web" {
ami = "${var.foo}"
security_groups = [
"foo",
aws_security_group.firewall.foo,
]
network_interface {
device_index = 0
description = "Main network interface"
}
connection {
default = true
}
provisioner "file" {
source = "foo"
destination = "bar"
}
}
locals {
security_group_ids = aws_security_group.firewall.*.id
web_ip = aws_instance.web.private_ip
}
locals {
literal = 2
literal_list = ["foo"]
literal_map = {"foo" = "bar"}
}
resource "aws_instance" "db" {
security_groups = aws_security_group.firewall.*.id
VPC = "foo"
tags = {
Name = "${var.bar}-database"
}
depends_on = ["aws_instance.web"]
provisioner "file" {
source = "here"
destination = "there"
connection {
default = false
}
}
}
output "web_ip" {
value = aws_instance.web.private_ip
sensitive = true
}
output "web_id" {
description = "The ID"
value = aws_instance.web.id
depends_on = ["aws_instance.db"]
}
atlas {
name = "example/foo"
}
module "child" {
source = "./baz"
toasty = true
}

View File

@ -1,95 +0,0 @@
terraform {
required_version = "foo"
}
variable "foo" {
default = "bar"
description = "bar"
}
variable "bar" {
type = "string"
}
variable "baz" {
type = "map"
default = {
key = "value"
}
}
provider "aws" {
access_key = "foo"
secret_key = "bar"
}
provider "do" {
api_key = "${var.foo}"
}
data "do" "simple" {
foo = "baz"
}
data "do" "depends" {
depends_on = ["data.do.simple"]
}
resource "aws_security_group" "firewall" {
count = 5
}
resource aws_instance "web" {
ami = "${var.foo}"
security_groups = [
"foo",
"${aws_security_group.firewall.foo}"
]
network_interface {
device_index = 0
description = "Main network interface"
}
provisioner "file" {
source = "foo"
destination = "bar"
}
}
locals {
security_group_ids = "${aws_security_group.firewall.*.id}"
web_ip = "${aws_instance.web.private_ip}"
}
locals {
literal = 2
literal_list = ["foo"]
literal_map = {"foo" = "bar"}
}
resource "aws_instance" "db" {
security_groups = "${aws_security_group.firewall.*.id}"
VPC = "foo"
depends_on = ["aws_instance.web"]
provisioner "file" {
source = "foo"
destination = "bar"
}
}
output "web_ip" {
value = "${aws_instance.web.private_ip}"
}
output "web_id" {
description = "The ID"
value = "${aws_instance.web.id}"
}
atlas {
name = "mitchellh/foo"
}

View File

@ -1,103 +0,0 @@
{
"variable": {
"foo": {
"default": "bar",
"description": "bar"
},
"bar": {
"type": "string"
},
"baz": {
"type": "map",
"default": {
"key": "value"
}
}
},
"provider": {
"aws": {
"access_key": "foo",
"secret_key": "bar"
},
"do": {
"api_key": "${var.foo}"
}
},
"data": {
"do": {
"simple": {
"foo": "baz"
},
"depends": {
"depends_on": ["data.do.simple"]
}
}
},
"resource": {
"aws_instance": {
"db": {
"security_groups": ["${aws_security_group.firewall.*.id}"],
"VPC": "foo",
"depends_on": ["aws_instance.web"],
"provisioner": [{
"file": {
"source": "foo",
"destination": "bar"
}
}]
},
"web": {
"ami": "${var.foo}",
"security_groups": [
"foo",
"${aws_security_group.firewall.foo}"
],
"network_interface": {
"device_index": 0,
"description": "Main network interface"
},
"provisioner": {
"file": {
"source": "foo",
"destination": "bar"
}
}
}
},
"aws_security_group": {
"firewall": {
"count": 5
}
}
},
"locals": {
"security_group_ids": "${aws_security_group.firewall.*.id}",
"web_ip": "${aws_instance.web.private_ip}",
"literal": 2,
"literal_list": ["foo"],
"literal_map": {"foo": "bar"}
},
"output": {
"web_id": {
"description": "The ID",
"value": "${aws_instance.web.id}"
},
"web_ip": {
"value": "${aws_instance.web.private_ip}"
}
},
"atlas": {
"name": "mitchellh/foo"
}
}

View File

@ -1,23 +0,0 @@
resource "aws_instance" "web" {
ami = "${var.foo}"
security_groups = [
"foo",
"${aws_security_group.firewall.foo}"
]
connection {
type = "ssh"
user = "root"
}
provisioner "shell" {
path = "foo"
connection {
user = "nobody"
}
}
provisioner "shell" {
path = "bar"
}
}

View File

@ -1,19 +0,0 @@
variable "ref" {
default = "foo"
}
resource "foo" "bar" {
depends_on = ["dep"]
provider = "foo-west"
count = 2
attr = "value"
ref = "${var.ref}"
provisioner "shell" {
inline = "echo"
}
lifecycle {
ignore_changes = ["config"]
}
}

View File

@ -1,3 +0,0 @@
resource "foo" "bar" {
count = 5
}

View File

@ -1,3 +0,0 @@
resource "foo" "bar" {
count = "${var.list}"
}

View File

@ -1,3 +0,0 @@
resource "foo" "bar" {
count = "5"
}

View File

@ -1,3 +0,0 @@
resource "foo" "bar" {
count = "${var.foo}"
}

View File

@ -1,14 +0,0 @@
resource "aws_instance" "web" {
ami = "foo"
lifecycle {
create_before_destroy = true
}
}
resource "aws_instance" "bar" {
ami = "foo"
lifecycle {
create_before_destroy = false
}
}

View File

@ -1,3 +0,0 @@
data "foo" "bar" {
count = 5
}

View File

@ -1,3 +0,0 @@
# I forgot the data source name!
data "null" {
}

View File

@ -1,2 +0,0 @@
This file just exists to test that LoadDir doesn't load non-Terraform
files.

View File

@ -1,3 +0,0 @@
output "i-am-nested" {
value = "what"
}

View File

@ -1,21 +0,0 @@
variable "foo" {
default = "bar"
description = "bar"
}
provider "aws" {
access_key = "foo"
secret_key = "bar"
}
data "do" "simple" {
foo = "baz"
}
resource "aws_instance" "db" {
security_groups = "${aws_security_group.firewall.*.id}"
}
output "web_ip" {
value = "${aws_instance.web.private_ip}"
}

View File

@ -1,24 +0,0 @@
provider "do" {
api_key = "${var.foo}"
}
data "do" "depends" {
depends_on = ["data.do.simple"]
}
resource "aws_security_group" "firewall" {
count = 5
}
resource aws_instance "web" {
ami = "${var.foo}"
security_groups = [
"foo",
"${aws_security_group.firewall.foo}"
]
network_interface {
device_index = 0
description = "Main network interface"
}
}

View File

View File

@ -1,8 +0,0 @@
variable "foo" {
default = "bar"
description = "bar"
}
resource "aws_instance" "db" {
security_groups = "${aws_security_group.firewall.*.id}"
}

View File

@ -1,2 +0,0 @@
resource "aws_instance" "db" {
}

View File

@ -1,4 +0,0 @@
variable "foo" {
default = "bar"
description = "bar"
}

View File

@ -1,4 +0,0 @@
variable "foo" {
default = "bar"
description = "bar"
}

View File

@ -1,3 +0,0 @@
variable "foo" {
default = "baz"
}

View File

@ -1,16 +0,0 @@
{
"data": {
"do": {
"depends": {
"hello": "world"
}
}
},
"resource": {
"aws_instance": {
"web": {
"foo": "bar"
}
}
}
}

View File

@ -1,22 +0,0 @@
variable "foo" {
default = "bar"
description = "bar"
}
provider "aws" {
access_key = "foo"
secret_key = "bar"
}
data "do" "simple" {
foo = "baz"
}
resource "aws_instance" "db" {
security_groups = "${aws_security_group.firewall.*.id}"
}
output "web_ip" {
value = "${aws_instance.web.private_ip}"
}

View File

@ -1,10 +0,0 @@
{
"resource": {
"aws_instance": {
"db": {
"ami": "foo",
"security_groups": ""
}
}
}
}

View File

@ -1,24 +0,0 @@
provider "do" {
api_key = "${var.foo}"
}
data "do" "depends" {
depends_on = ["data.do.simple"]
}
resource "aws_security_group" "firewall" {
count = 5
}
resource aws_instance "web" {
ami = "${var.foo}"
security_groups = [
"foo",
"${aws_security_group.firewall.foo}"
]
network_interface {
device_index = 0
description = "Main network interface"
}
}

View File

@ -1,20 +0,0 @@
provider "do" {
api_key = "${var.foo}"
}
resource "aws_security_group" "firewall" {
count = 5
}
resource aws_instance "web" {
ami = "${var.foo}"
security_groups = [
"foo",
"${aws_security_group.firewall.foo}"
]
network_interface {
device_index = 0
description = "Main network interface"
}
}

View File

@ -1,17 +0,0 @@
variable "foo" {
default = "bar"
description = "bar"
}
provider "aws" {
access_key = "foo"
secret_key = "bar"
}
resource "aws_instance" "db" {
security_groups = "${aws_security_group.firewall.*.id}"
}
output "web_ip" {
value = "${aws_instance.web.private_ip}"
}

View File

@ -1,17 +0,0 @@
variable "foo" {
default = "bar"
description = "bar"
}
provider "aws" {
access_key = "foo"
secret_key = "bar"
}
resource "aws_instance" "db" {
security_groups = "${aws_security_group.firewall.*.id}"
}
output "web_ip" {
value = "${aws_instance.web.private_ip}"
}

View File

@ -1,11 +0,0 @@
variable "empty_string" {
default = ""
}
variable "empty_list" {
default = []
}
variable "empty_map" {
default = {}
}

View File

View File

@ -1,7 +0,0 @@
variable "ami" {
default = [ "ami", "abc123" ]
}
resource "aws_instance" "quotes" {
ami = "${join(\",\", var.ami)}"
}

Binary file not shown.

View File

@ -1,5 +0,0 @@
{
"locals": {
"foo": "baz"
}
}

View File

@ -1,7 +0,0 @@
# The use of an equals to assign "locals" is something that would be rejected
# by the HCL2 parser (equals is reserved for attributes only) and so we can
# use it to verify that the old HCL parser was used.
locals {
foo = "bar"
}

View File

@ -1,7 +0,0 @@
#terraform:hcl2
locals {
# This direct expression is something that would be rejected by the old HCL
# parser, so we can use it as a marker that the HCL2 parser was used.
foo = 1 + 2
}

View File

@ -1,51 +0,0 @@
provider "aws" {
access_key = "foo"
secret_key = "bar"
}
resource "aws_iam_policy" "policy" {
name = "test_policy"
path = "/"
description = "My test policy"
policy = <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Action": [
"ec2:Describe*"
],
"Effect": "Allow",
"Resource": "*"
}
]
}
EOF
}
resource "aws_instance" "test" {
ami = "foo"
provisioner "remote-exec" {
inline = [
<<EOT
sudo \
A=val \
B=val2 \
sh script.sh
EOT
]
}
}
resource "aws_instance" "heredocwithnumbers" {
ami = "foo"
provisioner "local-exec" {
command = <<FOO123
echo several
lines
of output
FOO123
}
}

View File

@ -1,17 +0,0 @@
resource "aws_instance" "web" {
ami = "foo"
lifecycle {
ignore_changes = ["ami"]
}
}
resource "aws_instance" "bar" {
ami = "foo"
lifecycle {
ignore_changes = []
}
}
resource "aws_instance" "baz" {
ami = "foo"
}

View File

@ -1,10 +0,0 @@
variable "foo" {
default = "bar"
description = "bar"
}
provider "aws" {
foo = "bar"
}
resource "aws_security_group" "web" {}

View File

@ -1,7 +0,0 @@
variable "bar" {}
provider "aws" {
bar = "baz";
}
resource "aws_security_group" "db" {}

View File

@ -1 +0,0 @@
concat("foo","-","0.0/16")

View File

@ -1,5 +0,0 @@
resource "foo" "bar" {
lifecycle {
create_before_destroyy = false
}
}

View File

@ -1,7 +0,0 @@
module "child" {
source = "./child"
version = "0.1.2"
providers = {
"aws" = "aws.foo"
}
}

View File

@ -1,7 +0,0 @@
module "okay" {
source = "./okay"
}
module {
source = "./not-okay"
}

View File

@ -1,4 +0,0 @@
module "bar" {
memory = "1G"
source = "baz"
}

View File

@ -1,4 +0,0 @@
output "value" {
value = "foo"
depends_on = ["foo"]
}

View File

@ -1,14 +0,0 @@
resource "test_instance" "foo" {
}
output "foo_id" {
value = "${test_instance.foo.id}"
}
resource "test_instance" "bar" {
count = 3
}
output "bar_count" {
value = "${test_instance.bar.count}"
}

View File

@ -1,7 +0,0 @@
output "okay" {
value = "bar"
}
output {
value = "foo"
}

View File

@ -1,24 +0,0 @@
locals {
"count" = 1
}
resource "test_instance" "foo" {
count = "${local.count}"
}
output "foo_id" {
value = "${test_instance.foo.id}"
}
variable "condition" {
default = "true"
}
resource "test_instance" "bar" {
count = "${var.condition ? 1 : 0}"
}
output "bar_id" {
value = "${test_instance.bar.id}"
}

View File

@ -1,10 +0,0 @@
resource "aws_instance" "web" {
ami = "foo"
lifecycle {
prevent_destroy = "true"
}
}
resource "aws_instance" "bar" {
ami = "foo"
}

View File

@ -1,5 +0,0 @@
provider "aws" {
version = "bananas"
a = "a"
b = "b"
}

View File

@ -1,6 +0,0 @@
provider "aws" {
version = "0.0.1"
a = "a"
b = "b"
}

View File

@ -1,14 +0,0 @@
resource "aws_instance" "web" {
provisioner "shell" {}
provisioner "shell" {
path = "foo"
when = "destroy"
}
provisioner "shell" {
path = "foo"
when = "destroy"
on_failure = "continue"
}
}

View File

@ -1,11 +0,0 @@
resource "aws_instance" "web" {
ami = "${var.foo}"
security_groups = [
"foo",
"${aws_security_group.firewall.foo}"
]
provisioner "shell" {
path = "foo"
}
}

View File

@ -1,5 +0,0 @@
# I forgot the resource name!
resource "aws_instance" {
ami = "ami-abc123"
instance_type = "t2.micro"
}

View File

@ -1,4 +0,0 @@
resource "aws_instance" "foo" {
lifecycle {}
lifecycle {}
}

View File

@ -1,11 +0,0 @@
{
"resource" : {
"aws_security_group" : {
"allow_external_http_https" : {
"tags" : {
"Name" : "allow_external_http_https"
}
}
}
}
}

View File

@ -1,9 +0,0 @@
{
"terraform": {
"backend": {
"s3": {
"foo": "bar"
}
}
}
}

View File

@ -1,4 +0,0 @@
terraform {
backend "s3" {}
backend "s4" {}
}

View File

@ -1,5 +0,0 @@
terraform {
backend "s3" {
foo = "bar"
}
}

View File

@ -1,9 +0,0 @@
{
"terraform": [{
"backend": [{
"s3": {
"foo": "bar"
}
}]
}]
}

View File

@ -1,5 +0,0 @@
terraform {
backend "foo" {
key = "${var.var}"
}
}

View File

@ -1,3 +0,0 @@
resource "aws_instance" "web" {
depends_on = ["aws_instance.db"]
}

View File

@ -1,7 +0,0 @@
resource "aws_instance" "web" {
count = 5
}
output "ip" {
value = "${aws_instance.web.id}"
}

View File

@ -1,3 +0,0 @@
terraform {
required_version = "nope"
}

View File

@ -1,14 +0,0 @@
resource "aws_instance" "web" {
provisioner "shell" {}
provisioner "shell" {
path = "foo"
when = "destroy"
}
provisioner "shell" {
path = "foo"
when = "destroy"
on_failure = "continue"
}
}

View File

@ -1,11 +0,0 @@
resource "aws_instance" "foo" {
}
output "no_count_in_output" {
value = "${count.index}"
}
module "no_count_in_module" {
source = "./child"
somevar = "${count.index}"
}

View File

@ -1,3 +0,0 @@
resource "aws_instance" "web" {
count = -1
}

Some files were not shown because too many files have changed in this diff Show More