config: can detect variables in config strings
This commit is contained in:
parent
7e06b45232
commit
95ef186bf8
|
@ -1,5 +1,9 @@
|
||||||
package config
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
// 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 {
|
||||||
|
@ -7,13 +11,71 @@ type Config struct {
|
||||||
Resources []Resource
|
Resources []Resource
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// A resource represents a single Terraform resource in the configuration.
|
||||||
|
// A Terraform resource is something that represents some component that
|
||||||
|
// can be created and managed, and has some properties associated with it.
|
||||||
type Resource struct {
|
type Resource struct {
|
||||||
Name string
|
Name string
|
||||||
Type string
|
Type string
|
||||||
Config map[string]interface{}
|
Config map[string]interface{}
|
||||||
|
Variables map[string]InterpolatedVariable
|
||||||
}
|
}
|
||||||
|
|
||||||
type Variable struct {
|
type Variable struct {
|
||||||
Default string
|
Default string
|
||||||
Description string
|
Description string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// An InterpolatedVariable is a variable that is embedded within a string
|
||||||
|
// in the configuration, such as "hello ${world}" (world in this case is
|
||||||
|
// an interpolated variable).
|
||||||
|
//
|
||||||
|
// These variables can come from a variety of sources, represented by
|
||||||
|
// implementations of this interface.
|
||||||
|
type InterpolatedVariable interface {
|
||||||
|
FullKey() string
|
||||||
|
}
|
||||||
|
|
||||||
|
// A ResourceVariable is a variable that is referencing the field
|
||||||
|
// of a resource, such as "${aws_instance.foo.ami}"
|
||||||
|
type ResourceVariable struct {
|
||||||
|
Type string
|
||||||
|
Name string
|
||||||
|
Field string
|
||||||
|
|
||||||
|
key string
|
||||||
|
}
|
||||||
|
|
||||||
|
// A UserVariable is a variable that is referencing a user variable
|
||||||
|
// that is inputted from outside the configuration. This looks like
|
||||||
|
// "${var.foo}"
|
||||||
|
type UserVariable struct {
|
||||||
|
name string
|
||||||
|
key string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewResourceVariable(key string) (*ResourceVariable, error) {
|
||||||
|
parts := strings.SplitN(key, ".", 3)
|
||||||
|
return &ResourceVariable{
|
||||||
|
Type: parts[0],
|
||||||
|
Name: parts[1],
|
||||||
|
Field: parts[2],
|
||||||
|
key: key,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *ResourceVariable) FullKey() string {
|
||||||
|
return v.key
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewUserVariable(key string) (*UserVariable, error) {
|
||||||
|
name := key[len("var."):]
|
||||||
|
return &UserVariable{
|
||||||
|
key: key,
|
||||||
|
name: name,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *UserVariable) FullKey() string {
|
||||||
|
return v.key
|
||||||
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@ import (
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
"github.com/mitchellh/go-libucl"
|
"github.com/mitchellh/go-libucl"
|
||||||
|
"github.com/mitchellh/reflectwalk"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Put the parse flags we use for libucl in a constant so we can get
|
// Put the parse flags we use for libucl in a constant so we can get
|
||||||
|
@ -176,10 +177,20 @@ func loadResourcesLibucl(o *libucl.Object) ([]Resource, error) {
|
||||||
err)
|
err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
walker := new(variableDetectWalker)
|
||||||
|
if err := reflectwalk.Walk(config, walker); err != nil {
|
||||||
|
return nil, fmt.Errorf(
|
||||||
|
"Error reading config for %s[%s]: %s",
|
||||||
|
t.Key(),
|
||||||
|
r.Key(),
|
||||||
|
err)
|
||||||
|
}
|
||||||
|
|
||||||
result = append(result, Resource{
|
result = append(result, Resource{
|
||||||
Name: r.Key(),
|
Name: r.Key(),
|
||||||
Type: t.Key(),
|
Type: t.Key(),
|
||||||
Config: config,
|
Config: config,
|
||||||
|
Variables: walker.Variables,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -69,6 +69,23 @@ func resourcesStr(rs []Resource) string {
|
||||||
for k, _ := range r.Config {
|
for k, _ := range r.Config {
|
||||||
result += fmt.Sprintf(" %s\n", k)
|
result += fmt.Sprintf(" %s\n", k)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(r.Variables) > 0 {
|
||||||
|
result += fmt.Sprintf(" vars\n")
|
||||||
|
for _, rawV := range r.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)
|
return strings.TrimSpace(result)
|
||||||
|
@ -101,6 +118,9 @@ aws_security_group[firewall]
|
||||||
aws_instance[web]
|
aws_instance[web]
|
||||||
ami
|
ami
|
||||||
security_groups
|
security_groups
|
||||||
|
vars
|
||||||
|
user: var.foo
|
||||||
|
resource: aws_security_group.firewall.foo
|
||||||
`
|
`
|
||||||
|
|
||||||
const basicVariablesStr = `
|
const basicVariablesStr = `
|
||||||
|
|
|
@ -7,7 +7,7 @@ resource "aws_security_group" "firewall" {
|
||||||
}
|
}
|
||||||
|
|
||||||
resource aws_instance "web" {
|
resource aws_instance "web" {
|
||||||
ami = "ami-123456"
|
ami = "${var.foo}"
|
||||||
security_groups = [
|
security_groups = [
|
||||||
"foo",
|
"foo",
|
||||||
"${aws_security_group.firewall.foo}"
|
"${aws_security_group.firewall.foo}"
|
||||||
|
|
|
@ -0,0 +1,70 @@
|
||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// varRegexp is a regexp that matches variables such as ${foo.bar}
|
||||||
|
var varRegexp *regexp.Regexp
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
varRegexp = regexp.MustCompile(`(?i)(\$+)\{([-.a-z0-9_]+)\}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
// variableDetectWalker implements interfaces for the reflectwalk package
|
||||||
|
// (github.com/mitchellh/reflectwalk) that can be used to automatically
|
||||||
|
// pull out the variables that need replacing.
|
||||||
|
type variableDetectWalker struct {
|
||||||
|
Variables map[string]InterpolatedVariable
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *variableDetectWalker) Primitive(v reflect.Value) error {
|
||||||
|
// We only care about strings
|
||||||
|
if v.Kind() != reflect.String {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// XXX: This can be a lot more efficient if we used a real
|
||||||
|
// parser. A regexp is a hammer though that will get this working.
|
||||||
|
|
||||||
|
matches := varRegexp.FindAllStringSubmatch(v.String(), -1)
|
||||||
|
if len(matches) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, match := range matches {
|
||||||
|
dollars := len(match[1])
|
||||||
|
|
||||||
|
// If there are even amounts of dollar signs, then it is escaped
|
||||||
|
if dollars%2 == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise, record it
|
||||||
|
key := match[2]
|
||||||
|
if w.Variables == nil {
|
||||||
|
w.Variables = make(map[string]InterpolatedVariable)
|
||||||
|
}
|
||||||
|
if _, ok := w.Variables[key]; ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
var err error
|
||||||
|
var iv InterpolatedVariable
|
||||||
|
if strings.HasPrefix(key, "var.") {
|
||||||
|
iv, err = NewUserVariable(key)
|
||||||
|
} else {
|
||||||
|
iv, err = NewResourceVariable(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Variables[key] = iv
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
Loading…
Reference in New Issue