config: LoadDir loads override files

This commit is contained in:
Mitchell Hashimoto 2014-07-20 17:52:46 -07:00
parent 9d2e83d56d
commit 06cdd4fa42
6 changed files with 203 additions and 10 deletions

View File

@ -3,7 +3,6 @@ package config
import (
"fmt"
"io"
"strings"
)
// 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.
func loadTree(root string) (*importTree, error) {
var f fileLoaderFunc
if strings.HasSuffix(root, ".tf") {
switch ext(root) {
case ".tf":
fallthrough
case ".tf.json":
f = loadFileLibucl
} else if strings.HasSuffix(root, ".tf.json") {
f = loadFileLibucl
} else {
default:
}
if f == nil {
return nil, fmt.Errorf(
"%s: unknown configuration format. Use '.tf' or '.tf.json' extension",
root)

View File

@ -2,7 +2,11 @@ package config
import (
"fmt"
"io"
"os"
"path/filepath"
"sort"
"strings"
)
// Load loads the Terraform configuration from a given file.
@ -30,20 +34,67 @@ func Load(path string) (*Config, error) {
// LoadDir loads all the Terraform configuration files in a single
// directory and merges them together.
func LoadDir(path string) (*Config, error) {
matches, err := filepath.Glob(filepath.Join(path, "*.tf"))
func LoadDir(root string) (*Config, error) {
var files, overrides []string
f, err := os.Open(root)
if err != nil {
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(
"No Terraform configuration files found in directory: %s",
path)
root)
}
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)
if err != nil {
return nil, err
@ -59,5 +110,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
}
// 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 ""
}
}

View File

@ -171,6 +171,37 @@ func TestLoadDir_noMerge(t *testing.T) {
}
}
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))
m := make(map[string]*Output)
@ -503,6 +534,42 @@ foo
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
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 = `
aws
bar

View File

@ -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}"
}

View File

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

View File

@ -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"
}
}