Merge pull request #26 from hashicorp/f-override
Configuration Overrides
This commit is contained in:
commit
3b6ef5d3ac
|
@ -0,0 +1,58 @@
|
||||||
|
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 {
|
||||||
|
unknowns[k] = struct{}{}
|
||||||
|
}
|
||||||
|
for _, k := range c2.unknownKeys {
|
||||||
|
unknowns[k] = struct{}{}
|
||||||
|
}
|
||||||
|
for k, _ := range unknowns {
|
||||||
|
c.unknownKeys = append(c.unknownKeys, k)
|
||||||
|
}
|
||||||
|
|
||||||
|
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...)
|
||||||
|
}
|
||||||
|
|
||||||
|
return c, nil
|
||||||
|
}
|
|
@ -0,0 +1,83 @@
|
||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAppend(t *testing.T) {
|
||||||
|
cases := []struct {
|
||||||
|
c1, c2, result *Config
|
||||||
|
err bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
&Config{
|
||||||
|
Outputs: []*Output{
|
||||||
|
&Output{Name: "foo"},
|
||||||
|
},
|
||||||
|
ProviderConfigs: []*ProviderConfig{
|
||||||
|
&ProviderConfig{Name: "foo"},
|
||||||
|
},
|
||||||
|
Resources: []*Resource{
|
||||||
|
&Resource{Name: "foo"},
|
||||||
|
},
|
||||||
|
Variables: []*Variable{
|
||||||
|
&Variable{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: "bar"},
|
||||||
|
},
|
||||||
|
|
||||||
|
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"},
|
||||||
|
&Variable{Name: "bar"},
|
||||||
|
},
|
||||||
|
|
||||||
|
unknownKeys: []string{"foo", "bar"},
|
||||||
|
},
|
||||||
|
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, tc := range cases {
|
||||||
|
actual, err := Append(tc.c1, tc.c2)
|
||||||
|
if (err != nil) != tc.err {
|
||||||
|
t.Fatalf("%d: error fail", i)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(actual, tc.result) {
|
||||||
|
t.Fatalf("%d: bad:\n\n%#v", i, actual)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -13,10 +13,10 @@ import (
|
||||||
// Config is the configuration that comes from loading a collection
|
// Config is the configuration that comes from loading a collection
|
||||||
// of Terraform templates.
|
// of Terraform templates.
|
||||||
type Config struct {
|
type Config struct {
|
||||||
ProviderConfigs map[string]*ProviderConfig
|
ProviderConfigs []*ProviderConfig
|
||||||
Resources []*Resource
|
Resources []*Resource
|
||||||
Variables map[string]*Variable
|
Variables []*Variable
|
||||||
Outputs map[string]*Output
|
Outputs []*Output
|
||||||
|
|
||||||
// The fields below can be filled in by loaders for validation
|
// The fields below can be filled in by loaders for validation
|
||||||
// purposes.
|
// purposes.
|
||||||
|
@ -28,6 +28,7 @@ type Config struct {
|
||||||
// For example, Terraform needs to set the AWS access keys for the AWS
|
// For example, Terraform needs to set the AWS access keys for the AWS
|
||||||
// resource provider.
|
// resource provider.
|
||||||
type ProviderConfig struct {
|
type ProviderConfig struct {
|
||||||
|
Name string
|
||||||
RawConfig *RawConfig
|
RawConfig *RawConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -51,6 +52,7 @@ type Provisioner struct {
|
||||||
|
|
||||||
// Variable is a variable defined within the configuration.
|
// Variable is a variable defined within the configuration.
|
||||||
type Variable struct {
|
type Variable struct {
|
||||||
|
Name string
|
||||||
Default string
|
Default string
|
||||||
Description string
|
Description string
|
||||||
defaultSet bool
|
defaultSet bool
|
||||||
|
@ -98,9 +100,10 @@ type UserVariable struct {
|
||||||
// ProviderConfigName returns the name of the provider configuration in
|
// ProviderConfigName returns the name of the provider configuration in
|
||||||
// the given mapping that maps to the proper provider configuration
|
// the given mapping that maps to the proper provider configuration
|
||||||
// for this resource.
|
// for this resource.
|
||||||
func ProviderConfigName(t string, pcs map[string]*ProviderConfig) string {
|
func ProviderConfigName(t string, pcs []*ProviderConfig) string {
|
||||||
lk := ""
|
lk := ""
|
||||||
for k, _ := range pcs {
|
for _, v := range pcs {
|
||||||
|
k := v.Name
|
||||||
if strings.HasPrefix(t, k) && len(k) > len(lk) {
|
if strings.HasPrefix(t, k) && len(k) > len(lk) {
|
||||||
lk = k
|
lk = k
|
||||||
}
|
}
|
||||||
|
@ -124,6 +127,10 @@ func (c *Config) Validate() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
vars := c.allVariables()
|
vars := c.allVariables()
|
||||||
|
varMap := make(map[string]*Variable)
|
||||||
|
for _, v := range c.Variables {
|
||||||
|
varMap[v.Name] = v
|
||||||
|
}
|
||||||
|
|
||||||
// Check for references to user variables that do not actually
|
// Check for references to user variables that do not actually
|
||||||
// exist and record those errors.
|
// exist and record those errors.
|
||||||
|
@ -134,7 +141,7 @@ func (c *Config) Validate() error {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, ok := c.Variables[uv.Name]; !ok {
|
if _, ok := varMap[uv.Name]; !ok {
|
||||||
errs = append(errs, fmt.Errorf(
|
errs = append(errs, fmt.Errorf(
|
||||||
"%s: unknown variable referenced: %s",
|
"%s: unknown variable referenced: %s",
|
||||||
source,
|
source,
|
||||||
|
@ -244,6 +251,84 @@ func (c *Config) allVariables() map[string][]InterpolatedVariable {
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (o *Output) mergerName() string {
|
||||||
|
return o.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *Output) mergerMerge(m merger) merger {
|
||||||
|
o2 := m.(*Output)
|
||||||
|
|
||||||
|
result := *o
|
||||||
|
result.Name = o2.Name
|
||||||
|
result.RawConfig = result.RawConfig.merge(o2.RawConfig)
|
||||||
|
|
||||||
|
return &result
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ProviderConfig) mergerName() string {
|
||||||
|
return c.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ProviderConfig) mergerMerge(m merger) merger {
|
||||||
|
c2 := m.(*ProviderConfig)
|
||||||
|
|
||||||
|
result := *c
|
||||||
|
result.Name = c2.Name
|
||||||
|
result.RawConfig = result.RawConfig.merge(c2.RawConfig)
|
||||||
|
|
||||||
|
return &result
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Resource) mergerName() string {
|
||||||
|
return fmt.Sprintf("%s.%s", r.Type, r.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Resource) mergerMerge(m merger) merger {
|
||||||
|
r2 := m.(*Resource)
|
||||||
|
|
||||||
|
result := *r
|
||||||
|
result.Name = r2.Name
|
||||||
|
result.Type = r2.Type
|
||||||
|
result.RawConfig = result.RawConfig.merge(r2.RawConfig)
|
||||||
|
|
||||||
|
if r2.Count > 0 {
|
||||||
|
result.Count = r2.Count
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(r2.Provisioners) > 0 {
|
||||||
|
result.Provisioners = r2.Provisioners
|
||||||
|
}
|
||||||
|
|
||||||
|
return &result
|
||||||
|
}
|
||||||
|
|
||||||
|
// Merge merges two variables to create a new third variable.
|
||||||
|
func (v *Variable) Merge(v2 *Variable) *Variable {
|
||||||
|
// Shallow copy the variable
|
||||||
|
result := *v
|
||||||
|
|
||||||
|
// The names should be the same, but the second name always wins.
|
||||||
|
result.Name = v2.Name
|
||||||
|
|
||||||
|
if v2.defaultSet {
|
||||||
|
result.Default = v2.Default
|
||||||
|
result.defaultSet = true
|
||||||
|
}
|
||||||
|
if v2.Description != "" {
|
||||||
|
result.Description = v2.Description
|
||||||
|
}
|
||||||
|
|
||||||
|
return &result
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *Variable) mergerName() string {
|
||||||
|
return v.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *Variable) mergerMerge(m merger) merger {
|
||||||
|
return v.Merge(m.(*Variable))
|
||||||
|
}
|
||||||
|
|
||||||
// Required tests whether a variable is required or not.
|
// Required tests whether a variable is required or not.
|
||||||
func (v *Variable) Required() bool {
|
func (v *Variable) Required() bool {
|
||||||
return !v.defaultSet
|
return !v.defaultSet
|
||||||
|
|
|
@ -151,11 +151,11 @@ func TestNewUserVariable(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestProviderConfigName(t *testing.T) {
|
func TestProviderConfigName(t *testing.T) {
|
||||||
pcs := map[string]*ProviderConfig{
|
pcs := []*ProviderConfig{
|
||||||
"aw": new(ProviderConfig),
|
&ProviderConfig{Name: "aw"},
|
||||||
"aws": new(ProviderConfig),
|
&ProviderConfig{Name: "aws"},
|
||||||
"a": new(ProviderConfig),
|
&ProviderConfig{Name: "a"},
|
||||||
"gce_": new(ProviderConfig),
|
&ProviderConfig{Name: "gce_"},
|
||||||
}
|
}
|
||||||
|
|
||||||
n := ProviderConfigName("aws_instance", pcs)
|
n := ProviderConfigName("aws_instance", pcs)
|
||||||
|
|
|
@ -3,7 +3,6 @@ package config
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"strings"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// configurable is an interface that must be implemented by any configuration
|
// configurable is an interface that must be implemented by any configuration
|
||||||
|
@ -33,11 +32,15 @@ type fileLoaderFunc func(path string) (configurable, []string, error)
|
||||||
// executes the proper fileLoaderFunc.
|
// executes the proper fileLoaderFunc.
|
||||||
func loadTree(root string) (*importTree, error) {
|
func loadTree(root string) (*importTree, error) {
|
||||||
var f fileLoaderFunc
|
var f fileLoaderFunc
|
||||||
if strings.HasSuffix(root, ".tf") {
|
switch ext(root) {
|
||||||
|
case ".tf":
|
||||||
|
fallthrough
|
||||||
|
case ".tf.json":
|
||||||
f = loadFileLibucl
|
f = loadFileLibucl
|
||||||
} else if strings.HasSuffix(root, ".tf.json") {
|
default:
|
||||||
f = loadFileLibucl
|
}
|
||||||
} else {
|
|
||||||
|
if f == nil {
|
||||||
return nil, fmt.Errorf(
|
return nil, fmt.Errorf(
|
||||||
"%s: unknown configuration format. Use '.tf' or '.tf.json' extension",
|
"%s: unknown configuration format. Use '.tf' or '.tf.json' extension",
|
||||||
root)
|
root)
|
||||||
|
|
|
@ -2,7 +2,11 @@ package config
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Load loads the Terraform configuration from a given file.
|
// Load loads the Terraform configuration from a given file.
|
||||||
|
@ -29,28 +33,82 @@ func Load(path string) (*Config, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// LoadDir loads all the Terraform configuration files in a single
|
// LoadDir loads all the Terraform configuration files in a single
|
||||||
// directory and merges them together.
|
// directory and appends them together.
|
||||||
func LoadDir(path string) (*Config, error) {
|
//
|
||||||
matches, err := filepath.Glob(filepath.Join(path, "*.tf"))
|
// 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) {
|
||||||
|
var files, overrides []string
|
||||||
|
|
||||||
|
f, err := os.Open(root)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(matches) == 0 {
|
err = nil
|
||||||
|
for err != io.EOF {
|
||||||
|
var fis []os.FileInfo
|
||||||
|
fis, err = f.Readdir(128)
|
||||||
|
if err != nil && err != io.EOF {
|
||||||
|
f.Close()
|
||||||
|
return 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 == "" {
|
||||||
|
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(root, name)
|
||||||
|
if override {
|
||||||
|
overrides = append(overrides, path)
|
||||||
|
} else {
|
||||||
|
files = append(files, path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close the directory, we're done with it
|
||||||
|
f.Close()
|
||||||
|
|
||||||
|
if len(files) == 0 {
|
||||||
return nil, fmt.Errorf(
|
return nil, fmt.Errorf(
|
||||||
"No Terraform configuration files found in directory: %s",
|
"No Terraform configuration files found in directory: %s",
|
||||||
path)
|
root)
|
||||||
}
|
}
|
||||||
|
|
||||||
var result *Config
|
var result *Config
|
||||||
for _, f := range matches {
|
|
||||||
|
// 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 := Load(f)
|
c, err := Load(f)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if result != nil {
|
if result != nil {
|
||||||
result, err = Merge(result, c)
|
result, err = Append(result, c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -59,5 +117,30 @@ func LoadDir(path string) (*Config, error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Load all the overrides, and merge them into the config
|
||||||
|
for _, f := range overrides {
|
||||||
|
c, err := Load(f)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err = Merge(result, c)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return result, nil
|
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 ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -45,21 +45,26 @@ func (t *libuclConfigurable) Config() (*Config, error) {
|
||||||
|
|
||||||
// Start building up the actual configuration. We start with
|
// Start building up the actual configuration. We start with
|
||||||
// variables.
|
// variables.
|
||||||
|
// TODO(mitchellh): Make function like loadVariablesLibucl so that
|
||||||
|
// duplicates aren't overriden
|
||||||
config := new(Config)
|
config := new(Config)
|
||||||
config.Variables = make(map[string]*Variable)
|
if len(rawConfig.Variable) > 0 {
|
||||||
for k, v := range rawConfig.Variable {
|
config.Variables = make([]*Variable, 0, len(rawConfig.Variable))
|
||||||
defaultSet := false
|
for k, v := range rawConfig.Variable {
|
||||||
for _, f := range v.Fields {
|
defaultSet := false
|
||||||
if f == "Default" {
|
for _, f := range v.Fields {
|
||||||
defaultSet = true
|
if f == "Default" {
|
||||||
break
|
defaultSet = true
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
config.Variables[k] = &Variable{
|
config.Variables = append(config.Variables, &Variable{
|
||||||
Default: v.Default,
|
Name: k,
|
||||||
Description: v.Description,
|
Default: v.Default,
|
||||||
defaultSet: defaultSet,
|
Description: v.Description,
|
||||||
|
defaultSet: defaultSet,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -178,7 +183,7 @@ func loadFileLibucl(root string) (configurable, []string, error) {
|
||||||
|
|
||||||
// LoadOutputsLibucl recurses into the given libucl object and turns
|
// LoadOutputsLibucl recurses into the given libucl object and turns
|
||||||
// it into a mapping of outputs.
|
// it into a mapping of outputs.
|
||||||
func loadOutputsLibucl(o *libucl.Object) (map[string]*Output, error) {
|
func loadOutputsLibucl(o *libucl.Object) ([]*Output, error) {
|
||||||
objects := make(map[string]*libucl.Object)
|
objects := make(map[string]*libucl.Object)
|
||||||
|
|
||||||
// Iterate over all the "output" blocks and get the keys along with
|
// Iterate over all the "output" blocks and get the keys along with
|
||||||
|
@ -196,8 +201,13 @@ func loadOutputsLibucl(o *libucl.Object) (map[string]*Output, error) {
|
||||||
}
|
}
|
||||||
iter.Close()
|
iter.Close()
|
||||||
|
|
||||||
|
// If we have none, just return nil
|
||||||
|
if len(objects) == 0 {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
// Go through each object and turn it into an actual result.
|
// Go through each object and turn it into an actual result.
|
||||||
result := make(map[string]*Output)
|
result := make([]*Output, 0, len(objects))
|
||||||
for n, o := range objects {
|
for n, o := range objects {
|
||||||
var config map[string]interface{}
|
var config map[string]interface{}
|
||||||
|
|
||||||
|
@ -213,10 +223,10 @@ func loadOutputsLibucl(o *libucl.Object) (map[string]*Output, error) {
|
||||||
err)
|
err)
|
||||||
}
|
}
|
||||||
|
|
||||||
result[n] = &Output{
|
result = append(result, &Output{
|
||||||
Name: n,
|
Name: n,
|
||||||
RawConfig: rawConfig,
|
RawConfig: rawConfig,
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return result, nil
|
return result, nil
|
||||||
|
@ -224,7 +234,7 @@ func loadOutputsLibucl(o *libucl.Object) (map[string]*Output, error) {
|
||||||
|
|
||||||
// LoadProvidersLibucl recurses into the given libucl object and turns
|
// LoadProvidersLibucl recurses into the given libucl object and turns
|
||||||
// it into a mapping of provider configs.
|
// it into a mapping of provider configs.
|
||||||
func loadProvidersLibucl(o *libucl.Object) (map[string]*ProviderConfig, error) {
|
func loadProvidersLibucl(o *libucl.Object) ([]*ProviderConfig, error) {
|
||||||
objects := make(map[string]*libucl.Object)
|
objects := make(map[string]*libucl.Object)
|
||||||
|
|
||||||
// Iterate over all the "provider" blocks and get the keys along with
|
// Iterate over all the "provider" blocks and get the keys along with
|
||||||
|
@ -242,8 +252,12 @@ func loadProvidersLibucl(o *libucl.Object) (map[string]*ProviderConfig, error) {
|
||||||
}
|
}
|
||||||
iter.Close()
|
iter.Close()
|
||||||
|
|
||||||
|
if len(objects) == 0 {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
// Go through each object and turn it into an actual result.
|
// Go through each object and turn it into an actual result.
|
||||||
result := make(map[string]*ProviderConfig)
|
result := make([]*ProviderConfig, 0, len(objects))
|
||||||
for n, o := range objects {
|
for n, o := range objects {
|
||||||
var config map[string]interface{}
|
var config map[string]interface{}
|
||||||
|
|
||||||
|
@ -259,9 +273,10 @@ func loadProvidersLibucl(o *libucl.Object) (map[string]*ProviderConfig, error) {
|
||||||
err)
|
err)
|
||||||
}
|
}
|
||||||
|
|
||||||
result[n] = &ProviderConfig{
|
result = append(result, &ProviderConfig{
|
||||||
|
Name: n,
|
||||||
RawConfig: rawConfig,
|
RawConfig: rawConfig,
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return result, nil
|
return result, nil
|
||||||
|
|
|
@ -116,16 +116,6 @@ func TestLoad_variables(t *testing.T) {
|
||||||
if actual != strings.TrimSpace(variablesVariablesStr) {
|
if actual != strings.TrimSpace(variablesVariablesStr) {
|
||||||
t.Fatalf("bad:\n%s", actual)
|
t.Fatalf("bad:\n%s", actual)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !c.Variables["foo"].Required() {
|
|
||||||
t.Fatal("foo should be required")
|
|
||||||
}
|
|
||||||
if c.Variables["bar"].Required() {
|
|
||||||
t.Fatal("bar should not be required")
|
|
||||||
}
|
|
||||||
if c.Variables["baz"].Required() {
|
|
||||||
t.Fatal("baz should not be required")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLoadDir_basic(t *testing.T) {
|
func TestLoadDir_basic(t *testing.T) {
|
||||||
|
@ -166,16 +156,64 @@ func TestLoadDir_noConfigs(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func outputsStr(os map[string]*Output) string {
|
func TestLoadDir_noMerge(t *testing.T) {
|
||||||
|
c, err := LoadDir(filepath.Join(fixtureDir, "dir-merge"))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if c == nil {
|
||||||
|
t.Fatal("config should not be nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := c.Validate(); err == nil {
|
||||||
|
t.Fatal("should not be valid")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLoadDir_override(t *testing.T) {
|
||||||
|
c, err := LoadDir(filepath.Join(fixtureDir, "dir-override"))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if c == nil {
|
||||||
|
t.Fatal("config should not be nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
actual := variablesStr(c.Variables)
|
||||||
|
if actual != strings.TrimSpace(dirOverrideVariablesStr) {
|
||||||
|
t.Fatalf("bad:\n%s", actual)
|
||||||
|
}
|
||||||
|
|
||||||
|
actual = providerConfigsStr(c.ProviderConfigs)
|
||||||
|
if actual != strings.TrimSpace(dirOverrideProvidersStr) {
|
||||||
|
t.Fatalf("bad:\n%s", actual)
|
||||||
|
}
|
||||||
|
|
||||||
|
actual = resourcesStr(c.Resources)
|
||||||
|
if actual != strings.TrimSpace(dirOverrideResourcesStr) {
|
||||||
|
t.Fatalf("bad:\n%s", actual)
|
||||||
|
}
|
||||||
|
|
||||||
|
actual = outputsStr(c.Outputs)
|
||||||
|
if actual != strings.TrimSpace(dirOverrideOutputsStr) {
|
||||||
|
t.Fatalf("bad:\n%s", actual)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func outputsStr(os []*Output) string {
|
||||||
ns := make([]string, 0, len(os))
|
ns := make([]string, 0, len(os))
|
||||||
for n, _ := range os {
|
m := make(map[string]*Output)
|
||||||
ns = append(ns, n)
|
for _, o := range os {
|
||||||
|
ns = append(ns, o.Name)
|
||||||
|
m[o.Name] = o
|
||||||
}
|
}
|
||||||
sort.Strings(ns)
|
sort.Strings(ns)
|
||||||
|
|
||||||
result := ""
|
result := ""
|
||||||
for _, n := range ns {
|
for _, n := range ns {
|
||||||
o := os[n]
|
o := m[n]
|
||||||
|
|
||||||
result += fmt.Sprintf("%s\n", n)
|
result += fmt.Sprintf("%s\n", n)
|
||||||
|
|
||||||
|
@ -256,17 +294,19 @@ func TestLoad_connections(t *testing.T) {
|
||||||
|
|
||||||
// This helper turns a provider configs field into a deterministic
|
// This helper turns a provider configs field into a deterministic
|
||||||
// string value for comparison in tests.
|
// string value for comparison in tests.
|
||||||
func providerConfigsStr(pcs map[string]*ProviderConfig) string {
|
func providerConfigsStr(pcs []*ProviderConfig) string {
|
||||||
result := ""
|
result := ""
|
||||||
|
|
||||||
ns := make([]string, 0, len(pcs))
|
ns := make([]string, 0, len(pcs))
|
||||||
for n, _ := range pcs {
|
m := make(map[string]*ProviderConfig)
|
||||||
ns = append(ns, n)
|
for _, n := range pcs {
|
||||||
|
ns = append(ns, n.Name)
|
||||||
|
m[n.Name] = n
|
||||||
}
|
}
|
||||||
sort.Strings(ns)
|
sort.Strings(ns)
|
||||||
|
|
||||||
for _, n := range ns {
|
for _, n := range ns {
|
||||||
pc := pcs[n]
|
pc := m[n]
|
||||||
|
|
||||||
result += fmt.Sprintf("%s\n", n)
|
result += fmt.Sprintf("%s\n", n)
|
||||||
|
|
||||||
|
@ -384,16 +424,18 @@ func resourcesStr(rs []*Resource) string {
|
||||||
|
|
||||||
// This helper turns a variables field into a deterministic
|
// This helper turns a variables field into a deterministic
|
||||||
// string value for comparison in tests.
|
// string value for comparison in tests.
|
||||||
func variablesStr(vs map[string]*Variable) string {
|
func variablesStr(vs []*Variable) string {
|
||||||
result := ""
|
result := ""
|
||||||
ks := make([]string, 0, len(vs))
|
ks := make([]string, 0, len(vs))
|
||||||
for k, _ := range vs {
|
m := make(map[string]*Variable)
|
||||||
ks = append(ks, k)
|
for _, v := range vs {
|
||||||
|
ks = append(ks, v.Name)
|
||||||
|
m[v.Name] = v
|
||||||
}
|
}
|
||||||
sort.Strings(ks)
|
sort.Strings(ks)
|
||||||
|
|
||||||
for _, k := range ks {
|
for _, k := range ks {
|
||||||
v := vs[k]
|
v := m[k]
|
||||||
|
|
||||||
if v.Default == "" {
|
if v.Default == "" {
|
||||||
v.Default = "<>"
|
v.Default = "<>"
|
||||||
|
@ -402,9 +444,15 @@ func variablesStr(vs map[string]*Variable) string {
|
||||||
v.Description = "<>"
|
v.Description = "<>"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
required := ""
|
||||||
|
if v.Required() {
|
||||||
|
required = " (required)"
|
||||||
|
}
|
||||||
|
|
||||||
result += fmt.Sprintf(
|
result += fmt.Sprintf(
|
||||||
"%s\n %s\n %s\n",
|
"%s%s\n %s\n %s\n",
|
||||||
k,
|
k,
|
||||||
|
required,
|
||||||
v.Default,
|
v.Default,
|
||||||
v.Description)
|
v.Description)
|
||||||
}
|
}
|
||||||
|
@ -486,8 +534,46 @@ foo
|
||||||
bar
|
bar
|
||||||
`
|
`
|
||||||
|
|
||||||
|
const dirOverrideOutputsStr = `
|
||||||
|
web_ip
|
||||||
|
vars
|
||||||
|
resource: aws_instance.web.private_ip
|
||||||
|
`
|
||||||
|
|
||||||
|
const dirOverrideProvidersStr = `
|
||||||
|
aws
|
||||||
|
access_key
|
||||||
|
secret_key
|
||||||
|
do
|
||||||
|
api_key
|
||||||
|
vars
|
||||||
|
user: var.foo
|
||||||
|
`
|
||||||
|
|
||||||
|
const dirOverrideResourcesStr = `
|
||||||
|
aws_instance[db] (x1)
|
||||||
|
ami
|
||||||
|
security_groups
|
||||||
|
aws_instance[web] (x1)
|
||||||
|
ami
|
||||||
|
foo
|
||||||
|
network_interface
|
||||||
|
security_groups
|
||||||
|
vars
|
||||||
|
resource: aws_security_group.firewall.foo
|
||||||
|
user: var.foo
|
||||||
|
aws_security_group[firewall] (x5)
|
||||||
|
`
|
||||||
|
|
||||||
|
const dirOverrideVariablesStr = `
|
||||||
|
foo
|
||||||
|
bar
|
||||||
|
bar
|
||||||
|
`
|
||||||
|
|
||||||
const importProvidersStr = `
|
const importProvidersStr = `
|
||||||
aws
|
aws
|
||||||
|
bar
|
||||||
foo
|
foo
|
||||||
`
|
`
|
||||||
|
|
||||||
|
@ -497,7 +583,7 @@ aws_security_group[web] (x1)
|
||||||
`
|
`
|
||||||
|
|
||||||
const importVariablesStr = `
|
const importVariablesStr = `
|
||||||
bar
|
bar (required)
|
||||||
<>
|
<>
|
||||||
<>
|
<>
|
||||||
foo
|
foo
|
||||||
|
@ -538,7 +624,7 @@ bar
|
||||||
baz
|
baz
|
||||||
foo
|
foo
|
||||||
<>
|
<>
|
||||||
foo
|
foo (required)
|
||||||
<>
|
<>
|
||||||
<>
|
<>
|
||||||
`
|
`
|
||||||
|
|
162
config/merge.go
162
config/merge.go
|
@ -1,9 +1,5 @@
|
||||||
package config
|
package config
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Merge merges two configurations into a single configuration.
|
// Merge merges two configurations into a single configuration.
|
||||||
//
|
//
|
||||||
// Merge allows for the two configurations to have duplicate resources,
|
// Merge allows for the two configurations to have duplicate resources,
|
||||||
|
@ -24,59 +20,135 @@ func Merge(c1, c2 *Config) (*Config, error) {
|
||||||
c.unknownKeys = append(c.unknownKeys, k)
|
c.unknownKeys = append(c.unknownKeys, k)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Merge variables: Variable merging is quite simple. Set fields in
|
// NOTE: Everything below is pretty gross. Due to the lack of generics
|
||||||
// later set variables override those earlier.
|
// in Go, there is some hoop-jumping involved to make this merging a
|
||||||
c.Variables = c1.Variables
|
// little more test-friendly and less repetitive. Ironically, making it
|
||||||
for k, v2 := range c2.Variables {
|
// less repetitive involves being a little repetitive, but I prefer to
|
||||||
v1, ok := c.Variables[k]
|
// be repetitive with things that are less error prone than things that
|
||||||
if ok {
|
// are more error prone (more logic). Type conversions to an interface
|
||||||
if v2.Default == "" {
|
// are pretty low-error.
|
||||||
v2.Default = v1.Default
|
|
||||||
}
|
var m1, m2, mresult []merger
|
||||||
if v2.Description == "" {
|
|
||||||
v2.Description = v1.Description
|
// 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)
|
||||||
}
|
}
|
||||||
|
|
||||||
c.Variables[k] = v2
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Merge outputs: If they collide, just take the latest one for now. In
|
// Provider Configs
|
||||||
// the future, we might provide smarter merge functionality.
|
m1 = make([]merger, 0, len(c1.ProviderConfigs))
|
||||||
c.Outputs = make(map[string]*Output)
|
m2 = make([]merger, 0, len(c2.ProviderConfigs))
|
||||||
for k, v := range c1.Outputs {
|
for _, v := range c1.ProviderConfigs {
|
||||||
c.Outputs[k] = v
|
m1 = append(m1, v)
|
||||||
}
|
}
|
||||||
for k, v := range c2.Outputs {
|
for _, v := range c2.ProviderConfigs {
|
||||||
c.Outputs[k] = v
|
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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Merge provider configs: If they collide, we just take the latest one
|
// Resources
|
||||||
// for now. In the future, we might provide smarter merge functionality.
|
m1 = make([]merger, 0, len(c1.Resources))
|
||||||
c.ProviderConfigs = make(map[string]*ProviderConfig)
|
m2 = make([]merger, 0, len(c2.Resources))
|
||||||
for k, v := range c1.ProviderConfigs {
|
for _, v := range c1.Resources {
|
||||||
c.ProviderConfigs[k] = v
|
m1 = append(m1, v)
|
||||||
}
|
}
|
||||||
for k, v := range c2.ProviderConfigs {
|
for _, v := range c2.Resources {
|
||||||
c.ProviderConfigs[k] = v
|
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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Merge resources: If they collide, we just take the latest one
|
// Variables
|
||||||
// for now. In the future, we might provide smarter merge functionality.
|
m1 = make([]merger, 0, len(c1.Variables))
|
||||||
resources := make(map[string]*Resource)
|
m2 = make([]merger, 0, len(c2.Variables))
|
||||||
for _, r := range c1.Resources {
|
for _, v := range c1.Variables {
|
||||||
id := fmt.Sprintf("%s[%s]", r.Type, r.Name)
|
m1 = append(m1, v)
|
||||||
resources[id] = r
|
|
||||||
}
|
}
|
||||||
for _, r := range c2.Resources {
|
for _, v := range c2.Variables {
|
||||||
id := fmt.Sprintf("%s[%s]", r.Type, r.Name)
|
m2 = append(m2, v)
|
||||||
resources[id] = r
|
|
||||||
}
|
}
|
||||||
|
mresult = mergeSlice(m1, m2)
|
||||||
c.Resources = make([]*Resource, 0, len(resources))
|
if len(mresult) > 0 {
|
||||||
for _, r := range resources {
|
c.Variables = make([]*Variable, len(mresult))
|
||||||
c.Resources = append(c.Resources, r)
|
for i, v := range mresult {
|
||||||
|
c.Variables[i] = v.(*Variable)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return c, nil
|
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
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,149 @@
|
||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMerge(t *testing.T) {
|
||||||
|
cases := []struct {
|
||||||
|
c1, c2, result *Config
|
||||||
|
err bool
|
||||||
|
}{
|
||||||
|
// Normal good case.
|
||||||
|
{
|
||||||
|
&Config{
|
||||||
|
Outputs: []*Output{
|
||||||
|
&Output{Name: "foo"},
|
||||||
|
},
|
||||||
|
ProviderConfigs: []*ProviderConfig{
|
||||||
|
&ProviderConfig{Name: "foo"},
|
||||||
|
},
|
||||||
|
Resources: []*Resource{
|
||||||
|
&Resource{Name: "foo"},
|
||||||
|
},
|
||||||
|
Variables: []*Variable{
|
||||||
|
&Variable{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: "bar"},
|
||||||
|
},
|
||||||
|
|
||||||
|
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"},
|
||||||
|
&Variable{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"},
|
||||||
|
},
|
||||||
|
|
||||||
|
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", defaultSet: true},
|
||||||
|
&Variable{Name: "bar"},
|
||||||
|
},
|
||||||
|
|
||||||
|
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", defaultSet: true},
|
||||||
|
&Variable{Name: "foo"},
|
||||||
|
&Variable{Name: "bar"},
|
||||||
|
},
|
||||||
|
|
||||||
|
unknownKeys: []string{"foo", "bar"},
|
||||||
|
},
|
||||||
|
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, tc := range cases {
|
||||||
|
actual, err := Merge(tc.c1, tc.c2)
|
||||||
|
if (err != nil) != tc.err {
|
||||||
|
t.Fatalf("%d: error fail", i)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(actual, tc.result) {
|
||||||
|
t.Fatalf("%d: bad:\n\n%#v", i, actual)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -92,6 +92,25 @@ func (r *RawConfig) init() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *RawConfig) merge(r2 *RawConfig) *RawConfig {
|
||||||
|
rawRaw, err := copystructure.Copy(r.Raw)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
raw := rawRaw.(map[string]interface{})
|
||||||
|
for k, v := range r2.Raw {
|
||||||
|
raw[k] = v
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err := NewRawConfig(raw)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
// UnknownKeys returns the keys of the configuration that are unknown
|
// UnknownKeys returns the keys of the configuration that are unknown
|
||||||
// because they had interpolated variables that must be computed.
|
// because they had interpolated variables that must be computed.
|
||||||
func (r *RawConfig) UnknownKeys() []string {
|
func (r *RawConfig) UnknownKeys() []string {
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
variable "foo" {
|
||||||
|
default = "bar";
|
||||||
|
description = "bar";
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_instance" "db" {
|
||||||
|
security_groups = "${aws_security_group.firewall.*.id}"
|
||||||
|
}
|
|
@ -0,0 +1,2 @@
|
||||||
|
resource "aws_instance" "db" {
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
{
|
||||||
|
"resource": {
|
||||||
|
"aws_instance": {
|
||||||
|
"web": {
|
||||||
|
"foo": "bar",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
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}"
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
{
|
||||||
|
"resource": {
|
||||||
|
"aws_instance": {
|
||||||
|
"db": {
|
||||||
|
"ami": "foo",
|
||||||
|
"security_groups": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
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"
|
||||||
|
}
|
||||||
|
}
|
|
@ -462,7 +462,8 @@ func graphAddProviderConfigs(g *depgraph.Graph, c *config.Config) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Look up the provider config for this resource
|
// Look up the provider config for this resource
|
||||||
pcName := config.ProviderConfigName(resourceNode.Type, c.ProviderConfigs)
|
pcName := config.ProviderConfigName(
|
||||||
|
resourceNode.Type, c.ProviderConfigs)
|
||||||
if pcName == "" {
|
if pcName == "" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
@ -470,11 +471,22 @@ func graphAddProviderConfigs(g *depgraph.Graph, c *config.Config) {
|
||||||
// We have one, so build the noun if it hasn't already been made
|
// We have one, so build the noun if it hasn't already been made
|
||||||
pcNoun, ok := pcNouns[pcName]
|
pcNoun, ok := pcNouns[pcName]
|
||||||
if !ok {
|
if !ok {
|
||||||
|
var pc *config.ProviderConfig
|
||||||
|
for _, v := range c.ProviderConfigs {
|
||||||
|
if v.Name == pcName {
|
||||||
|
pc = v
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if pc == nil {
|
||||||
|
panic("pc not found")
|
||||||
|
}
|
||||||
|
|
||||||
pcNoun = &depgraph.Noun{
|
pcNoun = &depgraph.Noun{
|
||||||
Name: fmt.Sprintf("provider.%s", pcName),
|
Name: fmt.Sprintf("provider.%s", pcName),
|
||||||
Meta: &GraphNodeResourceProvider{
|
Meta: &GraphNodeResourceProvider{
|
||||||
ID: pcName,
|
ID: pcName,
|
||||||
Config: c.ProviderConfigs[pcName],
|
Config: pc,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
pcNouns[pcName] = pcNoun
|
pcNouns[pcName] = pcNoun
|
||||||
|
|
|
@ -53,6 +53,8 @@ func TestReadWritePlan(t *testing.T) {
|
||||||
t.Fatalf("err: %s", err)
|
t.Fatalf("err: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
println(reflect.DeepEqual(actual.Config.Variables, plan.Config.Variables))
|
||||||
|
|
||||||
if !reflect.DeepEqual(actual, plan) {
|
if !reflect.DeepEqual(actual, plan) {
|
||||||
t.Fatalf("bad: %#v", actual)
|
t.Fatalf("bad: %#v", actual)
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,9 +13,9 @@ func smcUserVariables(c *config.Config, vs map[string]string) []error {
|
||||||
|
|
||||||
// Check that all required variables are present
|
// Check that all required variables are present
|
||||||
required := make(map[string]struct{})
|
required := make(map[string]struct{})
|
||||||
for k, v := range c.Variables {
|
for _, v := range c.Variables {
|
||||||
if v.Required() {
|
if v.Required() {
|
||||||
required[k] = struct{}{}
|
required[v.Name] = struct{}{}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for k, _ := range vs {
|
for k, _ := range vs {
|
||||||
|
|
Loading…
Reference in New Issue