Add support for Opsworks Instances

New resource checklist
- [x] Acceptance testing
- [x] Documentation
- [x] Well-formed code
This commit is contained in:
Jeff Tang 2015-12-11 18:35:22 -05:00
parent a1f7789161
commit bcd5904eea
5 changed files with 1347 additions and 0 deletions

View File

@ -208,6 +208,7 @@ func Provider() terraform.ResourceProvider {
"aws_opsworks_mysql_layer": resourceAwsOpsworksMysqlLayer(),
"aws_opsworks_ganglia_layer": resourceAwsOpsworksGangliaLayer(),
"aws_opsworks_custom_layer": resourceAwsOpsworksCustomLayer(),
"aws_opsworks_instance": resourceAwsOpsworksInstance(),
"aws_placement_group": resourceAwsPlacementGroup(),
"aws_proxy_protocol_policy": resourceAwsProxyProtocolPolicy(),
"aws_rds_cluster": resourceAwsRDSCluster(),

View File

@ -0,0 +1,998 @@
package aws
import (
"bytes"
"fmt"
"log"
"time"
"github.com/hashicorp/terraform/helper/hashcode"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/helper/schema"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/service/opsworks"
)
func resourceAwsOpsworksInstance() *schema.Resource {
return &schema.Resource{
Create: resourceAwsOpsworksInstanceCreate,
Read: resourceAwsOpsworksInstanceRead,
Update: resourceAwsOpsworksInstanceUpdate,
Delete: resourceAwsOpsworksInstanceDelete,
Schema: map[string]*schema.Schema{
"id": &schema.Schema{
Type: schema.TypeString,
Computed: true,
},
"agent_version": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Default: "INHERIT",
},
"ami_id": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Computed: true,
ForceNew: true,
},
"architecture": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Default: "x86_64",
ValidateFunc: validateArchitecture,
},
"auto_scaling_type": &schema.Schema{
Type: schema.TypeString,
Optional: true,
ValidateFunc: validateAutoScalingType,
},
"availability_zone": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Computed: true,
ForceNew: true,
},
"created_at": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Computed: true,
},
"delete_ebs": &schema.Schema{
Type: schema.TypeBool,
Optional: true,
Default: true,
},
"delete_eip": &schema.Schema{
Type: schema.TypeBool,
Optional: true,
Default: true,
},
"ebs_optimized": &schema.Schema{
Type: schema.TypeBool,
Optional: true,
Default: false,
ForceNew: true,
},
"ec2_instance_id": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Computed: true,
},
"ecs_cluster_arn": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Computed: true,
},
"elastic_ip": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Computed: true,
},
"hostname": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Computed: true,
},
"infrastructure_class": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Computed: true,
},
"install_updates_on_boot": &schema.Schema{
Type: schema.TypeBool,
Optional: true,
Default: true,
},
"instance_profile_arn": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Computed: true,
},
"instance_type": &schema.Schema{
Type: schema.TypeString,
Optional: true,
},
"last_service_error_id": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Computed: true,
},
"layer_ids": &schema.Schema{
Type: schema.TypeList,
Required: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
"os": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Computed: true,
ForceNew: true,
},
"platform": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Computed: true,
},
"private_dns": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Computed: true,
},
"private_ip": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Computed: true,
},
"public_dns": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Computed: true,
},
"public_ip": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Computed: true,
},
"registered_by": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Computed: true,
},
"reported_agent_version": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Computed: true,
},
"reported_os_family": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Computed: true,
},
"reported_os_name": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Computed: true,
},
"reported_os_version": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Computed: true,
},
"root_device_type": &schema.Schema{
Type: schema.TypeString,
Optional: true,
ForceNew: true,
Computed: true,
ValidateFunc: validateRootDeviceType,
},
"root_device_volume_id": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Computed: true,
},
"security_group_ids": &schema.Schema{
Type: schema.TypeList,
Optional: true,
Computed: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
"ssh_host_dsa_key_fingerprint": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Computed: true,
},
"ssh_host_rsa_key_fingerprint": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Computed: true,
},
"ssh_key_name": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Computed: true,
},
"stack_id": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"state": &schema.Schema{
Type: schema.TypeString,
Optional: true,
ValidateFunc: validateState,
},
"status": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Computed: true,
},
"subnet_id": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Computed: true,
ForceNew: true,
},
"virtualization_type": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Computed: true,
ForceNew: true,
ValidateFunc: validateVirtualizationType,
},
"ebs_block_device": &schema.Schema{
Type: schema.TypeSet,
Optional: true,
Computed: true,
ForceNew: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"delete_on_termination": &schema.Schema{
Type: schema.TypeBool,
Optional: true,
Default: true,
ForceNew: true,
},
"device_name": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"iops": &schema.Schema{
Type: schema.TypeInt,
Optional: true,
Computed: true,
ForceNew: true,
},
"snapshot_id": &schema.Schema{
Type: schema.TypeString,
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,
},
},
},
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())
},
},
"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,
},
"virtual_name": &schema.Schema{
Type: schema.TypeString,
Required: true,
},
},
},
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())
},
},
"root_block_device": &schema.Schema{
// TODO: This is a set because we don't support singleton
// sub-resources today. We'll enforce that the set only ever has
// length zero or one below. When TF gains support for
// sub-resources this can be converted.
Type: schema.TypeSet,
Optional: true,
Computed: true,
ForceNew: 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,
},
"iops": &schema.Schema{
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,
},
},
},
Set: func(v interface{}) int {
// there can be only one root device; no need to hash anything
return 0
},
},
},
}
}
func validateArchitecture(v interface{}, k string) (ws []string, errors []error) {
value := v.(string)
if value != "x86_64" && value != "i386" {
errors = append(errors, fmt.Errorf(
"%q must be one of \"x86_64\" or \"i386\"", k))
}
return
}
func validateAutoScalingType(v interface{}, k string) (ws []string, errors []error) {
value := v.(string)
if value != "load" && value != "timer" {
errors = append(errors, fmt.Errorf(
"%q must be one of \"load\" or \"timer\"", k))
}
return
}
func validateRootDeviceType(v interface{}, k string) (ws []string, errors []error) {
value := v.(string)
if value != "ebs" && value != "instance-store" {
errors = append(errors, fmt.Errorf(
"%q must be one of \"ebs\" or \"instance-store\"", k))
}
return
}
func validateState(v interface{}, k string) (ws []string, errors []error) {
value := v.(string)
if value != "running" && value != "stopped" {
errors = append(errors, fmt.Errorf(
"%q must be one of \"running\" or \"stopped\"", k))
}
return
}
func validateVirtualizationType(v interface{}, k string) (ws []string, errors []error) {
value := v.(string)
if value != "paravirtual" && value != "hvm" {
errors = append(errors, fmt.Errorf(
"%q must be one of \"paravirtual\" or \"hvm\"", k))
}
return
}
func resourceAwsOpsworksInstanceValidate(d *schema.ResourceData) error {
if d.HasChange("ami_id") {
if v, ok := d.GetOk("os"); ok {
if v.(string) != "Custom" {
return fmt.Errorf("OS must be \"Custom\" when using using a custom ami_id")
}
}
if _, ok := d.GetOk("root_block_device"); ok {
return fmt.Errorf("Cannot specify root_block_device when using a custom ami_id.")
}
if _, ok := d.GetOk("ebs_block_device"); ok {
return fmt.Errorf("Cannot specify ebs_block_device when using a custom ami_id.")
}
if _, ok := d.GetOk("ephemeral_block_device"); ok {
return fmt.Errorf("Cannot specify ephemeral_block_device when using a custom ami_id.")
}
}
return nil
}
func resourceAwsOpsworksInstanceRead(d *schema.ResourceData, meta interface{}) error {
client := meta.(*AWSClient).opsworksconn
req := &opsworks.DescribeInstancesInput{
InstanceIds: []*string{
aws.String(d.Id()),
},
}
log.Printf("[DEBUG] Reading OpsWorks instance: %s", d.Id())
resp, err := client.DescribeInstances(req)
if err != nil {
if awserr, ok := err.(awserr.Error); ok {
if awserr.Code() == "ResourceNotFoundException" {
d.SetId("")
return nil
}
}
return err
}
instance := resp.Instances[0]
instanceId := *instance.InstanceId
d.SetId(instanceId)
d.Set("agent_version", instance.AgentVersion)
d.Set("ami_id", instance.AmiId)
d.Set("architecture", instance.Architecture)
d.Set("auto_scaling_type", instance.AutoScalingType)
d.Set("availability_zone", instance.AvailabilityZone)
d.Set("created_at", instance.CreatedAt)
d.Set("ebs_optimized", instance.EbsOptimized)
d.Set("ec2_instance_id", instance.Ec2InstanceId)
d.Set("ecs_cluster_arn", instance.EcsClusterArn)
d.Set("elastic_ip", instance.ElasticIp)
d.Set("hostname", instance.Hostname)
d.Set("infrastructure_class", instance.InfrastructureClass)
d.Set("install_updates_on_boot", instance.InstallUpdatesOnBoot)
d.Set("id", instanceId)
d.Set("instance_profile_arn", instance.InstanceProfileArn)
d.Set("instance_type", instance.InstanceType)
d.Set("last_service_error_id", instance.LastServiceErrorId)
d.Set("layer_ids", instance.LayerIds)
d.Set("os", instance.Os)
d.Set("platform", instance.Platform)
d.Set("private_dns", instance.PrivateDns)
d.Set("private_ip", instance.PrivateIp)
d.Set("public_dns", instance.PublicDns)
d.Set("public_ip", instance.PublicIp)
d.Set("registered_by", instance.RegisteredBy)
d.Set("reported_agent_version", instance.ReportedAgentVersion)
d.Set("reported_os_family", instance.ReportedOs.Family)
d.Set("reported_os_name", instance.ReportedOs.Name)
d.Set("reported_os_version", instance.ReportedOs.Version)
d.Set("root_device_type", instance.RootDeviceType)
d.Set("root_device_volume_id", instance.RootDeviceVolumeId)
d.Set("ssh_host_dsa_key_fingerprint", instance.SshHostDsaKeyFingerprint)
d.Set("ssh_host_rsa_key_fingerprint", instance.SshHostRsaKeyFingerprint)
d.Set("ssh_key_name", instance.SshKeyName)
d.Set("stack_id", instance.StackId)
d.Set("status", instance.Status)
d.Set("subnet_id", instance.SubnetId)
d.Set("virtualization_type", instance.VirtualizationType)
// Read BlockDeviceMapping
ibds, err := readOpsworksBlockDevices(d, instance, meta)
if err != nil {
return err
}
if err := d.Set("ebs_block_device", ibds["ebs"]); err != nil {
return err
}
if err := d.Set("ephemeral_block_device", ibds["ephemeral"]); err != nil {
return err
}
if ibds["root"] != nil {
if err := d.Set("root_block_device", []interface{}{ibds["root"]}); err != nil {
return err
}
} else {
d.Set("root_block_device", []interface{}{})
}
// Read Security Groups
sgs := make([]string, 0, len(instance.SecurityGroupIds))
for _, sg := range instance.SecurityGroupIds {
sgs = append(sgs, *sg)
}
if err := d.Set("security_group_ids", sgs); err != nil {
return err
}
return nil
}
func resourceAwsOpsworksInstanceCreate(d *schema.ResourceData, meta interface{}) error {
client := meta.(*AWSClient).opsworksconn
err := resourceAwsOpsworksInstanceValidate(d)
if err != nil {
return err
}
req := &opsworks.CreateInstanceInput{
AgentVersion: aws.String(d.Get("agent_version").(string)),
Architecture: aws.String(d.Get("architecture").(string)),
EbsOptimized: aws.Bool(d.Get("ebs_optimized").(bool)),
InstallUpdatesOnBoot: aws.Bool(d.Get("install_updates_on_boot").(bool)),
InstanceType: aws.String(d.Get("instance_type").(string)),
LayerIds: expandStringList(d.Get("layer_ids").([]interface{})),
StackId: aws.String(d.Get("stack_id").(string)),
}
if v, ok := d.GetOk("ami_id"); ok {
req.AmiId = aws.String(v.(string))
req.Os = aws.String("Custom")
}
if v, ok := d.GetOk("auto_scaling_type"); ok {
req.AutoScalingType = aws.String(v.(string))
}
if v, ok := d.GetOk("availability_zone"); ok {
req.AvailabilityZone = aws.String(v.(string))
}
if v, ok := d.GetOk("hostname"); ok {
req.Hostname = aws.String(v.(string))
}
if v, ok := d.GetOk("os"); ok {
req.Os = aws.String(v.(string))
}
if v, ok := d.GetOk("root_device_type"); ok {
req.RootDeviceType = aws.String(v.(string))
}
if v, ok := d.GetOk("ssh_key_name"); ok {
req.SshKeyName = aws.String(v.(string))
}
if v, ok := d.GetOk("subnet_id"); ok {
req.SubnetId = aws.String(v.(string))
}
if v, ok := d.GetOk("virtualization_type"); ok {
req.VirtualizationType = aws.String(v.(string))
}
var blockDevices []*opsworks.BlockDeviceMapping
if v, ok := d.GetOk("ebs_block_device"); ok {
vL := v.(*schema.Set).List()
for _, v := range vL {
bd := v.(map[string]interface{})
ebs := &opsworks.EbsBlockDevice{
DeleteOnTermination: aws.Bool(bd["delete_on_termination"].(bool)),
}
if v, ok := bd["snapshot_id"].(string); ok && v != "" {
ebs.SnapshotId = aws.String(v)
}
if v, ok := bd["volume_size"].(int); ok && v != 0 {
ebs.VolumeSize = aws.Int64(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.Int64(int64(v))
}
blockDevices = append(blockDevices, &opsworks.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, &opsworks.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 fmt.Errorf("Cannot specify more than one root_block_device.")
}
for _, v := range vL {
bd := v.(map[string]interface{})
ebs := &opsworks.EbsBlockDevice{
DeleteOnTermination: aws.Bool(bd["delete_on_termination"].(bool)),
}
if v, ok := bd["volume_size"].(int); ok && v != 0 {
ebs.VolumeSize = aws.Int64(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.Int64(int64(v))
}
blockDevices = append(blockDevices, &opsworks.BlockDeviceMapping{
DeviceName: aws.String("ROOT_DEVICE"),
Ebs: ebs,
})
}
}
if len(blockDevices) > 0 {
req.BlockDeviceMappings = blockDevices
}
log.Printf("[DEBUG] Creating OpsWorks instance")
var resp *opsworks.CreateInstanceOutput
resp, err = client.CreateInstance(req)
if err != nil {
return err
}
instanceId := *resp.InstanceId
d.SetId(instanceId)
d.Set("id", instanceId)
if v, ok := d.GetOk("state"); ok && v.(string) == "running" {
err := startOpsworksInstance(d, meta, false)
if err != nil {
return err
}
}
return resourceAwsOpsworksInstanceRead(d, meta)
}
func resourceAwsOpsworksInstanceUpdate(d *schema.ResourceData, meta interface{}) error {
client := meta.(*AWSClient).opsworksconn
err := resourceAwsOpsworksInstanceValidate(d)
if err != nil {
return err
}
req := &opsworks.UpdateInstanceInput{
AgentVersion: aws.String(d.Get("agent_version").(string)),
Architecture: aws.String(d.Get("architecture").(string)),
InstanceId: aws.String(d.Get("id").(string)),
InstallUpdatesOnBoot: aws.Bool(d.Get("install_updates_on_boot").(bool)),
}
if v, ok := d.GetOk("ami_id"); ok {
req.AmiId = aws.String(v.(string))
req.Os = aws.String("Custom")
}
if v, ok := d.GetOk("auto_scaling_type"); ok {
req.AutoScalingType = aws.String(v.(string))
}
if v, ok := d.GetOk("hostname"); ok {
req.Hostname = aws.String(v.(string))
}
if v, ok := d.GetOk("instance_type"); ok {
req.InstanceType = aws.String(v.(string))
}
if v, ok := d.GetOk("layer_ids"); ok {
req.LayerIds = expandStringList(v.([]interface{}))
}
if v, ok := d.GetOk("os"); ok {
req.Os = aws.String(v.(string))
}
if v, ok := d.GetOk("ssh_key_name"); ok {
req.SshKeyName = aws.String(v.(string))
}
log.Printf("[DEBUG] Updating OpsWorks instance: %s", d.Id())
_, err = client.UpdateInstance(req)
if err != nil {
return err
}
var status string
if v, ok := d.GetOk("status"); ok {
status = v.(string)
} else {
status = "stopped"
}
if v, ok := d.GetOk("state"); ok {
state := v.(string)
if state == "running" {
if status == "stopped" || status == "stopping" || status == "shutting_down" {
err := startOpsworksInstance(d, meta, false)
if err != nil {
return err
}
}
} else {
if status != "stopped" && status != "stopping" && status != "shutting_down" {
err := stopOpsworksInstance(d, meta, false)
if err != nil {
return err
}
}
}
}
return resourceAwsOpsworksInstanceRead(d, meta)
}
func resourceAwsOpsworksInstanceDelete(d *schema.ResourceData, meta interface{}) error {
client := meta.(*AWSClient).opsworksconn
if v, ok := d.GetOk("status"); ok && v.(string) != "stopped" {
err := stopOpsworksInstance(d, meta, true)
if err != nil {
return err
}
}
req := &opsworks.DeleteInstanceInput{
InstanceId: aws.String(d.Id()),
DeleteElasticIp: aws.Bool(d.Get("delete_eip").(bool)),
DeleteVolumes: aws.Bool(d.Get("delete_ebs").(bool)),
}
log.Printf("[DEBUG] Deleting OpsWorks instance: %s", d.Id())
_, err := client.DeleteInstance(req)
if err != nil {
return err
}
d.SetId("")
return nil
}
func startOpsworksInstance(d *schema.ResourceData, meta interface{}, wait bool) error {
client := meta.(*AWSClient).opsworksconn
instanceId := d.Get("id").(string)
req := &opsworks.StartInstanceInput{
InstanceId: aws.String(instanceId),
}
log.Printf("[DEBUG] Starting OpsWorks instance: %s", instanceId)
_, err := client.StartInstance(req)
if err != nil {
return err
}
if wait {
log.Printf("[DEBUG] Waiting for instance (%s) to become running", instanceId)
stateConf := &resource.StateChangeConf{
Pending: []string{"requested", "pending", "booting", "running_setup"},
Target: []string{"online"},
Refresh: OpsworksInstanceStateRefreshFunc(client, instanceId),
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 stopped: %s",
instanceId, err)
}
}
return nil
}
func stopOpsworksInstance(d *schema.ResourceData, meta interface{}, wait bool) error {
client := meta.(*AWSClient).opsworksconn
instanceId := d.Get("id").(string)
req := &opsworks.StopInstanceInput{
InstanceId: aws.String(instanceId),
}
log.Printf("[DEBUG] Stopping OpsWorks instance: %s", instanceId)
_, err := client.StopInstance(req)
if err != nil {
return err
}
if wait {
log.Printf("[DEBUG] Waiting for instance (%s) to become stopped", instanceId)
stateConf := &resource.StateChangeConf{
Pending: []string{"stopping", "terminating", "shutting_down", "terminated"},
Target: []string{"stopped"},
Refresh: OpsworksInstanceStateRefreshFunc(client, instanceId),
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 stopped: %s",
instanceId, err)
}
}
return nil
}
func readOpsworksBlockDevices(d *schema.ResourceData, instance *opsworks.Instance, meta interface{}) (
map[string]interface{}, error) {
blockDevices := make(map[string]interface{})
blockDevices["ebs"] = make([]map[string]interface{}, 0)
blockDevices["ephemeral"] = make([]map[string]interface{}, 0)
blockDevices["root"] = nil
if len(instance.BlockDeviceMappings) == 0 {
return nil, nil
}
for _, bdm := range instance.BlockDeviceMappings {
bd := make(map[string]interface{})
if bdm.Ebs != nil && bdm.Ebs.DeleteOnTermination != nil {
bd["delete_on_termination"] = *bdm.Ebs.DeleteOnTermination
}
if bdm.Ebs != nil && bdm.Ebs.VolumeSize != nil {
bd["volume_size"] = *bdm.Ebs.VolumeSize
}
if bdm.Ebs != nil && bdm.Ebs.VolumeType != nil {
bd["volume_type"] = *bdm.Ebs.VolumeType
}
if bdm.Ebs != nil && bdm.Ebs.Iops != nil {
bd["iops"] = *bdm.Ebs.Iops
}
if bdm.DeviceName != nil && *bdm.DeviceName == "ROOT_DEVICE" {
blockDevices["root"] = bd
} else {
if bdm.DeviceName != nil {
bd["device_name"] = *bdm.DeviceName
}
if bdm.VirtualName != nil {
bd["virtual_name"] = *bdm.VirtualName
blockDevices["ephemeral"] = append(blockDevices["ephemeral"].([]map[string]interface{}), bd)
} else {
if bdm.Ebs != nil && bdm.Ebs.SnapshotId != nil {
bd["snapshot_id"] = *bdm.Ebs.SnapshotId
}
blockDevices["ebs"] = append(blockDevices["ebs"].([]map[string]interface{}), bd)
}
}
}
return blockDevices, nil
}
func OpsworksInstanceStateRefreshFunc(conn *opsworks.OpsWorks, instanceID string) resource.StateRefreshFunc {
return func() (interface{}, string, error) {
resp, err := conn.DescribeInstances(&opsworks.DescribeInstancesInput{
InstanceIds: []*string{aws.String(instanceID)},
})
if err != nil {
if awserr, ok := err.(awserr.Error); ok && awserr.Code() == "ResourceNotFoundException" {
// Set this to nil as if we didn't find anything.
resp = nil
} else {
log.Printf("Error on OpsworksInstanceStateRefresh: %s", err)
return nil, "", err
}
}
if resp == nil || len(resp.Instances) == 0 {
// Sometimes AWS just has consistency issues and doesn't see
// our instance yet. Return an empty state.
return nil, "", nil
}
i := resp.Instances[0]
return i, *i.Status, nil
}
}

View File

@ -0,0 +1,211 @@
package aws
import (
"fmt"
"testing"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/service/opsworks"
"github.com/hashicorp/terraform/helper/acctest"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform"
)
// These tests assume the existence of predefined Opsworks IAM roles named `aws-opsworks-ec2-role`
// and `aws-opsworks-service-role`.
func TestAccAWSOpsworksInstance(t *testing.T) {
stackName := fmt.Sprintf("tf-%d", acctest.RandInt())
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckAwsOpsworksInstanceDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccAwsOpsworksInstanceConfigCreate(stackName),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr(
"aws_opsworks_instance.tf-acc", "hostname", "tf-acc1",
),
resource.TestCheckResourceAttr(
"aws_opsworks_instance.tf-acc", "instance_type", "t2.micro",
),
resource.TestCheckResourceAttr(
"aws_opsworks_instance.tf-acc", "state", "stopped",
),
resource.TestCheckResourceAttr(
"aws_opsworks_instance.tf-acc", "layer_ids.#", "1",
),
resource.TestCheckResourceAttr(
"aws_opsworks_instance.tf-acc", "install_updates_on_boot", "true",
),
resource.TestCheckResourceAttr(
"aws_opsworks_instance.tf-acc", "architecture", "x86_64",
),
resource.TestCheckResourceAttr(
"aws_opsworks_instance.tf-acc", "os", "Amazon Linux 2014.09", // inherited from opsworks_stack_test
),
resource.TestCheckResourceAttr(
"aws_opsworks_instance.tf-acc", "root_device_type", "ebs", // inherited from opsworks_stack_test
),
resource.TestCheckResourceAttr(
"aws_opsworks_instance.tf-acc", "availability_zone", "us-west-2a", // inherited from opsworks_stack_test
),
),
},
resource.TestStep{
Config: testAccAwsOpsworksInstanceConfigUpdate(stackName),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr(
"aws_opsworks_instance.tf-acc", "hostname", "tf-acc1",
),
resource.TestCheckResourceAttr(
"aws_opsworks_instance.tf-acc", "instance_type", "t2.small",
),
resource.TestCheckResourceAttr(
"aws_opsworks_instance.tf-acc", "layer_ids.#", "2",
),
resource.TestCheckResourceAttr(
"aws_opsworks_instance.tf-acc", "os", "Amazon Linux 2015.09",
),
),
},
},
})
}
func testAccCheckAwsOpsworksInstanceDestroy(s *terraform.State) error {
opsworksconn := testAccProvider.Meta().(*AWSClient).opsworksconn
for _, rs := range s.RootModule().Resources {
if rs.Type != "aws_opsworks_instance" {
continue
}
req := &opsworks.DescribeInstancesInput{
InstanceIds: []*string{
aws.String(rs.Primary.ID),
},
}
_, err := opsworksconn.DescribeInstances(req)
if err != nil {
if awserr, ok := err.(awserr.Error); ok {
if awserr.Code() == "ResourceNotFoundException" {
// not found, good to go
return nil
}
}
return err
}
}
return fmt.Errorf("Fall through error on OpsWorks instance test")
}
func testAccAwsOpsworksInstanceConfigCreate(name string) string {
return fmt.Sprintf(`
resource "aws_security_group" "tf-ops-acc-web" {
name = "%s-web"
ingress {
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
}
resource "aws_security_group" "tf-ops-acc-php" {
name = "%s-php"
ingress {
from_port = 8080
to_port = 8080
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
}
resource "aws_opsworks_static_web_layer" "tf-acc" {
stack_id = "${aws_opsworks_stack.tf-acc.id}"
custom_security_group_ids = [
"${aws_security_group.tf-ops-acc-web.id}",
]
}
resource "aws_opsworks_php_app_layer" "tf-acc" {
stack_id = "${aws_opsworks_stack.tf-acc.id}"
custom_security_group_ids = [
"${aws_security_group.tf-ops-acc-php.id}",
]
}
resource "aws_opsworks_instance" "tf-acc" {
stack_id = "${aws_opsworks_stack.tf-acc.id}"
layer_ids = [
"${aws_opsworks_static_web_layer.tf-acc.id}",
]
instance_type = "t2.micro"
state = "stopped"
hostname = "tf-acc1"
}
%s
`, name, name, testAccAwsOpsworksStackConfigVpcCreate(name))
}
func testAccAwsOpsworksInstanceConfigUpdate(name string) string {
return fmt.Sprintf(`
resource "aws_security_group" "tf-ops-acc-web" {
name = "%s-web"
ingress {
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
}
resource "aws_security_group" "tf-ops-acc-php" {
name = "%s-php"
ingress {
from_port = 8080
to_port = 8080
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
}
resource "aws_opsworks_static_web_layer" "tf-acc" {
stack_id = "${aws_opsworks_stack.tf-acc.id}"
custom_security_group_ids = [
"${aws_security_group.tf-ops-acc-web.id}",
]
}
resource "aws_opsworks_php_app_layer" "tf-acc" {
stack_id = "${aws_opsworks_stack.tf-acc.id}"
custom_security_group_ids = [
"${aws_security_group.tf-ops-acc-php.id}",
]
}
resource "aws_opsworks_instance" "tf-acc" {
stack_id = "${aws_opsworks_stack.tf-acc.id}"
layer_ids = [
"${aws_opsworks_static_web_layer.tf-acc.id}",
"${aws_opsworks_php_app_layer.tf-acc.id}",
]
instance_type = "t2.small"
state = "stopped"
hostname = "tf-acc1"
os = "Amazon Linux 2015.09"
}
%s
`, name, name, testAccAwsOpsworksStackConfigVpcCreate(name))
}

View File

@ -0,0 +1,133 @@
---
layout: "aws"
page_title: "AWS: aws_opsworks_instance"
sidebar_current: "docs-aws-resource-opsworks-instance"
description: |-
Provides an OpsWorks instance resource.
---
# aws\_opsworks\_instance
Provides an OpsWorks instance resource.
## Example Usage
```
resource "aws_opsworks_instance" "my-instance" {
stack_id = "${aws_opsworks_stack.my-stack.id}"
layer_ids = [
"${aws_opsworks_custom_layer.my-layer.id}",
]
instance_type = "t2.micro"
os = "Amazon Linux 2015.09"
state = "stopped"
```
## Argument Reference
The following arguments are supported:
* `instance_type` - (Required) The type of instance to start
* `stack_id` - (Required) The id of the stack the instance will belong to.
* `layer_ids` - (Required) The ids of the layers the instance will belong to.
* `state` - (Optional) The desired state of the instance. Can be either `"running"` or `"stopped"`.
* `install_updates_on_boot` - (Optional) Controls where to install OS and package updates when the instance boots. Defaults to `true`.
* `auto_scaling_type` - (Optional) Creates load-based or time-based instances. If set, can be either: `"load"` or `"timer"`.
* `availability_zone` - (Optional) Name of the availability zone where instances will be created
by default.
* `ebs_optimized` - (Optional) If true, the launched EC2 instance will be EBS-optimized.
* `hostname` - (Optional) The instance's host name.
* `architecture` - (Optional) Machine architecture for created instances. Can be either `"x86_64"` (the default) or `"i386"`
* `ami_id` - (Optional) The AMI to use for the instance. If an AMI is specified, `os` must be `"Custom"`.
* `os` - (Optional) Name of operating system that will be installed.
* `root_device_type` - (Optional) Name of the type of root device instances will have by default. Can be either `"ebs"` or `"instance-store"`
* `ssh_key_name` - (Optional) Name of the SSH keypair that instances will have by default.
* `agent_version` - (Optional) The AWS OpsWorks agent to install. Defaults to `"INHERIT"`.
* `subnet_id` - (Optional) Subnet ID to attach to
* `virtualization_type` - (Optional) Keyword to choose what virtualization mode created instances
will use. Can be either `"paravirtual"` or `"hvm"`.
* `root_block_device` - (Optional) Customize details about the root block
device of the instance. See [Block Devices](#block-devices) below for details.
* `ebs_block_device` - (Optional) Additional EBS block devices to attach to the
instance. See [Block Devices](#block-devices) below for details.
* `ephemeral_block_device` - (Optional) Customize Ephemeral (also known as
"Instance Store") volumes on the instance. See [Block Devices](#block-devices) below for details.
<a id="block-devices"></a>
## Block devices
Each of the `*_block_device` attributes controls a portion of the AWS
Instance's "Block Device Mapping". It's a good idea to familiarize yourself with [AWS's Block Device
Mapping docs](http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/block-device-mapping-concepts.html)
to understand the implications of using these attributes.
The `root_block_device` mapping supports the following:
* `volume_type` - (Optional) The type of volume. Can be `"standard"`, `"gp2"`,
or `"io1"`. (Default: `"standard"`).
* `volume_size` - (Optional) The size of the volume in gigabytes.
* `iops` - (Optional) The amount of provisioned
[IOPS](http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ebs-io-characteristics.html).
This must be set with a `volume_type` of `"io1"`.
* `delete_on_termination` - (Optional) Whether the volume should be destroyed
on instance termination (Default: `true`).
Modifying any of the `root_block_device` settings requires resource
replacement.
Each `ebs_block_device` supports the following:
* `device_name` - The name of the device to mount.
* `snapshot_id` - (Optional) The Snapshot ID to mount.
* `volume_type` - (Optional) The type of volume. Can be `"standard"`, `"gp2"`,
or `"io1"`. (Default: `"standard"`).
* `volume_size` - (Optional) The size of the volume in gigabytes.
* `iops` - (Optional) The amount of provisioned
[IOPS](http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ebs-io-characteristics.html).
This must be set with a `volume_type` of `"io1"`.
* `delete_on_termination` - (Optional) Whether the volume should be destroyed
on instance termination (Default: `true`).
* `encrypted` - (Optional) Enables [EBS
encryption](http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/EBSEncryption.html)
on the volume (Default: `false`). Cannot be used with `snapshot_id`.
Modifying any `ebs_block_device` currently requires resource replacement.
Each `ephemeral_block_device` supports the following:
* `device_name` - The name of the block device to mount on the instance.
* `virtual_name` - The [Instance Store Device
Name](http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/InstanceStorage.html#InstanceStoreDeviceNames)
(e.g. `"ephemeral0"`)
Each AWS Instance type has a different set of Instance Store block devices
available for attachment. AWS [publishes a
list](http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/InstanceStorage.html#StorageOnInstanceTypes)
of which ephemeral devices are available on each type. The devices are always
identified by the `virtual_name` in the format `"ephemeral{0..N}"`.
~> **NOTE:** Currently, changes to `*_block_device` configuration of _existing_
resources cannot be automatically detected by Terraform. After making updates
to block device configuration, resource recreation can be manually triggered by
using the [`taint` command](/docs/commands/taint.html).
## Attributes Reference
The following attributes are exported:
* `id` - The id of the OpsWorks instance.
* `agent_version` - The AWS OpsWorks agent version.
* `availability_zone` - The availability zone of the instance.
* `ssh_key_name` - The key name of the instance
* `public_dns` - The public DNS name assigned to the instance. For EC2-VPC, this
is only available if you've enabled DNS hostnames for your VPC
* `public_ip` - The public IP address assigned to the instance, if applicable.
* `private_dns` - The private DNS name assigned to the instance. Can only be
used inside the Amazon EC2, and only available if you've enabled DNS hostnames
for your VPC
* `private_ip` - The private IP address assigned to the instance
* `subnet_id` - The VPC subnet ID.
* `security_group_ids` - The associated security groups.

View File

@ -458,6 +458,10 @@
<a href="/docs/providers/aws/r/opsworks_haproxy_layer.html">aws_opsworks_haproxy_layer</a>
</li>
<li<%= sidebar_current("docs-aws-resource-opsworks-instance") %>>
<a href="/docs/providers/aws/r/opsworks_instance.html">aws_opsworks_instance</a>
</li>
<li<%= sidebar_current("docs-aws-resource-opsworks-java-app-layer") %>>
<a href="/docs/providers/aws/r/opsworks_java_app_layer.html">aws_opsworks_java_app_layer</a>
</li>