From ab507814b762f79ec2089ae1930202dbcfaaac9e Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sun, 25 May 2014 18:05:18 -0700 Subject: [PATCH] config: support for provider configs --- config/config.go | 14 ++++++-- config/config_tree.go | 10 ++++++ config/loader_libucl.go | 57 ++++++++++++++++++++++++++++++ config/loader_test.go | 57 ++++++++++++++++++++++++++++++ config/test-fixtures/basic.tf | 9 +++++ config/test-fixtures/import.tf | 4 +++ config/test-fixtures/import/one.tf | 4 +++ 7 files changed, 153 insertions(+), 2 deletions(-) diff --git a/config/config.go b/config/config.go index b4ada4ed16..3d21d891cf 100644 --- a/config/config.go +++ b/config/config.go @@ -12,8 +12,18 @@ import ( // Config is the configuration that comes from loading a collection // of Terraform templates. type Config struct { - Variables map[string]Variable - Resources []*Resource + ProviderConfigs map[string]*ProviderConfig + Resources []*Resource + Variables map[string]Variable +} + +// ProviderConfig is the configuration for a resource provider. +// +// For example, Terraform needs to set the AWS access keys for the AWS +// resource provider. +type ProviderConfig struct { + Config map[string]interface{} + Variables map[string]InterpolatedVariable } // A resource represents a single Terraform resource in the configuration. diff --git a/config/config_tree.go b/config/config_tree.go index 569ceb1254..1b3daf52c2 100644 --- a/config/config_tree.go +++ b/config/config_tree.go @@ -66,6 +66,16 @@ func mergeConfig(c1, c2 *Config) (*Config, error) { c.Variables[k] = v2 } + // Merge provider configs: If they collide, we just take the latest one + // for now. In the future, we might provide smarter merge functionality. + c.ProviderConfigs = make(map[string]*ProviderConfig) + for k, v := range c1.ProviderConfigs { + c.ProviderConfigs[k] = v + } + for k, v := range c2.ProviderConfigs { + c.ProviderConfigs[k] = v + } + // Merge resources: If they collide, we just take the latest one // for now. In the future, we might provide smarter merge functionality. resources := make(map[string]*Resource) diff --git a/config/loader_libucl.go b/config/loader_libucl.go index dcd5be336d..08d9e5e533 100644 --- a/config/loader_libucl.go +++ b/config/loader_libucl.go @@ -36,6 +36,17 @@ func (t *libuclConfigurable) Config() (*Config, error) { config := new(Config) config.Variables = rawConfig.Variable + // Build the provider configs + providers := t.Object.Get("provider") + if providers != nil { + var err error + config.ProviderConfigs, err = loadProvidersLibucl(providers) + providers.Close() + if err != nil { + return nil, err + } + } + // Build the resources resources := t.Object.Get("resource") if resources != nil { @@ -114,6 +125,52 @@ func loadFileLibucl(root string) (configurable, []string, error) { return result, importPaths, nil } +// LoadProvidersLibucl recurses into the given libucl object and turns +// it into a mapping of provider configs. +func loadProvidersLibucl(o *libucl.Object) (map[string]*ProviderConfig, error) { + objects := make(map[string]*libucl.Object) + + // Iterate over all the "provider" blocks and get the keys along with + // their raw configuration objects. We'll parse those later. + iter := o.Iterate(false) + for o1 := iter.Next(); o1 != nil; o1 = iter.Next() { + iter2 := o1.Iterate(true) + for o2 := iter2.Next(); o2 != nil; o2 = iter2.Next() { + objects[o2.Key()] = o2 + defer o2.Close() + } + + o1.Close() + iter2.Close() + } + iter.Close() + + // Go through each object and turn it into an actual result. + result := make(map[string]*ProviderConfig) + for n, o := range objects { + var config map[string]interface{} + + if err := o.Decode(&config); err != nil { + return nil, err + } + + walker := new(variableDetectWalker) + if err := reflectwalk.Walk(config, walker); err != nil { + return nil, fmt.Errorf( + "Error reading config for provider config %s: %s", + n, + err) + } + + result[n] = &ProviderConfig{ + Config: config, + Variables: walker.Variables, + } + } + + return result, nil +} + // Given a handle to a libucl object, this recurses into the structure // and pulls out a list of resources. // diff --git a/config/loader_test.go b/config/loader_test.go index 5a7886e5d3..e28d72c91d 100644 --- a/config/loader_test.go +++ b/config/loader_test.go @@ -29,6 +29,11 @@ func TestLoadBasic(t *testing.T) { t.Fatalf("bad:\n%s", actual) } + actual = providerConfigsStr(c.ProviderConfigs) + if actual != strings.TrimSpace(basicProvidersStr) { + t.Fatalf("bad:\n%s", actual) + } + actual = resourcesStr(c.Resources) if actual != strings.TrimSpace(basicResourcesStr) { t.Fatalf("bad:\n%s", actual) @@ -50,12 +55,49 @@ func TestLoadBasic_import(t *testing.T) { t.Fatalf("bad:\n%s", actual) } + actual = providerConfigsStr(c.ProviderConfigs) + if actual != strings.TrimSpace(importProvidersStr) { + t.Fatalf("bad:\n%s", actual) + } + actual = resourcesStr(c.Resources) if actual != strings.TrimSpace(importResourcesStr) { t.Fatalf("bad:\n%s", actual) } } +// This helper turns a provider configs field into a deterministic +// string value for comparison in tests. +func providerConfigsStr(pcs map[string]*ProviderConfig) string { + result := "" + for n, pc := range pcs { + result += fmt.Sprintf("%s\n", n) + + for k, _ := range pc.Config { + result += fmt.Sprintf(" %s\n", k) + } + + if len(pc.Variables) > 0 { + result += fmt.Sprintf(" vars\n") + for _, rawV := range pc.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 { @@ -113,6 +155,16 @@ func variablesStr(vs map[string]Variable) string { return strings.TrimSpace(result) } +const basicProvidersStr = ` +aws + access_key + secret_key +do + api_key + vars + user: var.foo +` + const basicResourcesStr = ` aws_security_group[firewall] aws_instance[web] @@ -129,6 +181,11 @@ foo bar ` +const importProvidersStr = ` +aws + foo +` + const importResourcesStr = ` aws_security_group[db] aws_security_group[web] diff --git a/config/test-fixtures/basic.tf b/config/test-fixtures/basic.tf index cbb8f916c5..8a861b3e80 100644 --- a/config/test-fixtures/basic.tf +++ b/config/test-fixtures/basic.tf @@ -3,6 +3,15 @@ variable "foo" { description = "bar"; } +provider "aws" { + access_key = "foo"; + secret_key = "bar"; +} + +provider "do" { + api_key = "${var.foo}"; +} + resource "aws_security_group" "firewall" { } diff --git a/config/test-fixtures/import.tf b/config/test-fixtures/import.tf index 2acc613a26..edd1afd422 100644 --- a/config/test-fixtures/import.tf +++ b/config/test-fixtures/import.tf @@ -5,4 +5,8 @@ variable "foo" { description = "bar"; } +provider "aws" { + foo = "bar"; +} + resource "aws_security_group" "web" {} diff --git a/config/test-fixtures/import/one.tf b/config/test-fixtures/import/one.tf index 92c78ae466..6b79a4c8cc 100644 --- a/config/test-fixtures/import/one.tf +++ b/config/test-fixtures/import/one.tf @@ -1,3 +1,7 @@ variable "bar" {} +provider "aws" { + bar = "baz"; +} + resource "aws_security_group" "db" {}