Merge pull request #7 from hashicorp/f-graph-refactor

terraform: Graph() refactor
This commit is contained in:
Mitchell Hashimoto 2014-06-30 21:26:36 -07:00
commit 935573d01c
3 changed files with 158 additions and 153 deletions

View File

@ -1,6 +1,7 @@
package terraform
import (
"errors"
"fmt"
"sort"
"strings"
@ -9,6 +10,36 @@ import (
"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
// graph. This node is just a placemarker and has no associated functionality.
const GraphRootNode = "root"
@ -31,12 +62,8 @@ type GraphNodeResourceProvider struct {
Config *config.ProviderConfig
}
// Graph builds a dependency graph for the given configuration and state.
//
// 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.
// 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.
@ -49,20 +76,24 @@ type GraphNodeResourceProvider struct {
// *GraphNodeResourceProvider - A resource provider that needs to be
// 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)
// First, build the initial resource graph. This only has the resources
// and no dependencies.
graphAddConfigResources(g, c, s)
graphAddConfigResources(g, opts.Config, opts.State)
// Next, add the state orphans if we have any
if s != nil {
graphAddOrphans(g, c, s)
if opts.State != nil {
graphAddOrphans(g, opts.Config, opts.State)
}
// Map the provider configurations to all of the resources
graphAddProviderConfigs(g, c)
graphAddProviderConfigs(g, opts.Config)
// Add all the variable dependencies
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
graphAddRoot(g)
return g
}
// GraphFull completes the raw graph returned by Graph by initializing
// all the resource providers.
//
// This may add new nodes to the graph, since it can add new resource
// providers based on the mapping given in the case that a provider
// configuration was not specified.
//
// Various errors can be returned from this function, such as if there
// is no matching provider for a resource, a resource provider can't be
// created, etc.
func GraphFull(g *depgraph.Graph, ps map[string]ResourceProviderFactory) error {
// 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, ps); err != nil {
return err
if err := graphAddMissingResourceProviders(g, opts.Providers); err != nil {
return nil, err
}
// Initialize all the providers
if err := graphInitResourceProviders(g, ps); err != nil {
return err
if err := graphInitResourceProviders(g, opts.Providers); err != nil {
return nil, err
}
// Map the providers to resources
if err := graphMapResourceProviders(g); err != nil {
return err
return nil, err
}
}
return nil
// 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
}
// GraphAddDiff takes an already-built graph of resources and adds the
// 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...)
}
// 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
@ -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
// doing it before the subnets are created. This function handles inserting
// these nodes for you.
func GraphAddDiff(g *depgraph.Graph, d *Diff) error {
func graphAddDiff(g *depgraph.Graph, d *Diff) error {
var nlist []*depgraph.Noun
for _, n := range g.Nouns {
rn, ok := n.Meta.(*GraphNodeResource)
@ -199,46 +272,6 @@ func GraphAddDiff(g *depgraph.Graph, d *Diff) error {
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
// the resources that do not have an explicit resource provider specified
// because no provider configuration was given.

View File

@ -9,8 +9,8 @@ import (
func TestGraph(t *testing.T) {
config := testConfig(t, "graph-basic")
g := Graph(config, nil)
if err := g.Validate(); err != nil {
g, err := Graph(&GraphOpts{Config: config})
if err != nil {
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) {
config := testConfig(t, "graph-cycle")
g := Graph(config, nil)
if err := g.Validate(); err == nil {
_, err := Graph(&GraphOpts{Config: config})
if err == nil {
t.Fatal("should error")
}
}
@ -41,8 +47,8 @@ func TestGraph_state(t *testing.T) {
},
}
g := Graph(config, state)
if err := g.Validate(); err != nil {
g, err := Graph(&GraphOpts{Config: config, State: state})
if err != nil {
t.Fatalf("err: %s", err)
}
@ -72,11 +78,8 @@ func TestGraphFull(t *testing.T) {
}
c := testConfig(t, "graph-basic")
g := Graph(c, nil)
if err := GraphFull(g, ps); err != nil {
t.Fatalf("err: %s", err)
}
if err := g.Validate(); err != nil {
g, err := Graph(&GraphOpts{Config: c, Providers: ps})
if err != nil {
t.Fatalf("err: %s", err)
}
@ -112,12 +115,6 @@ func TestGraphFull(t *testing.T) {
func TestGraphAddDiff(t *testing.T) {
config := testConfig(t, "graph-diff")
g := Graph(config, nil)
if err := g.Validate(); err != nil {
t.Fatalf("err: %s", err)
}
diff := &Diff{
Resources: map[string]*ResourceDiff{
"aws_instance.foo": &ResourceDiff{
@ -130,10 +127,8 @@ func TestGraphAddDiff(t *testing.T) {
},
}
if err := GraphAddDiff(g, diff); err != nil {
t.Fatalf("err: %s", err)
}
if err := g.Validate(); err != nil {
g, err := Graph(&GraphOpts{Config: config, Diff: diff})
if err != nil {
t.Fatalf("err: %s", err)
}
@ -156,6 +151,16 @@ func TestGraphAddDiff(t *testing.T) {
func TestGraphAddDiff_destroy(t *testing.T) {
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{
Resources: map[string]*ResourceState{
"aws_instance.foo": &ResourceState{
@ -175,26 +180,12 @@ func TestGraphAddDiff_destroy(t *testing.T) {
},
}
g := Graph(config, state)
if err := g.Validate(); err != nil {
t.Fatalf("err: %s", err)
}
diff := &Diff{
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 {
g, err := Graph(&GraphOpts{
Config: config,
Diff: diff,
State: state,
})
if err != nil {
t.Fatalf("err: %s", err)
}

View File

@ -46,7 +46,12 @@ func (t *Terraform) Apply(p *Plan) (*State, error) {
// everywhere, and is instead just empty otherwise.
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 {
return nil, err
}
@ -54,33 +59,12 @@ func (t *Terraform) Apply(p *Plan) (*State, error) {
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) {
g, err := t.Graph(opts.Config, opts.State)
g, err := Graph(&GraphOpts{
Config: opts.Config,
Providers: t.providers,
State: opts.State,
})
if err != nil {
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
// to their latest status.
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 {
return s, err
}
@ -102,13 +90,6 @@ func (t *Terraform) Refresh(c *config.Config, s *State) (*State, error) {
func (t *Terraform) apply(
g *depgraph.Graph,
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)
err := g.Walk(t.applyWalkFn(s, p))
return s, err