diff --git a/config/config.go b/config/config.go index fd6cfba3cf..1dd04227ce 100644 --- a/config/config.go +++ b/config/config.go @@ -31,9 +31,16 @@ type ProviderConfig struct { // 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 + Name string + Type string + Count int + RawConfig *RawConfig + Provisioners []*Provisioner +} + +// Provisioner is a configured provisioner step on a resource. +type Provisioner struct { Type string - Count int RawConfig *RawConfig } diff --git a/config/loader_libucl.go b/config/loader_libucl.go index bf219f0b09..b95190e4ee 100644 --- a/config/loader_libucl.go +++ b/config/loader_libucl.go @@ -312,6 +312,10 @@ func loadResourcesLibucl(o *libucl.Object) ([]*Resource, error) { // Remove the "count" from the config, since we treat that special delete(config, "count") + // Delete the "provisioner" section from the config since + // that is treated specially. + delete(config, "provisioner") + rawConfig, err := NewRawConfig(config) if err != nil { return nil, fmt.Errorf( @@ -335,14 +339,86 @@ func loadResourcesLibucl(o *libucl.Object) ([]*Resource, error) { } } + // If we have provisioners, then parse those out + var provisioners []*Provisioner + if po := r.Get("provisioner"); po != nil { + var err error + provisioners, err = loadProvisionersLibucl(po) + po.Close() + if err != nil { + return nil, fmt.Errorf( + "Error reading provisioners for %s[%s]: %s", + t.Key(), + r.Key(), + err) + } + } + result = append(result, &Resource{ - Name: r.Key(), - Type: t.Key(), - Count: count, - RawConfig: rawConfig, + Name: r.Key(), + Type: t.Key(), + Count: count, + RawConfig: rawConfig, + Provisioners: provisioners, }) } } return result, nil } + +func loadProvisionersLibucl(o *libucl.Object) ([]*Provisioner, error) { + pos := make([]*libucl.Object, 0, int(o.Len())) + + // Accumulate all the actual provisioner configuration objects. We + // have to iterate twice here: + // + // 1. The first iteration is of the list of `provisioner` blocks. + // 2. The second iteration is of the dictionary within the + // provisioner which will have only one element which is the + // type of provisioner to use along with tis config. + // + // In JSON it looks kind of like this: + // + // [ + // { + // "shell": { + // ... + // } + // } + // ] + // + 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() { + pos = append(pos, o2) + } + + o1.Close() + iter2.Close() + } + iter.Close() + + result := make([]*Provisioner, 0, len(pos)) + for _, po := range pos { + defer po.Close() + + var config map[string]interface{} + if err := po.Decode(&config); err != nil { + return nil, err + } + + rawConfig, err := NewRawConfig(config) + if err != nil { + return nil, err + } + + result = append(result, &Provisioner{ + Type: po.Key(), + RawConfig: rawConfig, + }) + } + + return result, nil +} diff --git a/config/loader_test.go b/config/loader_test.go index ba9dadb371..3102dfb734 100644 --- a/config/loader_test.go +++ b/config/loader_test.go @@ -131,6 +131,22 @@ func outputsStr(os map[string]*Output) string { return strings.TrimSpace(result) } +func TestLoad_provisioners(t *testing.T) { + c, err := Load(filepath.Join(fixtureDir, "provisioners.tf")) + if err != nil { + t.Fatalf("err: %s", err) + } + + if c == nil { + t.Fatal("config should not be nil") + } + + actual := resourcesStr(c.Resources) + if actual != strings.TrimSpace(provisionerResourcesStr) { + 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 { @@ -213,6 +229,23 @@ func resourcesStr(rs []*Resource) string { result += fmt.Sprintf(" %s\n", k) } + if len(r.Provisioners) > 0 { + result += fmt.Sprintf(" provisioners\n") + for _, p := range r.Provisioners { + result += fmt.Sprintf(" %s\n", p.Type) + + ks := make([]string, 0, len(p.RawConfig.Raw)) + for k, _ := range p.RawConfig.Raw { + ks = append(ks, k) + } + sort.Strings(ks) + + for _, k := range ks { + result += fmt.Sprintf(" %s\n", k) + } + } + } + if len(r.RawConfig.Variables) > 0 { result += fmt.Sprintf(" vars\n") @@ -328,6 +361,18 @@ foo bar ` +const provisionerResourcesStr = ` +aws_instance[web] + ami + security_groups + provisioners + shell + path + vars + resource: aws_security_group.firewall.foo + user: var.foo +` + const variablesVariablesStr = ` bar <> diff --git a/config/test-fixtures/provisioners.tf b/config/test-fixtures/provisioners.tf new file mode 100644 index 0000000000..16578134cf --- /dev/null +++ b/config/test-fixtures/provisioners.tf @@ -0,0 +1,11 @@ +resource "aws_instance" "web" { + ami = "${var.foo}" + security_groups = [ + "foo", + "${aws_security_group.firewall.foo}" + ] + + provisioner "shell" { + path = "foo" + } +}