diff --git a/terraform/context.go b/terraform/context.go index b6fc2d1723..3a240fe919 100644 --- a/terraform/context.go +++ b/terraform/context.go @@ -1057,7 +1057,8 @@ func (c *walkContext) genericWalkFn(cb genericWalkFunc) depgraph.WalkFunc { // This will keep track of whether we're stopped or not var stop uint32 = 0 - return func(n *depgraph.Noun) error { + var walkFn depgraph.WalkFunc + walkFn = func(n *depgraph.Noun) error { // If it is the root node, ignore if n.Name == GraphRootNode { return nil @@ -1132,6 +1133,42 @@ func (c *walkContext) genericWalkFn(cb genericWalkFunc) depgraph.WalkFunc { rn := n.Meta.(*GraphNodeResource) + // If we're expanding, then expand the nodes, and then rewalk the graph + if rn.ExpandMode > ResourceExpandNone { + ns, err := rn.Expand() + if err != nil { + return err + } + + // Go through all the nouns and run them in parallel, collecting + // any errors. + var l sync.Mutex + var wg sync.WaitGroup + errs := make([]error, 0, len(ns)) + for _, n := range ns { + wg.Add(1) + + go func() { + defer wg.Done() + if err := walkFn(n); err != nil { + l.Lock() + defer l.Unlock() + errs = append(errs, err) + } + }() + } + + // Wait for the subgraph + wg.Wait() + + // If there are errors, then we should return them + if len(errs) > 0 { + return &multierror.Error{Errors: errs} + } + + return nil + } + // Make sure that at least some resource configuration is set if rn.Config == nil { rn.Resource.Config = new(ResourceConfig) @@ -1163,6 +1200,8 @@ func (c *walkContext) genericWalkFn(cb genericWalkFunc) depgraph.WalkFunc { return nil } + + return walkFn } // applyProvisioners is used to run any provisioners a resource has diff --git a/terraform/graph.go b/terraform/graph.go index 3d1cf6c10f..a20ca76d47 100644 --- a/terraform/graph.go +++ b/terraform/graph.go @@ -92,6 +92,9 @@ type GraphNodeResource struct { Resource *Resource ResourceProviderNode string + Diff *ModuleDiff + State *ModuleState + // Expand, if true, indicates that this resource needs to be expanded // at walk-time to multiple resources. ExpandMode ResourceExpandMode @@ -372,6 +375,8 @@ func graphAddConfigResources( nounsList := make([]*depgraph.Noun, len(c.Resources)) for i, r := range c.Resources { name := r.Id() + + // Build the noun nounsList[i] = &depgraph.Noun{ Name: name, Meta: &GraphNodeResource{ @@ -385,6 +390,7 @@ func graphAddConfigResources( Type: r.Type, }, }, + State: mod.View(name), ExpandMode: ResourceExpandApply, }, } @@ -529,6 +535,11 @@ func graphAddDiff(g *depgraph.Graph, d *ModuleDiff) error { } } + // If we're expanding, save the diff so we can add it on later + if rn.ExpandMode > ResourceExpandNone { + rn.Diff = d + } + var rd *InstanceDiff if rn.ExpandMode == ResourceExpandNone { rd = diffs[0] @@ -1577,6 +1588,160 @@ func (p *graphSharedProvider) MergeConfig( return NewResourceConfig(rc) } +// Expand will expand this node into a subgraph if Expand is set. +func (n *GraphNodeResource) Expand() ([]*depgraph.Noun, error) { + count := 1 + + g := new(depgraph.Graph) + + // Determine the nodes to create. If we're just looking for the + // nodes to create, return that. + n.expandCreate(g, count) + + // Add in the diff if we have it + if n.Diff != nil { + if err := graphAddDiff(g, n.Diff); err != nil { + return nil, err + } + } + + // If we're just expanding the apply, then filter those out and + // return them now. + if n.ExpandMode == ResourceExpandApply { + return n.filterNouns(g, false), nil + } + + if n.State != nil { + // TODO: orphans + + // Add the tainted resources + graphAddTainted(g, n.State) + } + + return n.filterNouns(g, true), nil +} + +// expandCreate expands this resource and adds the resources to the graph. +func (n *GraphNodeResource) expandCreate(g *depgraph.Graph, count int) { + // Create the list of nouns that we'd have to create + create := make([]*depgraph.Noun, 0, count) + + // First thing, expand the counts that we have defined for our + // current config into the full set of resources. + r := n.Config + for i := 0; i < count; i++ { + name := r.Id() + index := -1 + + // If we have a count that is more than one, then make sure + // we suffix with the number of the resource that this is. + if count > 1 { + name = fmt.Sprintf("%s.%d", name, i) + index = i + } + + var state *ResourceState + if n.State != nil { + // Lookup the resource state + if s, ok := n.State.Resources[name]; ok { + state = s + } + + if state == nil { + if count == 1 { + // If the count is one, check the state for ".0" + // appended, which might exist if we go from + // count > 1 to count == 1. + k := r.Id() + ".0" + state = n.State.Resources[k] + } else if i == 0 { + // If count is greater than one, check for state + // with just the ID, which might exist if we go + // from count == 1 to count > 1 + state = n.State.Resources[r.Id()] + } + } + } + + if state == nil { + state = &ResourceState{ + Type: r.Type, + } + } + + flags := FlagPrimary + if len(state.Tainted) > 0 { + flags |= FlagHasTainted + } + + // Copy the base resource so we can fill it in + resource := n.copyResource(name) + resource.State = state.Primary + resource.Flags = flags + // TODO: we need the diff here... + + // Add the result + create = append(create, &depgraph.Noun{ + Name: name, + Meta: &GraphNodeResource{ + Index: index, + Config: r, + Resource: resource, + }, + }) + } + + g.Nouns = append(g.Nouns, create...) +} + +// copyResource copies the Resource structure to assign to a subgraph. +func (n *GraphNodeResource) copyResource(id string) *Resource { + info := *n.Resource.Info + info.Id = id + resource := *n.Resource + resource.Id = id + resource.Info = &info + resource.Config = NewResourceConfig(n.Config.RawConfig) + return &resource +} + +func (n *GraphNodeResource) filterNouns( + g *depgraph.Graph, destroy bool) []*depgraph.Noun { + result := make([]*depgraph.Noun, 0, len(g.Nouns)) + for _, n := range g.Nouns { + rn, ok := n.Meta.(*GraphNodeResource) + if !ok { + continue + } + + // If the diff is nil, then we're not destroying, so append only + // in that case. + if rn.Resource.Diff == nil { + if !destroy { + result = append(result, n) + } + + continue + } + + // If we are destroying, append it only if we care about destroys + if rn.Resource.Diff.Destroy { + if destroy { + result = append(result, n) + } + + continue + } + + // If we're not destroying, then add it only if we don't + // care about deploys. + if !destroy { + result = append(result, n) + } + } + return result +} + // matchingPrefixes takes a resource type and a set of resource // providers we know about by prefix and returns a list of prefixes // that might be valid for that resource. diff --git a/terraform/state.go b/terraform/state.go index f71a342896..ef537ab54f 100644 --- a/terraform/state.go +++ b/terraform/state.go @@ -212,6 +212,24 @@ func (m *ModuleState) Orphans(c *config.Config) []string { return result } +// View returns a view with the given resource prefix. +func (m *ModuleState) View(id string) *ModuleState { + if m == nil { + return m + } + + r := m.deepcopy() + for k, _ := range r.Resources { + if id == k || strings.HasPrefix(k, id+".") { + continue + } + + delete(r.Resources, k) + } + + return r +} + func (m *ModuleState) init() { if m.Outputs == nil { m.Outputs = make(map[string]string)