From 21b0a03d707db7955437065470a56d70e9701ec6 Mon Sep 17 00:00:00 2001 From: Matt Good Date: Mon, 23 Mar 2015 15:36:53 -0700 Subject: [PATCH] Support for multiple providers of the same type Adds an "alias" field to the provider which allows creating multiple instances of a provider under different names. This provides support for configurations such as multiple AWS providers for different regions. In each resource, the provider can be set with the "provider" field. (thanks to Cisco Cloud for their support) --- .../aws/resource_aws_instance_test.go | 111 ++++++++++++++++-- config/config.go | 2 + config/loader_hcl.go | 40 ++++++- helper/resource/testing.go | 12 +- terraform/context_test.go | 33 ++++++ terraform/eval_context_builtin.go | 7 +- terraform/eval_state.go | 11 +- terraform/graph_config_node.go | 14 ++- terraform/graph_config_node_test.go | 40 +++++++ terraform/state.go | 14 +++ terraform/terraform_test.go | 12 ++ .../apply-provider-alias/main.tf | 12 ++ .../graph-provider-alias/main.tf | 10 ++ terraform/transform_config_test.go | 20 ++++ terraform/transform_deposed.go | 6 +- terraform/transform_orphan.go | 6 +- terraform/transform_orphan_test.go | 7 ++ terraform/transform_resource.go | 8 +- terraform/transform_tainted.go | 6 +- terraform/transform_tainted_test.go | 7 ++ terraform/util.go | 6 +- 21 files changed, 347 insertions(+), 37 deletions(-) create mode 100644 terraform/test-fixtures/apply-provider-alias/main.tf create mode 100644 terraform/test-fixtures/graph-provider-alias/main.tf diff --git a/builtin/providers/aws/resource_aws_instance_test.go b/builtin/providers/aws/resource_aws_instance_test.go index 533c0a841..d924e1448 100644 --- a/builtin/providers/aws/resource_aws_instance_test.go +++ b/builtin/providers/aws/resource_aws_instance_test.go @@ -237,6 +237,38 @@ func TestAccAWSInstance_vpc(t *testing.T) { }) } +func TestAccAWSInstance_multipleRegions(t *testing.T) { + var v ec2.Instance + + // record the initialized providers so that we can use them to + // check for the instances in each region + var providers []*schema.Provider + providerFactories := map[string]terraform.ResourceProviderFactory{ + "aws": func() (terraform.ResourceProvider, error) { + p := Provider() + providers = append(providers, p.(*schema.Provider)) + return p, nil + }, + } + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProviderFactories: providerFactories, + CheckDestroy: testAccCheckInstanceDestroyWithProviders(&providers), + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccInstanceConfigMultipleRegions, + Check: resource.ComposeTestCheckFunc( + testAccCheckInstanceExistsWithProviders( + "aws_instance.foo", &v, &providers), + testAccCheckInstanceExistsWithProviders( + "aws_instance.bar", &v, &providers), + ), + }, + }, + }) +} + func TestAccAWSInstance_NetworkInstanceSecurityGroups(t *testing.T) { var v ec2.Instance @@ -364,7 +396,25 @@ func TestAccAWSInstance_associatePublicIPAndPrivateIP(t *testing.T) { } func testAccCheckInstanceDestroy(s *terraform.State) error { - conn := testAccProvider.Meta().(*AWSClient).ec2conn + return testAccCheckInstanceDestroyWithProvider(s, testAccProvider) +} + +func testAccCheckInstanceDestroyWithProviders(providers *[]*schema.Provider) resource.TestCheckFunc { + return func(s *terraform.State) error { + for _, provider := range *providers { + if provider.Meta() == nil { + continue + } + if err := testAccCheckInstanceDestroyWithProvider(s, provider); err != nil { + return err + } + } + return nil + } +} + +func testAccCheckInstanceDestroyWithProvider(s *terraform.State, provider *schema.Provider) error { + conn := provider.Meta().(*AWSClient).ec2conn for _, rs := range s.RootModule().Resources { if rs.Type != "aws_instance" { @@ -397,6 +447,11 @@ func testAccCheckInstanceDestroy(s *terraform.State) error { } func testAccCheckInstanceExists(n string, i *ec2.Instance) resource.TestCheckFunc { + providers := []*schema.Provider{testAccProvider} + return testAccCheckInstanceExistsWithProviders(n, i, &providers) +} + +func testAccCheckInstanceExistsWithProviders(n string, i *ec2.Instance, providers *[]*schema.Provider) resource.TestCheckFunc { return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[n] if !ok { @@ -406,21 +461,25 @@ func testAccCheckInstanceExists(n string, i *ec2.Instance) resource.TestCheckFun if rs.Primary.ID == "" { return fmt.Errorf("No ID is set") } + for _, provider := range *providers { + conn := provider.Meta().(*AWSClient).ec2conn + resp, err := conn.DescribeInstances(&ec2.DescribeInstancesInput{ + InstanceIDs: []*string{aws.String(rs.Primary.ID)}, + }) + if ec2err, ok := err.(aws.APIError); ok && ec2err.Code == "InvalidInstanceID.NotFound" { + continue + } + if err != nil { + return err + } - conn := testAccProvider.Meta().(*AWSClient).ec2conn - resp, err := conn.DescribeInstances(&ec2.DescribeInstancesInput{ - InstanceIDs: []*string{aws.String(rs.Primary.ID)}, - }) - if err != nil { - return err - } - if len(resp.Reservations) == 0 { - return fmt.Errorf("Instance not found") + if len(resp.Reservations) > 0 { + *i = *resp.Reservations[0].Instances[0] + return nil + } } - *i = *resp.Reservations[0].Instances[0] - - return nil + return fmt.Errorf("Instance not found") } } @@ -563,6 +622,32 @@ resource "aws_instance" "foo" { } ` +const testAccInstanceConfigMultipleRegions = ` +provider "aws" { + alias = "west" + region = "us-west-2" +} + +provider "aws" { + alias = "east" + region = "us-east-1" +} + +resource "aws_instance" "foo" { + # us-west-2 + provider = "aws.west" + ami = "ami-4fccb37f" + instance_type = "m1.small" +} + +resource "aws_instance" "bar" { + # us-east-1 + provider = "aws.east" + ami = "ami-8c6ea9e4" + instance_type = "m1.small" +} +` + const testAccCheckInstanceConfigTags = ` resource "aws_instance" "foo" { ami = "ami-4fccb37f" diff --git a/config/config.go b/config/config.go index 68c2fca1b..a3b21c4f0 100644 --- a/config/config.go +++ b/config/config.go @@ -63,6 +63,7 @@ type Module struct { // resource provider. type ProviderConfig struct { Name string + Alias string RawConfig *RawConfig } @@ -75,6 +76,7 @@ type Resource struct { RawCount *RawConfig RawConfig *RawConfig Provisioners []*Provisioner + Provider string DependsOn []string Lifecycle ResourceLifecycle } diff --git a/config/loader_hcl.go b/config/loader_hcl.go index f75f93df3..6f4fa8edd 100644 --- a/config/loader_hcl.go +++ b/config/loader_hcl.go @@ -325,13 +325,13 @@ func loadOutputsHcl(os *hclobj.Object) ([]*Output, error) { // LoadProvidersHcl recurses into the given HCL object and turns // it into a mapping of provider configs. func loadProvidersHcl(os *hclobj.Object) ([]*ProviderConfig, error) { - objects := make(map[string]*hclobj.Object) + var objects []*hclobj.Object // Iterate over all the "provider" blocks and get the keys along with // their raw configuration objects. We'll parse those later. for _, o1 := range os.Elem(false) { for _, o2 := range o1.Elem(true) { - objects[o2.Key] = o2 + objects = append(objects, o2) } } @@ -341,23 +341,38 @@ func loadProvidersHcl(os *hclobj.Object) ([]*ProviderConfig, error) { // Go through each object and turn it into an actual result. result := make([]*ProviderConfig, 0, len(objects)) - for n, o := range objects { + for _, o := range objects { var config map[string]interface{} if err := hcl.DecodeObject(&config, o); err != nil { return nil, err } + delete(config, "alias") + rawConfig, err := NewRawConfig(config) if err != nil { return nil, fmt.Errorf( "Error reading config for provider config %s: %s", - n, + o.Key, err) } + // If we have an alias field, then add those in + var alias string + if a := o.Get("alias", false); a != nil { + err := hcl.DecodeObject(&alias, a) + if err != nil { + return nil, fmt.Errorf( + "Error reading alias for provider[%s]: %s", + o.Key, + err) + } + } + result = append(result, &ProviderConfig{ - Name: n, + Name: o.Key, + Alias: alias, RawConfig: rawConfig, }) } @@ -417,6 +432,7 @@ func loadResourcesHcl(os *hclobj.Object) ([]*Resource, error) { delete(config, "count") delete(config, "depends_on") delete(config, "provisioner") + delete(config, "provider") delete(config, "lifecycle") rawConfig, err := NewRawConfig(config) @@ -488,6 +504,19 @@ func loadResourcesHcl(os *hclobj.Object) ([]*Resource, error) { } } + // If we have a provider, then parse it out + var provider string + if o := obj.Get("provider", false); o != nil { + err := hcl.DecodeObject(&provider, o) + if err != nil { + return nil, fmt.Errorf( + "Error reading provider for %s[%s]: %s", + t.Key, + k, + err) + } + } + // Check if the resource should be re-created before // destroying the existing instance var lifecycle ResourceLifecycle @@ -508,6 +537,7 @@ func loadResourcesHcl(os *hclobj.Object) ([]*Resource, error) { RawCount: countConfig, RawConfig: rawConfig, Provisioners: provisioners, + Provider: provider, DependsOn: dependsOn, Lifecycle: lifecycle, }) diff --git a/helper/resource/testing.go b/helper/resource/testing.go index 43a59e93c..4f46d2ba4 100644 --- a/helper/resource/testing.go +++ b/helper/resource/testing.go @@ -35,7 +35,8 @@ type TestCase struct { PreCheck func() // Provider is the ResourceProvider that will be under test. - Providers map[string]terraform.ResourceProvider + Providers map[string]terraform.ResourceProvider + ProviderFactories map[string]terraform.ResourceProviderFactory // CheckDestroy is called after the resource is finally destroyed // to allow the tester to test that the resource is truly gone. @@ -102,9 +103,12 @@ func Test(t TestT, c TestCase) { } // Build our context options that we can - ctxProviders := make(map[string]terraform.ResourceProviderFactory) - for k, p := range c.Providers { - ctxProviders[k] = terraform.ResourceProviderFactoryFixed(p) + ctxProviders := c.ProviderFactories + if ctxProviders == nil { + ctxProviders = make(map[string]terraform.ResourceProviderFactory) + for k, p := range c.Providers { + ctxProviders[k] = terraform.ResourceProviderFactoryFixed(p) + } } opts := terraform.ContextOpts{Providers: ctxProviders} diff --git a/terraform/context_test.go b/terraform/context_test.go index dc0e1ccea..eba7e93d2 100644 --- a/terraform/context_test.go +++ b/terraform/context_test.go @@ -3286,6 +3286,39 @@ func TestContext2Apply(t *testing.T) { } } +func TestContext2Apply_providerAlias(t *testing.T) { + m := testModule(t, "apply-provider-alias") + p := testProvider("aws") + p.ApplyFn = testApplyFn + p.DiffFn = testDiffFn + ctx := testContext2(t, &ContextOpts{ + Module: m, + Providers: map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + }) + + if _, err := ctx.Plan(); err != nil { + t.Fatalf("err: %s", err) + } + + state, err := ctx.Apply() + if err != nil { + t.Fatalf("err: %s", err) + } + + mod := state.RootModule() + if len(mod.Resources) < 2 { + t.Fatalf("bad: %#v", mod.Resources) + } + + actual := strings.TrimSpace(state.String()) + expected := strings.TrimSpace(testTerraformApplyProviderAliasStr) + if actual != expected { + t.Fatalf("bad: \n%s", actual) + } +} + func TestContext2Apply_emptyModule(t *testing.T) { m := testModule(t, "apply-empty-module") p := testProvider("aws") diff --git a/terraform/eval_context_builtin.go b/terraform/eval_context_builtin.go index d25ea76ff..7e1eac310 100644 --- a/terraform/eval_context_builtin.go +++ b/terraform/eval_context_builtin.go @@ -3,6 +3,7 @@ package terraform import ( "fmt" "log" + "strings" "sync" "github.com/hashicorp/terraform/config" @@ -68,9 +69,11 @@ func (ctx *BuiltinEvalContext) InitProvider(n string) (ResourceProvider, error) ctx.ProviderLock.Lock() defer ctx.ProviderLock.Unlock() - f, ok := ctx.Providers[n] + typeName := strings.SplitN(n, ".", 2)[0] + + f, ok := ctx.Providers[typeName] if !ok { - return nil, fmt.Errorf("Provider '%s' not found", n) + return nil, fmt.Errorf("Provider '%s' not found", typeName) } p, err := f() diff --git a/terraform/eval_state.go b/terraform/eval_state.go index cea738661..3a3e9efd6 100644 --- a/terraform/eval_state.go +++ b/terraform/eval_state.go @@ -154,12 +154,13 @@ func (n *EvalUpdateStateHook) Eval(ctx EvalContext) (interface{}, error) { type EvalWriteState struct { Name string ResourceType string + Provider string Dependencies []string State **InstanceState } func (n *EvalWriteState) Eval(ctx EvalContext) (interface{}, error) { - return writeInstanceToState(ctx, n.Name, n.ResourceType, n.Dependencies, + return writeInstanceToState(ctx, n.Name, n.ResourceType, n.Provider, n.Dependencies, func(rs *ResourceState) error { rs.Primary = *n.State return nil @@ -172,6 +173,7 @@ func (n *EvalWriteState) Eval(ctx EvalContext) (interface{}, error) { type EvalWriteStateTainted struct { Name string ResourceType string + Provider string Dependencies []string State **InstanceState // Index indicates which instance in the Tainted list to target, or -1 to append. @@ -181,7 +183,7 @@ type EvalWriteStateTainted struct { // EvalWriteStateTainted is an EvalNode implementation that writes the // one of the tainted InstanceStates for a specific resource out of the state. func (n *EvalWriteStateTainted) Eval(ctx EvalContext) (interface{}, error) { - return writeInstanceToState(ctx, n.Name, n.ResourceType, n.Dependencies, + return writeInstanceToState(ctx, n.Name, n.ResourceType, n.Provider, n.Dependencies, func(rs *ResourceState) error { if n.Index == -1 { rs.Tainted = append(rs.Tainted, *n.State) @@ -198,6 +200,7 @@ func (n *EvalWriteStateTainted) Eval(ctx EvalContext) (interface{}, error) { type EvalWriteStateDeposed struct { Name string ResourceType string + Provider string Dependencies []string State **InstanceState // Index indicates which instance in the Deposed list to target, or -1 to append. @@ -205,7 +208,7 @@ type EvalWriteStateDeposed struct { } func (n *EvalWriteStateDeposed) Eval(ctx EvalContext) (interface{}, error) { - return writeInstanceToState(ctx, n.Name, n.ResourceType, n.Dependencies, + return writeInstanceToState(ctx, n.Name, n.ResourceType, n.Provider, n.Dependencies, func(rs *ResourceState) error { if n.Index == -1 { rs.Deposed = append(rs.Deposed, *n.State) @@ -225,6 +228,7 @@ func writeInstanceToState( ctx EvalContext, resourceName string, resourceType string, + provider string, dependencies []string, writerFn func(*ResourceState) error, ) (*InstanceState, error) { @@ -252,6 +256,7 @@ func writeInstanceToState( } rs.Type = resourceType rs.Dependencies = dependencies + rs.Provider = provider if err := writerFn(rs); err != nil { return nil, err diff --git a/terraform/graph_config_node.go b/terraform/graph_config_node.go index 791431a71..4ae5144bb 100644 --- a/terraform/graph_config_node.go +++ b/terraform/graph_config_node.go @@ -110,7 +110,7 @@ func (n *GraphNodeConfigModule) ProvidedBy() []string { providers[p.Name] = struct{}{} } for _, r := range config.Resources { - providers[resourceProvider(r.Type)] = struct{}{} + providers[resourceProvider(r.Type, r.Provider)] = struct{}{} } // Turn the map into a string. This makes sure that the list is @@ -176,7 +176,7 @@ type GraphNodeConfigProvider struct { } func (n *GraphNodeConfigProvider) Name() string { - return fmt.Sprintf("provider.%s", n.Provider.Name) + return fmt.Sprintf("provider.%s", n.ProviderName()) } func (n *GraphNodeConfigProvider) ConfigType() GraphNodeConfigType { @@ -201,12 +201,16 @@ func (n *GraphNodeConfigProvider) DependentOn() []string { // GraphNodeEvalable impl. func (n *GraphNodeConfigProvider) EvalTree() EvalNode { - return ProviderEvalTree(n.Provider.Name, n.Provider.RawConfig) + return ProviderEvalTree(n.ProviderName(), n.Provider.RawConfig) } // GraphNodeProvider implementation func (n *GraphNodeConfigProvider) ProviderName() string { - return n.Provider.Name + if n.Provider.Alias == "" { + return n.Provider.Name + } else { + return fmt.Sprintf("%s.%s", n.Provider.Name, n.Provider.Alias) + } } // GraphNodeProvider implementation @@ -396,7 +400,7 @@ func (n *GraphNodeConfigResource) EvalTree() EvalNode { // GraphNodeProviderConsumer func (n *GraphNodeConfigResource) ProvidedBy() []string { - return []string{resourceProvider(n.Resource.Type)} + return []string{resourceProvider(n.Resource.Type, n.Resource.Provider)} } // GraphNodeProvisionerConsumer diff --git a/terraform/graph_config_node_test.go b/terraform/graph_config_node_test.go index 4ebf9bf12..33390fc24 100644 --- a/terraform/graph_config_node_test.go +++ b/terraform/graph_config_node_test.go @@ -58,6 +58,36 @@ func TestGraphNodeConfigProvider_ProviderName(t *testing.T) { } } +func TestGraphNodeConfigProvider_ProviderName_alias(t *testing.T) { + n := &GraphNodeConfigProvider{ + Provider: &config.ProviderConfig{Name: "foo", Alias: "bar"}, + } + + if v := n.ProviderName(); v != "foo.bar" { + t.Fatalf("bad: %#v", v) + } +} + +func TestGraphNodeConfigProvider_Name(t *testing.T) { + n := &GraphNodeConfigProvider{ + Provider: &config.ProviderConfig{Name: "foo"}, + } + + if v := n.Name(); v != "provider.foo" { + t.Fatalf("bad: %#v", v) + } +} + +func TestGraphNodeConfigProvider_Name_alias(t *testing.T) { + n := &GraphNodeConfigProvider{ + Provider: &config.ProviderConfig{Name: "foo", Alias: "bar"}, + } + + if v := n.Name(); v != "provider.foo.bar" { + t.Fatalf("bad: %#v", v) + } +} + func TestGraphNodeConfigResource_impl(t *testing.T) { var _ dag.Vertex = new(GraphNodeConfigResource) var _ dag.NamedVertex = new(GraphNodeConfigResource) @@ -76,6 +106,16 @@ func TestGraphNodeConfigResource_ProvidedBy(t *testing.T) { } } +func TestGraphNodeConfigResource_ProvidedBy_alias(t *testing.T) { + n := &GraphNodeConfigResource{ + Resource: &config.Resource{Type: "aws_instance", Provider: "aws.bar"}, + } + + if v := n.ProvidedBy(); v[0] != "aws.bar" { + t.Fatalf("bad: %#v", v) + } +} + func TestGraphNodeConfigResource_ProvisionedBy(t *testing.T) { n := &GraphNodeConfigResource{ Resource: &config.Resource{ diff --git a/terraform/state.go b/terraform/state.go index 20c0da501..d3b19c9de 100644 --- a/terraform/state.go +++ b/terraform/state.go @@ -572,6 +572,9 @@ func (m *ModuleState) String() string { buf.WriteString(fmt.Sprintf("%s:%s%s\n", k, taintStr, deposedStr)) buf.WriteString(fmt.Sprintf(" ID = %s\n", id)) + if rs.Provider != "" { + buf.WriteString(fmt.Sprintf(" provider = %s\n", rs.Provider)) + } var attributes map[string]string if rs.Primary != nil { @@ -680,6 +683,12 @@ type ResourceState struct { // similar to Tainted instances in that Terraform is only tracking them in // order to remember to destroy them. Deposed []*InstanceState `json:"deposed,omitempty"` + + // Provider is used when a resource is connected to a provider with an alias. + // If this string is empty, the resource is connected to the default provider, + // e.g. "aws_instance" goes with the "aws" provider. + // If the resource block contained a "provider" key, that value will be set here. + Provider string `json:"provider,omitempty"` } // Equal tests whether two ResourceStates are equal. @@ -688,6 +697,10 @@ func (s *ResourceState) Equal(other *ResourceState) bool { return false } + if s.Provider != other.Provider { + return false + } + // Dependencies must be equal sort.Strings(s.Dependencies) sort.Strings(other.Dependencies) @@ -769,6 +782,7 @@ func (r *ResourceState) deepcopy() *ResourceState { Dependencies: nil, Primary: r.Primary.deepcopy(), Tainted: nil, + Provider: r.Provider, } if r.Dependencies != nil { n.Dependencies = make([]string, len(r.Dependencies)) diff --git a/terraform/terraform_test.go b/terraform/terraform_test.go index 6e80f92f0..bb54ef6ac 100644 --- a/terraform/terraform_test.go +++ b/terraform/terraform_test.go @@ -182,6 +182,18 @@ aws_instance.foo: type = aws_instance ` +const testTerraformApplyProviderAliasStr = ` +aws_instance.bar: + ID = foo + provider = aws.bar + foo = bar + type = aws_instance +aws_instance.foo: + ID = foo + num = 2 + type = aws_instance +` + const testTerraformApplyEmptyModuleStr = ` Outputs: diff --git a/terraform/test-fixtures/apply-provider-alias/main.tf b/terraform/test-fixtures/apply-provider-alias/main.tf new file mode 100644 index 000000000..19fd985ab --- /dev/null +++ b/terraform/test-fixtures/apply-provider-alias/main.tf @@ -0,0 +1,12 @@ +provider "aws" { + alias = "bar" +} + +resource "aws_instance" "foo" { + num = "2" +} + +resource "aws_instance" "bar" { + foo = "bar" + provider = "aws.bar" +} diff --git a/terraform/test-fixtures/graph-provider-alias/main.tf b/terraform/test-fixtures/graph-provider-alias/main.tf new file mode 100644 index 000000000..f7c319fc5 --- /dev/null +++ b/terraform/test-fixtures/graph-provider-alias/main.tf @@ -0,0 +1,10 @@ +provider "aws" { +} + +provider "aws" { + alias = "foo" +} + +provider "aws" { + alias = "bar" +} \ No newline at end of file diff --git a/terraform/transform_config_test.go b/terraform/transform_config_test.go index 371f01448..75570d3f6 100644 --- a/terraform/transform_config_test.go +++ b/terraform/transform_config_test.go @@ -86,6 +86,20 @@ func TestConfigTransformer_outputs(t *testing.T) { } } +func TestConfigTransformer_providerAlias(t *testing.T) { + g := Graph{Path: RootModulePath} + tf := &ConfigTransformer{Module: testModule(t, "graph-provider-alias")} + if err := tf.Transform(&g); err != nil { + t.Fatalf("err: %s", err) + } + + actual := strings.TrimSpace(g.String()) + expected := strings.TrimSpace(testGraphProviderAliasStr) + if actual != expected { + t.Fatalf("bad:\n\n%s", actual) + } +} + func TestConfigTransformer_errMissingDeps(t *testing.T) { g := Graph{Path: RootModulePath} tf := &ConfigTransformer{Module: testModule(t, "graph-missing-deps")} @@ -126,3 +140,9 @@ aws_instance.foo output.foo aws_instance.foo ` + +const testGraphProviderAliasStr = ` +provider.aws +provider.aws.bar +provider.aws.foo +` diff --git a/terraform/transform_deposed.go b/terraform/transform_deposed.go index b7c7e3f06..6ae1695f0 100644 --- a/terraform/transform_deposed.go +++ b/terraform/transform_deposed.go @@ -40,6 +40,7 @@ func (t *DeposedTransformer) Transform(g *Graph) error { Index: i, ResourceName: k, ResourceType: rs.Type, + Provider: rs.Provider, }) } } @@ -52,6 +53,7 @@ type graphNodeDeposedResource struct { Index int ResourceName string ResourceType string + Provider string } func (n *graphNodeDeposedResource) Name() string { @@ -59,7 +61,7 @@ func (n *graphNodeDeposedResource) Name() string { } func (n *graphNodeDeposedResource) ProvidedBy() []string { - return []string{resourceProvider(n.ResourceName)} + return []string{resourceProvider(n.ResourceName, n.Provider)} } // GraphNodeEvalable impl. @@ -96,6 +98,7 @@ func (n *graphNodeDeposedResource) EvalTree() EvalNode { &EvalWriteStateDeposed{ Name: n.ResourceName, ResourceType: n.ResourceType, + Provider: n.Provider, State: &state, Index: n.Index, }, @@ -138,6 +141,7 @@ func (n *graphNodeDeposedResource) EvalTree() EvalNode { &EvalWriteStateDeposed{ Name: n.ResourceName, ResourceType: n.ResourceType, + Provider: n.Provider, State: &state, Index: n.Index, }, diff --git a/terraform/transform_orphan.go b/terraform/transform_orphan.go index 5de64c65c..58ff5f3db 100644 --- a/terraform/transform_orphan.go +++ b/terraform/transform_orphan.go @@ -89,6 +89,7 @@ func (t *OrphanTransformer) Transform(g *Graph) error { resourceVertexes[i] = g.Add(&graphNodeOrphanResource{ ResourceName: k, ResourceType: rs.Type, + Provider: rs.Provider, dependentOn: rs.Dependencies, }) } @@ -162,6 +163,7 @@ func (n *graphNodeOrphanModule) Expand(b GraphBuilder) (GraphNodeSubgraph, error type graphNodeOrphanResource struct { ResourceName string ResourceType string + Provider string dependentOn []string } @@ -179,7 +181,7 @@ func (n *graphNodeOrphanResource) Name() string { } func (n *graphNodeOrphanResource) ProvidedBy() []string { - return []string{resourceProvider(n.ResourceName)} + return []string{resourceProvider(n.ResourceName, n.Provider)} } // GraphNodeEvalable impl. @@ -215,6 +217,7 @@ func (n *graphNodeOrphanResource) EvalTree() EvalNode { &EvalWriteState{ Name: n.ResourceName, ResourceType: n.ResourceType, + Provider: n.Provider, Dependencies: n.DependentOn(), State: &state, }, @@ -272,6 +275,7 @@ func (n *graphNodeOrphanResource) EvalTree() EvalNode { &EvalWriteState{ Name: n.ResourceName, ResourceType: n.ResourceType, + Provider: n.Provider, Dependencies: n.DependentOn(), State: &state, }, diff --git a/terraform/transform_orphan_test.go b/terraform/transform_orphan_test.go index 17708c683..85bb4637b 100644 --- a/terraform/transform_orphan_test.go +++ b/terraform/transform_orphan_test.go @@ -342,6 +342,13 @@ func TestGraphNodeOrphanResource_ProvidedBy(t *testing.T) { } } +func TestGraphNodeOrphanResource_ProvidedBy_alias(t *testing.T) { + n := &graphNodeOrphanResource{ResourceName: "aws_instance.foo", Provider: "aws.bar"} + if v := n.ProvidedBy(); v[0] != "aws.bar" { + t.Fatalf("bad: %#v", v) + } +} + const testTransformOrphanBasicStr = ` aws_instance.db (orphan) aws_instance.web diff --git a/terraform/transform_resource.go b/terraform/transform_resource.go index 316883637..04c35befc 100644 --- a/terraform/transform_resource.go +++ b/terraform/transform_resource.go @@ -140,7 +140,7 @@ func (n *graphNodeExpandedResource) DependentOn() []string { // GraphNodeProviderConsumer func (n *graphNodeExpandedResource) ProvidedBy() []string { - return []string{resourceProvider(n.Resource.Type)} + return []string{resourceProvider(n.Resource.Type, n.Resource.Provider)} } // GraphNodeEvalable impl. @@ -230,6 +230,7 @@ func (n *graphNodeExpandedResource) EvalTree() EvalNode { &EvalWriteState{ Name: n.stateId(), ResourceType: n.Resource.Type, + Provider: n.Resource.Provider, Dependencies: n.DependentOn(), State: &state, }, @@ -270,6 +271,7 @@ func (n *graphNodeExpandedResource) EvalTree() EvalNode { &EvalWriteState{ Name: n.stateId(), ResourceType: n.Resource.Type, + Provider: n.Resource.Provider, Dependencies: n.DependentOn(), State: &state, }, @@ -416,6 +418,7 @@ func (n *graphNodeExpandedResource) EvalTree() EvalNode { &EvalWriteState{ Name: n.stateId(), ResourceType: n.Resource.Type, + Provider: n.Resource.Provider, Dependencies: n.DependentOn(), State: &state, }, @@ -459,6 +462,7 @@ func (n *graphNodeExpandedResource) EvalTree() EvalNode { &EvalWriteStateTainted{ Name: n.stateId(), ResourceType: n.Resource.Type, + Provider: n.Resource.Provider, Dependencies: n.DependentOn(), State: &state, Index: -1, @@ -476,6 +480,7 @@ func (n *graphNodeExpandedResource) EvalTree() EvalNode { Else: &EvalWriteState{ Name: n.stateId(), ResourceType: n.Resource.Type, + Provider: n.Resource.Provider, Dependencies: n.DependentOn(), State: &state, }, @@ -586,6 +591,7 @@ func (n *graphNodeExpandedResourceDestroy) EvalTree() EvalNode { &EvalWriteState{ Name: n.stateId(), ResourceType: n.Resource.Type, + Provider: n.Resource.Provider, Dependencies: n.DependentOn(), State: &state, }, diff --git a/terraform/transform_tainted.go b/terraform/transform_tainted.go index c88516ade..fdc1ae6bc 100644 --- a/terraform/transform_tainted.go +++ b/terraform/transform_tainted.go @@ -45,6 +45,7 @@ func (t *TaintedTransformer) Transform(g *Graph) error { Index: i, ResourceName: k, ResourceType: rs.Type, + Provider: rs.Provider, }) } } @@ -57,6 +58,7 @@ type graphNodeTaintedResource struct { Index int ResourceName string ResourceType string + Provider string } func (n *graphNodeTaintedResource) Name() string { @@ -64,7 +66,7 @@ func (n *graphNodeTaintedResource) Name() string { } func (n *graphNodeTaintedResource) ProvidedBy() []string { - return []string{resourceProvider(n.ResourceName)} + return []string{resourceProvider(n.ResourceName, n.Provider)} } // GraphNodeEvalable impl. @@ -101,6 +103,7 @@ func (n *graphNodeTaintedResource) EvalTree() EvalNode { &EvalWriteStateTainted{ Name: n.ResourceName, ResourceType: n.ResourceType, + Provider: n.Provider, State: &state, Index: n.Index, }, @@ -138,6 +141,7 @@ func (n *graphNodeTaintedResource) EvalTree() EvalNode { &EvalWriteStateTainted{ Name: n.ResourceName, ResourceType: n.ResourceType, + Provider: n.Provider, State: &state, Index: n.Index, }, diff --git a/terraform/transform_tainted_test.go b/terraform/transform_tainted_test.go index f78e86b5c..208165ea7 100644 --- a/terraform/transform_tainted_test.go +++ b/terraform/transform_tainted_test.go @@ -58,6 +58,13 @@ func TestGraphNodeTaintedResource_ProvidedBy(t *testing.T) { } } +func TestGraphNodeTaintedResource_ProvidedBy_alias(t *testing.T) { + n := &graphNodeTaintedResource{ResourceName: "aws_instance.foo", Provider: "aws.bar"} + if v := n.ProvidedBy(); v[0] != "aws.bar" { + t.Fatalf("bad: %#v", v) + } +} + const testTransformTaintedBasicStr = ` aws_instance.web aws_instance.web (tainted #1) diff --git a/terraform/util.go b/terraform/util.go index 7c8e0aaa2..d1ca197ea 100644 --- a/terraform/util.go +++ b/terraform/util.go @@ -47,7 +47,11 @@ func (s Semaphore) Release() { } // resourceProvider returns the provider name for the given type. -func resourceProvider(t string) string { +func resourceProvider(t, alias string) string { + if alias != "" { + return alias + } + idx := strings.IndexRune(t, '_') if idx == -1 { return ""