diff --git a/aws/provider.go b/aws/provider.go index 8c00f501dd7..187db702214 100644 --- a/aws/provider.go +++ b/aws/provider.go @@ -396,6 +396,7 @@ func Provider() terraform.ResourceProvider { "aws_dynamodb_table": resourceAwsDynamoDbTable(), "aws_dynamodb_table_item": resourceAwsDynamoDbTableItem(), "aws_dynamodb_global_table": resourceAwsDynamoDbGlobalTable(), + "aws_ec2_fleet": resourceAwsEc2Fleet(), "aws_ebs_snapshot": resourceAwsEbsSnapshot(), "aws_ebs_volume": resourceAwsEbsVolume(), "aws_ecr_lifecycle_policy": resourceAwsEcrLifecyclePolicy(), diff --git a/aws/resource_aws_ec2_fleet.go b/aws/resource_aws_ec2_fleet.go new file mode 100644 index 00000000000..7e816cddef9 --- /dev/null +++ b/aws/resource_aws_ec2_fleet.go @@ -0,0 +1,840 @@ +package aws + +import ( + "fmt" + "log" + "strconv" + "time" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/ec2" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/helper/schema" + "github.com/hashicorp/terraform/helper/validation" +) + +func resourceAwsEc2Fleet() *schema.Resource { + return &schema.Resource{ + Create: resourceAwsEc2FleetCreate, + Read: resourceAwsEc2FleetRead, + Update: resourceAwsEc2FleetUpdate, + Delete: resourceAwsEc2FleetDelete, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(10 * time.Minute), + Delete: schema.DefaultTimeout(10 * time.Minute), + Update: schema.DefaultTimeout(10 * time.Minute), + }, + + Schema: map[string]*schema.Schema{ + "excess_capacity_termination_policy": { + Type: schema.TypeString, + Optional: true, + Default: ec2.FleetExcessCapacityTerminationPolicyTermination, + ValidateFunc: validation.StringInSlice([]string{ + ec2.FleetExcessCapacityTerminationPolicyNoTermination, + ec2.FleetExcessCapacityTerminationPolicyTermination, + }, false), + }, + "launch_template_config": { + Type: schema.TypeList, + Required: true, + ForceNew: true, + MinItems: 1, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "launch_template_specification": { + Type: schema.TypeList, + Required: true, + ForceNew: true, + MinItems: 1, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "launch_template_id": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + "launch_template_name": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + "version": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + }, + }, + }, + "override": { + Type: schema.TypeList, + Optional: true, + ForceNew: true, + MaxItems: 50, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "availability_zone": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + "instance_type": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + "max_price": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + "priority": { + Type: schema.TypeFloat, + Optional: true, + ForceNew: true, + }, + "subnet_id": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + "weighted_capacity": { + Type: schema.TypeFloat, + Optional: true, + ForceNew: true, + }, + }, + }, + }, + }, + }, + }, + "on_demand_options": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { + if old == "1" && new == "0" { + return true + } + return false + }, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "allocation_strategy": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Default: "lowestPrice", + ValidateFunc: validation.StringInSlice([]string{ + // AWS SDK constant incorrect + // ec2.FleetOnDemandAllocationStrategyLowestPrice, + "lowestPrice", + ec2.FleetOnDemandAllocationStrategyPrioritized, + }, false), + }, + }, + }, + }, + "replace_unhealthy_instances": { + Type: schema.TypeBool, + Optional: true, + ForceNew: true, + }, + "spot_options": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { + if old == "1" && new == "0" { + return true + } + return false + }, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "allocation_strategy": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Default: "lowestPrice", + ValidateFunc: validation.StringInSlice([]string{ + ec2.SpotAllocationStrategyDiversified, + // AWS SDK constant incorrect + // ec2.SpotAllocationStrategyLowestPrice, + "lowestPrice", + }, false), + }, + "instance_interruption_behavior": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Default: ec2.SpotInstanceInterruptionBehaviorTerminate, + ValidateFunc: validation.StringInSlice([]string{ + ec2.SpotInstanceInterruptionBehaviorHibernate, + ec2.SpotInstanceInterruptionBehaviorStop, + ec2.SpotInstanceInterruptionBehaviorTerminate, + }, false), + }, + "instance_pools_to_use_count": { + Type: schema.TypeInt, + Optional: true, + ForceNew: true, + Default: 1, + ValidateFunc: validation.IntAtLeast(1), + }, + }, + }, + }, + "tags": { + Type: schema.TypeMap, + Optional: true, + ForceNew: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "target_capacity_specification": { + Type: schema.TypeList, + Required: true, + MinItems: 1, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "default_target_capacity_type": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.StringInSlice([]string{ + ec2.DefaultTargetCapacityTypeOnDemand, + ec2.DefaultTargetCapacityTypeSpot, + }, false), + }, + "on_demand_target_capacity": { + Type: schema.TypeInt, + Optional: true, + ForceNew: true, + DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { + // Show difference for new resources + if d.Id() == "" { + return false + } + // Show difference if value is configured + if new != "0" { + return false + } + // Show difference if existing state reflects different default type + defaultTargetCapacityTypeO, _ := d.GetChange("target_capacity_specification.0.default_target_capacity_type") + if defaultTargetCapacityTypeO.(string) != ec2.DefaultTargetCapacityTypeOnDemand { + return false + } + // Show difference if existing state reflects different total capacity + oldInt, err := strconv.Atoi(old) + if err != nil { + log.Printf("[WARN] %s DiffSuppressFunc error converting %s to integer: %s", k, old, err) + return false + } + totalTargetCapacityO, _ := d.GetChange("target_capacity_specification.0.total_target_capacity") + if oldInt != totalTargetCapacityO.(int) { + return false + } + return true + }, + }, + "spot_target_capacity": { + Type: schema.TypeInt, + Optional: true, + ForceNew: true, + DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { + // Show difference for new resources + if d.Id() == "" { + return false + } + // Show difference if value is configured + if new != "0" { + return false + } + // Show difference if existing state reflects different default type + defaultTargetCapacityTypeO, _ := d.GetChange("target_capacity_specification.0.default_target_capacity_type") + if defaultTargetCapacityTypeO.(string) != ec2.DefaultTargetCapacityTypeSpot { + return false + } + // Show difference if existing state reflects different total capacity + oldInt, err := strconv.Atoi(old) + if err != nil { + log.Printf("[WARN] %s DiffSuppressFunc error converting %s to integer: %s", k, old, err) + return false + } + totalTargetCapacityO, _ := d.GetChange("target_capacity_specification.0.total_target_capacity") + if oldInt != totalTargetCapacityO.(int) { + return false + } + return true + }, + }, + "total_target_capacity": { + Type: schema.TypeInt, + Required: true, + }, + }, + }, + }, + "terminate_instances": { + Type: schema.TypeBool, + Optional: true, + Default: false, + }, + "terminate_instances_with_expiration": { + Type: schema.TypeBool, + Optional: true, + ForceNew: true, + }, + "type": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Default: ec2.FleetTypeMaintain, + ValidateFunc: validation.StringInSlice([]string{ + ec2.FleetTypeMaintain, + ec2.FleetTypeRequest, + }, false), + }, + }, + } +} + +func resourceAwsEc2FleetCreate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).ec2conn + + input := &ec2.CreateFleetInput{ + ExcessCapacityTerminationPolicy: aws.String(d.Get("excess_capacity_termination_policy").(string)), + LaunchTemplateConfigs: expandEc2FleetLaunchTemplateConfigRequests(d.Get("launch_template_config").([]interface{})), + OnDemandOptions: expandEc2OnDemandOptionsRequest(d.Get("on_demand_options").([]interface{})), + ReplaceUnhealthyInstances: aws.Bool(d.Get("replace_unhealthy_instances").(bool)), + SpotOptions: expandEc2SpotOptionsRequest(d.Get("spot_options").([]interface{})), + TargetCapacitySpecification: expandEc2TargetCapacitySpecificationRequest(d.Get("target_capacity_specification").([]interface{})), + TerminateInstancesWithExpiration: aws.Bool(d.Get("terminate_instances_with_expiration").(bool)), + TagSpecifications: expandEc2TagSpecifications(d.Get("tags").(map[string]interface{})), + Type: aws.String(d.Get("type").(string)), + } + + log.Printf("[DEBUG] Creating EC2 Fleet: %s", input) + output, err := conn.CreateFleet(input) + if err != nil { + return fmt.Errorf("error creating EC2 Fleet: %s", err) + } + + d.SetId(aws.StringValue(output.FleetId)) + + // If a request type is fulfilled immediately, we can miss the transition from active to deleted + // Instead of an error here, allow the Read function to trigger recreation + target := []string{ec2.FleetStateCodeActive} + if d.Get("type").(string) == ec2.FleetTypeRequest { + target = append(target, ec2.FleetStateCodeDeleted) + target = append(target, ec2.FleetStateCodeDeletedRunning) + target = append(target, ec2.FleetStateCodeDeletedTerminating) + // AWS SDK constants incorrect + target = append(target, "deleted_running") + target = append(target, "deleted_terminating") + } + + stateConf := &resource.StateChangeConf{ + Pending: []string{ec2.FleetStateCodeSubmitted}, + Target: target, + Refresh: ec2FleetRefreshFunc(conn, d.Id()), + Timeout: d.Timeout(schema.TimeoutCreate), + } + + log.Printf("[DEBUG] Waiting for EC2 Fleet (%s) activation", d.Id()) + _, err = stateConf.WaitForState() + if err != nil { + return fmt.Errorf("error waiting for EC2 Fleet (%s) activation: %s", d.Id(), err) + } + + return resourceAwsEc2FleetRead(d, meta) +} + +func resourceAwsEc2FleetRead(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).ec2conn + + input := &ec2.DescribeFleetsInput{ + FleetIds: []*string{aws.String(d.Id())}, + } + + log.Printf("[DEBUG] Reading EC2 Fleet (%s): %s", d.Id(), input) + output, err := conn.DescribeFleets(input) + + if isAWSErr(err, "InvalidFleetId.NotFound", "") { + log.Printf("[WARN] EC2 Fleet (%s) not found, removing from state", d.Id()) + d.SetId("") + return nil + } + + if err != nil { + return fmt.Errorf("error reading EC2 Fleet: %s", err) + } + + if output == nil || len(output.Fleets) == 0 { + log.Printf("[WARN] EC2 Fleet (%s) not found, removing from state", d.Id()) + d.SetId("") + return nil + } + + var fleet *ec2.FleetData + for _, fleetData := range output.Fleets { + if fleetData == nil { + continue + } + if aws.StringValue(fleetData.FleetId) != d.Id() { + continue + } + fleet = fleetData + break + } + + if fleet == nil { + log.Printf("[WARN] EC2 Fleet (%s) not found, removing from state", d.Id()) + d.SetId("") + return nil + } + + deletedStates := []string{ + ec2.FleetStateCodeDeleted, + ec2.FleetStateCodeDeletedRunning, + ec2.FleetStateCodeDeletedTerminating, + // AWS SDK constants are incorrect + "deleted_running", + "deleted_terminating", + } + for _, deletedState := range deletedStates { + if aws.StringValue(fleet.FleetState) == deletedState { + log.Printf("[WARN] EC2 Fleet (%s) in deleted state (%s), removing from state", d.Id(), aws.StringValue(fleet.FleetState)) + d.SetId("") + return nil + } + } + + d.Set("excess_capacity_termination_policy", fleet.ExcessCapacityTerminationPolicy) + + if err := d.Set("launch_template_config", flattenEc2FleetLaunchTemplateConfigs(fleet.LaunchTemplateConfigs)); err != nil { + return fmt.Errorf("error setting launch_template_config: %s", err) + } + + if err := d.Set("on_demand_options", flattenEc2OnDemandOptions(fleet.OnDemandOptions)); err != nil { + return fmt.Errorf("error setting on_demand_options: %s", err) + } + + d.Set("replace_unhealthy_instances", fleet.ReplaceUnhealthyInstances) + + if err := d.Set("spot_options", flattenEc2SpotOptions(fleet.SpotOptions)); err != nil { + return fmt.Errorf("error setting spot_options: %s", err) + } + + if err := d.Set("target_capacity_specification", flattenEc2TargetCapacitySpecification(fleet.TargetCapacitySpecification)); err != nil { + return fmt.Errorf("error setting target_capacity_specification: %s", err) + } + + d.Set("terminate_instances_with_expiration", fleet.TerminateInstancesWithExpiration) + d.Set("type", fleet.Type) + + if err := d.Set("tags", tagsToMap(fleet.Tags)); err != nil { + return fmt.Errorf("error setting tags: %s", err) + } + + return nil +} + +func resourceAwsEc2FleetUpdate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).ec2conn + + input := &ec2.ModifyFleetInput{ + ExcessCapacityTerminationPolicy: aws.String(d.Get("excess_capacity_termination_policy").(string)), + FleetId: aws.String(d.Id()), + // InvalidTargetCapacitySpecification: Currently we only support total target capacity modification. + // TargetCapacitySpecification: expandEc2TargetCapacitySpecificationRequest(d.Get("target_capacity_specification").([]interface{})), + TargetCapacitySpecification: &ec2.TargetCapacitySpecificationRequest{ + TotalTargetCapacity: aws.Int64(int64(d.Get("target_capacity_specification.0.total_target_capacity").(int))), + }, + } + + log.Printf("[DEBUG] Modifying EC2 Fleet (%s): %s", d.Id(), input) + _, err := conn.ModifyFleet(input) + + if err != nil { + return fmt.Errorf("error modifying EC2 Fleet: %s", err) + } + + stateConf := &resource.StateChangeConf{ + Pending: []string{ec2.FleetStateCodeModifying}, + Target: []string{ec2.FleetStateCodeActive}, + Refresh: ec2FleetRefreshFunc(conn, d.Id()), + Timeout: d.Timeout(schema.TimeoutUpdate), + } + + log.Printf("[DEBUG] Waiting for EC2 Fleet (%s) modification", d.Id()) + _, err = stateConf.WaitForState() + if err != nil { + return fmt.Errorf("error waiting for EC2 Fleet (%s) modification: %s", d.Id(), err) + } + + return resourceAwsEc2FleetRead(d, meta) +} + +func resourceAwsEc2FleetDelete(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).ec2conn + + input := &ec2.DeleteFleetsInput{ + FleetIds: []*string{aws.String(d.Id())}, + TerminateInstances: aws.Bool(d.Get("terminate_instances").(bool)), + } + + log.Printf("[DEBUG] Deleting EC2 Fleet (%s): %s", d.Id(), input) + _, err := conn.DeleteFleets(input) + + if isAWSErr(err, "InvalidFleetId.NotFound", "") { + return nil + } + + if err != nil { + return fmt.Errorf("error deleting EC2 Fleet: %s", err) + } + + pending := []string{ec2.FleetStateCodeActive} + target := []string{ec2.FleetStateCodeDeleted} + if d.Get("terminate_instances").(bool) { + pending = append(pending, ec2.FleetStateCodeDeletedTerminating) + // AWS SDK constant is incorrect: unexpected state 'deleted_terminating', wanted target 'deleted, deleted-terminating' + pending = append(pending, "deleted_terminating") + } else { + target = append(target, ec2.FleetStateCodeDeletedRunning) + // AWS SDK constant is incorrect: unexpected state 'deleted_running', wanted target 'deleted, deleted-running' + target = append(target, "deleted_running") + } + + stateConf := &resource.StateChangeConf{ + Pending: pending, + Target: target, + Refresh: ec2FleetRefreshFunc(conn, d.Id()), + Timeout: d.Timeout(schema.TimeoutDelete), + } + + log.Printf("[DEBUG] Waiting for EC2 Fleet (%s) deletion", d.Id()) + _, err = stateConf.WaitForState() + if err != nil { + return fmt.Errorf("error waiting for EC2 Fleet (%s) deletion: %s", d.Id(), err) + } + + return nil +} + +func ec2FleetRefreshFunc(conn *ec2.EC2, fleetID string) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + input := &ec2.DescribeFleetsInput{ + FleetIds: []*string{aws.String(fleetID)}, + } + + log.Printf("[DEBUG] Reading EC2 Fleet (%s): %s", fleetID, input) + output, err := conn.DescribeFleets(input) + + if isAWSErr(err, "InvalidFleetId.NotFound", "") { + return nil, ec2.FleetStateCodeDeleted, nil + } + + if err != nil { + return nil, "", fmt.Errorf("error reading EC2 Fleet: %s", err) + } + + if output == nil || len(output.Fleets) == 0 { + return nil, ec2.FleetStateCodeDeleted, nil + } + + var fleet *ec2.FleetData + for _, fleetData := range output.Fleets { + if fleetData == nil { + continue + } + if aws.StringValue(fleetData.FleetId) == fleetID { + fleet = fleetData + break + } + } + + if fleet == nil { + return nil, ec2.FleetStateCodeDeleted, nil + } + + return fleet, aws.StringValue(fleet.FleetState), nil + } +} + +func expandEc2FleetLaunchTemplateConfigRequests(l []interface{}) []*ec2.FleetLaunchTemplateConfigRequest { + fleetLaunchTemplateConfigRequests := make([]*ec2.FleetLaunchTemplateConfigRequest, len(l)) + for i, m := range l { + if m == nil { + fleetLaunchTemplateConfigRequests[i] = &ec2.FleetLaunchTemplateConfigRequest{} + continue + } + + fleetLaunchTemplateConfigRequests[i] = expandEc2FleetLaunchTemplateConfigRequest(m.(map[string]interface{})) + } + return fleetLaunchTemplateConfigRequests +} + +func expandEc2FleetLaunchTemplateConfigRequest(m map[string]interface{}) *ec2.FleetLaunchTemplateConfigRequest { + fleetLaunchTemplateConfigRequest := &ec2.FleetLaunchTemplateConfigRequest{ + LaunchTemplateSpecification: expandEc2LaunchTemplateSpecificationRequest(m["launch_template_specification"].([]interface{})), + } + + if v, ok := m["override"]; ok { + fleetLaunchTemplateConfigRequest.Overrides = expandEc2FleetLaunchTemplateOverridesRequests(v.([]interface{})) + } + + return fleetLaunchTemplateConfigRequest +} + +func expandEc2FleetLaunchTemplateOverridesRequests(l []interface{}) []*ec2.FleetLaunchTemplateOverridesRequest { + if len(l) == 0 { + return nil + } + + fleetLaunchTemplateOverridesRequests := make([]*ec2.FleetLaunchTemplateOverridesRequest, len(l)) + for i, m := range l { + if m == nil { + fleetLaunchTemplateOverridesRequests[i] = &ec2.FleetLaunchTemplateOverridesRequest{} + continue + } + + fleetLaunchTemplateOverridesRequests[i] = expandEc2FleetLaunchTemplateOverridesRequest(m.(map[string]interface{})) + } + return fleetLaunchTemplateOverridesRequests +} + +func expandEc2FleetLaunchTemplateOverridesRequest(m map[string]interface{}) *ec2.FleetLaunchTemplateOverridesRequest { + fleetLaunchTemplateOverridesRequest := &ec2.FleetLaunchTemplateOverridesRequest{} + + if v, ok := m["availability_zone"]; ok && v.(string) != "" { + fleetLaunchTemplateOverridesRequest.AvailabilityZone = aws.String(v.(string)) + } + + if v, ok := m["instance_type"]; ok && v.(string) != "" { + fleetLaunchTemplateOverridesRequest.InstanceType = aws.String(v.(string)) + } + + if v, ok := m["max_price"]; ok && v.(string) != "" { + fleetLaunchTemplateOverridesRequest.MaxPrice = aws.String(v.(string)) + } + + if v, ok := m["priority"]; ok && v.(float64) != 0.0 { + fleetLaunchTemplateOverridesRequest.Priority = aws.Float64(v.(float64)) + } + + if v, ok := m["subnet_id"]; ok && v.(string) != "" { + fleetLaunchTemplateOverridesRequest.SubnetId = aws.String(v.(string)) + } + + if v, ok := m["weighted_capacity"]; ok && v.(float64) != 0.0 { + fleetLaunchTemplateOverridesRequest.WeightedCapacity = aws.Float64(v.(float64)) + } + + return fleetLaunchTemplateOverridesRequest +} + +func expandEc2LaunchTemplateSpecificationRequest(l []interface{}) *ec2.FleetLaunchTemplateSpecificationRequest { + fleetLaunchTemplateSpecificationRequest := &ec2.FleetLaunchTemplateSpecificationRequest{} + + if len(l) == 0 || l[0] == nil { + return fleetLaunchTemplateSpecificationRequest + } + + m := l[0].(map[string]interface{}) + + if v, ok := m["launch_template_id"]; ok && v.(string) != "" { + fleetLaunchTemplateSpecificationRequest.LaunchTemplateId = aws.String(v.(string)) + } + + if v, ok := m["launch_template_name"]; ok && v.(string) != "" { + fleetLaunchTemplateSpecificationRequest.LaunchTemplateName = aws.String(v.(string)) + } + + if v, ok := m["version"]; ok && v.(string) != "" { + fleetLaunchTemplateSpecificationRequest.Version = aws.String(v.(string)) + } + + return fleetLaunchTemplateSpecificationRequest +} + +func expandEc2OnDemandOptionsRequest(l []interface{}) *ec2.OnDemandOptionsRequest { + if len(l) == 0 || l[0] == nil { + return nil + } + + m := l[0].(map[string]interface{}) + + return &ec2.OnDemandOptionsRequest{ + AllocationStrategy: aws.String(m["allocation_strategy"].(string)), + } +} + +func expandEc2SpotOptionsRequest(l []interface{}) *ec2.SpotOptionsRequest { + if len(l) == 0 || l[0] == nil { + return nil + } + + m := l[0].(map[string]interface{}) + + spotOptionsRequest := &ec2.SpotOptionsRequest{ + AllocationStrategy: aws.String(m["allocation_strategy"].(string)), + InstanceInterruptionBehavior: aws.String(m["instance_interruption_behavior"].(string)), + } + + // InvalidFleetConfig: InstancePoolsToUseCount option is only available with the lowestPrice allocation strategy. + if aws.StringValue(spotOptionsRequest.AllocationStrategy) == "lowestPrice" { + spotOptionsRequest.InstancePoolsToUseCount = aws.Int64(int64(m["instance_pools_to_use_count"].(int))) + } + + return spotOptionsRequest +} + +func expandEc2TagSpecifications(m map[string]interface{}) []*ec2.TagSpecification { + if len(m) == 0 { + return nil + } + + return []*ec2.TagSpecification{ + { + ResourceType: aws.String("fleet"), + Tags: tagsFromMap(m), + }, + } +} + +func expandEc2TargetCapacitySpecificationRequest(l []interface{}) *ec2.TargetCapacitySpecificationRequest { + if len(l) == 0 || l[0] == nil { + return nil + } + + m := l[0].(map[string]interface{}) + + targetCapacitySpecificationrequest := &ec2.TargetCapacitySpecificationRequest{ + TotalTargetCapacity: aws.Int64(int64(m["total_target_capacity"].(int))), + } + + if v, ok := m["default_target_capacity_type"]; ok && v.(string) != "" { + targetCapacitySpecificationrequest.DefaultTargetCapacityType = aws.String(v.(string)) + } + + if v, ok := m["on_demand_target_capacity"]; ok && v.(int) != 0 { + targetCapacitySpecificationrequest.OnDemandTargetCapacity = aws.Int64(int64(v.(int))) + } + + if v, ok := m["spot_target_capacity"]; ok && v.(int) != 0 { + targetCapacitySpecificationrequest.SpotTargetCapacity = aws.Int64(int64(v.(int))) + } + + return targetCapacitySpecificationrequest +} + +func flattenEc2FleetLaunchTemplateConfigs(fleetLaunchTemplateConfigs []*ec2.FleetLaunchTemplateConfig) []interface{} { + l := make([]interface{}, len(fleetLaunchTemplateConfigs)) + + for i, fleetLaunchTemplateConfig := range fleetLaunchTemplateConfigs { + if fleetLaunchTemplateConfig == nil { + l[i] = map[string]interface{}{} + continue + } + m := map[string]interface{}{ + "launch_template_specification": flattenEc2FleetLaunchTemplateSpecification(fleetLaunchTemplateConfig.LaunchTemplateSpecification), + "override": flattenEc2FleetLaunchTemplateOverrides(fleetLaunchTemplateConfig.Overrides), + } + l[i] = m + } + + return l +} + +func flattenEc2FleetLaunchTemplateOverrides(fleetLaunchTemplateOverrides []*ec2.FleetLaunchTemplateOverrides) []interface{} { + l := make([]interface{}, len(fleetLaunchTemplateOverrides)) + + for i, fleetLaunchTemplateOverride := range fleetLaunchTemplateOverrides { + if fleetLaunchTemplateOverride == nil { + l[i] = map[string]interface{}{} + continue + } + m := map[string]interface{}{ + "availability_zone": aws.StringValue(fleetLaunchTemplateOverride.AvailabilityZone), + "instance_type": aws.StringValue(fleetLaunchTemplateOverride.InstanceType), + "max_price": aws.StringValue(fleetLaunchTemplateOverride.MaxPrice), + "priority": aws.Float64Value(fleetLaunchTemplateOverride.Priority), + "subnet_id": aws.StringValue(fleetLaunchTemplateOverride.SubnetId), + "weighted_capacity": aws.Float64Value(fleetLaunchTemplateOverride.WeightedCapacity), + } + l[i] = m + } + + return l +} + +func flattenEc2FleetLaunchTemplateSpecification(fleetLaunchTemplateSpecification *ec2.FleetLaunchTemplateSpecification) []interface{} { + if fleetLaunchTemplateSpecification == nil { + return []interface{}{} + } + + m := map[string]interface{}{ + "launch_template_id": aws.StringValue(fleetLaunchTemplateSpecification.LaunchTemplateId), + "launch_template_name": aws.StringValue(fleetLaunchTemplateSpecification.LaunchTemplateName), + "version": aws.StringValue(fleetLaunchTemplateSpecification.Version), + } + + return []interface{}{m} +} + +func flattenEc2OnDemandOptions(onDemandOptions *ec2.OnDemandOptions) []interface{} { + if onDemandOptions == nil { + return []interface{}{} + } + + m := map[string]interface{}{ + "allocation_strategy": aws.StringValue(onDemandOptions.AllocationStrategy), + } + + return []interface{}{m} +} + +func flattenEc2SpotOptions(spotOptions *ec2.SpotOptions) []interface{} { + if spotOptions == nil { + return []interface{}{} + } + + m := map[string]interface{}{ + "allocation_strategy": aws.StringValue(spotOptions.AllocationStrategy), + "instance_interruption_behavior": aws.StringValue(spotOptions.InstanceInterruptionBehavior), + "instance_pools_to_use_count": aws.Int64Value(spotOptions.InstancePoolsToUseCount), + } + + // API will omit InstancePoolsToUseCount if AllocationStrategy is diversified, which breaks our Default: 1 + // Here we just reset it to 1 to prevent removing the Default and setting up a special DiffSuppressFunc + if spotOptions.InstancePoolsToUseCount == nil && aws.StringValue(spotOptions.AllocationStrategy) == ec2.SpotAllocationStrategyDiversified { + m["instance_pools_to_use_count"] = 1 + } + + return []interface{}{m} +} + +func flattenEc2TargetCapacitySpecification(targetCapacitySpecification *ec2.TargetCapacitySpecification) []interface{} { + if targetCapacitySpecification == nil { + return []interface{}{} + } + + m := map[string]interface{}{ + "default_target_capacity_type": aws.StringValue(targetCapacitySpecification.DefaultTargetCapacityType), + "on_demand_target_capacity": aws.Int64Value(targetCapacitySpecification.OnDemandTargetCapacity), + "spot_target_capacity": aws.Int64Value(targetCapacitySpecification.SpotTargetCapacity), + "total_target_capacity": aws.Int64Value(targetCapacitySpecification.TotalTargetCapacity), + } + + return []interface{}{m} +} diff --git a/aws/resource_aws_ec2_fleet_test.go b/aws/resource_aws_ec2_fleet_test.go new file mode 100644 index 00000000000..61aa0111922 --- /dev/null +++ b/aws/resource_aws_ec2_fleet_test.go @@ -0,0 +1,1698 @@ +package aws + +import ( + "errors" + "fmt" + "strconv" + "testing" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/ec2" + "github.com/hashicorp/terraform/helper/acctest" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" +) + +func TestAccAWSEc2Fleet_basic(t *testing.T) { + var fleet1 ec2.FleetData + resourceName := "aws_ec2_fleet.test" + rName := acctest.RandomWithPrefix("tf-acc-test") + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSEc2FleetDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSEc2FleetConfig_TargetCapacitySpecification_DefaultTargetCapacityType(rName, "spot"), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSEc2FleetExists(resourceName, &fleet1), + resource.TestCheckResourceAttr(resourceName, "excess_capacity_termination_policy", "termination"), + resource.TestCheckResourceAttr(resourceName, "launch_template_config.#", "1"), + resource.TestCheckResourceAttr(resourceName, "launch_template_config.0.launch_template_specification.#", "1"), + resource.TestCheckResourceAttrSet(resourceName, "launch_template_config.0.launch_template_specification.0.launch_template_id"), + resource.TestCheckResourceAttrSet(resourceName, "launch_template_config.0.launch_template_specification.0.version"), + resource.TestCheckResourceAttr(resourceName, "launch_template_config.0.override.#", "0"), + resource.TestCheckResourceAttr(resourceName, "on_demand_options.#", "1"), + resource.TestCheckResourceAttr(resourceName, "on_demand_options.0.allocation_strategy", "lowestPrice"), + resource.TestCheckResourceAttr(resourceName, "replace_unhealthy_instances", "false"), + resource.TestCheckResourceAttr(resourceName, "spot_options.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spot_options.0.allocation_strategy", "lowestPrice"), + resource.TestCheckResourceAttr(resourceName, "spot_options.0.instance_interruption_behavior", "terminate"), + resource.TestCheckResourceAttr(resourceName, "spot_options.0.instance_pools_to_use_count", "1"), + resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), + resource.TestCheckResourceAttr(resourceName, "target_capacity_specification.#", "1"), + resource.TestCheckResourceAttr(resourceName, "target_capacity_specification.0.default_target_capacity_type", "spot"), + resource.TestCheckResourceAttr(resourceName, "target_capacity_specification.0.total_target_capacity", "0"), + resource.TestCheckResourceAttr(resourceName, "terminate_instances", "false"), + resource.TestCheckResourceAttr(resourceName, "terminate_instances_with_expiration", "false"), + resource.TestCheckResourceAttr(resourceName, "type", "maintain"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"terminate_instances"}, + }, + }, + }) +} + +func TestAccAWSEc2Fleet_disappears(t *testing.T) { + var fleet1 ec2.FleetData + resourceName := "aws_ec2_fleet.test" + rName := acctest.RandomWithPrefix("tf-acc-test") + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSEc2FleetDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSEc2FleetConfig_TargetCapacitySpecification_DefaultTargetCapacityType(rName, "spot"), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSEc2FleetExists(resourceName, &fleet1), + testAccCheckAWSEc2FleetDisappears(&fleet1), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + +func TestAccAWSEc2Fleet_ExcessCapacityTerminationPolicy(t *testing.T) { + var fleet1, fleet2 ec2.FleetData + resourceName := "aws_ec2_fleet.test" + rName := acctest.RandomWithPrefix("tf-acc-test") + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSEc2FleetDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSEc2FleetConfig_ExcessCapacityTerminationPolicy(rName, "no-termination"), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSEc2FleetExists(resourceName, &fleet1), + resource.TestCheckResourceAttr(resourceName, "excess_capacity_termination_policy", "no-termination"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"terminate_instances"}, + }, + { + Config: testAccAWSEc2FleetConfig_ExcessCapacityTerminationPolicy(rName, "termination"), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSEc2FleetExists(resourceName, &fleet2), + testAccCheckAWSEc2FleetNotRecreated(&fleet1, &fleet2), + resource.TestCheckResourceAttr(resourceName, "excess_capacity_termination_policy", "termination"), + ), + }, + }, + }) +} + +func TestAccAWSEc2Fleet_LaunchTemplateConfig_LaunchTemplateSpecification_LaunchTemplateId(t *testing.T) { + var fleet1, fleet2 ec2.FleetData + launchTemplateResourceName1 := "aws_launch_template.test1" + launchTemplateResourceName2 := "aws_launch_template.test2" + resourceName := "aws_ec2_fleet.test" + rName := acctest.RandomWithPrefix("tf-acc-test") + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSEc2FleetDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSEc2FleetConfig_LaunchTemplateConfig_LaunchTemplateSpecification_LaunchTemplateId(rName, launchTemplateResourceName1), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSEc2FleetExists(resourceName, &fleet1), + resource.TestCheckResourceAttr(resourceName, "launch_template_config.#", "1"), + resource.TestCheckResourceAttr(resourceName, "launch_template_config.0.launch_template_specification.#", "1"), + resource.TestCheckResourceAttrPair(resourceName, "launch_template_config.0.launch_template_specification.0.launch_template_id", launchTemplateResourceName1, "id"), + resource.TestCheckResourceAttrPair(resourceName, "launch_template_config.0.launch_template_specification.0.version", launchTemplateResourceName1, "latest_version"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"terminate_instances"}, + }, + { + Config: testAccAWSEc2FleetConfig_LaunchTemplateConfig_LaunchTemplateSpecification_LaunchTemplateId(rName, launchTemplateResourceName2), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSEc2FleetExists(resourceName, &fleet1), + testAccCheckAWSEc2FleetRecreated(&fleet1, &fleet2), + resource.TestCheckResourceAttr(resourceName, "launch_template_config.#", "1"), + resource.TestCheckResourceAttr(resourceName, "launch_template_config.0.launch_template_specification.#", "1"), + resource.TestCheckResourceAttrPair(resourceName, "launch_template_config.0.launch_template_specification.0.launch_template_id", launchTemplateResourceName2, "id"), + resource.TestCheckResourceAttrPair(resourceName, "launch_template_config.0.launch_template_specification.0.version", launchTemplateResourceName2, "latest_version"), + ), + }, + }, + }) +} + +func TestAccAWSEc2Fleet_LaunchTemplateConfig_LaunchTemplateSpecification_LaunchTemplateName(t *testing.T) { + var fleet1, fleet2 ec2.FleetData + launchTemplateResourceName1 := "aws_launch_template.test1" + launchTemplateResourceName2 := "aws_launch_template.test2" + resourceName := "aws_ec2_fleet.test" + rName := acctest.RandomWithPrefix("tf-acc-test") + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSEc2FleetDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSEc2FleetConfig_LaunchTemplateConfig_LaunchTemplateSpecification_LaunchTemplateName(rName, launchTemplateResourceName1), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSEc2FleetExists(resourceName, &fleet1), + resource.TestCheckResourceAttr(resourceName, "launch_template_config.#", "1"), + resource.TestCheckResourceAttr(resourceName, "launch_template_config.0.launch_template_specification.#", "1"), + resource.TestCheckResourceAttrPair(resourceName, "launch_template_config.0.launch_template_specification.0.launch_template_name", launchTemplateResourceName1, "name"), + resource.TestCheckResourceAttrPair(resourceName, "launch_template_config.0.launch_template_specification.0.version", launchTemplateResourceName1, "latest_version"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"terminate_instances"}, + }, + { + Config: testAccAWSEc2FleetConfig_LaunchTemplateConfig_LaunchTemplateSpecification_LaunchTemplateName(rName, launchTemplateResourceName2), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSEc2FleetExists(resourceName, &fleet1), + testAccCheckAWSEc2FleetRecreated(&fleet1, &fleet2), + resource.TestCheckResourceAttr(resourceName, "launch_template_config.#", "1"), + resource.TestCheckResourceAttr(resourceName, "launch_template_config.0.launch_template_specification.#", "1"), + resource.TestCheckResourceAttrPair(resourceName, "launch_template_config.0.launch_template_specification.0.launch_template_name", launchTemplateResourceName2, "name"), + resource.TestCheckResourceAttrPair(resourceName, "launch_template_config.0.launch_template_specification.0.version", launchTemplateResourceName2, "latest_version"), + ), + }, + }, + }) +} + +func TestAccAWSEc2Fleet_LaunchTemplateConfig_LaunchTemplateSpecification_Version(t *testing.T) { + var fleet1, fleet2 ec2.FleetData + var launchTemplate ec2.LaunchTemplate + launchTemplateResourceName := "aws_launch_template.test" + resourceName := "aws_ec2_fleet.test" + rName := acctest.RandomWithPrefix("tf-acc-test") + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSEc2FleetDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSEc2FleetConfig_LaunchTemplateConfig_LaunchTemplateSpecification_Version(rName, "t3.micro"), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSLaunchTemplateExists(launchTemplateResourceName, &launchTemplate), + resource.TestCheckResourceAttr(launchTemplateResourceName, "instance_type", "t3.micro"), + resource.TestCheckResourceAttr(launchTemplateResourceName, "latest_version", "1"), + testAccCheckAWSEc2FleetExists(resourceName, &fleet1), + resource.TestCheckResourceAttr(resourceName, "launch_template_config.#", "1"), + resource.TestCheckResourceAttr(resourceName, "launch_template_config.0.launch_template_specification.#", "1"), + resource.TestCheckResourceAttrPair(resourceName, "launch_template_config.0.launch_template_specification.0.launch_template_id", launchTemplateResourceName, "id"), + resource.TestCheckResourceAttrPair(resourceName, "launch_template_config.0.launch_template_specification.0.version", launchTemplateResourceName, "latest_version"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"terminate_instances"}, + }, + { + Config: testAccAWSEc2FleetConfig_LaunchTemplateConfig_LaunchTemplateSpecification_Version(rName, "t3.small"), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSLaunchTemplateExists(launchTemplateResourceName, &launchTemplate), + resource.TestCheckResourceAttr(launchTemplateResourceName, "instance_type", "t3.small"), + resource.TestCheckResourceAttr(launchTemplateResourceName, "latest_version", "2"), + testAccCheckAWSEc2FleetExists(resourceName, &fleet1), + testAccCheckAWSEc2FleetRecreated(&fleet1, &fleet2), + resource.TestCheckResourceAttr(resourceName, "launch_template_config.#", "1"), + resource.TestCheckResourceAttr(resourceName, "launch_template_config.0.launch_template_specification.#", "1"), + resource.TestCheckResourceAttrPair(resourceName, "launch_template_config.0.launch_template_specification.0.launch_template_id", launchTemplateResourceName, "id"), + resource.TestCheckResourceAttrPair(resourceName, "launch_template_config.0.launch_template_specification.0.version", launchTemplateResourceName, "latest_version"), + ), + }, + }, + }) +} + +func TestAccAWSEc2Fleet_LaunchTemplateConfig_Override_AvailabilityZone(t *testing.T) { + var fleet1, fleet2 ec2.FleetData + availabilityZonesDataSourceName := "data.aws_availability_zones.available" + resourceName := "aws_ec2_fleet.test" + rName := acctest.RandomWithPrefix("tf-acc-test") + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSEc2FleetDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSEc2FleetConfig_LaunchTemplateConfig_Override_AvailabilityZone(rName, 0), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSEc2FleetExists(resourceName, &fleet1), + resource.TestCheckResourceAttr(resourceName, "launch_template_config.#", "1"), + resource.TestCheckResourceAttr(resourceName, "launch_template_config.0.override.#", "1"), + resource.TestCheckResourceAttrPair(resourceName, "launch_template_config.0.override.0.availability_zone", availabilityZonesDataSourceName, "names.0"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"terminate_instances"}, + }, + { + Config: testAccAWSEc2FleetConfig_LaunchTemplateConfig_Override_AvailabilityZone(rName, 1), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSEc2FleetExists(resourceName, &fleet1), + testAccCheckAWSEc2FleetRecreated(&fleet1, &fleet2), + resource.TestCheckResourceAttr(resourceName, "launch_template_config.#", "1"), + resource.TestCheckResourceAttr(resourceName, "launch_template_config.0.override.#", "1"), + resource.TestCheckResourceAttrPair(resourceName, "launch_template_config.0.override.0.availability_zone", availabilityZonesDataSourceName, "names.1"), + ), + }, + }, + }) +} + +func TestAccAWSEc2Fleet_LaunchTemplateConfig_Override_InstanceType(t *testing.T) { + var fleet1, fleet2 ec2.FleetData + resourceName := "aws_ec2_fleet.test" + rName := acctest.RandomWithPrefix("tf-acc-test") + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSEc2FleetDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSEc2FleetConfig_LaunchTemplateConfig_Override_InstanceType(rName, "t3.small"), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSEc2FleetExists(resourceName, &fleet1), + resource.TestCheckResourceAttr(resourceName, "launch_template_config.#", "1"), + resource.TestCheckResourceAttr(resourceName, "launch_template_config.0.override.#", "1"), + resource.TestCheckResourceAttr(resourceName, "launch_template_config.0.override.0.instance_type", "t3.small"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"terminate_instances"}, + }, + { + Config: testAccAWSEc2FleetConfig_LaunchTemplateConfig_Override_InstanceType(rName, "t3.medium"), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSEc2FleetExists(resourceName, &fleet1), + testAccCheckAWSEc2FleetRecreated(&fleet1, &fleet2), + resource.TestCheckResourceAttr(resourceName, "launch_template_config.#", "1"), + resource.TestCheckResourceAttr(resourceName, "launch_template_config.0.override.#", "1"), + resource.TestCheckResourceAttr(resourceName, "launch_template_config.0.override.0.instance_type", "t3.medium"), + ), + }, + }, + }) +} + +func TestAccAWSEc2Fleet_LaunchTemplateConfig_Override_MaxPrice(t *testing.T) { + t.Skip("EC2 API is not correctly returning MaxPrice override") + + var fleet1, fleet2 ec2.FleetData + resourceName := "aws_ec2_fleet.test" + rName := acctest.RandomWithPrefix("tf-acc-test") + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSEc2FleetDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSEc2FleetConfig_LaunchTemplateConfig_Override_MaxPrice(rName, "1.01"), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSEc2FleetExists(resourceName, &fleet1), + resource.TestCheckResourceAttr(resourceName, "launch_template_config.#", "1"), + resource.TestCheckResourceAttr(resourceName, "launch_template_config.0.override.#", "1"), + resource.TestCheckResourceAttr(resourceName, "launch_template_config.0.override.0.max_price", "1.01"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"terminate_instances"}, + }, + { + Config: testAccAWSEc2FleetConfig_LaunchTemplateConfig_Override_MaxPrice(rName, "1.02"), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSEc2FleetExists(resourceName, &fleet1), + testAccCheckAWSEc2FleetRecreated(&fleet1, &fleet2), + resource.TestCheckResourceAttr(resourceName, "launch_template_config.#", "1"), + resource.TestCheckResourceAttr(resourceName, "launch_template_config.0.override.#", "1"), + resource.TestCheckResourceAttr(resourceName, "launch_template_config.0.override.0.max_price", "1.02"), + ), + }, + }, + }) +} + +func TestAccAWSEc2Fleet_LaunchTemplateConfig_Override_Priority(t *testing.T) { + var fleet1, fleet2 ec2.FleetData + resourceName := "aws_ec2_fleet.test" + rName := acctest.RandomWithPrefix("tf-acc-test") + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSEc2FleetDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSEc2FleetConfig_LaunchTemplateConfig_Override_Priority(rName, 1), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSEc2FleetExists(resourceName, &fleet1), + resource.TestCheckResourceAttr(resourceName, "launch_template_config.#", "1"), + resource.TestCheckResourceAttr(resourceName, "launch_template_config.0.override.#", "1"), + resource.TestCheckResourceAttr(resourceName, "launch_template_config.0.override.0.priority", "1"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"terminate_instances"}, + }, + { + Config: testAccAWSEc2FleetConfig_LaunchTemplateConfig_Override_Priority(rName, 2), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSEc2FleetExists(resourceName, &fleet1), + testAccCheckAWSEc2FleetRecreated(&fleet1, &fleet2), + resource.TestCheckResourceAttr(resourceName, "launch_template_config.#", "1"), + resource.TestCheckResourceAttr(resourceName, "launch_template_config.0.override.#", "1"), + resource.TestCheckResourceAttr(resourceName, "launch_template_config.0.override.0.priority", "2"), + ), + }, + }, + }) +} + +func TestAccAWSEc2Fleet_LaunchTemplateConfig_Override_Priority_Multiple(t *testing.T) { + var fleet1, fleet2 ec2.FleetData + resourceName := "aws_ec2_fleet.test" + rName := acctest.RandomWithPrefix("tf-acc-test") + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSEc2FleetDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSEc2FleetConfig_LaunchTemplateConfig_Override_Priority_Multiple(rName, 1, 2), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSEc2FleetExists(resourceName, &fleet1), + resource.TestCheckResourceAttr(resourceName, "launch_template_config.#", "1"), + resource.TestCheckResourceAttr(resourceName, "launch_template_config.0.override.#", "2"), + resource.TestCheckResourceAttr(resourceName, "launch_template_config.0.override.0.priority", "1"), + resource.TestCheckResourceAttr(resourceName, "launch_template_config.0.override.1.priority", "2"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"terminate_instances"}, + }, + { + Config: testAccAWSEc2FleetConfig_LaunchTemplateConfig_Override_Priority_Multiple(rName, 2, 1), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSEc2FleetExists(resourceName, &fleet1), + testAccCheckAWSEc2FleetRecreated(&fleet1, &fleet2), + resource.TestCheckResourceAttr(resourceName, "launch_template_config.#", "1"), + resource.TestCheckResourceAttr(resourceName, "launch_template_config.0.override.#", "2"), + resource.TestCheckResourceAttr(resourceName, "launch_template_config.0.override.0.priority", "2"), + resource.TestCheckResourceAttr(resourceName, "launch_template_config.0.override.1.priority", "1"), + ), + }, + }, + }) +} + +func TestAccAWSEc2Fleet_LaunchTemplateConfig_Override_SubnetId(t *testing.T) { + var fleet1, fleet2 ec2.FleetData + subnetResourceName1 := "aws_subnet.test.0" + subnetResourceName2 := "aws_subnet.test.1" + resourceName := "aws_ec2_fleet.test" + rName := acctest.RandomWithPrefix("tf-acc-test") + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSEc2FleetDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSEc2FleetConfig_LaunchTemplateConfig_Override_SubnetId(rName, 0), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSEc2FleetExists(resourceName, &fleet1), + resource.TestCheckResourceAttr(resourceName, "launch_template_config.#", "1"), + resource.TestCheckResourceAttr(resourceName, "launch_template_config.0.override.#", "1"), + resource.TestCheckResourceAttrPair(resourceName, "launch_template_config.0.override.0.subnet_id", subnetResourceName1, "id"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"terminate_instances"}, + }, + { + Config: testAccAWSEc2FleetConfig_LaunchTemplateConfig_Override_SubnetId(rName, 1), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSEc2FleetExists(resourceName, &fleet1), + testAccCheckAWSEc2FleetRecreated(&fleet1, &fleet2), + resource.TestCheckResourceAttr(resourceName, "launch_template_config.#", "1"), + resource.TestCheckResourceAttr(resourceName, "launch_template_config.0.override.#", "1"), + resource.TestCheckResourceAttrPair(resourceName, "launch_template_config.0.override.0.subnet_id", subnetResourceName2, "id"), + ), + }, + }, + }) +} + +func TestAccAWSEc2Fleet_LaunchTemplateConfig_Override_WeightedCapacity(t *testing.T) { + var fleet1, fleet2 ec2.FleetData + resourceName := "aws_ec2_fleet.test" + rName := acctest.RandomWithPrefix("tf-acc-test") + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSEc2FleetDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSEc2FleetConfig_LaunchTemplateConfig_Override_WeightedCapacity(rName, 1), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSEc2FleetExists(resourceName, &fleet1), + resource.TestCheckResourceAttr(resourceName, "launch_template_config.#", "1"), + resource.TestCheckResourceAttr(resourceName, "launch_template_config.0.override.#", "1"), + resource.TestCheckResourceAttr(resourceName, "launch_template_config.0.override.0.weighted_capacity", "1"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"terminate_instances"}, + }, + { + Config: testAccAWSEc2FleetConfig_LaunchTemplateConfig_Override_WeightedCapacity(rName, 2), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSEc2FleetExists(resourceName, &fleet1), + testAccCheckAWSEc2FleetRecreated(&fleet1, &fleet2), + resource.TestCheckResourceAttr(resourceName, "launch_template_config.#", "1"), + resource.TestCheckResourceAttr(resourceName, "launch_template_config.0.override.#", "1"), + resource.TestCheckResourceAttr(resourceName, "launch_template_config.0.override.0.weighted_capacity", "2"), + ), + }, + }, + }) +} + +func TestAccAWSEc2Fleet_LaunchTemplateConfig_Override_WeightedCapacity_Multiple(t *testing.T) { + var fleet1, fleet2 ec2.FleetData + resourceName := "aws_ec2_fleet.test" + rName := acctest.RandomWithPrefix("tf-acc-test") + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSEc2FleetDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSEc2FleetConfig_LaunchTemplateConfig_Override_WeightedCapacity_Multiple(rName, 1, 1), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSEc2FleetExists(resourceName, &fleet1), + resource.TestCheckResourceAttr(resourceName, "launch_template_config.#", "1"), + resource.TestCheckResourceAttr(resourceName, "launch_template_config.0.override.#", "2"), + resource.TestCheckResourceAttr(resourceName, "launch_template_config.0.override.0.weighted_capacity", "1"), + resource.TestCheckResourceAttr(resourceName, "launch_template_config.0.override.1.weighted_capacity", "1"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"terminate_instances"}, + }, + { + Config: testAccAWSEc2FleetConfig_LaunchTemplateConfig_Override_WeightedCapacity_Multiple(rName, 1, 2), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSEc2FleetExists(resourceName, &fleet1), + testAccCheckAWSEc2FleetRecreated(&fleet1, &fleet2), + resource.TestCheckResourceAttr(resourceName, "launch_template_config.#", "1"), + resource.TestCheckResourceAttr(resourceName, "launch_template_config.0.override.#", "2"), + resource.TestCheckResourceAttr(resourceName, "launch_template_config.0.override.0.weighted_capacity", "1"), + resource.TestCheckResourceAttr(resourceName, "launch_template_config.0.override.1.weighted_capacity", "2"), + ), + }, + }, + }) +} + +func TestAccAWSEc2Fleet_OnDemandOptions_AllocationStrategy(t *testing.T) { + var fleet1, fleet2 ec2.FleetData + resourceName := "aws_ec2_fleet.test" + rName := acctest.RandomWithPrefix("tf-acc-test") + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSEc2FleetDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSEc2FleetConfig_OnDemandOptions_AllocationStrategy(rName, "prioritized"), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSEc2FleetExists(resourceName, &fleet1), + resource.TestCheckResourceAttr(resourceName, "on_demand_options.#", "1"), + resource.TestCheckResourceAttr(resourceName, "on_demand_options.0.allocation_strategy", "prioritized"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"terminate_instances"}, + }, + { + Config: testAccAWSEc2FleetConfig_OnDemandOptions_AllocationStrategy(rName, "lowestPrice"), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSEc2FleetExists(resourceName, &fleet2), + testAccCheckAWSEc2FleetRecreated(&fleet1, &fleet2), + resource.TestCheckResourceAttr(resourceName, "on_demand_options.#", "1"), + resource.TestCheckResourceAttr(resourceName, "on_demand_options.0.allocation_strategy", "lowestPrice"), + ), + }, + }, + }) +} + +func TestAccAWSEc2Fleet_ReplaceUnhealthyInstances(t *testing.T) { + var fleet1, fleet2 ec2.FleetData + resourceName := "aws_ec2_fleet.test" + rName := acctest.RandomWithPrefix("tf-acc-test") + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSEc2FleetDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSEc2FleetConfig_ReplaceUnhealthyInstances(rName, true), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSEc2FleetExists(resourceName, &fleet1), + resource.TestCheckResourceAttr(resourceName, "replace_unhealthy_instances", "true"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"terminate_instances"}, + }, + { + Config: testAccAWSEc2FleetConfig_ReplaceUnhealthyInstances(rName, false), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSEc2FleetExists(resourceName, &fleet2), + testAccCheckAWSEc2FleetRecreated(&fleet1, &fleet2), + resource.TestCheckResourceAttr(resourceName, "replace_unhealthy_instances", "false"), + ), + }, + }, + }) +} + +func TestAccAWSEc2Fleet_SpotOptions_AllocationStrategy(t *testing.T) { + var fleet1, fleet2 ec2.FleetData + resourceName := "aws_ec2_fleet.test" + rName := acctest.RandomWithPrefix("tf-acc-test") + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSEc2FleetDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSEc2FleetConfig_SpotOptions_AllocationStrategy(rName, "diversified"), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSEc2FleetExists(resourceName, &fleet1), + resource.TestCheckResourceAttr(resourceName, "spot_options.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spot_options.0.allocation_strategy", "diversified"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"terminate_instances"}, + }, + { + Config: testAccAWSEc2FleetConfig_SpotOptions_AllocationStrategy(rName, "lowestPrice"), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSEc2FleetExists(resourceName, &fleet2), + testAccCheckAWSEc2FleetRecreated(&fleet1, &fleet2), + resource.TestCheckResourceAttr(resourceName, "spot_options.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spot_options.0.allocation_strategy", "lowestPrice"), + ), + }, + }, + }) +} + +func TestAccAWSEc2Fleet_SpotOptions_InstanceInterruptionBehavior(t *testing.T) { + var fleet1, fleet2 ec2.FleetData + resourceName := "aws_ec2_fleet.test" + rName := acctest.RandomWithPrefix("tf-acc-test") + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSEc2FleetDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSEc2FleetConfig_SpotOptions_InstanceInterruptionBehavior(rName, "stop"), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSEc2FleetExists(resourceName, &fleet1), + resource.TestCheckResourceAttr(resourceName, "spot_options.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spot_options.0.instance_interruption_behavior", "stop"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"terminate_instances"}, + }, + { + Config: testAccAWSEc2FleetConfig_SpotOptions_InstanceInterruptionBehavior(rName, "terminate"), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSEc2FleetExists(resourceName, &fleet2), + testAccCheckAWSEc2FleetRecreated(&fleet1, &fleet2), + resource.TestCheckResourceAttr(resourceName, "spot_options.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spot_options.0.instance_interruption_behavior", "terminate"), + ), + }, + }, + }) +} + +func TestAccAWSEc2Fleet_SpotOptions_InstancePoolsToUseCount(t *testing.T) { + var fleet1, fleet2 ec2.FleetData + resourceName := "aws_ec2_fleet.test" + rName := acctest.RandomWithPrefix("tf-acc-test") + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSEc2FleetDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSEc2FleetConfig_SpotOptions_InstancePoolsToUseCount(rName, 2), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSEc2FleetExists(resourceName, &fleet1), + resource.TestCheckResourceAttr(resourceName, "spot_options.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spot_options.0.instance_pools_to_use_count", "2"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"terminate_instances"}, + }, + { + Config: testAccAWSEc2FleetConfig_SpotOptions_InstancePoolsToUseCount(rName, 3), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSEc2FleetExists(resourceName, &fleet2), + testAccCheckAWSEc2FleetRecreated(&fleet1, &fleet2), + resource.TestCheckResourceAttr(resourceName, "spot_options.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spot_options.0.instance_pools_to_use_count", "3"), + ), + }, + }, + }) +} + +func TestAccAWSEc2Fleet_Tags(t *testing.T) { + var fleet1, fleet2 ec2.FleetData + resourceName := "aws_ec2_fleet.test" + rName := acctest.RandomWithPrefix("tf-acc-test") + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSEc2FleetDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSEc2FleetConfig_Tags(rName, "key1", "value1"), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSEc2FleetExists(resourceName, &fleet1), + resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), + resource.TestCheckResourceAttr(resourceName, "tags.key1", "value1"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"terminate_instances"}, + }, + { + Config: testAccAWSEc2FleetConfig_Tags(rName, "key1", "value1updated"), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSEc2FleetExists(resourceName, &fleet2), + testAccCheckAWSEc2FleetRecreated(&fleet1, &fleet2), + resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), + resource.TestCheckResourceAttr(resourceName, "tags.key1", "value1updated"), + ), + }, + }, + }) +} + +func TestAccAWSEc2Fleet_TargetCapacitySpecification_DefaultTargetCapacityType(t *testing.T) { + var fleet1, fleet2 ec2.FleetData + resourceName := "aws_ec2_fleet.test" + rName := acctest.RandomWithPrefix("tf-acc-test") + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSEc2FleetDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSEc2FleetConfig_TargetCapacitySpecification_DefaultTargetCapacityType(rName, "on-demand"), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSEc2FleetExists(resourceName, &fleet1), + resource.TestCheckResourceAttr(resourceName, "target_capacity_specification.#", "1"), + resource.TestCheckResourceAttr(resourceName, "target_capacity_specification.0.default_target_capacity_type", "on-demand"), + ), + }, + { + Config: testAccAWSEc2FleetConfig_TargetCapacitySpecification_DefaultTargetCapacityType(rName, "spot"), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSEc2FleetExists(resourceName, &fleet2), + testAccCheckAWSEc2FleetRecreated(&fleet1, &fleet2), + resource.TestCheckResourceAttr(resourceName, "target_capacity_specification.#", "1"), + resource.TestCheckResourceAttr(resourceName, "target_capacity_specification.0.default_target_capacity_type", "spot"), + ), + }, + }, + }) +} + +func TestAccAWSEc2Fleet_TargetCapacitySpecification_DefaultTargetCapacityType_OnDemand(t *testing.T) { + var fleet1 ec2.FleetData + resourceName := "aws_ec2_fleet.test" + rName := acctest.RandomWithPrefix("tf-acc-test") + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSEc2FleetDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSEc2FleetConfig_TargetCapacitySpecification_DefaultTargetCapacityType(rName, "on-demand"), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSEc2FleetExists(resourceName, &fleet1), + resource.TestCheckResourceAttr(resourceName, "target_capacity_specification.#", "1"), + resource.TestCheckResourceAttr(resourceName, "target_capacity_specification.0.default_target_capacity_type", "on-demand"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"terminate_instances"}, + }, + }, + }) +} + +func TestAccAWSEc2Fleet_TargetCapacitySpecification_DefaultTargetCapacityType_Spot(t *testing.T) { + var fleet1 ec2.FleetData + resourceName := "aws_ec2_fleet.test" + rName := acctest.RandomWithPrefix("tf-acc-test") + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSEc2FleetDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSEc2FleetConfig_TargetCapacitySpecification_DefaultTargetCapacityType(rName, "spot"), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSEc2FleetExists(resourceName, &fleet1), + resource.TestCheckResourceAttr(resourceName, "target_capacity_specification.#", "1"), + resource.TestCheckResourceAttr(resourceName, "target_capacity_specification.0.default_target_capacity_type", "spot"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"terminate_instances"}, + }, + }, + }) +} + +func TestAccAWSEc2Fleet_TargetCapacitySpecification_TotalTargetCapacity(t *testing.T) { + var fleet1, fleet2 ec2.FleetData + resourceName := "aws_ec2_fleet.test" + rName := acctest.RandomWithPrefix("tf-acc-test") + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSEc2FleetDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSEc2FleetConfig_TargetCapacitySpecification_TotalTargetCapacity(rName, 1), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSEc2FleetExists(resourceName, &fleet1), + resource.TestCheckResourceAttr(resourceName, "target_capacity_specification.#", "1"), + resource.TestCheckResourceAttr(resourceName, "target_capacity_specification.0.total_target_capacity", "1"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"terminate_instances"}, + }, + { + Config: testAccAWSEc2FleetConfig_TargetCapacitySpecification_TotalTargetCapacity(rName, 2), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSEc2FleetExists(resourceName, &fleet2), + testAccCheckAWSEc2FleetNotRecreated(&fleet1, &fleet2), + resource.TestCheckResourceAttr(resourceName, "target_capacity_specification.#", "1"), + resource.TestCheckResourceAttr(resourceName, "target_capacity_specification.0.total_target_capacity", "2"), + ), + }, + }, + }) +} + +func TestAccAWSEc2Fleet_TerminateInstancesWithExpiration(t *testing.T) { + var fleet1, fleet2 ec2.FleetData + resourceName := "aws_ec2_fleet.test" + rName := acctest.RandomWithPrefix("tf-acc-test") + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSEc2FleetDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSEc2FleetConfig_TerminateInstancesWithExpiration(rName, true), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSEc2FleetExists(resourceName, &fleet1), + resource.TestCheckResourceAttr(resourceName, "terminate_instances_with_expiration", "true"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"terminate_instances"}, + }, + { + Config: testAccAWSEc2FleetConfig_TerminateInstancesWithExpiration(rName, false), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSEc2FleetExists(resourceName, &fleet2), + testAccCheckAWSEc2FleetRecreated(&fleet1, &fleet2), + resource.TestCheckResourceAttr(resourceName, "terminate_instances_with_expiration", "false"), + ), + }, + }, + }) +} + +func TestAccAWSEc2Fleet_Type(t *testing.T) { + var fleet1 ec2.FleetData + resourceName := "aws_ec2_fleet.test" + rName := acctest.RandomWithPrefix("tf-acc-test") + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSEc2FleetDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSEc2FleetConfig_Type(rName, "maintain"), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSEc2FleetExists(resourceName, &fleet1), + resource.TestCheckResourceAttr(resourceName, "type", "maintain"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"terminate_instances"}, + }, + // This configuration will fulfill immediately, skip until ValidFrom is implemented + // { + // Config: testAccAWSEc2FleetConfig_Type(rName, "request"), + // Check: resource.ComposeTestCheckFunc( + // testAccCheckAWSEc2FleetExists(resourceName, &fleet2), + // testAccCheckAWSEc2FleetRecreated(&fleet1, &fleet2), + // resource.TestCheckResourceAttr(resourceName, "type", "request"), + // ), + // }, + }, + }) +} + +func testAccCheckAWSEc2FleetExists(resourceName string, fleet *ec2.FleetData) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[resourceName] + if !ok { + return fmt.Errorf("Not found: %s", resourceName) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("No EC2 Fleet ID is set") + } + + conn := testAccProvider.Meta().(*AWSClient).ec2conn + + input := &ec2.DescribeFleetsInput{ + FleetIds: []*string{aws.String(rs.Primary.ID)}, + } + + output, err := conn.DescribeFleets(input) + + if err != nil { + return err + } + + if output == nil { + return fmt.Errorf("EC2 Fleet not found") + } + + for _, fleetData := range output.Fleets { + if fleetData == nil { + continue + } + if aws.StringValue(fleetData.FleetId) != rs.Primary.ID { + continue + } + *fleet = *fleetData + break + } + + if fleet == nil { + return fmt.Errorf("EC2 Fleet not found") + } + + return nil + } +} + +func testAccCheckAWSEc2FleetDestroy(s *terraform.State) error { + conn := testAccProvider.Meta().(*AWSClient).ec2conn + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_ec2_fleet" { + continue + } + + input := &ec2.DescribeFleetsInput{ + FleetIds: []*string{aws.String(rs.Primary.ID)}, + } + + output, err := conn.DescribeFleets(input) + + if isAWSErr(err, "InvalidFleetId.NotFound", "") { + continue + } + + if err != nil { + return err + } + + if output == nil { + continue + } + + for _, fleetData := range output.Fleets { + if fleetData == nil { + continue + } + if aws.StringValue(fleetData.FleetId) != rs.Primary.ID { + continue + } + if aws.StringValue(fleetData.FleetState) == ec2.FleetStateCodeDeleted { + break + } + terminateInstances, err := strconv.ParseBool(rs.Primary.Attributes["terminate_instances"]) + if err != nil { + return fmt.Errorf("error converting terminate_instances (%s) to bool: %s", rs.Primary.Attributes["terminate_instances"], err) + } + if !terminateInstances && aws.StringValue(fleetData.FleetState) == ec2.FleetStateCodeDeletedRunning { + break + } + // AWS SDK constant is incorrect + if !terminateInstances && aws.StringValue(fleetData.FleetState) == "deleted_running" { + break + } + return fmt.Errorf("EC2 Fleet (%s) still exists in non-deleted (%s) state", rs.Primary.ID, aws.StringValue(fleetData.FleetState)) + } + } + + return nil +} + +func testAccCheckAWSEc2FleetDisappears(fleet *ec2.FleetData) resource.TestCheckFunc { + return func(s *terraform.State) error { + conn := testAccProvider.Meta().(*AWSClient).ec2conn + + input := &ec2.DeleteFleetsInput{ + FleetIds: []*string{fleet.FleetId}, + TerminateInstances: aws.Bool(false), + } + + _, err := conn.DeleteFleets(input) + + return err + } +} + +func testAccCheckAWSEc2FleetNotRecreated(i, j *ec2.FleetData) resource.TestCheckFunc { + return func(s *terraform.State) error { + if aws.TimeValue(i.CreateTime) != aws.TimeValue(j.CreateTime) { + return errors.New("EC2 Fleet was recreated") + } + + return nil + } +} + +func testAccCheckAWSEc2FleetRecreated(i, j *ec2.FleetData) resource.TestCheckFunc { + return func(s *terraform.State) error { + if aws.TimeValue(i.CreateTime) == aws.TimeValue(j.CreateTime) { + return errors.New("EC2 Fleet was not recreated") + } + + return nil + } +} + +func testAccAWSEc2FleetConfig_BaseLaunchTemplate(rName string) string { + return fmt.Sprintf(` +data "aws_ami" "test" { + most_recent = true + owners = ["amazon"] + + filter { + name = "name" + values = ["amzn-ami-hvm-*-x86_64-gp2"] + } +} + +resource "aws_launch_template" "test" { + image_id = "${data.aws_ami.test.id}" + instance_type = "t3.micro" + name = %q +} +`, rName) +} + +func testAccAWSEc2FleetConfig_ExcessCapacityTerminationPolicy(rName, excessCapacityTerminationPolicy string) string { + return testAccAWSEc2FleetConfig_BaseLaunchTemplate(rName) + fmt.Sprintf(` +resource "aws_ec2_fleet" "test" { + excess_capacity_termination_policy = %q + + launch_template_config { + launch_template_specification { + launch_template_id = "${aws_launch_template.test.id}" + version = "${aws_launch_template.test.latest_version}" + } + } + + target_capacity_specification { + default_target_capacity_type = "spot" + total_target_capacity = 0 + } +} +`, excessCapacityTerminationPolicy) +} + +func testAccAWSEc2FleetConfig_LaunchTemplateConfig_LaunchTemplateSpecification_LaunchTemplateId(rName, launchTemplateResourceName string) string { + return fmt.Sprintf(` +data "aws_ami" "test" { + most_recent = true + owners = ["amazon"] + + filter { + name = "name" + values = ["amzn-ami-hvm-*-x86_64-gp2"] + } +} + +resource "aws_launch_template" "test1" { + image_id = "${data.aws_ami.test.id}" + instance_type = "t3.micro" + name = "%s1" +} + +resource "aws_launch_template" "test2" { + image_id = "${data.aws_ami.test.id}" + instance_type = "t3.micro" + name = "%s2" +} + +resource "aws_ec2_fleet" "test" { + launch_template_config { + launch_template_specification { + launch_template_id = "${%s.id}" + version = "${%s.latest_version}" + } + } + + target_capacity_specification { + default_target_capacity_type = "spot" + total_target_capacity = 0 + } +} +`, rName, rName, launchTemplateResourceName, launchTemplateResourceName) +} + +func testAccAWSEc2FleetConfig_LaunchTemplateConfig_LaunchTemplateSpecification_LaunchTemplateName(rName, launchTemplateResourceName string) string { + return fmt.Sprintf(` +data "aws_ami" "test" { + most_recent = true + owners = ["amazon"] + + filter { + name = "name" + values = ["amzn-ami-hvm-*-x86_64-gp2"] + } +} + +resource "aws_launch_template" "test1" { + image_id = "${data.aws_ami.test.id}" + instance_type = "t3.micro" + name = "%s1" +} + +resource "aws_launch_template" "test2" { + image_id = "${data.aws_ami.test.id}" + instance_type = "t3.micro" + name = "%s2" +} + +resource "aws_ec2_fleet" "test" { + launch_template_config { + launch_template_specification { + launch_template_name = "${%s.name}" + version = "${%s.latest_version}" + } + } + + target_capacity_specification { + default_target_capacity_type = "spot" + total_target_capacity = 0 + } +} +`, rName, rName, launchTemplateResourceName, launchTemplateResourceName) +} + +func testAccAWSEc2FleetConfig_LaunchTemplateConfig_LaunchTemplateSpecification_Version(rName, instanceType string) string { + return fmt.Sprintf(` +data "aws_ami" "test" { + most_recent = true + owners = ["amazon"] + + filter { + name = "name" + values = ["amzn-ami-hvm-*-x86_64-gp2"] + } +} + +resource "aws_launch_template" "test" { + image_id = "${data.aws_ami.test.id}" + instance_type = %q + name = %q +} + +resource "aws_ec2_fleet" "test" { + launch_template_config { + launch_template_specification { + launch_template_id = "${aws_launch_template.test.id}" + version = "${aws_launch_template.test.latest_version}" + } + } + + target_capacity_specification { + default_target_capacity_type = "spot" + total_target_capacity = 0 + } +} +`, instanceType, rName) +} + +func testAccAWSEc2FleetConfig_LaunchTemplateConfig_Override_AvailabilityZone(rName string, availabilityZoneIndex int) string { + return testAccAWSEc2FleetConfig_BaseLaunchTemplate(rName) + fmt.Sprintf(` +data "aws_availability_zones" "available" {} + +resource "aws_ec2_fleet" "test" { + launch_template_config { + launch_template_specification { + launch_template_id = "${aws_launch_template.test.id}" + version = "${aws_launch_template.test.latest_version}" + } + + override { + availability_zone = "${data.aws_availability_zones.available.names[%d]}" + } + } + + target_capacity_specification { + default_target_capacity_type = "spot" + total_target_capacity = 0 + } +} +`, availabilityZoneIndex) +} + +func testAccAWSEc2FleetConfig_LaunchTemplateConfig_Override_InstanceType(rName, instanceType string) string { + return testAccAWSEc2FleetConfig_BaseLaunchTemplate(rName) + fmt.Sprintf(` +resource "aws_ec2_fleet" "test" { + launch_template_config { + launch_template_specification { + launch_template_id = "${aws_launch_template.test.id}" + version = "${aws_launch_template.test.latest_version}" + } + + override { + instance_type = %q + } + } + + target_capacity_specification { + default_target_capacity_type = "spot" + total_target_capacity = 0 + } +} +`, instanceType) +} + +func testAccAWSEc2FleetConfig_LaunchTemplateConfig_Override_MaxPrice(rName, maxPrice string) string { + return testAccAWSEc2FleetConfig_BaseLaunchTemplate(rName) + fmt.Sprintf(` +resource "aws_ec2_fleet" "test" { + launch_template_config { + launch_template_specification { + launch_template_id = "${aws_launch_template.test.id}" + version = "${aws_launch_template.test.latest_version}" + } + + override { + max_price = %q + } + } + + target_capacity_specification { + default_target_capacity_type = "spot" + total_target_capacity = 0 + } +} +`, maxPrice) +} + +func testAccAWSEc2FleetConfig_LaunchTemplateConfig_Override_Priority(rName string, priority int) string { + return testAccAWSEc2FleetConfig_BaseLaunchTemplate(rName) + fmt.Sprintf(` +resource "aws_ec2_fleet" "test" { + launch_template_config { + launch_template_specification { + launch_template_id = "${aws_launch_template.test.id}" + version = "${aws_launch_template.test.latest_version}" + } + + override { + priority = %d + } + } + + target_capacity_specification { + default_target_capacity_type = "spot" + total_target_capacity = 0 + } +} +`, priority) +} + +func testAccAWSEc2FleetConfig_LaunchTemplateConfig_Override_Priority_Multiple(rName string, priority1, priority2 int) string { + return testAccAWSEc2FleetConfig_BaseLaunchTemplate(rName) + fmt.Sprintf(` +resource "aws_ec2_fleet" "test" { + launch_template_config { + launch_template_specification { + launch_template_id = "${aws_launch_template.test.id}" + version = "${aws_launch_template.test.latest_version}" + } + + override { + instance_type = "${aws_launch_template.test.instance_type}" + priority = %d + } + + override { + instance_type = "t3.small" + priority = %d + } + } + + target_capacity_specification { + default_target_capacity_type = "spot" + total_target_capacity = 0 + } +} +`, priority1, priority2) +} + +func testAccAWSEc2FleetConfig_LaunchTemplateConfig_Override_SubnetId(rName string, subnetIndex int) string { + return testAccAWSEc2FleetConfig_BaseLaunchTemplate(rName) + fmt.Sprintf(` +variable "TestAccNameTag" { + default = "tf-acc-test-ec2-fleet-launchtemplateconfig-override-subnetid" +} + +resource "aws_vpc" "test" { + cidr_block = "10.1.0.0/16" + + tags { + Name = "${var.TestAccNameTag}" + } +} + +resource "aws_subnet" "test" { + count = 2 + + cidr_block = "10.1.${count.index}.0/24" + vpc_id = "${aws_vpc.test.id}" + + tags { + Name = "${var.TestAccNameTag}" + } +} + +resource "aws_ec2_fleet" "test" { + launch_template_config { + launch_template_specification { + launch_template_id = "${aws_launch_template.test.id}" + version = "${aws_launch_template.test.latest_version}" + } + + override { + subnet_id = "${aws_subnet.test.*.id[%d]}" + } + } + + target_capacity_specification { + default_target_capacity_type = "spot" + total_target_capacity = 0 + } +} +`, subnetIndex) +} + +func testAccAWSEc2FleetConfig_LaunchTemplateConfig_Override_WeightedCapacity(rName string, weightedCapacity int) string { + return testAccAWSEc2FleetConfig_BaseLaunchTemplate(rName) + fmt.Sprintf(` +resource "aws_ec2_fleet" "test" { + launch_template_config { + launch_template_specification { + launch_template_id = "${aws_launch_template.test.id}" + version = "${aws_launch_template.test.latest_version}" + } + + override { + weighted_capacity = %d + } + } + + target_capacity_specification { + default_target_capacity_type = "spot" + total_target_capacity = 0 + } +} +`, weightedCapacity) +} + +func testAccAWSEc2FleetConfig_LaunchTemplateConfig_Override_WeightedCapacity_Multiple(rName string, weightedCapacity1, weightedCapacity2 int) string { + return testAccAWSEc2FleetConfig_BaseLaunchTemplate(rName) + fmt.Sprintf(` +resource "aws_ec2_fleet" "test" { + launch_template_config { + launch_template_specification { + launch_template_id = "${aws_launch_template.test.id}" + version = "${aws_launch_template.test.latest_version}" + } + + override { + instance_type = "${aws_launch_template.test.instance_type}" + weighted_capacity = %d + } + + override { + instance_type = "t3.small" + weighted_capacity = %d + } + } + + target_capacity_specification { + default_target_capacity_type = "spot" + total_target_capacity = 0 + } +} +`, weightedCapacity1, weightedCapacity2) +} + +func testAccAWSEc2FleetConfig_OnDemandOptions_AllocationStrategy(rName, allocationStrategy string) string { + return testAccAWSEc2FleetConfig_BaseLaunchTemplate(rName) + fmt.Sprintf(` +resource "aws_ec2_fleet" "test" { + launch_template_config { + launch_template_specification { + launch_template_id = "${aws_launch_template.test.id}" + version = "${aws_launch_template.test.latest_version}" + } + } + + on_demand_options { + allocation_strategy = %q + } + + target_capacity_specification { + default_target_capacity_type = "on-demand" + total_target_capacity = 0 + } +} +`, allocationStrategy) +} + +func testAccAWSEc2FleetConfig_ReplaceUnhealthyInstances(rName string, replaceUnhealthyInstances bool) string { + return testAccAWSEc2FleetConfig_BaseLaunchTemplate(rName) + fmt.Sprintf(` +resource "aws_ec2_fleet" "test" { + replace_unhealthy_instances = %t + + launch_template_config { + launch_template_specification { + launch_template_id = "${aws_launch_template.test.id}" + version = "${aws_launch_template.test.latest_version}" + } + } + + target_capacity_specification { + default_target_capacity_type = "spot" + total_target_capacity = 0 + } +} +`, replaceUnhealthyInstances) +} + +func testAccAWSEc2FleetConfig_SpotOptions_AllocationStrategy(rName, allocationStrategy string) string { + return testAccAWSEc2FleetConfig_BaseLaunchTemplate(rName) + fmt.Sprintf(` +resource "aws_ec2_fleet" "test" { + launch_template_config { + launch_template_specification { + launch_template_id = "${aws_launch_template.test.id}" + version = "${aws_launch_template.test.latest_version}" + } + } + + spot_options { + allocation_strategy = %q + } + + target_capacity_specification { + default_target_capacity_type = "spot" + total_target_capacity = 0 + } +} +`, allocationStrategy) +} + +func testAccAWSEc2FleetConfig_SpotOptions_InstanceInterruptionBehavior(rName, instanceInterruptionBehavior string) string { + return testAccAWSEc2FleetConfig_BaseLaunchTemplate(rName) + fmt.Sprintf(` +resource "aws_ec2_fleet" "test" { + launch_template_config { + launch_template_specification { + launch_template_id = "${aws_launch_template.test.id}" + version = "${aws_launch_template.test.latest_version}" + } + } + + spot_options { + instance_interruption_behavior = %q + } + + target_capacity_specification { + default_target_capacity_type = "spot" + total_target_capacity = 0 + } +} +`, instanceInterruptionBehavior) +} + +func testAccAWSEc2FleetConfig_SpotOptions_InstancePoolsToUseCount(rName string, instancePoolsToUseCount int) string { + return testAccAWSEc2FleetConfig_BaseLaunchTemplate(rName) + fmt.Sprintf(` +resource "aws_ec2_fleet" "test" { + launch_template_config { + launch_template_specification { + launch_template_id = "${aws_launch_template.test.id}" + version = "${aws_launch_template.test.latest_version}" + } + } + + spot_options { + instance_pools_to_use_count = %d + } + + target_capacity_specification { + default_target_capacity_type = "spot" + total_target_capacity = 0 + } +} +`, instancePoolsToUseCount) +} + +func testAccAWSEc2FleetConfig_Tags(rName, key1, value1 string) string { + return testAccAWSEc2FleetConfig_BaseLaunchTemplate(rName) + fmt.Sprintf(` +resource "aws_ec2_fleet" "test" { + launch_template_config { + launch_template_specification { + launch_template_id = "${aws_launch_template.test.id}" + version = "${aws_launch_template.test.latest_version}" + } + } + + tags { + %q = %q + } + + target_capacity_specification { + default_target_capacity_type = "spot" + total_target_capacity = 0 + } +} +`, key1, value1) +} + +func testAccAWSEc2FleetConfig_TargetCapacitySpecification_DefaultTargetCapacityType(rName, defaultTargetCapacityType string) string { + return testAccAWSEc2FleetConfig_BaseLaunchTemplate(rName) + fmt.Sprintf(` +resource "aws_ec2_fleet" "test" { + launch_template_config { + launch_template_specification { + launch_template_id = "${aws_launch_template.test.id}" + version = "${aws_launch_template.test.latest_version}" + } + } + + target_capacity_specification { + default_target_capacity_type = %q + total_target_capacity = 0 + } +} +`, defaultTargetCapacityType) +} + +func testAccAWSEc2FleetConfig_TargetCapacitySpecification_TotalTargetCapacity(rName string, totalTargetCapacity int) string { + return testAccAWSEc2FleetConfig_BaseLaunchTemplate(rName) + fmt.Sprintf(` +resource "aws_ec2_fleet" "test" { + terminate_instances = true + + launch_template_config { + launch_template_specification { + launch_template_id = "${aws_launch_template.test.id}" + version = "${aws_launch_template.test.latest_version}" + } + } + + target_capacity_specification { + default_target_capacity_type = "spot" + total_target_capacity = %d + } +} +`, totalTargetCapacity) +} + +func testAccAWSEc2FleetConfig_TerminateInstancesWithExpiration(rName string, terminateInstancesWithExpiration bool) string { + return testAccAWSEc2FleetConfig_BaseLaunchTemplate(rName) + fmt.Sprintf(` +resource "aws_ec2_fleet" "test" { + terminate_instances_with_expiration = %t + + launch_template_config { + launch_template_specification { + launch_template_id = "${aws_launch_template.test.id}" + version = "${aws_launch_template.test.latest_version}" + } + } + + target_capacity_specification { + default_target_capacity_type = "spot" + total_target_capacity = 0 + } +} +`, terminateInstancesWithExpiration) +} + +func testAccAWSEc2FleetConfig_Type(rName, fleetType string) string { + return testAccAWSEc2FleetConfig_BaseLaunchTemplate(rName) + fmt.Sprintf(` +resource "aws_ec2_fleet" "test" { + type = %q + + launch_template_config { + launch_template_specification { + launch_template_id = "${aws_launch_template.test.id}" + version = "${aws_launch_template.test.latest_version}" + } + } + + target_capacity_specification { + default_target_capacity_type = "spot" + total_target_capacity = 0 + } +} +`, fleetType) +} diff --git a/website/aws.erb b/website/aws.erb index 1797d92bdb9..b580ab536f8 100644 --- a/website/aws.erb +++ b/website/aws.erb @@ -962,6 +962,10 @@ aws_ebs_volume +