From 42236a7d0e54fd1a242d2ff2cfe5c8da72f56b16 Mon Sep 17 00:00:00 2001 From: Albert Silva Date: Tue, 31 Jan 2023 07:10:41 -0500 Subject: [PATCH 01/20] add additional computed attributes to schema --- internal/service/ec2/ec2_fleet.go | 529 +++++++++++++++++++++++++++++- 1 file changed, 528 insertions(+), 1 deletion(-) diff --git a/internal/service/ec2/ec2_fleet.go b/internal/service/ec2/ec2_fleet.go index a626daafa46..324cecbd55c 100644 --- a/internal/service/ec2/ec2_fleet.go +++ b/internal/service/ec2/ec2_fleet.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "log" + "regexp" "strconv" "time" @@ -71,11 +72,428 @@ func ResourceFleet() *schema.Resource { Default: ec2.FleetExcessCapacityTerminationPolicyTermination, ValidateFunc: validation.StringInSlice(ec2.FleetExcessCapacityTerminationPolicy_Values(), false), }, + "fleet_instance_set": { + Type: schema.TypeList, + Optional: true, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "instance_ids": { + Type: schema.TypeList, + Optional: true, + Computed: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "instance_type": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + "launch_template_and_overrides": { + Type: schema.TypeList, + Optional: true, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "launch_template_specification": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "launch_template_id": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + "launch_template_name": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + "version": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + }, + }, + }, + "overrides": { + Type: schema.TypeList, + Optional: true, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "availability_zone": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + "image_id": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + "instance_requirements": { + Type: schema.TypeList, + Optional: true, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "accelerator_count": { + Type: schema.TypeList, + Optional: true, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "max": { + Type: schema.TypeInt, + Optional: true, + Computed: true, + }, + "min": { + Type: schema.TypeInt, + Optional: true, + Computed: true, + }, + }, + }, + }, + "accelerator_manufacturers": { + Type: schema.TypeSet, + Optional: true, + Computed: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "accelerator_names": { + Type: schema.TypeSet, + Optional: true, + Computed: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "accelerator_total_memory_mib": { + Type: schema.TypeList, + Optional: true, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "max": { + Type: schema.TypeInt, + Optional: true, + Computed: true, + }, + "min": { + Type: schema.TypeInt, + Optional: true, + Computed: true, + }, + }, + }, + }, + "accelerator_types": { + Type: schema.TypeSet, + Optional: true, + Computed: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "allowed_instance_types": { + Type: schema.TypeSet, + Optional: true, + Computed: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "bare_metal": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + "baseline_ebs_bandwidth_mbps": { + Type: schema.TypeList, + Optional: true, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "max": { + Type: schema.TypeInt, + Optional: true, + Computed: true, + }, + "min": { + Type: schema.TypeInt, + Optional: true, + Computed: true, + }, + }, + }, + }, + "burstable_performance": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + "cpu_manufacturers": { + Type: schema.TypeSet, + Optional: true, + Computed: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "excluded_instance_types": { + Type: schema.TypeSet, + Optional: true, + Computed: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "instance_generations": { + Type: schema.TypeSet, + Optional: true, + Computed: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "local_storage": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + "local_storage_types": { + Type: schema.TypeSet, + Optional: true, + Computed: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "memory_gib_per_vcpu": { + Type: schema.TypeList, + Optional: true, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "max": { + Type: schema.TypeFloat, + Optional: true, + Computed: true, + }, + "min": { + Type: schema.TypeFloat, + Optional: true, + Computed: true, + }, + }, + }, + }, + "memory_mib": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "max": { + Type: schema.TypeInt, + Optional: true, + Computed: true, + }, + "min": { + Type: schema.TypeInt, + Computed: true, + }, + }, + }, + }, + "network_bandwidth_gbps_request": { + Type: schema.TypeList, + Optional: true, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "max": { + Type: schema.TypeFloat, + Optional: true, + Computed: true, + }, + "min": { + Type: schema.TypeFloat, + Optional: true, + Computed: true, + }, + }, + }, + }, + "network_interface_count": { + Type: schema.TypeList, + Optional: true, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "max": { + Type: schema.TypeInt, + Optional: true, + Computed: true, + }, + "min": { + Type: schema.TypeInt, + Optional: true, + Computed: true, + }, + }, + }, + }, + "on_demand_max_price_percentage_over_lowest_price": { + Type: schema.TypeInt, + Optional: true, + Computed: true, + }, + "require_hibernate_support": { + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + "spot_max_price_percentage_over_lowest_price": { + Type: schema.TypeInt, + Optional: true, + Computed: true, + }, + "total_local_storage_gb": { + Type: schema.TypeList, + Optional: true, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "max": { + Type: schema.TypeFloat, + Optional: true, + Computed: true, + }, + "min": { + Type: schema.TypeFloat, + Optional: true, + Computed: true, + }, + }, + }, + }, + "vcpu_count": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "max": { + Type: schema.TypeInt, + Optional: true, + Computed: true, + }, + "min": { + Type: schema.TypeInt, + Computed: true, + }, + }, + }, + }, + }, + }, + }, + "instance_type": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + "max_price": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + "placement": { + Type: schema.TypeList, + Optional: true, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "group_id": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + "group_name": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + "spread_domain": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + }, + }, + }, + "priority": { + Type: schema.TypeFloat, + Optional: true, + Computed: true, + }, + "subnet_id": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + "weighted_capacity": { + Type: schema.TypeFloat, + Optional: true, + Computed: true, + }, + }, + }, + }, + }, + }, + }, + "lifecycle": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + "platform": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + }, + }, + }, + "fleet_state": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + "fulfilled_capacity": { + Type: schema.TypeFloat, + Optional: true, + Computed: true, + }, + "fulfilled_on_demand_capacity": { + Type: schema.TypeFloat, + Optional: true, + Computed: true, + }, "launch_template_config": { Type: schema.TypeList, Required: true, MinItems: 1, - MaxItems: 1, + MaxItems: 50, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "launch_template_specification": { @@ -92,6 +510,10 @@ func ResourceFleet() *schema.Resource { "launch_template_name": { Type: schema.TypeString, Optional: true, + ValidateFunc: validation.Any( + validation.StringLenBetween(3, 128), + validation.StringMatch(regexp.MustCompile(`[a-zA-Z0-9\(\)\.\-/_]+`), "must begin with a letter and contain only alphanumeric, underscore, period, or hyphen characters"), + ), }, "version": { Type: schema.TypeString, @@ -652,12 +1074,25 @@ func resourceFleetRead(d *schema.ResourceData, meta interface{}) error { AccountID: meta.(*conns.AWSClient).AccountID, Resource: fmt.Sprintf("fleet/%s", d.Id()), }.String() + d.Set("arn", arn) d.Set("context", fleet.Context) d.Set("excess_capacity_termination_policy", fleet.ExcessCapacityTerminationPolicy) + + if fleet.Instances != nil { + if err := d.Set("fleet_instance_set", flattenFleetInstanceSet(fleet.Instances)); err != nil { + return fmt.Errorf("setting fleet_instance_set: %w", err) + } + } + + d.Set("fleet_state", fleet.FleetState) + d.Set("fulfilled_capacity", fleet.FulfilledCapacity) + d.Set("fulfilled_on_demand_capacity", fleet.FulfilledOnDemandCapacity) + if err := d.Set("launch_template_config", flattenFleetLaunchTemplateConfigs(fleet.LaunchTemplateConfigs)); err != nil { return fmt.Errorf("setting launch_template_config: %w", err) } + if fleet.OnDemandOptions != nil { if err := d.Set("on_demand_options", []interface{}{flattenOnDemandOptions(fleet.OnDemandOptions)}); err != nil { return fmt.Errorf("setting on_demand_options: %w", err) @@ -665,7 +1100,9 @@ func resourceFleetRead(d *schema.ResourceData, meta interface{}) error { } else { d.Set("on_demand_options", nil) } + d.Set("replace_unhealthy_instances", fleet.ReplaceUnhealthyInstances) + if fleet.SpotOptions != nil { if err := d.Set("spot_options", []interface{}{flattenSpotOptions(fleet.SpotOptions)}); err != nil { return fmt.Errorf("setting spot_options: %w", err) @@ -673,6 +1110,7 @@ func resourceFleetRead(d *schema.ResourceData, meta interface{}) error { } else { d.Set("spot_options", nil) } + if fleet.TargetCapacitySpecification != nil { if err := d.Set("target_capacity_specification", []interface{}{flattenTargetCapacitySpecification(fleet.TargetCapacitySpecification)}); err != nil { return fmt.Errorf("setting target_capacity_specification: %w", err) @@ -680,6 +1118,7 @@ func resourceFleetRead(d *schema.ResourceData, meta interface{}) error { } else { d.Set("target_capacity_specification", nil) } + d.Set("terminate_instances_with_expiration", fleet.TerminateInstancesWithExpiration) d.Set("type", fleet.Type) @@ -1005,6 +1444,54 @@ func expandTargetCapacitySpecificationRequest(tfMap map[string]interface{}) *ec2 return apiObject } +func flattenFleetInstances(apiObject *ec2.DescribeFleetsInstances) map[string]interface{} { + if apiObject == nil { + return nil + } + + tfMap := map[string]interface{}{} + + if v := apiObject.InstanceIds; v != nil { + tfMap["instance_ids"] = aws.StringValueSlice(v) + } + + if v := apiObject.InstanceType; v != nil { + tfMap["instance_type"] = aws.StringValue(v) + } + + if v := apiObject.LaunchTemplateAndOverrides; v != nil { + tfMap["launch_template_and_overrides"] = []interface{}{flattenLaunchTemplatesAndOverridesResponse(v)} + } + + if v := apiObject.Lifecycle; v != nil { + tfMap["lifecycle"] = aws.StringValue(v) + } + + if v := apiObject.Platform; v != nil { + tfMap["platform"] = aws.StringValue(v) + } + + return tfMap +} + +func flattenFleetInstanceSet(apiObjects []*ec2.DescribeFleetsInstances) []interface{} { + if len(apiObjects) == 0 { + return nil + } + + var tfList []interface{} + + for _, apiObject := range apiObjects { + if apiObject == nil { + continue + } + + tfList = append(tfList, flattenFleetInstances(apiObject)) + } + + return tfList +} + func flattenFleetLaunchTemplateConfigs(apiObjects []*ec2.FleetLaunchTemplateConfig) []interface{} { if len(apiObjects) == 0 { return nil @@ -1063,6 +1550,24 @@ func flattenFleetLaunchTemplateSpecificationForFleet(apiObject *ec2.FleetLaunchT return tfMap } +func flattenLaunchTemplatesAndOverridesResponse(apiObject *ec2.LaunchTemplateAndOverridesResponse) map[string]interface{} { + if apiObject == nil { + return nil + } + + tfMap := map[string]interface{}{} + + if v := apiObject.LaunchTemplateSpecification; v != nil { + tfMap["launch_template_specification"] = []interface{}{flattenFleetLaunchTemplateSpecificationForFleet(v)} + } + + if v := apiObject.Overrides; v != nil { + tfMap["overrides"] = []interface{}{flattenFleetLaunchTemplateOverrides(v)} + } + + return tfMap +} + func flattenFleetLaunchTemplateOverrideses(apiObjects []*ec2.FleetLaunchTemplateOverrides) []interface{} { if len(apiObjects) == 0 { return nil @@ -1096,6 +1601,10 @@ func flattenFleetLaunchTemplateOverrides(apiObject *ec2.FleetLaunchTemplateOverr tfMap["instance_requirements"] = []interface{}{flattenInstanceRequirements(v)} } + if v := apiObject.ImageId; v != nil { + tfMap["image_id"] = aws.StringValue(v) + } + if v := apiObject.InstanceType; v != nil { tfMap["instance_type"] = aws.StringValue(v) } @@ -1104,6 +1613,10 @@ func flattenFleetLaunchTemplateOverrides(apiObject *ec2.FleetLaunchTemplateOverr tfMap["max_price"] = aws.StringValue(v) } + if v := apiObject.Placement; v != nil { + tfMap["placement"] = []interface{}{flattenPlacement(v)} + } + if v := apiObject.Priority; v != nil { tfMap["priority"] = aws.Float64Value(v) } @@ -1133,6 +1646,20 @@ func flattenOnDemandOptions(apiObject *ec2.OnDemandOptions) map[string]interface return tfMap } +func flattenPlacement(apiObject *ec2.PlacementResponse) map[string]interface{} { + if apiObject == nil { + return nil + } + + tfMap := map[string]interface{}{} + + if v := apiObject.GroupName; v != nil { + tfMap["group_name"] = aws.StringValue(v) + } + + return tfMap +} + func flattenSpotOptions(apiObject *ec2.SpotOptions) map[string]interface{} { if apiObject == nil { return nil From 542c61c3a0a340888c33134ee5e2a300e147d6e4 Mon Sep 17 00:00:00 2001 From: Albert Silva Date: Tue, 31 Jan 2023 07:29:43 -0500 Subject: [PATCH 02/20] add changelog --- .changelog/29181.txt | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .changelog/29181.txt diff --git a/.changelog/29181.txt b/.changelog/29181.txt new file mode 100644 index 00000000000..29a51f86bea --- /dev/null +++ b/.changelog/29181.txt @@ -0,0 +1,3 @@ +```release-note:enhancement +resource/aws_ec2_fleet: Add computed `fleet_instance_set`,`fleet_state`, `fulfilled_capacity`, and `fulfilled_on_demand_capacity` arguments +``` \ No newline at end of file From 67a04307c4e0b5d4a8493c7d70395aa82b934c33 Mon Sep 17 00:00:00 2001 From: Albert Silva Date: Tue, 31 Jan 2023 07:30:04 -0500 Subject: [PATCH 03/20] add arguments to launch_template_config.override --- internal/service/ec2/ec2_fleet.go | 62 ++++++++++++++++++++- internal/service/ec2/ec2_launch_template.go | 53 ++++++++++++++++++ 2 files changed, 114 insertions(+), 1 deletion(-) diff --git a/internal/service/ec2/ec2_fleet.go b/internal/service/ec2/ec2_fleet.go index 324cecbd55c..529379940de 100644 --- a/internal/service/ec2/ec2_fleet.go +++ b/internal/service/ec2/ec2_fleet.go @@ -532,6 +532,10 @@ func ResourceFleet() *schema.Resource { Type: schema.TypeString, Optional: true, }, + "image_id": { + Type: schema.TypeString, + Optional: true, + }, "instance_requirements": { Type: schema.TypeList, Optional: true, @@ -600,6 +604,19 @@ func ResourceFleet() *schema.Resource { ValidateFunc: validation.StringInSlice(ec2.AcceleratorType_Values(), false), }, }, + "allowed_instance_types": { + Type: schema.TypeSet, + Optional: true, + MinItems: 0, + MaxItems: 400, + Elem: &schema.Schema{ + Type: schema.TypeString, + ValidateFunc: validation.Any( + validation.StringMatch(regexp.MustCompile(`[a-zA-Z0-9\(\)\.\-/_]+`), "must begin with a letter and contain only alphanumeric, period, wildcard, or hyphen characters"), + validation.StringLenBetween(1, 30), + ), + }, + }, "bare_metal": { Type: schema.TypeString, Optional: true, @@ -640,8 +657,15 @@ func ResourceFleet() *schema.Resource { "excluded_instance_types": { Type: schema.TypeSet, Optional: true, + MinItems: 0, MaxItems: 400, - Elem: &schema.Schema{Type: schema.TypeString}, + Elem: &schema.Schema{ + Type: schema.TypeString, + ValidateFunc: validation.Any( + validation.StringMatch(regexp.MustCompile(`[a-zA-Z0-9\(\)\.\-/_]+`), "must begin with a letter and contain only alphanumeric, period, wildcard, or hyphen characters"), + validation.StringLenBetween(1, 30), + ), + }, }, "instance_generations": { Type: schema.TypeSet, @@ -702,6 +726,25 @@ func ResourceFleet() *schema.Resource { }, }, }, + "network_bandwidth_gbps_request": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "max": { + Type: schema.TypeFloat, + Optional: true, + ValidateFunc: verify.FloatGreaterThan(0.0), + }, + "min": { + Type: schema.TypeFloat, + Optional: true, + ValidateFunc: verify.FloatGreaterThan(0.0), + }, + }, + }, + }, "network_interface_count": { Type: schema.TypeList, Optional: true, @@ -784,6 +827,23 @@ func ResourceFleet() *schema.Resource { Type: schema.TypeString, Optional: true, }, + "placement": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "group_id": { + Type: schema.TypeString, + Optional: true, + }, + "group_name": { + Type: schema.TypeString, + Optional: true, + }, + }, + }, + }, "priority": { Type: schema.TypeFloat, Optional: true, diff --git a/internal/service/ec2/ec2_launch_template.go b/internal/service/ec2/ec2_launch_template.go index d6c00fb23e2..545fb2bd379 100644 --- a/internal/service/ec2/ec2_launch_template.go +++ b/internal/service/ec2/ec2_launch_template.go @@ -1586,6 +1586,10 @@ func expandInstanceRequirementsRequest(tfMap map[string]interface{}) *ec2.Instan apiObject.AcceleratorTypes = flex.ExpandStringSet(v) } + if v, ok := tfMap["allowed_instance_types"].(*schema.Set); ok && v.Len() > 0 { + apiObject.AllowedInstanceTypes = flex.ExpandStringSet(v) + } + if v, ok := tfMap["bare_metal"].(string); ok && v != "" { apiObject.BareMetal = aws.String(v) } @@ -1626,6 +1630,10 @@ func expandInstanceRequirementsRequest(tfMap map[string]interface{}) *ec2.Instan apiObject.MemoryMiB = expandMemoryMiBRequest(v[0].(map[string]interface{})) } + if v, ok := tfMap["network_bandwidth_gbps"].([]interface{}); ok && len(v) > 0 { + apiObject.NetworkBandwidthGbps = expandNetworkBandwidthGbpsRequest(v[0].(map[string]interface{})) + } + if v, ok := tfMap["network_interface_count"].([]interface{}); ok && len(v) > 0 { apiObject.NetworkInterfaceCount = expandNetworkInterfaceCountRequest(v[0].(map[string]interface{})) } @@ -1753,6 +1761,25 @@ func expandMemoryMiBRequest(tfMap map[string]interface{}) *ec2.MemoryMiBRequest return apiObject } +func expandNetworkBandwidthGbpsRequest(tfMap map[string]interface{}) *ec2.NetworkBandwidthGbpsRequest { + if tfMap == nil { + return nil + } + + apiObject := &ec2.NetworkBandwidthGbpsRequest{} + + var min float64 + if v, ok := tfMap["min"].(float64); ok { + apiObject.Min = aws.Float64(float64(v)) + } + + if v, ok := tfMap["max"].(float64); ok && v >= min { + apiObject.Max = aws.Float64(float64(v)) + } + + return apiObject +} + func expandNetworkInterfaceCountRequest(tfMap map[string]interface{}) *ec2.NetworkInterfaceCountRequest { if tfMap == nil { return nil @@ -2582,6 +2609,10 @@ func flattenInstanceRequirements(apiObject *ec2.InstanceRequirements) map[string tfMap["accelerator_types"] = aws.StringValueSlice(v) } + if v := apiObject.AllowedInstanceTypes; v != nil { + tfMap["allowed_instance_types"] = aws.StringValueSlice(v) + } + if v := apiObject.BareMetal; v != nil { tfMap["bare_metal"] = aws.StringValue(v) } @@ -2622,6 +2653,10 @@ func flattenInstanceRequirements(apiObject *ec2.InstanceRequirements) map[string tfMap["memory_mib"] = []interface{}{flattenMemoryMiB(v)} } + if v := apiObject.NetworkBandwidthGbps; v != nil { + tfMap["network_bandwidth_gbps"] = []interface{}{flattenNetworkBandwidthGbps(v)} + } + if v := apiObject.NetworkInterfaceCount; v != nil { tfMap["network_interface_count"] = []interface{}{flattenNetworkInterfaceCount(v)} } @@ -2739,6 +2774,24 @@ func flattenMemoryMiB(apiObject *ec2.MemoryMiB) map[string]interface{} { return tfMap } +func flattenNetworkBandwidthGbps(apiObject *ec2.NetworkBandwidthGbps) map[string]interface{} { + if apiObject == nil { + return nil + } + + tfMap := map[string]interface{}{} + + if v := apiObject.Max; v != nil { + tfMap["max"] = aws.Float64Value(v) + } + + if v := apiObject.Min; v != nil { + tfMap["min"] = aws.Float64Value(v) + } + + return tfMap +} + func flattenNetworkInterfaceCount(apiObject *ec2.NetworkInterfaceCount) map[string]interface{} { if apiObject == nil { return nil From 9c13a09d5ce11687e626a2074a3990fe7605d8e2 Mon Sep 17 00:00:00 2001 From: Albert Silva Date: Tue, 31 Jan 2023 07:45:35 -0500 Subject: [PATCH 04/20] add arguments to on_demand_options --- .changelog/29181.txt | 16 ++++- internal/service/ec2/ec2_fleet.go | 98 +++++++++++++++++++++++++++++++ 2 files changed, 113 insertions(+), 1 deletion(-) diff --git a/.changelog/29181.txt b/.changelog/29181.txt index 29a51f86bea..bf989330f2c 100644 --- a/.changelog/29181.txt +++ b/.changelog/29181.txt @@ -1,3 +1,17 @@ ```release-note:enhancement resource/aws_ec2_fleet: Add computed `fleet_instance_set`,`fleet_state`, `fulfilled_capacity`, and `fulfilled_on_demand_capacity` arguments -``` \ No newline at end of file +``` + +```release-note:enhancement +resource/aws_ec2_fleet: Add `launch_template_config.override.image_id`, `launch_template_config.override.instance_requirements.allowed_instance_types`, launch_template_config.override.instance_requirements.network_bandwidth_gbps_request`, `launch_template_config.override.placement` arguments +``` + +```release-note:enhancement +resource/aws_ec2_fleet: Add `on_demand_options.capacity_reservation_options`,`on_demand_options.max_total_price`, `on_demand_options.min_target_capacity`, `on_demand_options.single_availability_zone`, `on_demand_options.single_instance_type` arguments +``` + +```release-note:enhancement +#### TODO: Update Schema/CRUD +resource/aws_ec2_launch_template: Add expand and flatten support for `instance_requirements.allowed_instance_types`, `instance_requirements.network_bandwidth_gbps_request` +``` + diff --git a/internal/service/ec2/ec2_fleet.go b/internal/service/ec2/ec2_fleet.go index 529379940de..f1b23f4bc75 100644 --- a/internal/service/ec2/ec2_fleet.go +++ b/internal/service/ec2/ec2_fleet.go @@ -876,6 +876,36 @@ func ResourceFleet() *schema.Resource { Default: FleetOnDemandAllocationStrategyLowestPrice, ValidateFunc: validation.StringInSlice(FleetOnDemandAllocationStrategy_Values(), false), }, + "capacity_reservation_options": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "usage_strategy": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringInSlice(ec2.FleetCapacityReservationUsageStrategy_Values(), false), + }, + }, + }, + }, + "max_total_price": { + Type: schema.TypeString, + Optional: true, + }, + "min_target_capacity": { + Type: schema.TypeInt, + Optional: true, + }, + "single_availability_zone": { + Type: schema.TypeBool, + Optional: true, + }, + "single_instance_type": { + Type: schema.TypeBool, + Optional: true, + }, }, }, }, @@ -1273,6 +1303,20 @@ func resourceFleetDelete(d *schema.ResourceData, meta interface{}) error { return nil } +func expandCapacityReservationOptionsRequest(tfMap map[string]interface{}) *ec2.CapacityReservationOptionsRequest { + if tfMap == nil { + return nil + } + + apiObject := &ec2.CapacityReservationOptionsRequest{} + + if v, ok := tfMap["usage_strategy"].(string); ok && v != "" { + apiObject.UsageStrategy = aws.String(v) + } + + return apiObject +} + func expandFleetLaunchTemplateConfigRequests(tfList []interface{}) []*ec2.FleetLaunchTemplateConfigRequest { if len(tfList) == 0 { return nil @@ -1414,6 +1458,26 @@ func expandOnDemandOptionsRequest(tfMap map[string]interface{}) *ec2.OnDemandOpt apiObject.AllocationStrategy = aws.String(v) } + if v, ok := tfMap["capacity_reservation_options"]; ok && len(v.([]interface{})) > 0 && v.([]interface{})[0] != nil { + apiObject.CapacityReservationOptions = expandCapacityReservationOptionsRequest(v.([]interface{})[0].(map[string]interface{})) + } + + if v, ok := tfMap["max_total_price"].(string); ok && v != "" { + apiObject.MaxTotalPrice = aws.String(v) + } + + if v, ok := tfMap["min_target_capacity"].(int64); ok { + apiObject.MinTargetCapacity = aws.Int64(v) + } + + if v, ok := tfMap["single_availability_zone"].(bool); ok { + apiObject.SingleAvailabilityZone = aws.Bool(v) + } + + if v, ok := tfMap["single_instance_type"].(bool); ok { + apiObject.SingleInstanceType = aws.Bool(v) + } + return apiObject } @@ -1504,6 +1568,20 @@ func expandTargetCapacitySpecificationRequest(tfMap map[string]interface{}) *ec2 return apiObject } +func flattenCapacityReservationsOptions(apiObject *ec2.CapacityReservationOptions) map[string]interface{} { + if apiObject == nil { + return nil + } + + tfMap := map[string]interface{}{} + + if v := apiObject.UsageStrategy; v != nil { + tfMap["usage_strategy"] = aws.StringValue(v) + } + + return tfMap +} + func flattenFleetInstances(apiObject *ec2.DescribeFleetsInstances) map[string]interface{} { if apiObject == nil { return nil @@ -1703,6 +1781,26 @@ func flattenOnDemandOptions(apiObject *ec2.OnDemandOptions) map[string]interface tfMap["allocation_strategy"] = aws.StringValue(v) } + if v := apiObject.CapacityReservationOptions; v != nil { + tfMap["capacity_reservation_options"] = []interface{}{flattenCapacityReservationsOptions(v)} + } + + if v := apiObject.MaxTotalPrice; v != nil { + tfMap["max_total_price"] = aws.StringValue(v) + } + + if v := apiObject.MinTargetCapacity; v != nil { + tfMap["min_target_capacity"] = aws.Int64Value(v) + } + + if v := apiObject.SingleAvailabilityZone; v != nil { + tfMap["single_availability_zone"] = aws.BoolValue(v) + } + + if v := apiObject.SingleInstanceType; v != nil { + tfMap["single_instance_type"] = aws.BoolValue(v) + } + return tfMap } From 65cb240ba88677f01c091bc0cb5b89451b47da4b Mon Sep 17 00:00:00 2001 From: Albert Silva Date: Tue, 31 Jan 2023 07:50:37 -0500 Subject: [PATCH 05/20] add argument spot_options.maintenance_strategies.capacity_rebalance.termination_delay --- .changelog/29181.txt | 3 +++ internal/service/ec2/ec2_fleet.go | 14 ++++++++++++++ 2 files changed, 17 insertions(+) diff --git a/.changelog/29181.txt b/.changelog/29181.txt index bf989330f2c..f6ba634678a 100644 --- a/.changelog/29181.txt +++ b/.changelog/29181.txt @@ -15,3 +15,6 @@ resource/aws_ec2_fleet: Add `on_demand_options.capacity_reservation_options`,`on resource/aws_ec2_launch_template: Add expand and flatten support for `instance_requirements.allowed_instance_types`, `instance_requirements.network_bandwidth_gbps_request` ``` +```release-note:enhancement +resource/aws_ec2_fleet: Add `spot_options.maintenance_strategies.capacity_rebalance.termination_delay` argument +``` diff --git a/internal/service/ec2/ec2_fleet.go b/internal/service/ec2/ec2_fleet.go index f1b23f4bc75..531d51e1a53 100644 --- a/internal/service/ec2/ec2_fleet.go +++ b/internal/service/ec2/ec2_fleet.go @@ -918,6 +918,7 @@ func ResourceFleet() *schema.Resource { Type: schema.TypeList, Optional: true, MaxItems: 1, + ForceNew: true, DiffSuppressFunc: verify.SuppressMissingOptionalConfigurationBlock, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ @@ -962,6 +963,11 @@ func ResourceFleet() *schema.Resource { ForceNew: true, ValidateFunc: validation.StringInSlice(ec2.FleetReplacementStrategy_Values(), false), }, + "termination_delay": { + Type: schema.TypeInt, + Optional: true, + ValidateFunc: validation.IntBetween(120, 7200), + }, }, }, }, @@ -1535,6 +1541,10 @@ func expandFleetSpotCapacityRebalanceRequest(tfMap map[string]interface{}) *ec2. apiObject.ReplacementStrategy = aws.String(v) } + if v, ok := tfMap["termination_delay"].(int64); ok { + apiObject.TerminationDelay = aws.Int64(v) + } + return apiObject } @@ -1873,6 +1883,10 @@ func flattenFleetSpotCapacityRebalance(apiObject *ec2.FleetSpotCapacityRebalance tfMap["replacement_strategy"] = aws.StringValue(v) } + if v := apiObject.TerminationDelay; v != nil { + tfMap["termination_delay"] = aws.Int64Value(v) + } + return tfMap } From e1dc5f4932ffd2ba5c1890440573c61cbe73136e Mon Sep 17 00:00:00 2001 From: Albert Silva Date: Tue, 31 Jan 2023 07:53:54 -0500 Subject: [PATCH 06/20] add valid_from and valid_until arguments --- .changelog/29181.txt | 4 +++ internal/service/ec2/ec2_fleet.go | 51 ++++++++++++++++++++++++++----- 2 files changed, 47 insertions(+), 8 deletions(-) diff --git a/.changelog/29181.txt b/.changelog/29181.txt index f6ba634678a..de301067943 100644 --- a/.changelog/29181.txt +++ b/.changelog/29181.txt @@ -18,3 +18,7 @@ resource/aws_ec2_launch_template: Add expand and flatten support for `instance_r ```release-note:enhancement resource/aws_ec2_fleet: Add `spot_options.maintenance_strategies.capacity_rebalance.termination_delay` argument ``` + +```release-note:enhancement +resource/aws_ec2_fleet: Add `valid_from`, `valid_until` arguments +``` \ No newline at end of file diff --git a/internal/service/ec2/ec2_fleet.go b/internal/service/ec2/ec2_fleet.go index 531d51e1a53..d6729d69b85 100644 --- a/internal/service/ec2/ec2_fleet.go +++ b/internal/service/ec2/ec2_fleet.go @@ -1072,14 +1072,23 @@ func ResourceFleet() *schema.Resource { ForceNew: true, }, "type": { - Type: schema.TypeString, - Optional: true, - ForceNew: true, - Default: ec2.FleetTypeMaintain, - ValidateFunc: validation.StringInSlice([]string{ - ec2.FleetTypeMaintain, - ec2.FleetTypeRequest, - }, false), + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Default: ec2.FleetTypeMaintain, + ValidateFunc: validation.StringInSlice(ec2.FleetType_Values(), false), + }, + "valid_from": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + ValidateFunc: validation.IsRFC3339Time, + }, + "valid_until": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + ValidateFunc: validation.IsRFC3339Time, }, }, } @@ -1123,6 +1132,22 @@ func resourceFleetCreate(d *schema.ResourceData, meta interface{}) error { input.Context = aws.String(v.(string)) } + if v, ok := d.GetOk("valid_from"); ok { + validFrom, err := time.Parse(time.RFC3339, v.(string)) + if err != nil { + return err + } + input.ValidFrom = aws.Time(validFrom) + } + + if v, ok := d.GetOk("valid_until"); ok { + validUntil, err := time.Parse(time.RFC3339, v.(string)) + if err != nil { + return err + } + input.ValidUntil = aws.Time(validUntil) + } + log.Printf("[DEBUG] Creating EC2 Fleet: %s", input) output, err := conn.CreateFleet(input) @@ -1218,6 +1243,16 @@ func resourceFleetRead(d *schema.ResourceData, meta interface{}) error { d.Set("terminate_instances_with_expiration", fleet.TerminateInstancesWithExpiration) d.Set("type", fleet.Type) + if fleet.ValidFrom != nil && aws.TimeValue(fleet.ValidFrom).Format(time.RFC3339) != "1970-01-01T00:00:00Z" { + d.Set("valid_from", + aws.TimeValue(fleet.ValidFrom).Format(time.RFC3339)) + } + + if fleet.ValidUntil != nil && aws.TimeValue(fleet.ValidUntil).Format(time.RFC3339) != "1970-01-01T00:00:00Z" { + d.Set("valid_until", + aws.TimeValue(fleet.ValidUntil).Format(time.RFC3339)) + } + tags := KeyValueTags(fleet.Tags).IgnoreAWS().IgnoreConfig(ignoreTagsConfig) //lintignore:AWSR002 From 02710a6502cdd92b36f3260d528aed4dbff1e194 Mon Sep 17 00:00:00 2001 From: Albert Silva Date: Tue, 31 Jan 2023 07:56:15 -0500 Subject: [PATCH 07/20] enhance CustomizeDiff --- internal/service/ec2/ec2_fleet.go | 91 +++++++++++++++++++++++++------ 1 file changed, 75 insertions(+), 16 deletions(-) diff --git a/internal/service/ec2/ec2_fleet.go b/internal/service/ec2/ec2_fleet.go index d6729d69b85..ee5ee6b92c6 100644 --- a/internal/service/ec2/ec2_fleet.go +++ b/internal/service/ec2/ec2_fleet.go @@ -38,25 +38,10 @@ func ResourceFleet() *schema.Resource { Delete: schema.DefaultTimeout(10 * time.Minute), Update: schema.DefaultTimeout(10 * time.Minute), }, - CustomizeDiff: customdiff.All( - func(_ context.Context, diff *schema.ResourceDiff, meta interface{}) error { - if diff.Id() == "" { - if diff.Get("type").(string) != ec2.FleetTypeMaintain { - if v, ok := diff.GetOk("spot_options"); ok && len(v.([]interface{})) > 0 && v.([]interface{})[0] != nil { - tfMap := v.([]interface{})[0].(map[string]interface{}) - if v, ok := tfMap["maintenance_strategies"].([]interface{}); ok && len(v) > 0 { - return errors.New(`EC2 Fleet has an invalid configuration and can not be created. Capacity Rebalance maintenance strategies can only be specified for fleets of type maintain.`) - } - } - } - } - - return nil - }, + resourceFleetCustomizeDiff, verify.SetTagsDiff, ), - Schema: map[string]*schema.Schema{ "arn": { Type: schema.TypeString, @@ -1954,3 +1939,77 @@ func flattenTargetCapacitySpecification(apiObject *ec2.TargetCapacitySpecificati return tfMap } + +func resourceFleetCustomizeDiff(_ context.Context, diff *schema.ResourceDiff, v interface{}) error { + input := &ec2.CreateFleetInput{} + + if diff.Id() == "" { + if diff.Get("type").(string) != ec2.FleetTypeMaintain { + if v, ok := diff.GetOk("spot_options"); ok && len(v.([]interface{})) > 0 && v.([]interface{})[0] != nil { + tfMap := v.([]interface{})[0].(map[string]interface{}) + if v, ok := tfMap["maintenance_strategies"].([]interface{}); ok && len(v) > 0 { + return errors.New(`EC2 Fleet has an invalid configuration and can not be created. Capacity Rebalance maintenance strategies can only be specified for fleets of type maintain.`) + } + } + } + } + + // Launch template config validation: + if v, ok := diff.GetOk("launch_template_config"); ok && len(v.([]interface{})) > 0 { + input.LaunchTemplateConfigs = expandFleetLaunchTemplateConfigRequests(v.([]interface{})) + for _, config := range input.LaunchTemplateConfigs { + for _, override := range config.Overrides { + // InvalidOverride: + if override.InstanceRequirements != nil && override.InstanceType != nil { + return errors.New("launch_template_configs.overrides can specify instance_requirements or instance_type, but not both") + } + // InvalidPlacementConfigs: + if override.Placement.GroupId != nil && override.Placement.GroupName != nil { + return errors.New("launch_template_configs.overrides.placement can specify a group_id or group_name, but not both") + } + } + } + } + + // Fleet type `instant` specific validation: + if v, ok := diff.GetOk("type"); ok { + if v == ec2.FleetTypeInstant { + if v, ok := diff.GetOk("terminate_instances"); ok { + if !v.(bool) { + return errors.New(`EC2 Fleet of type instant must have terminate_instances set to true`) + } + } + if v, ok := diff.GetOk("excess_capacity_termination_policy"); ok { + if v.(string) != "" { + return errors.New(`EC2 Fleet of type instant must not have excess_capacity_termination_policy set`) + } + } + } else { + if v, ok := diff.GetOk("on_demand_options"); ok && len(v.([]interface{})) > 0 && v.([]interface{})[0] != nil { + input.OnDemandOptions = expandOnDemandOptionsRequest(v.([]interface{})[0].(map[string]interface{})) + if input.OnDemandOptions.CapacityReservationOptions != nil { + return errors.New("on_demand_options.capacity_reservation_options can only be specified for fleets of type instant") + } + if input.OnDemandOptions.MinTargetCapacity != nil { + return errors.New("on_demand_options.min_target_capacity can only be specified for fleets of type instant") + } + if input.OnDemandOptions.SingleAvailabilityZone != nil { + return errors.New("on_demand_options.single_availability_zone can only be specified for fleets of type instant") + } + if input.OnDemandOptions.SingleInstanceType != nil { + return errors.New("on_demand_options.single_instance_type can only be specified for fleets of type instant") + } + if input.SpotOptions.MinTargetCapacity != nil { + return errors.New("spot_options.min_target_capacity can only be specified for fleets of type instant") + } + if input.SpotOptions.SingleAvailabilityZone != nil { + return errors.New("spot_options.single_availability_zone can only be specified for fleets of type instant") + } + if input.SpotOptions.SingleInstanceType != nil { + return errors.New("spot_options.single_instance_type can only be specified for fleets of type instant") + } + } + } + } + return nil +} From 0eb2f2acf3cbf8dbf9ad672e05e131dfcc6c361d Mon Sep 17 00:00:00 2001 From: Albert Silva Date: Tue, 31 Jan 2023 08:01:18 -0500 Subject: [PATCH 08/20] add expandPlacement function --- internal/service/ec2/ec2_fleet.go | 58 +++++++++++++++++++++++++++++-- 1 file changed, 56 insertions(+), 2 deletions(-) diff --git a/internal/service/ec2/ec2_fleet.go b/internal/service/ec2/ec2_fleet.go index ee5ee6b92c6..f23b464b599 100644 --- a/internal/service/ec2/ec2_fleet.go +++ b/internal/service/ec2/ec2_fleet.go @@ -502,7 +502,7 @@ func ResourceFleet() *schema.Resource { }, "version": { Type: schema.TypeString, - Required: true, + Optional: true, }, }, }, @@ -510,7 +510,7 @@ func ResourceFleet() *schema.Resource { "override": { Type: schema.TypeList, Optional: true, - MaxItems: 50, + MaxItems: 300, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "availability_zone": { @@ -851,6 +851,7 @@ func ResourceFleet() *schema.Resource { Type: schema.TypeList, Optional: true, MaxItems: 1, + ForceNew: true, DiffSuppressFunc: verify.SuppressMissingOptionalConfigurationBlock, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ @@ -1454,10 +1455,17 @@ func expandFleetLaunchTemplateOverridesRequest(tfMap map[string]interface{}) *ec apiObject.InstanceType = aws.String(v) } + if v, ok := tfMap["image_id"].(string); ok && v != "" { + apiObject.ImageId = aws.String(v) + } + if v, ok := tfMap["max_price"].(string); ok && v != "" { apiObject.MaxPrice = aws.String(v) } + if v, ok := tfMap["placement"]; ok && len(v.([]interface{})) > 0 && v.([]interface{})[0] != nil { + apiObject.Placement = expandPlacement(v.([]interface{})[0].(map[string]interface{})) + } if v, ok := tfMap["priority"].(float64); ok && v != 0 { apiObject.Priority = aws.Float64(v) } @@ -1536,6 +1544,52 @@ func expandSpotOptionsRequest(tfMap map[string]interface{}) *ec2.SpotOptionsRequ return apiObject } +func expandPlacement(tfMap map[string]interface{}) *ec2.Placement { + if tfMap == nil { + return nil + } + + apiObject := &ec2.Placement{} + + if v, ok := tfMap["affinity"].(string); ok && v != "" { + apiObject.Affinity = aws.String(v) + } + + if v, ok := tfMap["availability_zone"].(string); ok && v != "" { + apiObject.AvailabilityZone = aws.String(v) + } + + if v, ok := tfMap["group_id"].(string); ok && v != "" { + apiObject.GroupId = aws.String(v) + } + + if v, ok := tfMap["group_name"].(string); ok && v != "" { + apiObject.GroupName = aws.String(v) + } + + if v, ok := tfMap["host_id"].(string); ok && v != "" { + apiObject.HostId = aws.String(v) + } + + if v, ok := tfMap["host_resource_group_arn"].(string); ok && v != "" { + apiObject.HostResourceGroupArn = aws.String(v) + } + + if v, ok := tfMap["partition_number"].(int); ok && v != 0 { + apiObject.PartitionNumber = aws.Int64(int64(v)) + } + + if v, ok := tfMap["spread_domain"].(string); ok && v != "" { + apiObject.SpreadDomain = aws.String(v) + } + + if v, ok := tfMap["tenancy"].(string); ok && v != "" { + apiObject.Tenancy = aws.String(v) + } + + return apiObject +} + func expandFleetSpotMaintenanceStrategiesRequest(tfMap map[string]interface{}) *ec2.FleetSpotMaintenanceStrategiesRequest { if tfMap == nil { return nil From dfbff501bab3241f16ad8c4319bed600f10371c3 Mon Sep 17 00:00:00 2001 From: Albert Silva Date: Tue, 31 Jan 2023 08:03:01 -0500 Subject: [PATCH 09/20] refactor/alphabetize arguments in CRUD functions --- internal/service/ec2/ec2_fleet.go | 54 +++++++++++++------------------ 1 file changed, 23 insertions(+), 31 deletions(-) diff --git a/internal/service/ec2/ec2_fleet.go b/internal/service/ec2/ec2_fleet.go index f23b464b599..43174813893 100644 --- a/internal/service/ec2/ec2_fleet.go +++ b/internal/service/ec2/ec2_fleet.go @@ -1087,35 +1087,34 @@ func resourceFleetCreate(d *schema.ResourceData, meta interface{}) error { fleetType := d.Get("type").(string) input := &ec2.CreateFleetInput{ - ExcessCapacityTerminationPolicy: aws.String(d.Get("excess_capacity_termination_policy").(string)), - ReplaceUnhealthyInstances: aws.Bool(d.Get("replace_unhealthy_instances").(bool)), - TagSpecifications: tagSpecificationsFromKeyValueTags(tags, ec2.ResourceTypeFleet), - TerminateInstancesWithExpiration: aws.Bool(d.Get("terminate_instances_with_expiration").(bool)), - Type: aws.String(fleetType), + LaunchTemplateConfigs: expandFleetLaunchTemplateConfigRequests(d.Get("launch_template_config").([]interface{})), + TargetCapacitySpecification: expandTargetCapacitySpecificationRequest(d.Get("target_capacity_specification").([]interface{})[0].(map[string]interface{})), + TagSpecifications: tagSpecificationsFromKeyValueTags(tags, ec2.ResourceTypeFleet), + Type: aws.String(fleetType), } if v, ok := d.GetOk("context"); ok { input.Context = aws.String(v.(string)) } - - if v, ok := d.GetOk("launch_template_config"); ok && len(v.([]interface{})) > 0 { - input.LaunchTemplateConfigs = expandFleetLaunchTemplateConfigRequests(v.([]interface{})) + // this argument is only valid for fleet_type of `maintain`, but was defaulted in the schema above, hence the extra check + if v, ok := d.GetOk("excess_capacity_termination_policy"); ok && v != "" && fleetType == "maintain" { + input.ExcessCapacityTerminationPolicy = aws.String(v.(string)) } if v, ok := d.GetOk("on_demand_options"); ok && len(v.([]interface{})) > 0 && v.([]interface{})[0] != nil { input.OnDemandOptions = expandOnDemandOptionsRequest(v.([]interface{})[0].(map[string]interface{})) } - if v, ok := d.GetOk("spot_options"); ok && len(v.([]interface{})) > 0 && v.([]interface{})[0] != nil { - input.SpotOptions = expandSpotOptionsRequest(v.([]interface{})[0].(map[string]interface{})) + if v, ok := d.GetOk("replace_unhealthy_instances"); ok { + input.ReplaceUnhealthyInstances = aws.Bool(v.(bool)) } - if v, ok := d.GetOk("target_capacity_specification"); ok && len(v.([]interface{})) > 0 && v.([]interface{})[0] != nil { - input.TargetCapacitySpecification = expandTargetCapacitySpecificationRequest(v.([]interface{})[0].(map[string]interface{})) + if v, ok := d.GetOk("spot_options"); ok && len(v.([]interface{})) > 0 && v.([]interface{})[0] != nil { + input.SpotOptions = expandSpotOptionsRequest(v.([]interface{})[0].(map[string]interface{})) } - if v, ok := d.GetOk("context"); ok { - input.Context = aws.String(v.(string)) + if v, ok := d.GetOk("terminate_instances_with_expiration"); ok { + input.TerminateInstancesWithExpiration = aws.Bool(v.(bool)) } if v, ok := d.GetOk("valid_from"); ok { @@ -1145,13 +1144,15 @@ func resourceFleetCreate(d *schema.ResourceData, meta interface{}) error { // 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. - targetStates := []string{ec2.FleetStateCodeActive} - if fleetType == ec2.FleetTypeRequest { - targetStates = append(targetStates, ec2.FleetStateCodeDeleted, ec2.FleetStateCodeDeletedRunning, ec2.FleetStateCodeDeletedTerminating) - } + if input.ValidFrom == nil { + targetStates := []string{ec2.FleetStateCodeActive} + if fleetType == ec2.FleetTypeRequest { + targetStates = append(targetStates, ec2.FleetStateCodeDeleted, ec2.FleetStateCodeDeletedRunning, ec2.FleetStateCodeDeletedTerminating) + } - if _, err := WaitFleet(conn, d.Id(), []string{ec2.FleetStateCodeSubmitted}, targetStates, d.Timeout(schema.TimeoutCreate), 0); err != nil { - return fmt.Errorf("waiting for EC2 Fleet (%s) create: %w", d.Id(), err) + if _, err := WaitFleet(conn, d.Id(), []string{ec2.FleetStateCodeSubmitted}, targetStates, d.Timeout(schema.TimeoutCreate), 0); err != nil { + return fmt.Errorf("waiting for EC2 Fleet (%s) create: %w", d.Id(), err) + } } return resourceFleetRead(d, meta) @@ -1173,7 +1174,6 @@ func resourceFleetRead(d *schema.ResourceData, meta interface{}) error { if err != nil { return fmt.Errorf("reading EC2 Fleet (%s): %w", d.Id(), err) } - arn := arn.ARN{ Partition: meta.(*conns.AWSClient).Partition, Service: ec2.ServiceName, @@ -1181,25 +1181,20 @@ func resourceFleetRead(d *schema.ResourceData, meta interface{}) error { AccountID: meta.(*conns.AWSClient).AccountID, Resource: fmt.Sprintf("fleet/%s", d.Id()), }.String() - d.Set("arn", arn) d.Set("context", fleet.Context) d.Set("excess_capacity_termination_policy", fleet.ExcessCapacityTerminationPolicy) - if fleet.Instances != nil { if err := d.Set("fleet_instance_set", flattenFleetInstanceSet(fleet.Instances)); err != nil { return fmt.Errorf("setting fleet_instance_set: %w", err) } } - d.Set("fleet_state", fleet.FleetState) d.Set("fulfilled_capacity", fleet.FulfilledCapacity) d.Set("fulfilled_on_demand_capacity", fleet.FulfilledOnDemandCapacity) - if err := d.Set("launch_template_config", flattenFleetLaunchTemplateConfigs(fleet.LaunchTemplateConfigs)); err != nil { return fmt.Errorf("setting launch_template_config: %w", err) } - if fleet.OnDemandOptions != nil { if err := d.Set("on_demand_options", []interface{}{flattenOnDemandOptions(fleet.OnDemandOptions)}); err != nil { return fmt.Errorf("setting on_demand_options: %w", err) @@ -1207,9 +1202,7 @@ func resourceFleetRead(d *schema.ResourceData, meta interface{}) error { } else { d.Set("on_demand_options", nil) } - d.Set("replace_unhealthy_instances", fleet.ReplaceUnhealthyInstances) - if fleet.SpotOptions != nil { if err := d.Set("spot_options", []interface{}{flattenSpotOptions(fleet.SpotOptions)}); err != nil { return fmt.Errorf("setting spot_options: %w", err) @@ -1217,7 +1210,6 @@ func resourceFleetRead(d *schema.ResourceData, meta interface{}) error { } else { d.Set("spot_options", nil) } - if fleet.TargetCapacitySpecification != nil { if err := d.Set("target_capacity_specification", []interface{}{flattenTargetCapacitySpecification(fleet.TargetCapacitySpecification)}); err != nil { return fmt.Errorf("setting target_capacity_specification: %w", err) @@ -1225,7 +1217,6 @@ func resourceFleetRead(d *schema.ResourceData, meta interface{}) error { } else { d.Set("target_capacity_specification", nil) } - d.Set("terminate_instances_with_expiration", fleet.TerminateInstancesWithExpiration) d.Set("type", fleet.Type) @@ -1257,11 +1248,12 @@ func resourceFleetUpdate(d *schema.ResourceData, meta interface{}) error { conn := meta.(*conns.AWSClient).EC2Conn() if d.HasChangesExcept("tags", "tags_all") { + input := &ec2.ModifyFleetInput{ Context: aws.String(d.Get("context").(string)), ExcessCapacityTerminationPolicy: aws.String(d.Get("excess_capacity_termination_policy").(string)), - LaunchTemplateConfigs: expandFleetLaunchTemplateConfigRequests(d.Get("launch_template_config").([]interface{})), FleetId: aws.String(d.Id()), + LaunchTemplateConfigs: expandFleetLaunchTemplateConfigRequests(d.Get("launch_template_config").([]interface{})), // InvalidTargetCapacitySpecification: Currently we only support total target capacity modification. // TargetCapacitySpecification: expandEc2TargetCapacitySpecificationRequest(d.Get("target_capacity_specification").([]interface{})), TargetCapacitySpecification: &ec2.TargetCapacitySpecificationRequest{ From abad2f852a002f3fa2b7b9beee4111a15c3a042f Mon Sep 17 00:00:00 2001 From: Albert Silva Date: Tue, 31 Jan 2023 08:03:26 -0500 Subject: [PATCH 10/20] add tests for fleet type 'instant' --- internal/service/ec2/ec2_fleet_test.go | 54 +++++++++++++++++++++++--- 1 file changed, 49 insertions(+), 5 deletions(-) diff --git a/internal/service/ec2/ec2_fleet_test.go b/internal/service/ec2/ec2_fleet_test.go index 1a52847bc24..b27bce116ab 100644 --- a/internal/service/ec2/ec2_fleet_test.go +++ b/internal/service/ec2/ec2_fleet_test.go @@ -2579,11 +2579,52 @@ func TestAccEC2Fleet_terminateInstancesWithExpiration(t *testing.T) { }) } -func TestAccEC2Fleet_type(t *testing.T) { +func TestAccEC2Fleet_type_maintain(t *testing.T) { var fleet1 ec2.FleetData resourceName := "aws_ec2_fleet.test" rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + excessCapacityTerminationPolicy := "termination" + fleetType := "maintain" + terminateInstances := false + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t); testAccPreCheckFleet(t) }, + ErrorCheck: acctest.ErrorCheck(t, ec2.EndpointsID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckFleetDestroy, + Steps: []resource.TestStep{ + { + Config: testAccFleetConfig_type(rName, fleetType, excessCapacityTerminationPolicy, terminateInstances), + Check: resource.ComposeTestCheckFunc( + testAccCheckFleetExists(resourceName, &fleet1), + resource.TestCheckResourceAttr(resourceName, "type", fleetType), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"terminate_instances"}, + }, + // This configuration will fulfill immediately, skip until ValidFrom is implemented + // { + // Config: testAccFleetConfig_type(rName, "request"), + // Check: resource.ComposeTestCheckFunc( + // testAccCheckFleetExists(resourceName, &fleet2), + // testAccCheckFleetRecreated(&fleet1, &fleet2), + // resource.TestCheckResourceAttr(resourceName, "type", "request"), + // ), + // }, + }, + }) +} +func TestAccEC2Fleet_type_instant(t *testing.T) { + var fleet1 ec2.FleetData + resourceName := "aws_ec2_fleet.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + excessCapacityTerminationPolicy := "" + fleetType := "instant" + terminateInstances := true resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t); testAccPreCheckFleet(t) }, ErrorCheck: acctest.ErrorCheck(t, ec2.EndpointsID), @@ -2591,10 +2632,10 @@ func TestAccEC2Fleet_type(t *testing.T) { CheckDestroy: testAccCheckFleetDestroy, Steps: []resource.TestStep{ { - Config: testAccFleetConfig_type(rName, "maintain"), + Config: testAccFleetConfig_type(rName, fleetType, excessCapacityTerminationPolicy, terminateInstances), Check: resource.ComposeTestCheckFunc( testAccCheckFleetExists(resourceName, &fleet1), - resource.TestCheckResourceAttr(resourceName, "type", "maintain"), + resource.TestCheckResourceAttr(resourceName, "type", fleetType), ), }, { @@ -3654,11 +3695,13 @@ resource "aws_ec2_fleet" "test" { `, rName, terminateInstancesWithExpiration)) } -func testAccFleetConfig_type(rName, fleetType string) string { +func testAccFleetConfig_type(rName, fleetType string, excessCapacityTerminationPolicy string, terminateInstance bool) string { return acctest.ConfigCompose(testAccFleetConfig_BaseLaunchTemplate(rName), fmt.Sprintf(` resource "aws_ec2_fleet" "test" { type = %[2]q + excess_capacity_termination_policy = %[3]q + launch_template_config { launch_template_specification { launch_template_id = aws_launch_template.test.id @@ -3671,9 +3714,10 @@ resource "aws_ec2_fleet" "test" { total_target_capacity = 0 } + terminate_instances = %[4]t tags = { Name = %[1]q } } -`, rName, fleetType)) +`, rName, fleetType, excessCapacityTerminationPolicy, terminateInstance)) } From 068917549c229a31bac80ec2f43c37400c619d14 Mon Sep 17 00:00:00 2001 From: Albert Silva Date: Tue, 31 Jan 2023 08:23:58 -0500 Subject: [PATCH 11/20] resolve merge issues around ctx and sdkdiag --- internal/service/ec2/ec2_fleet.go | 11 ++++++----- internal/service/ec2/ec2_fleet_test.go | 10 +++++----- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/internal/service/ec2/ec2_fleet.go b/internal/service/ec2/ec2_fleet.go index 52615780e06..e7c17a3b8eb 100644 --- a/internal/service/ec2/ec2_fleet.go +++ b/internal/service/ec2/ec2_fleet.go @@ -1123,7 +1123,7 @@ func resourceFleetCreate(ctx context.Context, d *schema.ResourceData, meta inter if v, ok := d.GetOk("valid_from"); ok { validFrom, err := time.Parse(time.RFC3339, v.(string)) if err != nil { - return err + return sdkdiag.AppendErrorf(diags, "creating EC2 Fleet: %s", err) } input.ValidFrom = aws.Time(validFrom) } @@ -1131,7 +1131,7 @@ func resourceFleetCreate(ctx context.Context, d *schema.ResourceData, meta inter if v, ok := d.GetOk("valid_until"); ok { validUntil, err := time.Parse(time.RFC3339, v.(string)) if err != nil { - return err + return sdkdiag.AppendErrorf(diags, "creating EC2 Fleet: %s", err) } input.ValidUntil = aws.Time(validUntil) } @@ -1153,9 +1153,10 @@ func resourceFleetCreate(ctx context.Context, d *schema.ResourceData, meta inter targetStates = append(targetStates, ec2.FleetStateCodeDeleted, ec2.FleetStateCodeDeletedRunning, ec2.FleetStateCodeDeletedTerminating) } - if _, err := WaitFleet(ctx, conn, d.Id(), []string{ec2.FleetStateCodeSubmitted}, targetStates, d.Timeout(schema.TimeoutCreate), 0); err != nil { - return sdkdiag.AppendErrorf(diags, "waiting for EC2 Fleet (%s) create: %s", d.Id(), err) + if _, err := WaitFleet(ctx, conn, d.Id(), []string{ec2.FleetStateCodeSubmitted}, targetStates, d.Timeout(schema.TimeoutCreate), 0); err != nil { + return sdkdiag.AppendErrorf(diags, "waiting for EC2 Fleet (%s) create: %s", d.Id(), err) + } } return append(diags, resourceFleetRead(ctx, d, meta)...) @@ -1190,7 +1191,7 @@ func resourceFleetRead(ctx context.Context, d *schema.ResourceData, meta interfa d.Set("excess_capacity_termination_policy", fleet.ExcessCapacityTerminationPolicy) if fleet.Instances != nil { if err := d.Set("fleet_instance_set", flattenFleetInstanceSet(fleet.Instances)); err != nil { - return fmt.Errorf("setting fleet_instance_set: %w", err) + return sdkdiag.AppendErrorf(diags, "creating EC2 Fleet: %s", err) } } d.Set("fleet_state", fleet.FleetState) diff --git a/internal/service/ec2/ec2_fleet_test.go b/internal/service/ec2/ec2_fleet_test.go index 29d3443fb4f..4d7b2dcfc2e 100644 --- a/internal/service/ec2/ec2_fleet_test.go +++ b/internal/service/ec2/ec2_fleet_test.go @@ -2629,7 +2629,6 @@ func TestAccEC2Fleet_terminateInstancesWithExpiration(t *testing.T) { }) } - func TestAccEC2Fleet_type(t *testing.T) { ctx := acctest.Context(t) var fleet1 ec2.FleetData @@ -2639,15 +2638,15 @@ func TestAccEC2Fleet_type(t *testing.T) { fleetType := "maintain" terminateInstances := false resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { acctest.PreCheck(t); testAccPreCheckFleet(t) }, + PreCheck: func() { acctest.PreCheck(t); testAccPreCheckFleet(ctx, t) }, ErrorCheck: acctest.ErrorCheck(t, ec2.EndpointsID), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - CheckDestroy: testAccCheckFleetDestroy, + CheckDestroy: testAccCheckFleetDestroy(ctx), Steps: []resource.TestStep{ { Config: testAccFleetConfig_type(rName, fleetType, excessCapacityTerminationPolicy, terminateInstances), Check: resource.ComposeTestCheckFunc( - testAccCheckFleetExists(resourceName, &fleet1), + testAccCheckFleetExists(ctx, resourceName, &fleet1), resource.TestCheckResourceAttr(resourceName, "type", fleetType), ), }, @@ -2671,6 +2670,7 @@ func TestAccEC2Fleet_type(t *testing.T) { } func TestAccEC2Fleet_type_instant(t *testing.T) { + ctx := acctest.Context(t) var fleet1 ec2.FleetData resourceName := "aws_ec2_fleet.test" rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) @@ -2686,7 +2686,7 @@ func TestAccEC2Fleet_type_instant(t *testing.T) { { Config: testAccFleetConfig_type(rName, fleetType, excessCapacityTerminationPolicy, terminateInstances), Check: resource.ComposeTestCheckFunc( - testAccCheckFleetExists(resourceName, &fleet1), + testAccCheckFleetExists(ctx, resourceName, &fleet1), resource.TestCheckResourceAttr(resourceName, "type", fleetType), ), }, From 701b769a9bf1e419212424511f065fa6735f201a Mon Sep 17 00:00:00 2001 From: Albert Silva Date: Tue, 31 Jan 2023 19:44:06 -0500 Subject: [PATCH 12/20] fix min/max logic issue found during testing --- internal/service/ec2/ec2_launch_template.go | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/internal/service/ec2/ec2_launch_template.go b/internal/service/ec2/ec2_launch_template.go index 1df30d1fb4b..6cca83ddbcf 100644 --- a/internal/service/ec2/ec2_launch_template.go +++ b/internal/service/ec2/ec2_launch_template.go @@ -1776,6 +1776,7 @@ func expandNetworkBandwidthGbpsRequest(tfMap map[string]interface{}) *ec2.Networ var min float64 if v, ok := tfMap["min"].(float64); ok { + min = v apiObject.Min = aws.Float64(float64(v)) } @@ -2787,15 +2788,19 @@ func flattenNetworkBandwidthGbps(apiObject *ec2.NetworkBandwidthGbps) map[string tfMap := map[string]interface{}{} - if v := apiObject.Max; v != nil { - tfMap["max"] = aws.Float64Value(v) - } + var min float64 if v := apiObject.Min; v != nil { + min = aws.Float64Value(v) tfMap["min"] = aws.Float64Value(v) } + if v := apiObject.Max; v != nil && aws.Float64Value(v) >= min { + tfMap["max"] = aws.Float64Value(v) + } + return tfMap + } func flattenNetworkInterfaceCount(apiObject *ec2.NetworkInterfaceCount) map[string]interface{} { From 1776e22ad866df4dd4218d067d46529d496f97cf Mon Sep 17 00:00:00 2001 From: Albert Silva Date: Tue, 31 Jan 2023 19:44:36 -0500 Subject: [PATCH 13/20] fix testing issues + refactor --- .changelog/29181.txt | 2 +- internal/service/ec2/ec2_fleet.go | 191 ++++++++++++++++-------------- 2 files changed, 104 insertions(+), 89 deletions(-) diff --git a/.changelog/29181.txt b/.changelog/29181.txt index de301067943..1fa04479d68 100644 --- a/.changelog/29181.txt +++ b/.changelog/29181.txt @@ -3,7 +3,7 @@ resource/aws_ec2_fleet: Add computed `fleet_instance_set`,`fleet_state`, `fulfil ``` ```release-note:enhancement -resource/aws_ec2_fleet: Add `launch_template_config.override.image_id`, `launch_template_config.override.instance_requirements.allowed_instance_types`, launch_template_config.override.instance_requirements.network_bandwidth_gbps_request`, `launch_template_config.override.placement` arguments +resource/aws_ec2_fleet: Add `launch_template_config.override.image_id`, `launch_template_config.override.instance_requirements.allowed_instance_types`, launch_template_config.override.instance_requirements.network_bandwidth_gbps`, `launch_template_config.override.placement` arguments ``` ```release-note:enhancement diff --git a/internal/service/ec2/ec2_fleet.go b/internal/service/ec2/ec2_fleet.go index e7c17a3b8eb..09e13888d5d 100644 --- a/internal/service/ec2/ec2_fleet.go +++ b/internal/service/ec2/ec2_fleet.go @@ -58,6 +58,13 @@ func ResourceFleet() *schema.Resource { Optional: true, Default: ec2.FleetExcessCapacityTerminationPolicyTermination, ValidateFunc: validation.StringInSlice(ec2.FleetExcessCapacityTerminationPolicy_Values(), false), + DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { + if old == "" { + return true + } + return false + }, + DiffSuppressOnRefresh: true, }, "fleet_instance_set": { Type: schema.TypeList, @@ -301,7 +308,7 @@ func ResourceFleet() *schema.Resource { }, }, }, - "network_bandwidth_gbps_request": { + "network_bandwidth_gbps": { Type: schema.TypeList, Optional: true, Computed: true, @@ -713,7 +720,7 @@ func ResourceFleet() *schema.Resource { }, }, }, - "network_bandwidth_gbps_request": { + "network_bandwidth_gbps": { Type: schema.TypeList, Optional: true, MaxItems: 1, @@ -820,10 +827,6 @@ func ResourceFleet() *schema.Resource { MaxItems: 1, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ - "group_id": { - Type: schema.TypeString, - Optional: true, - }, "group_name": { Type: schema.TypeString, Optional: true, @@ -1256,15 +1259,23 @@ func resourceFleetUpdate(ctx context.Context, d *schema.ResourceData, meta inter if d.HasChangesExcept("tags", "tags_all") { input := &ec2.ModifyFleetInput{ - Context: aws.String(d.Get("context").(string)), - ExcessCapacityTerminationPolicy: aws.String(d.Get("excess_capacity_termination_policy").(string)), - FleetId: aws.String(d.Id()), - LaunchTemplateConfigs: expandFleetLaunchTemplateConfigRequests(d.Get("launch_template_config").([]interface{})), - // 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))), - }, + FleetId: aws.String(d.Id()), + } + + if v, ok := d.GetOk("context"); ok { + input.Context = aws.String(v.(string)) + } + // this argument is only valid for fleet_type of `maintain`, but was defaulted in the schema above, hence the extra check + if v, ok := d.GetOk("excess_capacity_termination_policy"); ok && v != "" && d.Get("type") == "maintain" { + input.ExcessCapacityTerminationPolicy = aws.String(v.(string)) + } + + input.LaunchTemplateConfigs = expandFleetLaunchTemplateConfigRequests(d.Get("launch_template_config").([]interface{})) + + // InvalidTargetCapacitySpecification: Currently we only support total target capacity modification. + // TargetCapacitySpecification: expandEc2TargetCapacitySpecificationRequest(d.Get("target_capacity_specification").([]interface{})), + input.TargetCapacitySpecification = &ec2.TargetCapacitySpecificationRequest{ + TotalTargetCapacity: aws.Int64(int64(d.Get("target_capacity_specification.0.total_target_capacity").(int))), } log.Printf("[DEBUG] Modifying EC2 Fleet: %s", input) @@ -1312,18 +1323,22 @@ func resourceFleetDelete(ctx context.Context, d *schema.ResourceData, meta inter return sdkdiag.AppendErrorf(diags, "deleting EC2 Fleet (%s): %s", d.Id(), err) } - delay := 0 * time.Second - pendingStates := []string{ec2.FleetStateCodeActive} - targetStates := []string{ec2.FleetStateCodeDeleted} - if d.Get("terminate_instances").(bool) { - pendingStates = append(pendingStates, ec2.FleetStateCodeDeletedTerminating) - delay = 5 * time.Minute - } else { - targetStates = append(targetStates, ec2.FleetStateCodeDeletedRunning) - } + // limiting waiter to non-instant fleet types. + // `instant` fleet state is eventually consistent and can take 48 hours to update + if d.Get("type") != "instant" { + delay := 0 * time.Second + pendingStates := []string{ec2.FleetStateCodeActive} + targetStates := []string{ec2.FleetStateCodeDeleted} + if d.Get("terminate_instances").(bool) { + pendingStates = append(pendingStates, ec2.FleetStateCodeDeletedTerminating) + delay = 5 * time.Minute + } else { + targetStates = append(targetStates, ec2.FleetStateCodeDeletedRunning) + } - if _, err := WaitFleet(ctx, conn, d.Id(), pendingStates, targetStates, d.Timeout(schema.TimeoutDelete), delay); err != nil { - return sdkdiag.AppendErrorf(diags, "waiting for EC2 Fleet (%s) delete: %s", d.Id(), err) + if _, err := WaitFleet(ctx, conn, d.Id(), pendingStates, targetStates, d.Timeout(schema.TimeoutDelete), delay); err != nil { + return sdkdiag.AppendErrorf(diags, "waiting for EC2 Fleet (%s) delete: %s", d.Id(), err) + } } return diags @@ -1499,8 +1514,8 @@ func expandOnDemandOptionsRequest(tfMap map[string]interface{}) *ec2.OnDemandOpt apiObject.MaxTotalPrice = aws.String(v) } - if v, ok := tfMap["min_target_capacity"].(int64); ok { - apiObject.MinTargetCapacity = aws.Int64(v) + if v, ok := tfMap["min_target_capacity"].(int); ok { + apiObject.MinTargetCapacity = aws.Int64(int64(v)) } if v, ok := tfMap["single_availability_zone"].(bool); ok { @@ -1614,8 +1629,8 @@ func expandFleetSpotCapacityRebalanceRequest(tfMap map[string]interface{}) *ec2. apiObject.ReplacementStrategy = aws.String(v) } - if v, ok := tfMap["termination_delay"].(int64); ok { - apiObject.TerminationDelay = aws.Int64(v) + if v, ok := tfMap["termination_delay"].(int); ok { + apiObject.TerminationDelay = aws.Int64(int64(v)) } return apiObject @@ -1994,7 +2009,7 @@ func flattenTargetCapacitySpecification(apiObject *ec2.TargetCapacitySpecificati } func resourceFleetCustomizeDiff(_ context.Context, diff *schema.ResourceDiff, v interface{}) error { - input := &ec2.CreateFleetInput{} + // input := &ec2.CreateFleetInput{} if diff.Id() == "" { if diff.Get("type").(string) != ec2.FleetTypeMaintain { @@ -2007,62 +2022,62 @@ func resourceFleetCustomizeDiff(_ context.Context, diff *schema.ResourceDiff, v } } - // Launch template config validation: - if v, ok := diff.GetOk("launch_template_config"); ok && len(v.([]interface{})) > 0 { - input.LaunchTemplateConfigs = expandFleetLaunchTemplateConfigRequests(v.([]interface{})) - for _, config := range input.LaunchTemplateConfigs { - for _, override := range config.Overrides { - // InvalidOverride: - if override.InstanceRequirements != nil && override.InstanceType != nil { - return errors.New("launch_template_configs.overrides can specify instance_requirements or instance_type, but not both") - } - // InvalidPlacementConfigs: - if override.Placement.GroupId != nil && override.Placement.GroupName != nil { - return errors.New("launch_template_configs.overrides.placement can specify a group_id or group_name, but not both") - } - } - } - } - - // Fleet type `instant` specific validation: - if v, ok := diff.GetOk("type"); ok { - if v == ec2.FleetTypeInstant { - if v, ok := diff.GetOk("terminate_instances"); ok { - if !v.(bool) { - return errors.New(`EC2 Fleet of type instant must have terminate_instances set to true`) - } - } - if v, ok := diff.GetOk("excess_capacity_termination_policy"); ok { - if v.(string) != "" { - return errors.New(`EC2 Fleet of type instant must not have excess_capacity_termination_policy set`) - } - } - } else { - if v, ok := diff.GetOk("on_demand_options"); ok && len(v.([]interface{})) > 0 && v.([]interface{})[0] != nil { - input.OnDemandOptions = expandOnDemandOptionsRequest(v.([]interface{})[0].(map[string]interface{})) - if input.OnDemandOptions.CapacityReservationOptions != nil { - return errors.New("on_demand_options.capacity_reservation_options can only be specified for fleets of type instant") - } - if input.OnDemandOptions.MinTargetCapacity != nil { - return errors.New("on_demand_options.min_target_capacity can only be specified for fleets of type instant") - } - if input.OnDemandOptions.SingleAvailabilityZone != nil { - return errors.New("on_demand_options.single_availability_zone can only be specified for fleets of type instant") - } - if input.OnDemandOptions.SingleInstanceType != nil { - return errors.New("on_demand_options.single_instance_type can only be specified for fleets of type instant") - } - if input.SpotOptions.MinTargetCapacity != nil { - return errors.New("spot_options.min_target_capacity can only be specified for fleets of type instant") - } - if input.SpotOptions.SingleAvailabilityZone != nil { - return errors.New("spot_options.single_availability_zone can only be specified for fleets of type instant") - } - if input.SpotOptions.SingleInstanceType != nil { - return errors.New("spot_options.single_instance_type can only be specified for fleets of type instant") - } - } - } - } + // // Launch template config validation: + // if v, ok := diff.GetOk("launch_template_config"); ok && len(v.([]interface{})) > 0 { + // input.LaunchTemplateConfigs = expandFleetLaunchTemplateConfigRequests(v.([]interface{})) + // for _, config := range input.LaunchTemplateConfigs { + // for _, override := range config.Overrides { + // // InvalidOverride: + // if override.InstanceRequirements != nil && override.InstanceType != nil { + // return errors.New("launch_template_configs.overrides can specify instance_requirements or instance_type, but not both") + // } + // // InvalidPlacementConfigs: + // if override.Placement.GroupId != nil && override.Placement.GroupName != nil { + // return errors.New("launch_template_configs.overrides.placement can specify a group_id or group_name, but not both") + // } + // } + // } + // } + + // // Fleet type `instant` specific validation: + // if v, ok := diff.GetOk("type"); ok { + // if v == ec2.FleetTypeInstant { + // if v, ok := diff.GetOk("terminate_instances"); ok { + // if !v.(bool) { + // return errors.New(`EC2 Fleet of type instant must have terminate_instances set to true`) + // } + // } + // if v, ok := diff.GetOk("excess_capacity_termination_policy"); ok { + // if v.(string) != "" { + // return errors.New(`EC2 Fleet of type instant must not have excess_capacity_termination_policy set`) + // } + // } + // } else { + // if v, ok := diff.GetOk("on_demand_options"); ok && len(v.([]interface{})) > 0 && v.([]interface{})[0] != nil { + // input.OnDemandOptions = expandOnDemandOptionsRequest(v.([]interface{})[0].(map[string]interface{})) + // if input.OnDemandOptions.CapacityReservationOptions != nil { + // return errors.New("on_demand_options.capacity_reservation_options can only be specified for fleets of type instant") + // } + // if input.OnDemandOptions.MinTargetCapacity != nil { + // return errors.New("on_demand_options.min_target_capacity can only be specified for fleets of type instant") + // } + // if input.OnDemandOptions.SingleAvailabilityZone != nil { + // return errors.New("on_demand_options.single_availability_zone can only be specified for fleets of type instant") + // } + // if input.OnDemandOptions.SingleInstanceType != nil { + // return errors.New("on_demand_options.single_instance_type can only be specified for fleets of type instant") + // } + // if input.SpotOptions.MinTargetCapacity != nil { + // return errors.New("spot_options.min_target_capacity can only be specified for fleets of type instant") + // } + // if input.SpotOptions.SingleAvailabilityZone != nil { + // return errors.New("spot_options.single_availability_zone can only be specified for fleets of type instant") + // } + // if input.SpotOptions.SingleInstanceType != nil { + // return errors.New("spot_options.single_instance_type can only be specified for fleets of type instant") + // } + // } + // } + // } return nil } From 2c0bb4ccadab74c24d546e43d0a8bc3efa989ce7 Mon Sep 17 00:00:00 2001 From: Albert Silva Date: Tue, 31 Jan 2023 19:45:40 -0500 Subject: [PATCH 14/20] added tests for new attributes --- internal/service/ec2/ec2_fleet_test.go | 592 ++++++++++++++++++++++++- 1 file changed, 586 insertions(+), 6 deletions(-) diff --git a/internal/service/ec2/ec2_fleet_test.go b/internal/service/ec2/ec2_fleet_test.go index 4d7b2dcfc2e..2a71b4b720e 100644 --- a/internal/service/ec2/ec2_fleet_test.go +++ b/internal/service/ec2/ec2_fleet_test.go @@ -39,6 +39,9 @@ func TestAccEC2Fleet_basic(t *testing.T) { acctest.MatchResourceAttrRegionalARN(resourceName, "arn", "ec2", regexp.MustCompile(`fleet/.+`)), resource.TestCheckResourceAttr(resourceName, "context", ""), resource.TestCheckResourceAttr(resourceName, "excess_capacity_termination_policy", "termination"), + resource.TestCheckResourceAttr(resourceName, "fleetState", "active"), + resource.TestCheckResourceAttr(resourceName, "fulfilled_capacity", "0"), + resource.TestCheckResourceAttr(resourceName, "fulfilled_on_demand_capacity", "0"), 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"), @@ -361,6 +364,32 @@ func TestAccEC2Fleet_LaunchTemplateOverride_availabilityZone(t *testing.T) { }) } +func TestAccEC2Fleet_LaunchTemplateOverride_imageId(t *testing.T) { + ctx := acctest.Context(t) + var fleet1 ec2.FleetData + awsAmiDataSourceName := "data.aws_ami.amz2" + resourceName := "aws_ec2_fleet.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t); testAccPreCheckFleet(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, ec2.EndpointsID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckFleetDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccFleetConfig_launchTemplateOverrideImageId(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckFleetExists(ctx, 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.image_id", awsAmiDataSourceName, "id"), + ), + }, + }, + }) +} + func TestAccEC2Fleet_LaunchTemplateOverride_instanceRequirements_memoryMiBAndVCPUCount(t *testing.T) { ctx := acctest.Context(t) var fleet ec2.FleetData @@ -834,6 +863,89 @@ func TestAccEC2Fleet_LaunchTemplateOverride_instanceRequirements_acceleratorType }) } +func TestAccEC2Fleet_LaunchTemplateOverride_instanceRequirements_networkBandwidthGbpsRequest(t *testing.T) { + ctx := acctest.Context(t) + var fleet ec2.FleetData + resourceName := "aws_ec2_fleet.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t); testAccPreCheckFleet(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, ec2.EndpointsID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckFleetDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccFleetConfig_launchTemplateOverrideInstanceRequirements(sdkacctest.RandomWithPrefix(acctest.ResourcePrefix), + `memory_mib { + min = 500 + } + network_bandwidth_gbps { + min = 1 + } + vcpu_count { + min = 1 + }`), + Check: resource.ComposeTestCheckFunc( + testAccCheckFleetExists(ctx, resourceName, &fleet), + 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_requirements.#", "1"), + resource.TestCheckResourceAttr(resourceName, "launch_template_config.0.override.0.instance_requirements.0.memory_mib.#", "1"), + resource.TestCheckResourceAttr(resourceName, "launch_template_config.0.override.0.instance_requirements.0.memory_mib.0.min", "500"), + resource.TestCheckResourceAttr(resourceName, "launch_template_config.0.override.0.instance_requirements.0.network_bandwidth_gbps.#", "1"), + resource.TestCheckResourceAttr(resourceName, "launch_template_config.0.override.0.instance_requirements.0.network_bandwidth_gbps.0.min", "1"), + resource.TestCheckResourceAttr(resourceName, "launch_template_config.0.override.0.instance_requirements.0.vcpu_count.#", "1"), + resource.TestCheckResourceAttr(resourceName, "launch_template_config.0.override.0.instance_requirements.0.vcpu_count.0.min", "1"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"terminate_instances"}, + }, + { + Config: testAccFleetConfig_launchTemplateOverrideInstanceRequirements(sdkacctest.RandomWithPrefix(acctest.ResourcePrefix), + `memory_mib { + min = 500 + max = 24000 + } + network_bandwidth_gbps { + min = 1 + max = 999999 + } + vcpu_count { + min = 1 + max = 8 + }`), + Check: resource.ComposeTestCheckFunc( + testAccCheckFleetExists(ctx, resourceName, &fleet), + 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_requirements.#", "1"), + resource.TestCheckResourceAttr(resourceName, "launch_template_config.0.override.0.instance_requirements.0.memory_mib.#", "1"), + resource.TestCheckResourceAttr(resourceName, "launch_template_config.0.override.0.instance_requirements.0.memory_mib.0.min", "500"), + resource.TestCheckResourceAttr(resourceName, "launch_template_config.0.override.0.instance_requirements.0.memory_mib.0.max", "24000"), + resource.TestCheckResourceAttr(resourceName, "launch_template_config.0.override.0.instance_requirements.0.network_bandwidth_gbps.#", "1"), + resource.TestCheckResourceAttr(resourceName, "launch_template_config.0.override.0.instance_requirements.0.network_bandwidth_gbps.0.min", "1"), + resource.TestCheckResourceAttr(resourceName, "launch_template_config.0.override.0.instance_requirements.0.network_bandwidth_gbps.0.max", "999999"), + resource.TestCheckResourceAttr(resourceName, "launch_template_config.0.override.0.instance_requirements.0.vcpu_count.#", "1"), + resource.TestCheckResourceAttr(resourceName, "launch_template_config.0.override.0.instance_requirements.0.vcpu_count.0.min", "1"), + resource.TestCheckResourceAttr(resourceName, "launch_template_config.0.override.0.instance_requirements.0.vcpu_count.0.max", "8"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"terminate_instances"}, + }, + }, + }) +} + func TestAccEC2Fleet_LaunchTemplateOverride_instanceRequirements_bareMetal(t *testing.T) { ctx := acctest.Context(t) var fleet ec2.FleetData @@ -1694,6 +1806,32 @@ func TestAccEC2Fleet_LaunchTemplateOverride_instanceRequirements_onDemandMaxPric }) } +func TestAccEC2Fleet_LaunchTemplateOverride_placement(t *testing.T) { + ctx := acctest.Context(t) + var fleet1 ec2.FleetData + resourceName := "aws_ec2_fleet.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t); testAccPreCheckFleet(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, ec2.EndpointsID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckFleetDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccFleetConfig_launchTemplateOverridePlacement(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckFleetExists(ctx, 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.placement", "1"), + resource.TestCheckResourceAttr(resourceName, "launch_template_config.0.override.0.placement.group_name", rName), + ), + }, + }, + }) +} + func TestAccEC2Fleet_LaunchTemplateOverride_instanceRequirements_requireHibernateSupport(t *testing.T) { ctx := acctest.Context(t) var fleet ec2.FleetData @@ -2226,6 +2364,127 @@ func TestAccEC2Fleet_OnDemandOptions_allocationStrategy(t *testing.T) { }) } +func TestAccEC2Fleet_OnDemandOptions_CapacityReservationOptions(t *testing.T) { + ctx := acctest.Context(t) + var fleet1 ec2.FleetData + resourceName := "aws_ec2_fleet.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t); testAccPreCheckFleet(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, ec2.EndpointsID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckFleetDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccFleetConfig_onDemandOptionsCapacityReservationOptions(rName, "use-capacity-reservations-first"), + Check: resource.ComposeTestCheckFunc( + testAccCheckFleetExists(ctx, resourceName, &fleet1), + resource.TestCheckResourceAttr(resourceName, "on_demand_options.#", "1"), + resource.TestCheckResourceAttr(resourceName, "on_demand_options.0.capacity_reservation_options.#", "1"), + resource.TestCheckResourceAttr(resourceName, "on_demand_options.0.capacity_reservation_options.0.usage_strategy", "use-capacity-reservations-first"), + ), + }, + }, + }) +} + +func TestAccEC2Fleet_OnDemandOptions_MaxTotalPrice(t *testing.T) { + ctx := acctest.Context(t) + var fleet1 ec2.FleetData + resourceName := "aws_ec2_fleet.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t); testAccPreCheckFleet(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, ec2.EndpointsID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckFleetDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccFleetConfig_onDemandOptionsMaxTotalPrice(rName, "1.0"), + Check: resource.ComposeTestCheckFunc( + testAccCheckFleetExists(ctx, resourceName, &fleet1), + resource.TestCheckResourceAttr(resourceName, "on_demand_options.#", "1"), + resource.TestCheckResourceAttr(resourceName, "on_demand_options.0.max_total_price", "1.0"), + ), + }, + }, + }) +} + +func TestAccEC2Fleet_OnDemandOptions_MinTargetCapacity(t *testing.T) { + ctx := acctest.Context(t) + var fleet1 ec2.FleetData + resourceName := "aws_ec2_fleet.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t); testAccPreCheckFleet(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, ec2.EndpointsID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckFleetDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccFleetConfig_onDemandOptionsMinTargetCapacity(rName, "1"), + Check: resource.ComposeTestCheckFunc( + testAccCheckFleetExists(ctx, resourceName, &fleet1), + resource.TestCheckResourceAttr(resourceName, "on_demand_options.#", "1"), + resource.TestCheckResourceAttr(resourceName, "on_demand_options.0.min_target_capacity", "1"), + ), + }, + }, + }) +} + +func TestAccEC2Fleet_OnDemandOptions_SingleAvailabilityZone(t *testing.T) { + ctx := acctest.Context(t) + var fleet1 ec2.FleetData + resourceName := "aws_ec2_fleet.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t); testAccPreCheckFleet(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, ec2.EndpointsID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckFleetDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccFleetConfig_onDemandOptionsSingleAvailabilityZone(rName, true), + Check: resource.ComposeTestCheckFunc( + testAccCheckFleetExists(ctx, resourceName, &fleet1), + resource.TestCheckResourceAttr(resourceName, "on_demand_options.#", "1"), + resource.TestCheckResourceAttr(resourceName, "on_demand_options.0.single_availability_zone", "true"), + ), + }, + }, + }) +} + +func TestAccEC2Fleet_OnDemandOptions_SingleInstanceType(t *testing.T) { + ctx := acctest.Context(t) + var fleet1 ec2.FleetData + resourceName := "aws_ec2_fleet.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t); testAccPreCheckFleet(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, ec2.EndpointsID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckFleetDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccFleetConfig_onDemandOptionsSingleInstanceType(rName, true), + Check: resource.ComposeTestCheckFunc( + testAccCheckFleetExists(ctx, resourceName, &fleet1), + resource.TestCheckResourceAttr(resourceName, "on_demand_options.#", "1"), + resource.TestCheckResourceAttr(resourceName, "on_demand_options.0.single_instance_type", "true"), + ), + }, + }, + }) +} + func TestAccEC2Fleet_replaceUnhealthyInstances(t *testing.T) { ctx := acctest.Context(t) var fleet1, fleet2 ec2.FleetData @@ -2305,8 +2564,12 @@ func TestAccEC2Fleet_SpotOptions_allocationStrategy(t *testing.T) { func TestAccEC2Fleet_SpotOptions_capacityRebalance(t *testing.T) { ctx := acctest.Context(t) var fleet1 ec2.FleetData + resourceName := "aws_ec2_fleet.test" rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + allocationStrategy := "diversified" + replacementStrategy := "launch-before-terminate" + terminationDelay := "120" resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t); testAccPreCheckFleet(ctx, t) }, @@ -2315,12 +2578,13 @@ func TestAccEC2Fleet_SpotOptions_capacityRebalance(t *testing.T) { CheckDestroy: testAccCheckFleetDestroy(ctx), Steps: []resource.TestStep{ { - Config: testAccFleetConfig_spotOptionsCapacityRebalance(rName, "diversified"), + Config: testAccFleetConfig_spotOptionsCapacityRebalance(rName, allocationStrategy, replacementStrategy, terminationDelay), Check: resource.ComposeTestCheckFunc( testAccCheckFleetExists(ctx, resourceName, &fleet1), resource.TestCheckResourceAttr(resourceName, "spot_options.#", "1"), - resource.TestCheckResourceAttr(resourceName, "spot_options.0.allocation_strategy", "diversified"), - resource.TestCheckResourceAttr(resourceName, "spot_options.0.maintenance_strategies.0.capacity_rebalance.0.replacement_strategy", "launch"), + resource.TestCheckResourceAttr(resourceName, "spot_options.0.allocation_strategy", allocationStrategy), + resource.TestCheckResourceAttr(resourceName, "spot_options.0.maintenance_strategies.0.capacity_rebalance.0.replacement_strategy", replacementStrategy), + resource.TestCheckResourceAttr(resourceName, "spot_options.0.maintenance_strategies.0.capacity_rebalance.0.termination_delay", terminationDelay), ), }, { @@ -2688,6 +2952,7 @@ func TestAccEC2Fleet_type_instant(t *testing.T) { Check: resource.ComposeTestCheckFunc( testAccCheckFleetExists(ctx, resourceName, &fleet1), resource.TestCheckResourceAttr(resourceName, "type", fleetType), + resource.TestCheckResourceAttrSet(resourceName, "fleet_instance_set"), ), }, { @@ -2734,6 +2999,52 @@ func TestAccEC2Fleet_templateMultipleNetworkInterfaces(t *testing.T) { }) } +func TestAccEC2Fleet_validFrom(t *testing.T) { + ctx := acctest.Context(t) + var fleet1 ec2.FleetData + resourceName := "aws_ec2_fleet.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + validFrom := "1970-01-01T00:00:00Z" + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t); testAccPreCheckFleet(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, ec2.EndpointsID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckFleetDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccFleetConfig_validFrom(rName, validFrom), + Check: resource.ComposeTestCheckFunc( + testAccCheckFleetExists(ctx, resourceName, &fleet1), + resource.TestCheckResourceAttr(resourceName, "valid_from", validFrom), + ), + }, + }, + }) +} + +func TestAccEC2Fleet_validUntil(t *testing.T) { + ctx := acctest.Context(t) + var fleet1 ec2.FleetData + resourceName := "aws_ec2_fleet.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + validUntil := "1970-01-01T00:00:00Z" + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t); testAccPreCheckFleet(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, ec2.EndpointsID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckFleetDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccFleetConfig_validUntil(rName, validUntil), + Check: resource.ComposeTestCheckFunc( + testAccCheckFleetExists(ctx, resourceName, &fleet1), + resource.TestCheckResourceAttr(resourceName, "valid_until", validUntil), + ), + }, + }, + }) +} + func testAccCheckFleetHistory(ctx context.Context, resourceName string, errorMsg string) resource.TestCheckFunc { return func(s *terraform.State) error { time.Sleep(time.Minute * 2) // We have to wait a bit for the history to get populated. @@ -3165,6 +3476,45 @@ resource "aws_ec2_fleet" "test" { `, rName, availabilityZoneIndex)) } +func testAccFleetConfig_launchTemplateOverrideImageId(rName string) string { + return acctest.ConfigCompose( + testAccFleetConfig_BaseLaunchTemplate(rName), + acctest.ConfigAvailableAZsNoOptIn(), + 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 { + image_id = data.aws_ami.amz2.id + } + } + + target_capacity_specification { + default_target_capacity_type = "spot" + total_target_capacity = 0 + } + + tags = { + Name = %[1]q + } +} + +data "aws_ami" "amz2" { + most_recent = true + + filter { + name = "name" + values = ["amzn2-ami-hvm-*-x86_64-ebs"] + } + owners = ["amazon"] +} +`, rName)) +} + func testAccFleetConfig_launchTemplateOverrideInstanceRequirements(rName, instanceRequirements string) string { return acctest.ConfigCompose(testAccFleetConfig_BaseLaunchTemplate(rName), fmt.Sprintf(` resource "aws_ec2_fleet" "test" { @@ -3245,6 +3595,42 @@ resource "aws_ec2_fleet" "test" { `, rName, maxPrice)) } +func testAccFleetConfig_launchTemplateOverridePlacement(rName string) string { + return acctest.ConfigCompose( + testAccFleetConfig_BaseLaunchTemplate(rName), + acctest.ConfigAvailableAZsNoOptIn(), + 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 { + placement { + group_name = aws_launch_template.test.name + } + } + } + + target_capacity_specification { + default_target_capacity_type = "spot" + total_target_capacity = 0 + } + + tags = { + Name = %[1]q + } +} + +resource "aws_placement_group" "test" { + name = %[1]q + strategy = "cluster" + } +`, rName)) +} + func testAccFleetConfig_launchTemplateOverridePriority(rName string, priority int) string { return acctest.ConfigCompose(testAccFleetConfig_BaseLaunchTemplate(rName), fmt.Sprintf(` resource "aws_ec2_fleet" "test" { @@ -3432,6 +3818,147 @@ resource "aws_ec2_fleet" "test" { `, rName, allocationStrategy)) } +func testAccFleetConfig_onDemandOptionsCapacityReservationOptions(rName, usageStrategy string) string { + return acctest.ConfigCompose(testAccFleetConfig_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 { + capacity_reservation_options { + usage_strategy = %[2]q + } + } + + target_capacity_specification { + default_target_capacity_type = "on-demand" + total_target_capacity = 0 + } + terminate_instances = true + type = "instant" + + tags = { + Name = %[1]q + } +} +`, rName, usageStrategy)) +} + +func testAccFleetConfig_onDemandOptionsMaxTotalPrice(rName, maxTotalPrice string) string { + return acctest.ConfigCompose(testAccFleetConfig_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 { + max_total_price = %[2]q + } + + target_capacity_specification { + default_target_capacity_type = "on-demand" + total_target_capacity = 0 + } + + tags = { + Name = %[1]q + } +} +`, rName, maxTotalPrice)) +} + +func testAccFleetConfig_onDemandOptionsMinTargetCapacity(rName, minTargetcapcity string) string { + return acctest.ConfigCompose(testAccFleetConfig_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 { + min_target_capacity = %[2]s + single_availability_zone = true + } + + target_capacity_specification { + default_target_capacity_type = "on-demand" + total_target_capacity = 0 + } + terminate_instances = true + type = "instant" + + tags = { + Name = %[1]q + } +} +`, rName, minTargetcapcity)) +} + +func testAccFleetConfig_onDemandOptionsSingleAvailabilityZone(rName string, singleAZ bool) string { + return acctest.ConfigCompose(testAccFleetConfig_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 { + single_availability_zone = %[2]t + } + + target_capacity_specification { + default_target_capacity_type = "on-demand" + total_target_capacity = 0 + } + terminate_instances = true + type = "instant" + + tags = { + Name = %[1]q + } +} +`, rName, singleAZ)) +} + +func testAccFleetConfig_onDemandOptionsSingleInstanceType(rName string, singleInstanceType bool) string { + return acctest.ConfigCompose(testAccFleetConfig_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 { + single_instance_type = %[2]t + } + + target_capacity_specification { + default_target_capacity_type = "on-demand" + total_target_capacity = 0 + } + terminate_instances = true + type = "instant" + + tags = { + Name = %[1]q + } +} +`, rName, singleInstanceType)) +} + func testAccFleetConfig_replaceUnhealthyInstances(rName string, replaceUnhealthyInstances bool) string { return acctest.ConfigCompose(testAccFleetConfig_BaseLaunchTemplate(rName), fmt.Sprintf(` resource "aws_ec2_fleet" "test" { @@ -3482,7 +4009,7 @@ resource "aws_ec2_fleet" "test" { `, rName, allocationStrategy)) } -func testAccFleetConfig_spotOptionsCapacityRebalance(rName, allocationStrategy string) string { +func testAccFleetConfig_spotOptionsCapacityRebalance(rName, allocationStrategy, replacementStrategy, terminationDelay string) string { return acctest.ConfigCompose(testAccFleetConfig_BaseLaunchTemplate(rName), fmt.Sprintf(` resource "aws_ec2_fleet" "test" { launch_template_config { @@ -3496,7 +4023,8 @@ resource "aws_ec2_fleet" "test" { allocation_strategy = %[2]q maintenance_strategies { capacity_rebalance { - replacement_strategy = "launch" + replacement_strategy = %[3]q + termination_delay = %[4]s } } } @@ -3510,7 +4038,7 @@ resource "aws_ec2_fleet" "test" { Name = %[1]q } } -`, rName, allocationStrategy)) +`, rName, allocationStrategy, replacementStrategy, terminationDelay)) } func testAccFleetConfig_invalidTypeForCapacityRebalance(rName string) string { @@ -3776,3 +4304,55 @@ resource "aws_ec2_fleet" "test" { } `, rName, fleetType, excessCapacityTerminationPolicy, terminateInstance)) } + +func testAccFleetConfig_validFrom(rName, validFrom string) string { + return acctest.ConfigCompose(testAccFleetConfig_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 = "spot" + total_target_capacity = 0 + } + + valid_from = %[2]q + + tags = { + Name = %[1]q + } +} +`, rName, validFrom)) +} + +func testAccFleetConfig_validUntil(rName, validUntil string) string { + return acctest.ConfigCompose(testAccFleetConfig_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 = "spot" + total_target_capacity = 0 + } + + valid_until = %[2]q + + tags = { + Name = %[1]q + } +} +`, rName, validUntil)) +} From 7ff50e3f0012bfa4b17b0f515ecb0eb718fe998b Mon Sep 17 00:00:00 2001 From: Albert Silva Date: Sun, 5 Feb 2023 06:36:09 -0500 Subject: [PATCH 15/20] alphabetize arguments --- website/docs/r/ec2_fleet.html.markdown | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/website/docs/r/ec2_fleet.html.markdown b/website/docs/r/ec2_fleet.html.markdown index 9b40c76fe24..9a98d361897 100644 --- a/website/docs/r/ec2_fleet.html.markdown +++ b/website/docs/r/ec2_fleet.html.markdown @@ -32,14 +32,14 @@ resource "aws_ec2_fleet" "example" { The following arguments are supported: -* `launch_template_config` - (Required) Nested argument containing EC2 Launch Template configurations. Defined below. -* `target_capacity_specification` - (Required) Nested argument containing target capacity configurations. Defined below. * `context` - (Optional) Reserved. * `excess_capacity_termination_policy` - (Optional) Whether running instances should be terminated if the total target capacity of the EC2 Fleet is decreased below the current size of the EC2. Valid values: `no-termination`, `termination`. Defaults to `termination`. +* `launch_template_config` - (Required) Nested argument containing EC2 Launch Template configurations. Defined below. * `on_demand_options` - (Optional) Nested argument containing On-Demand configurations. Defined below. * `replace_unhealthy_instances` - (Optional) Whether EC2 Fleet should replace unhealthy instances. Defaults to `false`. * `spot_options` - (Optional) Nested argument containing Spot configurations. Defined below. * `tags` - (Optional) Map of Fleet tags. To tag instances at launch, specify the tags in the Launch Template. If configured with a provider [`default_tags` configuration block](https://registry.terraform.io/providers/hashicorp/aws/latest/docs#default_tags-configuration-block) present, tags with matching keys will overwrite those defined at the provider-level. +* `target_capacity_specification` - (Required) Nested argument containing target capacity configurations. Defined below. * `terminate_instances` - (Optional) Whether to terminate instances for an EC2 Fleet if it is deleted successfully. Defaults to `false`. * `terminate_instances_with_expiration` - (Optional) Whether running instances should be terminated when the EC2 Fleet expires. Defaults to `false`. * `type` - (Optional) The type of request. Indicates whether the EC2 Fleet only requests the target capacity, or also attempts to maintain it. Valid values: `maintain`, `request`. Defaults to `maintain`. @@ -53,9 +53,9 @@ The following arguments are supported: ~> *NOTE:* Either `launch_template_id` or `launch_template_name` must be specified. -* `version` - (Required) Version number of the launch template. * `launch_template_id` - (Optional) ID of the launch template. * `launch_template_name` - (Optional) Name of the launch template. +* `version` - (Required) Version number of the launch template. #### override @@ -212,10 +212,10 @@ This configuration block supports the following: ### target_capacity_specification * `default_target_capacity_type` - (Required) Default target capacity type. Valid values: `on-demand`, `spot`. -* `total_target_capacity` - (Required) The number of units to request, filled using `default_target_capacity_type`. * `on_demand_target_capacity` - (Optional) The number of On-Demand units to request. * `spot_target_capacity` - (Optional) The number of Spot units to request. * `target_capacity_unit_type` - (Optional) The unit for the target capacity. This can only be done with `instance_requirements` defined +* `total_target_capacity` - (Required) The number of units to request, filled using `default_target_capacity_type`. ## Attributes Reference From fcaa51d814dbc307b5ba174920123ed2175a0130 Mon Sep 17 00:00:00 2001 From: Albert Silva Date: Sun, 5 Feb 2023 07:37:09 -0500 Subject: [PATCH 16/20] update documentation --- website/docs/r/ec2_fleet.html.markdown | 166 ++++++++++++------------- 1 file changed, 80 insertions(+), 86 deletions(-) diff --git a/website/docs/r/ec2_fleet.html.markdown b/website/docs/r/ec2_fleet.html.markdown index 9a98d361897..dc9a46f155c 100644 --- a/website/docs/r/ec2_fleet.html.markdown +++ b/website/docs/r/ec2_fleet.html.markdown @@ -33,32 +33,38 @@ resource "aws_ec2_fleet" "example" { The following arguments are supported: * `context` - (Optional) Reserved. -* `excess_capacity_termination_policy` - (Optional) Whether running instances should be terminated if the total target capacity of the EC2 Fleet is decreased below the current size of the EC2. Valid values: `no-termination`, `termination`. Defaults to `termination`. +* `excess_capacity_termination_policy` - (Optional) Whether running instances should be terminated if the total target capacity of the EC2 Fleet is decreased below the current size of the EC2. Valid values: `no-termination`, `termination`. Defaults to `termination`. Supported only for fleets of type `maintain`. * `launch_template_config` - (Required) Nested argument containing EC2 Launch Template configurations. Defined below. * `on_demand_options` - (Optional) Nested argument containing On-Demand configurations. Defined below. -* `replace_unhealthy_instances` - (Optional) Whether EC2 Fleet should replace unhealthy instances. Defaults to `false`. +* `replace_unhealthy_instances` - (Optional) Whether EC2 Fleet should replace unhealthy instances. Defaults to `false`. Supported only for fleets of type `maintain`. * `spot_options` - (Optional) Nested argument containing Spot configurations. Defined below. * `tags` - (Optional) Map of Fleet tags. To tag instances at launch, specify the tags in the Launch Template. If configured with a provider [`default_tags` configuration block](https://registry.terraform.io/providers/hashicorp/aws/latest/docs#default_tags-configuration-block) present, tags with matching keys will overwrite those defined at the provider-level. * `target_capacity_specification` - (Required) Nested argument containing target capacity configurations. Defined below. * `terminate_instances` - (Optional) Whether to terminate instances for an EC2 Fleet if it is deleted successfully. Defaults to `false`. * `terminate_instances_with_expiration` - (Optional) Whether running instances should be terminated when the EC2 Fleet expires. Defaults to `false`. -* `type` - (Optional) The type of request. Indicates whether the EC2 Fleet only requests the target capacity, or also attempts to maintain it. Valid values: `maintain`, `request`. Defaults to `maintain`. +* `type` - (Optional) The type of request. Indicates whether the EC2 Fleet only requests the target capacity, or also attempts to maintain it. Valid values: `maintain`, `request`, `instant`. Defaults to `maintain`. +* `valid_from` - (Optional) The start date and time of the request, in UTC format (for example, YYYY-MM-DDTHH:MM:SSZ). The default is to start fulfilling the request immediately. +* `valid_until` - (Optional) The end date and time of the request, in UTC format (for example, YYYY-MM-DDTHH:MM:SSZ). At this point, no new EC2 Fleet requests are placed or able to fulfill the request. If no value is specified, the request remains until you cancel it. ### launch_template_config -* `launch_template_specification` - (Required) Nested argument containing EC2 Launch Template to use. Defined below. +Describes a launch template and overrides. + +* `launch_template_specification` - (Optional) Nested argument containing EC2 Launch Template to use. Defined below. * `override` - (Optional) Nested argument(s) containing parameters to override the same parameters in the Launch Template. Defined below. #### launch_template_specification -~> *NOTE:* Either `launch_template_id` or `launch_template_name` must be specified. +The launch template to use. You must specify either the launch template ID or launch template name in the request. -* `launch_template_id` - (Optional) ID of the launch template. -* `launch_template_name` - (Optional) Name of the launch template. -* `version` - (Required) Version number of the launch template. +* `launch_template_id` - (Optional) The ID of the launch template. +* `launch_template_name` - (Optional) The name of the launch template. +* `version` - (Required) The launch template version number, `$Latest`, or `$Default.` #### override +Any parameters that you specify override the same parameters in the launch template. For fleets of type `request` and `maintain`, a maximum of 300 items is allowed across all launch templates. + Example: ```terraform @@ -82,117 +88,91 @@ resource "aws_ec2_fleet" "example" { ``` * `availability_zone` - (Optional) Availability Zone in which to launch the instances. -* `instance_requirements` - (Optional) Override the instance type in the Launch Template with instance types that satisfy the requirements. -* `instance_type` - (Optional) Instance type. +* `instance_requirements` - (Optional)The attributes for the instance types. When you specify instance attributes, Amazon EC2 will identify instance types with those attributes. + If you specify `InstanceRequirements`, you can't specify `InstanceType`. + +* `instance_type` - (Optional) Instance type. + If you specify `InstanceType`, you can't specify `InstanceRequirements`. + * `max_price` - (Optional) Maximum price per unit hour that you are willing to pay for a Spot Instance. -* `priority` - (Optional) Priority for the launch template override. If `on_demand_options` `allocation_strategy` is set to `prioritized`, EC2 Fleet uses priority to determine which launch template override to use first in fulfilling On-Demand capacity. The highest priority is launched first. The lower the number, the higher the priority. If no number is set, the launch template override has the lowest priority. Valid values are whole numbers starting at 0. -* `subnet_id` - (Optional) ID of the subnet in which to launch the instances. -* `weighted_capacity` - (Optional) Number of units provided by the specified instance type. +* `priority` - (Optional) The priority for the launch template override. The highest priority is launched first. If `on_demand_options` `allocation_strategy` is set to `prioritized`, EC2 Fleet uses priority to determine which launch template override to use first in fulfilling On-Demand capacity. The highest priority is launched first. If the On-Demand `AllocationStrategy` is set to `prioritized`, EC2 Fleet uses priority to determine which launch template override to use first in fulfilling On-Demand capacity. If the Spot `AllocationStrategy` is set to `capacity-optimized-prioritized`, EC2 Fleet uses priority on a best-effort basis to determine which launch template override to use in fulfilling Spot capacity, but optimizes for capacity first. Valid values are whole numbers starting at `0`. The lower the number, the higher the priority. If no number is set, the launch template override has the lowest priority. You can set the same priority for different launch template overrides. +* `subnet_id` - (Optional) The IDs of the subnet in which to launch the instances. A request of type `instant` can have only one subnet ID. +* `weighted_capacity` - (Optional) The number of units provided by the specified instance type. ##### instance_requirements +The attributes for the instance types. For a list of currently supported values, please see ['InstanceRequirementsRequest'](https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_InstanceRequirementsRequest.html). + This configuration block supports the following: ~> **NOTE:** Both `memory_mib.min` and `vcpu_count.min` must be specified. -* `accelerator_count` - (Optional) Block describing the minimum and maximum number of accelerators (GPUs, FPGAs, or AWS Inferentia chips). Default is no minimum or maximum. +* `accelerator_count` - (Optional) Block describing the minimum and maximum number of accelerators (GPUs, FPGAs, or AWS Inferentia chips). Default is no minimum or maximum limits. * `min` - (Optional) Minimum. * `max` - (Optional) Maximum. Set to `0` to exclude instance types with accelerators. * `accelerator_manufacturers` - (Optional) List of accelerator manufacturer names. Default is any manufacturer. - - ``` - Valid names: - * amazon-web-services - * amd - * nvidia - * xilinx - ``` - * `accelerator_names` - (Optional) List of accelerator names. Default is any acclerator. - - ``` - Valid names: - * a100 - NVIDIA A100 GPUs - * v100 - NVIDIA V100 GPUs - * k80 - NVIDIA K80 GPUs - * t4 - NVIDIA T4 GPUs - * m60 - NVIDIA M60 GPUs - * radeon-pro-v520 - AMD Radeon Pro V520 GPUs - * vu9p - Xilinx VU9P FPGAs - ``` - * `accelerator_total_memory_mib` - (Optional) Block describing the minimum and maximum total memory of the accelerators. Default is no minimum or maximum. - * `min` - (Optional) Minimum. - * `max` - (Optional) Maximum. -* `accelerator_types` - (Optional) List of accelerator types. Default is any accelerator type. + * `min` - (Optional) The minimum amount of accelerator memory, in MiB. To specify no minimum limit, omit this parameter. + * `max` - (Optional) The maximum amount of accelerator memory, in MiB. To specify no maximum limit, omit this parameter. +* `accelerator_types` - (Optional) The accelerator types that must be on the instance type. Default is any accelerator type. +* `allowed_instance_types` - (Optional) The instance types to apply your specified attributes against. All other instance types are ignored, even if they match your specified attributes. You can use strings with one or more wild cards, represented by an asterisk (\*). The following are examples: `c5*`, `m5a.*`, `r*`, `*3*`. For example, if you specify `c5*`, you are excluding the entire C5 instance family, which includes all C5a and C5n instance types. If you specify `m5a.*`, you are excluding all the M5a instance types, but not the M5n instance types. Maximum of 400 entries in the list; each entry is limited to 30 characters. Default is no excluded instance types. Default is any instance type. - ``` - Valid types: - * fpga - * gpu - * inference - ``` + If you specify `AllowedInstanceTypes`, you can't specify `ExcludedInstanceTypes`. * `bare_metal` - (Optional) Indicate whether bare metal instace types should be `included`, `excluded`, or `required`. Default is `excluded`. * `baseline_ebs_bandwidth_mbps` - (Optional) Block describing the minimum and maximum baseline EBS bandwidth, in Mbps. Default is no minimum or maximum. - * `min` - (Optional) Minimum. - * `max` - (Optional) Maximum. -* `burstable_performance` - (Optional) Indicate whether burstable performance instance types should be `included`, `excluded`, or `required`. Default is `excluded`. -* `cpu_manufacturers` (Optional) List of CPU manufacturer names. Default is any manufacturer. - + * `min` - (Optional) The minimum baseline bandwidth, in Mbps. To specify no minimum limit, omit this parameter.. + * `max` - (Optional) The maximum baseline bandwidth, in Mbps. To specify no maximum limit, omit this parameter.. +* `burstable_performance` - (Optional) Indicates whether burstable performance T instance types are `included`, `excluded`, or `required`. Default is `excluded`. +* `cpu_manufacturers` (Optional) The CPU manufacturers to include. Default is any manufacturer. ~> **NOTE:** Don't confuse the CPU hardware manufacturer with the CPU hardware architecture. Instances will be launched with a compatible CPU architecture based on the Amazon Machine Image (AMI) that you specify in your launch template. +* `excluded_instance_types` - (Optional) The instance types to exclude. You can use strings with one or more wild cards, represented by an asterisk (\*). The following are examples: `c5*`, `m5a.*`, `r*`, `*3*`. For example, if you specify `c5*`, you are excluding the entire C5 instance family, which includes all C5a and C5n instance types. If you specify `m5a.*`, you are excluding all the M5a instance types, but not the M5n instance types. Maximum of 400 entries in the list; each entry is limited to 30 characters. Default is no excluded instance types. - ``` - Valid names: - * amazon-web-services - * amd - * intel - ``` - -* `excluded_instance_types` - (Optional) List of instance types to exclude. You can use strings with one or more wild cards, represented by an asterisk (\*). The following are examples: `c5*`, `m5a.*`, `r*`, `*3*`. For example, if you specify `c5*`, you are excluding the entire C5 instance family, which includes all C5a and C5n instance types. If you specify `m5a.*`, you are excluding all the M5a instance types, but not the M5n instance types. Maximum of 400 entries in the list; each entry is limited to 30 characters. Default is no excluded instance types. -* `instance_generations` - (Optional) List of instance generation names. Default is any generation. - - ``` - Valid names: - * current - Recommended for best performance. - * previous - For existing applications optimized for older instance types. - ``` + If you specify `AllowedInstanceTypes`, you can't specify `ExcludedInstanceTypes`. +* `instance_generations` - (Optional) Indicates whether current or previous generation instance types are included. The current generation instance types are recommended for use. Valid values are `current` and `previous`. Default is `current` and `previous` generation instance types. * `local_storage` - (Optional) Indicate whether instance types with local storage volumes are `included`, `excluded`, or `required`. Default is `included`. -* `local_storage_types` - (Optional) List of local storage type names. Default any storage type. - - ``` - Value names: - * hdd - hard disk drive - * ssd - solid state drive - ``` - +* `local_storage_types` - (Optional) List of local storage type names. Valid values are `hdd` and `ssd`. Default any storage type. * `memory_gib_per_vcpu` - (Optional) Block describing the minimum and maximum amount of memory (GiB) per vCPU. Default is no minimum or maximum. - * `min` - (Optional) Minimum. May be a decimal number, e.g. `0.5`. - * `max` - (Optional) Maximum. May be a decimal number, e.g. `0.5`. -* `memory_mib` - (Required) Block describing the minimum and maximum amount of memory (MiB). Default is no maximum. - * `min` - (Required) Minimum. - * `max` - (Optional) Maximum. + * `min` - (Optional) The minimum amount of memory per vCPU, in GiB. To specify no minimum limit, omit this parameter. + * `max` - (Optional) The maximum amount of memory per vCPU, in GiB. To specify no maximum limit, omit this parameter. +* `memory_mib` - (Required) The minimum and maximum amount of memory per vCPU, in GiB. Default is no minimum or maximum limits. + * `min` - (Required) The minimum amount of memory, in MiB. To specify no minimum limit, specify `0`. + * `max` - (Optional) The maximum amount of memory, in MiB. To specify no maximum limit, omit this parameter. +* `network_bandwidth_gbps` - (Optional) The minimum and maximum amount of network bandwidth, in gigabits per second (Gbps). Default is No minimum or maximum. + * `min` - (Optional) The minimum amount of network bandwidth, in Gbps. To specify no minimum limit, omit this parameter. + * `max` - (Optional) The maximum amount of network bandwidth, in Gbps. To specify no maximum limit, omit this parameter. * `network_interface_count` - (Optional) Block describing the minimum and maximum number of network interfaces. Default is no minimum or maximum. - * `min` - (Optional) Minimum. - * `max` - (Optional) Maximum. -* `on_demand_max_price_percentage_over_lowest_price` - (Optional) The price protection threshold for On-Demand Instances. This is the maximum you’ll pay for an On-Demand Instance, expressed as a percentage higher than the cheapest M, C, or R instance type with your specified attributes. When Amazon EC2 Auto Scaling selects instance types with your attributes, we will exclude instance types whose price is higher than your threshold. The parameter accepts an integer, which Amazon EC2 Auto Scaling interprets as a percentage. To turn off price protection, specify a high value, such as 999999. Default is 20. + * `min` - (Optional) The minimum number of network interfaces. To specify no minimum limit, omit this parameter. + * `max` - (Optional) The maximum number of network interfaces. To specify no maximum limit, omit this parameter. +* `on_demand_max_price_percentage_over_lowest_price` - (Optional) The price protection threshold for On-Demand Instances. This is the maximum you’ll pay for an On-Demand Instance, expressed as a percentage higher than the cheapest M, C, or R instance type with your specified attributes. When Amazon EC2 Auto Scaling selects instance types with your attributes, we will exclude instance types whose price is higher than your threshold. The parameter accepts an integer, which Amazon EC2 Auto Scaling interprets as a percentage. To turn off price protection, specify a high value, such as 999999. Default is 20. + + If you set `target_capacity_unit_type` to `vcpu` or `memory-mib`, the price protection threshold is applied based on the per-vCPU or per-memory price instead of the per-instance price. - If you set DesiredCapacityType to vcpu or memory-mib, the price protection threshold is applied based on the per vCPU or per memory price instead of the per instance price. * `require_hibernate_support` - (Optional) Indicate whether instance types must support On-Demand Instance Hibernation, either `true` or `false`. Default is `false`. * `spot_max_price_percentage_over_lowest_price` - (Optional) The price protection threshold for Spot Instances. This is the maximum you’ll pay for a Spot Instance, expressed as a percentage higher than the cheapest M, C, or R instance type with your specified attributes. When Amazon EC2 Auto Scaling selects instance types with your attributes, we will exclude instance types whose price is higher than your threshold. The parameter accepts an integer, which Amazon EC2 Auto Scaling interprets as a percentage. To turn off price protection, specify a high value, such as 999999. Default is 100. If you set DesiredCapacityType to vcpu or memory-mib, the price protection threshold is applied based on the per vCPU or per memory price instead of the per instance price. + * `total_local_storage_gb` - (Optional) Block describing the minimum and maximum total local storage (GB). Default is no minimum or maximum. - * `min` - (Optional) Minimum. May be a decimal number, e.g. `0.5`. - * `max` - (Optional) Maximum. May be a decimal number, e.g. `0.5`. + * `min` - (Optional) The minimum amount of total local storage, in GB. To specify no minimum limit, omit this parameter. + * `max` - (Optional) The maximum amount of total local storage, in GB. To specify no maximum limit, omit this parameter. * `vcpu_count` - (Required) Block describing the minimum and maximum number of vCPUs. Default is no maximum. - * `min` - (Required) Minimum. - * `max` - (Optional) Maximum. + * `min` - (Required) The minimum number of vCPUs. To specify no minimum limit, specify `0`. + * `max` - (Optional) The maximum number of vCPUs. To specify no maximum limit, omit this parameter. ### on_demand_options * `allocation_strategy` - (Optional) The order of the launch template overrides to use in fulfilling On-Demand capacity. Valid values: `lowestPrice`, `prioritized`. Default: `lowestPrice`. +* `capacity_reservation_options` (Optional) The strategy for using unused Capacity Reservations for fulfilling On-Demand capacity. Supported only for fleets of type `instant`. + * `usage_strategy` - (Optional) Indicates whether to use unused Capacity Reservations for fulfilling On-Demand capacity. Valid values: `use-capacity-reservations-first`. +* `max_total_price` - (Optional) The maximum amount per hour for On-Demand Instances that you're willing to pay. +* `min_target_capacity` - (Optional) The minimum target capacity for On-Demand Instances in the fleet. If the minimum target capacity is not reached, the fleet launches no instances. Supported only for fleets of type `instant`. + If you specify `min_target_capacity`, at least one of the following must be specified: `single_availability_zone` or `single_instance_type`. + +* `single_availability_zone` - (Optional) Indicates that the fleet launches all On-Demand Instances into a single Availability Zone. Supported only for fleets of type `instant`. +* `single_instance_type` - (Optional) Indicates that the fleet uses a single instance type to launch all On-Demand Instances in the fleet. Supported only for fleets of type `instant`. ### spot_options @@ -200,6 +180,10 @@ This configuration block supports the following: * `instance_interruption_behavior` - (Optional) Behavior when a Spot Instance is interrupted. Valid values: `hibernate`, `stop`, `terminate`. Default: `terminate`. * `instance_pools_to_use_count` - (Optional) Number of Spot pools across which to allocate your target Spot capacity. Valid only when Spot `allocation_strategy` is set to `lowestPrice`. Default: `1`. * `maintenance_strategies` - (Optional) Nested argument containing maintenance strategies for managing your Spot Instances that are at an elevated risk of being interrupted. Defined below. +* `max_total_price` - (Optional) The maximum amount per hour for Spot Instances that you're willing to pay. +* `min_target_capacity` - (Optional) The minimum target capacity for Spot Instances in the fleet. If the minimum target capacity is not reached, the fleet launches no instances. Supported only for fleets of type `instant`. +* `single_availability_zone` - (Optional) Indicates that the fleet launches all Spot Instances into a single Availability Zone. Supported only for fleets of type `instant`. +* `single_instance_type` - (Optional) Indicates that the fleet uses a single instance type to launch all Spot Instances in the fleet. Supported only for fleets of type `instant`. ### maintenance_strategies @@ -214,7 +198,9 @@ This configuration block supports the following: * `default_target_capacity_type` - (Required) Default target capacity type. Valid values: `on-demand`, `spot`. * `on_demand_target_capacity` - (Optional) The number of On-Demand units to request. * `spot_target_capacity` - (Optional) The number of Spot units to request. -* `target_capacity_unit_type` - (Optional) The unit for the target capacity. This can only be done with `instance_requirements` defined +* `target_capacity_unit_type` - (Optional) The unit for the target capacity. + If you specify `target_capacity_unit_type`, `instance_requirements` must be specified. + * `total_target_capacity` - (Required) The number of units to request, filled using `default_target_capacity_type`. ## Attributes Reference @@ -223,6 +209,14 @@ In addition to all arguments above, the following attributes are exported: * `id` - Fleet identifier * `arn` - The ARN of the fleet +* `fleet_instance_set` - Information about the instances that were launched by the fleet. Available only when `type` is set to `instant`. + * `instance_ids` - The IDs of the instances. + * `instance_type` - The instance type. + * `lifecycle` - Indicates if the instance that was launched is a Spot Instance or On-Demand Instance. + * `platform` - The value is `Windows` for Windows instances. Otherwise, the value is blank. +* `fleet_state` - The state of the EC2 Fleet. +* `fulfilled_capacity` - The number of units fulfilled by this request compared to the set target capacity. +* `fulfilled_on_demand_capacity` - The number of units fulfilled by this request compared to the set target On-Demand capacity. * `tags_all` - A map of tags assigned to the resource, including those inherited from the provider [`default_tags` configuration block](https://registry.terraform.io/providers/hashicorp/aws/latest/docs#default_tags-configuration-block). ## Timeouts From d2075cabb4814b3e134226a0cfbb11cfb9a75d8f Mon Sep 17 00:00:00 2001 From: Albert Silva Date: Sun, 5 Feb 2023 08:46:02 -0500 Subject: [PATCH 17/20] fix issues found in testing+linting --- .changelog/29181.txt | 5 +- internal/service/ec2/ec2_fleet.go | 539 ++------------------ internal/service/ec2/ec2_fleet_test.go | 401 ++++++++------- internal/service/ec2/ec2_launch_template.go | 5 +- 4 files changed, 279 insertions(+), 671 deletions(-) diff --git a/.changelog/29181.txt b/.changelog/29181.txt index 1fa04479d68..ea5badde363 100644 --- a/.changelog/29181.txt +++ b/.changelog/29181.txt @@ -1,9 +1,9 @@ ```release-note:enhancement -resource/aws_ec2_fleet: Add computed `fleet_instance_set`,`fleet_state`, `fulfilled_capacity`, and `fulfilled_on_demand_capacity` arguments +resource/aws_ec2_fleet: Add computed `fleet_instance_set`, `fleet_state`, `fulfilled_capacity`, and `fulfilled_on_demand_capacity` arguments ``` ```release-note:enhancement -resource/aws_ec2_fleet: Add `launch_template_config.override.image_id`, `launch_template_config.override.instance_requirements.allowed_instance_types`, launch_template_config.override.instance_requirements.network_bandwidth_gbps`, `launch_template_config.override.placement` arguments +resource/aws_ec2_fleet: Add `launch_template_config.override.instance_requirements.allowed_instance_types`, launch_template_config.override.instance_requirements.network_bandwidth_gbps` arguments ``` ```release-note:enhancement @@ -11,7 +11,6 @@ resource/aws_ec2_fleet: Add `on_demand_options.capacity_reservation_options`,`on ``` ```release-note:enhancement -#### TODO: Update Schema/CRUD resource/aws_ec2_launch_template: Add expand and flatten support for `instance_requirements.allowed_instance_types`, `instance_requirements.network_bandwidth_gbps_request` ``` diff --git a/internal/service/ec2/ec2_fleet.go b/internal/service/ec2/ec2_fleet.go index 09e13888d5d..2faa4573882 100644 --- a/internal/service/ec2/ec2_fleet.go +++ b/internal/service/ec2/ec2_fleet.go @@ -59,10 +59,7 @@ func ResourceFleet() *schema.Resource { Default: ec2.FleetExcessCapacityTerminationPolicyTermination, ValidateFunc: validation.StringInSlice(ec2.FleetExcessCapacityTerminationPolicy_Values(), false), DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { - if old == "" { - return true - } - return false + return d.Get("type") != "maintain" }, DiffSuppressOnRefresh: true, }, @@ -85,376 +82,6 @@ func ResourceFleet() *schema.Resource { Optional: true, Computed: true, }, - "launch_template_and_overrides": { - Type: schema.TypeList, - Optional: true, - Computed: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "launch_template_specification": { - Type: schema.TypeList, - Computed: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "launch_template_id": { - Type: schema.TypeString, - Optional: true, - Computed: true, - }, - "launch_template_name": { - Type: schema.TypeString, - Optional: true, - Computed: true, - }, - "version": { - Type: schema.TypeString, - Optional: true, - Computed: true, - }, - }, - }, - }, - "overrides": { - Type: schema.TypeList, - Optional: true, - Computed: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "availability_zone": { - Type: schema.TypeString, - Optional: true, - Computed: true, - }, - "image_id": { - Type: schema.TypeString, - Optional: true, - Computed: true, - }, - "instance_requirements": { - Type: schema.TypeList, - Optional: true, - Computed: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "accelerator_count": { - Type: schema.TypeList, - Optional: true, - Computed: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "max": { - Type: schema.TypeInt, - Optional: true, - Computed: true, - }, - "min": { - Type: schema.TypeInt, - Optional: true, - Computed: true, - }, - }, - }, - }, - "accelerator_manufacturers": { - Type: schema.TypeSet, - Optional: true, - Computed: true, - Elem: &schema.Schema{ - Type: schema.TypeString, - }, - }, - "accelerator_names": { - Type: schema.TypeSet, - Optional: true, - Computed: true, - Elem: &schema.Schema{ - Type: schema.TypeString, - }, - }, - "accelerator_total_memory_mib": { - Type: schema.TypeList, - Optional: true, - Computed: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "max": { - Type: schema.TypeInt, - Optional: true, - Computed: true, - }, - "min": { - Type: schema.TypeInt, - Optional: true, - Computed: true, - }, - }, - }, - }, - "accelerator_types": { - Type: schema.TypeSet, - Optional: true, - Computed: true, - Elem: &schema.Schema{ - Type: schema.TypeString, - }, - }, - "allowed_instance_types": { - Type: schema.TypeSet, - Optional: true, - Computed: true, - Elem: &schema.Schema{ - Type: schema.TypeString, - }, - }, - "bare_metal": { - Type: schema.TypeString, - Optional: true, - Computed: true, - }, - "baseline_ebs_bandwidth_mbps": { - Type: schema.TypeList, - Optional: true, - Computed: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "max": { - Type: schema.TypeInt, - Optional: true, - Computed: true, - }, - "min": { - Type: schema.TypeInt, - Optional: true, - Computed: true, - }, - }, - }, - }, - "burstable_performance": { - Type: schema.TypeString, - Optional: true, - Computed: true, - }, - "cpu_manufacturers": { - Type: schema.TypeSet, - Optional: true, - Computed: true, - Elem: &schema.Schema{ - Type: schema.TypeString, - }, - }, - "excluded_instance_types": { - Type: schema.TypeSet, - Optional: true, - Computed: true, - Elem: &schema.Schema{ - Type: schema.TypeString, - }, - }, - "instance_generations": { - Type: schema.TypeSet, - Optional: true, - Computed: true, - Elem: &schema.Schema{ - Type: schema.TypeString, - }, - }, - "local_storage": { - Type: schema.TypeString, - Optional: true, - Computed: true, - }, - "local_storage_types": { - Type: schema.TypeSet, - Optional: true, - Computed: true, - Elem: &schema.Schema{ - Type: schema.TypeString, - }, - }, - "memory_gib_per_vcpu": { - Type: schema.TypeList, - Optional: true, - Computed: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "max": { - Type: schema.TypeFloat, - Optional: true, - Computed: true, - }, - "min": { - Type: schema.TypeFloat, - Optional: true, - Computed: true, - }, - }, - }, - }, - "memory_mib": { - Type: schema.TypeList, - Computed: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "max": { - Type: schema.TypeInt, - Optional: true, - Computed: true, - }, - "min": { - Type: schema.TypeInt, - Computed: true, - }, - }, - }, - }, - "network_bandwidth_gbps": { - Type: schema.TypeList, - Optional: true, - Computed: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "max": { - Type: schema.TypeFloat, - Optional: true, - Computed: true, - }, - "min": { - Type: schema.TypeFloat, - Optional: true, - Computed: true, - }, - }, - }, - }, - "network_interface_count": { - Type: schema.TypeList, - Optional: true, - Computed: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "max": { - Type: schema.TypeInt, - Optional: true, - Computed: true, - }, - "min": { - Type: schema.TypeInt, - Optional: true, - Computed: true, - }, - }, - }, - }, - "on_demand_max_price_percentage_over_lowest_price": { - Type: schema.TypeInt, - Optional: true, - Computed: true, - }, - "require_hibernate_support": { - Type: schema.TypeBool, - Optional: true, - Computed: true, - }, - "spot_max_price_percentage_over_lowest_price": { - Type: schema.TypeInt, - Optional: true, - Computed: true, - }, - "total_local_storage_gb": { - Type: schema.TypeList, - Optional: true, - Computed: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "max": { - Type: schema.TypeFloat, - Optional: true, - Computed: true, - }, - "min": { - Type: schema.TypeFloat, - Optional: true, - Computed: true, - }, - }, - }, - }, - "vcpu_count": { - Type: schema.TypeList, - Computed: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "max": { - Type: schema.TypeInt, - Optional: true, - Computed: true, - }, - "min": { - Type: schema.TypeInt, - Computed: true, - }, - }, - }, - }, - }, - }, - }, - "instance_type": { - Type: schema.TypeString, - Optional: true, - Computed: true, - }, - "max_price": { - Type: schema.TypeString, - Optional: true, - Computed: true, - }, - "placement": { - Type: schema.TypeList, - Optional: true, - Computed: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "group_id": { - Type: schema.TypeString, - Optional: true, - Computed: true, - }, - "group_name": { - Type: schema.TypeString, - Optional: true, - Computed: true, - }, - "spread_domain": { - Type: schema.TypeString, - Optional: true, - Computed: true, - }, - }, - }, - }, - "priority": { - Type: schema.TypeFloat, - Optional: true, - Computed: true, - }, - "subnet_id": { - Type: schema.TypeString, - Optional: true, - Computed: true, - }, - "weighted_capacity": { - Type: schema.TypeFloat, - Optional: true, - Computed: true, - }, - }, - }, - }, - }, - }, - }, "lifecycle": { Type: schema.TypeString, Optional: true, @@ -486,13 +113,13 @@ func ResourceFleet() *schema.Resource { "launch_template_config": { Type: schema.TypeList, Required: true, - MinItems: 1, + MinItems: 0, MaxItems: 50, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "launch_template_specification": { Type: schema.TypeList, - Required: true, + Optional: true, MinItems: 1, MaxItems: 1, Elem: &schema.Resource{ @@ -511,7 +138,7 @@ func ResourceFleet() *schema.Resource { }, "version": { Type: schema.TypeString, - Optional: true, + Required: true, }, }, }, @@ -526,10 +153,11 @@ func ResourceFleet() *schema.Resource { Type: schema.TypeString, Optional: true, }, - "image_id": { - Type: schema.TypeString, - Optional: true, - }, + // Pending AWS to provide this attribute back in the `Describe` call + // "image_id": { + // Type: schema.TypeString, + // Optional: true, + // }, "instance_requirements": { Type: schema.TypeList, Optional: true, @@ -821,19 +449,20 @@ func ResourceFleet() *schema.Resource { Type: schema.TypeString, Optional: true, }, - "placement": { - Type: schema.TypeList, - Optional: true, - MaxItems: 1, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "group_name": { - Type: schema.TypeString, - Optional: true, - }, - }, - }, - }, + // Pending AWS to provide this attribute back in the `Describe` call + // "placement": { + // Type: schema.TypeList, + // Optional: true, + // MaxItems: 1, + // Elem: &schema.Resource{ + // Schema: map[string]*schema.Schema{ + // "group_name": { + // Type: schema.TypeString, + // Optional: true, + // }, + // }, + // }, + // }, "priority": { Type: schema.TypeFloat, Optional: true, @@ -867,20 +496,21 @@ func ResourceFleet() *schema.Resource { Default: FleetOnDemandAllocationStrategyLowestPrice, ValidateFunc: validation.StringInSlice(FleetOnDemandAllocationStrategy_Values(), false), }, - "capacity_reservation_options": { - Type: schema.TypeList, - Optional: true, - MaxItems: 1, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "usage_strategy": { - Type: schema.TypeString, - Optional: true, - ValidateFunc: validation.StringInSlice(ec2.FleetCapacityReservationUsageStrategy_Values(), false), - }, - }, - }, - }, + // Pending AWS to provide this attribute back in the `Describe` call + // "capacity_reservation_options": { + // Type: schema.TypeList, + // Optional: true, + // MaxItems: 1, + // Elem: &schema.Resource{ + // Schema: map[string]*schema.Schema{ + // "usage_strategy": { + // Type: schema.TypeString, + // Optional: true, + // ValidateFunc: validation.StringInSlice(ec2.FleetCapacityReservationUsageStrategy_Values(), false), + // }, + // }, + // }, + // }, "max_total_price": { Type: schema.TypeString, Optional: true, @@ -1102,6 +732,7 @@ func resourceFleetCreate(ctx context.Context, d *schema.ResourceData, meta inter if v, ok := d.GetOk("context"); ok { input.Context = aws.String(v.(string)) } + // this argument is only valid for fleet_type of `maintain`, but was defaulted in the schema above, hence the extra check if v, ok := d.GetOk("excess_capacity_termination_policy"); ok && v != "" && fleetType == "maintain" { input.ExcessCapacityTerminationPolicy = aws.String(v.(string)) @@ -1158,7 +789,6 @@ func resourceFleetCreate(ctx context.Context, d *schema.ResourceData, meta inter if _, err := WaitFleet(ctx, conn, d.Id(), []string{ec2.FleetStateCodeSubmitted}, targetStates, d.Timeout(schema.TimeoutCreate), 0); err != nil { return sdkdiag.AppendErrorf(diags, "waiting for EC2 Fleet (%s) create: %s", d.Id(), err) - } } @@ -1257,7 +887,6 @@ func resourceFleetUpdate(ctx context.Context, d *schema.ResourceData, meta inter conn := meta.(*conns.AWSClient).EC2Conn() if d.HasChangesExcept("tags", "tags_all") { - input := &ec2.ModifyFleetInput{ FleetId: aws.String(d.Id()), } @@ -1695,10 +1324,6 @@ func flattenFleetInstances(apiObject *ec2.DescribeFleetsInstances) map[string]in tfMap["instance_type"] = aws.StringValue(v) } - if v := apiObject.LaunchTemplateAndOverrides; v != nil { - tfMap["launch_template_and_overrides"] = []interface{}{flattenLaunchTemplatesAndOverridesResponse(v)} - } - if v := apiObject.Lifecycle; v != nil { tfMap["lifecycle"] = aws.StringValue(v) } @@ -1786,23 +1411,25 @@ func flattenFleetLaunchTemplateSpecificationForFleet(apiObject *ec2.FleetLaunchT return tfMap } -func flattenLaunchTemplatesAndOverridesResponse(apiObject *ec2.LaunchTemplateAndOverridesResponse) map[string]interface{} { - if apiObject == nil { - return nil - } +// Pending AWS to provide this attribute back in the `Describe` call - tfMap := map[string]interface{}{} +// func flattenLaunchTemplatesAndOverridesResponse(apiObject *ec2.LaunchTemplateAndOverridesResponse) map[string]interface{} { +// if apiObject == nil { +// return nil +// } - if v := apiObject.LaunchTemplateSpecification; v != nil { - tfMap["launch_template_specification"] = []interface{}{flattenFleetLaunchTemplateSpecificationForFleet(v)} - } +// tfMap := map[string]interface{}{} - if v := apiObject.Overrides; v != nil { - tfMap["overrides"] = []interface{}{flattenFleetLaunchTemplateOverrides(v)} - } +// if v := apiObject.LaunchTemplateSpecification; v != nil { +// tfMap["launch_template_specification"] = []interface{}{flattenFleetLaunchTemplateSpecificationForFleet(v)} +// } - return tfMap -} +// if v := apiObject.Overrides; v != nil { +// tfMap["overrides"] = []interface{}{flattenFleetLaunchTemplateOverrides(v)} +// } + +// return tfMap +// } func flattenFleetLaunchTemplateOverrideses(apiObjects []*ec2.FleetLaunchTemplateOverrides) []interface{} { if len(apiObjects) == 0 { @@ -2021,63 +1648,5 @@ func resourceFleetCustomizeDiff(_ context.Context, diff *schema.ResourceDiff, v } } } - - // // Launch template config validation: - // if v, ok := diff.GetOk("launch_template_config"); ok && len(v.([]interface{})) > 0 { - // input.LaunchTemplateConfigs = expandFleetLaunchTemplateConfigRequests(v.([]interface{})) - // for _, config := range input.LaunchTemplateConfigs { - // for _, override := range config.Overrides { - // // InvalidOverride: - // if override.InstanceRequirements != nil && override.InstanceType != nil { - // return errors.New("launch_template_configs.overrides can specify instance_requirements or instance_type, but not both") - // } - // // InvalidPlacementConfigs: - // if override.Placement.GroupId != nil && override.Placement.GroupName != nil { - // return errors.New("launch_template_configs.overrides.placement can specify a group_id or group_name, but not both") - // } - // } - // } - // } - - // // Fleet type `instant` specific validation: - // if v, ok := diff.GetOk("type"); ok { - // if v == ec2.FleetTypeInstant { - // if v, ok := diff.GetOk("terminate_instances"); ok { - // if !v.(bool) { - // return errors.New(`EC2 Fleet of type instant must have terminate_instances set to true`) - // } - // } - // if v, ok := diff.GetOk("excess_capacity_termination_policy"); ok { - // if v.(string) != "" { - // return errors.New(`EC2 Fleet of type instant must not have excess_capacity_termination_policy set`) - // } - // } - // } else { - // if v, ok := diff.GetOk("on_demand_options"); ok && len(v.([]interface{})) > 0 && v.([]interface{})[0] != nil { - // input.OnDemandOptions = expandOnDemandOptionsRequest(v.([]interface{})[0].(map[string]interface{})) - // if input.OnDemandOptions.CapacityReservationOptions != nil { - // return errors.New("on_demand_options.capacity_reservation_options can only be specified for fleets of type instant") - // } - // if input.OnDemandOptions.MinTargetCapacity != nil { - // return errors.New("on_demand_options.min_target_capacity can only be specified for fleets of type instant") - // } - // if input.OnDemandOptions.SingleAvailabilityZone != nil { - // return errors.New("on_demand_options.single_availability_zone can only be specified for fleets of type instant") - // } - // if input.OnDemandOptions.SingleInstanceType != nil { - // return errors.New("on_demand_options.single_instance_type can only be specified for fleets of type instant") - // } - // if input.SpotOptions.MinTargetCapacity != nil { - // return errors.New("spot_options.min_target_capacity can only be specified for fleets of type instant") - // } - // if input.SpotOptions.SingleAvailabilityZone != nil { - // return errors.New("spot_options.single_availability_zone can only be specified for fleets of type instant") - // } - // if input.SpotOptions.SingleInstanceType != nil { - // return errors.New("spot_options.single_instance_type can only be specified for fleets of type instant") - // } - // } - // } - // } return nil } diff --git a/internal/service/ec2/ec2_fleet_test.go b/internal/service/ec2/ec2_fleet_test.go index 2a71b4b720e..0650d77a7d0 100644 --- a/internal/service/ec2/ec2_fleet_test.go +++ b/internal/service/ec2/ec2_fleet_test.go @@ -39,7 +39,7 @@ func TestAccEC2Fleet_basic(t *testing.T) { acctest.MatchResourceAttrRegionalARN(resourceName, "arn", "ec2", regexp.MustCompile(`fleet/.+`)), resource.TestCheckResourceAttr(resourceName, "context", ""), resource.TestCheckResourceAttr(resourceName, "excess_capacity_termination_policy", "termination"), - resource.TestCheckResourceAttr(resourceName, "fleetState", "active"), + resource.TestCheckResourceAttr(resourceName, "fleet_state", "active"), resource.TestCheckResourceAttr(resourceName, "fulfilled_capacity", "0"), resource.TestCheckResourceAttr(resourceName, "fulfilled_on_demand_capacity", "0"), resource.TestCheckResourceAttr(resourceName, "launch_template_config.#", "1"), @@ -364,31 +364,33 @@ func TestAccEC2Fleet_LaunchTemplateOverride_availabilityZone(t *testing.T) { }) } -func TestAccEC2Fleet_LaunchTemplateOverride_imageId(t *testing.T) { - ctx := acctest.Context(t) - var fleet1 ec2.FleetData - awsAmiDataSourceName := "data.aws_ami.amz2" - resourceName := "aws_ec2_fleet.test" - rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) - - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { acctest.PreCheck(t); testAccPreCheckFleet(ctx, t) }, - ErrorCheck: acctest.ErrorCheck(t, ec2.EndpointsID), - ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - CheckDestroy: testAccCheckFleetDestroy(ctx), - Steps: []resource.TestStep{ - { - Config: testAccFleetConfig_launchTemplateOverrideImageId(rName), - Check: resource.ComposeTestCheckFunc( - testAccCheckFleetExists(ctx, 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.image_id", awsAmiDataSourceName, "id"), - ), - }, - }, - }) -} +// Pending AWS to provide this attribute back in the `Describe` call + +// func TestAccEC2Fleet_LaunchTemplateOverride_imageId(t *testing.T) { +// ctx := acctest.Context(t) +// var fleet1 ec2.FleetData +// awsAmiDataSourceName := "data.aws_ami.amz2" +// resourceName := "aws_ec2_fleet.test" +// rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + +// resource.ParallelTest(t, resource.TestCase{ +// PreCheck: func() { acctest.PreCheck(t); testAccPreCheckFleet(ctx, t) }, +// ErrorCheck: acctest.ErrorCheck(t, ec2.EndpointsID), +// ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, +// CheckDestroy: testAccCheckFleetDestroy(ctx), +// Steps: []resource.TestStep{ +// { +// Config: testAccFleetConfig_launchTemplateOverrideImageId(rName), +// Check: resource.ComposeTestCheckFunc( +// testAccCheckFleetExists(ctx, 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.image_id", awsAmiDataSourceName, "id"), +// ), +// }, +// }, +// }) +// } func TestAccEC2Fleet_LaunchTemplateOverride_instanceRequirements_memoryMiBAndVCPUCount(t *testing.T) { ctx := acctest.Context(t) @@ -1806,31 +1808,33 @@ func TestAccEC2Fleet_LaunchTemplateOverride_instanceRequirements_onDemandMaxPric }) } -func TestAccEC2Fleet_LaunchTemplateOverride_placement(t *testing.T) { - ctx := acctest.Context(t) - var fleet1 ec2.FleetData - resourceName := "aws_ec2_fleet.test" - rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) - - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { acctest.PreCheck(t); testAccPreCheckFleet(ctx, t) }, - ErrorCheck: acctest.ErrorCheck(t, ec2.EndpointsID), - ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - CheckDestroy: testAccCheckFleetDestroy(ctx), - Steps: []resource.TestStep{ - { - Config: testAccFleetConfig_launchTemplateOverridePlacement(rName), - Check: resource.ComposeTestCheckFunc( - testAccCheckFleetExists(ctx, 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.placement", "1"), - resource.TestCheckResourceAttr(resourceName, "launch_template_config.0.override.0.placement.group_name", rName), - ), - }, - }, - }) -} +// Pending AWS to provide this attribute back in the `Describe` call + +// func TestAccEC2Fleet_LaunchTemplateOverride_placement(t *testing.T) { +// ctx := acctest.Context(t) +// var fleet1 ec2.FleetData +// resourceName := "aws_ec2_fleet.test" +// rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + +// resource.ParallelTest(t, resource.TestCase{ +// PreCheck: func() { acctest.PreCheck(t); testAccPreCheckFleet(ctx, t) }, +// ErrorCheck: acctest.ErrorCheck(t, ec2.EndpointsID), +// ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, +// CheckDestroy: testAccCheckFleetDestroy(ctx), +// Steps: []resource.TestStep{ +// { +// Config: testAccFleetConfig_launchTemplateOverridePlacement(rName), +// Check: resource.ComposeTestCheckFunc( +// testAccCheckFleetExists(ctx, 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.placement", "1"), +// resource.TestCheckResourceAttr(resourceName, "launch_template_config.0.override.0.placement.group_name", rName), +// ), +// }, +// }, +// }) +// } func TestAccEC2Fleet_LaunchTemplateOverride_instanceRequirements_requireHibernateSupport(t *testing.T) { ctx := acctest.Context(t) @@ -2364,30 +2368,32 @@ func TestAccEC2Fleet_OnDemandOptions_allocationStrategy(t *testing.T) { }) } -func TestAccEC2Fleet_OnDemandOptions_CapacityReservationOptions(t *testing.T) { - ctx := acctest.Context(t) - var fleet1 ec2.FleetData - resourceName := "aws_ec2_fleet.test" - rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) - - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { acctest.PreCheck(t); testAccPreCheckFleet(ctx, t) }, - ErrorCheck: acctest.ErrorCheck(t, ec2.EndpointsID), - ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - CheckDestroy: testAccCheckFleetDestroy(ctx), - Steps: []resource.TestStep{ - { - Config: testAccFleetConfig_onDemandOptionsCapacityReservationOptions(rName, "use-capacity-reservations-first"), - Check: resource.ComposeTestCheckFunc( - testAccCheckFleetExists(ctx, resourceName, &fleet1), - resource.TestCheckResourceAttr(resourceName, "on_demand_options.#", "1"), - resource.TestCheckResourceAttr(resourceName, "on_demand_options.0.capacity_reservation_options.#", "1"), - resource.TestCheckResourceAttr(resourceName, "on_demand_options.0.capacity_reservation_options.0.usage_strategy", "use-capacity-reservations-first"), - ), - }, - }, - }) -} +// Pending AWS to provide this attribute back in the `Describe` call + +// func TestAccEC2Fleet_OnDemandOptions_CapacityReservationOptions(t *testing.T) { +// ctx := acctest.Context(t) +// var fleet1 ec2.FleetData +// resourceName := "aws_ec2_fleet.test" +// rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + +// resource.ParallelTest(t, resource.TestCase{ +// PreCheck: func() { acctest.PreCheck(t); testAccPreCheckFleet(ctx, t) }, +// ErrorCheck: acctest.ErrorCheck(t, ec2.EndpointsID), +// ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, +// CheckDestroy: testAccCheckFleetDestroy(ctx), +// Steps: []resource.TestStep{ +// { +// Config: testAccFleetConfig_onDemandOptionsCapacityReservationOptions(rName, "use-capacity-reservations-first"), +// Check: resource.ComposeTestCheckFunc( +// testAccCheckFleetExists(ctx, resourceName, &fleet1), +// resource.TestCheckResourceAttr(resourceName, "on_demand_options.#", "1"), +// resource.TestCheckResourceAttr(resourceName, "on_demand_options.0.capacity_reservation_options.#", "1"), +// resource.TestCheckResourceAttr(resourceName, "on_demand_options.0.capacity_reservation_options.0.usage_strategy", "use-capacity-reservations-first"), +// ), +// }, +// }, +// }) +// } func TestAccEC2Fleet_OnDemandOptions_MaxTotalPrice(t *testing.T) { ctx := acctest.Context(t) @@ -2938,8 +2944,8 @@ func TestAccEC2Fleet_type_instant(t *testing.T) { var fleet1 ec2.FleetData resourceName := "aws_ec2_fleet.test" rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) - excessCapacityTerminationPolicy := "" fleetType := "instant" + totalTargetCapacity := "2" terminateInstances := true resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t); testAccPreCheckFleet(ctx, t) }, @@ -2948,11 +2954,15 @@ func TestAccEC2Fleet_type_instant(t *testing.T) { CheckDestroy: testAccCheckFleetDestroy(ctx), Steps: []resource.TestStep{ { - Config: testAccFleetConfig_type(rName, fleetType, excessCapacityTerminationPolicy, terminateInstances), + Config: testAccFleetConfig_type_instant(rName, fleetType, terminateInstances, totalTargetCapacity), Check: resource.ComposeTestCheckFunc( testAccCheckFleetExists(ctx, resourceName, &fleet1), resource.TestCheckResourceAttr(resourceName, "type", fleetType), - resource.TestCheckResourceAttrSet(resourceName, "fleet_instance_set"), + resource.TestCheckResourceAttr(resourceName, "fleet_instance_set.#", "1"), + resource.TestCheckResourceAttr(resourceName, "fleet_instance_set.0.instance_ids.#", totalTargetCapacity), + resource.TestCheckResourceAttrSet(resourceName, "fleet_instance_set.0.instance_ids.0"), + resource.TestCheckResourceAttrSet(resourceName, "fleet_instance_set.0.instance_type"), + resource.TestCheckResourceAttrSet(resourceName, "fleet_instance_set.0.lifecycle"), ), }, { @@ -3476,44 +3486,46 @@ resource "aws_ec2_fleet" "test" { `, rName, availabilityZoneIndex)) } -func testAccFleetConfig_launchTemplateOverrideImageId(rName string) string { - return acctest.ConfigCompose( - testAccFleetConfig_BaseLaunchTemplate(rName), - acctest.ConfigAvailableAZsNoOptIn(), - 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 { - image_id = data.aws_ami.amz2.id - } - } - - target_capacity_specification { - default_target_capacity_type = "spot" - total_target_capacity = 0 - } - - tags = { - Name = %[1]q - } -} - -data "aws_ami" "amz2" { - most_recent = true - - filter { - name = "name" - values = ["amzn2-ami-hvm-*-x86_64-ebs"] - } - owners = ["amazon"] -} -`, rName)) -} +// Pending AWS to provide this attribute back in the `Describe` call + +// func testAccFleetConfig_launchTemplateOverrideImageId(rName string) string { +// return acctest.ConfigCompose( +// testAccFleetConfig_BaseLaunchTemplate(rName), +// acctest.ConfigAvailableAZsNoOptIn(), +// 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 { +// image_id = data.aws_ami.amz2.id +// } +// } + +// target_capacity_specification { +// default_target_capacity_type = "spot" +// total_target_capacity = 0 +// } + +// tags = { +// Name = %[1]q +// } +// } + +// data "aws_ami" "amz2" { +// most_recent = true + +// filter { +// name = "name" +// values = ["amzn2-ami-hvm-*-x86_64-ebs"] +// } +// owners = ["amazon"] +// } +// `, rName)) +// } func testAccFleetConfig_launchTemplateOverrideInstanceRequirements(rName, instanceRequirements string) string { return acctest.ConfigCompose(testAccFleetConfig_BaseLaunchTemplate(rName), fmt.Sprintf(` @@ -3595,41 +3607,43 @@ resource "aws_ec2_fleet" "test" { `, rName, maxPrice)) } -func testAccFleetConfig_launchTemplateOverridePlacement(rName string) string { - return acctest.ConfigCompose( - testAccFleetConfig_BaseLaunchTemplate(rName), - acctest.ConfigAvailableAZsNoOptIn(), - 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 { - placement { - group_name = aws_launch_template.test.name - } - } - } - - target_capacity_specification { - default_target_capacity_type = "spot" - total_target_capacity = 0 - } - - tags = { - Name = %[1]q - } -} - -resource "aws_placement_group" "test" { - name = %[1]q - strategy = "cluster" - } -`, rName)) -} +// Pending AWS to provide this attribute back in the `Describe` call + +// func testAccFleetConfig_launchTemplateOverridePlacement(rName string) string { +// return acctest.ConfigCompose( +// testAccFleetConfig_BaseLaunchTemplate(rName), +// acctest.ConfigAvailableAZsNoOptIn(), +// 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 { +// placement { +// group_name = aws_launch_template.test.name +// } +// } +// } + +// target_capacity_specification { +// default_target_capacity_type = "spot" +// total_target_capacity = 0 +// } + +// tags = { +// Name = %[1]q +// } +// } + +// resource "aws_placement_group" "test" { +// name = %[1]q +// strategy = "cluster" +// } +// `, rName)) +// } func testAccFleetConfig_launchTemplateOverridePriority(rName string, priority int) string { return acctest.ConfigCompose(testAccFleetConfig_BaseLaunchTemplate(rName), fmt.Sprintf(` @@ -3818,35 +3832,37 @@ resource "aws_ec2_fleet" "test" { `, rName, allocationStrategy)) } -func testAccFleetConfig_onDemandOptionsCapacityReservationOptions(rName, usageStrategy string) string { - return acctest.ConfigCompose(testAccFleetConfig_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 { - capacity_reservation_options { - usage_strategy = %[2]q - } - } - - target_capacity_specification { - default_target_capacity_type = "on-demand" - total_target_capacity = 0 - } - terminate_instances = true - type = "instant" - - tags = { - Name = %[1]q - } -} -`, rName, usageStrategy)) -} +// Pending AWS to provide this attribute back in the `Describe` call + +// func testAccFleetConfig_onDemandOptionsCapacityReservationOptions(rName, usageStrategy string) string { +// return acctest.ConfigCompose(testAccFleetConfig_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 { +// capacity_reservation_options { +// usage_strategy = %[2]q +// } +// } + +// target_capacity_specification { +// default_target_capacity_type = "on-demand" +// total_target_capacity = 0 +// } +// terminate_instances = true +// type = "instant" + +// tags = { +// Name = %[1]q +// } +// } +// `, rName, usageStrategy)) +// } func testAccFleetConfig_onDemandOptionsMaxTotalPrice(rName, maxTotalPrice string) string { return acctest.ConfigCompose(testAccFleetConfig_BaseLaunchTemplate(rName), fmt.Sprintf(` @@ -4278,6 +4294,31 @@ resource "aws_ec2_fleet" "test" { `, rName, terminateInstancesWithExpiration)) } +func testAccFleetConfig_type_instant(rName, fleetType string, terminateInstance bool, totalTargetCapacity string) string { + return acctest.ConfigCompose(testAccFleetConfig_BaseLaunchTemplate(rName), fmt.Sprintf(` +resource "aws_ec2_fleet" "test" { + type = %[2]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 = %[4]q + } + + terminate_instances = %[3]t + tags = { + Name = %[1]q + } +} +`, rName, fleetType, terminateInstance, totalTargetCapacity)) +} + func testAccFleetConfig_type(rName, fleetType string, excessCapacityTerminationPolicy string, terminateInstance bool) string { return acctest.ConfigCompose(testAccFleetConfig_BaseLaunchTemplate(rName), fmt.Sprintf(` resource "aws_ec2_fleet" "test" { diff --git a/internal/service/ec2/ec2_launch_template.go b/internal/service/ec2/ec2_launch_template.go index 6cca83ddbcf..7edce2f94c5 100644 --- a/internal/service/ec2/ec2_launch_template.go +++ b/internal/service/ec2/ec2_launch_template.go @@ -1777,11 +1777,11 @@ func expandNetworkBandwidthGbpsRequest(tfMap map[string]interface{}) *ec2.Networ var min float64 if v, ok := tfMap["min"].(float64); ok { min = v - apiObject.Min = aws.Float64(float64(v)) + apiObject.Min = aws.Float64(v) } if v, ok := tfMap["max"].(float64); ok && v >= min { - apiObject.Max = aws.Float64(float64(v)) + apiObject.Max = aws.Float64(v) } return apiObject @@ -2800,7 +2800,6 @@ func flattenNetworkBandwidthGbps(apiObject *ec2.NetworkBandwidthGbps) map[string } return tfMap - } func flattenNetworkInterfaceCount(apiObject *ec2.NetworkInterfaceCount) map[string]interface{} { From 3846b84288770a1a79dc3a34ebf38ef1776f919e Mon Sep 17 00:00:00 2001 From: Albert Silva Date: Sun, 5 Feb 2023 08:53:40 -0500 Subject: [PATCH 18/20] terrafmt --- internal/service/ec2/ec2_fleet_test.go | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/internal/service/ec2/ec2_fleet_test.go b/internal/service/ec2/ec2_fleet_test.go index 0650d77a7d0..cd548469ff9 100644 --- a/internal/service/ec2/ec2_fleet_test.go +++ b/internal/service/ec2/ec2_fleet_test.go @@ -3901,8 +3901,8 @@ resource "aws_ec2_fleet" "test" { } on_demand_options { - min_target_capacity = %[2]s - single_availability_zone = true + min_target_capacity = %[2]s + single_availability_zone = true } target_capacity_specification { @@ -3910,7 +3910,7 @@ resource "aws_ec2_fleet" "test" { total_target_capacity = 0 } terminate_instances = true - type = "instant" + type = "instant" tags = { Name = %[1]q @@ -3930,7 +3930,7 @@ resource "aws_ec2_fleet" "test" { } on_demand_options { - single_availability_zone = %[2]t + single_availability_zone = %[2]t } target_capacity_specification { @@ -3938,7 +3938,7 @@ resource "aws_ec2_fleet" "test" { total_target_capacity = 0 } terminate_instances = true - type = "instant" + type = "instant" tags = { Name = %[1]q @@ -3958,7 +3958,7 @@ resource "aws_ec2_fleet" "test" { } on_demand_options { - single_instance_type = %[2]t + single_instance_type = %[2]t } target_capacity_specification { @@ -3966,7 +3966,7 @@ resource "aws_ec2_fleet" "test" { total_target_capacity = 0 } terminate_instances = true - type = "instant" + type = "instant" tags = { Name = %[1]q @@ -4040,7 +4040,7 @@ resource "aws_ec2_fleet" "test" { maintenance_strategies { capacity_rebalance { replacement_strategy = %[3]q - termination_delay = %[4]s + termination_delay = %[4]s } } } @@ -4349,7 +4349,7 @@ resource "aws_ec2_fleet" "test" { func testAccFleetConfig_validFrom(rName, validFrom string) string { return acctest.ConfigCompose(testAccFleetConfig_BaseLaunchTemplate(rName), fmt.Sprintf(` resource "aws_ec2_fleet" "test" { - + launch_template_config { launch_template_specification { @@ -4375,7 +4375,7 @@ resource "aws_ec2_fleet" "test" { func testAccFleetConfig_validUntil(rName, validUntil string) string { return acctest.ConfigCompose(testAccFleetConfig_BaseLaunchTemplate(rName), fmt.Sprintf(` resource "aws_ec2_fleet" "test" { - + launch_template_config { launch_template_specification { From 49f9f893f37123fb95db388bfe9811ad9d04fba4 Mon Sep 17 00:00:00 2001 From: Albert Silva Date: Sun, 5 Feb 2023 09:07:33 -0500 Subject: [PATCH 19/20] markdownlint --- website/docs/r/ec2_fleet.html.markdown | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/website/docs/r/ec2_fleet.html.markdown b/website/docs/r/ec2_fleet.html.markdown index dc9a46f155c..01bb9b806ec 100644 --- a/website/docs/r/ec2_fleet.html.markdown +++ b/website/docs/r/ec2_fleet.html.markdown @@ -55,7 +55,7 @@ Describes a launch template and overrides. #### launch_template_specification -The launch template to use. You must specify either the launch template ID or launch template name in the request. +The launch template to use. You must specify either the launch template ID or launch template name in the request. * `launch_template_id` - (Optional) The ID of the launch template. * `launch_template_name` - (Optional) The name of the launch template. @@ -88,10 +88,10 @@ resource "aws_ec2_fleet" "example" { ``` * `availability_zone` - (Optional) Availability Zone in which to launch the instances. -* `instance_requirements` - (Optional)The attributes for the instance types. When you specify instance attributes, Amazon EC2 will identify instance types with those attributes. +* `instance_requirements` - (Optional)The attributes for the instance types. When you specify instance attributes, Amazon EC2 will identify instance types with those attributes. If you specify `InstanceRequirements`, you can't specify `InstanceType`. - -* `instance_type` - (Optional) Instance type. + +* `instance_type` - (Optional) Instance type. If you specify `InstanceType`, you can't specify `InstanceRequirements`. * `max_price` - (Optional) Maximum price per unit hour that you are willing to pay for a Spot Instance. @@ -116,7 +116,7 @@ This configuration block supports the following: * `min` - (Optional) The minimum amount of accelerator memory, in MiB. To specify no minimum limit, omit this parameter. * `max` - (Optional) The maximum amount of accelerator memory, in MiB. To specify no maximum limit, omit this parameter. * `accelerator_types` - (Optional) The accelerator types that must be on the instance type. Default is any accelerator type. -* `allowed_instance_types` - (Optional) The instance types to apply your specified attributes against. All other instance types are ignored, even if they match your specified attributes. You can use strings with one or more wild cards, represented by an asterisk (\*). The following are examples: `c5*`, `m5a.*`, `r*`, `*3*`. For example, if you specify `c5*`, you are excluding the entire C5 instance family, which includes all C5a and C5n instance types. If you specify `m5a.*`, you are excluding all the M5a instance types, but not the M5n instance types. Maximum of 400 entries in the list; each entry is limited to 30 characters. Default is no excluded instance types. Default is any instance type. +* `allowed_instance_types` - (Optional) The instance types to apply your specified attributes against. All other instance types are ignored, even if they match your specified attributes. You can use strings with one or more wild cards,represented by an asterisk (\*). The following are examples: `c5*`, `m5a.*`, `r*`, `*3*`. For example, if you specify `c5*`, you are excluding the entire C5 instance family, which includes all C5a and C5n instance types. If you specify `m5a.*`, you are excluding all the M5a instance types, but not the M5n instance types. Maximum of 400 entries in the list; each entry is limited to 30 characters. Default is no excluded instance types. Default is any instance type. If you specify `AllowedInstanceTypes`, you can't specify `ExcludedInstanceTypes`. @@ -127,7 +127,7 @@ This configuration block supports the following: * `burstable_performance` - (Optional) Indicates whether burstable performance T instance types are `included`, `excluded`, or `required`. Default is `excluded`. * `cpu_manufacturers` (Optional) The CPU manufacturers to include. Default is any manufacturer. ~> **NOTE:** Don't confuse the CPU hardware manufacturer with the CPU hardware architecture. Instances will be launched with a compatible CPU architecture based on the Amazon Machine Image (AMI) that you specify in your launch template. -* `excluded_instance_types` - (Optional) The instance types to exclude. You can use strings with one or more wild cards, represented by an asterisk (\*). The following are examples: `c5*`, `m5a.*`, `r*`, `*3*`. For example, if you specify `c5*`, you are excluding the entire C5 instance family, which includes all C5a and C5n instance types. If you specify `m5a.*`, you are excluding all the M5a instance types, but not the M5n instance types. Maximum of 400 entries in the list; each entry is limited to 30 characters. Default is no excluded instance types. +* `excluded_instance_types` - (Optional) The instance types to exclude. You can use strings with one or more wild cards, represented by an asterisk (\*). The following are examples: `c5*`, `m5a.*`, `r*`, `*3*`. For example, if you specify `c5*`, you are excluding the entire C5 instance family, which includes all C5a and C5n instance types. If you specify `m5a.*`, you are excluding all the M5a instance types, but not the M5n instance types. Maximum of 400 entries in the list; each entry is limited to 30 characters. Default is no excluded instance types. If you specify `AllowedInstanceTypes`, you can't specify `ExcludedInstanceTypes`. @@ -146,7 +146,7 @@ This configuration block supports the following: * `network_interface_count` - (Optional) Block describing the minimum and maximum number of network interfaces. Default is no minimum or maximum. * `min` - (Optional) The minimum number of network interfaces. To specify no minimum limit, omit this parameter. * `max` - (Optional) The maximum number of network interfaces. To specify no maximum limit, omit this parameter. -* `on_demand_max_price_percentage_over_lowest_price` - (Optional) The price protection threshold for On-Demand Instances. This is the maximum you’ll pay for an On-Demand Instance, expressed as a percentage higher than the cheapest M, C, or R instance type with your specified attributes. When Amazon EC2 Auto Scaling selects instance types with your attributes, we will exclude instance types whose price is higher than your threshold. The parameter accepts an integer, which Amazon EC2 Auto Scaling interprets as a percentage. To turn off price protection, specify a high value, such as 999999. Default is 20. +* `on_demand_max_price_percentage_over_lowest_price` - (Optional) The price protection threshold for On-Demand Instances. This is the maximum you’ll pay for an On-Demand Instance, expressed as a percentage higher than the cheapest M, C, or R instance type with your specified attributes. When Amazon EC2 Auto Scaling selects instance types with your attributes, we will exclude instance types whose price is higher than your threshold. The parameter accepts an integer, which Amazon EC2 Auto Scaling interprets as a percentage. To turn off price protection, specify a high value, such as 999999. Default is 20. If you set `target_capacity_unit_type` to `vcpu` or `memory-mib`, the price protection threshold is applied based on the per-vCPU or per-memory price instead of the per-instance price. @@ -168,7 +168,7 @@ This configuration block supports the following: * `capacity_reservation_options` (Optional) The strategy for using unused Capacity Reservations for fulfilling On-Demand capacity. Supported only for fleets of type `instant`. * `usage_strategy` - (Optional) Indicates whether to use unused Capacity Reservations for fulfilling On-Demand capacity. Valid values: `use-capacity-reservations-first`. * `max_total_price` - (Optional) The maximum amount per hour for On-Demand Instances that you're willing to pay. -* `min_target_capacity` - (Optional) The minimum target capacity for On-Demand Instances in the fleet. If the minimum target capacity is not reached, the fleet launches no instances. Supported only for fleets of type `instant`. +* `min_target_capacity` - (Optional) The minimum target capacity for On-Demand Instances in the fleet. If the minimum target capacity is not reached, the fleet launches no instances. Supported only for fleets of type `instant`. If you specify `min_target_capacity`, at least one of the following must be specified: `single_availability_zone` or `single_instance_type`. * `single_availability_zone` - (Optional) Indicates that the fleet launches all On-Demand Instances into a single Availability Zone. Supported only for fleets of type `instant`. From de9065747d0cd7e55469b815f16a3811095c4c5e Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Tue, 28 Feb 2023 14:44:07 -0500 Subject: [PATCH 20/20] Fix golangci-lint 'unused'. --- internal/service/ec2/ec2_fleet.go | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/internal/service/ec2/ec2_fleet.go b/internal/service/ec2/ec2_fleet.go index 9dc4fed6828..1bf967f5daf 100644 --- a/internal/service/ec2/ec2_fleet.go +++ b/internal/service/ec2/ec2_fleet.go @@ -1088,10 +1088,17 @@ func expandFleetLaunchTemplateOverridesRequest(tfMap map[string]interface{}) *ec apiObject.InstanceType = aws.String(v) } + if v, ok := tfMap["image_id"].(string); ok && v != "" { + apiObject.ImageId = aws.String(v) + } + if v, ok := tfMap["max_price"].(string); ok && v != "" { apiObject.MaxPrice = aws.String(v) } + if v, ok := tfMap["placement"]; ok && len(v.([]interface{})) > 0 && v.([]interface{})[0] != nil { + apiObject.Placement = expandPlacement(v.([]interface{})[0].(map[string]interface{})) + } if v, ok := tfMap["priority"].(float64); ok && v != 0 { apiObject.Priority = aws.Float64(v) } @@ -1446,6 +1453,10 @@ func flattenFleetLaunchTemplateOverrides(apiObject *ec2.FleetLaunchTemplateOverr tfMap["instance_requirements"] = []interface{}{flattenInstanceRequirements(v)} } + if v := apiObject.ImageId; v != nil { + tfMap["image_id"] = aws.StringValue(v) + } + if v := apiObject.InstanceType; v != nil { tfMap["instance_type"] = aws.StringValue(v) } @@ -1454,6 +1465,10 @@ func flattenFleetLaunchTemplateOverrides(apiObject *ec2.FleetLaunchTemplateOverr tfMap["max_price"] = aws.StringValue(v) } + if v := apiObject.Placement; v != nil { + tfMap["placement"] = []interface{}{flattenPlacement(v)} + } + if v := apiObject.Priority; v != nil { tfMap["priority"] = aws.Float64Value(v) }