terraform: update graph to build subgraphs for modules
This commit is contained in:
parent
97da02c368
commit
a6f792b3aa
|
@ -1023,6 +1023,9 @@ func (c *Context) genericWalkFn(cb genericWalkFunc) depgraph.WalkFunc {
|
||||||
}()
|
}()
|
||||||
|
|
||||||
switch m := n.Meta.(type) {
|
switch m := n.Meta.(type) {
|
||||||
|
case *GraphNodeModule:
|
||||||
|
// TODO
|
||||||
|
return nil
|
||||||
case *GraphNodeResource:
|
case *GraphNodeResource:
|
||||||
// Continue, we care about this the most
|
// Continue, we care about this the most
|
||||||
case *GraphNodeResourceMeta:
|
case *GraphNodeResourceMeta:
|
||||||
|
|
|
@ -2179,6 +2179,67 @@ func TestContextRefresh_hook(t *testing.T) {
|
||||||
*/
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestContextRefresh_modules(t *testing.T) {
|
||||||
|
p := testProvider("aws")
|
||||||
|
m := testModule(t, "refresh-modules")
|
||||||
|
state := &State{
|
||||||
|
Modules: []*ModuleState{
|
||||||
|
&ModuleState{
|
||||||
|
Path: rootModulePath,
|
||||||
|
Resources: map[string]*ResourceState{
|
||||||
|
"aws_instance.web": &ResourceState{
|
||||||
|
Type: "aws_instance",
|
||||||
|
Tainted: []*InstanceState{
|
||||||
|
&InstanceState{
|
||||||
|
ID: "bar",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
&ModuleState{
|
||||||
|
Path: []string{"root", "child"},
|
||||||
|
Resources: map[string]*ResourceState{
|
||||||
|
"aws_instance.web": &ResourceState{
|
||||||
|
Type: "aws_instance",
|
||||||
|
Primary: &InstanceState{
|
||||||
|
ID: "baz",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
ctx := testContext(t, &ContextOpts{
|
||||||
|
Module: m,
|
||||||
|
Providers: map[string]ResourceProviderFactory{
|
||||||
|
"aws": testProviderFuncFixed(p),
|
||||||
|
},
|
||||||
|
State: state,
|
||||||
|
})
|
||||||
|
|
||||||
|
p.RefreshFn = func(info *InstanceInfo, s *InstanceState) (*InstanceState, error) {
|
||||||
|
if s.ID != "baz" {
|
||||||
|
return s, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
s.ID = "new"
|
||||||
|
return s, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
s, err := ctx.Refresh()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
actual := strings.TrimSpace(s.String())
|
||||||
|
expected := strings.TrimSpace(testContextRefreshModuleStr)
|
||||||
|
if actual != expected {
|
||||||
|
t.Fatalf("bad:\n\n%s\n\n%s", actual, expected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestContextRefresh_state(t *testing.T) {
|
func TestContextRefresh_state(t *testing.T) {
|
||||||
p := testProvider("aws")
|
p := testProvider("aws")
|
||||||
m := testModule(t, "refresh-basic")
|
m := testModule(t, "refresh-basic")
|
||||||
|
@ -2476,6 +2537,16 @@ root
|
||||||
root -> aws_instance.foo
|
root -> aws_instance.foo
|
||||||
`
|
`
|
||||||
|
|
||||||
|
const testContextRefreshModuleStr = `
|
||||||
|
aws_instance.web: (1 tainted)
|
||||||
|
ID = <not created>
|
||||||
|
Tainted ID 1 = foo
|
||||||
|
|
||||||
|
module.child:
|
||||||
|
aws_instance.web:
|
||||||
|
ID = new
|
||||||
|
`
|
||||||
|
|
||||||
const testContextRefreshTaintedStr = `
|
const testContextRefreshTaintedStr = `
|
||||||
aws_instance.web: (1 tainted)
|
aws_instance.web: (1 tainted)
|
||||||
ID = <not created>
|
ID = <not created>
|
||||||
|
|
|
@ -24,8 +24,13 @@ type GraphOpts struct {
|
||||||
//Config *config.Config
|
//Config *config.Config
|
||||||
|
|
||||||
// Module is the relative root of a module tree for this graph. This
|
// Module is the relative root of a module tree for this graph. This
|
||||||
// is the only required item.
|
// is the only required item. This should always be the absolute root
|
||||||
Module *module.Tree
|
// 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
|
// Diff of changes that will be applied to the given state. This will
|
||||||
// associate a ResourceDiff with applicable resources. Additionally,
|
// associate a ResourceDiff with applicable resources. Additionally,
|
||||||
|
@ -59,6 +64,8 @@ const GraphRootNode = "root"
|
||||||
// that will be created/managed.
|
// that will be created/managed.
|
||||||
type GraphNodeModule struct {
|
type GraphNodeModule struct {
|
||||||
Config *config.Module
|
Config *config.Module
|
||||||
|
Path []string
|
||||||
|
Graph *depgraph.Graph
|
||||||
}
|
}
|
||||||
|
|
||||||
// GraphNodeResource is a node type in the graph that represents a resource
|
// GraphNodeResource is a node type in the graph that represents a resource
|
||||||
|
@ -109,8 +116,27 @@ func Graph(opts *GraphOpts) (*depgraph.Graph, error) {
|
||||||
if opts.Module == nil {
|
if opts.Module == nil {
|
||||||
return nil, errors.New("Module is required for Graph")
|
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")
|
||||||
|
}
|
||||||
|
|
||||||
config := opts.Module.Config()
|
// 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]
|
||||||
|
}
|
||||||
|
|
||||||
|
config := currentModule.Config()
|
||||||
|
|
||||||
|
// Get the state of the module that we're working with.
|
||||||
|
var mod *ModuleState
|
||||||
|
if opts.State != nil {
|
||||||
|
mod = opts.State.ModuleByPath(opts.ModulePath)
|
||||||
|
}
|
||||||
|
|
||||||
log.Printf("[DEBUG] Creating graph...")
|
log.Printf("[DEBUG] Creating graph...")
|
||||||
|
|
||||||
|
@ -119,7 +145,7 @@ func Graph(opts *GraphOpts) (*depgraph.Graph, error) {
|
||||||
// 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. This only adds resources that are in the config
|
// 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).
|
// and not "orphans" (that are in the state, but not in the config).
|
||||||
graphAddConfigResources(g, config, opts.State)
|
graphAddConfigResources(g, config, mod)
|
||||||
|
|
||||||
// Add the modules that are in the configuration.
|
// Add the modules that are in the configuration.
|
||||||
graphAddConfigModules(g, config, opts)
|
graphAddConfigModules(g, config, opts)
|
||||||
|
@ -127,12 +153,12 @@ func Graph(opts *GraphOpts) (*depgraph.Graph, error) {
|
||||||
// Add explicit dependsOn dependencies to the graph
|
// Add explicit dependsOn dependencies to the graph
|
||||||
graphAddExplicitDeps(g)
|
graphAddExplicitDeps(g)
|
||||||
|
|
||||||
if opts.State != nil {
|
if mod != nil {
|
||||||
// Next, add the state orphans if we have any
|
// Next, add the state orphans if we have any
|
||||||
graphAddOrphans(g, config, opts.State)
|
graphAddOrphans(g, config, mod)
|
||||||
|
|
||||||
// Add tainted resources if we have any.
|
// Add tainted resources if we have any.
|
||||||
graphAddTainted(g, opts.State)
|
graphAddTainted(g, mod)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Map the provider configurations to all of the resources
|
// Map the provider configurations to all of the resources
|
||||||
|
@ -238,20 +264,39 @@ func graphInitState(s *State, g *depgraph.Graph) {
|
||||||
|
|
||||||
// graphAddConfigModules adds the modules from a configuration structure
|
// graphAddConfigModules adds the modules from a configuration structure
|
||||||
// into the graph, expanding each to their own sub-graph.
|
// into the graph, expanding each to their own sub-graph.
|
||||||
func graphAddConfigModules(g *depgraph.Graph, c *config.Config, opts *GraphOpts) {
|
func graphAddConfigModules(
|
||||||
|
g *depgraph.Graph,
|
||||||
|
c *config.Config,
|
||||||
|
opts *GraphOpts) error {
|
||||||
// Just short-circuit the whole thing if we don't have modules
|
// Just short-circuit the whole thing if we don't have modules
|
||||||
if len(c.Modules) == 0 {
|
if len(c.Modules) == 0 {
|
||||||
return
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build the list of nouns to add to the graph
|
// Build the list of nouns to add to the graph
|
||||||
nounsList := make([]*depgraph.Noun, 0, len(c.Modules))
|
nounsList := make([]*depgraph.Noun, 0, len(c.Modules))
|
||||||
for _, m := range c.Modules {
|
for _, m := range c.Modules {
|
||||||
name := fmt.Sprintf("module.%s", m.Name)
|
name := fmt.Sprintf("module.%s", m.Name)
|
||||||
|
path := make([]string, len(opts.ModulePath)+1)
|
||||||
|
copy(path, opts.ModulePath)
|
||||||
|
path[len(opts.ModulePath)] = m.Name
|
||||||
|
|
||||||
|
// Build the opts we'll use to make the next graph
|
||||||
|
subOpts := *opts
|
||||||
|
subOpts.ModulePath = path
|
||||||
|
subGraph, err := Graph(&subOpts)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf(
|
||||||
|
"Error building module graph '%s': %s",
|
||||||
|
m.Name, err)
|
||||||
|
}
|
||||||
|
|
||||||
n := &depgraph.Noun{
|
n := &depgraph.Noun{
|
||||||
Name: name,
|
Name: name,
|
||||||
Meta: &GraphNodeModule{
|
Meta: &GraphNodeModule{
|
||||||
Config: m,
|
Config: m,
|
||||||
|
Path: path,
|
||||||
|
Graph: subGraph,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -259,14 +304,12 @@ func graphAddConfigModules(g *depgraph.Graph, c *config.Config, opts *GraphOpts)
|
||||||
}
|
}
|
||||||
|
|
||||||
g.Nouns = append(g.Nouns, nounsList...)
|
g.Nouns = append(g.Nouns, nounsList...)
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// configGraph turns a configuration structure into a dependency graph.
|
// configGraph turns a configuration structure into a dependency graph.
|
||||||
func graphAddConfigResources(
|
func graphAddConfigResources(
|
||||||
g *depgraph.Graph, c *config.Config, s *State) {
|
g *depgraph.Graph, c *config.Config, mod *ModuleState) {
|
||||||
// TODO: Handle non-root modules
|
|
||||||
mod := s.ModuleByPath(rootModulePath)
|
|
||||||
|
|
||||||
// This tracks all the resource nouns
|
// This tracks all the resource nouns
|
||||||
nouns := make(map[string]*depgraph.Noun)
|
nouns := make(map[string]*depgraph.Noun)
|
||||||
for _, r := range c.Resources {
|
for _, r := range c.Resources {
|
||||||
|
@ -283,7 +326,7 @@ func graphAddConfigResources(
|
||||||
}
|
}
|
||||||
|
|
||||||
var state *ResourceState
|
var state *ResourceState
|
||||||
if s != nil && mod != nil {
|
if mod != nil {
|
||||||
// Lookup the resource state
|
// Lookup the resource state
|
||||||
state = mod.Resources[name]
|
state = mod.Resources[name]
|
||||||
if state == nil {
|
if state == nil {
|
||||||
|
@ -633,12 +676,7 @@ func graphAddMissingResourceProviders(
|
||||||
}
|
}
|
||||||
|
|
||||||
// graphAddOrphans adds the orphans to the graph.
|
// graphAddOrphans adds the orphans to the graph.
|
||||||
func graphAddOrphans(g *depgraph.Graph, c *config.Config, s *State) {
|
func graphAddOrphans(g *depgraph.Graph, c *config.Config, mod *ModuleState) {
|
||||||
// TODO: Handle other modules
|
|
||||||
mod := s.ModuleByPath(rootModulePath)
|
|
||||||
if mod == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var nlist []*depgraph.Noun
|
var nlist []*depgraph.Noun
|
||||||
for _, k := range mod.Orphans(c) {
|
for _, k := range mod.Orphans(c) {
|
||||||
rs := mod.Resources[k]
|
rs := mod.Resources[k]
|
||||||
|
@ -826,13 +864,7 @@ func graphAddVariableDeps(g *depgraph.Graph) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// graphAddTainted adds the tainted instances to the graph.
|
// graphAddTainted adds the tainted instances to the graph.
|
||||||
func graphAddTainted(g *depgraph.Graph, s *State) {
|
func graphAddTainted(g *depgraph.Graph, mod *ModuleState) {
|
||||||
// TODO: Handle other modules
|
|
||||||
mod := s.ModuleByPath(rootModulePath)
|
|
||||||
if mod == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var nlist []*depgraph.Noun
|
var nlist []*depgraph.Noun
|
||||||
for k, rs := range mod.Resources {
|
for k, rs := range mod.Resources {
|
||||||
// If we have no tainted resources, continue on
|
// If we have no tainted resources, continue on
|
||||||
|
|
|
@ -94,6 +94,22 @@ func TestGraph_modules(t *testing.T) {
|
||||||
if actual != expected {
|
if actual != expected {
|
||||||
t.Fatalf("bad:\n\n%s", actual)
|
t.Fatalf("bad:\n\n%s", actual)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
n := g.Noun("module.consul")
|
||||||
|
if n == nil {
|
||||||
|
t.Fatal("can't find noun")
|
||||||
|
}
|
||||||
|
mn := n.Meta.(*GraphNodeModule)
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(mn.Path, []string{"root", "consul"}) {
|
||||||
|
t.Fatalf("bad: %#v", mn.Path)
|
||||||
|
}
|
||||||
|
|
||||||
|
actual = strings.TrimSpace(mn.Graph.String())
|
||||||
|
expected = strings.TrimSpace(testTerraformGraphModulesConsulStr)
|
||||||
|
if actual != expected {
|
||||||
|
t.Fatalf("bad:\n\n%s", actual)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGraph_state(t *testing.T) {
|
func TestGraph_state(t *testing.T) {
|
||||||
|
@ -799,6 +815,13 @@ root
|
||||||
root -> module.consul
|
root -> module.consul
|
||||||
`
|
`
|
||||||
|
|
||||||
|
const testTerraformGraphModulesConsulStr = `
|
||||||
|
root: root
|
||||||
|
aws_instance.server
|
||||||
|
root
|
||||||
|
root -> aws_instance.server
|
||||||
|
`
|
||||||
|
|
||||||
const testTerraformGraphStateStr = `
|
const testTerraformGraphStateStr = `
|
||||||
root: root
|
root: root
|
||||||
aws_instance.old
|
aws_instance.old
|
||||||
|
|
|
@ -114,11 +114,11 @@ func (s *State) String() string {
|
||||||
|
|
||||||
// If we're the root module, we just write the output directly.
|
// If we're the root module, we just write the output directly.
|
||||||
if reflect.DeepEqual(m.Path, rootModulePath) {
|
if reflect.DeepEqual(m.Path, rootModulePath) {
|
||||||
buf.WriteString(mStr)
|
buf.WriteString(mStr+"\n")
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
buf.WriteString(fmt.Sprintf("%s:\n", strings.Join(m.Path, ".")))
|
buf.WriteString(fmt.Sprintf("module.%s:\n", strings.Join(m.Path[1:], ".")))
|
||||||
|
|
||||||
s := bufio.NewScanner(strings.NewReader(mStr))
|
s := bufio.NewScanner(strings.NewReader(mStr))
|
||||||
for s.Scan() {
|
for s.Scan() {
|
||||||
|
|
|
@ -5,6 +5,8 @@ import (
|
||||||
"crypto/sha1"
|
"crypto/sha1"
|
||||||
"encoding/gob"
|
"encoding/gob"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"sync"
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
|
@ -31,6 +33,18 @@ func checksumStruct(t *testing.T, i interface{}) string {
|
||||||
return hex.EncodeToString(sum[:])
|
return hex.EncodeToString(sum[:])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func tempDir(t *testing.T) string {
|
||||||
|
dir, err := ioutil.TempDir("", "tf")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
if err := os.RemoveAll(dir); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return dir
|
||||||
|
}
|
||||||
|
|
||||||
func testConfig(t *testing.T, name string) *config.Config {
|
func testConfig(t *testing.T, name string) *config.Config {
|
||||||
c, err := config.Load(filepath.Join(fixtureDir, name, "main.tf"))
|
c, err := config.Load(filepath.Join(fixtureDir, name, "main.tf"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -46,6 +60,11 @@ func testModule(t *testing.T, name string) *module.Tree {
|
||||||
t.Fatalf("err: %s", err)
|
t.Fatalf("err: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
s := &module.FolderStorage{StorageDir: tempDir(t)}
|
||||||
|
if err := mod.Load(s, module.GetModeGet); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
return mod
|
return mod
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
resource "aws_instance" "server" {}
|
|
@ -1,5 +1,6 @@
|
||||||
module "consul" {
|
module "consul" {
|
||||||
foo = "${aws_security_group.firewall.foo}"
|
foo = "${aws_security_group.firewall.foo}"
|
||||||
|
source = "./consul"
|
||||||
}
|
}
|
||||||
|
|
||||||
provider "aws" {}
|
provider "aws" {}
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
resource "aws_instance" "web" {}
|
|
@ -0,0 +1,5 @@
|
||||||
|
module "child" {
|
||||||
|
source = "./child"
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_instance" "web" {}
|
Loading…
Reference in New Issue