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
RawConfig *RawConfig
Provisioners []*Provisioner
DependsOn []string
}
// Provisioner is a configured provisioner step on a resource.
@ -154,6 +155,17 @@ func (c *Config) Validate() error {
}
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 _, v := range vs {
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) {
c := testConfig(t, "validate-bad-multi-resource")
if err := c.Validate(); err == nil {

View File

@ -352,16 +352,11 @@ func loadResourcesLibucl(o *libucl.Object) ([]*Resource, error) {
err)
}
// 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")
// Delete the "connection" section since we handle that
// seperately
// Remove the fields we handle specially
delete(config, "connection")
delete(config, "count")
delete(config, "depends_on")
delete(config, "provisioner")
rawConfig, err := NewRawConfig(config)
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
var provisioners []*Provisioner
if po := r.Get("provisioner"); po != nil {
@ -422,6 +431,7 @@ func loadResourcesLibucl(o *libucl.Object) ([]*Resource, error) {
Count: count,
RawConfig: rawConfig,
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 {
result += fmt.Sprintf(" vars\n")
@ -479,6 +486,8 @@ do
const basicResourcesStr = `
aws_instance[db] (x1)
security_groups
dependsOn
aws_instance.web
vars
resource: aws_security_group.firewall.*.id
aws_instance[web] (x1)

View File

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

View File

@ -20,7 +20,8 @@
"resource": {
"aws_instance": {
"db": {
"security_groups": ["${aws_security_group.firewall.*.id}"]
"security_groups": ["${aws_security_group.firewall.*.id}"],
"depends_on": ["aws_instance.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
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.
graphAddConfigResources(g, opts.Config, opts.State)
// Add explicit dependsOn dependencies to the graph
graphAddExplicitDeps(g)
// Next, add the state orphans if we have any
if opts.State != nil {
graphAddOrphans(g, opts.Config, opts.State)
@ -377,6 +380,48 @@ func graphAddDiff(g *depgraph.Graph, d *Diff) error {
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
// the resources that do not have an explicit resource provider specified
// 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) {
config := testConfig(t, "graph-basic")
state := &State{
@ -347,6 +362,16 @@ root
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 = `
root: root
aws_instance.foo

View File

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