2039 lines
52 KiB
Go
2039 lines
52 KiB
Go
package terraform
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"log"
|
|
"sort"
|
|
"strings"
|
|
|
|
"github.com/hashicorp/terraform/config"
|
|
"github.com/hashicorp/terraform/config/module"
|
|
"github.com/hashicorp/terraform/depgraph"
|
|
"github.com/hashicorp/terraform/helper/multierror"
|
|
)
|
|
|
|
// GraphOpts are options used to create the resource graph that Terraform
|
|
// walks to make changes to infrastructure.
|
|
//
|
|
// Depending on what options are set, the resulting graph will come in
|
|
// varying degrees of completeness.
|
|
type GraphOpts struct {
|
|
// Config is the configuration from which to build the basic graph.
|
|
// This is the only required item.
|
|
//Config *config.Config
|
|
|
|
// Module is the relative root of a module tree for this graph. This
|
|
// is the only required item. This should always be the absolute root
|
|
// of the tree. ModulePath below should be used to constrain the depth.
|
|
//
|
|
// ModulePath specifies the place in the tree where Module exists.
|
|
// This is used for State lookups.
|
|
Module *module.Tree
|
|
ModulePath []string
|
|
|
|
// Diff of changes that will be applied to the given state. This will
|
|
// associate a ResourceDiff with applicable resources. Additionally,
|
|
// new resource nodes representing resource destruction may be inserted
|
|
// into the graph.
|
|
Diff *Diff
|
|
|
|
// State, if present, will make the ResourceState available on each
|
|
// resource node. Additionally, any orphans will be added automatically
|
|
// to the graph.
|
|
//
|
|
// Note: the state will be modified so it is initialized with basic
|
|
// empty states for all modules/resources in this graph. If you call prune
|
|
// later, these will be removed, but the graph adds important metadata.
|
|
State *State
|
|
|
|
// Providers is a mapping of prefixes to a resource provider. If given,
|
|
// resource providers will be found, initialized, and associated to the
|
|
// resources in the graph.
|
|
//
|
|
// This will also potentially insert new nodes into the graph for
|
|
// the configuration of resource providers.
|
|
Providers map[string]ResourceProviderFactory
|
|
|
|
// Provisioners is a mapping of names to a resource provisioner.
|
|
// These must be provided to support resource provisioners.
|
|
Provisioners map[string]ResourceProvisionerFactory
|
|
|
|
// parent specifies the parent graph if there is one. This should not be
|
|
// set manually.
|
|
parent *depgraph.Graph
|
|
}
|
|
|
|
// GraphRootNode is the name of the root node in the Terraform resource
|
|
// graph. This node is just a placemarker and has no associated functionality.
|
|
const GraphRootNode = "root"
|
|
|
|
// GraphMeta is the metadata attached to the graph itself.
|
|
type GraphMeta struct {
|
|
// ModulePath is the path of the module that this graph represents.
|
|
ModulePath []string
|
|
}
|
|
|
|
// GraphNodeModule is a node type in the graph that represents a module
|
|
// that will be created/managed.
|
|
type GraphNodeModule struct {
|
|
Config *config.Module
|
|
Path []string
|
|
Graph *depgraph.Graph
|
|
State *ModuleState
|
|
Flags ResourceFlag
|
|
}
|
|
|
|
// GraphNodeResource is a node type in the graph that represents a resource
|
|
// that will be created or managed. Unlike the GraphNodeResourceMeta node,
|
|
// this represents a _single_, _resource_ to be managed, not a set of resources
|
|
// or a component of a resource.
|
|
type GraphNodeResource struct {
|
|
Index int
|
|
Config *config.Resource
|
|
Resource *Resource
|
|
ResourceProviderNode string
|
|
|
|
// All the fields below are related to expansion. These are set by
|
|
// the graph but aren't useful individually.
|
|
ExpandMode ResourceExpandMode
|
|
Diff *ModuleDiff
|
|
State *ModuleState
|
|
}
|
|
|
|
// GraphNodeResourceProvider is a node type in the graph that represents
|
|
// the configuration for a resource provider.
|
|
type GraphNodeResourceProvider struct {
|
|
ID string
|
|
Provider *graphSharedProvider
|
|
}
|
|
|
|
// graphSharedProvider is a structure that stores a configuration
|
|
// with initialized providers and might be shared across different
|
|
// graphs in order to have only one instance of a provider.
|
|
type graphSharedProvider struct {
|
|
Config *config.ProviderConfig
|
|
Providers map[string]ResourceProvider
|
|
ProviderKeys []string
|
|
Parent *graphSharedProvider
|
|
|
|
overrideConfig map[string]map[string]interface{}
|
|
parentNoun *depgraph.Noun
|
|
}
|
|
|
|
// ResourceExpandMode specifies the expand behavior of the GraphNodeResource
|
|
// node.
|
|
type ResourceExpandMode byte
|
|
|
|
const (
|
|
ResourceExpandNone ResourceExpandMode = iota
|
|
ResourceExpandApply
|
|
ResourceExpandDestroy
|
|
)
|
|
|
|
// Graph builds a dependency graph of all the resources for infrastructure
|
|
// change.
|
|
//
|
|
// This dependency graph shows the correct order that any resources need
|
|
// to be operated on.
|
|
//
|
|
// The Meta field of a graph Noun can contain one of the follow types. A
|
|
// description is next to each type to explain what it is.
|
|
//
|
|
// *GraphNodeResource - A resource. See the documentation of this
|
|
// struct for more details.
|
|
// *GraphNodeResourceProvider - A resource provider that needs to be
|
|
// configured at this point.
|
|
//
|
|
func Graph(opts *GraphOpts) (*depgraph.Graph, error) {
|
|
if opts.Module == nil {
|
|
return nil, errors.New("Module is required for Graph")
|
|
}
|
|
if opts.ModulePath == nil {
|
|
opts.ModulePath = rootModulePath
|
|
}
|
|
if !opts.Module.Loaded() {
|
|
return nil, errors.New("Module must be loaded")
|
|
}
|
|
|
|
// Get the correct module in the tree that we're looking for.
|
|
currentModule := opts.Module
|
|
for _, n := range opts.ModulePath[1:] {
|
|
children := currentModule.Children()
|
|
currentModule = children[n]
|
|
}
|
|
|
|
var conf *config.Config
|
|
if currentModule != nil {
|
|
conf = currentModule.Config()
|
|
} else {
|
|
conf = new(config.Config)
|
|
}
|
|
|
|
// Get the state and diff of the module that we're working with.
|
|
var modDiff *ModuleDiff
|
|
var modState *ModuleState
|
|
if opts.Diff != nil {
|
|
modDiff = opts.Diff.ModuleByPath(opts.ModulePath)
|
|
}
|
|
if opts.State != nil {
|
|
modState = opts.State.ModuleByPath(opts.ModulePath)
|
|
}
|
|
|
|
log.Printf("[DEBUG] Creating graph for path: %v", opts.ModulePath)
|
|
|
|
g := new(depgraph.Graph)
|
|
g.Meta = &GraphMeta{
|
|
ModulePath: opts.ModulePath,
|
|
}
|
|
|
|
// First, build the initial resource graph. This only has the resources
|
|
// and no dependencies. This only adds resources that are in the config
|
|
// and not "orphans" (that are in the state, but not in the config).
|
|
graphAddConfigResources(g, conf, modState)
|
|
|
|
if modState != nil {
|
|
// Next, add the state orphans if we have any
|
|
graphAddOrphans(g, conf, modState)
|
|
|
|
// Add tainted resources if we have any.
|
|
graphAddTainted(g, modState)
|
|
}
|
|
|
|
// Create the resource provider nodes for explicitly configured
|
|
// providers within our graph.
|
|
graphAddConfigProviderConfigs(g, conf)
|
|
|
|
if opts.parent != nil {
|
|
// Add/merge the provider configurations from the parent so that
|
|
// we properly "inherit" providers.
|
|
graphAddParentProviderConfigs(g, opts.parent)
|
|
}
|
|
|
|
// First pass matching resources to providers. This will allow us to
|
|
// determine what providers are missing.
|
|
graphMapResourceProviderId(g)
|
|
|
|
if opts.Providers != nil {
|
|
// Add missing providers from the mapping.
|
|
if err := graphAddMissingResourceProviders(g, opts.Providers); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Initialize all the providers
|
|
if err := graphInitResourceProviders(g, opts.Providers); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Map the providers to resources
|
|
if err := graphMapResourceProviders(g); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
// Add the modules that are in the configuration.
|
|
if err := graphAddConfigModules(g, conf, opts); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if opts.State != nil {
|
|
// Add module orphans if we have any of those
|
|
if ms := opts.State.Children(opts.ModulePath); len(ms) > 0 {
|
|
if err := graphAddModuleOrphans(g, conf, ms, opts); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
}
|
|
|
|
// Add the orphan dependencies
|
|
graphAddOrphanDeps(g, modState)
|
|
|
|
// Add the orphan module dependencies
|
|
graphAddOrphanModuleDeps(g, modState)
|
|
|
|
// Add the provider dependencies
|
|
graphAddResourceProviderDeps(g)
|
|
|
|
// Now, prune the providers that we aren't using.
|
|
graphPruneResourceProviders(g)
|
|
|
|
// Add explicit dependsOn dependencies to the graph
|
|
graphAddExplicitDeps(g)
|
|
|
|
// Setup the provisioners. These may have variable dependencies,
|
|
// and must be done before dependency setup
|
|
if err := graphMapResourceProvisioners(g, opts.Provisioners); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Add all the variable dependencies
|
|
graphAddVariableDeps(g)
|
|
|
|
// Build the root so that we have a single valid root
|
|
graphAddRoot(g)
|
|
|
|
// If we have a diff, then make sure to add that in
|
|
if modDiff != nil {
|
|
if err := graphAddDiff(g, opts.Diff, modDiff); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
// Encode the dependencies
|
|
graphEncodeDependencies(g)
|
|
|
|
// Validate
|
|
if err := g.Validate(); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
log.Printf(
|
|
"[DEBUG] Graph %v created and valid. %d nouns.",
|
|
opts.ModulePath,
|
|
len(g.Nouns))
|
|
|
|
return g, nil
|
|
}
|
|
|
|
// graphEncodeDependencies is used to initialize a State with a ResourceState
|
|
// for every resource.
|
|
//
|
|
// This method is very important to call because it will properly setup
|
|
// the ResourceState dependency information with data from the graph. This
|
|
// allows orphaned resources to be destroyed in the proper order.
|
|
func graphEncodeDependencies(g *depgraph.Graph) {
|
|
for _, n := range g.Nouns {
|
|
switch rn := n.Meta.(type) {
|
|
case *GraphNodeResource:
|
|
// If we are using create-before-destroy, there
|
|
// are some special depedencies injected on the
|
|
// deposed node that would cause a circular depedency
|
|
// chain if persisted. We must only handle the new node,
|
|
// node the deposed node.
|
|
r := rn.Resource
|
|
if r.Flags&FlagDeposed != 0 {
|
|
continue
|
|
}
|
|
|
|
// Update the dependencies
|
|
var inject []string
|
|
for _, dep := range n.Deps {
|
|
switch target := dep.Target.Meta.(type) {
|
|
case *GraphNodeModule:
|
|
inject = append(inject, dep.Target.Name)
|
|
|
|
case *GraphNodeResource:
|
|
if target.Resource.Id == r.Id {
|
|
continue
|
|
}
|
|
inject = append(inject, target.Resource.Id)
|
|
|
|
case *GraphNodeResourceProvider:
|
|
// Do nothing
|
|
|
|
default:
|
|
panic(fmt.Sprintf("Unknown graph node: %#v", dep.Target))
|
|
}
|
|
}
|
|
|
|
// Update the dependencies
|
|
r.Dependencies = inject
|
|
|
|
case *GraphNodeModule:
|
|
// Update the dependencies
|
|
var inject []string
|
|
for _, dep := range n.Deps {
|
|
switch target := dep.Target.Meta.(type) {
|
|
case *GraphNodeModule:
|
|
if dep.Target.Name == n.Name {
|
|
continue
|
|
}
|
|
inject = append(inject, dep.Target.Name)
|
|
|
|
case *GraphNodeResource:
|
|
inject = append(inject, target.Resource.Id)
|
|
|
|
case *GraphNodeResourceProvider:
|
|
// Do nothing
|
|
|
|
default:
|
|
panic(fmt.Sprintf("Unknown graph node: %#v", dep.Target))
|
|
}
|
|
|
|
}
|
|
|
|
// Update the dependencies
|
|
if rn.State != nil {
|
|
rn.State.Dependencies = inject
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// graphAddConfigModules adds the modules from a configuration structure
|
|
// into the graph, expanding each to their own sub-graph.
|
|
func graphAddConfigModules(
|
|
g *depgraph.Graph,
|
|
c *config.Config,
|
|
opts *GraphOpts) error {
|
|
// Just short-circuit the whole thing if we don't have modules
|
|
if len(c.Modules) == 0 {
|
|
return nil
|
|
}
|
|
|
|
// Build the list of nouns to add to the graph
|
|
nounsList := make([]*depgraph.Noun, 0, len(c.Modules))
|
|
for _, m := range c.Modules {
|
|
if n, err := graphModuleNoun(m.Name, m, g, opts); err != nil {
|
|
return err
|
|
} else {
|
|
// Attach the module state if any
|
|
if opts.State != nil {
|
|
module := n.Meta.(*GraphNodeModule)
|
|
module.State = opts.State.ModuleByPath(module.Path)
|
|
if module.State == nil {
|
|
module.State = opts.State.AddModule(module.Path)
|
|
}
|
|
}
|
|
nounsList = append(nounsList, n)
|
|
}
|
|
}
|
|
|
|
g.Nouns = append(g.Nouns, nounsList...)
|
|
return nil
|
|
}
|
|
|
|
// configGraph turns a configuration structure into a dependency graph.
|
|
func graphAddConfigResources(
|
|
g *depgraph.Graph, c *config.Config, mod *ModuleState) {
|
|
meta := g.Meta.(*GraphMeta)
|
|
|
|
// This tracks all the resource nouns
|
|
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{
|
|
Index: -1,
|
|
Config: r,
|
|
Resource: &Resource{
|
|
Id: name,
|
|
Info: &InstanceInfo{
|
|
Id: name,
|
|
ModulePath: meta.ModulePath,
|
|
Type: r.Type,
|
|
},
|
|
},
|
|
State: mod.View(name),
|
|
ExpandMode: ResourceExpandApply,
|
|
},
|
|
}
|
|
|
|
/*
|
|
TODO: probably did something important, bring it back somehow
|
|
resourceNouns := make([]*depgraph.Noun, r.Count)
|
|
for i := 0; i < r.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 r.Count > 1 {
|
|
name = fmt.Sprintf("%s.%d", name, i)
|
|
index = i
|
|
}
|
|
|
|
var state *ResourceState
|
|
if mod != nil {
|
|
// Lookup the resource state
|
|
state = mod.Resources[name]
|
|
if state == nil {
|
|
if r.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.
|
|
state = mod.Resources[r.Id()+".0"]
|
|
} 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 = mod.Resources[r.Id()]
|
|
}
|
|
|
|
// TODO(mitchellh): If one of the above works, delete
|
|
// the old style and just copy it to the new style.
|
|
}
|
|
}
|
|
|
|
if state == nil {
|
|
state = &ResourceState{
|
|
Type: r.Type,
|
|
}
|
|
}
|
|
|
|
flags := FlagPrimary
|
|
if len(state.Tainted) > 0 {
|
|
flags |= FlagHasTainted
|
|
}
|
|
|
|
resourceNouns[i] = &depgraph.Noun{
|
|
Name: name,
|
|
Meta: &GraphNodeResource{
|
|
Index: index,
|
|
Config: r,
|
|
Resource: &Resource{
|
|
Id: name,
|
|
Info: &InstanceInfo{
|
|
Id: name,
|
|
ModulePath: meta.ModulePath,
|
|
Type: r.Type,
|
|
},
|
|
State: state.Primary,
|
|
Config: NewResourceConfig(r.RawConfig),
|
|
Flags: flags,
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
// If we have more than one, then create a meta node to track
|
|
// the resources.
|
|
if r.Count > 1 {
|
|
metaNoun := &depgraph.Noun{
|
|
Name: r.Id(),
|
|
Meta: &GraphNodeResourceMeta{
|
|
ID: r.Id(),
|
|
Name: r.Name,
|
|
Type: r.Type,
|
|
Count: r.Count,
|
|
},
|
|
}
|
|
|
|
// Create the dependencies on this noun
|
|
for _, n := range resourceNouns {
|
|
metaNoun.Deps = append(metaNoun.Deps, &depgraph.Dependency{
|
|
Name: n.Name,
|
|
Source: metaNoun,
|
|
Target: n,
|
|
})
|
|
}
|
|
|
|
// Assign it to the map so that we have it
|
|
nouns[metaNoun.Name] = metaNoun
|
|
}
|
|
|
|
for _, n := range resourceNouns {
|
|
nouns[n.Name] = n
|
|
}
|
|
*/
|
|
}
|
|
|
|
g.Name = "terraform"
|
|
g.Nouns = append(g.Nouns, nounsList...)
|
|
}
|
|
|
|
// graphAddDiff takes an already-built graph of resources and adds the
|
|
// diffs to the resource nodes themselves.
|
|
//
|
|
// This may also introduces new graph elements. If there are diffs that
|
|
// require a destroy, new elements may be introduced since destroy order
|
|
// is different than create order. For example, destroying a VPC requires
|
|
// destroying the VPC's subnets first, whereas creating a VPC requires
|
|
// doing it before the subnets are created. This function handles inserting
|
|
// these nodes for you.
|
|
func graphAddDiff(g *depgraph.Graph, gDiff *Diff, d *ModuleDiff) error {
|
|
var nlist []*depgraph.Noun
|
|
var modules []*depgraph.Noun
|
|
injected := make(map[*depgraph.Dependency]struct{})
|
|
for _, n := range g.Nouns {
|
|
// A module is being destroyed if all it's resources are being
|
|
// destroyed (via a destroy plan) or if it is orphaned. Only in
|
|
// those cases do we need to handle depedency inversion.
|
|
if mod, ok := n.Meta.(*GraphNodeModule); ok {
|
|
md := gDiff.ModuleByPath(mod.Path)
|
|
if mod.Flags&FlagOrphan != 0 || (md != nil && md.Destroy) {
|
|
modules = append(modules, n)
|
|
}
|
|
continue
|
|
}
|
|
|
|
rn, ok := n.Meta.(*GraphNodeResource)
|
|
if !ok {
|
|
continue
|
|
}
|
|
if rn.Resource.Flags&FlagTainted != 0 {
|
|
continue
|
|
}
|
|
|
|
change := false
|
|
destroy := false
|
|
diffs := d.Instances(rn.Resource.Id)
|
|
if len(diffs) == 0 {
|
|
continue
|
|
}
|
|
for _, d := range diffs {
|
|
if d.Destroy {
|
|
destroy = true
|
|
}
|
|
|
|
if len(d.Attributes) > 0 {
|
|
change = true
|
|
}
|
|
}
|
|
|
|
// If we're expanding, save the diff so we can add it on later
|
|
if rn.ExpandMode > ResourceExpandNone {
|
|
rn.Diff = d
|
|
}
|
|
|
|
// If we are not expanding, then we assign the
|
|
// instance diff to the resource.
|
|
var rd *InstanceDiff
|
|
if rn.ExpandMode == ResourceExpandNone {
|
|
rd = diffs[0]
|
|
}
|
|
|
|
if destroy {
|
|
// If we're destroying, we create a new destroy node with
|
|
// the proper dependencies. Perform a dirty copy operation.
|
|
newNode := new(GraphNodeResource)
|
|
*newNode = *rn
|
|
newNode.Resource = new(Resource)
|
|
*newNode.Resource = *rn.Resource
|
|
|
|
// Make the diff _just_ the destroy.
|
|
newNode.Resource.Diff = &InstanceDiff{Destroy: true}
|
|
|
|
// Make sure ExpandDestroy is set if Expand
|
|
if newNode.ExpandMode == ResourceExpandApply {
|
|
newNode.ExpandMode = ResourceExpandDestroy
|
|
}
|
|
|
|
// Create the new node
|
|
newN := &depgraph.Noun{
|
|
Name: fmt.Sprintf("%s (destroy)", newNode.Resource.Id),
|
|
Meta: newNode,
|
|
}
|
|
newN.Deps = make([]*depgraph.Dependency, len(n.Deps))
|
|
|
|
// Copy all the dependencies and do a fixup later
|
|
copy(newN.Deps, n.Deps)
|
|
|
|
// Append it to the list so we handle it later
|
|
nlist = append(nlist, newN)
|
|
|
|
if rd != nil {
|
|
// Mark the old diff to not destroy since we handle that in
|
|
// the dedicated node.
|
|
newDiff := new(InstanceDiff)
|
|
*newDiff = *rd
|
|
newDiff.Destroy = false
|
|
rd = newDiff
|
|
}
|
|
|
|
// The dependency ordering depends on if the CreateBeforeDestroy
|
|
// flag is enabled. If so, we must create the replacement first,
|
|
// and then destroy the old instance.
|
|
if rn.Config != nil && rn.Config.Lifecycle.CreateBeforeDestroy && change {
|
|
dep := &depgraph.Dependency{
|
|
Name: n.Name,
|
|
Source: newN,
|
|
Target: n,
|
|
}
|
|
|
|
// Add the old noun to the new noun dependencies so that
|
|
// the create happens before the destroy.
|
|
newN.Deps = append(newN.Deps, dep)
|
|
|
|
// Mark that this dependency has been injected so that
|
|
// we do not invert the direction below.
|
|
injected[dep] = struct{}{}
|
|
|
|
// Add a depedency from the root, since the create node
|
|
// does not depend on us
|
|
if g.Root != nil {
|
|
g.Root.Deps = append(g.Root.Deps, &depgraph.Dependency{
|
|
Name: newN.Name,
|
|
Source: g.Root,
|
|
Target: newN,
|
|
})
|
|
}
|
|
|
|
// Set the ReplacePrimary flag on the new instance so that
|
|
// it will become the new primary, and Diposed flag on the
|
|
// existing instance so that it will step down
|
|
rn.Resource.Flags |= FlagReplacePrimary
|
|
newNode.Resource.Flags |= FlagDeposed
|
|
|
|
// This logic is not intuitive, but we need to make the
|
|
// destroy depend upon any resources that depend on the
|
|
// create. The reason is suppose you have a LB depend on
|
|
// a web server. You need the order to be create, update LB,
|
|
// destroy. Without this, the update LB and destroy can
|
|
// be executed in an arbitrary order (likely in parallel).
|
|
incoming := g.DependsOn(n)
|
|
for _, inc := range incoming {
|
|
// Ignore the root...
|
|
if inc == g.Root {
|
|
continue
|
|
}
|
|
dep := &depgraph.Dependency{
|
|
Name: inc.Name,
|
|
Source: newN,
|
|
Target: inc,
|
|
}
|
|
injected[dep] = struct{}{}
|
|
newN.Deps = append(newN.Deps, dep)
|
|
}
|
|
|
|
} else {
|
|
dep := &depgraph.Dependency{
|
|
Name: newN.Name,
|
|
Source: n,
|
|
Target: newN,
|
|
}
|
|
|
|
// Add the new noun to our dependencies so that
|
|
// the destroy happens before the apply.
|
|
n.Deps = append(n.Deps, dep)
|
|
}
|
|
}
|
|
|
|
rn.Resource.Diff = rd
|
|
}
|
|
|
|
// Go through each resource and module and make sure we
|
|
// calculate all the dependencies properly.
|
|
invertDeps := [][]*depgraph.Noun{nlist, modules}
|
|
for _, list := range invertDeps {
|
|
for _, n := range list {
|
|
deps := n.Deps
|
|
num := len(deps)
|
|
for i := 0; i < num; i++ {
|
|
dep := deps[i]
|
|
|
|
// Check if this dependency was just injected, otherwise
|
|
// we will incorrectly flip the depedency twice.
|
|
if _, ok := injected[dep]; ok {
|
|
continue
|
|
}
|
|
|
|
switch target := dep.Target.Meta.(type) {
|
|
case *GraphNodeResource:
|
|
// If the other node is also being deleted,
|
|
// we must be deleted first. E.g. if A -> B,
|
|
// then when we create, B is created first then A.
|
|
// On teardown, A is destroyed first, then B.
|
|
// Thus we must flip our depedency and instead inject
|
|
// it on B.
|
|
for _, n2 := range nlist {
|
|
rn2 := n2.Meta.(*GraphNodeResource)
|
|
if target.Resource.Id == rn2.Resource.Id {
|
|
newDep := &depgraph.Dependency{
|
|
Name: n.Name,
|
|
Source: n2,
|
|
Target: n,
|
|
}
|
|
injected[newDep] = struct{}{}
|
|
n2.Deps = append(n2.Deps, newDep)
|
|
break
|
|
}
|
|
}
|
|
|
|
// Drop the dependency. We may have created
|
|
// an inverse depedency if the dependent resource
|
|
// is also being deleted, but this dependence is
|
|
// no longer required.
|
|
deps[i], deps[num-1] = deps[num-1], nil
|
|
num--
|
|
i--
|
|
|
|
case *GraphNodeModule:
|
|
// We invert any module dependencies so we're destroyed
|
|
// first, before any modules are applied.
|
|
newDep := &depgraph.Dependency{
|
|
Name: n.Name,
|
|
Source: dep.Target,
|
|
Target: n,
|
|
}
|
|
dep.Target.Deps = append(dep.Target.Deps, newDep)
|
|
|
|
// Drop the dependency. We may have created
|
|
// an inverse depedency if the dependent resource
|
|
// is also being deleted, but this dependence is
|
|
// no longer required.
|
|
deps[i], deps[num-1] = deps[num-1], nil
|
|
num--
|
|
i--
|
|
case *GraphNodeResourceProvider:
|
|
// Keep these around, but fix up the source to be ourselves
|
|
// rather than the old node.
|
|
newDep := *dep
|
|
newDep.Source = n
|
|
deps[i] = &newDep
|
|
default:
|
|
panic(fmt.Errorf("Unhandled depedency type: %#v", dep.Target.Meta))
|
|
}
|
|
}
|
|
n.Deps = deps[:num]
|
|
}
|
|
}
|
|
|
|
// Add the nouns to the graph
|
|
g.Nouns = append(g.Nouns, nlist...)
|
|
|
|
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
|
|
}
|
|
if rn.Config == nil {
|
|
// Orphan. It can't be depended on or have depends (explicit)
|
|
// anyways.
|
|
continue
|
|
}
|
|
|
|
rs[rn.Resource.Id] = n
|
|
if rn.Config != nil && 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.
|
|
func graphAddMissingResourceProviders(
|
|
g *depgraph.Graph,
|
|
ps map[string]ResourceProviderFactory) error {
|
|
var errs []error
|
|
|
|
for _, n := range g.Nouns {
|
|
rn, ok := n.Meta.(*GraphNodeResource)
|
|
if !ok {
|
|
continue
|
|
}
|
|
if rn.ResourceProviderNode != "" {
|
|
continue
|
|
}
|
|
|
|
prefixes := matchingPrefixes(rn.Resource.Info.Type, ps)
|
|
if len(prefixes) == 0 {
|
|
errs = append(errs, fmt.Errorf(
|
|
"No matching provider for type: %s",
|
|
rn.Resource.Info.Type))
|
|
continue
|
|
}
|
|
|
|
// The resource provider ID is simply the shortest matching
|
|
// prefix, since that'll give us the most resource providers
|
|
// to choose from.
|
|
id := prefixes[len(prefixes)-1]
|
|
rn.ResourceProviderNode = fmt.Sprintf("provider.%s", id)
|
|
|
|
// If we don't have a matching noun for this yet, insert it.
|
|
if g.Noun(rn.ResourceProviderNode) == nil {
|
|
pn := &depgraph.Noun{
|
|
Name: rn.ResourceProviderNode,
|
|
Meta: &GraphNodeResourceProvider{
|
|
ID: id,
|
|
Provider: new(graphSharedProvider),
|
|
},
|
|
}
|
|
g.Nouns = append(g.Nouns, pn)
|
|
}
|
|
}
|
|
|
|
if len(errs) > 0 {
|
|
return &multierror.Error{Errors: errs}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func graphAddModuleOrphans(
|
|
g *depgraph.Graph,
|
|
config *config.Config,
|
|
ms []*ModuleState,
|
|
opts *GraphOpts) error {
|
|
// Build a lookup map for the modules we do have defined
|
|
childrenKeys := make(map[string]struct{})
|
|
for _, m := range config.Modules {
|
|
childrenKeys[m.Name] = struct{}{}
|
|
}
|
|
|
|
// Go through each of the child modules. If we don't have it in our
|
|
// config, it is an orphan.
|
|
var nounsList []*depgraph.Noun
|
|
for _, m := range ms {
|
|
k := m.Path[len(m.Path)-1]
|
|
if _, ok := childrenKeys[k]; ok {
|
|
// We have this module configured
|
|
continue
|
|
}
|
|
|
|
if n, err := graphModuleNoun(k, nil, g, opts); err != nil {
|
|
return err
|
|
} else {
|
|
// Mark this module as being an orphan
|
|
module := n.Meta.(*GraphNodeModule)
|
|
module.Flags |= FlagOrphan
|
|
module.State = m
|
|
nounsList = append(nounsList, n)
|
|
}
|
|
}
|
|
|
|
g.Nouns = append(g.Nouns, nounsList...)
|
|
return nil
|
|
}
|
|
|
|
// graphAddOrphanDeps adds the dependencies to the orphans based on their
|
|
// explicit Dependencies state.
|
|
func graphAddOrphanDeps(g *depgraph.Graph, mod *ModuleState) {
|
|
for _, n := range g.Nouns {
|
|
rn, ok := n.Meta.(*GraphNodeResource)
|
|
if !ok {
|
|
continue
|
|
}
|
|
if rn.Resource.Flags&FlagOrphan == 0 {
|
|
continue
|
|
}
|
|
|
|
// If we have no dependencies, then just continue
|
|
rs := mod.Resources[n.Name]
|
|
if len(rs.Dependencies) == 0 {
|
|
continue
|
|
}
|
|
|
|
for _, n2 := range g.Nouns {
|
|
// Don't ever depend on ourselves
|
|
if n2.Meta == n.Meta {
|
|
continue
|
|
}
|
|
|
|
var compareName string
|
|
switch rn2 := n2.Meta.(type) {
|
|
case *GraphNodeModule:
|
|
compareName = n2.Name
|
|
case *GraphNodeResource:
|
|
compareName = rn2.Resource.Id
|
|
}
|
|
if compareName == "" {
|
|
continue
|
|
}
|
|
|
|
for _, depName := range rs.Dependencies {
|
|
if !strings.HasPrefix(depName, compareName) {
|
|
continue
|
|
}
|
|
dep := &depgraph.Dependency{
|
|
Name: depName,
|
|
Source: n,
|
|
Target: n2,
|
|
}
|
|
n.Deps = append(n.Deps, dep)
|
|
break
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// graphAddOrphanModuleDeps adds the dependencies to the orphan
|
|
// modules based on their explicit Dependencies state.
|
|
func graphAddOrphanModuleDeps(g *depgraph.Graph, mod *ModuleState) {
|
|
for _, n := range g.Nouns {
|
|
module, ok := n.Meta.(*GraphNodeModule)
|
|
if !ok {
|
|
continue
|
|
}
|
|
if module.Flags&FlagOrphan == 0 {
|
|
continue
|
|
}
|
|
|
|
// If we have no dependencies, then just continue
|
|
if len(module.State.Dependencies) == 0 {
|
|
continue
|
|
}
|
|
|
|
for _, n2 := range g.Nouns {
|
|
// Don't ever depend on ourselves
|
|
if n2.Meta == n.Meta {
|
|
continue
|
|
}
|
|
|
|
var compareName string
|
|
switch rn2 := n2.Meta.(type) {
|
|
case *GraphNodeModule:
|
|
compareName = n2.Name
|
|
case *GraphNodeResource:
|
|
compareName = rn2.Resource.Id
|
|
}
|
|
if compareName == "" {
|
|
continue
|
|
}
|
|
|
|
for _, depName := range module.State.Dependencies {
|
|
if !strings.HasPrefix(depName, compareName) {
|
|
continue
|
|
}
|
|
dep := &depgraph.Dependency{
|
|
Name: depName,
|
|
Source: n,
|
|
Target: n2,
|
|
}
|
|
n.Deps = append(n.Deps, dep)
|
|
break
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// graphAddOrphans adds the orphans to the graph.
|
|
func graphAddOrphans(g *depgraph.Graph, c *config.Config, mod *ModuleState) {
|
|
meta := g.Meta.(*GraphMeta)
|
|
|
|
var nlist []*depgraph.Noun
|
|
for _, k := range mod.Orphans(c) {
|
|
rs := mod.Resources[k]
|
|
noun := &depgraph.Noun{
|
|
Name: k,
|
|
Meta: &GraphNodeResource{
|
|
Index: -1,
|
|
Resource: &Resource{
|
|
Id: k,
|
|
Info: &InstanceInfo{
|
|
Id: k,
|
|
ModulePath: meta.ModulePath,
|
|
Type: rs.Type,
|
|
},
|
|
State: rs.Primary,
|
|
Config: NewResourceConfig(nil),
|
|
Flags: FlagOrphan,
|
|
},
|
|
},
|
|
}
|
|
|
|
// Append it to the list so we handle it later
|
|
nlist = append(nlist, noun)
|
|
}
|
|
|
|
// Add the nouns to the graph
|
|
g.Nouns = append(g.Nouns, nlist...)
|
|
}
|
|
|
|
// graphAddParentProviderConfigs goes through and adds/merges provider
|
|
// configurations from the parent.
|
|
func graphAddParentProviderConfigs(g, parent *depgraph.Graph) {
|
|
var nounsList []*depgraph.Noun
|
|
for _, n := range parent.Nouns {
|
|
pn, ok := n.Meta.(*GraphNodeResourceProvider)
|
|
if !ok {
|
|
continue
|
|
}
|
|
|
|
// If we have a provider configuration with the exact same
|
|
// name, then set specify the parent pointer to their shared
|
|
// config.
|
|
ourProviderRaw := g.Noun(n.Name)
|
|
|
|
// If we don't have a matching configuration, then create one.
|
|
if ourProviderRaw == nil {
|
|
noun := &depgraph.Noun{
|
|
Name: n.Name,
|
|
Meta: &GraphNodeResourceProvider{
|
|
ID: pn.ID,
|
|
Provider: &graphSharedProvider{
|
|
Parent: pn.Provider,
|
|
parentNoun: n,
|
|
},
|
|
},
|
|
}
|
|
|
|
nounsList = append(nounsList, noun)
|
|
continue
|
|
}
|
|
|
|
// If we have a matching configuration, then set the parent pointer
|
|
ourProvider := ourProviderRaw.Meta.(*GraphNodeResourceProvider)
|
|
ourProvider.Provider.Parent = pn.Provider
|
|
ourProvider.Provider.parentNoun = n
|
|
}
|
|
|
|
g.Nouns = append(g.Nouns, nounsList...)
|
|
}
|
|
|
|
// graphAddConfigProviderConfigs adds a GraphNodeResourceProvider for every
|
|
// `provider` configuration block. Note that a provider may exist that
|
|
// isn't used for any resources. These will be pruned later.
|
|
func graphAddConfigProviderConfigs(g *depgraph.Graph, c *config.Config) {
|
|
nounsList := make([]*depgraph.Noun, 0, len(c.ProviderConfigs))
|
|
for _, pc := range c.ProviderConfigs {
|
|
noun := &depgraph.Noun{
|
|
Name: fmt.Sprintf("provider.%s", pc.Name),
|
|
Meta: &GraphNodeResourceProvider{
|
|
ID: pc.Name,
|
|
Provider: &graphSharedProvider{
|
|
Config: pc,
|
|
},
|
|
},
|
|
}
|
|
|
|
nounsList = append(nounsList, noun)
|
|
}
|
|
|
|
// Add all the provider config nouns to the graph
|
|
g.Nouns = append(g.Nouns, nounsList...)
|
|
}
|
|
|
|
// graphAddRoot adds a root element to the graph so that there is a single
|
|
// root to point to all the dependencies.
|
|
func graphAddRoot(g *depgraph.Graph) {
|
|
root := &depgraph.Noun{Name: GraphRootNode}
|
|
for _, n := range g.Nouns {
|
|
switch n.Meta.(type) {
|
|
case *GraphNodeResourceProvider:
|
|
// ResourceProviders don't need to be in the root deps because
|
|
// they're always pointed to by some resource.
|
|
continue
|
|
}
|
|
|
|
root.Deps = append(root.Deps, &depgraph.Dependency{
|
|
Name: n.Name,
|
|
Source: root,
|
|
Target: n,
|
|
})
|
|
}
|
|
g.Nouns = append(g.Nouns, root)
|
|
g.Root = root
|
|
}
|
|
|
|
// graphAddVariableDeps inspects all the nouns and adds any dependencies
|
|
// based on variable values.
|
|
func graphAddVariableDeps(g *depgraph.Graph) {
|
|
for _, n := range g.Nouns {
|
|
switch m := n.Meta.(type) {
|
|
case *GraphNodeModule:
|
|
if m.Config != nil {
|
|
vars := m.Config.RawConfig.Variables
|
|
nounAddVariableDeps(g, n, vars, false)
|
|
}
|
|
|
|
case *GraphNodeResource:
|
|
if m.Config != nil {
|
|
// Handle the count variables
|
|
vars := m.Config.RawCount.Variables
|
|
nounAddVariableDeps(g, n, vars, false)
|
|
|
|
// Handle the resource variables
|
|
vars = m.Config.RawConfig.Variables
|
|
nounAddVariableDeps(g, n, vars, false)
|
|
}
|
|
|
|
// Handle the variables of the resource provisioners
|
|
for _, p := range m.Resource.Provisioners {
|
|
vars := p.RawConfig.Variables
|
|
nounAddVariableDeps(g, n, vars, true)
|
|
|
|
vars = p.ConnInfo.Variables
|
|
nounAddVariableDeps(g, n, vars, true)
|
|
}
|
|
|
|
case *GraphNodeResourceProvider:
|
|
if m.Provider != nil && m.Provider.Config != nil {
|
|
vars := m.Provider.Config.RawConfig.Variables
|
|
nounAddVariableDeps(g, n, vars, false)
|
|
}
|
|
|
|
default:
|
|
// Other node types don't have dependencies or we don't support it
|
|
continue
|
|
}
|
|
}
|
|
}
|
|
|
|
// graphAddTainted adds the tainted instances to the graph.
|
|
func graphAddTainted(g *depgraph.Graph, mod *ModuleState) {
|
|
meta := g.Meta.(*GraphMeta)
|
|
|
|
var nlist []*depgraph.Noun
|
|
for k, rs := range mod.Resources {
|
|
// If we have no tainted resources, continue on
|
|
if len(rs.Tainted) == 0 {
|
|
continue
|
|
}
|
|
|
|
// Find the untainted resource of this in the noun list. If our
|
|
// name is 3 parts, then we mus be a count instance, so our
|
|
// untainted node is just the noun without the count.
|
|
var untainted *depgraph.Noun
|
|
untaintedK := k
|
|
if ps := strings.Split(k, "."); len(ps) == 3 {
|
|
untaintedK = strings.Join(ps[0:2], ".")
|
|
}
|
|
for _, n := range g.Nouns {
|
|
if n.Name == untaintedK {
|
|
untainted = n
|
|
break
|
|
}
|
|
}
|
|
|
|
for i, is := range rs.Tainted {
|
|
name := fmt.Sprintf("%s (tainted #%d)", k, i+1)
|
|
|
|
// Add each of the tainted resources to the graph, and encode
|
|
// a dependency from the non-tainted resource to this so that
|
|
// tainted resources are always destroyed first.
|
|
noun := &depgraph.Noun{
|
|
Name: name,
|
|
Meta: &GraphNodeResource{
|
|
Index: -1,
|
|
Resource: &Resource{
|
|
Id: k,
|
|
Info: &InstanceInfo{
|
|
Id: k,
|
|
ModulePath: meta.ModulePath,
|
|
Type: rs.Type,
|
|
},
|
|
State: is,
|
|
Config: NewResourceConfig(nil),
|
|
Diff: &InstanceDiff{Destroy: true},
|
|
Flags: FlagTainted,
|
|
TaintedIndex: i,
|
|
},
|
|
},
|
|
}
|
|
|
|
// Append it to the list so we handle it later
|
|
nlist = append(nlist, noun)
|
|
|
|
// If we have an untainted version, then make sure to add
|
|
// the dependency.
|
|
if untainted != nil {
|
|
dep := &depgraph.Dependency{
|
|
Name: name,
|
|
Source: untainted,
|
|
Target: noun,
|
|
}
|
|
|
|
untainted.Deps = append(untainted.Deps, dep)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Add the nouns to the graph
|
|
g.Nouns = append(g.Nouns, nlist...)
|
|
}
|
|
|
|
// graphModuleNoun creates a noun for a module.
|
|
func graphModuleNoun(
|
|
n string, m *config.Module,
|
|
g *depgraph.Graph, opts *GraphOpts) (*depgraph.Noun, error) {
|
|
name := fmt.Sprintf("module.%s", n)
|
|
path := make([]string, len(opts.ModulePath)+1)
|
|
copy(path, opts.ModulePath)
|
|
path[len(opts.ModulePath)] = n
|
|
|
|
// Build the opts we'll use to make the next graph
|
|
subOpts := *opts
|
|
subOpts.ModulePath = path
|
|
subOpts.parent = g
|
|
subGraph, err := Graph(&subOpts)
|
|
if err != nil {
|
|
return nil, fmt.Errorf(
|
|
"Error building module graph '%s': %s",
|
|
n, err)
|
|
}
|
|
|
|
return &depgraph.Noun{
|
|
Name: name,
|
|
Meta: &GraphNodeModule{
|
|
Config: m,
|
|
Path: path,
|
|
Graph: subGraph,
|
|
},
|
|
}, nil
|
|
}
|
|
|
|
// nounAddVariableDeps updates the dependencies of a noun given
|
|
// a set of associated variable values
|
|
func nounAddVariableDeps(
|
|
g *depgraph.Graph,
|
|
n *depgraph.Noun,
|
|
vars map[string]config.InterpolatedVariable,
|
|
removeSelf bool) {
|
|
for _, rawV := range vars {
|
|
var name string
|
|
var target *depgraph.Noun
|
|
|
|
switch v := rawV.(type) {
|
|
case *config.ModuleVariable:
|
|
name = fmt.Sprintf("module.%s", v.Name)
|
|
target = g.Noun(name)
|
|
case *config.ResourceVariable:
|
|
// For resource variables, if we ourselves are a resource, then
|
|
// we have to check whether to expand or not to create the proper
|
|
// resource dependency.
|
|
rn, ok := n.Meta.(*GraphNodeResource)
|
|
if !ok || rn.ExpandMode > ResourceExpandNone {
|
|
name = v.ResourceId()
|
|
target = g.Noun(v.ResourceId())
|
|
break
|
|
}
|
|
|
|
// We're an expanded resource, so add the specific index
|
|
// as the dependency.
|
|
name = fmt.Sprintf("%s.%d", v.ResourceId(), v.Index)
|
|
target = g.Noun(name)
|
|
default:
|
|
}
|
|
|
|
if target == nil {
|
|
continue
|
|
}
|
|
|
|
// If we're ignoring self-references, then don't add that
|
|
// dependency.
|
|
if removeSelf && n == target {
|
|
continue
|
|
}
|
|
|
|
// Build the dependency
|
|
dep := &depgraph.Dependency{
|
|
Name: name,
|
|
Source: n,
|
|
Target: target,
|
|
}
|
|
|
|
n.Deps = append(n.Deps, dep)
|
|
}
|
|
}
|
|
|
|
// graphInitResourceProviders maps the resource providers onto the graph
|
|
// given a mapping of prefixes to resource providers.
|
|
//
|
|
// Unlike the graphAdd* functions, this one can return an error if resource
|
|
// providers can't be found or can't be instantiated.
|
|
func graphInitResourceProviders(
|
|
g *depgraph.Graph,
|
|
ps map[string]ResourceProviderFactory) error {
|
|
var errs []error
|
|
|
|
// Keep track of providers we know we couldn't instantiate so
|
|
// that we don't get a ton of errors about the same provider.
|
|
failures := make(map[string]struct{})
|
|
|
|
for _, n := range g.Nouns {
|
|
// We only care about the resource providers first. There is guaranteed
|
|
// to be only one node per tuple (providerId, providerConfig), which
|
|
// means we don't need to verify we have instantiated it before.
|
|
rn, ok := n.Meta.(*GraphNodeResourceProvider)
|
|
if !ok {
|
|
continue
|
|
}
|
|
|
|
prefixes := matchingPrefixes(rn.ID, ps)
|
|
if len(prefixes) > 0 {
|
|
if _, ok := failures[prefixes[0]]; ok {
|
|
// We already failed this provider, meaning this
|
|
// resource will never succeed, so just continue.
|
|
continue
|
|
}
|
|
}
|
|
|
|
sharedProvider := rn.Provider
|
|
|
|
// Go through each prefix and instantiate if necessary, then
|
|
// verify if this provider is of use to us or not.
|
|
sharedProvider.Providers = make(map[string]ResourceProvider)
|
|
sharedProvider.ProviderKeys = prefixes
|
|
for _, prefix := range prefixes {
|
|
p, err := ps[prefix]()
|
|
if err != nil {
|
|
errs = append(errs, fmt.Errorf(
|
|
"Error instantiating resource provider for "+
|
|
"prefix %s: %s", prefix, err))
|
|
|
|
// Record the error so that we don't check it again
|
|
failures[prefix] = struct{}{}
|
|
|
|
// Jump to the next prefix
|
|
continue
|
|
}
|
|
|
|
sharedProvider.Providers[prefix] = p
|
|
}
|
|
|
|
// If we never found a provider, then error and continue
|
|
if len(sharedProvider.Providers) == 0 {
|
|
errs = append(errs, fmt.Errorf(
|
|
"Provider for configuration '%s' not found.",
|
|
rn.ID))
|
|
continue
|
|
}
|
|
}
|
|
|
|
if len(errs) > 0 {
|
|
return &multierror.Error{Errors: errs}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// graphAddResourceProviderDeps goes through all the nodes in the graph
|
|
// and adds any dependencies to resource providers as needed.
|
|
func graphAddResourceProviderDeps(g *depgraph.Graph) {
|
|
for _, rawN := range g.Nouns {
|
|
switch n := rawN.Meta.(type) {
|
|
case *GraphNodeModule:
|
|
// Check if the module depends on any of our providers
|
|
// by seeing if there is a parent node back.
|
|
for _, moduleRaw := range n.Graph.Nouns {
|
|
pn, ok := moduleRaw.Meta.(*GraphNodeResourceProvider)
|
|
if !ok {
|
|
continue
|
|
}
|
|
if pn.Provider.parentNoun == nil {
|
|
continue
|
|
}
|
|
|
|
// Create the dependency to the provider
|
|
dep := &depgraph.Dependency{
|
|
Name: pn.Provider.parentNoun.Name,
|
|
Source: rawN,
|
|
Target: pn.Provider.parentNoun,
|
|
}
|
|
rawN.Deps = append(rawN.Deps, dep)
|
|
}
|
|
case *GraphNodeResource:
|
|
// Not sure how this would happen, but we might as well
|
|
// check for it.
|
|
if n.ResourceProviderNode == "" {
|
|
continue
|
|
}
|
|
|
|
// Get the noun this depends on.
|
|
target := g.Noun(n.ResourceProviderNode)
|
|
|
|
// Create the dependency to the provider
|
|
dep := &depgraph.Dependency{
|
|
Name: target.Name,
|
|
Source: rawN,
|
|
Target: target,
|
|
}
|
|
rawN.Deps = append(rawN.Deps, dep)
|
|
}
|
|
}
|
|
}
|
|
|
|
// graphPruneResourceProviders will remove the GraphNodeResourceProvider
|
|
// nodes that aren't used in any way.
|
|
func graphPruneResourceProviders(g *depgraph.Graph) {
|
|
// First, build a mapping of the providers we have.
|
|
ps := make(map[string]struct{})
|
|
for _, n := range g.Nouns {
|
|
_, ok := n.Meta.(*GraphNodeResourceProvider)
|
|
if !ok {
|
|
continue
|
|
}
|
|
|
|
ps[n.Name] = struct{}{}
|
|
}
|
|
|
|
// Now go through all the dependencies throughout and find
|
|
// if any of these aren't reachable.
|
|
for _, n := range g.Nouns {
|
|
for _, dep := range n.Deps {
|
|
delete(ps, dep.Target.Name)
|
|
}
|
|
}
|
|
|
|
if len(ps) == 0 {
|
|
// We used all of them!
|
|
return
|
|
}
|
|
|
|
// Now go through and remove these nodes that aren't used
|
|
for i := 0; i < len(g.Nouns); i++ {
|
|
if _, ok := ps[g.Nouns[i].Name]; !ok {
|
|
continue
|
|
}
|
|
|
|
// Delete this node
|
|
copy(g.Nouns[i:], g.Nouns[i+1:])
|
|
g.Nouns[len(g.Nouns)-1] = nil
|
|
g.Nouns = g.Nouns[:len(g.Nouns)-1]
|
|
i--
|
|
}
|
|
}
|
|
|
|
// graphMapResourceProviderId goes through the graph and maps the
|
|
// ID of a resource provider node to each resource. This lets us know which
|
|
// configuration is for which resource.
|
|
//
|
|
// This is safe to call multiple times.
|
|
func graphMapResourceProviderId(g *depgraph.Graph) {
|
|
// Build the list of provider configs we have
|
|
ps := make(map[string]string)
|
|
for _, n := range g.Nouns {
|
|
pn, ok := n.Meta.(*GraphNodeResourceProvider)
|
|
if !ok {
|
|
continue
|
|
}
|
|
|
|
ps[n.Name] = pn.ID
|
|
}
|
|
|
|
// Go through every resource and find the shortest matching provider
|
|
for _, n := range g.Nouns {
|
|
rn, ok := n.Meta.(*GraphNodeResource)
|
|
if !ok {
|
|
continue
|
|
}
|
|
|
|
var match, matchNode string
|
|
for n, p := range ps {
|
|
if !strings.HasPrefix(rn.Resource.Info.Type, p) {
|
|
continue
|
|
}
|
|
if len(p) > len(match) {
|
|
match = p
|
|
matchNode = n
|
|
}
|
|
}
|
|
if matchNode == "" {
|
|
continue
|
|
}
|
|
|
|
rn.ResourceProviderNode = matchNode
|
|
}
|
|
}
|
|
|
|
// graphMapResourceProviders takes a graph that already has initialized
|
|
// the resource providers (using graphInitResourceProviders) and maps the
|
|
// resource providers to the resources themselves.
|
|
func graphMapResourceProviders(g *depgraph.Graph) error {
|
|
var errs []error
|
|
|
|
// First build a mapping of resource provider ID to the node that
|
|
// contains those resources.
|
|
mapping := make(map[string]*GraphNodeResourceProvider)
|
|
for _, n := range g.Nouns {
|
|
rn, ok := n.Meta.(*GraphNodeResourceProvider)
|
|
if !ok {
|
|
continue
|
|
}
|
|
mapping[rn.ID] = rn
|
|
}
|
|
|
|
// Now go through each of the resources and find a matching provider.
|
|
for _, n := range g.Nouns {
|
|
rn, ok := n.Meta.(*GraphNodeResource)
|
|
if !ok {
|
|
continue
|
|
}
|
|
|
|
rpnRaw := g.Noun(rn.ResourceProviderNode)
|
|
if rpnRaw == nil {
|
|
// This should never happen since when building the graph
|
|
// we ensure that everything matches up.
|
|
panic(fmt.Sprintf(
|
|
"Resource provider not found: %s (type: %s)",
|
|
rn.ResourceProviderNode,
|
|
rn.Resource.Info.Type))
|
|
}
|
|
rpn := rpnRaw.Meta.(*GraphNodeResourceProvider)
|
|
|
|
var provider ResourceProvider
|
|
for _, k := range rpn.Provider.ProviderKeys {
|
|
// Only try this provider if it has the right prefix
|
|
if !strings.HasPrefix(rn.Resource.Info.Type, k) {
|
|
continue
|
|
}
|
|
|
|
rp := rpn.Provider.Providers[k]
|
|
if ProviderSatisfies(rp, rn.Resource.Info.Type) {
|
|
provider = rp
|
|
break
|
|
}
|
|
}
|
|
|
|
if provider == nil {
|
|
errs = append(errs, fmt.Errorf(
|
|
"Resource provider not found for resource type '%s'",
|
|
rn.Resource.Info.Type))
|
|
continue
|
|
}
|
|
|
|
rn.Resource.Provider = provider
|
|
}
|
|
|
|
if len(errs) > 0 {
|
|
return &multierror.Error{Errors: errs}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// graphMapResourceProvisioners takes a graph that already has
|
|
// the resources and maps the resource provisioners to the resources themselves.
|
|
func graphMapResourceProvisioners(g *depgraph.Graph,
|
|
provisioners map[string]ResourceProvisionerFactory) error {
|
|
var errs []error
|
|
|
|
// Create a cache of resource provisioners, avoids duplicate
|
|
// initialization of the instances
|
|
cache := make(map[string]ResourceProvisioner)
|
|
|
|
// Go through each of the resources and find a matching provisioners
|
|
for _, n := range g.Nouns {
|
|
rn, ok := n.Meta.(*GraphNodeResource)
|
|
if !ok {
|
|
continue
|
|
}
|
|
|
|
// Ignore orphan nodes with no provisioners
|
|
if rn.Config == nil {
|
|
continue
|
|
}
|
|
|
|
// Check each provisioner
|
|
for _, p := range rn.Config.Provisioners {
|
|
// Check for a cached provisioner
|
|
provisioner, ok := cache[p.Type]
|
|
if !ok {
|
|
// Lookup the factory method
|
|
factory, ok := provisioners[p.Type]
|
|
if !ok {
|
|
errs = append(errs, fmt.Errorf(
|
|
"Resource provisioner not found for provisioner type '%s'",
|
|
p.Type))
|
|
continue
|
|
}
|
|
|
|
// Initialize the provisioner
|
|
prov, err := factory()
|
|
if err != nil {
|
|
errs = append(errs, fmt.Errorf(
|
|
"Failed to instantiate provisioner type '%s': %v",
|
|
p.Type, err))
|
|
continue
|
|
}
|
|
provisioner = prov
|
|
|
|
// Cache this type of provisioner
|
|
cache[p.Type] = prov
|
|
}
|
|
|
|
// Save the provisioner
|
|
rn.Resource.Provisioners = append(rn.Resource.Provisioners, &ResourceProvisionerConfig{
|
|
Type: p.Type,
|
|
Provisioner: provisioner,
|
|
Config: NewResourceConfig(p.RawConfig),
|
|
RawConfig: p.RawConfig,
|
|
ConnInfo: p.ConnInfo,
|
|
})
|
|
}
|
|
}
|
|
|
|
if len(errs) > 0 {
|
|
return &multierror.Error{Errors: errs}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// grpahRemoveInvalidDeps goes through the graph and removes dependencies
|
|
// that no longer exist.
|
|
func graphRemoveInvalidDeps(g *depgraph.Graph) {
|
|
check := make(map[*depgraph.Noun]struct{})
|
|
for _, n := range g.Nouns {
|
|
check[n] = struct{}{}
|
|
}
|
|
for _, n := range g.Nouns {
|
|
deps := n.Deps
|
|
num := len(deps)
|
|
for i := 0; i < num; i++ {
|
|
if _, ok := check[deps[i].Target]; !ok {
|
|
deps[i], deps[num-1] = deps[num-1], nil
|
|
i--
|
|
num--
|
|
}
|
|
}
|
|
n.Deps = deps[:num]
|
|
}
|
|
}
|
|
|
|
// MergeConfig merges all the configurations in the proper order
|
|
// to result in the final configuration to use to configure this
|
|
// provider.
|
|
func (p *graphSharedProvider) MergeConfig(
|
|
raw bool, override map[string]interface{}) *ResourceConfig {
|
|
var rawMap map[string]interface{}
|
|
if p.Config != nil {
|
|
rawMap = p.Config.RawConfig.Config()
|
|
}
|
|
if rawMap == nil {
|
|
rawMap = make(map[string]interface{})
|
|
}
|
|
for k, v := range override {
|
|
rawMap[k] = v
|
|
}
|
|
|
|
// Merge in all the parent configurations
|
|
if p.Parent != nil {
|
|
parent := p.Parent
|
|
for parent != nil {
|
|
if parent.Config != nil {
|
|
var merge map[string]interface{}
|
|
if raw {
|
|
merge = parent.Config.RawConfig.Raw
|
|
} else {
|
|
merge = parent.Config.RawConfig.Config()
|
|
}
|
|
|
|
for k, v := range merge {
|
|
rawMap[k] = v
|
|
}
|
|
}
|
|
|
|
parent = parent.Parent
|
|
}
|
|
}
|
|
|
|
rc, err := config.NewRawConfig(rawMap)
|
|
if err != nil {
|
|
panic("error building config: " + err.Error())
|
|
}
|
|
|
|
return NewResourceConfig(rc)
|
|
}
|
|
|
|
// Expand will expand this node into a subgraph if Expand is set.
|
|
func (n *GraphNodeResource) Expand() (*depgraph.Graph, error) {
|
|
// Expand the count out, which should be interpolated at this point.
|
|
count, err := n.Config.Count()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
log.Printf("[DEBUG] %s: expanding to count = %d", n.Resource.Id, count)
|
|
|
|
// TODO: can we DRY this up?
|
|
g := new(depgraph.Graph)
|
|
g.Meta = &GraphMeta{
|
|
ModulePath: n.Resource.Info.ModulePath,
|
|
}
|
|
|
|
// Do the initial expansion of the nodes, attaching diffs if
|
|
// applicable
|
|
n.expand(g, count, n.Diff)
|
|
|
|
// Add all the variable dependencies
|
|
graphAddVariableDeps(g)
|
|
|
|
// Filter the nodes depending on the expansion type
|
|
switch n.ExpandMode {
|
|
case ResourceExpandApply:
|
|
n.filterResources(g, false)
|
|
case ResourceExpandDestroy:
|
|
n.filterResources(g, true)
|
|
default:
|
|
panic(fmt.Sprintf("Unhandled expansion mode %d", n.ExpandMode))
|
|
}
|
|
|
|
// Return the finalized graph
|
|
return g, n.finalizeGraph(g)
|
|
}
|
|
|
|
// expand expands this resource and adds the resources to the graph. It
|
|
// adds both create and destroy resources.
|
|
func (n *GraphNodeResource) expand(g *depgraph.Graph, count int, diff *ModuleDiff) {
|
|
// Create the list of nouns
|
|
result := make([]*depgraph.Noun, 0, count)
|
|
|
|
// Build the key set so we know what is removed
|
|
var keys map[string]struct{}
|
|
if n.State != nil {
|
|
keys = make(map[string]struct{})
|
|
for k, _ := range n.State.Resources {
|
|
keys[k] = struct{}{}
|
|
}
|
|
}
|
|
|
|
// First thing, expand the counts that we have defined for our
|
|
// current config into the full set of resources that are being
|
|
// created.
|
|
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
|
|
delete(keys, name)
|
|
}
|
|
|
|
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"
|
|
if state == nil {
|
|
state = n.State.Resources[k]
|
|
}
|
|
delete(keys, 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
|
|
if state == nil {
|
|
state = n.State.Resources[r.Id()]
|
|
}
|
|
delete(keys, r.Id())
|
|
}
|
|
}
|
|
|
|
// Add in the diff if we have it
|
|
var inDiff *InstanceDiff
|
|
if diff != nil {
|
|
// Looup the instance diff
|
|
if d, ok := diff.Resources[name]; ok {
|
|
inDiff = d
|
|
}
|
|
|
|
if inDiff == 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"
|
|
inDiff = diff.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
|
|
inDiff = diff.Resources[r.Id()]
|
|
}
|
|
}
|
|
}
|
|
|
|
// Initialize a default state if not available
|
|
if state == nil {
|
|
state = &ResourceState{
|
|
Type: r.Type,
|
|
}
|
|
}
|
|
|
|
// Prepare the diff if it exists
|
|
if inDiff != nil {
|
|
switch n.ExpandMode {
|
|
case ResourceExpandApply:
|
|
// Disable Destroy if we aren't doing a destroy expansion.
|
|
// There is a seperate expansion for the destruction action.
|
|
d := new(InstanceDiff)
|
|
*d = *inDiff
|
|
inDiff = d
|
|
inDiff.Destroy = false
|
|
|
|
// If we require a new resource, there is a seperate delete
|
|
// phase, so the create phase must not have access to the ID.
|
|
if inDiff.RequiresNew() {
|
|
s := new(ResourceState)
|
|
*s = *state
|
|
state = s
|
|
state.Primary = nil
|
|
}
|
|
|
|
case ResourceExpandDestroy:
|
|
// If we are doing a destroy, make sure it is exclusively
|
|
// a destroy, since there is a seperate expansion for the apply
|
|
inDiff = new(InstanceDiff)
|
|
inDiff.Destroy = true
|
|
|
|
default:
|
|
panic(fmt.Sprintf("Unhandled expansion mode %d", n.ExpandMode))
|
|
}
|
|
}
|
|
|
|
// Inherit the existing flags!
|
|
flags := n.Resource.Flags
|
|
if len(state.Tainted) > 0 {
|
|
flags |= FlagHasTainted
|
|
}
|
|
|
|
// Copy the base resource so we can fill it in
|
|
resource := n.copyResource(name)
|
|
resource.CountIndex = i
|
|
resource.State = state.Primary
|
|
resource.Flags = flags
|
|
resource.Diff = inDiff
|
|
|
|
// Add the result
|
|
result = append(result, &depgraph.Noun{
|
|
Name: name,
|
|
Meta: &GraphNodeResource{
|
|
Index: index,
|
|
Config: r,
|
|
Resource: resource,
|
|
},
|
|
})
|
|
}
|
|
|
|
// Go over the leftover keys which are orphans (decreasing counts)
|
|
for k, _ := range keys {
|
|
rs := n.State.Resources[k]
|
|
|
|
resource := n.copyResource(k)
|
|
resource.Config = NewResourceConfig(nil)
|
|
resource.State = rs.Primary
|
|
resource.Flags = FlagOrphan
|
|
resource.Diff = &InstanceDiff{Destroy: true}
|
|
|
|
noun := &depgraph.Noun{
|
|
Name: k,
|
|
Meta: &GraphNodeResource{
|
|
Index: -1,
|
|
Resource: resource,
|
|
},
|
|
}
|
|
|
|
result = append(result, noun)
|
|
}
|
|
|
|
g.Nouns = append(g.Nouns, result...)
|
|
}
|
|
|
|
// 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)
|
|
resource.Diff = nil
|
|
return &resource
|
|
}
|
|
|
|
// filterResources is used to remove resources from the sub-graph based
|
|
// on the ExpandMode. This is because there is a Destroy sub-graph, and
|
|
// Apply sub-graph, and we cannot includes the same instances in both
|
|
// sub-graphs.
|
|
func (n *GraphNodeResource) filterResources(g *depgraph.Graph, destroy bool) {
|
|
result := make([]*depgraph.Noun, 0, len(g.Nouns))
|
|
for _, n := range g.Nouns {
|
|
rn, ok := n.Meta.(*GraphNodeResource)
|
|
if !ok {
|
|
continue
|
|
}
|
|
|
|
if destroy {
|
|
if rn.Resource.Diff != nil && rn.Resource.Diff.Destroy {
|
|
result = append(result, n)
|
|
}
|
|
continue
|
|
}
|
|
|
|
if rn.Resource.Flags&FlagOrphan != 0 ||
|
|
rn.Resource.Diff == nil || !rn.Resource.Diff.Destroy {
|
|
result = append(result, n)
|
|
}
|
|
}
|
|
g.Nouns = result
|
|
}
|
|
|
|
// finalizeGraph is used to ensure the generated graph is valid
|
|
func (n *GraphNodeResource) finalizeGraph(g *depgraph.Graph) error {
|
|
// Remove the dependencies that don't exist
|
|
graphRemoveInvalidDeps(g)
|
|
|
|
// Build the root so that we have a single valid root
|
|
graphAddRoot(g)
|
|
|
|
// Validate
|
|
if err := g.Validate(); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// 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.
|
|
//
|
|
// The list returned is in the order that they should be attempted.
|
|
func matchingPrefixes(
|
|
t string,
|
|
ps map[string]ResourceProviderFactory) []string {
|
|
result := make([]string, 0, 1)
|
|
for prefix, _ := range ps {
|
|
if strings.HasPrefix(t, prefix) {
|
|
result = append(result, prefix)
|
|
}
|
|
}
|
|
|
|
// Sort by longest first
|
|
sort.Sort(stringLenSort(result))
|
|
|
|
return result
|
|
}
|
|
|
|
// stringLenSort implements sort.Interface and sorts strings in increasing
|
|
// length order. i.e. "a", "aa", "aaa"
|
|
type stringLenSort []string
|
|
|
|
func (s stringLenSort) Len() int {
|
|
return len(s)
|
|
}
|
|
|
|
func (s stringLenSort) Less(i, j int) bool {
|
|
return len(s[i]) < len(s[j])
|
|
}
|
|
|
|
func (s stringLenSort) Swap(i, j int) {
|
|
s[i], s[j] = s[j], s[i]
|
|
}
|