Skip to content

provider/aws: Allow aws_instances to be resized rather than forcing a new instance #11998

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

Merged
merged 1 commit into from
Feb 16, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 54 additions & 1 deletion builtin/providers/aws/resource_aws_instance.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,6 @@ func resourceAwsInstance() *schema.Resource {
"instance_type": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
},

"key_name": {
Expand Down Expand Up @@ -606,6 +605,60 @@ func resourceAwsInstanceUpdate(d *schema.ResourceData, meta interface{}) error {
}
}

if d.HasChange("instance_type") && !d.IsNewResource() {
log.Printf("[INFO] Stopping Instance %q for instance_type change", d.Id())
_, err := conn.StopInstances(&ec2.StopInstancesInput{
InstanceIds: []*string{aws.String(d.Id())},
})

stateConf := &resource.StateChangeConf{
Pending: []string{"pending", "running", "shutting-down", "stopped", "stopping"},
Target: []string{"stopped"},
Refresh: InstanceStateRefreshFunc(conn, d.Id()),
Timeout: 10 * time.Minute,
Delay: 10 * time.Second,
MinTimeout: 3 * time.Second,
}

_, err = stateConf.WaitForState()
if err != nil {
return fmt.Errorf(
"Error waiting for instance (%s) to stop: %s", d.Id(), err)
}

log.Printf("[INFO] Modifying instance type %s", d.Id())
_, err = conn.ModifyInstanceAttribute(&ec2.ModifyInstanceAttributeInput{
InstanceId: aws.String(d.Id()),
InstanceType: &ec2.AttributeValue{
Value: aws.String(d.Get("instance_type").(string)),
},
})
if err != nil {
return err
}

log.Printf("[INFO] Starting Instance %q after instance_type change", d.Id())
_, err = conn.StartInstances(&ec2.StartInstancesInput{
InstanceIds: []*string{aws.String(d.Id())},
})

stateConf = &resource.StateChangeConf{
Pending: []string{"pending", "stopped"},
Target: []string{"running"},
Refresh: InstanceStateRefreshFunc(conn, d.Id()),
Timeout: 10 * time.Minute,
Delay: 10 * time.Second,
MinTimeout: 3 * time.Second,
}

_, err = stateConf.WaitForState()
if err != nil {
return fmt.Errorf(
"Error waiting for instance (%s) to become ready: %s",
d.Id(), err)
}
}

if d.HasChange("disable_api_termination") {
_, err := conn.ModifyInstanceAttribute(&ec2.ModifyInstanceAttributeInput{
InstanceId: aws.String(d.Id()),
Expand Down
117 changes: 91 additions & 26 deletions builtin/providers/aws/resource_aws_instance_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ func TestAccAWSInstance_basic(t *testing.T) {
CheckDestroy: testAccCheckInstanceDestroy,
Steps: []resource.TestStep{
// Create a volume to cover #1249
resource.TestStep{
{
// Need a resource in this config so the provisioner will be available
Config: testAccInstanceConfig_pre,
Check: func(*terraform.State) error {
Expand All @@ -59,7 +59,7 @@ func TestAccAWSInstance_basic(t *testing.T) {
},
},

resource.TestStep{
{
Config: testAccInstanceConfig,
Check: resource.ComposeTestCheckFunc(
testAccCheckInstanceExists(
Expand All @@ -77,7 +77,7 @@ func TestAccAWSInstance_basic(t *testing.T) {
// We repeat the exact same test so that we can be sure
// that the user data hash stuff is working without generating
// an incorrect diff.
resource.TestStep{
{
Config: testAccInstanceConfig,
Check: resource.ComposeTestCheckFunc(
testAccCheckInstanceExists(
Expand All @@ -93,7 +93,7 @@ func TestAccAWSInstance_basic(t *testing.T) {
},

// Clean up volume created above
resource.TestStep{
{
Config: testAccInstanceConfig,
Check: func(*terraform.State) error {
conn := testAccProvider.Meta().(*AWSClient).ec2conn
Expand Down Expand Up @@ -134,7 +134,7 @@ func TestAccAWSInstance_GP2IopsDevice(t *testing.T) {
Providers: testAccProviders,
CheckDestroy: testAccCheckInstanceDestroy,
Steps: []resource.TestStep{
resource.TestStep{
{
Config: testAccInstanceGP2IopsDevice,
//Config: testAccInstanceConfigBlockDevices,
Check: resource.ComposeTestCheckFunc(
Expand Down Expand Up @@ -199,7 +199,7 @@ func TestAccAWSInstance_blockDevices(t *testing.T) {
Providers: testAccProviders,
CheckDestroy: testAccCheckInstanceDestroy,
Steps: []resource.TestStep{
resource.TestStep{
{
Config: testAccInstanceConfigBlockDevices,
Check: resource.ComposeTestCheckFunc(
testAccCheckInstanceExists(
Expand Down Expand Up @@ -254,7 +254,7 @@ func TestAccAWSInstance_rootInstanceStore(t *testing.T) {
Providers: testAccProviders,
CheckDestroy: testAccCheckInstanceDestroy,
Steps: []resource.TestStep{
resource.TestStep{
{
Config: `
resource "aws_instance" "foo" {
# us-west-2
Expand Down Expand Up @@ -323,7 +323,7 @@ func TestAcctABSInstance_noAMIEphemeralDevices(t *testing.T) {
Providers: testAccProviders,
CheckDestroy: testAccCheckInstanceDestroy,
Steps: []resource.TestStep{
resource.TestStep{
{
Config: `
resource "aws_instance" "foo" {
# us-west-2
Expand Down Expand Up @@ -400,23 +400,23 @@ func TestAccAWSInstance_sourceDestCheck(t *testing.T) {
Providers: testAccProviders,
CheckDestroy: testAccCheckInstanceDestroy,
Steps: []resource.TestStep{
resource.TestStep{
{
Config: testAccInstanceConfigSourceDestDisable,
Check: resource.ComposeTestCheckFunc(
testAccCheckInstanceExists("aws_instance.foo", &v),
testCheck(false),
),
},

resource.TestStep{
{
Config: testAccInstanceConfigSourceDestEnable,
Check: resource.ComposeTestCheckFunc(
testAccCheckInstanceExists("aws_instance.foo", &v),
testCheck(true),
),
},

resource.TestStep{
{
Config: testAccInstanceConfigSourceDestDisable,
Check: resource.ComposeTestCheckFunc(
testAccCheckInstanceExists("aws_instance.foo", &v),
Expand Down Expand Up @@ -454,15 +454,15 @@ func TestAccAWSInstance_disableApiTermination(t *testing.T) {
Providers: testAccProviders,
CheckDestroy: testAccCheckInstanceDestroy,
Steps: []resource.TestStep{
resource.TestStep{
{
Config: testAccInstanceConfigDisableAPITermination(true),
Check: resource.ComposeTestCheckFunc(
testAccCheckInstanceExists("aws_instance.foo", &v),
checkDisableApiTermination(true),
),
},

resource.TestStep{
{
Config: testAccInstanceConfigDisableAPITermination(false),
Check: resource.ComposeTestCheckFunc(
testAccCheckInstanceExists("aws_instance.foo", &v),
Expand All @@ -483,7 +483,7 @@ func TestAccAWSInstance_vpc(t *testing.T) {
Providers: testAccProviders,
CheckDestroy: testAccCheckInstanceDestroy,
Steps: []resource.TestStep{
resource.TestStep{
{
Config: testAccInstanceConfigVPC,
Check: resource.ComposeTestCheckFunc(
testAccCheckInstanceExists(
Expand Down Expand Up @@ -517,7 +517,7 @@ func TestAccAWSInstance_multipleRegions(t *testing.T) {
ProviderFactories: providerFactories,
CheckDestroy: testAccCheckInstanceDestroyWithProviders(&providers),
Steps: []resource.TestStep{
resource.TestStep{
{
Config: testAccInstanceConfigMultipleRegions,
Check: resource.ComposeTestCheckFunc(
testAccCheckInstanceExistsWithProviders(
Expand All @@ -540,7 +540,7 @@ func TestAccAWSInstance_NetworkInstanceSecurityGroups(t *testing.T) {
Providers: testAccProviders,
CheckDestroy: testAccCheckInstanceDestroy,
Steps: []resource.TestStep{
resource.TestStep{
{
Config: testAccInstanceNetworkInstanceSecurityGroups,
Check: resource.ComposeTestCheckFunc(
testAccCheckInstanceExists(
Expand All @@ -560,7 +560,7 @@ func TestAccAWSInstance_NetworkInstanceVPCSecurityGroupIDs(t *testing.T) {
Providers: testAccProviders,
CheckDestroy: testAccCheckInstanceDestroy,
Steps: []resource.TestStep{
resource.TestStep{
{
Config: testAccInstanceNetworkInstanceVPCSecurityGroupIDs,
Check: resource.ComposeTestCheckFunc(
testAccCheckInstanceExists(
Expand All @@ -583,7 +583,7 @@ func TestAccAWSInstance_tags(t *testing.T) {
Providers: testAccProviders,
CheckDestroy: testAccCheckInstanceDestroy,
Steps: []resource.TestStep{
resource.TestStep{
{
Config: testAccCheckInstanceConfigTags,
Check: resource.ComposeTestCheckFunc(
testAccCheckInstanceExists("aws_instance.foo", &v),
Expand All @@ -593,7 +593,7 @@ func TestAccAWSInstance_tags(t *testing.T) {
),
},

resource.TestStep{
{
Config: testAccCheckInstanceConfigTagsUpdate,
Check: resource.ComposeTestCheckFunc(
testAccCheckInstanceExists("aws_instance.foo", &v),
Expand Down Expand Up @@ -624,7 +624,7 @@ func TestAccAWSInstance_privateIP(t *testing.T) {
Providers: testAccProviders,
CheckDestroy: testAccCheckInstanceDestroy,
Steps: []resource.TestStep{
resource.TestStep{
{
Config: testAccInstanceConfigPrivateIP,
Check: resource.ComposeTestCheckFunc(
testAccCheckInstanceExists("aws_instance.foo", &v),
Expand Down Expand Up @@ -655,7 +655,7 @@ func TestAccAWSInstance_associatePublicIPAndPrivateIP(t *testing.T) {
Providers: testAccProviders,
CheckDestroy: testAccCheckInstanceDestroy,
Steps: []resource.TestStep{
resource.TestStep{
{
Config: testAccInstanceConfigAssociatePublicIPAndPrivateIP,
Check: resource.ComposeTestCheckFunc(
testAccCheckInstanceExists("aws_instance.foo", &v),
Expand Down Expand Up @@ -691,7 +691,7 @@ func TestAccAWSInstance_keyPairCheck(t *testing.T) {
Providers: testAccProviders,
CheckDestroy: testAccCheckInstanceDestroy,
Steps: []resource.TestStep{
resource.TestStep{
{
Config: testAccInstanceConfigKeyPair,
Check: resource.ComposeTestCheckFunc(
testAccCheckInstanceExists("aws_instance.foo", &v),
Expand All @@ -710,7 +710,7 @@ func TestAccAWSInstance_rootBlockDeviceMismatch(t *testing.T) {
Providers: testAccProviders,
CheckDestroy: testAccCheckInstanceDestroy,
Steps: []resource.TestStep{
resource.TestStep{
{
Config: testAccInstanceConfigRootBlockDeviceMismatch,
Check: resource.ComposeTestCheckFunc(
testAccCheckInstanceExists("aws_instance.foo", &v),
Expand Down Expand Up @@ -740,15 +740,15 @@ func TestAccAWSInstance_forceNewAndTagsDrift(t *testing.T) {
Providers: testAccProviders,
CheckDestroy: testAccCheckInstanceDestroy,
Steps: []resource.TestStep{
resource.TestStep{
{
Config: testAccInstanceConfigForceNewAndTagsDrift,
Check: resource.ComposeTestCheckFunc(
testAccCheckInstanceExists("aws_instance.foo", &v),
driftTags(&v),
),
ExpectNonEmptyPlan: true,
},
resource.TestStep{
{
Config: testAccInstanceConfigForceNewAndTagsDrift_Update,
Check: resource.ComposeTestCheckFunc(
testAccCheckInstanceExists("aws_instance.foo", &v),
Expand All @@ -758,6 +758,43 @@ func TestAccAWSInstance_forceNewAndTagsDrift(t *testing.T) {
})
}

func TestAccAWSInstance_changeInstanceType(t *testing.T) {
var before ec2.Instance
var after ec2.Instance

resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckInstanceDestroy,
Steps: []resource.TestStep{
{
Config: testAccInstanceConfigWithSmallInstanceType,
Check: resource.ComposeTestCheckFunc(
testAccCheckInstanceExists("aws_instance.foo", &before),
),
},
{
Config: testAccInstanceConfigUpdateInstanceType,
Check: resource.ComposeTestCheckFunc(
testAccCheckInstanceExists("aws_instance.foo", &after),
testAccCheckInstanceNotRecreated(
t, &before, &after),
),
},
},
})
}

func testAccCheckInstanceNotRecreated(t *testing.T,
before, after *ec2.Instance) resource.TestCheckFunc {
return func(s *terraform.State) error {
if *before.InstanceId != *after.InstanceId {
t.Fatalf("AWS Instance IDs have changed. Before %s. After %s", *before.InstanceId, *after.InstanceId)
}
return nil
}
}

func testAccCheckInstanceDestroy(s *terraform.State) error {
return testAccCheckInstanceDestroyWithProvider(s, testAccProvider)
}
Expand Down Expand Up @@ -873,7 +910,7 @@ func driftTags(instance *ec2.Instance) resource.TestCheckFunc {
_, err := conn.CreateTags(&ec2.CreateTagsInput{
Resources: []*string{instance.InstanceId},
Tags: []*ec2.Tag{
&ec2.Tag{
{
Key: aws.String("Drift"),
Value: aws.String("Happens"),
},
Expand Down Expand Up @@ -921,6 +958,34 @@ resource "aws_instance" "foo" {
}
`

const testAccInstanceConfigWithSmallInstanceType = `
resource "aws_instance" "foo" {
# us-west-2
ami = "ami-55a7ea65"
availability_zone = "us-west-2a"

instance_type = "m3.medium"

tags {
Name = "tf-acctest"
}
}
`

const testAccInstanceConfigUpdateInstanceType = `
resource "aws_instance" "foo" {
# us-west-2
ami = "ami-55a7ea65"
availability_zone = "us-west-2a"

instance_type = "m3.large"

tags {
Name = "tf-acctest"
}
}
`

const testAccInstanceGP2IopsDevice = `
resource "aws_instance" "foo" {
# us-west-2
Expand Down