config: can detect variables in config strings
This commit is contained in:
parent
7e06b45232
commit
95ef186bf8
|
@ -1,5 +1,9 @@
|
|||
package config
|
||||
|
||||
import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Config is the configuration that comes from loading a collection
|
||||
// of Terraform templates.
|
||||
type Config struct {
|
||||
|
@ -7,13 +11,71 @@ type Config struct {
|
|||
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 {
|
||||
Name string
|
||||
Type string
|
||||
Config map[string]interface{}
|
||||
Variables map[string]InterpolatedVariable
|
||||
}
|
||||
|
||||
type Variable struct {
|
||||
Default 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"
|
||||
|
||||
"github.com/mitchellh/go-libucl"
|
||||
"github.com/mitchellh/reflectwalk"
|
||||
)
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
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{
|
||||
Name: r.Key(),
|
||||
Type: t.Key(),
|
||||
Config: config,
|
||||
Variables: walker.Variables,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -69,6 +69,23 @@ func resourcesStr(rs []Resource) string {
|
|||
for k, _ := range r.Config {
|
||||
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)
|
||||
|
@ -101,6 +118,9 @@ aws_security_group[firewall]
|
|||
aws_instance[web]
|
||||
ami
|
||||
security_groups
|
||||
vars
|
||||
user: var.foo
|
||||
resource: aws_security_group.firewall.foo
|
||||
`
|
||||
|
||||
const basicVariablesStr = `
|
||||
|
|
|
@ -7,7 +7,7 @@ resource "aws_security_group" "firewall" {
|
|||
}
|
||||
|
||||
resource aws_instance "web" {
|
||||
ami = "ami-123456"
|
||||
ami = "${var.foo}"
|
||||
security_groups = [
|
||||
"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