Merge pull request #33 from hashicorp/f-depends
DependsOn Meta-Parameter
This commit is contained in:
commit
0e091224c9
|
@ -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)
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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" {
|
||||||
|
|
|
@ -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": {
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
resource "aws_instance" "web" {
|
||||||
|
depends_on = ["aws_instance.db"]
|
||||||
|
}
|
|
@ -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"]
|
||||||
}
|
}
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
resource "aws_instance" "web" {}
|
||||||
|
|
||||||
|
resource "aws_instance" "db" {
|
||||||
|
depends_on = ["aws_instance.web"]
|
||||||
|
}
|
Loading…
Reference in New Issue