diff --git a/terraform/test-fixtures/transform-config-mode-data/main.tf b/terraform/test-fixtures/transform-config-mode-data/main.tf new file mode 100644 index 0000000000..3c3e7e50d5 --- /dev/null +++ b/terraform/test-fixtures/transform-config-mode-data/main.tf @@ -0,0 +1,3 @@ +data "aws_ami" "foo" {} + +resource "aws_instance" "web" {} diff --git a/terraform/transform_config.go b/terraform/transform_config.go index c2dad20c96..61bce8532a 100644 --- a/terraform/transform_config.go +++ b/terraform/transform_config.go @@ -4,7 +4,9 @@ import ( "errors" "fmt" "log" + "sync" + "github.com/hashicorp/terraform/config" "github.com/hashicorp/terraform/config/module" "github.com/hashicorp/terraform/dag" ) @@ -23,10 +25,25 @@ import ( type ConfigTransformer struct { Concrete ConcreteResourceNodeFunc + // Module is the module to add resources from. Module *module.Tree + + // Unique will only add resources that aren't already present in the graph. + Unique bool + + // Mode will only add resources that match the given mode + ModeFilter bool + Mode config.ResourceMode + + l sync.Mutex + uniqueMap map[string]struct{} } func (t *ConfigTransformer) Transform(g *Graph) error { + // Lock since we use some internal state + t.l.Lock() + defer t.l.Unlock() + // If no module is given, we don't do anything if t.Module == nil { return nil @@ -37,6 +54,18 @@ func (t *ConfigTransformer) Transform(g *Graph) error { return errors.New("module must be loaded for ConfigTransformer") } + // Reset the uniqueness map. If we're tracking uniques, then populate + // it with addresses. + t.uniqueMap = make(map[string]struct{}) + defer func() { t.uniqueMap = nil }() + if t.Unique { + for _, v := range g.Vertices() { + if rn, ok := v.(GraphNodeResource); ok { + t.uniqueMap[rn.ResourceAddr().String()] = struct{}{} + } + } + } + // Start the transformation process return t.transform(g, t.Module) } @@ -66,13 +95,13 @@ 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() + conf := m.Config() // Build the path we're at path := m.Path() // Write all the resources out - for _, r := range config.Resources { + for _, r := range conf.Resources { // Build the resource address addr, err := parseResourceAddressConfig(r) if err != nil { @@ -81,6 +110,16 @@ func (t *ConfigTransformer) transformSingle(g *Graph, m *module.Tree) error { } addr.Path = path + // If this is already in our uniqueness map, don't add it again + if _, ok := t.uniqueMap[addr.String()]; ok { + continue + } + + // Remove non-matching modes + if t.ModeFilter && addr.Mode != t.Mode { + continue + } + // Build the abstract node and the concrete one abstract := &NodeAbstractResource{Addr: addr} var node dag.Vertex = abstract diff --git a/terraform/transform_config_test.go b/terraform/transform_config_test.go index 31bb7c8e0d..1b2c27cbe4 100644 --- a/terraform/transform_config_test.go +++ b/terraform/transform_config_test.go @@ -5,6 +5,7 @@ import ( "strings" "testing" + "github.com/hashicorp/terraform/config" "github.com/hashicorp/terraform/config/module" ) @@ -48,6 +49,80 @@ func TestConfigTransformer(t *testing.T) { } } +func TestConfigTransformer_mode(t *testing.T) { + g := Graph{Path: RootModulePath} + tf := &ConfigTransformer{ + Module: testModule(t, "transform-config-mode-data"), + ModeFilter: true, + Mode: config.DataResourceMode, + } + if err := tf.Transform(&g); err != nil { + t.Fatalf("err: %s", err) + } + + actual := strings.TrimSpace(g.String()) + expected := strings.TrimSpace(` +data.aws_ami.foo +`) + if actual != expected { + t.Fatalf("bad:\n\n%s", actual) + } +} + +func TestConfigTransformer_nonUnique(t *testing.T) { + addr, err := ParseResourceAddress("aws_instance.web") + if err != nil { + t.Fatalf("bad: %s", err) + } + + g := Graph{Path: RootModulePath} + g.Add(&NodeAbstractResource{Addr: addr}) + 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(` +aws_instance.web +aws_instance.web +aws_load_balancer.weblb +aws_security_group.firewall +openstack_floating_ip.random +`) + if actual != expected { + t.Fatalf("bad:\n\n%s", actual) + } +} + +func TestConfigTransformer_unique(t *testing.T) { + addr, err := ParseResourceAddress("aws_instance.web") + if err != nil { + t.Fatalf("bad: %s", err) + } + + g := Graph{Path: RootModulePath} + g.Add(&NodeAbstractResource{Addr: addr}) + tf := &ConfigTransformer{ + Module: testModule(t, "graph-basic"), + Unique: true, + } + if err := tf.Transform(&g); err != nil { + t.Fatalf("err: %s", err) + } + + actual := strings.TrimSpace(g.String()) + expected := strings.TrimSpace(` +aws_instance.web +aws_load_balancer.weblb +aws_security_group.firewall +openstack_floating_ip.random +`) + if actual != expected { + t.Fatalf("bad:\n\n%s", actual) + } +} + const testConfigTransformerGraphBasicStr = ` aws_instance.web aws_load_balancer.weblb