diff --git a/terraform/transform_config.go b/terraform/transform_config.go index dd1ed4844..c2dad20c9 100644 --- a/terraform/transform_config.go +++ b/terraform/transform_config.go @@ -3,10 +3,10 @@ package terraform import ( "errors" "fmt" + "log" - "github.com/hashicorp/go-multierror" - "github.com/hashicorp/terraform/config" "github.com/hashicorp/terraform/config/module" + "github.com/hashicorp/terraform/dag" ) // ConfigTransformer is a GraphTransformer that adds all the resources @@ -21,6 +21,8 @@ import ( // all resources including module resources, rather than creating module // nodes that are then "flattened". type ConfigTransformer struct { + Concrete ConcreteResourceNodeFunc + Module *module.Tree } @@ -35,82 +37,60 @@ func (t *ConfigTransformer) Transform(g *Graph) error { return errors.New("module must be loaded for ConfigTransformer") } - // Get the module we care about - module := t.Module.Child(g.Path[1:]) - if module == nil { + // Start the transformation process + return t.transform(g, t.Module) +} + +func (t *ConfigTransformer) transform(g *Graph, m *module.Tree) error { + // If no config, do nothing + if m == nil { return nil } - // Get the configuration for this module - config := module.Config() - - // Create the node list we'll use for the graph - nodes := make([]graphNodeConfig, 0, - (len(config.Variables)+ - len(config.ProviderConfigs)+ - len(config.Modules)+ - len(config.Resources)+ - len(config.Outputs))*2) - - // Write all the variables out - for _, v := range config.Variables { - nodes = append(nodes, &GraphNodeConfigVariable{ - Variable: v, - ModuleTree: t.Module, - ModulePath: g.Path, - }) + // Add our resources + if err := t.transformSingle(g, m); err != nil { + return err } - // Write all the provider configs out - for _, pc := range config.ProviderConfigs { - nodes = append(nodes, &GraphNodeConfigProvider{Provider: pc}) - } - - // Write all the resources out - for _, r := range config.Resources { - nodes = append(nodes, &GraphNodeConfigResource{ - Resource: r, - Path: g.Path, - }) - } - - // Write all the modules out - children := module.Children() - for _, m := range config.Modules { - path := make([]string, len(g.Path), len(g.Path)+1) - copy(path, g.Path) - path = append(path, m.Name) - - nodes = append(nodes, &GraphNodeConfigModule{ - Path: path, - Module: m, - Tree: children[m.Name], - }) - } - - // Write all the outputs out - for _, o := range config.Outputs { - nodes = append(nodes, &GraphNodeConfigOutput{Output: o}) - } - - // Err is where the final error value will go if there is one - var err error - - // Build the graph vertices - for _, n := range nodes { - g.Add(n) - } - - // Build up the dependencies. We have to do this outside of the above - // loop since the nodes need to be in place for us to build the deps. - for _, n := range nodes { - if missing := g.ConnectDependent(n); len(missing) > 0 { - for _, m := range missing { - err = multierror.Append(err, fmt.Errorf( - "%s: missing dependency: %s", n.Name(), m)) - } + // Transform all the children. + for _, c := range m.Children() { + if err := t.transform(g, c); err != nil { + return err } } - return err + return nil +} + +func (t *ConfigTransformer) transformSingle(g *Graph, m *module.Tree) error { + log.Printf("[TRACE] ConfigTransformer: Starting for path: %v", m.Path()) + + // Get the configuration for this module + config := m.Config() + + // Build the path we're at + path := m.Path() + + // Write all the resources out + for _, r := range config.Resources { + // Build the resource address + addr, err := parseResourceAddressConfig(r) + if err != nil { + panic(fmt.Sprintf( + "Error parsing config address, this is a bug: %#v", r)) + } + addr.Path = path + + // Build the abstract node and the concrete one + abstract := &NodeAbstractResource{Addr: addr} + var node dag.Vertex = abstract + if f := t.Concrete; f != nil { + node = f(abstract) + } + + // Add it to the graph + g.Add(node) + } + + return nil } diff --git a/terraform/transform_config_test.go b/terraform/transform_config_test.go new file mode 100644 index 000000000..81ce1fcab --- /dev/null +++ b/terraform/transform_config_test.go @@ -0,0 +1,56 @@ +package terraform + +import ( + "path/filepath" + "strings" + "testing" + + "github.com/hashicorp/terraform/config/module" +) + +func TestConfigTransformer_nilModule(t *testing.T) { + g := Graph{Path: RootModulePath} + tf := &ConfigTransformer{} + if err := tf.Transform(&g); err != nil { + t.Fatalf("err: %s", err) + } + + if len(g.Vertices()) > 0 { + t.Fatalf("graph is not empty: %s", g) + } +} + +func TestConfigTransformer_unloadedModule(t *testing.T) { + mod, err := module.NewTreeModule( + "", filepath.Join(fixtureDir, "graph-basic")) + if err != nil { + t.Fatalf("err: %s", err) + } + + g := Graph{Path: RootModulePath} + tf := &ConfigTransformer{Module: mod} + if err := tf.Transform(&g); err == nil { + t.Fatal("should error") + } +} + +func TestConfigTransformer(t *testing.T) { + g := Graph{Path: RootModulePath} + tf := &ConfigTransformer{Module: testModule(t, "graph-basic")} + if err := tf.Transform(&g); err != nil { + t.Fatalf("err: %s", err) + } + + actual := strings.TrimSpace(g.String()) + expected := strings.TrimSpace(testConfigTransformerGraphBasicStr) + if actual != expected { + t.Fatalf("bad:\n\n%s", actual) + } +} + +const testConfigTransformerGraphBasicStr = ` +aws_instance.web +aws_load_balancer.weblb +aws_security_group.firewall +openstack_floating_ip.random +`