provider/aws: Add failing test for OpsWorks endpoints (#13024)

Fix an issue when upgrading from Terraform < 0.9 to 0.9+, when we added
support for the regional endpoints in OpsWorks Stacks. OpsWorks Stacks
can only be managed via the endpoint with which they were created, not
where the stack resides.
This commit is contained in:
Clint 2017-03-28 04:29:20 -05:00 committed by Paul Stack
parent b1d6b2e554
commit dd25334b46
2 changed files with 307 additions and 8 deletions

View File

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

View File

@ -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 = <<EOT
{
"Version": "2008-10-17",
"Statement": [
{
"Sid": "",
"Effect": "Allow",
"Principal": {
"Service": "opsworks.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
}
EOT
}
resource "aws_iam_role_policy" "opsworks_service" {
name = "tf_opsworks_service_%d"
role = "${aws_iam_role.opsworks_service.id}"
policy = <<EOT
{
"Statement": [
{
"Action": [
"ec2:*",
"iam:PassRole",
"cloudwatch:GetMetricStatistics",
"elasticloadbalancing:*",
"rds:*"
],
"Effect": "Allow",
"Resource": ["*"]
}
]
}
EOT
}
resource "aws_iam_role" "opsworks_instance" {
name = "tf_opsworks_instance_%d"
assume_role_policy = <<EOT
{
"Version": "2008-10-17",
"Statement": [
{
"Sid": "",
"Effect": "Allow",
"Principal": {
"Service": "ec2.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
}
EOT
}
resource "aws_iam_instance_profile" "opsworks_instance" {
name = "%s_profile"
roles = ["${aws_iam_role.opsworks_instance.name}"]
}`, rName, rInt, rInt, rInt, rName)
}
func testAccAwsOpsWorksStack_regional_endpoint(rName string, rInt int) string {
return fmt.Sprintf(`
provider "aws" {
region = "us-west-2"
}
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 = <<EOT
{
"Version": "2008-10-17",
"Statement": [
{
"Sid": "",
"Effect": "Allow",
"Principal": {
"Service": "opsworks.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
}
EOT
}
resource "aws_iam_role_policy" "opsworks_service" {
name = "tf_opsworks_service_%d"
role = "${aws_iam_role.opsworks_service.id}"
policy = <<EOT
{
"Statement": [
{
"Action": [
"ec2:*",
"iam:PassRole",
"cloudwatch:GetMetricStatistics",
"elasticloadbalancing:*",
"rds:*"
],
"Effect": "Allow",
"Resource": ["*"]
}
]
}
EOT
}
resource "aws_iam_role" "opsworks_instance" {
name = "tf_opsworks_instance_%d"
assume_role_policy = <<EOT
{
"Version": "2008-10-17",
"Statement": [
{
"Sid": "",
"Effect": "Allow",
"Principal": {
"Service": "ec2.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
}
EOT
}
resource "aws_iam_instance_profile" "opsworks_instance" {
name = "%s_profile"
roles = ["${aws_iam_role.opsworks_instance.name}"]
}`, rName, rInt, rInt, rInt, rName)
}
////////////////////////////
//// Checkers and Utilities
////////////////////////////