terraform: expand resource nodes at walk time

This commit is contained in:
Mitchell Hashimoto 2014-10-02 10:42:58 -07:00
parent fecb68f117
commit fb1c224e12
3 changed files with 223 additions and 1 deletions

View File

@ -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

View File

@ -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.

View File

@ -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)