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)
This commit is contained in:
Matt Good 2015-03-23 15:36:53 -07:00 committed by Mitchell Hashimoto
parent ff224dec13
commit 21b0a03d70
21 changed files with 347 additions and 37 deletions

View File

@ -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"

View File

@ -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
}

View File

@ -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,
})

View File

@ -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}

View File

@ -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")

View File

@ -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()

View File

@ -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

View File

@ -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

View File

@ -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{

View File

@ -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))

View File

@ -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 = `
<no state>
Outputs:

View File

@ -0,0 +1,12 @@
provider "aws" {
alias = "bar"
}
resource "aws_instance" "foo" {
num = "2"
}
resource "aws_instance" "bar" {
foo = "bar"
provider = "aws.bar"
}

View File

@ -0,0 +1,10 @@
provider "aws" {
}
provider "aws" {
alias = "foo"
}
provider "aws" {
alias = "bar"
}

View File

@ -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
`

View File

@ -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,
},

View File

@ -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,
},

View File

@ -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

View File

@ -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,
},

View File

@ -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,
},

View File

@ -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)

View File

@ -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 ""