config: parsing of "locals" blocks in configuration
This commit is contained in:
parent
e2272f71a0
commit
f6797d6cb0
|
@ -34,6 +34,7 @@ type Config struct {
|
|||
ProviderConfigs []*ProviderConfig
|
||||
Resources []*Resource
|
||||
Variables []*Variable
|
||||
Locals []*Local
|
||||
Outputs []*Output
|
||||
|
||||
// The fields below can be filled in by loaders for validation
|
||||
|
@ -147,7 +148,7 @@ func (p *Provisioner) Copy() *Provisioner {
|
|||
}
|
||||
}
|
||||
|
||||
// Variable is a variable defined within the configuration.
|
||||
// Variable is a module argument defined within the configuration.
|
||||
type Variable struct {
|
||||
Name string
|
||||
DeclaredType string `mapstructure:"type"`
|
||||
|
@ -155,6 +156,12 @@ type Variable struct {
|
|||
Description string
|
||||
}
|
||||
|
||||
// Local is a local value defined within the configuration.
|
||||
type Local struct {
|
||||
Name string
|
||||
RawConfig *RawConfig
|
||||
}
|
||||
|
||||
// Output is an output defined within the configuration. An output is
|
||||
// resulting data that is highlighted by Terraform when finished. An
|
||||
// output marked Sensitive will be output in a masked form following
|
||||
|
@ -680,6 +687,29 @@ func (c *Config) Validate() error {
|
|||
}
|
||||
}
|
||||
|
||||
// Check that all locals are valid
|
||||
{
|
||||
found := make(map[string]struct{})
|
||||
for _, l := range c.Locals {
|
||||
if _, ok := found[l.Name]; ok {
|
||||
errs = append(errs, fmt.Errorf(
|
||||
"%s: duplicate local. local value names must be unique",
|
||||
l.Name,
|
||||
))
|
||||
continue
|
||||
}
|
||||
found[l.Name] = struct{}{}
|
||||
|
||||
for _, v := range l.RawConfig.Variables {
|
||||
if _, ok := v.(*CountVariable); ok {
|
||||
errs = append(errs, fmt.Errorf(
|
||||
"local %s: count variables are only valid within resources", l.Name,
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check that all outputs are valid
|
||||
{
|
||||
found := make(map[string]struct{})
|
||||
|
|
|
@ -148,6 +148,42 @@ func outputsStr(os []*Output) string {
|
|||
return strings.TrimSpace(result)
|
||||
}
|
||||
|
||||
func localsStr(ls []*Local) string {
|
||||
ns := make([]string, 0, len(ls))
|
||||
m := make(map[string]*Local)
|
||||
for _, l := range ls {
|
||||
ns = append(ns, l.Name)
|
||||
m[l.Name] = l
|
||||
}
|
||||
sort.Strings(ns)
|
||||
|
||||
result := ""
|
||||
for _, n := range ns {
|
||||
l := m[n]
|
||||
|
||||
result += fmt.Sprintf("%s\n", n)
|
||||
|
||||
if len(l.RawConfig.Variables) > 0 {
|
||||
result += fmt.Sprintf(" vars\n")
|
||||
for _, rawV := range l.RawConfig.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 provider configs field into a deterministic
|
||||
// string value for comparison in tests.
|
||||
func providerConfigsStr(pcs []*ProviderConfig) string {
|
||||
|
|
|
@ -37,6 +37,7 @@ func (t *hclConfigurable) Config() (*Config, error) {
|
|||
validKeys := map[string]struct{}{
|
||||
"atlas": struct{}{},
|
||||
"data": struct{}{},
|
||||
"locals": struct{}{},
|
||||
"module": struct{}{},
|
||||
"output": struct{}{},
|
||||
"provider": struct{}{},
|
||||
|
@ -72,6 +73,15 @@ func (t *hclConfigurable) Config() (*Config, error) {
|
|||
}
|
||||
}
|
||||
|
||||
// Build local values
|
||||
if locals := list.Filter("locals"); len(locals.Items) > 0 {
|
||||
var err error
|
||||
config.Locals, err = loadLocalsHcl(locals)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// Get Atlas configuration
|
||||
if atlas := list.Filter("atlas"); len(atlas.Items) > 0 {
|
||||
var err error
|
||||
|
@ -408,6 +418,59 @@ func loadModulesHcl(list *ast.ObjectList) ([]*Module, error) {
|
|||
return result, nil
|
||||
}
|
||||
|
||||
// loadLocalsHcl recurses into the given HCL object turns it into
|
||||
// a list of locals.
|
||||
func loadLocalsHcl(list *ast.ObjectList) ([]*Local, error) {
|
||||
|
||||
result := make([]*Local, 0, len(list.Items))
|
||||
|
||||
for _, block := range list.Items {
|
||||
if len(block.Keys) > 0 {
|
||||
return nil, fmt.Errorf(
|
||||
"locals block at %s should not have label %q",
|
||||
block.Pos(), block.Keys[0].Token.Value(),
|
||||
)
|
||||
}
|
||||
|
||||
blockObj, ok := block.Val.(*ast.ObjectType)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("locals value at %s should be a block", block.Val.Pos())
|
||||
}
|
||||
|
||||
// blockObj now contains directly our local decls
|
||||
for _, item := range blockObj.List.Items {
|
||||
if len(item.Keys) != 1 {
|
||||
return nil, fmt.Errorf("local declaration at %s may not be a block", item.Val.Pos())
|
||||
}
|
||||
|
||||
// By the time we get here there can only be one item left, but
|
||||
// we'll decode into a map anyway because it's a convenient way
|
||||
// to extract both the key and the value robustly.
|
||||
kv := map[string]interface{}{}
|
||||
hcl.DecodeObject(&kv, item)
|
||||
for k, v := range kv {
|
||||
rawConfig, err := NewRawConfig(map[string]interface{}{
|
||||
"value": v,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(
|
||||
"error parsing local value %q at %s: %s",
|
||||
k, item.Val.Pos(), err,
|
||||
)
|
||||
}
|
||||
|
||||
result = append(result, &Local{
|
||||
Name: k,
|
||||
RawConfig: rawConfig,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// LoadOutputsHcl recurses into the given HCL object and turns
|
||||
// it into a mapping of outputs.
|
||||
func loadOutputsHcl(list *ast.ObjectList) ([]*Output, error) {
|
||||
|
|
|
@ -180,17 +180,17 @@ func TestLoadFileBasic(t *testing.T) {
|
|||
}
|
||||
|
||||
if c.Dir != "" {
|
||||
t.Fatalf("bad: %#v", c.Dir)
|
||||
t.Fatalf("wrong dir %#v; want %#v", c.Dir, "")
|
||||
}
|
||||
|
||||
expectedTF := &Terraform{RequiredVersion: "foo"}
|
||||
if !reflect.DeepEqual(c.Terraform, expectedTF) {
|
||||
t.Fatalf("bad: %#v", c.Terraform)
|
||||
t.Fatalf("wrong terraform block %#v; want %#v", c.Terraform, expectedTF)
|
||||
}
|
||||
|
||||
expectedAtlas := &AtlasConfig{Name: "mitchellh/foo"}
|
||||
if !reflect.DeepEqual(c.Atlas, expectedAtlas) {
|
||||
t.Fatalf("bad: %#v", c.Atlas)
|
||||
t.Fatalf("wrong atlas config %#v; want %#v", c.Atlas, expectedAtlas)
|
||||
}
|
||||
|
||||
actual := variablesStr(c.Variables)
|
||||
|
@ -208,6 +208,10 @@ func TestLoadFileBasic(t *testing.T) {
|
|||
t.Fatalf("bad:\n%s", actual)
|
||||
}
|
||||
|
||||
if actual, want := localsStr(c.Locals), strings.TrimSpace(basicLocalsStr); actual != want {
|
||||
t.Fatalf("wrong locals:\n%s\nwant:\n%s", actual, want)
|
||||
}
|
||||
|
||||
actual = outputsStr(c.Outputs)
|
||||
if actual != strings.TrimSpace(basicOutputsStr) {
|
||||
t.Fatalf("bad:\n%s", actual)
|
||||
|
@ -288,6 +292,10 @@ func TestLoadFileBasic_json(t *testing.T) {
|
|||
t.Fatalf("bad:\n%s", actual)
|
||||
}
|
||||
|
||||
if actual, want := localsStr(c.Locals), strings.TrimSpace(basicLocalsStr); actual != want {
|
||||
t.Fatalf("wrong locals:\n%s\nwant:\n%s", actual, want)
|
||||
}
|
||||
|
||||
actual = outputsStr(c.Outputs)
|
||||
if actual != strings.TrimSpace(basicOutputsStr) {
|
||||
t.Fatalf("bad:\n%s", actual)
|
||||
|
@ -1055,6 +1063,18 @@ web_ip
|
|||
resource: aws_instance.web.private_ip
|
||||
`
|
||||
|
||||
const basicLocalsStr = `
|
||||
literal
|
||||
literal_list
|
||||
literal_map
|
||||
security_group_ids
|
||||
vars
|
||||
resource: aws_security_group.firewall.*.id
|
||||
web_ip
|
||||
vars
|
||||
resource: aws_instance.web.private_ip
|
||||
`
|
||||
|
||||
const basicProvidersStr = `
|
||||
aws
|
||||
access_key
|
||||
|
|
|
@ -58,6 +58,17 @@ resource aws_instance "web" {
|
|||
}
|
||||
}
|
||||
|
||||
locals {
|
||||
security_group_ids = "${aws_security_group.firewall.*.id}"
|
||||
web_ip = "${aws_instance.web.private_ip}"
|
||||
}
|
||||
|
||||
locals {
|
||||
literal = 2
|
||||
literal_list = ["foo"]
|
||||
literal_map = {"foo" = "bar"}
|
||||
}
|
||||
|
||||
resource "aws_instance" "db" {
|
||||
security_groups = "${aws_security_group.firewall.*.id}"
|
||||
VPC = "foo"
|
||||
|
|
|
@ -79,6 +79,14 @@
|
|||
}
|
||||
},
|
||||
|
||||
"locals": {
|
||||
"security_group_ids": "${aws_security_group.firewall.*.id}",
|
||||
"web_ip": "${aws_instance.web.private_ip}",
|
||||
"literal": 2,
|
||||
"literal_list": ["foo"],
|
||||
"literal_map": {"foo": "bar"}
|
||||
},
|
||||
|
||||
"output": {
|
||||
"web_ip": {
|
||||
"value": "${aws_instance.web.private_ip}"
|
||||
|
|
Loading…
Reference in New Issue