1014 lines
25 KiB
Go
1014 lines
25 KiB
Go
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
|
|
}
|
|
|
|
// If nothing was found, then return no state
|
|
if len(resp.Instances) == 0 {
|
|
d.SetId("")
|
|
return nil
|
|
}
|
|
instance := resp.Instances[0]
|
|
|
|
if instance.InstanceId == nil {
|
|
d.SetId("")
|
|
return nil
|
|
}
|
|
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
|
|
}
|
|
|
|
if resp.InstanceId == nil {
|
|
return fmt.Errorf("Error launching instance: no instance returned in response")
|
|
}
|
|
|
|
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
|
|
}
|
|
}
|