Skip to content

Commit

Permalink
Should close hashicorp#22 and maybe hashicorp#50
Browse files Browse the repository at this point in the history
  • Loading branch information
Arthur Burkart committed Oct 20, 2017
1 parent b17bb9f commit 03adc67
Show file tree
Hide file tree
Showing 3 changed files with 187 additions and 43 deletions.
72 changes: 70 additions & 2 deletions aws/data_source_aws_instance_test.go
@@ -1,9 +1,9 @@
package aws

import (
"testing"

"fmt"
"regexp"
"testing"

"github.com/hashicorp/terraform/helper/acctest"
"github.com/hashicorp/terraform/helper/resource"
Expand All @@ -26,6 +26,37 @@ func TestAccAWSInstanceDataSource_basic(t *testing.T) {
})
}

func TestAccAWSInstanceDataSource_ebs_stopped(t *testing.T) {
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
Steps: []resource.TestStep{
{
Config: testAccInstanceDataSourceConfig_ebs_stopped,
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("data.aws_instance.web-instance", "ami", "ami-e389729b"),
resource.TestCheckResourceAttr("data.aws_instance.web-instance", "tags.%", "1"),
resource.TestCheckResourceAttr("data.aws_instance.web-instance", "instance_type", "m1.small"),
resource.TestCheckResourceAttr("data.aws_instance.web-instance", "instance_state", "stopped"),
),
},
},
})
}

func TestAccAWSInstanceDataSource_instanceStore_stopped(t *testing.T) {
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
Steps: []resource.TestStep{
{
Config: testAccInstanceDataSourceConfig_instanceStore_stopped,
ExpectError: regexp.MustCompile(`AMIs with an 'instance-store' root device cannot be stopped.`),
},
},
})
}

func TestAccAWSInstanceDataSource_tags(t *testing.T) {
rInt := acctest.RandInt()
resource.Test(t, resource.TestCase{
Expand Down Expand Up @@ -235,6 +266,43 @@ data "aws_instance" "web-instance" {
}
`

const testAccInstanceDataSourceConfig_ebs_stopped = `
resource "aws_instance" "web" {
# us-west-2
ami = "ami-e389729b"
instance_type = "m1.small"
instance_state = "stopped"
tags {
Name = "HelloWorld"
}
}
data "aws_instance" "web-instance" {
filter {
name = "instance-id"
values = ["${aws_instance.web.id}"]
}
}
`
const testAccInstanceDataSourceConfig_instanceStore_stopped = `
resource "aws_instance" "web" {
# us-west-2
ami = "ami-4fccb37f"
instance_type = "m1.small"
instance_state = "stopped"
tags {
Name = "HelloWorld"
}
}
data "aws_instance" "web-instance" {
filter {
name = "instance-id"
values = ["${aws_instance.web.id}"]
}
}
`

// Use the tags attribute to filter
func testAccInstanceDataSourceConfig_Tags(rInt int) string {
return fmt.Sprintf(`
Expand Down
156 changes: 116 additions & 40 deletions aws/resource_aws_instance.go
Expand Up @@ -203,6 +203,21 @@ func resourceAwsInstance() *schema.Resource {
"instance_state": {
Type: schema.TypeString,
Computed: true,
Optional: true,
ValidateFunc: func(v interface{}, name string) (warns []string, errs []error) {
s := v.(string)
validState := map[string]bool{
"stopped": true,
"running": true,
}

if !validState[s] {
errs = append(errs, fmt.Errorf(
"%q contains an invalid value, %q. Valid states are: %q and %q.",
name, s, "stopped", "running"))
}
return
},
},

"private_dns": {
Expand Down Expand Up @@ -455,6 +470,10 @@ func resourceAwsInstanceCreate(d *schema.ResourceData, meta interface{}) error {
UserData: instanceOpts.UserData64,
}

if err := validateInstanceState(conn, d); err != nil {
return err
}

_, ipv6CountOk := d.GetOk("ipv6_address_count")
_, ipv6AddressOk := d.GetOk("ipv6_addresses")

Expand Down Expand Up @@ -767,6 +786,12 @@ func resourceAwsInstanceUpdate(d *schema.ResourceData, meta interface{}) error {

d.Partial(true)

if !d.IsNewResource() {
if err := validateInstanceState(conn, d); err != nil {
return err
}
}

restricted := meta.(*AWSClient).IsGovCloud() || meta.(*AWSClient).IsChinaCloud()

if d.HasChange("tags") {
Expand Down Expand Up @@ -925,28 +950,12 @@ 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: d.Timeout(schema.TimeoutUpdate),
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)
if err := awsStopInstance(conn, d, "instance_type"); err != nil {
return err
}

log.Printf("[INFO] Modifying instance type %s", d.Id())
_, err = conn.ModifyInstanceAttribute(&ec2.ModifyInstanceAttributeInput{
_, err := conn.ModifyInstanceAttribute(&ec2.ModifyInstanceAttributeInput{
InstanceId: aws.String(d.Id()),
InstanceType: &ec2.AttributeValue{
Value: aws.String(d.Get("instance_type").(string)),
Expand All @@ -956,25 +965,8 @@ func resourceAwsInstanceUpdate(d *schema.ResourceData, meta interface{}) error {
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(), "terminated"),
Timeout: d.Timeout(schema.TimeoutUpdate),
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 err := awsStartInstance(conn, d, "instance_type"); err != nil {
return err
}
}

Expand Down Expand Up @@ -1021,6 +1013,20 @@ func resourceAwsInstanceUpdate(d *schema.ResourceData, meta interface{}) error {
}
}

if d.HasChange("instance_state") {
instance_state := d.Get("instance_state").(string)
if instance_state == "stopped" {
if err := awsStopInstance(conn, d, "instance_state"); err != nil {
return err
}
}
if instance_state == "running" {
if err := awsStartInstance(conn, d, "instance_state"); err != nil {
return err
}
}
}

// TODO(mitchellh): wait for the attributes we modified to
// persist the change...

Expand All @@ -1032,7 +1038,7 @@ func resourceAwsInstanceUpdate(d *schema.ResourceData, meta interface{}) error {
func resourceAwsInstanceDelete(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).ec2conn

if err := awsTerminateInstance(conn, d.Id(), d); err != nil {
if err := awsTerminateInstance(conn, d); err != nil {
return err
}

Expand Down Expand Up @@ -1677,7 +1683,55 @@ func buildAwsInstanceOpts(
return opts, nil
}

func awsTerminateInstance(conn *ec2.EC2, id string, d *schema.ResourceData) error {
func awsStartInstance(conn *ec2.EC2, d *schema.ResourceData, f string) error {
log.Printf("[INFO] Starting Instance %q after %q change", d.Id(), f)
_, 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(), "terminated"),
Timeout: d.Timeout(schema.TimeoutUpdate),
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)
}
return nil
}

func awsStopInstance(conn *ec2.EC2, d *schema.ResourceData, f string) error {
log.Printf("[INFO] Stopping Instance %q for %q change", d.Id(), f)
_, 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: d.Timeout(schema.TimeoutUpdate),
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)
}
return nil
}

func awsTerminateInstance(conn *ec2.EC2, d *schema.ResourceData) error {
id := d.Id()
log.Printf("[INFO] Terminating instance: %s", id)
req := &ec2.TerminateInstancesInput{
InstanceIds: []*string{aws.String(id)},
Expand Down Expand Up @@ -1750,3 +1804,25 @@ func getAwsInstanceVolumeIds(conn *ec2.EC2, d *schema.ResourceData) ([]*string,

return volumeIds, nil
}

func validateInstanceState(conn *ec2.EC2, d *schema.ResourceData) error {
log.Printf("[DEBUG] Checking whether instance can be stopped.")
imageId, imageIdOk := d.GetOk("ami")
instanceState, instanceStateOk := d.GetOk("instance_state")
if imageIdOk && instanceStateOk && instanceState == "stopped" {
resp, err := conn.DescribeImages(&ec2.DescribeImagesInput{
ImageIds: []*string{aws.String(imageId.(string))},
})
if err != nil {
return err
}
if len(resp.Images) == 0 {
return fmt.Errorf("The image id '%s' does not exist.", imageId)
}
image := resp.Images[0]
if *image.RootDeviceType == ec2.DeviceTypeInstanceStore {
return fmt.Errorf("AMIs with an 'instance-store' root device cannot be stopped.")
}
}
return nil
}
2 changes: 1 addition & 1 deletion aws/resource_aws_spot_instance_request.go
Expand Up @@ -343,7 +343,7 @@ func resourceAwsSpotInstanceRequestDelete(d *schema.ResourceData, meta interface

if instanceId := d.Get("spot_instance_id").(string); instanceId != "" {
log.Printf("[INFO] Terminating instance: %s", instanceId)
if err := awsTerminateInstance(conn, instanceId, d); err != nil {
if err := awsTerminateInstance(conn, d); err != nil {
return fmt.Errorf("Error terminating spot instance: %s", err)
}
}
Expand Down

0 comments on commit 03adc67

Please sign in to comment.