Merge pull request #7 from hashicorp/f-graph-refactor
terraform: Graph() refactor
This commit is contained in:
commit
935573d01c
|
@ -1,6 +1,7 @@
|
||||||
package terraform
|
package terraform
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
@ -9,6 +10,36 @@ import (
|
||||||
"github.com/hashicorp/terraform/depgraph"
|
"github.com/hashicorp/terraform/depgraph"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// 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
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
// GraphRootNode is the name of the root node in the Terraform resource
|
// GraphRootNode is the name of the root node in the Terraform resource
|
||||||
// graph. This node is just a placemarker and has no associated functionality.
|
// graph. This node is just a placemarker and has no associated functionality.
|
||||||
const GraphRootNode = "root"
|
const GraphRootNode = "root"
|
||||||
|
@ -31,12 +62,8 @@ type GraphNodeResourceProvider struct {
|
||||||
Config *config.ProviderConfig
|
Config *config.ProviderConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
// Graph builds a dependency graph for the given configuration and state.
|
// Graph builds a dependency graph of all the resources for infrastructure
|
||||||
//
|
// change.
|
||||||
// Before using this graph, Validate should be called on it. This will perform
|
|
||||||
// some initialization necessary such as setting up a root node. This function
|
|
||||||
// doesn't perform the Validate automatically in case the caller wants to
|
|
||||||
// modify the graph.
|
|
||||||
//
|
//
|
||||||
// This dependency graph shows the correct order that any resources need
|
// This dependency graph shows the correct order that any resources need
|
||||||
// to be operated on.
|
// to be operated on.
|
||||||
|
@ -49,20 +76,24 @@ type GraphNodeResourceProvider struct {
|
||||||
// *GraphNodeResourceProvider - A resource provider that needs to be
|
// *GraphNodeResourceProvider - A resource provider that needs to be
|
||||||
// configured at this point.
|
// configured at this point.
|
||||||
//
|
//
|
||||||
func Graph(c *config.Config, s *State) *depgraph.Graph {
|
func Graph(opts *GraphOpts) (*depgraph.Graph, error) {
|
||||||
|
if opts.Config == nil {
|
||||||
|
return nil, errors.New("Config is required for Graph")
|
||||||
|
}
|
||||||
|
|
||||||
g := new(depgraph.Graph)
|
g := new(depgraph.Graph)
|
||||||
|
|
||||||
// First, build the initial resource graph. This only has the resources
|
// First, build the initial resource graph. This only has the resources
|
||||||
// and no dependencies.
|
// and no dependencies.
|
||||||
graphAddConfigResources(g, c, s)
|
graphAddConfigResources(g, opts.Config, opts.State)
|
||||||
|
|
||||||
// Next, add the state orphans if we have any
|
// Next, add the state orphans if we have any
|
||||||
if s != nil {
|
if opts.State != nil {
|
||||||
graphAddOrphans(g, c, s)
|
graphAddOrphans(g, opts.Config, opts.State)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Map the provider configurations to all of the resources
|
// Map the provider configurations to all of the resources
|
||||||
graphAddProviderConfigs(g, c)
|
graphAddProviderConfigs(g, opts.Config)
|
||||||
|
|
||||||
// Add all the variable dependencies
|
// Add all the variable dependencies
|
||||||
graphAddVariableDeps(g)
|
graphAddVariableDeps(g)
|
||||||
|
@ -70,39 +101,81 @@ func Graph(c *config.Config, s *State) *depgraph.Graph {
|
||||||
// Build the root so that we have a single valid root
|
// Build the root so that we have a single valid root
|
||||||
graphAddRoot(g)
|
graphAddRoot(g)
|
||||||
|
|
||||||
return g
|
// If providers were given, lets associate the proper providers and
|
||||||
|
// instantiate them.
|
||||||
|
if len(opts.Providers) > 0 {
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we have a diff, then make sure to add that in
|
||||||
|
if opts.Diff != nil {
|
||||||
|
if err := graphAddDiff(g, opts.Diff); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate
|
||||||
|
if err := g.Validate(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return g, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GraphFull completes the raw graph returned by Graph by initializing
|
// configGraph turns a configuration structure into a dependency graph.
|
||||||
// all the resource providers.
|
func graphAddConfigResources(
|
||||||
//
|
g *depgraph.Graph, c *config.Config, s *State) {
|
||||||
// This may add new nodes to the graph, since it can add new resource
|
// This tracks all the resource nouns
|
||||||
// providers based on the mapping given in the case that a provider
|
nouns := make(map[string]*depgraph.Noun)
|
||||||
// configuration was not specified.
|
for _, r := range c.Resources {
|
||||||
//
|
var state *ResourceState
|
||||||
// Various errors can be returned from this function, such as if there
|
if s != nil {
|
||||||
// is no matching provider for a resource, a resource provider can't be
|
state = s.Resources[r.Id()]
|
||||||
// created, etc.
|
}
|
||||||
func GraphFull(g *depgraph.Graph, ps map[string]ResourceProviderFactory) error {
|
if state == nil {
|
||||||
// Add missing providers from the mapping
|
state = &ResourceState{
|
||||||
if err := graphAddMissingResourceProviders(g, ps); err != nil {
|
Type: r.Type,
|
||||||
return err
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
noun := &depgraph.Noun{
|
||||||
|
Name: r.Id(),
|
||||||
|
Meta: &GraphNodeResource{
|
||||||
|
Type: r.Type,
|
||||||
|
Config: r,
|
||||||
|
Resource: &Resource{
|
||||||
|
Id: r.Id(),
|
||||||
|
State: state,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
nouns[noun.Name] = noun
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize all the providers
|
// Build the list of nouns that we iterate over
|
||||||
if err := graphInitResourceProviders(g, ps); err != nil {
|
nounsList := make([]*depgraph.Noun, 0, len(nouns))
|
||||||
return err
|
for _, n := range nouns {
|
||||||
|
nounsList = append(nounsList, n)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Map the providers to resources
|
g.Name = "terraform"
|
||||||
if err := graphMapResourceProviders(g); err != nil {
|
g.Nouns = append(g.Nouns, nounsList...)
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GraphAddDiff takes an already-built graph of resources and adds the
|
// graphAddDiff takes an already-built graph of resources and adds the
|
||||||
// diffs to the resource nodes themselves.
|
// diffs to the resource nodes themselves.
|
||||||
//
|
//
|
||||||
// This may also introduces new graph elements. If there are diffs that
|
// This may also introduces new graph elements. If there are diffs that
|
||||||
|
@ -111,7 +184,7 @@ func GraphFull(g *depgraph.Graph, ps map[string]ResourceProviderFactory) error {
|
||||||
// destroying the VPC's subnets first, whereas creating 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
|
// doing it before the subnets are created. This function handles inserting
|
||||||
// these nodes for you.
|
// these nodes for you.
|
||||||
func GraphAddDiff(g *depgraph.Graph, d *Diff) error {
|
func graphAddDiff(g *depgraph.Graph, d *Diff) error {
|
||||||
var nlist []*depgraph.Noun
|
var nlist []*depgraph.Noun
|
||||||
for _, n := range g.Nouns {
|
for _, n := range g.Nouns {
|
||||||
rn, ok := n.Meta.(*GraphNodeResource)
|
rn, ok := n.Meta.(*GraphNodeResource)
|
||||||
|
@ -199,46 +272,6 @@ func GraphAddDiff(g *depgraph.Graph, d *Diff) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// configGraph turns a configuration structure into a dependency graph.
|
|
||||||
func graphAddConfigResources(
|
|
||||||
g *depgraph.Graph, c *config.Config, s *State) {
|
|
||||||
// This tracks all the resource nouns
|
|
||||||
nouns := make(map[string]*depgraph.Noun)
|
|
||||||
for _, r := range c.Resources {
|
|
||||||
var state *ResourceState
|
|
||||||
if s != nil {
|
|
||||||
state = s.Resources[r.Id()]
|
|
||||||
}
|
|
||||||
if state == nil {
|
|
||||||
state = &ResourceState{
|
|
||||||
Type: r.Type,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
noun := &depgraph.Noun{
|
|
||||||
Name: r.Id(),
|
|
||||||
Meta: &GraphNodeResource{
|
|
||||||
Type: r.Type,
|
|
||||||
Config: r,
|
|
||||||
Resource: &Resource{
|
|
||||||
Id: r.Id(),
|
|
||||||
State: state,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
nouns[noun.Name] = noun
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build the list of nouns that we iterate over
|
|
||||||
nounsList := make([]*depgraph.Noun, 0, len(nouns))
|
|
||||||
for _, n := range nouns {
|
|
||||||
nounsList = append(nounsList, n)
|
|
||||||
}
|
|
||||||
|
|
||||||
g.Name = "terraform"
|
|
||||||
g.Nouns = append(g.Nouns, nounsList...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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.
|
||||||
|
|
|
@ -9,8 +9,8 @@ import (
|
||||||
func TestGraph(t *testing.T) {
|
func TestGraph(t *testing.T) {
|
||||||
config := testConfig(t, "graph-basic")
|
config := testConfig(t, "graph-basic")
|
||||||
|
|
||||||
g := Graph(config, nil)
|
g, err := Graph(&GraphOpts{Config: config})
|
||||||
if err := g.Validate(); err != nil {
|
if err != nil {
|
||||||
t.Fatalf("err: %s", err)
|
t.Fatalf("err: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -21,11 +21,17 @@ func TestGraph(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestGraph_configRequired(t *testing.T) {
|
||||||
|
if _, err := Graph(new(GraphOpts)); err == nil {
|
||||||
|
t.Fatal("should error")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestGraph_cycle(t *testing.T) {
|
func TestGraph_cycle(t *testing.T) {
|
||||||
config := testConfig(t, "graph-cycle")
|
config := testConfig(t, "graph-cycle")
|
||||||
|
|
||||||
g := Graph(config, nil)
|
_, err := Graph(&GraphOpts{Config: config})
|
||||||
if err := g.Validate(); err == nil {
|
if err == nil {
|
||||||
t.Fatal("should error")
|
t.Fatal("should error")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -41,8 +47,8 @@ func TestGraph_state(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
g := Graph(config, state)
|
g, err := Graph(&GraphOpts{Config: config, State: state})
|
||||||
if err := g.Validate(); err != nil {
|
if err != nil {
|
||||||
t.Fatalf("err: %s", err)
|
t.Fatalf("err: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -72,11 +78,8 @@ func TestGraphFull(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
c := testConfig(t, "graph-basic")
|
c := testConfig(t, "graph-basic")
|
||||||
g := Graph(c, nil)
|
g, err := Graph(&GraphOpts{Config: c, Providers: ps})
|
||||||
if err := GraphFull(g, ps); err != nil {
|
if err != nil {
|
||||||
t.Fatalf("err: %s", err)
|
|
||||||
}
|
|
||||||
if err := g.Validate(); err != nil {
|
|
||||||
t.Fatalf("err: %s", err)
|
t.Fatalf("err: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -112,12 +115,6 @@ func TestGraphFull(t *testing.T) {
|
||||||
|
|
||||||
func TestGraphAddDiff(t *testing.T) {
|
func TestGraphAddDiff(t *testing.T) {
|
||||||
config := testConfig(t, "graph-diff")
|
config := testConfig(t, "graph-diff")
|
||||||
|
|
||||||
g := Graph(config, nil)
|
|
||||||
if err := g.Validate(); err != nil {
|
|
||||||
t.Fatalf("err: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
diff := &Diff{
|
diff := &Diff{
|
||||||
Resources: map[string]*ResourceDiff{
|
Resources: map[string]*ResourceDiff{
|
||||||
"aws_instance.foo": &ResourceDiff{
|
"aws_instance.foo": &ResourceDiff{
|
||||||
|
@ -130,10 +127,8 @@ func TestGraphAddDiff(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := GraphAddDiff(g, diff); err != nil {
|
g, err := Graph(&GraphOpts{Config: config, Diff: diff})
|
||||||
t.Fatalf("err: %s", err)
|
if err != nil {
|
||||||
}
|
|
||||||
if err := g.Validate(); err != nil {
|
|
||||||
t.Fatalf("err: %s", err)
|
t.Fatalf("err: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -156,6 +151,16 @@ func TestGraphAddDiff(t *testing.T) {
|
||||||
|
|
||||||
func TestGraphAddDiff_destroy(t *testing.T) {
|
func TestGraphAddDiff_destroy(t *testing.T) {
|
||||||
config := testConfig(t, "graph-diff-destroy")
|
config := testConfig(t, "graph-diff-destroy")
|
||||||
|
diff := &Diff{
|
||||||
|
Resources: map[string]*ResourceDiff{
|
||||||
|
"aws_instance.foo": &ResourceDiff{
|
||||||
|
Destroy: true,
|
||||||
|
},
|
||||||
|
"aws_instance.bar": &ResourceDiff{
|
||||||
|
Destroy: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
state := &State{
|
state := &State{
|
||||||
Resources: map[string]*ResourceState{
|
Resources: map[string]*ResourceState{
|
||||||
"aws_instance.foo": &ResourceState{
|
"aws_instance.foo": &ResourceState{
|
||||||
|
@ -175,26 +180,12 @@ func TestGraphAddDiff_destroy(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
g := Graph(config, state)
|
g, err := Graph(&GraphOpts{
|
||||||
if err := g.Validate(); err != nil {
|
Config: config,
|
||||||
t.Fatalf("err: %s", err)
|
Diff: diff,
|
||||||
}
|
State: state,
|
||||||
|
})
|
||||||
diff := &Diff{
|
if err != nil {
|
||||||
Resources: map[string]*ResourceDiff{
|
|
||||||
"aws_instance.foo": &ResourceDiff{
|
|
||||||
Destroy: true,
|
|
||||||
},
|
|
||||||
"aws_instance.bar": &ResourceDiff{
|
|
||||||
Destroy: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := GraphAddDiff(g, diff); err != nil {
|
|
||||||
t.Fatalf("err: %s", err)
|
|
||||||
}
|
|
||||||
if err := g.Validate(); err != nil {
|
|
||||||
t.Fatalf("err: %s", err)
|
t.Fatalf("err: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -46,7 +46,12 @@ func (t *Terraform) Apply(p *Plan) (*State, error) {
|
||||||
// everywhere, and is instead just empty otherwise.
|
// everywhere, and is instead just empty otherwise.
|
||||||
p.init()
|
p.init()
|
||||||
|
|
||||||
g, err := t.Graph(p.Config, p.State)
|
g, err := Graph(&GraphOpts{
|
||||||
|
Config: p.Config,
|
||||||
|
Diff: p.Diff,
|
||||||
|
Providers: t.providers,
|
||||||
|
State: p.State,
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -54,33 +59,12 @@ func (t *Terraform) Apply(p *Plan) (*State, error) {
|
||||||
return t.apply(g, p)
|
return t.apply(g, p)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Graph returns the dependency graph for the given configuration and
|
|
||||||
// state file.
|
|
||||||
//
|
|
||||||
// The resulting graph may have more resources than the configuration, because
|
|
||||||
// it can contain resources in the state file that need to be modified.
|
|
||||||
func (t *Terraform) Graph(c *config.Config, s *State) (*depgraph.Graph, error) {
|
|
||||||
// Get the basic graph with the raw metadata
|
|
||||||
g := Graph(c, s)
|
|
||||||
if err := g.Validate(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fill the graph with the providers
|
|
||||||
if err := GraphFull(g, t.providers); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate the graph so that it can setup a root and such
|
|
||||||
if err := g.Validate(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return g, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *Terraform) Plan(opts *PlanOpts) (*Plan, error) {
|
func (t *Terraform) Plan(opts *PlanOpts) (*Plan, error) {
|
||||||
g, err := t.Graph(opts.Config, opts.State)
|
g, err := Graph(&GraphOpts{
|
||||||
|
Config: opts.Config,
|
||||||
|
Providers: t.providers,
|
||||||
|
State: opts.State,
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -91,7 +75,11 @@ func (t *Terraform) Plan(opts *PlanOpts) (*Plan, error) {
|
||||||
// Refresh goes through all the resources in the state and refreshes them
|
// Refresh goes through all the resources in the state and refreshes them
|
||||||
// to their latest status.
|
// to their latest status.
|
||||||
func (t *Terraform) Refresh(c *config.Config, s *State) (*State, error) {
|
func (t *Terraform) Refresh(c *config.Config, s *State) (*State, error) {
|
||||||
g, err := t.Graph(c, s)
|
g, err := Graph(&GraphOpts{
|
||||||
|
Config: c,
|
||||||
|
Providers: t.providers,
|
||||||
|
State: s,
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return s, err
|
return s, err
|
||||||
}
|
}
|
||||||
|
@ -102,13 +90,6 @@ func (t *Terraform) Refresh(c *config.Config, s *State) (*State, error) {
|
||||||
func (t *Terraform) apply(
|
func (t *Terraform) apply(
|
||||||
g *depgraph.Graph,
|
g *depgraph.Graph,
|
||||||
p *Plan) (*State, error) {
|
p *Plan) (*State, error) {
|
||||||
if err := GraphAddDiff(g, p.Diff); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if err := g.Validate(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
s := new(State)
|
s := new(State)
|
||||||
err := g.Walk(t.applyWalkFn(s, p))
|
err := g.Walk(t.applyWalkFn(s, p))
|
||||||
return s, err
|
return s, err
|
||||||
|
|
Loading…
Reference in New Issue