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