config: support for provider configs
This commit is contained in:
parent
87b3046d2a
commit
ab507814b7
|
@ -12,8 +12,18 @@ 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 {
|
||||||
Variables map[string]Variable
|
ProviderConfigs map[string]*ProviderConfig
|
||||||
Resources []*Resource
|
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.
|
// A resource represents a single Terraform resource in the configuration.
|
||||||
|
|
|
@ -66,6 +66,16 @@ func mergeConfig(c1, c2 *Config) (*Config, error) {
|
||||||
c.Variables[k] = v2
|
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
|
// Merge resources: If they collide, we just take the latest one
|
||||||
// for now. In the future, we might provide smarter merge functionality.
|
// for now. In the future, we might provide smarter merge functionality.
|
||||||
resources := make(map[string]*Resource)
|
resources := make(map[string]*Resource)
|
||||||
|
|
|
@ -36,6 +36,17 @@ func (t *libuclConfigurable) Config() (*Config, error) {
|
||||||
config := new(Config)
|
config := new(Config)
|
||||||
config.Variables = rawConfig.Variable
|
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
|
// Build the resources
|
||||||
resources := t.Object.Get("resource")
|
resources := t.Object.Get("resource")
|
||||||
if resources != nil {
|
if resources != nil {
|
||||||
|
@ -114,6 +125,52 @@ func loadFileLibucl(root string) (configurable, []string, error) {
|
||||||
return result, importPaths, nil
|
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
|
// Given a handle to a libucl object, this recurses into the structure
|
||||||
// and pulls out a list of resources.
|
// and pulls out a list of resources.
|
||||||
//
|
//
|
||||||
|
|
|
@ -29,6 +29,11 @@ func TestLoadBasic(t *testing.T) {
|
||||||
t.Fatalf("bad:\n%s", actual)
|
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)
|
actual = resourcesStr(c.Resources)
|
||||||
if actual != strings.TrimSpace(basicResourcesStr) {
|
if actual != strings.TrimSpace(basicResourcesStr) {
|
||||||
t.Fatalf("bad:\n%s", actual)
|
t.Fatalf("bad:\n%s", actual)
|
||||||
|
@ -50,12 +55,49 @@ func TestLoadBasic_import(t *testing.T) {
|
||||||
t.Fatalf("bad:\n%s", actual)
|
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)
|
actual = resourcesStr(c.Resources)
|
||||||
if actual != strings.TrimSpace(importResourcesStr) {
|
if actual != strings.TrimSpace(importResourcesStr) {
|
||||||
t.Fatalf("bad:\n%s", actual)
|
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
|
// This helper turns a resources field into a deterministic
|
||||||
// string value for comparison in tests.
|
// string value for comparison in tests.
|
||||||
func resourcesStr(rs []*Resource) string {
|
func resourcesStr(rs []*Resource) string {
|
||||||
|
@ -113,6 +155,16 @@ func variablesStr(vs map[string]Variable) string {
|
||||||
return strings.TrimSpace(result)
|
return strings.TrimSpace(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const basicProvidersStr = `
|
||||||
|
aws
|
||||||
|
access_key
|
||||||
|
secret_key
|
||||||
|
do
|
||||||
|
api_key
|
||||||
|
vars
|
||||||
|
user: var.foo
|
||||||
|
`
|
||||||
|
|
||||||
const basicResourcesStr = `
|
const basicResourcesStr = `
|
||||||
aws_security_group[firewall]
|
aws_security_group[firewall]
|
||||||
aws_instance[web]
|
aws_instance[web]
|
||||||
|
@ -129,6 +181,11 @@ foo
|
||||||
bar
|
bar
|
||||||
`
|
`
|
||||||
|
|
||||||
|
const importProvidersStr = `
|
||||||
|
aws
|
||||||
|
foo
|
||||||
|
`
|
||||||
|
|
||||||
const importResourcesStr = `
|
const importResourcesStr = `
|
||||||
aws_security_group[db]
|
aws_security_group[db]
|
||||||
aws_security_group[web]
|
aws_security_group[web]
|
||||||
|
|
|
@ -3,6 +3,15 @@ variable "foo" {
|
||||||
description = "bar";
|
description = "bar";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
provider "aws" {
|
||||||
|
access_key = "foo";
|
||||||
|
secret_key = "bar";
|
||||||
|
}
|
||||||
|
|
||||||
|
provider "do" {
|
||||||
|
api_key = "${var.foo}";
|
||||||
|
}
|
||||||
|
|
||||||
resource "aws_security_group" "firewall" {
|
resource "aws_security_group" "firewall" {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,4 +5,8 @@ variable "foo" {
|
||||||
description = "bar";
|
description = "bar";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
provider "aws" {
|
||||||
|
foo = "bar";
|
||||||
|
}
|
||||||
|
|
||||||
resource "aws_security_group" "web" {}
|
resource "aws_security_group" "web" {}
|
||||||
|
|
|
@ -1,3 +1,7 @@
|
||||||
variable "bar" {}
|
variable "bar" {}
|
||||||
|
|
||||||
|
provider "aws" {
|
||||||
|
bar = "baz";
|
||||||
|
}
|
||||||
|
|
||||||
resource "aws_security_group" "db" {}
|
resource "aws_security_group" "db" {}
|
||||||
|
|
Loading…
Reference in New Issue