2014-06-27 18:47:19 +02:00
|
|
|
package aws
|
|
|
|
|
|
|
|
import (
|
2014-10-17 18:12:45 +02:00
|
|
|
"bytes"
|
2014-08-22 17:46:48 +02:00
|
|
|
"crypto/sha1"
|
2015-03-13 16:54:00 +01:00
|
|
|
"encoding/base64"
|
2014-08-22 17:46:48 +02:00
|
|
|
"encoding/hex"
|
2014-06-27 18:47:19 +02:00
|
|
|
"fmt"
|
|
|
|
"log"
|
2014-07-28 18:47:40 +02:00
|
|
|
"strings"
|
2014-07-01 19:10:11 +02:00
|
|
|
"time"
|
2014-06-27 18:47:19 +02:00
|
|
|
|
2015-06-03 20:36:57 +02:00
|
|
|
"github.com/aws/aws-sdk-go/aws"
|
|
|
|
"github.com/aws/aws-sdk-go/aws/awserr"
|
provider/aws: spot_instance_request
This is an iteration on the great work done by @dalehamel in PRs #2095
and #2109.
The core team went back and forth on how to best model Spot Instance
Requests, requesting and then rejecting a separate-resource
implementation in #2109.
After more internal discussion, we landed once again on a separate
resource to model Spot Instance Requests. Out of respect for
@dalehamel's already-significant donated time, with this I'm attempting
to pick up the work to take this across the finish line.
Important architectural decisions represented here:
* Spot Instance Requests are always of type "persistent", to properly
match Terraform's declarative model.
* The spot_instance_request resource exports several attributes that
are expected to be constantly changing as the spot market changes:
spot_bid_status, spot_request_state, and instance_id. Creating
additional resource dependencies based on these attributes is not
recommended, as Terraform diffs will be continually generated to keep
up with the live changes.
* When a Spot Instance Request is deleted/canceled, an attempt is made
to terminate the last-known attached spot instance. Race conditions
dictate that this attempt cannot guarantee that the associated spot
instance is terminated immediately.
Implementation notes:
* This version of aws_spot_instance_request borrows a lot of common
code from aws_instance.
* In order to facilitate borrowing, we introduce `awsInstanceOpts`, an
internal representation of instance details that's meant to be shared
between resources. The goal here would be to refactor ASG Launch
Configurations to use the same struct.
* The new aws_spot_instance_request acc. test is passing.
* All aws_instance acc. tests remain passing.
2015-06-05 17:12:09 +02:00
|
|
|
"github.com/aws/aws-sdk-go/aws/awsutil"
|
2015-06-03 20:36:57 +02:00
|
|
|
"github.com/aws/aws-sdk-go/service/ec2"
|
2014-08-22 03:38:43 +02:00
|
|
|
"github.com/hashicorp/terraform/helper/hashcode"
|
2014-07-01 19:10:11 +02:00
|
|
|
"github.com/hashicorp/terraform/helper/resource"
|
2014-08-22 03:38:43 +02:00
|
|
|
"github.com/hashicorp/terraform/helper/schema"
|
2014-06-27 18:47:19 +02:00
|
|
|
)
|
|
|
|
|
2014-08-22 03:38:43 +02:00
|
|
|
func resourceAwsInstance() *schema.Resource {
|
|
|
|
return &schema.Resource{
|
|
|
|
Create: resourceAwsInstanceCreate,
|
|
|
|
Read: resourceAwsInstanceRead,
|
|
|
|
Update: resourceAwsInstanceUpdate,
|
|
|
|
Delete: resourceAwsInstanceDelete,
|
|
|
|
|
2015-02-24 18:00:22 +01:00
|
|
|
SchemaVersion: 1,
|
|
|
|
MigrateState: resourceAwsInstanceMigrateState,
|
|
|
|
|
2014-08-22 03:38:43 +02:00
|
|
|
Schema: map[string]*schema.Schema{
|
|
|
|
"ami": &schema.Schema{
|
|
|
|
Type: schema.TypeString,
|
|
|
|
Required: true,
|
|
|
|
ForceNew: true,
|
|
|
|
},
|
|
|
|
|
|
|
|
"associate_public_ip_address": &schema.Schema{
|
|
|
|
Type: schema.TypeBool,
|
|
|
|
ForceNew: true,
|
provider/aws: spot_instance_request
This is an iteration on the great work done by @dalehamel in PRs #2095
and #2109.
The core team went back and forth on how to best model Spot Instance
Requests, requesting and then rejecting a separate-resource
implementation in #2109.
After more internal discussion, we landed once again on a separate
resource to model Spot Instance Requests. Out of respect for
@dalehamel's already-significant donated time, with this I'm attempting
to pick up the work to take this across the finish line.
Important architectural decisions represented here:
* Spot Instance Requests are always of type "persistent", to properly
match Terraform's declarative model.
* The spot_instance_request resource exports several attributes that
are expected to be constantly changing as the spot market changes:
spot_bid_status, spot_request_state, and instance_id. Creating
additional resource dependencies based on these attributes is not
recommended, as Terraform diffs will be continually generated to keep
up with the live changes.
* When a Spot Instance Request is deleted/canceled, an attempt is made
to terminate the last-known attached spot instance. Race conditions
dictate that this attempt cannot guarantee that the associated spot
instance is terminated immediately.
Implementation notes:
* This version of aws_spot_instance_request borrows a lot of common
code from aws_instance.
* In order to facilitate borrowing, we introduce `awsInstanceOpts`, an
internal representation of instance details that's meant to be shared
between resources. The goal here would be to refactor ASG Launch
Configurations to use the same struct.
* The new aws_spot_instance_request acc. test is passing.
* All aws_instance acc. tests remain passing.
2015-06-05 17:12:09 +02:00
|
|
|
Optional: true,
|
2014-08-22 03:38:43 +02:00
|
|
|
},
|
|
|
|
|
|
|
|
"availability_zone": &schema.Schema{
|
|
|
|
Type: schema.TypeString,
|
|
|
|
Optional: true,
|
|
|
|
Computed: true,
|
|
|
|
ForceNew: true,
|
|
|
|
},
|
|
|
|
|
2015-04-02 08:33:16 +02:00
|
|
|
"placement_group": &schema.Schema{
|
|
|
|
Type: schema.TypeString,
|
|
|
|
Optional: true,
|
|
|
|
Computed: true,
|
|
|
|
ForceNew: true,
|
|
|
|
},
|
|
|
|
|
2014-08-22 03:38:43 +02:00
|
|
|
"instance_type": &schema.Schema{
|
|
|
|
Type: schema.TypeString,
|
|
|
|
Required: true,
|
|
|
|
ForceNew: true,
|
|
|
|
},
|
|
|
|
|
|
|
|
"key_name": &schema.Schema{
|
|
|
|
Type: schema.TypeString,
|
2014-08-22 21:20:06 +02:00
|
|
|
Optional: true,
|
2014-08-22 03:38:43 +02:00
|
|
|
ForceNew: true,
|
2014-08-22 21:20:06 +02:00
|
|
|
Computed: true,
|
2014-08-22 03:38:43 +02:00
|
|
|
},
|
|
|
|
|
|
|
|
"subnet_id": &schema.Schema{
|
|
|
|
Type: schema.TypeString,
|
|
|
|
Optional: true,
|
|
|
|
Computed: true,
|
|
|
|
ForceNew: true,
|
|
|
|
},
|
|
|
|
|
|
|
|
"private_ip": &schema.Schema{
|
|
|
|
Type: schema.TypeString,
|
|
|
|
Optional: true,
|
2014-09-18 22:26:49 +02:00
|
|
|
ForceNew: true,
|
2014-08-22 03:38:43 +02:00
|
|
|
Computed: true,
|
|
|
|
},
|
|
|
|
|
|
|
|
"source_dest_check": &schema.Schema{
|
|
|
|
Type: schema.TypeBool,
|
|
|
|
Optional: true,
|
2015-06-03 22:54:01 +02:00
|
|
|
Default: true,
|
2014-08-22 03:38:43 +02:00
|
|
|
},
|
|
|
|
|
|
|
|
"user_data": &schema.Schema{
|
|
|
|
Type: schema.TypeString,
|
|
|
|
Optional: true,
|
|
|
|
ForceNew: true,
|
2014-08-22 17:46:48 +02:00
|
|
|
StateFunc: func(v interface{}) string {
|
2014-08-23 03:11:06 +02:00
|
|
|
switch v.(type) {
|
|
|
|
case string:
|
|
|
|
hash := sha1.Sum([]byte(v.(string)))
|
|
|
|
return hex.EncodeToString(hash[:])
|
|
|
|
default:
|
|
|
|
return ""
|
|
|
|
}
|
2014-08-22 17:46:48 +02:00
|
|
|
},
|
2014-08-22 03:38:43 +02:00
|
|
|
},
|
|
|
|
|
|
|
|
"security_groups": &schema.Schema{
|
|
|
|
Type: schema.TypeSet,
|
|
|
|
Optional: true,
|
2014-08-28 00:26:15 +02:00
|
|
|
Computed: true,
|
2014-10-11 02:14:35 +02:00
|
|
|
ForceNew: true,
|
2014-08-22 03:38:43 +02:00
|
|
|
Elem: &schema.Schema{Type: schema.TypeString},
|
2015-04-09 15:38:16 +02:00
|
|
|
Set: schema.HashString,
|
2014-08-22 03:38:43 +02:00
|
|
|
},
|
|
|
|
|
2015-03-07 07:14:04 +01:00
|
|
|
"vpc_security_group_ids": &schema.Schema{
|
2015-03-07 07:04:53 +01:00
|
|
|
Type: schema.TypeSet,
|
|
|
|
Optional: true,
|
|
|
|
Computed: true,
|
|
|
|
Elem: &schema.Schema{Type: schema.TypeString},
|
|
|
|
Set: func(v interface{}) int {
|
|
|
|
return hashcode.String(v.(string))
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
2014-08-22 03:38:43 +02:00
|
|
|
"public_dns": &schema.Schema{
|
|
|
|
Type: schema.TypeString,
|
|
|
|
Computed: true,
|
|
|
|
},
|
|
|
|
|
|
|
|
"public_ip": &schema.Schema{
|
|
|
|
Type: schema.TypeString,
|
|
|
|
Computed: true,
|
|
|
|
},
|
|
|
|
|
|
|
|
"private_dns": &schema.Schema{
|
|
|
|
Type: schema.TypeString,
|
|
|
|
Computed: true,
|
|
|
|
},
|
2014-09-03 12:18:40 +02:00
|
|
|
|
|
|
|
"ebs_optimized": &schema.Schema{
|
|
|
|
Type: schema.TypeBool,
|
|
|
|
Optional: true,
|
|
|
|
},
|
2014-10-09 01:43:13 +02:00
|
|
|
|
2015-05-15 21:18:05 +02:00
|
|
|
"disable_api_termination": &schema.Schema{
|
|
|
|
Type: schema.TypeBool,
|
|
|
|
Optional: true,
|
|
|
|
},
|
|
|
|
|
2015-06-25 16:58:28 +02:00
|
|
|
"monitoring": &schema.Schema{
|
|
|
|
Type: schema.TypeBool,
|
|
|
|
Optional: true,
|
|
|
|
},
|
|
|
|
|
2014-09-23 20:06:30 +02:00
|
|
|
"iam_instance_profile": &schema.Schema{
|
2014-09-28 20:51:49 +02:00
|
|
|
Type: schema.TypeString,
|
2014-09-23 20:06:30 +02:00
|
|
|
ForceNew: true,
|
|
|
|
Optional: true,
|
|
|
|
},
|
2015-02-24 18:00:22 +01:00
|
|
|
|
2014-11-04 12:08:30 +01:00
|
|
|
"tenancy": &schema.Schema{
|
|
|
|
Type: schema.TypeString,
|
|
|
|
Optional: true,
|
|
|
|
Computed: true,
|
|
|
|
ForceNew: true,
|
|
|
|
},
|
2015-02-24 18:00:22 +01:00
|
|
|
|
2014-10-13 22:55:59 +02:00
|
|
|
"tags": tagsSchema(),
|
2014-10-17 18:12:45 +02:00
|
|
|
|
|
|
|
"block_device": &schema.Schema{
|
2015-02-24 18:00:22 +01:00
|
|
|
Type: schema.TypeMap,
|
|
|
|
Optional: true,
|
|
|
|
Removed: "Split out into three sub-types; see Changelog and Docs",
|
|
|
|
},
|
|
|
|
|
|
|
|
"ebs_block_device": &schema.Schema{
|
2014-10-17 18:12:45 +02:00
|
|
|
Type: schema.TypeSet,
|
|
|
|
Optional: true,
|
2014-12-25 21:58:26 +01:00
|
|
|
Computed: true,
|
2014-10-17 18:12:45 +02:00
|
|
|
Elem: &schema.Resource{
|
|
|
|
Schema: map[string]*schema.Schema{
|
2015-02-24 18:00:22 +01:00
|
|
|
"delete_on_termination": &schema.Schema{
|
|
|
|
Type: schema.TypeBool,
|
|
|
|
Optional: true,
|
|
|
|
Default: true,
|
|
|
|
ForceNew: true,
|
|
|
|
},
|
|
|
|
|
2014-10-17 18:12:45 +02:00
|
|
|
"device_name": &schema.Schema{
|
|
|
|
Type: schema.TypeString,
|
|
|
|
Required: true,
|
|
|
|
ForceNew: true,
|
|
|
|
},
|
|
|
|
|
2015-02-24 18:00:22 +01:00
|
|
|
"encrypted": &schema.Schema{
|
|
|
|
Type: schema.TypeBool,
|
2014-11-22 10:50:22 +01:00
|
|
|
Optional: true,
|
2015-02-24 18:00:22 +01:00
|
|
|
Computed: true,
|
2014-11-22 10:50:22 +01:00
|
|
|
ForceNew: true,
|
|
|
|
},
|
|
|
|
|
2015-02-24 18:00:22 +01:00
|
|
|
"iops": &schema.Schema{
|
|
|
|
Type: schema.TypeInt,
|
2014-10-17 18:12:45 +02:00
|
|
|
Optional: true,
|
2014-12-25 18:21:05 +01:00
|
|
|
Computed: true,
|
2014-10-17 18:12:45 +02:00
|
|
|
ForceNew: true,
|
|
|
|
},
|
|
|
|
|
2015-02-24 18:00:22 +01:00
|
|
|
"snapshot_id": &schema.Schema{
|
2014-10-17 18:12:45 +02:00
|
|
|
Type: schema.TypeString,
|
|
|
|
Optional: true,
|
2014-12-25 18:21:05 +01:00
|
|
|
Computed: true,
|
2014-10-17 18:12:45 +02:00
|
|
|
ForceNew: true,
|
|
|
|
},
|
|
|
|
|
|
|
|
"volume_size": &schema.Schema{
|
|
|
|
Type: schema.TypeInt,
|
|
|
|
Optional: true,
|
2014-12-25 18:21:05 +01:00
|
|
|
Computed: true,
|
2014-10-17 18:12:45 +02:00
|
|
|
ForceNew: true,
|
|
|
|
},
|
|
|
|
|
2015-02-24 18:00:22 +01:00
|
|
|
"volume_type": &schema.Schema{
|
|
|
|
Type: schema.TypeString,
|
2014-10-17 18:12:45 +02:00
|
|
|
Optional: true,
|
2015-02-24 18:00:22 +01:00
|
|
|
Computed: true,
|
2014-10-17 18:12:45 +02:00
|
|
|
ForceNew: true,
|
|
|
|
},
|
2015-02-24 18:00:22 +01:00
|
|
|
},
|
|
|
|
},
|
|
|
|
Set: func(v interface{}) int {
|
|
|
|
var buf bytes.Buffer
|
|
|
|
m := v.(map[string]interface{})
|
|
|
|
buf.WriteString(fmt.Sprintf("%s-", m["device_name"].(string)))
|
|
|
|
buf.WriteString(fmt.Sprintf("%s-", m["snapshot_id"].(string)))
|
|
|
|
return hashcode.String(buf.String())
|
|
|
|
},
|
|
|
|
},
|
2014-10-17 18:12:45 +02:00
|
|
|
|
2015-02-24 18:00:22 +01:00
|
|
|
"ephemeral_block_device": &schema.Schema{
|
|
|
|
Type: schema.TypeSet,
|
|
|
|
Optional: true,
|
|
|
|
Computed: true,
|
|
|
|
ForceNew: true,
|
|
|
|
Elem: &schema.Resource{
|
|
|
|
Schema: map[string]*schema.Schema{
|
|
|
|
"device_name": &schema.Schema{
|
|
|
|
Type: schema.TypeString,
|
|
|
|
Required: true,
|
2014-10-17 18:12:45 +02:00
|
|
|
},
|
2015-03-03 07:07:36 +01:00
|
|
|
|
2015-02-24 18:00:22 +01:00
|
|
|
"virtual_name": &schema.Schema{
|
|
|
|
Type: schema.TypeString,
|
|
|
|
Required: true,
|
2015-03-03 07:07:36 +01:00
|
|
|
},
|
2014-10-17 18:12:45 +02:00
|
|
|
},
|
|
|
|
},
|
2015-02-24 18:00:22 +01:00
|
|
|
Set: func(v interface{}) int {
|
|
|
|
var buf bytes.Buffer
|
|
|
|
m := v.(map[string]interface{})
|
|
|
|
buf.WriteString(fmt.Sprintf("%s-", m["device_name"].(string)))
|
|
|
|
buf.WriteString(fmt.Sprintf("%s-", m["virtual_name"].(string)))
|
|
|
|
return hashcode.String(buf.String())
|
|
|
|
},
|
2014-10-17 18:12:45 +02:00
|
|
|
},
|
providers/aws: add root_block_device to aws_instance
AWS provides a single `BlockDeviceMapping` to manage three different
kinds of block devices:
(a) The root volume
(b) Ephemeral storage
(c) Additional EBS volumes
Each of these types has slightly different semantics [1].
(a) The root volume is defined by the AMI; it can only be customized
with `volume_size`, `volume_type`, and `delete_on_termination`.
(b) Ephemeral storage is made available based on instance type [2]. It's
attached automatically if _no_ block device mappings are specified, and
must otherwise be defined with block device mapping entries that contain
only DeviceName set to a device like "/dev/sdX" and VirtualName set to
"ephemeralN".
(c) Additional EBS volumes are controlled by mappings that omit
`virtual_name` and can specify `volume_size`, `volume_type`,
`delete_on_termination`, `snapshot_id`, and `encryption`.
After deciding to ignore root block devices to fix #859, we had users
with configurations that were attempting to manage the root block device chime
in on #913.
Terraform does not have the primitives to be able to properly handle a
single collection of resources that is partially managed and partially
computed, so our strategy here is to break out logical sub-resources for
Terraform and hide the BlockDeviceMapping inside the provider
implementation.
Now (a) is supported by the `root_block_device` sub-resource, and (b)
and (c) are still both merged together under `block_device`, though I
have yet to see ephemeral block devices working properly.
Looking into possibly separating out `ephemeral_block_device` and
`ebs_block_device` sub-resources as well, which seem like the logical
next step. We'll wait until the next big release for this, though, since
it will break backcompat.
[1] http://bit.ly/ec2bdmap
[2] http://bit.ly/instancestorebytype
Fixes #913
Refs #858
2015-02-18 18:45:30 +01:00
|
|
|
|
|
|
|
"root_block_device": &schema.Schema{
|
2015-02-24 18:00:22 +01:00
|
|
|
// TODO: This is a set because we don't support singleton
|
|
|
|
// sub-resources today. We'll enforce that the set only ever has
|
providers/aws: add root_block_device to aws_instance
AWS provides a single `BlockDeviceMapping` to manage three different
kinds of block devices:
(a) The root volume
(b) Ephemeral storage
(c) Additional EBS volumes
Each of these types has slightly different semantics [1].
(a) The root volume is defined by the AMI; it can only be customized
with `volume_size`, `volume_type`, and `delete_on_termination`.
(b) Ephemeral storage is made available based on instance type [2]. It's
attached automatically if _no_ block device mappings are specified, and
must otherwise be defined with block device mapping entries that contain
only DeviceName set to a device like "/dev/sdX" and VirtualName set to
"ephemeralN".
(c) Additional EBS volumes are controlled by mappings that omit
`virtual_name` and can specify `volume_size`, `volume_type`,
`delete_on_termination`, `snapshot_id`, and `encryption`.
After deciding to ignore root block devices to fix #859, we had users
with configurations that were attempting to manage the root block device chime
in on #913.
Terraform does not have the primitives to be able to properly handle a
single collection of resources that is partially managed and partially
computed, so our strategy here is to break out logical sub-resources for
Terraform and hide the BlockDeviceMapping inside the provider
implementation.
Now (a) is supported by the `root_block_device` sub-resource, and (b)
and (c) are still both merged together under `block_device`, though I
have yet to see ephemeral block devices working properly.
Looking into possibly separating out `ephemeral_block_device` and
`ebs_block_device` sub-resources as well, which seem like the logical
next step. We'll wait until the next big release for this, though, since
it will break backcompat.
[1] http://bit.ly/ec2bdmap
[2] http://bit.ly/instancestorebytype
Fixes #913
Refs #858
2015-02-18 18:45:30 +01:00
|
|
|
// length zero or one below. When TF gains support for
|
|
|
|
// sub-resources this can be converted.
|
2015-02-24 18:00:22 +01:00
|
|
|
Type: schema.TypeSet,
|
providers/aws: add root_block_device to aws_instance
AWS provides a single `BlockDeviceMapping` to manage three different
kinds of block devices:
(a) The root volume
(b) Ephemeral storage
(c) Additional EBS volumes
Each of these types has slightly different semantics [1].
(a) The root volume is defined by the AMI; it can only be customized
with `volume_size`, `volume_type`, and `delete_on_termination`.
(b) Ephemeral storage is made available based on instance type [2]. It's
attached automatically if _no_ block device mappings are specified, and
must otherwise be defined with block device mapping entries that contain
only DeviceName set to a device like "/dev/sdX" and VirtualName set to
"ephemeralN".
(c) Additional EBS volumes are controlled by mappings that omit
`virtual_name` and can specify `volume_size`, `volume_type`,
`delete_on_termination`, `snapshot_id`, and `encryption`.
After deciding to ignore root block devices to fix #859, we had users
with configurations that were attempting to manage the root block device chime
in on #913.
Terraform does not have the primitives to be able to properly handle a
single collection of resources that is partially managed and partially
computed, so our strategy here is to break out logical sub-resources for
Terraform and hide the BlockDeviceMapping inside the provider
implementation.
Now (a) is supported by the `root_block_device` sub-resource, and (b)
and (c) are still both merged together under `block_device`, though I
have yet to see ephemeral block devices working properly.
Looking into possibly separating out `ephemeral_block_device` and
`ebs_block_device` sub-resources as well, which seem like the logical
next step. We'll wait until the next big release for this, though, since
it will break backcompat.
[1] http://bit.ly/ec2bdmap
[2] http://bit.ly/instancestorebytype
Fixes #913
Refs #858
2015-02-18 18:45:30 +01:00
|
|
|
Optional: true,
|
|
|
|
Computed: true,
|
|
|
|
Elem: &schema.Resource{
|
|
|
|
// "You can only modify the volume size, volume type, and Delete on
|
|
|
|
// Termination flag on the block device mapping entry for the root
|
|
|
|
// device volume." - bit.ly/ec2bdmap
|
|
|
|
Schema: map[string]*schema.Schema{
|
|
|
|
"delete_on_termination": &schema.Schema{
|
|
|
|
Type: schema.TypeBool,
|
|
|
|
Optional: true,
|
|
|
|
Default: true,
|
|
|
|
ForceNew: true,
|
|
|
|
},
|
|
|
|
|
2015-02-24 18:00:22 +01:00
|
|
|
"iops": &schema.Schema{
|
providers/aws: add root_block_device to aws_instance
AWS provides a single `BlockDeviceMapping` to manage three different
kinds of block devices:
(a) The root volume
(b) Ephemeral storage
(c) Additional EBS volumes
Each of these types has slightly different semantics [1].
(a) The root volume is defined by the AMI; it can only be customized
with `volume_size`, `volume_type`, and `delete_on_termination`.
(b) Ephemeral storage is made available based on instance type [2]. It's
attached automatically if _no_ block device mappings are specified, and
must otherwise be defined with block device mapping entries that contain
only DeviceName set to a device like "/dev/sdX" and VirtualName set to
"ephemeralN".
(c) Additional EBS volumes are controlled by mappings that omit
`virtual_name` and can specify `volume_size`, `volume_type`,
`delete_on_termination`, `snapshot_id`, and `encryption`.
After deciding to ignore root block devices to fix #859, we had users
with configurations that were attempting to manage the root block device chime
in on #913.
Terraform does not have the primitives to be able to properly handle a
single collection of resources that is partially managed and partially
computed, so our strategy here is to break out logical sub-resources for
Terraform and hide the BlockDeviceMapping inside the provider
implementation.
Now (a) is supported by the `root_block_device` sub-resource, and (b)
and (c) are still both merged together under `block_device`, though I
have yet to see ephemeral block devices working properly.
Looking into possibly separating out `ephemeral_block_device` and
`ebs_block_device` sub-resources as well, which seem like the logical
next step. We'll wait until the next big release for this, though, since
it will break backcompat.
[1] http://bit.ly/ec2bdmap
[2] http://bit.ly/instancestorebytype
Fixes #913
Refs #858
2015-02-18 18:45:30 +01:00
|
|
|
Type: schema.TypeInt,
|
|
|
|
Optional: true,
|
|
|
|
Computed: true,
|
|
|
|
ForceNew: true,
|
|
|
|
},
|
|
|
|
|
|
|
|
"volume_size": &schema.Schema{
|
|
|
|
Type: schema.TypeInt,
|
|
|
|
Optional: true,
|
|
|
|
Computed: true,
|
|
|
|
ForceNew: true,
|
|
|
|
},
|
|
|
|
|
|
|
|
"volume_type": &schema.Schema{
|
|
|
|
Type: schema.TypeString,
|
|
|
|
Optional: true,
|
|
|
|
Computed: true,
|
|
|
|
ForceNew: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
2015-02-24 18:00:22 +01:00
|
|
|
Set: func(v interface{}) int {
|
2015-04-08 00:05:00 +02:00
|
|
|
// there can be only one root device; no need to hash anything
|
|
|
|
return 0
|
2015-02-24 18:00:22 +01:00
|
|
|
},
|
providers/aws: add root_block_device to aws_instance
AWS provides a single `BlockDeviceMapping` to manage three different
kinds of block devices:
(a) The root volume
(b) Ephemeral storage
(c) Additional EBS volumes
Each of these types has slightly different semantics [1].
(a) The root volume is defined by the AMI; it can only be customized
with `volume_size`, `volume_type`, and `delete_on_termination`.
(b) Ephemeral storage is made available based on instance type [2]. It's
attached automatically if _no_ block device mappings are specified, and
must otherwise be defined with block device mapping entries that contain
only DeviceName set to a device like "/dev/sdX" and VirtualName set to
"ephemeralN".
(c) Additional EBS volumes are controlled by mappings that omit
`virtual_name` and can specify `volume_size`, `volume_type`,
`delete_on_termination`, `snapshot_id`, and `encryption`.
After deciding to ignore root block devices to fix #859, we had users
with configurations that were attempting to manage the root block device chime
in on #913.
Terraform does not have the primitives to be able to properly handle a
single collection of resources that is partially managed and partially
computed, so our strategy here is to break out logical sub-resources for
Terraform and hide the BlockDeviceMapping inside the provider
implementation.
Now (a) is supported by the `root_block_device` sub-resource, and (b)
and (c) are still both merged together under `block_device`, though I
have yet to see ephemeral block devices working properly.
Looking into possibly separating out `ephemeral_block_device` and
`ebs_block_device` sub-resources as well, which seem like the logical
next step. We'll wait until the next big release for this, though, since
it will break backcompat.
[1] http://bit.ly/ec2bdmap
[2] http://bit.ly/instancestorebytype
Fixes #913
Refs #858
2015-02-18 18:45:30 +01:00
|
|
|
},
|
2014-08-22 03:38:43 +02:00
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func resourceAwsInstanceCreate(d *schema.ResourceData, meta interface{}) error {
|
2015-04-16 22:05:55 +02:00
|
|
|
conn := meta.(*AWSClient).ec2conn
|
2014-06-27 18:47:19 +02:00
|
|
|
|
provider/aws: spot_instance_request
This is an iteration on the great work done by @dalehamel in PRs #2095
and #2109.
The core team went back and forth on how to best model Spot Instance
Requests, requesting and then rejecting a separate-resource
implementation in #2109.
After more internal discussion, we landed once again on a separate
resource to model Spot Instance Requests. Out of respect for
@dalehamel's already-significant donated time, with this I'm attempting
to pick up the work to take this across the finish line.
Important architectural decisions represented here:
* Spot Instance Requests are always of type "persistent", to properly
match Terraform's declarative model.
* The spot_instance_request resource exports several attributes that
are expected to be constantly changing as the spot market changes:
spot_bid_status, spot_request_state, and instance_id. Creating
additional resource dependencies based on these attributes is not
recommended, as Terraform diffs will be continually generated to keep
up with the live changes.
* When a Spot Instance Request is deleted/canceled, an attempt is made
to terminate the last-known attached spot instance. Race conditions
dictate that this attempt cannot guarantee that the associated spot
instance is terminated immediately.
Implementation notes:
* This version of aws_spot_instance_request borrows a lot of common
code from aws_instance.
* In order to facilitate borrowing, we introduce `awsInstanceOpts`, an
internal representation of instance details that's meant to be shared
between resources. The goal here would be to refactor ASG Launch
Configurations to use the same struct.
* The new aws_spot_instance_request acc. test is passing.
* All aws_instance acc. tests remain passing.
2015-06-05 17:12:09 +02:00
|
|
|
instanceOpts, err := buildAwsInstanceOpts(d, meta)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
2014-07-29 14:06:53 +02:00
|
|
|
}
|
|
|
|
|
2014-07-15 06:56:37 +02:00
|
|
|
// Build the creation struct
|
2015-04-16 19:01:10 +02:00
|
|
|
runOpts := &ec2.RunInstancesInput{
|
provider/aws: spot_instance_request
This is an iteration on the great work done by @dalehamel in PRs #2095
and #2109.
The core team went back and forth on how to best model Spot Instance
Requests, requesting and then rejecting a separate-resource
implementation in #2109.
After more internal discussion, we landed once again on a separate
resource to model Spot Instance Requests. Out of respect for
@dalehamel's already-significant donated time, with this I'm attempting
to pick up the work to take this across the finish line.
Important architectural decisions represented here:
* Spot Instance Requests are always of type "persistent", to properly
match Terraform's declarative model.
* The spot_instance_request resource exports several attributes that
are expected to be constantly changing as the spot market changes:
spot_bid_status, spot_request_state, and instance_id. Creating
additional resource dependencies based on these attributes is not
recommended, as Terraform diffs will be continually generated to keep
up with the live changes.
* When a Spot Instance Request is deleted/canceled, an attempt is made
to terminate the last-known attached spot instance. Race conditions
dictate that this attempt cannot guarantee that the associated spot
instance is terminated immediately.
Implementation notes:
* This version of aws_spot_instance_request borrows a lot of common
code from aws_instance.
* In order to facilitate borrowing, we introduce `awsInstanceOpts`, an
internal representation of instance details that's meant to be shared
between resources. The goal here would be to refactor ASG Launch
Configurations to use the same struct.
* The new aws_spot_instance_request acc. test is passing.
* All aws_instance acc. tests remain passing.
2015-06-05 17:12:09 +02:00
|
|
|
BlockDeviceMappings: instanceOpts.BlockDeviceMappings,
|
|
|
|
DisableAPITermination: instanceOpts.DisableAPITermination,
|
|
|
|
EBSOptimized: instanceOpts.EBSOptimized,
|
2015-06-25 16:58:28 +02:00
|
|
|
Monitoring: instanceOpts.Monitoring,
|
provider/aws: spot_instance_request
This is an iteration on the great work done by @dalehamel in PRs #2095
and #2109.
The core team went back and forth on how to best model Spot Instance
Requests, requesting and then rejecting a separate-resource
implementation in #2109.
After more internal discussion, we landed once again on a separate
resource to model Spot Instance Requests. Out of respect for
@dalehamel's already-significant donated time, with this I'm attempting
to pick up the work to take this across the finish line.
Important architectural decisions represented here:
* Spot Instance Requests are always of type "persistent", to properly
match Terraform's declarative model.
* The spot_instance_request resource exports several attributes that
are expected to be constantly changing as the spot market changes:
spot_bid_status, spot_request_state, and instance_id. Creating
additional resource dependencies based on these attributes is not
recommended, as Terraform diffs will be continually generated to keep
up with the live changes.
* When a Spot Instance Request is deleted/canceled, an attempt is made
to terminate the last-known attached spot instance. Race conditions
dictate that this attempt cannot guarantee that the associated spot
instance is terminated immediately.
Implementation notes:
* This version of aws_spot_instance_request borrows a lot of common
code from aws_instance.
* In order to facilitate borrowing, we introduce `awsInstanceOpts`, an
internal representation of instance details that's meant to be shared
between resources. The goal here would be to refactor ASG Launch
Configurations to use the same struct.
* The new aws_spot_instance_request acc. test is passing.
* All aws_instance acc. tests remain passing.
2015-06-05 17:12:09 +02:00
|
|
|
IAMInstanceProfile: instanceOpts.IAMInstanceProfile,
|
|
|
|
ImageID: instanceOpts.ImageID,
|
|
|
|
InstanceType: instanceOpts.InstanceType,
|
2015-06-10 21:02:26 +02:00
|
|
|
KeyName: instanceOpts.KeyName,
|
2015-05-15 21:18:05 +02:00
|
|
|
MaxCount: aws.Long(int64(1)),
|
|
|
|
MinCount: aws.Long(int64(1)),
|
provider/aws: spot_instance_request
This is an iteration on the great work done by @dalehamel in PRs #2095
and #2109.
The core team went back and forth on how to best model Spot Instance
Requests, requesting and then rejecting a separate-resource
implementation in #2109.
After more internal discussion, we landed once again on a separate
resource to model Spot Instance Requests. Out of respect for
@dalehamel's already-significant donated time, with this I'm attempting
to pick up the work to take this across the finish line.
Important architectural decisions represented here:
* Spot Instance Requests are always of type "persistent", to properly
match Terraform's declarative model.
* The spot_instance_request resource exports several attributes that
are expected to be constantly changing as the spot market changes:
spot_bid_status, spot_request_state, and instance_id. Creating
additional resource dependencies based on these attributes is not
recommended, as Terraform diffs will be continually generated to keep
up with the live changes.
* When a Spot Instance Request is deleted/canceled, an attempt is made
to terminate the last-known attached spot instance. Race conditions
dictate that this attempt cannot guarantee that the associated spot
instance is terminated immediately.
Implementation notes:
* This version of aws_spot_instance_request borrows a lot of common
code from aws_instance.
* In order to facilitate borrowing, we introduce `awsInstanceOpts`, an
internal representation of instance details that's meant to be shared
between resources. The goal here would be to refactor ASG Launch
Configurations to use the same struct.
* The new aws_spot_instance_request acc. test is passing.
* All aws_instance acc. tests remain passing.
2015-06-05 17:12:09 +02:00
|
|
|
NetworkInterfaces: instanceOpts.NetworkInterfaces,
|
|
|
|
Placement: instanceOpts.Placement,
|
|
|
|
PrivateIPAddress: instanceOpts.PrivateIPAddress,
|
|
|
|
SecurityGroupIDs: instanceOpts.SecurityGroupIDs,
|
|
|
|
SecurityGroups: instanceOpts.SecurityGroups,
|
|
|
|
SubnetID: instanceOpts.SubnetID,
|
|
|
|
UserData: instanceOpts.UserData64,
|
2015-02-24 18:00:22 +01:00
|
|
|
}
|
|
|
|
|
2014-07-15 06:56:37 +02:00
|
|
|
// Create the instance
|
provider/aws: spot_instance_request
This is an iteration on the great work done by @dalehamel in PRs #2095
and #2109.
The core team went back and forth on how to best model Spot Instance
Requests, requesting and then rejecting a separate-resource
implementation in #2109.
After more internal discussion, we landed once again on a separate
resource to model Spot Instance Requests. Out of respect for
@dalehamel's already-significant donated time, with this I'm attempting
to pick up the work to take this across the finish line.
Important architectural decisions represented here:
* Spot Instance Requests are always of type "persistent", to properly
match Terraform's declarative model.
* The spot_instance_request resource exports several attributes that
are expected to be constantly changing as the spot market changes:
spot_bid_status, spot_request_state, and instance_id. Creating
additional resource dependencies based on these attributes is not
recommended, as Terraform diffs will be continually generated to keep
up with the live changes.
* When a Spot Instance Request is deleted/canceled, an attempt is made
to terminate the last-known attached spot instance. Race conditions
dictate that this attempt cannot guarantee that the associated spot
instance is terminated immediately.
Implementation notes:
* This version of aws_spot_instance_request borrows a lot of common
code from aws_instance.
* In order to facilitate borrowing, we introduce `awsInstanceOpts`, an
internal representation of instance details that's meant to be shared
between resources. The goal here would be to refactor ASG Launch
Configurations to use the same struct.
* The new aws_spot_instance_request acc. test is passing.
* All aws_instance acc. tests remain passing.
2015-06-05 17:12:09 +02:00
|
|
|
log.Printf("[DEBUG] Run configuration: %s", awsutil.StringValue(runOpts))
|
2015-05-21 21:58:34 +02:00
|
|
|
|
|
|
|
var runResp *ec2.Reservation
|
|
|
|
for i := 0; i < 5; i++ {
|
|
|
|
runResp, err = conn.RunInstances(runOpts)
|
|
|
|
if awsErr, ok := err.(awserr.Error); ok {
|
|
|
|
// IAM profiles can take ~10 seconds to propagate in AWS:
|
|
|
|
// http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/iam-roles-for-amazon-ec2.html#launch-instance-with-role-console
|
|
|
|
if awsErr.Code() == "InvalidParameterValue" && strings.Contains(awsErr.Message(), "Invalid IAM Instance Profile") {
|
|
|
|
log.Printf("[DEBUG] Invalid IAM Instance Profile referenced, retrying...")
|
|
|
|
time.Sleep(2 * time.Second)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break
|
|
|
|
}
|
2014-06-27 18:47:19 +02:00
|
|
|
if err != nil {
|
2014-08-22 03:38:43 +02:00
|
|
|
return fmt.Errorf("Error launching source instance: %s", err)
|
2014-06-27 18:47:19 +02:00
|
|
|
}
|
|
|
|
|
2015-04-16 19:01:10 +02:00
|
|
|
instance := runResp.Instances[0]
|
2015-03-05 16:45:39 +01:00
|
|
|
log.Printf("[INFO] Instance ID: %s", *instance.InstanceID)
|
2014-06-27 18:47:19 +02:00
|
|
|
|
|
|
|
// Store the resulting ID so we can look this up later
|
2015-03-05 16:45:39 +01:00
|
|
|
d.SetId(*instance.InstanceID)
|
2014-06-27 18:47:19 +02:00
|
|
|
|
|
|
|
// Wait for the instance to become running so we can get some attributes
|
|
|
|
// that aren't available until later.
|
|
|
|
log.Printf(
|
|
|
|
"[DEBUG] Waiting for instance (%s) to become running",
|
2015-03-05 16:45:39 +01:00
|
|
|
*instance.InstanceID)
|
2014-07-01 19:10:11 +02:00
|
|
|
|
|
|
|
stateConf := &resource.StateChangeConf{
|
2014-07-28 18:10:28 +02:00
|
|
|
Pending: []string{"pending"},
|
|
|
|
Target: "running",
|
2015-04-16 19:01:10 +02:00
|
|
|
Refresh: InstanceStateRefreshFunc(conn, *instance.InstanceID),
|
2014-07-28 18:10:28 +02:00
|
|
|
Timeout: 10 * time.Minute,
|
|
|
|
Delay: 10 * time.Second,
|
|
|
|
MinTimeout: 3 * time.Second,
|
2014-07-01 19:10:11 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
instanceRaw, err := stateConf.WaitForState()
|
2014-06-27 18:47:19 +02:00
|
|
|
if err != nil {
|
2014-08-22 03:38:43 +02:00
|
|
|
return fmt.Errorf(
|
2014-06-27 18:47:19 +02:00
|
|
|
"Error waiting for instance (%s) to become ready: %s",
|
2015-03-05 16:45:39 +01:00
|
|
|
*instance.InstanceID, err)
|
2014-06-27 18:47:19 +02:00
|
|
|
}
|
2014-07-01 19:10:11 +02:00
|
|
|
|
2014-06-27 18:47:19 +02:00
|
|
|
instance = instanceRaw.(*ec2.Instance)
|
|
|
|
|
2014-07-15 02:24:10 +02:00
|
|
|
// Initialize the connection info
|
2015-03-05 16:45:39 +01:00
|
|
|
if instance.PublicIPAddress != nil {
|
|
|
|
d.SetConnInfo(map[string]string{
|
|
|
|
"type": "ssh",
|
|
|
|
"host": *instance.PublicIPAddress,
|
|
|
|
})
|
2015-04-22 12:25:28 +02:00
|
|
|
} else if instance.PrivateIPAddress != nil {
|
|
|
|
d.SetConnInfo(map[string]string{
|
|
|
|
"type": "ssh",
|
|
|
|
"host": *instance.PrivateIPAddress,
|
|
|
|
})
|
2015-03-05 16:45:39 +01:00
|
|
|
}
|
2014-07-15 02:24:10 +02:00
|
|
|
|
2014-06-27 18:47:19 +02:00
|
|
|
// Set our attributes
|
2014-08-22 03:38:43 +02:00
|
|
|
if err := resourceAwsInstanceRead(d, meta); err != nil {
|
|
|
|
return err
|
2014-07-14 23:16:59 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// Update if we need to
|
2014-08-22 03:38:43 +02:00
|
|
|
return resourceAwsInstanceUpdate(d, meta)
|
2014-07-14 23:16:59 +02:00
|
|
|
}
|
|
|
|
|
2014-08-22 03:38:43 +02:00
|
|
|
func resourceAwsInstanceRead(d *schema.ResourceData, meta interface{}) error {
|
2015-04-16 22:05:55 +02:00
|
|
|
conn := meta.(*AWSClient).ec2conn
|
2014-06-27 18:47:19 +02:00
|
|
|
|
2015-04-16 19:01:10 +02:00
|
|
|
resp, err := conn.DescribeInstances(&ec2.DescribeInstancesInput{
|
|
|
|
InstanceIDs: []*string{aws.String(d.Id())},
|
2015-03-05 16:45:39 +01:00
|
|
|
})
|
2014-06-27 18:47:19 +02:00
|
|
|
if err != nil {
|
|
|
|
// If the instance was not found, return nil so that we can show
|
|
|
|
// that the instance is gone.
|
2015-05-20 13:21:23 +02:00
|
|
|
if ec2err, ok := err.(awserr.Error); ok && ec2err.Code() == "InvalidInstanceID.NotFound" {
|
2014-08-22 03:38:43 +02:00
|
|
|
d.SetId("")
|
|
|
|
return nil
|
2014-06-27 18:47:19 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// Some other error, report it
|
2014-08-22 03:38:43 +02:00
|
|
|
return err
|
2014-06-27 18:47:19 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// If nothing was found, then return no state
|
|
|
|
if len(resp.Reservations) == 0 {
|
2014-08-22 03:38:43 +02:00
|
|
|
d.SetId("")
|
|
|
|
return nil
|
2014-06-27 18:47:19 +02:00
|
|
|
}
|
|
|
|
|
2015-04-16 19:01:10 +02:00
|
|
|
instance := resp.Reservations[0].Instances[0]
|
2014-06-27 18:47:19 +02:00
|
|
|
|
|
|
|
// If the instance is terminated, then it is gone
|
2015-03-05 16:45:39 +01:00
|
|
|
if *instance.State.Name == "terminated" {
|
2014-08-22 03:38:43 +02:00
|
|
|
d.SetId("")
|
|
|
|
return nil
|
2014-06-27 18:47:19 +02:00
|
|
|
}
|
|
|
|
|
2015-03-19 16:07:46 +01:00
|
|
|
if instance.Placement != nil {
|
|
|
|
d.Set("availability_zone", instance.Placement.AvailabilityZone)
|
|
|
|
}
|
|
|
|
if instance.Placement.Tenancy != nil {
|
|
|
|
d.Set("tenancy", instance.Placement.Tenancy)
|
|
|
|
}
|
|
|
|
|
2015-06-27 02:12:20 +02:00
|
|
|
d.Set("ami", instance.ImageID)
|
2015-06-16 20:23:50 +02:00
|
|
|
d.Set("instance_type", instance.InstanceType)
|
2014-08-22 03:38:43 +02:00
|
|
|
d.Set("key_name", instance.KeyName)
|
2015-03-05 16:45:39 +01:00
|
|
|
d.Set("public_dns", instance.PublicDNSName)
|
|
|
|
d.Set("public_ip", instance.PublicIPAddress)
|
2014-08-22 03:38:43 +02:00
|
|
|
d.Set("private_dns", instance.PrivateDNSName)
|
2015-03-05 16:45:39 +01:00
|
|
|
d.Set("private_ip", instance.PrivateIPAddress)
|
|
|
|
if len(instance.NetworkInterfaces) > 0 {
|
|
|
|
d.Set("subnet_id", instance.NetworkInterfaces[0].SubnetID)
|
|
|
|
} else {
|
|
|
|
d.Set("subnet_id", instance.SubnetID)
|
|
|
|
}
|
|
|
|
d.Set("ebs_optimized", instance.EBSOptimized)
|
2015-06-25 16:58:28 +02:00
|
|
|
|
|
|
|
if instance.Monitoring != nil && instance.Monitoring.State != nil {
|
2015-06-25 18:07:11 +02:00
|
|
|
monitoringState := *instance.Monitoring.State
|
|
|
|
d.Set("monitoring", monitoringState == "enabled" || monitoringState == "pending")
|
2015-06-25 16:58:28 +02:00
|
|
|
}
|
|
|
|
|
2015-05-12 21:58:10 +02:00
|
|
|
d.Set("tags", tagsToMap(instance.Tags))
|
2014-08-22 03:38:43 +02:00
|
|
|
|
|
|
|
// Determine whether we're referring to security groups with
|
|
|
|
// IDs or names. We use a heuristic to figure this out. By default,
|
|
|
|
// we use IDs if we're in a VPC. However, if we previously had an
|
|
|
|
// all-name list of security groups, we use names. Or, if we had any
|
|
|
|
// IDs, we use IDs.
|
2015-03-18 20:54:44 +01:00
|
|
|
useID := instance.SubnetID != nil && *instance.SubnetID != ""
|
2014-08-22 03:38:43 +02:00
|
|
|
if v := d.Get("security_groups"); v != nil {
|
2015-04-22 00:07:30 +02:00
|
|
|
match := useID
|
|
|
|
sgs := v.(*schema.Set).List()
|
|
|
|
if len(sgs) > 0 {
|
|
|
|
match = false
|
|
|
|
for _, v := range v.(*schema.Set).List() {
|
|
|
|
if strings.HasPrefix(v.(string), "sg-") {
|
|
|
|
match = true
|
|
|
|
break
|
|
|
|
}
|
2014-07-28 18:47:40 +02:00
|
|
|
}
|
|
|
|
}
|
2014-08-22 03:38:43 +02:00
|
|
|
|
|
|
|
useID = match
|
2014-07-28 18:47:40 +02:00
|
|
|
}
|
|
|
|
|
2014-07-15 06:56:37 +02:00
|
|
|
// Build up the security groups
|
2015-04-15 19:12:17 +02:00
|
|
|
sgs := make([]string, 0, len(instance.SecurityGroups))
|
2015-03-07 07:04:53 +01:00
|
|
|
if useID {
|
2015-04-15 19:12:17 +02:00
|
|
|
for _, sg := range instance.SecurityGroups {
|
|
|
|
sgs = append(sgs, *sg.GroupID)
|
2015-03-07 07:04:53 +01:00
|
|
|
}
|
2015-04-15 19:12:17 +02:00
|
|
|
log.Printf("[DEBUG] Setting Security Group IDs: %#v", sgs)
|
|
|
|
if err := d.Set("vpc_security_group_ids", sgs); err != nil {
|
|
|
|
return err
|
2015-03-07 07:16:59 +01:00
|
|
|
}
|
2015-03-07 07:04:53 +01:00
|
|
|
} else {
|
2015-04-15 19:12:17 +02:00
|
|
|
for _, sg := range instance.SecurityGroups {
|
|
|
|
sgs = append(sgs, *sg.GroupName)
|
2014-07-15 06:56:37 +02:00
|
|
|
}
|
2015-04-15 19:12:17 +02:00
|
|
|
log.Printf("[DEBUG] Setting Security Group Names: %#v", sgs)
|
|
|
|
if err := d.Set("security_groups", sgs); err != nil {
|
2014-12-25 18:21:05 +01:00
|
|
|
return err
|
|
|
|
}
|
2014-07-15 06:56:37 +02:00
|
|
|
}
|
2015-02-18 23:45:13 +01:00
|
|
|
|
2015-04-16 19:01:10 +02:00
|
|
|
if err := readBlockDevices(d, instance, conn); err != nil {
|
2014-10-17 18:12:45 +02:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2014-08-22 03:38:43 +02:00
|
|
|
return nil
|
2014-06-27 18:47:19 +02:00
|
|
|
}
|
2014-07-01 19:10:11 +02:00
|
|
|
|
2014-11-21 17:58:34 +01:00
|
|
|
func resourceAwsInstanceUpdate(d *schema.ResourceData, meta interface{}) error {
|
2015-04-16 22:05:55 +02:00
|
|
|
conn := meta.(*AWSClient).ec2conn
|
2015-04-16 19:01:10 +02:00
|
|
|
|
|
|
|
d.Partial(true)
|
2015-05-20 22:33:01 +02:00
|
|
|
if err := setTags(conn, d); err != nil {
|
|
|
|
return err
|
|
|
|
} else {
|
|
|
|
d.SetPartial("tags")
|
|
|
|
}
|
2014-11-21 17:58:34 +01:00
|
|
|
|
2015-03-18 20:54:44 +01:00
|
|
|
// SourceDestCheck can only be set on VPC instances
|
|
|
|
if d.Get("subnet_id").(string) != "" {
|
|
|
|
log.Printf("[INFO] Modifying instance %s", d.Id())
|
2015-04-16 19:01:10 +02:00
|
|
|
_, err := conn.ModifyInstanceAttribute(&ec2.ModifyInstanceAttributeInput{
|
2015-03-18 20:54:44 +01:00
|
|
|
InstanceID: aws.String(d.Id()),
|
|
|
|
SourceDestCheck: &ec2.AttributeBooleanValue{
|
|
|
|
Value: aws.Boolean(d.Get("source_dest_check").(bool)),
|
|
|
|
},
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2014-11-21 17:58:34 +01:00
|
|
|
}
|
|
|
|
|
2015-04-22 00:07:30 +02:00
|
|
|
if d.HasChange("vpc_security_group_ids") {
|
|
|
|
var groups []*string
|
2015-06-11 14:59:42 +02:00
|
|
|
if v := d.Get("vpc_security_group_ids").(*schema.Set); v.Len() > 0 {
|
|
|
|
for _, v := range v.List() {
|
2015-04-22 00:07:30 +02:00
|
|
|
groups = append(groups, aws.String(v.(string)))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
_, err := conn.ModifyInstanceAttribute(&ec2.ModifyInstanceAttributeInput{
|
|
|
|
InstanceID: aws.String(d.Id()),
|
|
|
|
Groups: groups,
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2015-05-15 21:18:05 +02:00
|
|
|
}
|
2015-04-22 00:07:30 +02:00
|
|
|
|
2015-05-15 21:18:05 +02:00
|
|
|
if d.HasChange("disable_api_termination") {
|
|
|
|
_, err := conn.ModifyInstanceAttribute(&ec2.ModifyInstanceAttributeInput{
|
|
|
|
InstanceID: aws.String(d.Id()),
|
|
|
|
DisableAPITermination: &ec2.AttributeBooleanValue{
|
|
|
|
Value: aws.Boolean(d.Get("disable_api_termination").(bool)),
|
|
|
|
},
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2015-04-22 00:07:30 +02:00
|
|
|
}
|
|
|
|
|
2015-07-20 19:32:58 +02:00
|
|
|
if d.HasChange("monitoring") {
|
|
|
|
var mErr error
|
|
|
|
if d.Get("monitoring").(bool) {
|
|
|
|
log.Printf("[DEBUG] Enabling monitoring for Instance (%s)", d.Id())
|
|
|
|
_, mErr = conn.MonitorInstances(&ec2.MonitorInstancesInput{
|
|
|
|
InstanceIDs: []*string{aws.String(d.Id())},
|
|
|
|
})
|
|
|
|
} else {
|
|
|
|
log.Printf("[DEBUG] Disabling monitoring for Instance (%s)", d.Id())
|
|
|
|
_, mErr = conn.UnmonitorInstances(&ec2.UnmonitorInstancesInput{
|
|
|
|
InstanceIDs: []*string{aws.String(d.Id())},
|
|
|
|
})
|
|
|
|
}
|
|
|
|
if mErr != nil {
|
|
|
|
return fmt.Errorf("[WARN] Error updating Instance monitoring: %s", mErr)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-02-21 21:26:46 +01:00
|
|
|
// TODO(mitchellh): wait for the attributes we modified to
|
|
|
|
// persist the change...
|
|
|
|
|
2015-04-16 19:01:10 +02:00
|
|
|
d.Partial(false)
|
2014-11-21 17:58:34 +01:00
|
|
|
|
2015-04-16 19:01:10 +02:00
|
|
|
return resourceAwsInstanceRead(d, meta)
|
2014-11-21 17:58:34 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
func resourceAwsInstanceDelete(d *schema.ResourceData, meta interface{}) error {
|
2015-04-16 22:05:55 +02:00
|
|
|
conn := meta.(*AWSClient).ec2conn
|
2014-11-21 17:58:34 +01:00
|
|
|
|
provider/aws: spot_instance_request
This is an iteration on the great work done by @dalehamel in PRs #2095
and #2109.
The core team went back and forth on how to best model Spot Instance
Requests, requesting and then rejecting a separate-resource
implementation in #2109.
After more internal discussion, we landed once again on a separate
resource to model Spot Instance Requests. Out of respect for
@dalehamel's already-significant donated time, with this I'm attempting
to pick up the work to take this across the finish line.
Important architectural decisions represented here:
* Spot Instance Requests are always of type "persistent", to properly
match Terraform's declarative model.
* The spot_instance_request resource exports several attributes that
are expected to be constantly changing as the spot market changes:
spot_bid_status, spot_request_state, and instance_id. Creating
additional resource dependencies based on these attributes is not
recommended, as Terraform diffs will be continually generated to keep
up with the live changes.
* When a Spot Instance Request is deleted/canceled, an attempt is made
to terminate the last-known attached spot instance. Race conditions
dictate that this attempt cannot guarantee that the associated spot
instance is terminated immediately.
Implementation notes:
* This version of aws_spot_instance_request borrows a lot of common
code from aws_instance.
* In order to facilitate borrowing, we introduce `awsInstanceOpts`, an
internal representation of instance details that's meant to be shared
between resources. The goal here would be to refactor ASG Launch
Configurations to use the same struct.
* The new aws_spot_instance_request acc. test is passing.
* All aws_instance acc. tests remain passing.
2015-06-05 17:12:09 +02:00
|
|
|
if err := awsTerminateInstance(conn, d.Id()); err != nil {
|
|
|
|
return err
|
2014-11-21 17:58:34 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
d.SetId("")
|
2014-08-22 03:38:43 +02:00
|
|
|
return nil
|
2014-06-27 18:47:19 +02:00
|
|
|
}
|
2014-07-01 19:10:11 +02:00
|
|
|
|
|
|
|
// InstanceStateRefreshFunc returns a resource.StateRefreshFunc that is used to watch
|
|
|
|
// an EC2 instance.
|
|
|
|
func InstanceStateRefreshFunc(conn *ec2.EC2, instanceID string) resource.StateRefreshFunc {
|
|
|
|
return func() (interface{}, string, error) {
|
2015-04-16 19:01:10 +02:00
|
|
|
resp, err := conn.DescribeInstances(&ec2.DescribeInstancesInput{
|
|
|
|
InstanceIDs: []*string{aws.String(instanceID)},
|
2015-03-05 16:45:39 +01:00
|
|
|
})
|
2014-07-01 19:10:11 +02:00
|
|
|
if err != nil {
|
2015-05-20 13:21:23 +02:00
|
|
|
if ec2err, ok := err.(awserr.Error); ok && ec2err.Code() == "InvalidInstanceID.NotFound" {
|
2014-07-01 19:10:11 +02:00
|
|
|
// Set this to nil as if we didn't find anything.
|
|
|
|
resp = nil
|
|
|
|
} else {
|
|
|
|
log.Printf("Error on InstanceStateRefresh: %s", err)
|
|
|
|
return nil, "", err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if resp == nil || len(resp.Reservations) == 0 || len(resp.Reservations[0].Instances) == 0 {
|
|
|
|
// Sometimes AWS just has consistency issues and doesn't see
|
|
|
|
// our instance yet. Return an empty state.
|
|
|
|
return nil, "", nil
|
|
|
|
}
|
|
|
|
|
2015-04-16 19:01:10 +02:00
|
|
|
i := resp.Reservations[0].Instances[0]
|
2015-03-05 16:45:39 +01:00
|
|
|
return i, *i.State.Name, nil
|
2014-07-01 19:10:11 +02:00
|
|
|
}
|
|
|
|
}
|
2014-10-17 18:12:45 +02:00
|
|
|
|
2015-04-16 19:01:10 +02:00
|
|
|
func readBlockDevices(d *schema.ResourceData, instance *ec2.Instance, conn *ec2.EC2) error {
|
|
|
|
ibds, err := readBlockDevicesFromInstance(instance, conn)
|
2015-02-24 18:00:22 +01:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := d.Set("ebs_block_device", ibds["ebs"]); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if ibds["root"] != nil {
|
|
|
|
if err := d.Set("root_block_device", []interface{}{ibds["root"]}); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2014-07-01 19:10:11 +02:00
|
|
|
}
|
2015-02-24 18:00:22 +01:00
|
|
|
|
|
|
|
return nil
|
2014-07-01 19:10:11 +02:00
|
|
|
}
|
2014-10-17 18:12:45 +02:00
|
|
|
|
2015-04-16 19:01:10 +02:00
|
|
|
func readBlockDevicesFromInstance(instance *ec2.Instance, conn *ec2.EC2) (map[string]interface{}, error) {
|
2015-02-24 18:00:22 +01:00
|
|
|
blockDevices := make(map[string]interface{})
|
|
|
|
blockDevices["ebs"] = make([]map[string]interface{}, 0)
|
|
|
|
blockDevices["root"] = nil
|
|
|
|
|
2015-04-16 19:01:10 +02:00
|
|
|
instanceBlockDevices := make(map[string]*ec2.InstanceBlockDeviceMapping)
|
2015-02-24 18:00:22 +01:00
|
|
|
for _, bd := range instance.BlockDeviceMappings {
|
|
|
|
if bd.EBS != nil {
|
|
|
|
instanceBlockDevices[*(bd.EBS.VolumeID)] = bd
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-03-19 19:14:31 +01:00
|
|
|
if len(instanceBlockDevices) == 0 {
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
|
2015-04-16 19:01:10 +02:00
|
|
|
volIDs := make([]*string, 0, len(instanceBlockDevices))
|
2015-02-24 18:00:22 +01:00
|
|
|
for volID := range instanceBlockDevices {
|
2015-04-16 19:01:10 +02:00
|
|
|
volIDs = append(volIDs, aws.String(volID))
|
2015-02-24 18:00:22 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// Need to call DescribeVolumes to get volume_size and volume_type for each
|
|
|
|
// EBS block device
|
2015-04-16 19:01:10 +02:00
|
|
|
volResp, err := conn.DescribeVolumes(&ec2.DescribeVolumesInput{
|
2015-02-24 18:00:22 +01:00
|
|
|
VolumeIDs: volIDs,
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, vol := range volResp.Volumes {
|
|
|
|
instanceBd := instanceBlockDevices[*vol.VolumeID]
|
|
|
|
bd := make(map[string]interface{})
|
|
|
|
|
|
|
|
if instanceBd.EBS != nil && instanceBd.EBS.DeleteOnTermination != nil {
|
|
|
|
bd["delete_on_termination"] = *instanceBd.EBS.DeleteOnTermination
|
|
|
|
}
|
|
|
|
if vol.Size != nil {
|
|
|
|
bd["volume_size"] = *vol.Size
|
|
|
|
}
|
|
|
|
if vol.VolumeType != nil {
|
|
|
|
bd["volume_type"] = *vol.VolumeType
|
|
|
|
}
|
|
|
|
if vol.IOPS != nil {
|
|
|
|
bd["iops"] = *vol.IOPS
|
|
|
|
}
|
|
|
|
|
|
|
|
if blockDeviceIsRoot(instanceBd, instance) {
|
|
|
|
blockDevices["root"] = bd
|
|
|
|
} else {
|
2015-03-23 17:58:45 +01:00
|
|
|
if instanceBd.DeviceName != nil {
|
|
|
|
bd["device_name"] = *instanceBd.DeviceName
|
|
|
|
}
|
2015-02-24 18:00:22 +01:00
|
|
|
if vol.Encrypted != nil {
|
|
|
|
bd["encrypted"] = *vol.Encrypted
|
|
|
|
}
|
|
|
|
if vol.SnapshotID != nil {
|
|
|
|
bd["snapshot_id"] = *vol.SnapshotID
|
|
|
|
}
|
|
|
|
|
|
|
|
blockDevices["ebs"] = append(blockDevices["ebs"].([]map[string]interface{}), bd)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return blockDevices, nil
|
|
|
|
}
|
|
|
|
|
2015-04-16 19:01:10 +02:00
|
|
|
func blockDeviceIsRoot(bd *ec2.InstanceBlockDeviceMapping, instance *ec2.Instance) bool {
|
2015-02-24 18:00:22 +01:00
|
|
|
return (bd.DeviceName != nil &&
|
|
|
|
instance.RootDeviceName != nil &&
|
|
|
|
*bd.DeviceName == *instance.RootDeviceName)
|
2014-10-17 18:12:45 +02:00
|
|
|
}
|
2015-03-23 17:58:45 +01:00
|
|
|
|
2015-04-16 19:01:10 +02:00
|
|
|
func fetchRootDeviceName(ami string, conn *ec2.EC2) (*string, error) {
|
2015-03-23 17:58:45 +01:00
|
|
|
if ami == "" {
|
|
|
|
return nil, fmt.Errorf("Cannot fetch root device name for blank AMI ID.")
|
|
|
|
}
|
|
|
|
|
|
|
|
log.Printf("[DEBUG] Describing AMI %q to get root block device name", ami)
|
2015-06-08 18:00:05 +02:00
|
|
|
res, err := conn.DescribeImages(&ec2.DescribeImagesInput{
|
|
|
|
ImageIDs: []*string{aws.String(ami)},
|
|
|
|
})
|
|
|
|
if err != nil {
|
2015-03-23 17:58:45 +01:00
|
|
|
return nil, err
|
|
|
|
}
|
2015-06-08 18:00:05 +02:00
|
|
|
|
|
|
|
// For a bad image, we just return nil so we don't block a refresh
|
|
|
|
if len(res.Images) == 0 {
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
image := res.Images[0]
|
|
|
|
rootDeviceName := image.RootDeviceName
|
|
|
|
|
|
|
|
// Some AMIs have a RootDeviceName like "/dev/sda1" that does not appear as a
|
|
|
|
// DeviceName in the BlockDeviceMapping list (which will instead have
|
|
|
|
// something like "/dev/sda")
|
|
|
|
//
|
|
|
|
// While this seems like it breaks an invariant of AMIs, it ends up working
|
|
|
|
// on the AWS side, and AMIs like this are common enough that we need to
|
|
|
|
// special case it so Terraform does the right thing.
|
|
|
|
//
|
|
|
|
// Our heuristic is: if the RootDeviceName does not appear in the
|
|
|
|
// BlockDeviceMapping, assume that the DeviceName of the first
|
|
|
|
// BlockDeviceMapping entry serves as the root device.
|
|
|
|
rootDeviceNameInMapping := false
|
|
|
|
for _, bdm := range image.BlockDeviceMappings {
|
|
|
|
if bdm.DeviceName == image.RootDeviceName {
|
|
|
|
rootDeviceNameInMapping = true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if !rootDeviceNameInMapping && len(image.BlockDeviceMappings) > 0 {
|
|
|
|
rootDeviceName = image.BlockDeviceMappings[0].DeviceName
|
|
|
|
}
|
|
|
|
|
2015-07-07 23:12:41 +02:00
|
|
|
if rootDeviceName == nil {
|
|
|
|
return nil, fmt.Errorf("[WARN] Error finding Root Device Name for AMI (%s)", ami)
|
|
|
|
}
|
|
|
|
|
2015-06-08 18:00:05 +02:00
|
|
|
return rootDeviceName, nil
|
2014-10-17 18:12:45 +02:00
|
|
|
}
|
provider/aws: spot_instance_request
This is an iteration on the great work done by @dalehamel in PRs #2095
and #2109.
The core team went back and forth on how to best model Spot Instance
Requests, requesting and then rejecting a separate-resource
implementation in #2109.
After more internal discussion, we landed once again on a separate
resource to model Spot Instance Requests. Out of respect for
@dalehamel's already-significant donated time, with this I'm attempting
to pick up the work to take this across the finish line.
Important architectural decisions represented here:
* Spot Instance Requests are always of type "persistent", to properly
match Terraform's declarative model.
* The spot_instance_request resource exports several attributes that
are expected to be constantly changing as the spot market changes:
spot_bid_status, spot_request_state, and instance_id. Creating
additional resource dependencies based on these attributes is not
recommended, as Terraform diffs will be continually generated to keep
up with the live changes.
* When a Spot Instance Request is deleted/canceled, an attempt is made
to terminate the last-known attached spot instance. Race conditions
dictate that this attempt cannot guarantee that the associated spot
instance is terminated immediately.
Implementation notes:
* This version of aws_spot_instance_request borrows a lot of common
code from aws_instance.
* In order to facilitate borrowing, we introduce `awsInstanceOpts`, an
internal representation of instance details that's meant to be shared
between resources. The goal here would be to refactor ASG Launch
Configurations to use the same struct.
* The new aws_spot_instance_request acc. test is passing.
* All aws_instance acc. tests remain passing.
2015-06-05 17:12:09 +02:00
|
|
|
|
|
|
|
func readBlockDeviceMappingsFromConfig(
|
|
|
|
d *schema.ResourceData, conn *ec2.EC2) ([]*ec2.BlockDeviceMapping, error) {
|
|
|
|
blockDevices := make([]*ec2.BlockDeviceMapping, 0)
|
|
|
|
|
|
|
|
if v, ok := d.GetOk("ebs_block_device"); ok {
|
|
|
|
vL := v.(*schema.Set).List()
|
|
|
|
for _, v := range vL {
|
|
|
|
bd := v.(map[string]interface{})
|
|
|
|
ebs := &ec2.EBSBlockDevice{
|
|
|
|
DeleteOnTermination: aws.Boolean(bd["delete_on_termination"].(bool)),
|
|
|
|
}
|
|
|
|
|
|
|
|
if v, ok := bd["snapshot_id"].(string); ok && v != "" {
|
|
|
|
ebs.SnapshotID = aws.String(v)
|
|
|
|
}
|
|
|
|
|
|
|
|
if v, ok := bd["encrypted"].(bool); ok && v {
|
|
|
|
ebs.Encrypted = aws.Boolean(v)
|
|
|
|
}
|
|
|
|
|
|
|
|
if v, ok := bd["volume_size"].(int); ok && v != 0 {
|
|
|
|
ebs.VolumeSize = aws.Long(int64(v))
|
|
|
|
}
|
|
|
|
|
|
|
|
if v, ok := bd["volume_type"].(string); ok && v != "" {
|
|
|
|
ebs.VolumeType = aws.String(v)
|
|
|
|
}
|
|
|
|
|
|
|
|
if v, ok := bd["iops"].(int); ok && v > 0 {
|
|
|
|
ebs.IOPS = aws.Long(int64(v))
|
|
|
|
}
|
|
|
|
|
|
|
|
blockDevices = append(blockDevices, &ec2.BlockDeviceMapping{
|
|
|
|
DeviceName: aws.String(bd["device_name"].(string)),
|
|
|
|
EBS: ebs,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if v, ok := d.GetOk("ephemeral_block_device"); ok {
|
|
|
|
vL := v.(*schema.Set).List()
|
|
|
|
for _, v := range vL {
|
|
|
|
bd := v.(map[string]interface{})
|
|
|
|
blockDevices = append(blockDevices, &ec2.BlockDeviceMapping{
|
|
|
|
DeviceName: aws.String(bd["device_name"].(string)),
|
|
|
|
VirtualName: aws.String(bd["virtual_name"].(string)),
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if v, ok := d.GetOk("root_block_device"); ok {
|
|
|
|
vL := v.(*schema.Set).List()
|
|
|
|
if len(vL) > 1 {
|
|
|
|
return nil, fmt.Errorf("Cannot specify more than one root_block_device.")
|
|
|
|
}
|
|
|
|
for _, v := range vL {
|
|
|
|
bd := v.(map[string]interface{})
|
|
|
|
ebs := &ec2.EBSBlockDevice{
|
|
|
|
DeleteOnTermination: aws.Boolean(bd["delete_on_termination"].(bool)),
|
|
|
|
}
|
|
|
|
|
|
|
|
if v, ok := bd["volume_size"].(int); ok && v != 0 {
|
|
|
|
ebs.VolumeSize = aws.Long(int64(v))
|
|
|
|
}
|
|
|
|
|
|
|
|
if v, ok := bd["volume_type"].(string); ok && v != "" {
|
|
|
|
ebs.VolumeType = aws.String(v)
|
|
|
|
}
|
|
|
|
|
|
|
|
if v, ok := bd["iops"].(int); ok && v > 0 {
|
|
|
|
ebs.IOPS = aws.Long(int64(v))
|
|
|
|
}
|
|
|
|
|
|
|
|
if dn, err := fetchRootDeviceName(d.Get("ami").(string), conn); err == nil {
|
|
|
|
if dn == nil {
|
|
|
|
return nil, fmt.Errorf(
|
|
|
|
"Expected 1 AMI for ID: %s, got none",
|
|
|
|
d.Get("ami").(string))
|
|
|
|
}
|
|
|
|
|
|
|
|
blockDevices = append(blockDevices, &ec2.BlockDeviceMapping{
|
|
|
|
DeviceName: dn,
|
|
|
|
EBS: ebs,
|
|
|
|
})
|
|
|
|
} else {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return blockDevices, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
type awsInstanceOpts struct {
|
|
|
|
BlockDeviceMappings []*ec2.BlockDeviceMapping
|
|
|
|
DisableAPITermination *bool
|
|
|
|
EBSOptimized *bool
|
2015-06-25 16:58:28 +02:00
|
|
|
Monitoring *ec2.RunInstancesMonitoringEnabled
|
provider/aws: spot_instance_request
This is an iteration on the great work done by @dalehamel in PRs #2095
and #2109.
The core team went back and forth on how to best model Spot Instance
Requests, requesting and then rejecting a separate-resource
implementation in #2109.
After more internal discussion, we landed once again on a separate
resource to model Spot Instance Requests. Out of respect for
@dalehamel's already-significant donated time, with this I'm attempting
to pick up the work to take this across the finish line.
Important architectural decisions represented here:
* Spot Instance Requests are always of type "persistent", to properly
match Terraform's declarative model.
* The spot_instance_request resource exports several attributes that
are expected to be constantly changing as the spot market changes:
spot_bid_status, spot_request_state, and instance_id. Creating
additional resource dependencies based on these attributes is not
recommended, as Terraform diffs will be continually generated to keep
up with the live changes.
* When a Spot Instance Request is deleted/canceled, an attempt is made
to terminate the last-known attached spot instance. Race conditions
dictate that this attempt cannot guarantee that the associated spot
instance is terminated immediately.
Implementation notes:
* This version of aws_spot_instance_request borrows a lot of common
code from aws_instance.
* In order to facilitate borrowing, we introduce `awsInstanceOpts`, an
internal representation of instance details that's meant to be shared
between resources. The goal here would be to refactor ASG Launch
Configurations to use the same struct.
* The new aws_spot_instance_request acc. test is passing.
* All aws_instance acc. tests remain passing.
2015-06-05 17:12:09 +02:00
|
|
|
IAMInstanceProfile *ec2.IAMInstanceProfileSpecification
|
|
|
|
ImageID *string
|
|
|
|
InstanceType *string
|
|
|
|
KeyName *string
|
|
|
|
NetworkInterfaces []*ec2.InstanceNetworkInterfaceSpecification
|
|
|
|
Placement *ec2.Placement
|
|
|
|
PrivateIPAddress *string
|
|
|
|
SecurityGroupIDs []*string
|
|
|
|
SecurityGroups []*string
|
|
|
|
SpotPlacement *ec2.SpotPlacement
|
|
|
|
SubnetID *string
|
|
|
|
UserData64 *string
|
|
|
|
}
|
|
|
|
|
|
|
|
func buildAwsInstanceOpts(
|
|
|
|
d *schema.ResourceData, meta interface{}) (*awsInstanceOpts, error) {
|
|
|
|
conn := meta.(*AWSClient).ec2conn
|
|
|
|
|
|
|
|
opts := &awsInstanceOpts{
|
|
|
|
DisableAPITermination: aws.Boolean(d.Get("disable_api_termination").(bool)),
|
|
|
|
EBSOptimized: aws.Boolean(d.Get("ebs_optimized").(bool)),
|
|
|
|
ImageID: aws.String(d.Get("ami").(string)),
|
|
|
|
InstanceType: aws.String(d.Get("instance_type").(string)),
|
|
|
|
}
|
|
|
|
|
2015-06-25 16:58:28 +02:00
|
|
|
opts.Monitoring = &ec2.RunInstancesMonitoringEnabled{
|
|
|
|
Enabled: aws.Boolean(d.Get("monitoring").(bool)),
|
|
|
|
}
|
|
|
|
|
provider/aws: spot_instance_request
This is an iteration on the great work done by @dalehamel in PRs #2095
and #2109.
The core team went back and forth on how to best model Spot Instance
Requests, requesting and then rejecting a separate-resource
implementation in #2109.
After more internal discussion, we landed once again on a separate
resource to model Spot Instance Requests. Out of respect for
@dalehamel's already-significant donated time, with this I'm attempting
to pick up the work to take this across the finish line.
Important architectural decisions represented here:
* Spot Instance Requests are always of type "persistent", to properly
match Terraform's declarative model.
* The spot_instance_request resource exports several attributes that
are expected to be constantly changing as the spot market changes:
spot_bid_status, spot_request_state, and instance_id. Creating
additional resource dependencies based on these attributes is not
recommended, as Terraform diffs will be continually generated to keep
up with the live changes.
* When a Spot Instance Request is deleted/canceled, an attempt is made
to terminate the last-known attached spot instance. Race conditions
dictate that this attempt cannot guarantee that the associated spot
instance is terminated immediately.
Implementation notes:
* This version of aws_spot_instance_request borrows a lot of common
code from aws_instance.
* In order to facilitate borrowing, we introduce `awsInstanceOpts`, an
internal representation of instance details that's meant to be shared
between resources. The goal here would be to refactor ASG Launch
Configurations to use the same struct.
* The new aws_spot_instance_request acc. test is passing.
* All aws_instance acc. tests remain passing.
2015-06-05 17:12:09 +02:00
|
|
|
opts.IAMInstanceProfile = &ec2.IAMInstanceProfileSpecification{
|
|
|
|
Name: aws.String(d.Get("iam_instance_profile").(string)),
|
|
|
|
}
|
|
|
|
|
|
|
|
opts.UserData64 = aws.String(
|
|
|
|
base64.StdEncoding.EncodeToString([]byte(d.Get("user_data").(string))))
|
|
|
|
|
|
|
|
// check for non-default Subnet, and cast it to a String
|
|
|
|
subnet, hasSubnet := d.GetOk("subnet_id")
|
|
|
|
subnetID := subnet.(string)
|
|
|
|
|
|
|
|
// Placement is used for aws_instance; SpotPlacement is used for
|
|
|
|
// aws_spot_instance_request. They represent the same data. :-|
|
|
|
|
opts.Placement = &ec2.Placement{
|
|
|
|
AvailabilityZone: aws.String(d.Get("availability_zone").(string)),
|
|
|
|
GroupName: aws.String(d.Get("placement_group").(string)),
|
|
|
|
}
|
|
|
|
|
|
|
|
opts.SpotPlacement = &ec2.SpotPlacement{
|
|
|
|
AvailabilityZone: aws.String(d.Get("availability_zone").(string)),
|
|
|
|
GroupName: aws.String(d.Get("placement_group").(string)),
|
|
|
|
}
|
|
|
|
|
|
|
|
if v := d.Get("tenancy").(string); v != "" {
|
|
|
|
opts.Placement.Tenancy = aws.String(v)
|
|
|
|
}
|
|
|
|
|
|
|
|
associatePublicIPAddress := d.Get("associate_public_ip_address").(bool)
|
|
|
|
|
|
|
|
var groups []*string
|
|
|
|
if v := d.Get("security_groups"); v != nil {
|
|
|
|
// Security group names.
|
|
|
|
// For a nondefault VPC, you must use security group IDs instead.
|
|
|
|
// See http://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_RunInstances.html
|
|
|
|
sgs := v.(*schema.Set).List()
|
|
|
|
if len(sgs) > 0 && hasSubnet {
|
|
|
|
log.Printf("[WARN] Deprecated. Attempting to use 'security_groups' within a VPC instance. Use 'vpc_security_group_ids' instead.")
|
|
|
|
}
|
|
|
|
for _, v := range sgs {
|
|
|
|
str := v.(string)
|
|
|
|
groups = append(groups, aws.String(str))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if hasSubnet && associatePublicIPAddress {
|
|
|
|
// If we have a non-default VPC / Subnet specified, we can flag
|
|
|
|
// AssociatePublicIpAddress to get a Public IP assigned. By default these are not provided.
|
|
|
|
// You cannot specify both SubnetId and the NetworkInterface.0.* parameters though, otherwise
|
|
|
|
// you get: Network interfaces and an instance-level subnet ID may not be specified on the same request
|
|
|
|
// You also need to attach Security Groups to the NetworkInterface instead of the instance,
|
|
|
|
// to avoid: Network interfaces and an instance-level security groups may not be specified on
|
|
|
|
// the same request
|
|
|
|
ni := &ec2.InstanceNetworkInterfaceSpecification{
|
|
|
|
AssociatePublicIPAddress: aws.Boolean(associatePublicIPAddress),
|
|
|
|
DeviceIndex: aws.Long(int64(0)),
|
|
|
|
SubnetID: aws.String(subnetID),
|
|
|
|
Groups: groups,
|
|
|
|
}
|
|
|
|
|
|
|
|
if v, ok := d.GetOk("private_ip"); ok {
|
|
|
|
ni.PrivateIPAddress = aws.String(v.(string))
|
|
|
|
}
|
|
|
|
|
2015-06-11 14:59:42 +02:00
|
|
|
if v := d.Get("vpc_security_group_ids").(*schema.Set); v.Len() > 0 {
|
|
|
|
for _, v := range v.List() {
|
provider/aws: spot_instance_request
This is an iteration on the great work done by @dalehamel in PRs #2095
and #2109.
The core team went back and forth on how to best model Spot Instance
Requests, requesting and then rejecting a separate-resource
implementation in #2109.
After more internal discussion, we landed once again on a separate
resource to model Spot Instance Requests. Out of respect for
@dalehamel's already-significant donated time, with this I'm attempting
to pick up the work to take this across the finish line.
Important architectural decisions represented here:
* Spot Instance Requests are always of type "persistent", to properly
match Terraform's declarative model.
* The spot_instance_request resource exports several attributes that
are expected to be constantly changing as the spot market changes:
spot_bid_status, spot_request_state, and instance_id. Creating
additional resource dependencies based on these attributes is not
recommended, as Terraform diffs will be continually generated to keep
up with the live changes.
* When a Spot Instance Request is deleted/canceled, an attempt is made
to terminate the last-known attached spot instance. Race conditions
dictate that this attempt cannot guarantee that the associated spot
instance is terminated immediately.
Implementation notes:
* This version of aws_spot_instance_request borrows a lot of common
code from aws_instance.
* In order to facilitate borrowing, we introduce `awsInstanceOpts`, an
internal representation of instance details that's meant to be shared
between resources. The goal here would be to refactor ASG Launch
Configurations to use the same struct.
* The new aws_spot_instance_request acc. test is passing.
* All aws_instance acc. tests remain passing.
2015-06-05 17:12:09 +02:00
|
|
|
ni.Groups = append(ni.Groups, aws.String(v.(string)))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
opts.NetworkInterfaces = []*ec2.InstanceNetworkInterfaceSpecification{ni}
|
|
|
|
} else {
|
|
|
|
if subnetID != "" {
|
|
|
|
opts.SubnetID = aws.String(subnetID)
|
|
|
|
}
|
|
|
|
|
|
|
|
if v, ok := d.GetOk("private_ip"); ok {
|
|
|
|
opts.PrivateIPAddress = aws.String(v.(string))
|
|
|
|
}
|
|
|
|
if opts.SubnetID != nil &&
|
|
|
|
*opts.SubnetID != "" {
|
|
|
|
opts.SecurityGroupIDs = groups
|
|
|
|
} else {
|
|
|
|
opts.SecurityGroups = groups
|
|
|
|
}
|
|
|
|
|
2015-06-11 14:59:42 +02:00
|
|
|
if v := d.Get("vpc_security_group_ids").(*schema.Set); v.Len() > 0 {
|
|
|
|
for _, v := range v.List() {
|
provider/aws: spot_instance_request
This is an iteration on the great work done by @dalehamel in PRs #2095
and #2109.
The core team went back and forth on how to best model Spot Instance
Requests, requesting and then rejecting a separate-resource
implementation in #2109.
After more internal discussion, we landed once again on a separate
resource to model Spot Instance Requests. Out of respect for
@dalehamel's already-significant donated time, with this I'm attempting
to pick up the work to take this across the finish line.
Important architectural decisions represented here:
* Spot Instance Requests are always of type "persistent", to properly
match Terraform's declarative model.
* The spot_instance_request resource exports several attributes that
are expected to be constantly changing as the spot market changes:
spot_bid_status, spot_request_state, and instance_id. Creating
additional resource dependencies based on these attributes is not
recommended, as Terraform diffs will be continually generated to keep
up with the live changes.
* When a Spot Instance Request is deleted/canceled, an attempt is made
to terminate the last-known attached spot instance. Race conditions
dictate that this attempt cannot guarantee that the associated spot
instance is terminated immediately.
Implementation notes:
* This version of aws_spot_instance_request borrows a lot of common
code from aws_instance.
* In order to facilitate borrowing, we introduce `awsInstanceOpts`, an
internal representation of instance details that's meant to be shared
between resources. The goal here would be to refactor ASG Launch
Configurations to use the same struct.
* The new aws_spot_instance_request acc. test is passing.
* All aws_instance acc. tests remain passing.
2015-06-05 17:12:09 +02:00
|
|
|
opts.SecurityGroupIDs = append(opts.SecurityGroupIDs, aws.String(v.(string)))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if v, ok := d.GetOk("key_name"); ok {
|
|
|
|
opts.KeyName = aws.String(v.(string))
|
|
|
|
}
|
|
|
|
|
|
|
|
blockDevices, err := readBlockDeviceMappingsFromConfig(d, conn)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if len(blockDevices) > 0 {
|
|
|
|
opts.BlockDeviceMappings = blockDevices
|
|
|
|
}
|
|
|
|
|
|
|
|
return opts, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func awsTerminateInstance(conn *ec2.EC2, id string) error {
|
|
|
|
log.Printf("[INFO] Terminating instance: %s", id)
|
|
|
|
req := &ec2.TerminateInstancesInput{
|
|
|
|
InstanceIDs: []*string{aws.String(id)},
|
|
|
|
}
|
|
|
|
if _, err := conn.TerminateInstances(req); err != nil {
|
|
|
|
return fmt.Errorf("Error terminating instance: %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
log.Printf("[DEBUG] Waiting for instance (%s) to become terminated", id)
|
|
|
|
|
|
|
|
stateConf := &resource.StateChangeConf{
|
|
|
|
Pending: []string{"pending", "running", "shutting-down", "stopped", "stopping"},
|
|
|
|
Target: "terminated",
|
|
|
|
Refresh: InstanceStateRefreshFunc(conn, 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 terminate: %s", id, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|