diff --git a/terraform/graph.go b/terraform/graph.go index 97eed7f0f..271386f80 100644 --- a/terraform/graph.go +++ b/terraform/graph.go @@ -9,6 +9,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. If specified, + // State becomes required. 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" @@ -49,20 +79,20 @@ 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) { 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,36 +100,78 @@ func Graph(c *config.Config, s *State) *depgraph.Graph { // Build the root so that we have a single valid root 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 -// 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 { - // Add missing providers from the mapping - if err := graphAddMissingResourceProviders(g, ps); err != nil { - return err +// 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 } - // Initialize all the providers - if err := graphInitResourceProviders(g, ps); err != nil { - return err + // Build the list of nouns that we iterate over + nounsList := make([]*depgraph.Noun, 0, len(nouns)) + for _, n := range nouns { + nounsList = append(nounsList, n) } - // Map the providers to resources - if err := graphMapResourceProviders(g); err != nil { - return err - } - - return nil + g.Name = "terraform" + g.Nouns = append(g.Nouns, nounsList...) } // GraphAddDiff takes an already-built graph of resources and adds the @@ -111,7 +183,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 +271,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. diff --git a/terraform/graph_test.go b/terraform/graph_test.go index ed379a1c6..04821ed66 100644 --- a/terraform/graph_test.go +++ b/terraform/graph_test.go @@ -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) } @@ -24,8 +24,8 @@ func TestGraph(t *testing.T) { 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 +41,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 +72,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 +109,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 +121,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 +145,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 +174,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) } diff --git a/terraform/terraform.go b/terraform/terraform.go index 149bd9f6e..92e3a1f70 100644 --- a/terraform/terraform.go +++ b/terraform/terraform.go @@ -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