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) { func TestAccAWSInstance_NetworkInstanceSecurityGroups(t *testing.T) {
var v ec2.Instance var v ec2.Instance
@ -364,7 +396,25 @@ func TestAccAWSInstance_associatePublicIPAndPrivateIP(t *testing.T) {
} }
func testAccCheckInstanceDestroy(s *terraform.State) error { 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 { for _, rs := range s.RootModule().Resources {
if rs.Type != "aws_instance" { if rs.Type != "aws_instance" {
@ -397,6 +447,11 @@ func testAccCheckInstanceDestroy(s *terraform.State) error {
} }
func testAccCheckInstanceExists(n string, i *ec2.Instance) resource.TestCheckFunc { 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 { return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources[n] rs, ok := s.RootModule().Resources[n]
if !ok { if !ok {
@ -406,21 +461,25 @@ func testAccCheckInstanceExists(n string, i *ec2.Instance) resource.TestCheckFun
if rs.Primary.ID == "" { if rs.Primary.ID == "" {
return fmt.Errorf("No ID is set") 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 if len(resp.Reservations) > 0 {
resp, err := conn.DescribeInstances(&ec2.DescribeInstancesInput{ *i = *resp.Reservations[0].Instances[0]
InstanceIDs: []*string{aws.String(rs.Primary.ID)}, return nil
}) }
if err != nil {
return err
}
if len(resp.Reservations) == 0 {
return fmt.Errorf("Instance not found")
} }
*i = *resp.Reservations[0].Instances[0] return fmt.Errorf("Instance not found")
return nil
} }
} }
@ -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 = ` const testAccCheckInstanceConfigTags = `
resource "aws_instance" "foo" { resource "aws_instance" "foo" {
ami = "ami-4fccb37f" ami = "ami-4fccb37f"

View File

@ -63,6 +63,7 @@ type Module struct {
// resource provider. // resource provider.
type ProviderConfig struct { type ProviderConfig struct {
Name string Name string
Alias string
RawConfig *RawConfig RawConfig *RawConfig
} }
@ -75,6 +76,7 @@ type Resource struct {
RawCount *RawConfig RawCount *RawConfig
RawConfig *RawConfig RawConfig *RawConfig
Provisioners []*Provisioner Provisioners []*Provisioner
Provider string
DependsOn []string DependsOn []string
Lifecycle ResourceLifecycle Lifecycle ResourceLifecycle
} }

View File

@ -325,13 +325,13 @@ func loadOutputsHcl(os *hclobj.Object) ([]*Output, error) {
// LoadProvidersHcl recurses into the given HCL object and turns // LoadProvidersHcl recurses into the given HCL object and turns
// it into a mapping of provider configs. // it into a mapping of provider configs.
func loadProvidersHcl(os *hclobj.Object) ([]*ProviderConfig, error) { 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 // Iterate over all the "provider" blocks and get the keys along with
// their raw configuration objects. We'll parse those later. // their raw configuration objects. We'll parse those later.
for _, o1 := range os.Elem(false) { for _, o1 := range os.Elem(false) {
for _, o2 := range o1.Elem(true) { 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. // Go through each object and turn it into an actual result.
result := make([]*ProviderConfig, 0, len(objects)) result := make([]*ProviderConfig, 0, len(objects))
for n, o := range objects { for _, o := range objects {
var config map[string]interface{} var config map[string]interface{}
if err := hcl.DecodeObject(&config, o); err != nil { if err := hcl.DecodeObject(&config, o); err != nil {
return nil, err return nil, err
} }
delete(config, "alias")
rawConfig, err := NewRawConfig(config) rawConfig, err := NewRawConfig(config)
if err != nil { if err != nil {
return nil, fmt.Errorf( return nil, fmt.Errorf(
"Error reading config for provider config %s: %s", "Error reading config for provider config %s: %s",
n, o.Key,
err) 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{ result = append(result, &ProviderConfig{
Name: n, Name: o.Key,
Alias: alias,
RawConfig: rawConfig, RawConfig: rawConfig,
}) })
} }
@ -417,6 +432,7 @@ func loadResourcesHcl(os *hclobj.Object) ([]*Resource, error) {
delete(config, "count") delete(config, "count")
delete(config, "depends_on") delete(config, "depends_on")
delete(config, "provisioner") delete(config, "provisioner")
delete(config, "provider")
delete(config, "lifecycle") delete(config, "lifecycle")
rawConfig, err := NewRawConfig(config) 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 // Check if the resource should be re-created before
// destroying the existing instance // destroying the existing instance
var lifecycle ResourceLifecycle var lifecycle ResourceLifecycle
@ -508,6 +537,7 @@ func loadResourcesHcl(os *hclobj.Object) ([]*Resource, error) {
RawCount: countConfig, RawCount: countConfig,
RawConfig: rawConfig, RawConfig: rawConfig,
Provisioners: provisioners, Provisioners: provisioners,
Provider: provider,
DependsOn: dependsOn, DependsOn: dependsOn,
Lifecycle: lifecycle, Lifecycle: lifecycle,
}) })

View File

@ -35,7 +35,8 @@ type TestCase struct {
PreCheck func() PreCheck func()
// Provider is the ResourceProvider that will be under test. // 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 // CheckDestroy is called after the resource is finally destroyed
// to allow the tester to test that the resource is truly gone. // 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 // Build our context options that we can
ctxProviders := make(map[string]terraform.ResourceProviderFactory) ctxProviders := c.ProviderFactories
for k, p := range c.Providers { if ctxProviders == nil {
ctxProviders[k] = terraform.ResourceProviderFactoryFixed(p) ctxProviders = make(map[string]terraform.ResourceProviderFactory)
for k, p := range c.Providers {
ctxProviders[k] = terraform.ResourceProviderFactoryFixed(p)
}
} }
opts := terraform.ContextOpts{Providers: ctxProviders} 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) { func TestContext2Apply_emptyModule(t *testing.T) {
m := testModule(t, "apply-empty-module") m := testModule(t, "apply-empty-module")
p := testProvider("aws") p := testProvider("aws")

View File

@ -3,6 +3,7 @@ package terraform
import ( import (
"fmt" "fmt"
"log" "log"
"strings"
"sync" "sync"
"github.com/hashicorp/terraform/config" "github.com/hashicorp/terraform/config"
@ -68,9 +69,11 @@ func (ctx *BuiltinEvalContext) InitProvider(n string) (ResourceProvider, error)
ctx.ProviderLock.Lock() ctx.ProviderLock.Lock()
defer ctx.ProviderLock.Unlock() defer ctx.ProviderLock.Unlock()
f, ok := ctx.Providers[n] typeName := strings.SplitN(n, ".", 2)[0]
f, ok := ctx.Providers[typeName]
if !ok { if !ok {
return nil, fmt.Errorf("Provider '%s' not found", n) return nil, fmt.Errorf("Provider '%s' not found", typeName)
} }
p, err := f() p, err := f()

View File

@ -154,12 +154,13 @@ func (n *EvalUpdateStateHook) Eval(ctx EvalContext) (interface{}, error) {
type EvalWriteState struct { type EvalWriteState struct {
Name string Name string
ResourceType string ResourceType string
Provider string
Dependencies []string Dependencies []string
State **InstanceState State **InstanceState
} }
func (n *EvalWriteState) Eval(ctx EvalContext) (interface{}, error) { 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 { func(rs *ResourceState) error {
rs.Primary = *n.State rs.Primary = *n.State
return nil return nil
@ -172,6 +173,7 @@ func (n *EvalWriteState) Eval(ctx EvalContext) (interface{}, error) {
type EvalWriteStateTainted struct { type EvalWriteStateTainted struct {
Name string Name string
ResourceType string ResourceType string
Provider string
Dependencies []string Dependencies []string
State **InstanceState State **InstanceState
// Index indicates which instance in the Tainted list to target, or -1 to append. // 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 // EvalWriteStateTainted is an EvalNode implementation that writes the
// one of the tainted InstanceStates for a specific resource out of the state. // one of the tainted InstanceStates for a specific resource out of the state.
func (n *EvalWriteStateTainted) Eval(ctx EvalContext) (interface{}, error) { 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 { func(rs *ResourceState) error {
if n.Index == -1 { if n.Index == -1 {
rs.Tainted = append(rs.Tainted, *n.State) rs.Tainted = append(rs.Tainted, *n.State)
@ -198,6 +200,7 @@ func (n *EvalWriteStateTainted) Eval(ctx EvalContext) (interface{}, error) {
type EvalWriteStateDeposed struct { type EvalWriteStateDeposed struct {
Name string Name string
ResourceType string ResourceType string
Provider string
Dependencies []string Dependencies []string
State **InstanceState State **InstanceState
// Index indicates which instance in the Deposed list to target, or -1 to append. // 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) { 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 { func(rs *ResourceState) error {
if n.Index == -1 { if n.Index == -1 {
rs.Deposed = append(rs.Deposed, *n.State) rs.Deposed = append(rs.Deposed, *n.State)
@ -225,6 +228,7 @@ func writeInstanceToState(
ctx EvalContext, ctx EvalContext,
resourceName string, resourceName string,
resourceType string, resourceType string,
provider string,
dependencies []string, dependencies []string,
writerFn func(*ResourceState) error, writerFn func(*ResourceState) error,
) (*InstanceState, error) { ) (*InstanceState, error) {
@ -252,6 +256,7 @@ func writeInstanceToState(
} }
rs.Type = resourceType rs.Type = resourceType
rs.Dependencies = dependencies rs.Dependencies = dependencies
rs.Provider = provider
if err := writerFn(rs); err != nil { if err := writerFn(rs); err != nil {
return nil, err return nil, err

View File

@ -110,7 +110,7 @@ func (n *GraphNodeConfigModule) ProvidedBy() []string {
providers[p.Name] = struct{}{} providers[p.Name] = struct{}{}
} }
for _, r := range config.Resources { 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 // 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 { 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 { func (n *GraphNodeConfigProvider) ConfigType() GraphNodeConfigType {
@ -201,12 +201,16 @@ func (n *GraphNodeConfigProvider) DependentOn() []string {
// GraphNodeEvalable impl. // GraphNodeEvalable impl.
func (n *GraphNodeConfigProvider) EvalTree() EvalNode { func (n *GraphNodeConfigProvider) EvalTree() EvalNode {
return ProviderEvalTree(n.Provider.Name, n.Provider.RawConfig) return ProviderEvalTree(n.ProviderName(), n.Provider.RawConfig)
} }
// GraphNodeProvider implementation // GraphNodeProvider implementation
func (n *GraphNodeConfigProvider) ProviderName() string { 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 // GraphNodeProvider implementation
@ -396,7 +400,7 @@ func (n *GraphNodeConfigResource) EvalTree() EvalNode {
// GraphNodeProviderConsumer // GraphNodeProviderConsumer
func (n *GraphNodeConfigResource) ProvidedBy() []string { func (n *GraphNodeConfigResource) ProvidedBy() []string {
return []string{resourceProvider(n.Resource.Type)} return []string{resourceProvider(n.Resource.Type, n.Resource.Provider)}
} }
// GraphNodeProvisionerConsumer // 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) { func TestGraphNodeConfigResource_impl(t *testing.T) {
var _ dag.Vertex = new(GraphNodeConfigResource) var _ dag.Vertex = new(GraphNodeConfigResource)
var _ dag.NamedVertex = 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) { func TestGraphNodeConfigResource_ProvisionedBy(t *testing.T) {
n := &GraphNodeConfigResource{ n := &GraphNodeConfigResource{
Resource: &config.Resource{ 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("%s:%s%s\n", k, taintStr, deposedStr))
buf.WriteString(fmt.Sprintf(" ID = %s\n", id)) 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 var attributes map[string]string
if rs.Primary != nil { if rs.Primary != nil {
@ -680,6 +683,12 @@ type ResourceState struct {
// similar to Tainted instances in that Terraform is only tracking them in // similar to Tainted instances in that Terraform is only tracking them in
// order to remember to destroy them. // order to remember to destroy them.
Deposed []*InstanceState `json:"deposed,omitempty"` 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. // Equal tests whether two ResourceStates are equal.
@ -688,6 +697,10 @@ func (s *ResourceState) Equal(other *ResourceState) bool {
return false return false
} }
if s.Provider != other.Provider {
return false
}
// Dependencies must be equal // Dependencies must be equal
sort.Strings(s.Dependencies) sort.Strings(s.Dependencies)
sort.Strings(other.Dependencies) sort.Strings(other.Dependencies)
@ -769,6 +782,7 @@ func (r *ResourceState) deepcopy() *ResourceState {
Dependencies: nil, Dependencies: nil,
Primary: r.Primary.deepcopy(), Primary: r.Primary.deepcopy(),
Tainted: nil, Tainted: nil,
Provider: r.Provider,
} }
if r.Dependencies != nil { if r.Dependencies != nil {
n.Dependencies = make([]string, len(r.Dependencies)) n.Dependencies = make([]string, len(r.Dependencies))

View File

@ -182,6 +182,18 @@ aws_instance.foo:
type = aws_instance 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 = ` const testTerraformApplyEmptyModuleStr = `
<no state> <no state>
Outputs: 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) { func TestConfigTransformer_errMissingDeps(t *testing.T) {
g := Graph{Path: RootModulePath} g := Graph{Path: RootModulePath}
tf := &ConfigTransformer{Module: testModule(t, "graph-missing-deps")} tf := &ConfigTransformer{Module: testModule(t, "graph-missing-deps")}
@ -126,3 +140,9 @@ aws_instance.foo
output.foo output.foo
aws_instance.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, Index: i,
ResourceName: k, ResourceName: k,
ResourceType: rs.Type, ResourceType: rs.Type,
Provider: rs.Provider,
}) })
} }
} }
@ -52,6 +53,7 @@ type graphNodeDeposedResource struct {
Index int Index int
ResourceName string ResourceName string
ResourceType string ResourceType string
Provider string
} }
func (n *graphNodeDeposedResource) Name() string { func (n *graphNodeDeposedResource) Name() string {
@ -59,7 +61,7 @@ func (n *graphNodeDeposedResource) Name() string {
} }
func (n *graphNodeDeposedResource) ProvidedBy() []string { func (n *graphNodeDeposedResource) ProvidedBy() []string {
return []string{resourceProvider(n.ResourceName)} return []string{resourceProvider(n.ResourceName, n.Provider)}
} }
// GraphNodeEvalable impl. // GraphNodeEvalable impl.
@ -96,6 +98,7 @@ func (n *graphNodeDeposedResource) EvalTree() EvalNode {
&EvalWriteStateDeposed{ &EvalWriteStateDeposed{
Name: n.ResourceName, Name: n.ResourceName,
ResourceType: n.ResourceType, ResourceType: n.ResourceType,
Provider: n.Provider,
State: &state, State: &state,
Index: n.Index, Index: n.Index,
}, },
@ -138,6 +141,7 @@ func (n *graphNodeDeposedResource) EvalTree() EvalNode {
&EvalWriteStateDeposed{ &EvalWriteStateDeposed{
Name: n.ResourceName, Name: n.ResourceName,
ResourceType: n.ResourceType, ResourceType: n.ResourceType,
Provider: n.Provider,
State: &state, State: &state,
Index: n.Index, Index: n.Index,
}, },

View File

@ -89,6 +89,7 @@ func (t *OrphanTransformer) Transform(g *Graph) error {
resourceVertexes[i] = g.Add(&graphNodeOrphanResource{ resourceVertexes[i] = g.Add(&graphNodeOrphanResource{
ResourceName: k, ResourceName: k,
ResourceType: rs.Type, ResourceType: rs.Type,
Provider: rs.Provider,
dependentOn: rs.Dependencies, dependentOn: rs.Dependencies,
}) })
} }
@ -162,6 +163,7 @@ func (n *graphNodeOrphanModule) Expand(b GraphBuilder) (GraphNodeSubgraph, error
type graphNodeOrphanResource struct { type graphNodeOrphanResource struct {
ResourceName string ResourceName string
ResourceType string ResourceType string
Provider string
dependentOn []string dependentOn []string
} }
@ -179,7 +181,7 @@ func (n *graphNodeOrphanResource) Name() string {
} }
func (n *graphNodeOrphanResource) ProvidedBy() []string { func (n *graphNodeOrphanResource) ProvidedBy() []string {
return []string{resourceProvider(n.ResourceName)} return []string{resourceProvider(n.ResourceName, n.Provider)}
} }
// GraphNodeEvalable impl. // GraphNodeEvalable impl.
@ -215,6 +217,7 @@ func (n *graphNodeOrphanResource) EvalTree() EvalNode {
&EvalWriteState{ &EvalWriteState{
Name: n.ResourceName, Name: n.ResourceName,
ResourceType: n.ResourceType, ResourceType: n.ResourceType,
Provider: n.Provider,
Dependencies: n.DependentOn(), Dependencies: n.DependentOn(),
State: &state, State: &state,
}, },
@ -272,6 +275,7 @@ func (n *graphNodeOrphanResource) EvalTree() EvalNode {
&EvalWriteState{ &EvalWriteState{
Name: n.ResourceName, Name: n.ResourceName,
ResourceType: n.ResourceType, ResourceType: n.ResourceType,
Provider: n.Provider,
Dependencies: n.DependentOn(), Dependencies: n.DependentOn(),
State: &state, 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 = ` const testTransformOrphanBasicStr = `
aws_instance.db (orphan) aws_instance.db (orphan)
aws_instance.web aws_instance.web

View File

@ -140,7 +140,7 @@ func (n *graphNodeExpandedResource) DependentOn() []string {
// GraphNodeProviderConsumer // GraphNodeProviderConsumer
func (n *graphNodeExpandedResource) ProvidedBy() []string { func (n *graphNodeExpandedResource) ProvidedBy() []string {
return []string{resourceProvider(n.Resource.Type)} return []string{resourceProvider(n.Resource.Type, n.Resource.Provider)}
} }
// GraphNodeEvalable impl. // GraphNodeEvalable impl.
@ -230,6 +230,7 @@ func (n *graphNodeExpandedResource) EvalTree() EvalNode {
&EvalWriteState{ &EvalWriteState{
Name: n.stateId(), Name: n.stateId(),
ResourceType: n.Resource.Type, ResourceType: n.Resource.Type,
Provider: n.Resource.Provider,
Dependencies: n.DependentOn(), Dependencies: n.DependentOn(),
State: &state, State: &state,
}, },
@ -270,6 +271,7 @@ func (n *graphNodeExpandedResource) EvalTree() EvalNode {
&EvalWriteState{ &EvalWriteState{
Name: n.stateId(), Name: n.stateId(),
ResourceType: n.Resource.Type, ResourceType: n.Resource.Type,
Provider: n.Resource.Provider,
Dependencies: n.DependentOn(), Dependencies: n.DependentOn(),
State: &state, State: &state,
}, },
@ -416,6 +418,7 @@ func (n *graphNodeExpandedResource) EvalTree() EvalNode {
&EvalWriteState{ &EvalWriteState{
Name: n.stateId(), Name: n.stateId(),
ResourceType: n.Resource.Type, ResourceType: n.Resource.Type,
Provider: n.Resource.Provider,
Dependencies: n.DependentOn(), Dependencies: n.DependentOn(),
State: &state, State: &state,
}, },
@ -459,6 +462,7 @@ func (n *graphNodeExpandedResource) EvalTree() EvalNode {
&EvalWriteStateTainted{ &EvalWriteStateTainted{
Name: n.stateId(), Name: n.stateId(),
ResourceType: n.Resource.Type, ResourceType: n.Resource.Type,
Provider: n.Resource.Provider,
Dependencies: n.DependentOn(), Dependencies: n.DependentOn(),
State: &state, State: &state,
Index: -1, Index: -1,
@ -476,6 +480,7 @@ func (n *graphNodeExpandedResource) EvalTree() EvalNode {
Else: &EvalWriteState{ Else: &EvalWriteState{
Name: n.stateId(), Name: n.stateId(),
ResourceType: n.Resource.Type, ResourceType: n.Resource.Type,
Provider: n.Resource.Provider,
Dependencies: n.DependentOn(), Dependencies: n.DependentOn(),
State: &state, State: &state,
}, },
@ -586,6 +591,7 @@ func (n *graphNodeExpandedResourceDestroy) EvalTree() EvalNode {
&EvalWriteState{ &EvalWriteState{
Name: n.stateId(), Name: n.stateId(),
ResourceType: n.Resource.Type, ResourceType: n.Resource.Type,
Provider: n.Resource.Provider,
Dependencies: n.DependentOn(), Dependencies: n.DependentOn(),
State: &state, State: &state,
}, },

View File

@ -45,6 +45,7 @@ func (t *TaintedTransformer) Transform(g *Graph) error {
Index: i, Index: i,
ResourceName: k, ResourceName: k,
ResourceType: rs.Type, ResourceType: rs.Type,
Provider: rs.Provider,
}) })
} }
} }
@ -57,6 +58,7 @@ type graphNodeTaintedResource struct {
Index int Index int
ResourceName string ResourceName string
ResourceType string ResourceType string
Provider string
} }
func (n *graphNodeTaintedResource) Name() string { func (n *graphNodeTaintedResource) Name() string {
@ -64,7 +66,7 @@ func (n *graphNodeTaintedResource) Name() string {
} }
func (n *graphNodeTaintedResource) ProvidedBy() []string { func (n *graphNodeTaintedResource) ProvidedBy() []string {
return []string{resourceProvider(n.ResourceName)} return []string{resourceProvider(n.ResourceName, n.Provider)}
} }
// GraphNodeEvalable impl. // GraphNodeEvalable impl.
@ -101,6 +103,7 @@ func (n *graphNodeTaintedResource) EvalTree() EvalNode {
&EvalWriteStateTainted{ &EvalWriteStateTainted{
Name: n.ResourceName, Name: n.ResourceName,
ResourceType: n.ResourceType, ResourceType: n.ResourceType,
Provider: n.Provider,
State: &state, State: &state,
Index: n.Index, Index: n.Index,
}, },
@ -138,6 +141,7 @@ func (n *graphNodeTaintedResource) EvalTree() EvalNode {
&EvalWriteStateTainted{ &EvalWriteStateTainted{
Name: n.ResourceName, Name: n.ResourceName,
ResourceType: n.ResourceType, ResourceType: n.ResourceType,
Provider: n.Provider,
State: &state, State: &state,
Index: n.Index, 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 = ` const testTransformTaintedBasicStr = `
aws_instance.web aws_instance.web
aws_instance.web (tainted #1) aws_instance.web (tainted #1)

View File

@ -47,7 +47,11 @@ func (s Semaphore) Release() {
} }
// resourceProvider returns the provider name for the given type. // 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, '_') idx := strings.IndexRune(t, '_')
if idx == -1 { if idx == -1 {
return "" return ""