Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
stack72 committed Aug 22, 2016
1 parent b5e0f2e commit 7bac93a
Show file tree
Hide file tree
Showing 4 changed files with 749 additions and 102 deletions.
169 changes: 99 additions & 70 deletions builtin/providers/aws/resource_aws_spot_fleet_request.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ func resourceAwsSpotFleetRequest() *schema.Resource {
Delete: resourceAwsSpotFleetRequestDelete,
Update: resourceAwsSpotFleetRequestUpdate,

SchemaVersion: 1,
MigrateState: resourceAwsSpotFleetRequestMigrateState,

Schema: map[string]*schema.Schema{
"iam_fleet_role": &schema.Schema{
Type: schema.TypeString,
Expand All @@ -49,7 +52,7 @@ func resourceAwsSpotFleetRequest() *schema.Resource {
"associate_public_ip_address": &schema.Schema{
Type: schema.TypeBool,
Optional: true,
Default: true,
Default: false,
},
"ebs_block_device": &schema.Schema{
Type: schema.TypeSet,
Expand Down Expand Up @@ -192,7 +195,6 @@ func resourceAwsSpotFleetRequest() *schema.Resource {
Type: schema.TypeBool,
Optional: true,
},
// "network_interface_set"
"placement_group": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Expand All @@ -204,12 +206,6 @@ func resourceAwsSpotFleetRequest() *schema.Resource {
Optional: true,
ForceNew: true,
},
"subnet_id": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Computed: true,
ForceNew: true,
},
"user_data": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Expand All @@ -229,9 +225,16 @@ func resourceAwsSpotFleetRequest() *schema.Resource {
Optional: true,
ForceNew: true,
},
"subnet_id": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Computed: true,
ForceNew: true,
},
"availability_zone": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Computed: true,
ForceNew: true,
},
},
Expand Down Expand Up @@ -291,19 +294,16 @@ func resourceAwsSpotFleetRequest() *schema.Resource {
func buildSpotFleetLaunchSpecification(d map[string]interface{}, meta interface{}) (*ec2.SpotFleetLaunchSpecification, error) {
conn := meta.(*AWSClient).ec2conn

_, hasSubnet := d["subnet_id"]
_, hasAZ := d["availability_zone"]
if !hasAZ && !hasSubnet {
return nil, fmt.Errorf("LaunchSpecification must include a subnet_id or an availability_zone")
}

opts := &ec2.SpotFleetLaunchSpecification{
ImageId: aws.String(d["ami"].(string)),
InstanceType: aws.String(d["instance_type"].(string)),
SpotPrice: aws.String(d["spot_price"].(string)),
Placement: &ec2.SpotPlacement{
AvailabilityZone: aws.String(d["availability_zone"].(string)),
},
}

if v, ok := d["availability_zone"]; ok {
opts.Placement = &ec2.SpotPlacement{
AvailabilityZone: aws.String(v.(string)),
}
}

if v, ok := d["ebs_optimized"]; ok {
Expand All @@ -327,31 +327,45 @@ func buildSpotFleetLaunchSpecification(d map[string]interface{}, meta interface{
base64.StdEncoding.EncodeToString([]byte(v.(string))))
}

// check for non-default Subnet, and cast it to a String
subnet, hasSubnet := d["subnet_id"]
subnetID := subnet.(string)
if v, ok := d["key_name"]; ok {
opts.KeyName = aws.String(v.(string))
}

var associatePublicIPAddress bool
if v, ok := d["associate_public_ip_address"]; ok {
associatePublicIPAddress = v.(bool)
if v, ok := d["weighted_capacity"]; ok && v != "" {
wc, err := strconv.ParseFloat(v.(string), 64)
if err != nil {
return nil, err
}
opts.WeightedCapacity = aws.Float64(wc)
}

var groups []*string
if v, ok := d["security_groups"]; ok {
// 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 {
var groupIds []*string
if v, ok := d["vpc_security_group_ids"]; ok {
if s := v.(*schema.Set); s.Len() > 0 {
for _, v := range s.List() {
opts.SecurityGroups = append(opts.SecurityGroups, &ec2.GroupIdentifier{GroupId: aws.String(v.(string))})
groupIds = append(groupIds, aws.String(v.(string)))
}
}
}

subnetId, hasSubnetId := d["subnet_id"]
if hasSubnetId {
opts.SubnetId = aws.String(subnetId.(string))
}

associatePublicIpAddress, hasPublicIpAddress := d["associate_public_ip_address"]
if hasPublicIpAddress && associatePublicIpAddress.(bool) == true && hasSubnetId {

// 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
Expand All @@ -360,47 +374,14 @@ func buildSpotFleetLaunchSpecification(d map[string]interface{}, meta interface{
// to avoid: Network interfaces and an instance-level security groups may not be specified on
// the same request
ni := &ec2.InstanceNetworkInterfaceSpecification{
AssociatePublicIpAddress: aws.Bool(associatePublicIPAddress),
AssociatePublicIpAddress: aws.Bool(true),
DeviceIndex: aws.Int64(int64(0)),
SubnetId: aws.String(subnetID),
Groups: groups,
}

if v, ok := d["private_ip"]; ok {
ni.PrivateIpAddress = aws.String(v.(string))
}

if v := d["vpc_security_group_ids"].(*schema.Set); v.Len() > 0 {
for _, v := range v.List() {
ni.Groups = append(ni.Groups, aws.String(v.(string)))
}
SubnetId: aws.String(subnetId.(string)),
Groups: groupIds,
}

opts.NetworkInterfaces = []*ec2.InstanceNetworkInterfaceSpecification{ni}
} else {
if subnetID != "" {
opts.SubnetId = aws.String(subnetID)
}

if v, ok := d["vpc_security_group_ids"]; ok {
if s := v.(*schema.Set); s.Len() > 0 {
for _, v := range s.List() {
opts.SecurityGroups = append(opts.SecurityGroups, &ec2.GroupIdentifier{GroupId: aws.String(v.(string))})
}
}
}
}

if v, ok := d["key_name"]; ok {
opts.KeyName = aws.String(v.(string))
}

if v, ok := d["weighted_capacity"]; ok && v != "" {
wc, err := strconv.ParseFloat(v.(string), 64)
if err != nil {
return nil, err
}
opts.WeightedCapacity = aws.Float64(wc)
opts.SubnetId = aws.String("")
}

blockDevices, err := readSpotFleetBlockDeviceMappingsFromConfig(d, conn)
Expand Down Expand Up @@ -617,9 +598,52 @@ func resourceAwsSpotFleetRequestCreate(d *schema.ResourceData, meta interface{})

d.SetId(*resp.SpotFleetRequestId)

log.Printf("[INFO] Spot Fleet Request ID: %s", d.Id())
log.Println("[INFO] Waiting for Spot Fleet Request to be active")
stateConf := &resource.StateChangeConf{
Pending: []string{"submitted"},
Target: []string{"active"},
Refresh: resourceAwsSpotFleetRequestStateRefreshFunc(d, meta),
Timeout: 10 * time.Minute,
MinTimeout: 10 * time.Second,
Delay: 30 * time.Second,
}

_, err = stateConf.WaitForState()
if err != nil {
return err
}

return resourceAwsSpotFleetRequestRead(d, meta)
}

func resourceAwsSpotFleetRequestStateRefreshFunc(d *schema.ResourceData, meta interface{}) resource.StateRefreshFunc {
return func() (interface{}, string, error) {
conn := meta.(*AWSClient).ec2conn
req := &ec2.DescribeSpotFleetRequestsInput{
SpotFleetRequestIds: []*string{aws.String(d.Id())},
}
resp, err := conn.DescribeSpotFleetRequests(req)

if err != nil {
log.Printf("Error on retrieving Spot Fleet Request when waiting: %s", err)
return nil, "", nil
}

if resp == nil {
return nil, "", nil
}

if len(resp.SpotFleetRequestConfigs) == 0 {
return nil, "", nil
}

spotFleetRequest := resp.SpotFleetRequestConfigs[0]

return spotFleetRequest, *spotFleetRequest.SpotFleetRequestState, nil
}
}

func resourceAwsSpotFleetRequestRead(d *schema.ResourceData, meta interface{}) error {
// http://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_DescribeSpotFleetRequests.html
conn := meta.(*AWSClient).ec2conn
Expand Down Expand Up @@ -773,7 +797,7 @@ func launchSpecToMap(
}

if l.WeightedCapacity != nil {
m["weighted_capacity"] = fmt.Sprintf("%.3f", aws.Float64Value(l.WeightedCapacity))
m["weighted_capacity"] = floatToString(*l.WeightedCapacity)
}

// m["security_groups"] = securityGroupsToSet(l.SecutiryGroups)
Expand Down Expand Up @@ -941,9 +965,10 @@ func hashLaunchSpecification(v interface{}) int {
var buf bytes.Buffer
m := v.(map[string]interface{})
buf.WriteString(fmt.Sprintf("%s-", m["ami"].(string)))
if m["availability_zone"] != nil && m["availability_zone"] != "" {
if m["availability_zone"] != "" {
buf.WriteString(fmt.Sprintf("%s-", m["availability_zone"].(string)))
} else if m["subnet_id"] != nil && m["subnet_id"] != "" {
}
if m["subnet_id"] != "" {
buf.WriteString(fmt.Sprintf("%s-", m["subnet_id"].(string)))
}
buf.WriteString(fmt.Sprintf("%s-", m["instance_type"].(string)))
Expand All @@ -959,3 +984,7 @@ func hashEbsBlockDevice(v interface{}) int {
buf.WriteString(fmt.Sprintf("%s-", m["snapshot_id"].(string)))
return hashcode.String(buf.String())
}

func floatToString(input float64) string {
return strconv.FormatFloat(input, 'f', 0, 64)
}
33 changes: 33 additions & 0 deletions builtin/providers/aws/resource_aws_spot_fleet_request_migrate.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package aws

import (
"fmt"
"log"

"github.com/hashicorp/terraform/terraform"
)

func resourceAwsSpotFleetRequestMigrateState(
v int, is *terraform.InstanceState, meta interface{}) (*terraform.InstanceState, error) {
switch v {
case 0:
log.Println("[INFO] Found AWS Spot Fleet Request State v0; migrating to v1")
return migrateSpotFleetRequestV0toV1(is)
default:
return is, fmt.Errorf("Unexpected schema version: %d", v)
}
}

func migrateSpotFleetRequestV0toV1(is *terraform.InstanceState) (*terraform.InstanceState, error) {
if is.Empty() {
log.Println("[DEBUG] Empty Spot Fleet Request State; nothing to migrate.")
return is, nil
}

log.Printf("[DEBUG] Attributes before migration: %#v", is.Attributes)

is.Attributes["associate_public_ip_address"] = "false"

log.Printf("[DEBUG] Attributes after migration: %#v", is.Attributes)
return is, nil
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package aws

import (
"testing"

"github.com/hashicorp/terraform/terraform"
)

func TestAWSSpotFleetRequestMigrateState(t *testing.T) {
cases := map[string]struct {
StateVersion int
ID string
Attributes map[string]string
Expected string
Meta interface{}
}{
"v0_1": {
StateVersion: 0,
ID: "some_id",
Attributes: map[string]string{
"associate_public_ip_address": "true",
},
Expected: "false",
},
}

for tn, tc := range cases {
is := &terraform.InstanceState{
ID: tc.ID,
Attributes: tc.Attributes,
}
is, err := resourceAwsSpotFleetRequestMigrateState(
tc.StateVersion, is, tc.Meta)

if err != nil {
t.Fatalf("bad: %s, err: %#v", tn, err)
}

if is.Attributes["associate_public_ip_address"] != tc.Expected {
t.Fatalf("bad Spot Fleet Request Migrate: %s\n\n expected: %s", is.Attributes["associate_public_ip_address"], tc.Expected)
}
}
}
Loading

0 comments on commit 7bac93a

Please sign in to comment.