github.com/gabrielperezs/terraform@v0.7.0-rc2.0.20160715084931-f7da2612946f/builtin/providers/aws/resource_aws_spot_fleet_request.go (about)

     1  package aws
     2  
     3  import (
     4  	"bytes"
     5  	"crypto/sha1"
     6  	"encoding/base64"
     7  	"encoding/hex"
     8  	"fmt"
     9  	"log"
    10  	"strconv"
    11  	"time"
    12  
    13  	"github.com/aws/aws-sdk-go/aws"
    14  	"github.com/aws/aws-sdk-go/aws/awserr"
    15  	"github.com/aws/aws-sdk-go/service/ec2"
    16  	"github.com/hashicorp/terraform/helper/hashcode"
    17  	"github.com/hashicorp/terraform/helper/resource"
    18  	"github.com/hashicorp/terraform/helper/schema"
    19  )
    20  
    21  func resourceAwsSpotFleetRequest() *schema.Resource {
    22  	return &schema.Resource{
    23  		Create: resourceAwsSpotFleetRequestCreate,
    24  		Read:   resourceAwsSpotFleetRequestRead,
    25  		Delete: resourceAwsSpotFleetRequestDelete,
    26  		Update: resourceAwsSpotFleetRequestUpdate,
    27  
    28  		Schema: map[string]*schema.Schema{
    29  			"iam_fleet_role": &schema.Schema{
    30  				Type:     schema.TypeString,
    31  				Required: true,
    32  				ForceNew: true,
    33  			},
    34  			// http://docs.aws.amazon.com/sdk-for-go/api/service/ec2.html#type-SpotFleetLaunchSpecification
    35  			// http://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_SpotFleetLaunchSpecification.html
    36  			"launch_specification": &schema.Schema{
    37  				Type:     schema.TypeSet,
    38  				Required: true,
    39  				ForceNew: true,
    40  				Elem: &schema.Resource{
    41  					Schema: map[string]*schema.Schema{
    42  						"vpc_security_group_ids": &schema.Schema{
    43  							Type:     schema.TypeSet,
    44  							Optional: true,
    45  							Computed: true,
    46  							Elem:     &schema.Schema{Type: schema.TypeString},
    47  							Set:      schema.HashString,
    48  						},
    49  						"associate_public_ip_address": &schema.Schema{
    50  							Type:     schema.TypeBool,
    51  							Optional: true,
    52  							Default:  true,
    53  						},
    54  						"ebs_block_device": &schema.Schema{
    55  							Type:     schema.TypeSet,
    56  							Optional: true,
    57  							Computed: true,
    58  							Elem: &schema.Resource{
    59  								Schema: map[string]*schema.Schema{
    60  									"delete_on_termination": &schema.Schema{
    61  										Type:     schema.TypeBool,
    62  										Optional: true,
    63  										Default:  true,
    64  										ForceNew: true,
    65  									},
    66  									"device_name": &schema.Schema{
    67  										Type:     schema.TypeString,
    68  										Required: true,
    69  										ForceNew: true,
    70  									},
    71  									"encrypted": &schema.Schema{
    72  										Type:     schema.TypeBool,
    73  										Optional: true,
    74  										Computed: true,
    75  										ForceNew: true,
    76  									},
    77  									"iops": &schema.Schema{
    78  										Type:     schema.TypeInt,
    79  										Optional: true,
    80  										Computed: true,
    81  										ForceNew: true,
    82  									},
    83  									"snapshot_id": &schema.Schema{
    84  										Type:     schema.TypeString,
    85  										Optional: true,
    86  										Computed: true,
    87  										ForceNew: true,
    88  									},
    89  									"volume_size": &schema.Schema{
    90  										Type:     schema.TypeInt,
    91  										Optional: true,
    92  										Computed: true,
    93  										ForceNew: true,
    94  									},
    95  									"volume_type": &schema.Schema{
    96  										Type:     schema.TypeString,
    97  										Optional: true,
    98  										Computed: true,
    99  										ForceNew: true,
   100  									},
   101  								},
   102  							},
   103  							Set: hashEbsBlockDevice,
   104  						},
   105  						"ephemeral_block_device": &schema.Schema{
   106  							Type:     schema.TypeSet,
   107  							Optional: true,
   108  							Computed: true,
   109  							ForceNew: true,
   110  							Elem: &schema.Resource{
   111  								Schema: map[string]*schema.Schema{
   112  									"device_name": &schema.Schema{
   113  										Type:     schema.TypeString,
   114  										Required: true,
   115  									},
   116  									"virtual_name": &schema.Schema{
   117  										Type:     schema.TypeString,
   118  										Required: true,
   119  									},
   120  								},
   121  							},
   122  							Set: hashEphemeralBlockDevice,
   123  						},
   124  						"root_block_device": &schema.Schema{
   125  							// TODO: This is a set because we don't support singleton
   126  							//       sub-resources today. We'll enforce that the set only ever has
   127  							//       length zero or one below. When TF gains support for
   128  							//       sub-resources this can be converted.
   129  							Type:     schema.TypeSet,
   130  							Optional: true,
   131  							Computed: true,
   132  							Elem: &schema.Resource{
   133  								// "You can only modify the volume size, volume type, and Delete on
   134  								// Termination flag on the block device mapping entry for the root
   135  								// device volume." - bit.ly/ec2bdmap
   136  								Schema: map[string]*schema.Schema{
   137  									"delete_on_termination": &schema.Schema{
   138  										Type:     schema.TypeBool,
   139  										Optional: true,
   140  										Default:  true,
   141  										ForceNew: true,
   142  									},
   143  									"iops": &schema.Schema{
   144  										Type:     schema.TypeInt,
   145  										Optional: true,
   146  										Computed: true,
   147  										ForceNew: true,
   148  									},
   149  									"volume_size": &schema.Schema{
   150  										Type:     schema.TypeInt,
   151  										Optional: true,
   152  										Computed: true,
   153  										ForceNew: true,
   154  									},
   155  									"volume_type": &schema.Schema{
   156  										Type:     schema.TypeString,
   157  										Optional: true,
   158  										Computed: true,
   159  										ForceNew: true,
   160  									},
   161  								},
   162  							},
   163  							Set: hashRootBlockDevice,
   164  						},
   165  						"ebs_optimized": &schema.Schema{
   166  							Type:     schema.TypeBool,
   167  							Optional: true,
   168  						},
   169  						"iam_instance_profile": &schema.Schema{
   170  							Type:     schema.TypeString,
   171  							ForceNew: true,
   172  							Optional: true,
   173  						},
   174  						"ami": &schema.Schema{
   175  							Type:     schema.TypeString,
   176  							Required: true,
   177  							ForceNew: true,
   178  						},
   179  						"instance_type": &schema.Schema{
   180  							Type:     schema.TypeString,
   181  							Required: true,
   182  							ForceNew: true,
   183  						},
   184  						"key_name": &schema.Schema{
   185  							Type:         schema.TypeString,
   186  							Optional:     true,
   187  							ForceNew:     true,
   188  							Computed:     true,
   189  							ValidateFunc: validateSpotFleetRequestKeyName,
   190  						},
   191  						"monitoring": &schema.Schema{
   192  							Type:     schema.TypeBool,
   193  							Optional: true,
   194  						},
   195  						//									"network_interface_set"
   196  						"placement_group": &schema.Schema{
   197  							Type:     schema.TypeString,
   198  							Optional: true,
   199  							Computed: true,
   200  							ForceNew: true,
   201  						},
   202  						"spot_price": &schema.Schema{
   203  							Type:     schema.TypeString,
   204  							Optional: true,
   205  							ForceNew: true,
   206  						},
   207  						"subnet_id": &schema.Schema{
   208  							Type:     schema.TypeString,
   209  							Optional: true,
   210  							Computed: true,
   211  							ForceNew: true,
   212  						},
   213  						"user_data": &schema.Schema{
   214  							Type:     schema.TypeString,
   215  							Optional: true,
   216  							ForceNew: true,
   217  							StateFunc: func(v interface{}) string {
   218  								switch v.(type) {
   219  								case string:
   220  									hash := sha1.Sum([]byte(v.(string)))
   221  									return hex.EncodeToString(hash[:])
   222  								default:
   223  									return ""
   224  								}
   225  							},
   226  						},
   227  						"weighted_capacity": &schema.Schema{
   228  							Type:     schema.TypeString,
   229  							Optional: true,
   230  							ForceNew: true,
   231  						},
   232  						"availability_zone": &schema.Schema{
   233  							Type:     schema.TypeString,
   234  							Optional: true,
   235  							ForceNew: true,
   236  						},
   237  					},
   238  				},
   239  				Set: hashLaunchSpecification,
   240  			},
   241  			// Everything on a spot fleet is ForceNew except target_capacity
   242  			"target_capacity": &schema.Schema{
   243  				Type:     schema.TypeInt,
   244  				Required: true,
   245  				ForceNew: false,
   246  			},
   247  			"allocation_strategy": &schema.Schema{
   248  				Type:     schema.TypeString,
   249  				Optional: true,
   250  				Default:  "lowestPrice",
   251  				ForceNew: true,
   252  			},
   253  			"excess_capacity_termination_policy": &schema.Schema{
   254  				Type:     schema.TypeString,
   255  				Optional: true,
   256  				Default:  "Default",
   257  				ForceNew: false,
   258  			},
   259  			"spot_price": &schema.Schema{
   260  				Type:     schema.TypeString,
   261  				Required: true,
   262  				ForceNew: true,
   263  			},
   264  			"terminate_instances_with_expiration": &schema.Schema{
   265  				Type:     schema.TypeBool,
   266  				Optional: true,
   267  				ForceNew: true,
   268  			},
   269  			"valid_from": &schema.Schema{
   270  				Type:     schema.TypeString,
   271  				Optional: true,
   272  				ForceNew: true,
   273  			},
   274  			"valid_until": &schema.Schema{
   275  				Type:     schema.TypeString,
   276  				Optional: true,
   277  				ForceNew: true,
   278  			},
   279  			"spot_request_state": &schema.Schema{
   280  				Type:     schema.TypeString,
   281  				Computed: true,
   282  			},
   283  			"client_token": &schema.Schema{
   284  				Type:     schema.TypeString,
   285  				Computed: true,
   286  			},
   287  		},
   288  	}
   289  }
   290  
   291  func buildSpotFleetLaunchSpecification(d map[string]interface{}, meta interface{}) (*ec2.SpotFleetLaunchSpecification, error) {
   292  	conn := meta.(*AWSClient).ec2conn
   293  	opts := &ec2.SpotFleetLaunchSpecification{
   294  		ImageId:      aws.String(d["ami"].(string)),
   295  		InstanceType: aws.String(d["instance_type"].(string)),
   296  		SpotPrice:    aws.String(d["spot_price"].(string)),
   297  		Placement: &ec2.SpotPlacement{
   298  			AvailabilityZone: aws.String(d["availability_zone"].(string)),
   299  		},
   300  	}
   301  
   302  	if v, ok := d["ebs_optimized"]; ok {
   303  		opts.EbsOptimized = aws.Bool(v.(bool))
   304  	}
   305  
   306  	if v, ok := d["monitoring"]; ok {
   307  		opts.Monitoring = &ec2.SpotFleetMonitoring{
   308  			Enabled: aws.Bool(v.(bool)),
   309  		}
   310  	}
   311  
   312  	if v, ok := d["iam_instance_profile"]; ok {
   313  		opts.IamInstanceProfile = &ec2.IamInstanceProfileSpecification{
   314  			Name: aws.String(v.(string)),
   315  		}
   316  	}
   317  
   318  	if v, ok := d["user_data"]; ok {
   319  		opts.UserData = aws.String(
   320  			base64.StdEncoding.EncodeToString([]byte(v.(string))))
   321  	}
   322  
   323  	// check for non-default Subnet, and cast it to a String
   324  	subnet, hasSubnet := d["subnet_id"]
   325  	subnetID := subnet.(string)
   326  
   327  	var associatePublicIPAddress bool
   328  	if v, ok := d["associate_public_ip_address"]; ok {
   329  		associatePublicIPAddress = v.(bool)
   330  	}
   331  
   332  	var groups []*string
   333  	if v, ok := d["security_groups"]; ok {
   334  		// Security group names.
   335  		// For a nondefault VPC, you must use security group IDs instead.
   336  		// See http://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_RunInstances.html
   337  		sgs := v.(*schema.Set).List()
   338  		if len(sgs) > 0 && hasSubnet {
   339  			log.Printf("[WARN] Deprecated. Attempting to use 'security_groups' within a VPC instance. Use 'vpc_security_group_ids' instead.")
   340  		}
   341  		for _, v := range sgs {
   342  			str := v.(string)
   343  			groups = append(groups, aws.String(str))
   344  		}
   345  	}
   346  
   347  	if hasSubnet && associatePublicIPAddress {
   348  		// If we have a non-default VPC / Subnet specified, we can flag
   349  		// AssociatePublicIpAddress to get a Public IP assigned. By default these are not provided.
   350  		// You cannot specify both SubnetId and the NetworkInterface.0.* parameters though, otherwise
   351  		// you get: Network interfaces and an instance-level subnet ID may not be specified on the same request
   352  		// You also need to attach Security Groups to the NetworkInterface instead of the instance,
   353  		// to avoid: Network interfaces and an instance-level security groups may not be specified on
   354  		// the same request
   355  		ni := &ec2.InstanceNetworkInterfaceSpecification{
   356  			AssociatePublicIpAddress: aws.Bool(associatePublicIPAddress),
   357  			DeviceIndex:              aws.Int64(int64(0)),
   358  			SubnetId:                 aws.String(subnetID),
   359  			Groups:                   groups,
   360  		}
   361  
   362  		if v, ok := d["private_ip"]; ok {
   363  			ni.PrivateIpAddress = aws.String(v.(string))
   364  		}
   365  
   366  		if v := d["vpc_security_group_ids"].(*schema.Set); v.Len() > 0 {
   367  			for _, v := range v.List() {
   368  				ni.Groups = append(ni.Groups, aws.String(v.(string)))
   369  			}
   370  		}
   371  
   372  		opts.NetworkInterfaces = []*ec2.InstanceNetworkInterfaceSpecification{ni}
   373  	} else {
   374  		if subnetID != "" {
   375  			opts.SubnetId = aws.String(subnetID)
   376  		}
   377  
   378  		if v, ok := d["vpc_security_group_ids"]; ok {
   379  			if s := v.(*schema.Set); s.Len() > 0 {
   380  				for _, v := range s.List() {
   381  					opts.SecurityGroups = append(opts.SecurityGroups, &ec2.GroupIdentifier{GroupId: aws.String(v.(string))})
   382  				}
   383  			}
   384  		}
   385  	}
   386  
   387  	if v, ok := d["key_name"]; ok {
   388  		opts.KeyName = aws.String(v.(string))
   389  	}
   390  
   391  	if v, ok := d["weighted_capacity"]; ok && v != "" {
   392  		wc, err := strconv.ParseFloat(v.(string), 64)
   393  		if err != nil {
   394  			return nil, err
   395  		}
   396  		opts.WeightedCapacity = aws.Float64(wc)
   397  	}
   398  
   399  	blockDevices, err := readSpotFleetBlockDeviceMappingsFromConfig(d, conn)
   400  	if err != nil {
   401  		return nil, err
   402  	}
   403  	if len(blockDevices) > 0 {
   404  		opts.BlockDeviceMappings = blockDevices
   405  	}
   406  
   407  	return opts, nil
   408  }
   409  
   410  func validateSpotFleetRequestKeyName(v interface{}, k string) (ws []string, errors []error) {
   411  	value := v.(string)
   412  
   413  	if value == "" {
   414  		errors = append(errors, fmt.Errorf("Key name cannot be empty."))
   415  	}
   416  
   417  	return
   418  }
   419  
   420  func readSpotFleetBlockDeviceMappingsFromConfig(
   421  	d map[string]interface{}, conn *ec2.EC2) ([]*ec2.BlockDeviceMapping, error) {
   422  	blockDevices := make([]*ec2.BlockDeviceMapping, 0)
   423  
   424  	if v, ok := d["ebs_block_device"]; ok {
   425  		vL := v.(*schema.Set).List()
   426  		for _, v := range vL {
   427  			bd := v.(map[string]interface{})
   428  			ebs := &ec2.EbsBlockDevice{
   429  				DeleteOnTermination: aws.Bool(bd["delete_on_termination"].(bool)),
   430  			}
   431  
   432  			if v, ok := bd["snapshot_id"].(string); ok && v != "" {
   433  				ebs.SnapshotId = aws.String(v)
   434  			}
   435  
   436  			if v, ok := bd["encrypted"].(bool); ok && v {
   437  				ebs.Encrypted = aws.Bool(v)
   438  			}
   439  
   440  			if v, ok := bd["volume_size"].(int); ok && v != 0 {
   441  				ebs.VolumeSize = aws.Int64(int64(v))
   442  			}
   443  
   444  			if v, ok := bd["volume_type"].(string); ok && v != "" {
   445  				ebs.VolumeType = aws.String(v)
   446  			}
   447  
   448  			if v, ok := bd["iops"].(int); ok && v > 0 {
   449  				ebs.Iops = aws.Int64(int64(v))
   450  			}
   451  
   452  			blockDevices = append(blockDevices, &ec2.BlockDeviceMapping{
   453  				DeviceName: aws.String(bd["device_name"].(string)),
   454  				Ebs:        ebs,
   455  			})
   456  		}
   457  	}
   458  
   459  	if v, ok := d["ephemeral_block_device"]; ok {
   460  		vL := v.(*schema.Set).List()
   461  		for _, v := range vL {
   462  			bd := v.(map[string]interface{})
   463  			blockDevices = append(blockDevices, &ec2.BlockDeviceMapping{
   464  				DeviceName:  aws.String(bd["device_name"].(string)),
   465  				VirtualName: aws.String(bd["virtual_name"].(string)),
   466  			})
   467  		}
   468  	}
   469  
   470  	if v, ok := d["root_block_device"]; ok {
   471  		vL := v.(*schema.Set).List()
   472  		if len(vL) > 1 {
   473  			return nil, fmt.Errorf("Cannot specify more than one root_block_device.")
   474  		}
   475  		for _, v := range vL {
   476  			bd := v.(map[string]interface{})
   477  			ebs := &ec2.EbsBlockDevice{
   478  				DeleteOnTermination: aws.Bool(bd["delete_on_termination"].(bool)),
   479  			}
   480  
   481  			if v, ok := bd["volume_size"].(int); ok && v != 0 {
   482  				ebs.VolumeSize = aws.Int64(int64(v))
   483  			}
   484  
   485  			if v, ok := bd["volume_type"].(string); ok && v != "" {
   486  				ebs.VolumeType = aws.String(v)
   487  			}
   488  
   489  			if v, ok := bd["iops"].(int); ok && v > 0 {
   490  				ebs.Iops = aws.Int64(int64(v))
   491  			}
   492  
   493  			if dn, err := fetchRootDeviceName(d["ami"].(string), conn); err == nil {
   494  				if dn == nil {
   495  					return nil, fmt.Errorf(
   496  						"Expected 1 AMI for ID: %s, got none",
   497  						d["ami"].(string))
   498  				}
   499  
   500  				blockDevices = append(blockDevices, &ec2.BlockDeviceMapping{
   501  					DeviceName: dn,
   502  					Ebs:        ebs,
   503  				})
   504  			} else {
   505  				return nil, err
   506  			}
   507  		}
   508  	}
   509  
   510  	return blockDevices, nil
   511  }
   512  
   513  func buildAwsSpotFleetLaunchSpecifications(
   514  	d *schema.ResourceData, meta interface{}) ([]*ec2.SpotFleetLaunchSpecification, error) {
   515  
   516  	user_specs := d.Get("launch_specification").(*schema.Set).List()
   517  	specs := make([]*ec2.SpotFleetLaunchSpecification, len(user_specs))
   518  	for i, user_spec := range user_specs {
   519  		user_spec_map := user_spec.(map[string]interface{})
   520  		// panic: interface conversion: interface {} is map[string]interface {}, not *schema.ResourceData
   521  		opts, err := buildSpotFleetLaunchSpecification(user_spec_map, meta)
   522  		if err != nil {
   523  			return nil, err
   524  		}
   525  		specs[i] = opts
   526  	}
   527  
   528  	return specs, nil
   529  }
   530  
   531  func resourceAwsSpotFleetRequestCreate(d *schema.ResourceData, meta interface{}) error {
   532  	// http://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_RequestSpotFleet.html
   533  	conn := meta.(*AWSClient).ec2conn
   534  
   535  	launch_specs, err := buildAwsSpotFleetLaunchSpecifications(d, meta)
   536  	if err != nil {
   537  		return err
   538  	}
   539  
   540  	// http://docs.aws.amazon.com/sdk-for-go/api/service/ec2.html#type-SpotFleetRequestConfigData
   541  	spotFleetConfig := &ec2.SpotFleetRequestConfigData{
   542  		IamFleetRole:                     aws.String(d.Get("iam_fleet_role").(string)),
   543  		LaunchSpecifications:             launch_specs,
   544  		SpotPrice:                        aws.String(d.Get("spot_price").(string)),
   545  		TargetCapacity:                   aws.Int64(int64(d.Get("target_capacity").(int))),
   546  		ClientToken:                      aws.String(resource.UniqueId()),
   547  		TerminateInstancesWithExpiration: aws.Bool(d.Get("terminate_instances_with_expiration").(bool)),
   548  	}
   549  
   550  	if v, ok := d.GetOk("excess_capacity_termination_policy"); ok {
   551  		spotFleetConfig.ExcessCapacityTerminationPolicy = aws.String(v.(string))
   552  	}
   553  
   554  	if v, ok := d.GetOk("allocation_strategy"); ok {
   555  		spotFleetConfig.AllocationStrategy = aws.String(v.(string))
   556  	} else {
   557  		spotFleetConfig.AllocationStrategy = aws.String("lowestPrice")
   558  	}
   559  
   560  	if v, ok := d.GetOk("valid_from"); ok {
   561  		valid_from, err := time.Parse(awsAutoscalingScheduleTimeLayout, v.(string))
   562  		if err != nil {
   563  			return err
   564  		}
   565  		spotFleetConfig.ValidFrom = &valid_from
   566  	}
   567  
   568  	if v, ok := d.GetOk("valid_until"); ok {
   569  		valid_until, err := time.Parse(awsAutoscalingScheduleTimeLayout, v.(string))
   570  		if err != nil {
   571  			return err
   572  		}
   573  		spotFleetConfig.ValidUntil = &valid_until
   574  	} else {
   575  		valid_until := time.Now().Add(24 * time.Hour)
   576  		spotFleetConfig.ValidUntil = &valid_until
   577  	}
   578  
   579  	// http://docs.aws.amazon.com/sdk-for-go/api/service/ec2.html#type-RequestSpotFleetInput
   580  	spotFleetOpts := &ec2.RequestSpotFleetInput{
   581  		SpotFleetRequestConfig: spotFleetConfig,
   582  		DryRun:                 aws.Bool(false),
   583  	}
   584  
   585  	log.Printf("[DEBUG] Requesting spot fleet with these opts: %+v", spotFleetOpts)
   586  
   587  	// Since IAM is eventually consistent, we retry creation as a newly created role may not
   588  	// take effect immediately, resulting in an InvalidSpotFleetRequestConfig error
   589  	var resp *ec2.RequestSpotFleetOutput
   590  	err = resource.Retry(1*time.Minute, func() *resource.RetryError {
   591  		var err error
   592  		resp, err = conn.RequestSpotFleet(spotFleetOpts)
   593  
   594  		if err != nil {
   595  			if awsErr, ok := err.(awserr.Error); ok {
   596  				// IAM is eventually consistent :/
   597  				if awsErr.Code() == "InvalidSpotFleetRequestConfig" {
   598  					return resource.RetryableError(
   599  						fmt.Errorf("[WARN] Error creating Spot fleet request, retrying: %s", err))
   600  				}
   601  			}
   602  			return resource.NonRetryableError(err)
   603  		}
   604  		return nil
   605  	})
   606  
   607  	if err != nil {
   608  		return fmt.Errorf("Error requesting spot fleet: %s", err)
   609  	}
   610  
   611  	d.SetId(*resp.SpotFleetRequestId)
   612  
   613  	return resourceAwsSpotFleetRequestRead(d, meta)
   614  }
   615  
   616  func resourceAwsSpotFleetRequestRead(d *schema.ResourceData, meta interface{}) error {
   617  	// http://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_DescribeSpotFleetRequests.html
   618  	conn := meta.(*AWSClient).ec2conn
   619  
   620  	req := &ec2.DescribeSpotFleetRequestsInput{
   621  		SpotFleetRequestIds: []*string{aws.String(d.Id())},
   622  	}
   623  	resp, err := conn.DescribeSpotFleetRequests(req)
   624  
   625  	if err != nil {
   626  		// If the spot request was not found, return nil so that we can show
   627  		// that it is gone.
   628  		ec2err, ok := err.(awserr.Error)
   629  		if ok && ec2err.Code() == "InvalidSpotFleetRequestID.NotFound" {
   630  			d.SetId("")
   631  			return nil
   632  		}
   633  
   634  		// Some other error, report it
   635  		return err
   636  	}
   637  
   638  	sfr := resp.SpotFleetRequestConfigs[0]
   639  
   640  	// if the request is cancelled, then it is gone
   641  	cancelledStates := map[string]bool{
   642  		"cancelled":             true,
   643  		"cancelled_running":     true,
   644  		"cancelled_terminating": true,
   645  	}
   646  	if _, ok := cancelledStates[*sfr.SpotFleetRequestState]; ok {
   647  		d.SetId("")
   648  		return nil
   649  	}
   650  
   651  	d.SetId(*sfr.SpotFleetRequestId)
   652  	d.Set("spot_request_state", aws.StringValue(sfr.SpotFleetRequestState))
   653  
   654  	config := sfr.SpotFleetRequestConfig
   655  
   656  	if config.AllocationStrategy != nil {
   657  		d.Set("allocation_strategy", aws.StringValue(config.AllocationStrategy))
   658  	}
   659  
   660  	if config.ClientToken != nil {
   661  		d.Set("client_token", aws.StringValue(config.ClientToken))
   662  	}
   663  
   664  	if config.ExcessCapacityTerminationPolicy != nil {
   665  		d.Set("excess_capacity_termination_policy",
   666  			aws.StringValue(config.ExcessCapacityTerminationPolicy))
   667  	}
   668  
   669  	if config.IamFleetRole != nil {
   670  		d.Set("iam_fleet_role", aws.StringValue(config.IamFleetRole))
   671  	}
   672  
   673  	if config.SpotPrice != nil {
   674  		d.Set("spot_price", aws.StringValue(config.SpotPrice))
   675  	}
   676  
   677  	if config.TargetCapacity != nil {
   678  		d.Set("target_capacity", aws.Int64Value(config.TargetCapacity))
   679  	}
   680  
   681  	if config.TerminateInstancesWithExpiration != nil {
   682  		d.Set("terminate_instances_with_expiration",
   683  			aws.BoolValue(config.TerminateInstancesWithExpiration))
   684  	}
   685  
   686  	if config.ValidFrom != nil {
   687  		d.Set("valid_from",
   688  			aws.TimeValue(config.ValidFrom).Format(awsAutoscalingScheduleTimeLayout))
   689  	}
   690  
   691  	if config.ValidUntil != nil {
   692  		d.Set("valid_until",
   693  			aws.TimeValue(config.ValidUntil).Format(awsAutoscalingScheduleTimeLayout))
   694  	}
   695  
   696  	d.Set("launch_specification", launchSpecsToSet(config.LaunchSpecifications, conn))
   697  
   698  	return nil
   699  }
   700  
   701  func launchSpecsToSet(ls []*ec2.SpotFleetLaunchSpecification, conn *ec2.EC2) *schema.Set {
   702  	specs := &schema.Set{F: hashLaunchSpecification}
   703  	for _, val := range ls {
   704  		dn, err := fetchRootDeviceName(aws.StringValue(val.ImageId), conn)
   705  		if err != nil {
   706  			log.Panic(err)
   707  		} else {
   708  			ls := launchSpecToMap(val, dn)
   709  			specs.Add(ls)
   710  		}
   711  	}
   712  	return specs
   713  }
   714  
   715  func launchSpecToMap(
   716  	l *ec2.SpotFleetLaunchSpecification,
   717  	rootDevName *string,
   718  ) map[string]interface{} {
   719  	m := make(map[string]interface{})
   720  
   721  	m["root_block_device"] = rootBlockDeviceToSet(l.BlockDeviceMappings, rootDevName)
   722  	m["ebs_block_device"] = ebsBlockDevicesToSet(l.BlockDeviceMappings, rootDevName)
   723  	m["ephemeral_block_device"] = ephemeralBlockDevicesToSet(l.BlockDeviceMappings)
   724  
   725  	if l.ImageId != nil {
   726  		m["ami"] = aws.StringValue(l.ImageId)
   727  	}
   728  
   729  	if l.InstanceType != nil {
   730  		m["instance_type"] = aws.StringValue(l.InstanceType)
   731  	}
   732  
   733  	if l.SpotPrice != nil {
   734  		m["spot_price"] = aws.StringValue(l.SpotPrice)
   735  	}
   736  
   737  	if l.EbsOptimized != nil {
   738  		m["ebs_optimized"] = aws.BoolValue(l.EbsOptimized)
   739  	}
   740  
   741  	if l.Monitoring != nil && l.Monitoring.Enabled != nil {
   742  		m["monitoring"] = aws.BoolValue(l.Monitoring.Enabled)
   743  	}
   744  
   745  	if l.IamInstanceProfile != nil && l.IamInstanceProfile.Name != nil {
   746  		m["iam_instance_profile"] = aws.StringValue(l.IamInstanceProfile.Name)
   747  	}
   748  
   749  	if l.UserData != nil {
   750  		ud_dec, err := base64.StdEncoding.DecodeString(aws.StringValue(l.UserData))
   751  		if err == nil {
   752  			m["user_data"] = string(ud_dec)
   753  		}
   754  	}
   755  
   756  	if l.KeyName != nil {
   757  		m["key_name"] = aws.StringValue(l.KeyName)
   758  	}
   759  
   760  	if l.Placement != nil {
   761  		m["availability_zone"] = aws.StringValue(l.Placement.AvailabilityZone)
   762  	}
   763  
   764  	if l.SubnetId != nil {
   765  		m["subnet_id"] = aws.StringValue(l.SubnetId)
   766  	}
   767  
   768  	if l.WeightedCapacity != nil {
   769  		m["weighted_capacity"] = fmt.Sprintf("%.3f", aws.Float64Value(l.WeightedCapacity))
   770  	}
   771  
   772  	// m["security_groups"] = securityGroupsToSet(l.SecutiryGroups)
   773  	return m
   774  }
   775  
   776  func ebsBlockDevicesToSet(bdm []*ec2.BlockDeviceMapping, rootDevName *string) *schema.Set {
   777  	set := &schema.Set{F: hashEphemeralBlockDevice}
   778  
   779  	for _, val := range bdm {
   780  		if val.Ebs != nil {
   781  			m := make(map[string]interface{})
   782  
   783  			ebs := val.Ebs
   784  
   785  			if val.DeviceName != nil {
   786  				if aws.StringValue(rootDevName) == aws.StringValue(val.DeviceName) {
   787  					continue
   788  				}
   789  
   790  				m["device_name"] = aws.StringValue(val.DeviceName)
   791  			}
   792  
   793  			if ebs.DeleteOnTermination != nil {
   794  				m["delete_on_termination"] = aws.BoolValue(ebs.DeleteOnTermination)
   795  			}
   796  
   797  			if ebs.SnapshotId != nil {
   798  				m["snapshot_id"] = aws.StringValue(ebs.SnapshotId)
   799  			}
   800  
   801  			if ebs.Encrypted != nil {
   802  				m["encrypted"] = aws.BoolValue(ebs.Encrypted)
   803  			}
   804  
   805  			if ebs.VolumeSize != nil {
   806  				m["volume_size"] = aws.Int64Value(ebs.VolumeSize)
   807  			}
   808  
   809  			if ebs.VolumeType != nil {
   810  				m["volume_type"] = aws.StringValue(ebs.VolumeType)
   811  			}
   812  
   813  			if ebs.Iops != nil {
   814  				m["iops"] = aws.Int64Value(ebs.Iops)
   815  			}
   816  
   817  			set.Add(m)
   818  		}
   819  	}
   820  
   821  	return set
   822  }
   823  
   824  func ephemeralBlockDevicesToSet(bdm []*ec2.BlockDeviceMapping) *schema.Set {
   825  	set := &schema.Set{F: hashEphemeralBlockDevice}
   826  
   827  	for _, val := range bdm {
   828  		if val.VirtualName != nil {
   829  			m := make(map[string]interface{})
   830  			m["virtual_name"] = aws.StringValue(val.VirtualName)
   831  
   832  			if val.DeviceName != nil {
   833  				m["device_name"] = aws.StringValue(val.DeviceName)
   834  			}
   835  
   836  			set.Add(m)
   837  		}
   838  	}
   839  
   840  	return set
   841  }
   842  
   843  func rootBlockDeviceToSet(
   844  	bdm []*ec2.BlockDeviceMapping,
   845  	rootDevName *string,
   846  ) *schema.Set {
   847  	set := &schema.Set{F: hashRootBlockDevice}
   848  
   849  	if rootDevName != nil {
   850  		for _, val := range bdm {
   851  			if aws.StringValue(val.DeviceName) == aws.StringValue(rootDevName) {
   852  				m := make(map[string]interface{})
   853  				if val.Ebs.DeleteOnTermination != nil {
   854  					m["delete_on_termination"] = aws.BoolValue(val.Ebs.DeleteOnTermination)
   855  				}
   856  
   857  				if val.Ebs.VolumeSize != nil {
   858  					m["volume_size"] = aws.Int64Value(val.Ebs.VolumeSize)
   859  				}
   860  
   861  				if val.Ebs.VolumeType != nil {
   862  					m["volume_type"] = aws.StringValue(val.Ebs.VolumeType)
   863  				}
   864  
   865  				if val.Ebs.Iops != nil {
   866  					m["iops"] = aws.Int64Value(val.Ebs.Iops)
   867  				}
   868  
   869  				set.Add(m)
   870  			}
   871  		}
   872  	}
   873  
   874  	return set
   875  }
   876  
   877  func resourceAwsSpotFleetRequestUpdate(d *schema.ResourceData, meta interface{}) error {
   878  	// http://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_ModifySpotFleetRequest.html
   879  	conn := meta.(*AWSClient).ec2conn
   880  
   881  	d.Partial(true)
   882  
   883  	req := &ec2.ModifySpotFleetRequestInput{
   884  		SpotFleetRequestId: aws.String(d.Id()),
   885  	}
   886  
   887  	if val, ok := d.GetOk("target_capacity"); ok {
   888  		req.TargetCapacity = aws.Int64(int64(val.(int)))
   889  	}
   890  
   891  	if val, ok := d.GetOk("excess_capacity_termination_policy"); ok {
   892  		req.ExcessCapacityTerminationPolicy = aws.String(val.(string))
   893  	}
   894  
   895  	resp, err := conn.ModifySpotFleetRequest(req)
   896  	if err == nil && aws.BoolValue(resp.Return) {
   897  		// TODO: rollback to old values?
   898  	}
   899  
   900  	return nil
   901  }
   902  
   903  func resourceAwsSpotFleetRequestDelete(d *schema.ResourceData, meta interface{}) error {
   904  	// http://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_CancelSpotFleetRequests.html
   905  	conn := meta.(*AWSClient).ec2conn
   906  
   907  	log.Printf("[INFO] Cancelling spot fleet request: %s", d.Id())
   908  	_, err := conn.CancelSpotFleetRequests(&ec2.CancelSpotFleetRequestsInput{
   909  		SpotFleetRequestIds: []*string{aws.String(d.Id())},
   910  		TerminateInstances:  aws.Bool(d.Get("terminate_instances_with_expiration").(bool)),
   911  	})
   912  
   913  	if err != nil {
   914  		return fmt.Errorf("Error cancelling spot request (%s): %s", d.Id(), err)
   915  	}
   916  
   917  	return nil
   918  }
   919  
   920  func hashEphemeralBlockDevice(v interface{}) int {
   921  	var buf bytes.Buffer
   922  	m := v.(map[string]interface{})
   923  	buf.WriteString(fmt.Sprintf("%s-", m["device_name"].(string)))
   924  	buf.WriteString(fmt.Sprintf("%s-", m["virtual_name"].(string)))
   925  	return hashcode.String(buf.String())
   926  }
   927  
   928  func hashRootBlockDevice(v interface{}) int {
   929  	// there can be only one root device; no need to hash anything
   930  	return 0
   931  }
   932  
   933  func hashLaunchSpecification(v interface{}) int {
   934  	var buf bytes.Buffer
   935  	m := v.(map[string]interface{})
   936  	buf.WriteString(fmt.Sprintf("%s-", m["ami"].(string)))
   937  	if m["availability_zone"] != nil && m["availability_zone"] != "" {
   938  		buf.WriteString(fmt.Sprintf("%s-", m["availability_zone"].(string)))
   939  	} else if m["subnet_id"] != nil && m["subnet_id"] != "" {
   940  		buf.WriteString(fmt.Sprintf("%s-", m["subnet_id"].(string)))
   941  	} else {
   942  		panic(
   943  			fmt.Sprintf(
   944  				"Must set one of:\navailability_zone %#v\nsubnet_id: %#v",
   945  				m["availability_zone"],
   946  				m["subnet_id"]))
   947  	}
   948  	buf.WriteString(fmt.Sprintf("%s-", m["instance_type"].(string)))
   949  	buf.WriteString(fmt.Sprintf("%s-", m["spot_price"].(string)))
   950  	buf.WriteString(fmt.Sprintf("%s-", m["user_data"].(string)))
   951  	return hashcode.String(buf.String())
   952  }
   953  
   954  func hashEbsBlockDevice(v interface{}) int {
   955  	var buf bytes.Buffer
   956  	m := v.(map[string]interface{})
   957  	buf.WriteString(fmt.Sprintf("%s-", m["device_name"].(string)))
   958  	buf.WriteString(fmt.Sprintf("%s-", m["snapshot_id"].(string)))
   959  	return hashcode.String(buf.String())
   960  }