Skip to content

Commit

Permalink
Support for multiple providers of the same type
Browse files Browse the repository at this point in the history
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)
  • Loading branch information
mgood committed Apr 2, 2015
1 parent 854267f commit 5ae9795
Show file tree
Hide file tree
Showing 20 changed files with 336 additions and 37 deletions.
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 TestAccInstance_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 @@ -3008,6 +3008,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

0 comments on commit 5ae9795

Please sign in to comment.