config: support for provider configs

This commit is contained in:
Mitchell Hashimoto 2014-05-25 18:05:18 -07:00
parent 87b3046d2a
commit ab507814b7
7 changed files with 153 additions and 2 deletions

View File

@ -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.

View File

@ -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)

View File

@ -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.
//

View File

@ -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]

View File

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

View File

@ -5,4 +5,8 @@ variable "foo" {
description = "bar";
}
provider "aws" {
foo = "bar";
}
resource "aws_security_group" "web" {}

View File

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