diff --git a/builtin/providers/aws/resource_aws_opsworks_stack.go b/builtin/providers/aws/resource_aws_opsworks_stack.go index 6a58583d51f2..aae83c26794a 100644 --- a/builtin/providers/aws/resource_aws_opsworks_stack.go +++ b/builtin/providers/aws/resource_aws_opsworks_stack.go @@ -3,14 +3,17 @@ package aws import ( "fmt" "log" + "os" "strings" "time" + "github.com/hashicorp/errwrap" "github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/helper/schema" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/awserr" + "github.com/aws/aws-sdk-go/aws/session" "github.com/aws/aws-sdk-go/service/opsworks" ) @@ -183,6 +186,11 @@ func resourceAwsOpsworksStack() *schema.Resource { Computed: true, Optional: true, }, + + "stack_endpoint": { + Type: schema.TypeString, + Computed: true, + }, }, } } @@ -254,6 +262,13 @@ func resourceAwsOpsworksSetStackCustomCookbooksSource(d *schema.ResourceData, v func resourceAwsOpsworksStackRead(d *schema.ResourceData, meta interface{}) error { client := meta.(*AWSClient).opsworksconn + var conErr error + if v := d.Get("stack_endpoint").(string); v != "" { + client, conErr = opsworksConnForRegion(v, meta) + if conErr != nil { + return conErr + } + } req := &opsworks.DescribeStacksInput{ StackIds: []*string{ @@ -263,16 +278,53 @@ func resourceAwsOpsworksStackRead(d *schema.ResourceData, meta interface{}) erro log.Printf("[DEBUG] Reading OpsWorks stack: %s", d.Id()) - resp, err := client.DescribeStacks(req) - if err != nil { - if awserr, ok := err.(awserr.Error); ok { - if awserr.Code() == "ResourceNotFoundException" { - log.Printf("[DEBUG] OpsWorks stack (%s) not found", d.Id()) - d.SetId("") - return nil + // notFound represents the number of times we've called DescribeStacks looking + // for this Stack. If it's not found in the the default region we're in, we + // check us-east-1 in the event this stack was created with Terraform before + // version 0.9 + // See https://github.com/hashicorp/terraform/issues/12842 + var notFound int + var resp *opsworks.DescribeStacksOutput + var dErr error + + for { + resp, dErr = client.DescribeStacks(req) + if dErr != nil { + if awserr, ok := dErr.(awserr.Error); ok { + if awserr.Code() == "ResourceNotFoundException" { + if notFound < 1 { + // If we haven't already, try us-east-1, legacy connection + notFound++ + var connErr error + client, connErr = opsworksConnForRegion("us-east-1", meta) + if connErr != nil { + return connErr + } + // start again from the top of the FOR loop, but with a client + // configured to talk to us-east-1 + continue + } + + // We've tried both the original and us-east-1 endpoint, and the stack + // is still not found + log.Printf("[DEBUG] OpsWorks stack (%s) not found", d.Id()) + d.SetId("") + return nil + } + // not ResoureNotFoundException, fall through to returning error } + return dErr } - return err + // If the stack was found, set the stack_endpoint + if client.Config.Region != nil && *client.Config.Region != "" { + log.Printf("[DEBUG] Setting stack_endpoint for (%s) to (%s)", d.Id(), *client.Config.Region) + if err := d.Set("stack_endpoint", *client.Config.Region); err != nil { + log.Printf("[WARN] Error setting stack_endpoint: %s", err) + } + } + log.Printf("[DEBUG] Breaking stack endpoint search, found stack for (%s)", d.Id()) + // Break the FOR loop + break } stack := resp.Stacks[0] @@ -309,6 +361,40 @@ func resourceAwsOpsworksStackRead(d *schema.ResourceData, meta interface{}) erro return nil } +// opsworksConn will return a connection for the stack_endpoint in the +// configuration. Stacks can only be accessed or managed within the endpoint +// in which they are created, so we allow users to specify an original endpoint +// for Stacks created before multiple endpoints were offered (Terraform v0.9.0). +// See: +// - https://github.com/hashicorp/terraform/pull/12688 +// - https://github.com/hashicorp/terraform/issues/12842 +func opsworksConnForRegion(region string, meta interface{}) (*opsworks.OpsWorks, error) { + originalConn := meta.(*AWSClient).opsworksconn + + // Regions are the same, no need to reconfigure + if originalConn.Config.Region != nil && *originalConn.Config.Region == region { + return originalConn, nil + } + + // Set up base session + sess, err := session.NewSession(&originalConn.Config) + if err != nil { + return nil, errwrap.Wrapf("Error creating AWS session: {{err}}", err) + } + + sess.Handlers.Build.PushBackNamed(addTerraformVersionToUserAgent) + + if extraDebug := os.Getenv("TERRAFORM_AWS_AUTHFAILURE_DEBUG"); extraDebug != "" { + sess.Handlers.UnmarshalError.PushFrontNamed(debugAuthFailure) + } + + newSession := sess.Copy(&aws.Config{Region: aws.String(region)}) + newOpsworksconn := opsworks.New(newSession) + + log.Printf("[DEBUG] Returning new OpsWorks client") + return newOpsworksconn, nil +} + func resourceAwsOpsworksStackCreate(d *schema.ResourceData, meta interface{}) error { client := meta.(*AWSClient).opsworksconn @@ -396,6 +482,13 @@ func resourceAwsOpsworksStackCreate(d *schema.ResourceData, meta interface{}) er func resourceAwsOpsworksStackUpdate(d *schema.ResourceData, meta interface{}) error { client := meta.(*AWSClient).opsworksconn + var conErr error + if v := d.Get("stack_endpoint").(string); v != "" { + client, conErr = opsworksConnForRegion(v, meta) + if conErr != nil { + return conErr + } + } err := resourceAwsOpsworksStackValidate(d) if err != nil { @@ -456,6 +549,13 @@ func resourceAwsOpsworksStackUpdate(d *schema.ResourceData, meta interface{}) er func resourceAwsOpsworksStackDelete(d *schema.ResourceData, meta interface{}) error { client := meta.(*AWSClient).opsworksconn + var conErr error + if v := d.Get("stack_endpoint").(string); v != "" { + client, conErr = opsworksConnForRegion(v, meta) + if conErr != nil { + return conErr + } + } req := &opsworks.DeleteStackInput{ StackId: aws.String(d.Id()), diff --git a/builtin/providers/aws/resource_aws_opsworks_stack_test.go b/builtin/providers/aws/resource_aws_opsworks_stack_test.go index 532ef5570722..eb366e33c081 100644 --- a/builtin/providers/aws/resource_aws_opsworks_stack_test.go +++ b/builtin/providers/aws/resource_aws_opsworks_stack_test.go @@ -74,6 +74,205 @@ func TestAccAWSOpsworksStackVpc(t *testing.T) { }) } +// Tests the addition of regional endpoints and supporting the classic link used +// to create Stack's prior to v0.9.0. +// See https://github.com/hashicorp/terraform/issues/12842 +func TestAccAWSOpsWorksStack_classic_endpoints(t *testing.T) { + stackName := fmt.Sprintf("tf-opsworks-acc-%d", acctest.RandInt()) + rInt := acctest.RandInt() + var opsstack opsworks.Stack + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAwsOpsworksStackDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccAwsOpsWorksStack_classic_endpoint(stackName, rInt), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSOpsworksStackExists( + "aws_opsworks_stack.main", false, &opsstack), + ), + }, + // Ensure that changing to us-west-2 region results in no plan + resource.TestStep{ + Config: testAccAwsOpsWorksStack_regional_endpoint(stackName, rInt), + PlanOnly: true, + }, + }, + }) + +} + +func testAccAwsOpsWorksStack_classic_endpoint(rName string, rInt int) string { + return fmt.Sprintf(` +provider "aws" { + region = "us-east-1" +} + +resource "aws_opsworks_stack" "main" { + name = "%s" + region = "us-west-2" + service_role_arn = "${aws_iam_role.opsworks_service.arn}" + default_instance_profile_arn = "${aws_iam_instance_profile.opsworks_instance.arn}" + + configuration_manager_version = "12" + default_availability_zone = "us-west-2b" +} + +resource "aws_iam_role" "opsworks_service" { + name = "tf_opsworks_service_%d" + + assume_role_policy = <