Merge pull request #33 from hashicorp/f-depends

DependsOn Meta-Parameter
This commit is contained in:
Mitchell Hashimoto 2014-07-22 18:21:44 -07:00
commit 0e091224c9
11 changed files with 131 additions and 10 deletions

View File

@ -42,6 +42,7 @@ type Resource struct {
Count int Count int
RawConfig *RawConfig RawConfig *RawConfig
Provisioners []*Provisioner Provisioners []*Provisioner
DependsOn []string
} }
// Provisioner is a configured provisioner step on a resource. // Provisioner is a configured provisioner step on a resource.
@ -154,6 +155,17 @@ func (c *Config) Validate() error {
} }
dupped = nil dupped = nil
// Make sure all dependsOn are valid in resources
for n, r := range resources {
for _, d := range r.DependsOn {
if _, ok := resources[d]; !ok {
errs = append(errs, fmt.Errorf(
"%s: resource depends on non-existent resource '%s'",
n, d))
}
}
}
for source, vs := range vars { for source, vs := range vars {
for _, v := range vs { for _, v := range vs {
rv, ok := v.(*ResourceVariable) rv, ok := v.(*ResourceVariable)

View File

@ -16,6 +16,13 @@ func TestConfigValidate(t *testing.T) {
} }
} }
func TestConfigValidate_badDependsOn(t *testing.T) {
c := testConfig(t, "validate-bad-depends-on")
if err := c.Validate(); err == nil {
t.Fatal("should not be valid")
}
}
func TestConfigValidate_badMultiResource(t *testing.T) { func TestConfigValidate_badMultiResource(t *testing.T) {
c := testConfig(t, "validate-bad-multi-resource") c := testConfig(t, "validate-bad-multi-resource")
if err := c.Validate(); err == nil { if err := c.Validate(); err == nil {

View File

@ -352,16 +352,11 @@ func loadResourcesLibucl(o *libucl.Object) ([]*Resource, error) {
err) err)
} }
// Remove the "count" from the config, since we treat that special // Remove the fields we handle specially
delete(config, "count")
// Delete the "provisioner" section from the config since
// that is treated specially.
delete(config, "provisioner")
// Delete the "connection" section since we handle that
// seperately
delete(config, "connection") delete(config, "connection")
delete(config, "count")
delete(config, "depends_on")
delete(config, "provisioner")
rawConfig, err := NewRawConfig(config) rawConfig, err := NewRawConfig(config)
if err != nil { if err != nil {
@ -401,6 +396,20 @@ func loadResourcesLibucl(o *libucl.Object) ([]*Resource, error) {
} }
} }
// If we have depends fields, then add those in
var dependsOn []string
if deps := r.Get("depends_on"); deps != nil {
err := deps.Decode(&dependsOn)
deps.Close()
if err != nil {
return nil, fmt.Errorf(
"Error reading depends_on for %s[%s]: %s",
t.Key(),
r.Key(),
err)
}
}
// If we have provisioners, then parse those out // If we have provisioners, then parse those out
var provisioners []*Provisioner var provisioners []*Provisioner
if po := r.Get("provisioner"); po != nil { if po := r.Get("provisioner"); po != nil {
@ -422,6 +431,7 @@ func loadResourcesLibucl(o *libucl.Object) ([]*Resource, error) {
Count: count, Count: count,
RawConfig: rawConfig, RawConfig: rawConfig,
Provisioners: provisioners, Provisioners: provisioners,
DependsOn: dependsOn,
}) })
} }
} }

View File

@ -393,6 +393,13 @@ func resourcesStr(rs []*Resource) string {
} }
} }
if len(r.DependsOn) > 0 {
result += fmt.Sprintf(" dependsOn\n")
for _, d := range r.DependsOn {
result += fmt.Sprintf(" %s\n", d)
}
}
if len(r.RawConfig.Variables) > 0 { if len(r.RawConfig.Variables) > 0 {
result += fmt.Sprintf(" vars\n") result += fmt.Sprintf(" vars\n")
@ -479,6 +486,8 @@ do
const basicResourcesStr = ` const basicResourcesStr = `
aws_instance[db] (x1) aws_instance[db] (x1)
security_groups security_groups
dependsOn
aws_instance.web
vars vars
resource: aws_security_group.firewall.*.id resource: aws_security_group.firewall.*.id
aws_instance[web] (x1) aws_instance[web] (x1)

View File

@ -31,6 +31,8 @@ resource aws_instance "web" {
resource "aws_instance" "db" { resource "aws_instance" "db" {
security_groups = "${aws_security_group.firewall.*.id}" security_groups = "${aws_security_group.firewall.*.id}"
depends_on = ["aws_instance.web"]
} }
output "web_ip" { output "web_ip" {

View File

@ -20,7 +20,8 @@
"resource": { "resource": {
"aws_instance": { "aws_instance": {
"db": { "db": {
"security_groups": ["${aws_security_group.firewall.*.id}"] "security_groups": ["${aws_security_group.firewall.*.id}"],
"depends_on": ["aws_instance.web"]
}, },
"web": { "web": {

View File

@ -0,0 +1,3 @@
resource "aws_instance" "web" {
depends_on = ["aws_instance.db"]
}

View File

@ -32,4 +32,6 @@ resource aws_instance "web" {
device_index = 0 device_index = 0
description = "Main network interface" description = "Main network interface"
} }
depends_on = ["aws_security_group.firewall"]
} }

View File

@ -106,6 +106,9 @@ func Graph(opts *GraphOpts) (*depgraph.Graph, error) {
// and no dependencies. // and no dependencies.
graphAddConfigResources(g, opts.Config, opts.State) graphAddConfigResources(g, opts.Config, opts.State)
// Add explicit dependsOn dependencies to the graph
graphAddExplicitDeps(g)
// Next, add the state orphans if we have any // Next, add the state orphans if we have any
if opts.State != nil { if opts.State != nil {
graphAddOrphans(g, opts.Config, opts.State) graphAddOrphans(g, opts.Config, opts.State)
@ -377,6 +380,48 @@ func graphAddDiff(g *depgraph.Graph, d *Diff) error {
return nil return nil
} }
// graphAddExplicitDeps adds the dependencies to the graph for the explicit
// dependsOn configurations.
func graphAddExplicitDeps(g *depgraph.Graph) {
depends := false
rs := make(map[string]*depgraph.Noun)
for _, n := range g.Nouns {
rn, ok := n.Meta.(*GraphNodeResource)
if !ok {
continue
}
rs[rn.Config.Id()] = n
if len(rn.Config.DependsOn) > 0 {
depends = true
}
}
// If we didn't have any dependsOn, just return
if !depends {
return
}
for _, n1 := range rs {
rn1 := n1.Meta.(*GraphNodeResource)
for _, d := range rn1.Config.DependsOn {
for _, n2 := range rs {
rn2 := n2.Meta.(*GraphNodeResource)
if rn2.Config.Id() != d {
continue
}
n1.Deps = append(n1.Deps, &depgraph.Dependency{
Name: d,
Source: n1,
Target: n2,
})
}
}
}
}
// graphAddMissingResourceProviders adds GraphNodeResourceProvider nodes for // graphAddMissingResourceProviders adds GraphNodeResourceProvider nodes for
// the resources that do not have an explicit resource provider specified // the resources that do not have an explicit resource provider specified
// because no provider configuration was given. // because no provider configuration was given.

View File

@ -51,6 +51,21 @@ func TestGraph_cycle(t *testing.T) {
} }
} }
func TestGraph_dependsOn(t *testing.T) {
config := testConfig(t, "graph-depends-on")
g, err := Graph(&GraphOpts{Config: config})
if err != nil {
t.Fatalf("err: %s", err)
}
actual := strings.TrimSpace(g.String())
expected := strings.TrimSpace(testTerraformGraphDependsStr)
if actual != expected {
t.Fatalf("bad:\n\n%s", actual)
}
}
func TestGraph_state(t *testing.T) { func TestGraph_state(t *testing.T) {
config := testConfig(t, "graph-basic") config := testConfig(t, "graph-basic")
state := &State{ state := &State{
@ -347,6 +362,16 @@ root
root -> aws_load_balancer.weblb root -> aws_load_balancer.weblb
` `
const testTerraformGraphDependsStr = `
root: root
aws_instance.db
aws_instance.db -> aws_instance.web
aws_instance.web
root
root -> aws_instance.db
root -> aws_instance.web
`
const testTerraformGraphDiffStr = ` const testTerraformGraphDiffStr = `
root: root root: root
aws_instance.foo aws_instance.foo

View File

@ -0,0 +1,5 @@
resource "aws_instance" "web" {}
resource "aws_instance" "db" {
depends_on = ["aws_instance.web"]
}