Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for multiple providers of the same type #1281

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
112 changes: 99 additions & 13 deletions builtin/providers/aws/resource_aws_instance_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,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

Expand Down Expand Up @@ -344,7 +376,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" {
Expand Down Expand Up @@ -377,6 +427,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 {
Expand All @@ -387,20 +442,25 @@ func testAccCheckInstanceExists(n string, i *ec2.Instance) resource.TestCheckFun
return fmt.Errorf("No ID is set")
}

conn := testAccProvider.Meta().(*AWSClient).ec2conn
resp, err := conn.DescribeInstances(&ec2.DescribeInstancesRequest{
InstanceIDs: []string{rs.Primary.ID},
})
if err != nil {
return err
}
if len(resp.Reservations) == 0 {
return fmt.Errorf("Instance not found")
}
for _, provider := range *providers {
conn := provider.Meta().(*AWSClient).ec2conn
resp, err := conn.DescribeInstances(&ec2.DescribeInstancesRequest{
InstanceIDs: []string{rs.Primary.ID},
})
if ec2err, ok := err.(aws.APIError); ok && ec2err.Code == "InvalidInstanceID.NotFound" {
continue
}
if err != nil {
return err
}

*i = resp.Reservations[0].Instances[0]
if len(resp.Reservations) > 0 {
*i = resp.Reservations[0].Instances[0]
return nil
}
}

return nil
return fmt.Errorf("Instance not found")
}
}

Expand Down Expand Up @@ -543,6 +603,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"
Expand Down
2 changes: 2 additions & 0 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ type Module struct {
// resource provider.
type ProviderConfig struct {
Name string
Alias string
RawConfig *RawConfig
}

Expand All @@ -75,6 +76,7 @@ type Resource struct {
RawCount *RawConfig
RawConfig *RawConfig
Provisioners []*Provisioner
Provider string
DependsOn []string
Lifecycle ResourceLifecycle
}
Expand Down
40 changes: 35 additions & 5 deletions config/loader_hcl.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}

Expand All @@ -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,
})
}
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand All @@ -508,6 +537,7 @@ func loadResourcesHcl(os *hclobj.Object) ([]*Resource, error) {
RawCount: countConfig,
RawConfig: rawConfig,
Provisioners: provisioners,
Provider: provider,
DependsOn: dependsOn,
Lifecycle: lifecycle,
})
Expand Down
12 changes: 8 additions & 4 deletions helper/resource/testing.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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}

Expand Down
33 changes: 33 additions & 0 deletions terraform/context_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3057,6 +3057,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")
Expand Down
7 changes: 5 additions & 2 deletions terraform/eval_context_builtin.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package terraform
import (
"fmt"
"log"
"strings"
"sync"

"github.com/hashicorp/terraform/config"
Expand Down Expand Up @@ -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()
Expand Down