config: LoadDir loads override files
This commit is contained in:
parent
9d2e83d56d
commit
06cdd4fa42
|
@ -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.
|
||||||
|
@ -30,20 +34,67 @@ 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 merges them together.
|
||||||
func LoadDir(path string) (*Config, error) {
|
func LoadDir(root string) (*Config, error) {
|
||||||
matches, err := filepath.Glob(filepath.Join(path, "*.tf"))
|
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
|
||||||
|
@ -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
|
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 ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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 {
|
func outputsStr(os []*Output) string {
|
||||||
ns := make([]string, 0, len(os))
|
ns := make([]string, 0, len(os))
|
||||||
m := make(map[string]*Output)
|
m := make(map[string]*Output)
|
||||||
|
@ -503,6 +534,42 @@ 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
|
||||||
|
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
|
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"
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue