From cc1b98aea26efbc7b98455de52c53e81694cea5c Mon Sep 17 00:00:00 2001 From: Mohammed Date: Thu, 28 Apr 2022 11:36:35 -0500 Subject: [PATCH 1/9] Add InstanceRequirement (ABIS) to Spot Request --- internal/service/ec2/spot_fleet_request.go | 146 ++++++++++++++++++++- 1 file changed, 141 insertions(+), 5 deletions(-) diff --git a/internal/service/ec2/spot_fleet_request.go b/internal/service/ec2/spot_fleet_request.go index 49e4909b3a4b..e49e300492cc 100644 --- a/internal/service/ec2/spot_fleet_request.go +++ b/internal/service/ec2/spot_fleet_request.go @@ -360,6 +360,52 @@ func ResourceSpotFleetRequest() *schema.Resource { ForceNew: true, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ + "instance_requirements": { + Type: schema.TypeList, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "vcpu_count": { + Type: schema.TypeList, + Required: true, + ForceNew: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "min": { + Type: schema.TypeInt, + Optional: true, + ForceNew: true, + }, + "max": { + Type: schema.TypeInt, + Optional: true, + ForceNew: true, + }, + }, + }, + }, + "memory_mib": { + Type: schema.TypeList, + Required: true, + ForceNew: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "min": { + Type: schema.TypeInt, + Optional: true, + ForceNew: true, + }, + "max": { + Type: schema.TypeInt, + Optional: true, + ForceNew: true, + }, + }, + }, + }, + }, + }, + }, "availability_zone": { Type: schema.TypeString, Optional: true, @@ -860,11 +906,34 @@ func buildLaunchTemplateConfigs(d *schema.ResourceData) []*ec2.LaunchTemplateCon if v, ok := ors["availability_zone"].(string); ok && v != "" { lto.AvailabilityZone = aws.String(v) } + if v, ok := ors["instance_requirements"]; ok { + vL := v.([]interface{}) + irs := &ec2.InstanceRequirements{} + + for _, v := range vL { + ir := v.(map[string]interface{}) + if v, ok := ir["vcpu_count"]; ok { + irL := v.([]interface{}) + vals := irL[0].(map[string]interface{}) + irs.VCpuCount = &ec2.VCpuCountRange{Min: aws.Int64(int64(vals["min"].(int))), Max: aws.Int64(int64(vals["max"].(int)))} + } + if v, ok := ir["memory_mib"]; ok { + irL := v.([]interface{}) + vals := irL[0].(map[string]interface{}) + irs.MemoryMiB = &ec2.MemoryMiB{Min: aws.Int64(int64(vals["min"].(int))), Max: aws.Int64(int64(vals["max"].(int)))} + } + } + lto.InstanceRequirements = irs + } if v, ok := ors["instance_type"].(string); ok && v != "" { lto.InstanceType = aws.String(v) } + if v, ok := ors["priority"].(float64); ok && v > 0 { + lto.Priority = aws.Float64(v) + } + if v, ok := ors["spot_price"].(string); ok && v != "" { lto.SpotPrice = aws.String(v) } @@ -877,10 +946,6 @@ func buildLaunchTemplateConfigs(d *schema.ResourceData) []*ec2.LaunchTemplateCon lto.WeightedCapacity = aws.Float64(v) } - if v, ok := ors["priority"].(float64); ok { - lto.Priority = aws.Float64(v) - } - overrides = append(overrides, lto) } @@ -1375,10 +1440,67 @@ func flattenSpotFleetRequestLaunchTemplateOverrides(override *ec2.LaunchTemplate if override.Priority != nil { m["priority"] = aws.Float64Value(override.Priority) } + if v := override.InstanceRequirements; v != nil { + m["instance_requirements"] = []interface{}{flattenInstanceRequirements(v)} + } return m } +func flattenInstanceRequirements(apiObject *ec2.InstanceRequirements) map[string]interface{} { + if apiObject == nil { + return nil + } + + irMap := map[string]interface{}{} + + if v := apiObject.VCpuCount; v != nil { + irMap["vcpu_count"] = []interface{}{flattenInstanceRequirementsCpu(v)} + + } + if v := apiObject.MemoryMiB; v != nil { + irMap["memory_mib"] = []interface{}{flattenInstanceRequirementsMemory(v)} + } + + return irMap +} + +func flattenInstanceRequirementsCpu(apiObject *ec2.VCpuCountRange) map[string]interface{} { + if apiObject == nil { + return nil + } + + rMap := map[string]interface{}{} + + if v := apiObject.Min; v != nil { + rMap["min"] = aws.Int64Value(v) + + } + if v := apiObject.Max; v != nil { + rMap["max"] = aws.Int64Value(v) + + } + return rMap +} + +func flattenInstanceRequirementsMemory(apiObject *ec2.MemoryMiB) map[string]interface{} { + if apiObject == nil { + return nil + } + + rMap := map[string]interface{}{} + + if v := apiObject.Min; v != nil { + rMap["min"] = aws.Int64Value(v) + + } + if v := apiObject.Max; v != nil { + rMap["max"] = aws.Int64Value(v) + + } + return rMap +} + func launchSpecsToSet(launchSpecs []*ec2.SpotFleetLaunchSpecification, conn *ec2.EC2) (*schema.Set, error) { specSet := &schema.Set{F: hashLaunchSpecification} for _, spec := range launchSpecs { @@ -1772,7 +1894,21 @@ func hashLaunchTemplateOverrides(v interface{}) int { if m["priority"] != nil { buf.WriteString(fmt.Sprintf("%f-", m["priority"].(float64))) } - + if d, ok := m["instance_requirement"]; ok { + if len(d.(map[string]interface{})) > 0 { + ir := d.(map[string]interface{}) + if a, ok := ir["vcpu_count"]; ok { + r := a.(map[string]interface{}) + buf.WriteString(fmt.Sprintf("%d-", r["min"].(int64))) + buf.WriteString(fmt.Sprintf("%d-", r["max"].(int64))) + } + if a, ok := ir["memory_mib"]; ok { + r := a.(map[string]interface{}) + buf.WriteString(fmt.Sprintf("%d-", r["min"].(int64))) + buf.WriteString(fmt.Sprintf("%d-", r["max"].(int64))) + } + } + } return create.StringHashcode(buf.String()) } From 08ba557dc81985ad30151e71dc5ab1d3719942fc Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Mon, 9 May 2022 12:27:28 -0400 Subject: [PATCH 2/9] Revert "Add InstanceRequirement (ABIS) to Spot Request" This reverts commit cc1b98aea26efbc7b98455de52c53e81694cea5c. --- internal/service/ec2/spot_fleet_request.go | 146 +-------------------- 1 file changed, 5 insertions(+), 141 deletions(-) diff --git a/internal/service/ec2/spot_fleet_request.go b/internal/service/ec2/spot_fleet_request.go index e49e300492cc..49e4909b3a4b 100644 --- a/internal/service/ec2/spot_fleet_request.go +++ b/internal/service/ec2/spot_fleet_request.go @@ -360,52 +360,6 @@ func ResourceSpotFleetRequest() *schema.Resource { ForceNew: true, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ - "instance_requirements": { - Type: schema.TypeList, - Optional: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "vcpu_count": { - Type: schema.TypeList, - Required: true, - ForceNew: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "min": { - Type: schema.TypeInt, - Optional: true, - ForceNew: true, - }, - "max": { - Type: schema.TypeInt, - Optional: true, - ForceNew: true, - }, - }, - }, - }, - "memory_mib": { - Type: schema.TypeList, - Required: true, - ForceNew: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "min": { - Type: schema.TypeInt, - Optional: true, - ForceNew: true, - }, - "max": { - Type: schema.TypeInt, - Optional: true, - ForceNew: true, - }, - }, - }, - }, - }, - }, - }, "availability_zone": { Type: schema.TypeString, Optional: true, @@ -906,34 +860,11 @@ func buildLaunchTemplateConfigs(d *schema.ResourceData) []*ec2.LaunchTemplateCon if v, ok := ors["availability_zone"].(string); ok && v != "" { lto.AvailabilityZone = aws.String(v) } - if v, ok := ors["instance_requirements"]; ok { - vL := v.([]interface{}) - irs := &ec2.InstanceRequirements{} - - for _, v := range vL { - ir := v.(map[string]interface{}) - if v, ok := ir["vcpu_count"]; ok { - irL := v.([]interface{}) - vals := irL[0].(map[string]interface{}) - irs.VCpuCount = &ec2.VCpuCountRange{Min: aws.Int64(int64(vals["min"].(int))), Max: aws.Int64(int64(vals["max"].(int)))} - } - if v, ok := ir["memory_mib"]; ok { - irL := v.([]interface{}) - vals := irL[0].(map[string]interface{}) - irs.MemoryMiB = &ec2.MemoryMiB{Min: aws.Int64(int64(vals["min"].(int))), Max: aws.Int64(int64(vals["max"].(int)))} - } - } - lto.InstanceRequirements = irs - } if v, ok := ors["instance_type"].(string); ok && v != "" { lto.InstanceType = aws.String(v) } - if v, ok := ors["priority"].(float64); ok && v > 0 { - lto.Priority = aws.Float64(v) - } - if v, ok := ors["spot_price"].(string); ok && v != "" { lto.SpotPrice = aws.String(v) } @@ -946,6 +877,10 @@ func buildLaunchTemplateConfigs(d *schema.ResourceData) []*ec2.LaunchTemplateCon lto.WeightedCapacity = aws.Float64(v) } + if v, ok := ors["priority"].(float64); ok { + lto.Priority = aws.Float64(v) + } + overrides = append(overrides, lto) } @@ -1440,67 +1375,10 @@ func flattenSpotFleetRequestLaunchTemplateOverrides(override *ec2.LaunchTemplate if override.Priority != nil { m["priority"] = aws.Float64Value(override.Priority) } - if v := override.InstanceRequirements; v != nil { - m["instance_requirements"] = []interface{}{flattenInstanceRequirements(v)} - } return m } -func flattenInstanceRequirements(apiObject *ec2.InstanceRequirements) map[string]interface{} { - if apiObject == nil { - return nil - } - - irMap := map[string]interface{}{} - - if v := apiObject.VCpuCount; v != nil { - irMap["vcpu_count"] = []interface{}{flattenInstanceRequirementsCpu(v)} - - } - if v := apiObject.MemoryMiB; v != nil { - irMap["memory_mib"] = []interface{}{flattenInstanceRequirementsMemory(v)} - } - - return irMap -} - -func flattenInstanceRequirementsCpu(apiObject *ec2.VCpuCountRange) map[string]interface{} { - if apiObject == nil { - return nil - } - - rMap := map[string]interface{}{} - - if v := apiObject.Min; v != nil { - rMap["min"] = aws.Int64Value(v) - - } - if v := apiObject.Max; v != nil { - rMap["max"] = aws.Int64Value(v) - - } - return rMap -} - -func flattenInstanceRequirementsMemory(apiObject *ec2.MemoryMiB) map[string]interface{} { - if apiObject == nil { - return nil - } - - rMap := map[string]interface{}{} - - if v := apiObject.Min; v != nil { - rMap["min"] = aws.Int64Value(v) - - } - if v := apiObject.Max; v != nil { - rMap["max"] = aws.Int64Value(v) - - } - return rMap -} - func launchSpecsToSet(launchSpecs []*ec2.SpotFleetLaunchSpecification, conn *ec2.EC2) (*schema.Set, error) { specSet := &schema.Set{F: hashLaunchSpecification} for _, spec := range launchSpecs { @@ -1894,21 +1772,7 @@ func hashLaunchTemplateOverrides(v interface{}) int { if m["priority"] != nil { buf.WriteString(fmt.Sprintf("%f-", m["priority"].(float64))) } - if d, ok := m["instance_requirement"]; ok { - if len(d.(map[string]interface{})) > 0 { - ir := d.(map[string]interface{}) - if a, ok := ir["vcpu_count"]; ok { - r := a.(map[string]interface{}) - buf.WriteString(fmt.Sprintf("%d-", r["min"].(int64))) - buf.WriteString(fmt.Sprintf("%d-", r["max"].(int64))) - } - if a, ok := ir["memory_mib"]; ok { - r := a.(map[string]interface{}) - buf.WriteString(fmt.Sprintf("%d-", r["min"].(int64))) - buf.WriteString(fmt.Sprintf("%d-", r["max"].(int64))) - } - } - } + return create.StringHashcode(buf.String()) } From f09b778b2ad810da713ef78fd2da1f954a3d5a12 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Mon, 9 May 2022 14:03:14 -0400 Subject: [PATCH 3/9] Add CHANGELOG entry. --- .changelog/24448.txt | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .changelog/24448.txt diff --git a/.changelog/24448.txt b/.changelog/24448.txt new file mode 100644 index 000000000000..35a6ac78c34a --- /dev/null +++ b/.changelog/24448.txt @@ -0,0 +1,3 @@ +```release-note:enhancement +resource/aws_spot_fleet_request: Add `overrides.instance_requirements` argument +``` \ No newline at end of file From 87e15d6de64ab82b259c210bf1467033a7986afd Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Mon, 9 May 2022 14:41:49 -0400 Subject: [PATCH 4/9] r/aws_spot_fleet_request: Tidy up 'launch_template_config' expanders. --- .../service/ec2/ec2_spot_fleet_request.go | 679 ++++++++++++++++-- 1 file changed, 625 insertions(+), 54 deletions(-) diff --git a/internal/service/ec2/ec2_spot_fleet_request.go b/internal/service/ec2/ec2_spot_fleet_request.go index c24f3ecf4f15..2269e56606d7 100644 --- a/internal/service/ec2/ec2_spot_fleet_request.go +++ b/internal/service/ec2/ec2_spot_fleet_request.go @@ -389,6 +389,288 @@ func ResourceSpotFleetRequest() *schema.Resource { Optional: true, ForceNew: true, }, + "instance_requirements": { + Type: schema.TypeList, + Optional: true, + ForceNew: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "accelerator_count": { + Type: schema.TypeList, + Optional: true, + ForceNew: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "max": { + Type: schema.TypeInt, + Optional: true, + ForceNew: true, + ValidateFunc: validation.IntAtLeast(0), + }, + "min": { + Type: schema.TypeInt, + Optional: true, + ForceNew: true, + ValidateFunc: validation.IntAtLeast(1), + }, + }, + }, + }, + "accelerator_manufacturers": { + Type: schema.TypeSet, + Optional: true, + ForceNew: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + ValidateFunc: validation.StringInSlice(ec2.AcceleratorManufacturer_Values(), false), + }, + }, + "accelerator_names": { + Type: schema.TypeSet, + Optional: true, + ForceNew: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + ValidateFunc: validation.StringInSlice(ec2.AcceleratorName_Values(), false), + }, + }, + "accelerator_total_memory_mib": { + Type: schema.TypeList, + Optional: true, + ForceNew: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "max": { + Type: schema.TypeInt, + Optional: true, + ForceNew: true, + ValidateFunc: validation.IntAtLeast(1), + }, + "min": { + Type: schema.TypeInt, + Optional: true, + ForceNew: true, + ValidateFunc: validation.IntAtLeast(1), + }, + }, + }, + }, + "accelerator_types": { + Type: schema.TypeSet, + Optional: true, + ForceNew: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + ValidateFunc: validation.StringInSlice(ec2.AcceleratorType_Values(), false), + }, + }, + "bare_metal": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + ValidateFunc: validation.StringInSlice(ec2.BareMetal_Values(), false), + }, + "baseline_ebs_bandwidth_mbps": { + Type: schema.TypeList, + Optional: true, + ForceNew: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "max": { + Type: schema.TypeInt, + Optional: true, + ForceNew: true, + ValidateFunc: validation.IntAtLeast(1), + }, + "min": { + Type: schema.TypeInt, + Optional: true, + ForceNew: true, + ValidateFunc: validation.IntAtLeast(1), + }, + }, + }, + }, + "burstable_performance": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + ValidateFunc: validation.StringInSlice(ec2.BurstablePerformance_Values(), false), + }, + "cpu_manufacturers": { + Type: schema.TypeSet, + Optional: true, + ForceNew: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + ValidateFunc: validation.StringInSlice(ec2.CpuManufacturer_Values(), false), + }, + }, + "excluded_instance_types": { + Type: schema.TypeSet, + Optional: true, + ForceNew: true, + MaxItems: 400, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "instance_generations": { + Type: schema.TypeSet, + Optional: true, + ForceNew: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + ValidateFunc: validation.StringInSlice(ec2.InstanceGeneration_Values(), false), + }, + }, + "local_storage": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + ValidateFunc: validation.StringInSlice(ec2.LocalStorage_Values(), false), + }, + "local_storage_types": { + Type: schema.TypeSet, + Optional: true, + ForceNew: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + ValidateFunc: validation.StringInSlice(ec2.LocalStorageType_Values(), false), + }, + }, + "memory_gib_per_vcpu": { + Type: schema.TypeList, + Optional: true, + ForceNew: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "max": { + Type: schema.TypeFloat, + Optional: true, + ForceNew: true, + ValidateFunc: verify.FloatGreaterThan(0.0), + }, + "min": { + Type: schema.TypeFloat, + Optional: true, + ForceNew: true, + ValidateFunc: verify.FloatGreaterThan(0.0), + }, + }, + }, + }, + "memory_mib": { + Type: schema.TypeList, + Required: true, + ForceNew: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "max": { + Type: schema.TypeInt, + Optional: true, + ForceNew: true, + ValidateFunc: validation.IntAtLeast(1), + }, + "min": { + Type: schema.TypeInt, + Required: true, + ForceNew: true, + ValidateFunc: validation.IntAtLeast(1), + }, + }, + }, + }, + "network_interface_count": { + Type: schema.TypeList, + Optional: true, + ForceNew: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "max": { + Type: schema.TypeInt, + Optional: true, + ForceNew: true, + ValidateFunc: validation.IntAtLeast(1), + }, + "min": { + Type: schema.TypeInt, + Optional: true, + ForceNew: true, + ValidateFunc: validation.IntAtLeast(1), + }, + }, + }, + }, + "on_demand_max_price_percentage_over_lowest_price": { + Type: schema.TypeInt, + Optional: true, + ForceNew: true, + ValidateFunc: validation.IntAtLeast(1), + }, + "require_hibernate_support": { + Type: schema.TypeBool, + Optional: true, + ForceNew: true, + }, + "spot_max_price_percentage_over_lowest_price": { + Type: schema.TypeInt, + Optional: true, + ForceNew: true, + ValidateFunc: validation.IntAtLeast(1), + }, + "total_local_storage_gb": { + Type: schema.TypeList, + Optional: true, + ForceNew: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "max": { + Type: schema.TypeFloat, + Optional: true, + ForceNew: true, + ValidateFunc: verify.FloatGreaterThan(0.0), + }, + "min": { + Type: schema.TypeFloat, + Optional: true, + ForceNew: true, + ValidateFunc: verify.FloatGreaterThan(0.0), + }, + }, + }, + }, + "vcpu_count": { + Type: schema.TypeList, + Required: true, + ForceNew: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "max": { + Type: schema.TypeInt, + Optional: true, + ForceNew: true, + ValidateFunc: validation.IntAtLeast(1), + }, + "min": { + Type: schema.TypeInt, + Required: true, + ForceNew: true, + ValidateFunc: validation.IntAtLeast(1), + }, + }, + }, + }, + }, + }, + }, "instance_type": { Type: schema.TypeString, Optional: true, @@ -546,7 +828,6 @@ func resourceSpotFleetRequestCreate(d *schema.ResourceData, meta interface{}) er tags := defaultTagsConfig.MergeTags(tftags.New(d.Get("tags").(map[string]interface{}))) _, launchSpecificationOk := d.GetOk("launch_specification") - _, launchTemplateConfigsOk := d.GetOk("launch_template_config") // http://docs.aws.amazon.com/sdk-for-go/api/service/ec2.html#type-SpotFleetRequestConfigData spotFleetConfig := &ec2.SpotFleetRequestConfigData{ @@ -568,9 +849,8 @@ func resourceSpotFleetRequestCreate(d *schema.ResourceData, meta interface{}) er spotFleetConfig.LaunchSpecifications = launchSpecs } - if launchTemplateConfigsOk { - launchTemplates := buildLaunchTemplateConfigs(d) - spotFleetConfig.LaunchTemplateConfigs = launchTemplates + if v, ok := d.GetOk("launch_template_config"); ok && v.(*schema.Set).Len() > 0 { + spotFleetConfig.LaunchTemplateConfigs = expandLaunchTemplateConfigs(v.(*schema.Set).List()) } if v, ok := d.GetOk("excess_capacity_termination_policy"); ok { @@ -1172,81 +1452,372 @@ func buildSpotFleetLaunchSpecifications(d *schema.ResourceData, meta interface{} return specs, nil } -func buildLaunchTemplateConfigs(d *schema.ResourceData) []*ec2.LaunchTemplateConfig { - launchTemplateConfigs := d.Get("launch_template_config").(*schema.Set) - configs := make([]*ec2.LaunchTemplateConfig, 0) +func expandLaunchTemplateConfig(tfMap map[string]interface{}) *ec2.LaunchTemplateConfig { + if tfMap == nil { + return nil + } - for _, launchTemplateConfig := range launchTemplateConfigs.List() { + apiObject := &ec2.LaunchTemplateConfig{} - ltc := &ec2.LaunchTemplateConfig{} + if v, ok := tfMap["launch_template_specification"].([]interface{}); ok && len(v) > 0 { + apiObject.LaunchTemplateSpecification = expandFleetLaunchTemplateSpecification(v[0].(map[string]interface{})) + } - ltcMap := launchTemplateConfig.(map[string]interface{}) + if v, ok := tfMap["overrides"].(*schema.Set); ok && v.Len() > 0 { + apiObject.Overrides = expandLaunchTemplateOverrideses(v.List()) + } - //launch template spec - if v, ok := ltcMap["launch_template_specification"]; ok { - vL := v.([]interface{}) - lts := vL[0].(map[string]interface{}) + return apiObject +} - flts := &ec2.FleetLaunchTemplateSpecification{} +func expandLaunchTemplateConfigs(tfList []interface{}) []*ec2.LaunchTemplateConfig { + if len(tfList) == 0 { + return nil + } - if v, ok := lts["id"].(string); ok && v != "" { - flts.LaunchTemplateId = aws.String(v) - } + var apiObjects []*ec2.LaunchTemplateConfig - if v, ok := lts["name"].(string); ok && v != "" { - flts.LaunchTemplateName = aws.String(v) - } + for _, tfMapRaw := range tfList { + tfMap, ok := tfMapRaw.(map[string]interface{}) - if v, ok := lts["version"].(string); ok && v != "" { - flts.Version = aws.String(v) - } + if !ok { + continue + } - ltc.LaunchTemplateSpecification = flts + apiObject := expandLaunchTemplateConfig(tfMap) + if apiObject == nil { + continue } - if v, ok := ltcMap["overrides"]; ok && v.(*schema.Set).Len() > 0 { - vL := v.(*schema.Set).List() - overrides := make([]*ec2.LaunchTemplateOverrides, 0) + apiObjects = append(apiObjects, apiObject) + } - for _, v := range vL { - ors := v.(map[string]interface{}) - lto := &ec2.LaunchTemplateOverrides{} + return apiObjects +} - if v, ok := ors["availability_zone"].(string); ok && v != "" { - lto.AvailabilityZone = aws.String(v) - } +func expandFleetLaunchTemplateSpecification(tfMap map[string]interface{}) *ec2.FleetLaunchTemplateSpecification { + if tfMap == nil { + return nil + } - if v, ok := ors["instance_type"].(string); ok && v != "" { - lto.InstanceType = aws.String(v) - } + apiObject := &ec2.FleetLaunchTemplateSpecification{} - if v, ok := ors["spot_price"].(string); ok && v != "" { - lto.SpotPrice = aws.String(v) - } + if v, ok := tfMap["id"].(string); ok && v != "" { + apiObject.LaunchTemplateId = aws.String(v) + } - if v, ok := ors["subnet_id"].(string); ok && v != "" { - lto.SubnetId = aws.String(v) - } + if v, ok := tfMap["name"].(string); ok && v != "" { + apiObject.LaunchTemplateName = aws.String(v) + } - if v, ok := ors["weighted_capacity"].(float64); ok && v > 0 { - lto.WeightedCapacity = aws.Float64(v) - } + if v, ok := tfMap["version"].(string); ok && v != "" { + apiObject.Version = aws.String(v) + } - if v, ok := ors["priority"].(float64); ok { - lto.Priority = aws.Float64(v) - } + return apiObject +} - overrides = append(overrides, lto) - } +func expandLaunchTemplateOverrides(tfMap map[string]interface{}) *ec2.LaunchTemplateOverrides { + if tfMap == nil { + return nil + } + + apiObject := &ec2.LaunchTemplateOverrides{} + + if v, ok := tfMap["availability_zone"].(string); ok && v != "" { + apiObject.AvailabilityZone = aws.String(v) + } + + if v, ok := tfMap["instance_requirements"].([]interface{}); ok && len(v) > 0 { + apiObject.InstanceRequirements = expandInstanceRequirements(v[0].(map[string]interface{})) + } + + if v, ok := tfMap["instance_type"].(string); ok && v != "" { + apiObject.InstanceType = aws.String(v) + } + + if v, ok := tfMap["priority"].(float64); ok && v != 0.0 { + apiObject.Priority = aws.Float64(v) + } + + if v, ok := tfMap["spot_price"].(string); ok && v != "" { + apiObject.SpotPrice = aws.String(v) + } + + if v, ok := tfMap["subnet_id"].(string); ok && v != "" { + apiObject.SubnetId = aws.String(v) + } + + if v, ok := tfMap["weighted_capacity"].(float64); ok && v != 0.0 { + apiObject.WeightedCapacity = aws.Float64(v) + } - ltc.Overrides = overrides + return apiObject +} + +func expandLaunchTemplateOverrideses(tfList []interface{}) []*ec2.LaunchTemplateOverrides { + if len(tfList) == 0 { + return nil + } + + var apiObjects []*ec2.LaunchTemplateOverrides + + for _, tfMapRaw := range tfList { + tfMap, ok := tfMapRaw.(map[string]interface{}) + + if !ok { + continue + } + + apiObject := expandLaunchTemplateOverrides(tfMap) + + if apiObject == nil { + continue } - configs = append(configs, ltc) + apiObjects = append(apiObjects, apiObject) + } + + return apiObjects +} + +func expandInstanceRequirements(tfMap map[string]interface{}) *ec2.InstanceRequirements { + if tfMap == nil { + return nil + } + + apiObject := &ec2.InstanceRequirements{} + + if v, ok := tfMap["accelerator_count"].([]interface{}); ok && len(v) > 0 { + apiObject.AcceleratorCount = expandAcceleratorCount(v[0].(map[string]interface{})) + } + + if v, ok := tfMap["accelerator_manufacturers"].(*schema.Set); ok && v.Len() > 0 { + apiObject.AcceleratorManufacturers = flex.ExpandStringSet(v) + } + + if v, ok := tfMap["accelerator_names"].(*schema.Set); ok && v.Len() > 0 { + apiObject.AcceleratorNames = flex.ExpandStringSet(v) + } + + if v, ok := tfMap["accelerator_total_memory_mib"].([]interface{}); ok && len(v) > 0 { + apiObject.AcceleratorTotalMemoryMiB = expandAcceleratorTotalMemoryMiB(v[0].(map[string]interface{})) + } + + if v, ok := tfMap["accelerator_types"].(*schema.Set); ok && v.Len() > 0 { + apiObject.AcceleratorTypes = flex.ExpandStringSet(v) + } + + if v, ok := tfMap["bare_metal"].(string); ok && v != "" { + apiObject.BareMetal = aws.String(v) + } + + if v, ok := tfMap["baseline_ebs_bandwidth_mbps"].([]interface{}); ok && len(v) > 0 { + apiObject.BaselineEbsBandwidthMbps = expandBaselineEbsBandwidthMbps(v[0].(map[string]interface{})) + } + + if v, ok := tfMap["burstable_performance"].(string); ok && v != "" { + apiObject.BurstablePerformance = aws.String(v) + } + + if v, ok := tfMap["cpu_manufacturers"].(*schema.Set); ok && v.Len() > 0 { + apiObject.CpuManufacturers = flex.ExpandStringSet(v) + } + + if v, ok := tfMap["excluded_instance_types"].(*schema.Set); ok && v.Len() > 0 { + apiObject.ExcludedInstanceTypes = flex.ExpandStringSet(v) + } + + if v, ok := tfMap["instance_generations"].(*schema.Set); ok && v.Len() > 0 { + apiObject.InstanceGenerations = flex.ExpandStringSet(v) + } + + if v, ok := tfMap["local_storage"].(string); ok && v != "" { + apiObject.LocalStorage = aws.String(v) + } + + if v, ok := tfMap["local_storage_types"].(*schema.Set); ok && v.Len() > 0 { + apiObject.LocalStorageTypes = flex.ExpandStringSet(v) + } + + if v, ok := tfMap["memory_gib_per_vcpu"].([]interface{}); ok && len(v) > 0 { + apiObject.MemoryGiBPerVCpu = expandMemoryGiBPerVCpu(v[0].(map[string]interface{})) + } + + if v, ok := tfMap["memory_mib"].([]interface{}); ok && len(v) > 0 { + apiObject.MemoryMiB = expandMemoryMiB(v[0].(map[string]interface{})) + } + + if v, ok := tfMap["network_interface_count"].([]interface{}); ok && len(v) > 0 { + apiObject.NetworkInterfaceCount = expandNetworkInterfaceCount(v[0].(map[string]interface{})) + } + + if v, ok := tfMap["on_demand_max_price_percentage_over_lowest_price"].(int); ok && v != 0 { + apiObject.OnDemandMaxPricePercentageOverLowestPrice = aws.Int64(int64(v)) + } + + if v, ok := tfMap["require_hibernate_support"].(bool); ok && v { + apiObject.RequireHibernateSupport = aws.Bool(v) + } + + if v, ok := tfMap["spot_max_price_percentage_over_lowest_price"].(int); ok && v != 0 { + apiObject.SpotMaxPricePercentageOverLowestPrice = aws.Int64(int64(v)) + } + + if v, ok := tfMap["total_local_storage_gb"].([]interface{}); ok && len(v) > 0 { + apiObject.TotalLocalStorageGB = expandTotalLocalStorageGB(v[0].(map[string]interface{})) + } + + if v, ok := tfMap["vcpu_count"].([]interface{}); ok && len(v) > 0 { + apiObject.VCpuCount = expandVCpuCountRange(v[0].(map[string]interface{})) + } + + return apiObject +} + +func expandAcceleratorCount(tfMap map[string]interface{}) *ec2.AcceleratorCount { + if tfMap == nil { + return nil + } + + apiObject := &ec2.AcceleratorCount{} + + if v, ok := tfMap["max"].(int); ok { + apiObject.Max = aws.Int64(int64(v)) + } + + if v, ok := tfMap["min"].(int); ok { + apiObject.Min = aws.Int64(int64(v)) + } + + return apiObject +} + +func expandAcceleratorTotalMemoryMiB(tfMap map[string]interface{}) *ec2.AcceleratorTotalMemoryMiB { + if tfMap == nil { + return nil + } + + apiObject := &ec2.AcceleratorTotalMemoryMiB{} + + if v, ok := tfMap["max"].(int); ok { + apiObject.Max = aws.Int64(int64(v)) + } + + if v, ok := tfMap["min"].(int); ok { + apiObject.Min = aws.Int64(int64(v)) + } + + return apiObject +} + +func expandBaselineEbsBandwidthMbps(tfMap map[string]interface{}) *ec2.BaselineEbsBandwidthMbps { + if tfMap == nil { + return nil + } + + apiObject := &ec2.BaselineEbsBandwidthMbps{} + + if v, ok := tfMap["max"].(int); ok { + apiObject.Max = aws.Int64(int64(v)) + } + + if v, ok := tfMap["min"].(int); ok { + apiObject.Min = aws.Int64(int64(v)) + } + + return apiObject +} + +func expandMemoryGiBPerVCpu(tfMap map[string]interface{}) *ec2.MemoryGiBPerVCpu { + if tfMap == nil { + return nil + } + + apiObject := &ec2.MemoryGiBPerVCpu{} + + if v, ok := tfMap["max"].(float64); ok { + apiObject.Max = aws.Float64(v) + } + + if v, ok := tfMap["min"].(float64); ok { + apiObject.Min = aws.Float64(v) + } + + return apiObject +} + +func expandMemoryMiB(tfMap map[string]interface{}) *ec2.MemoryMiB { + if tfMap == nil { + return nil + } + + apiObject := &ec2.MemoryMiB{} + + if v, ok := tfMap["max"].(int); ok { + apiObject.Max = aws.Int64(int64(v)) + } + + if v, ok := tfMap["min"].(int); ok { + apiObject.Min = aws.Int64(int64(v)) + } + + return apiObject +} + +func expandNetworkInterfaceCount(tfMap map[string]interface{}) *ec2.NetworkInterfaceCount { + if tfMap == nil { + return nil + } + + apiObject := &ec2.NetworkInterfaceCount{} + + if v, ok := tfMap["max"].(int); ok { + apiObject.Max = aws.Int64(int64(v)) + } + + if v, ok := tfMap["min"].(int); ok { + apiObject.Min = aws.Int64(int64(v)) + } + + return apiObject +} + +func expandTotalLocalStorageGB(tfMap map[string]interface{}) *ec2.TotalLocalStorageGB { + if tfMap == nil { + return nil + } + + apiObject := &ec2.TotalLocalStorageGB{} + + if v, ok := tfMap["max"].(float64); ok { + apiObject.Max = aws.Float64(v) + } + + if v, ok := tfMap["min"].(float64); ok { + apiObject.Min = aws.Float64(v) + } + + return apiObject +} + +func expandVCpuCountRange(tfMap map[string]interface{}) *ec2.VCpuCountRange { + if tfMap == nil { + return nil + } + + apiObject := &ec2.VCpuCountRange{} + + if v, ok := tfMap["max"].(int); ok { + apiObject.Max = aws.Int64(int64(v)) + } + + if v, ok := tfMap["min"].(int); ok { + apiObject.Min = aws.Int64(int64(v)) } - return configs + return apiObject } func expandSpotMaintenanceStrategies(l []interface{}) *ec2.SpotMaintenanceStrategies { From 02d80a429a04a9856468af7eb575a0392277ce40 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Mon, 9 May 2022 15:05:09 -0400 Subject: [PATCH 5/9] r/aws_spot_fleet_request: Tidy up 'launch_template_config' flatteners. Acceptance test output: % make testacc TESTS=TestAccEC2SpotFleetRequest_launchTemplateWithOverrides PKG=ec2 ==> Checking that code complies with gofmt requirements... TF_ACC=1 go test ./internal/service/ec2/... -v -count 1 -parallel 20 -run='TestAccEC2SpotFleetRequest_launchTemplateWithOverrides' -timeout 180m === RUN TestAccEC2SpotFleetRequest_launchTemplateWithOverrides === PAUSE TestAccEC2SpotFleetRequest_launchTemplateWithOverrides === CONT TestAccEC2SpotFleetRequest_launchTemplateWithOverrides --- PASS: TestAccEC2SpotFleetRequest_launchTemplateWithOverrides (147.48s) PASS ok github.com/hashicorp/terraform-provider-aws/internal/service/ec2 151.577s --- .../service/ec2/ec2_spot_fleet_request.go | 159 +++++++++++------- .../ec2/ec2_spot_fleet_request_test.go | 12 ++ 2 files changed, 106 insertions(+), 65 deletions(-) diff --git a/internal/service/ec2/ec2_spot_fleet_request.go b/internal/service/ec2/ec2_spot_fleet_request.go index 2269e56606d7..6c628c79f194 100644 --- a/internal/service/ec2/ec2_spot_fleet_request.go +++ b/internal/service/ec2/ec2_spot_fleet_request.go @@ -702,7 +702,6 @@ func ResourceSpotFleetRequest() *schema.Resource { }, }, }, - Set: hashLaunchTemplateOverrides, }, }, }, @@ -1055,10 +1054,8 @@ func resourceSpotFleetRequestRead(d *schema.ResourceData, meta interface{}) erro return fmt.Errorf("setting tags_all: %w", err) } - if len(config.LaunchTemplateConfigs) > 0 { - if err := d.Set("launch_template_config", flattenFleetLaunchTemplateConfig(config.LaunchTemplateConfigs)); err != nil { - return fmt.Errorf("setting launch_template_config: %w", err) - } + if err := d.Set("launch_template_config", flattenLaunchTemplateConfigs(config.LaunchTemplateConfigs)); err != nil { + return fmt.Errorf("setting launch_template_config: %w", err) } d.Set("on_demand_target_capacity", config.OnDemandTargetCapacity) @@ -1850,35 +1847,6 @@ func expandSpotCapacityRebalance(l []interface{}) *ec2.SpotCapacityRebalance { return capacityRebalance } -func flattenSpotFleetRequestLaunchTemplateOverrides(override *ec2.LaunchTemplateOverrides) map[string]interface{} { - m := make(map[string]interface{}) - - if override.AvailabilityZone != nil { - m["availability_zone"] = aws.StringValue(override.AvailabilityZone) - } - if override.InstanceType != nil { - m["instance_type"] = aws.StringValue(override.InstanceType) - } - - if override.SpotPrice != nil { - m["spot_price"] = aws.StringValue(override.SpotPrice) - } - - if override.SubnetId != nil { - m["subnet_id"] = aws.StringValue(override.SubnetId) - } - - if override.WeightedCapacity != nil { - m["weighted_capacity"] = aws.Float64Value(override.WeightedCapacity) - } - - if override.Priority != nil { - m["priority"] = aws.Float64Value(override.Priority) - } - - return m -} - func launchSpecsToSet(conn *ec2.EC2, launchSpecs []*ec2.SpotFleetLaunchSpecification) (*schema.Set, error) { specSet := &schema.Set{F: hashLaunchSpecification} for _, spec := range launchSpecs { @@ -2157,57 +2125,118 @@ func hashEbsBlockDevice(v interface{}) int { return create.StringHashcode(buf.String()) } -func flattenFleetLaunchTemplateConfig(ltcs []*ec2.LaunchTemplateConfig) []map[string]interface{} { - result := make([]map[string]interface{}, 0) +func flattenLaunchTemplateConfig(apiObject *ec2.LaunchTemplateConfig) map[string]interface{} { + if apiObject == nil { + return nil + } - for _, ltc := range ltcs { - ltcRes := map[string]interface{}{} + tfMap := map[string]interface{}{} - if ltc.LaunchTemplateSpecification != nil { - ltcRes["launch_template_specification"] = flattenFleetLaunchTemplateSpecification(ltc.LaunchTemplateSpecification) - } + if v := apiObject.LaunchTemplateSpecification; v != nil { + tfMap["launch_template_specification"] = []interface{}{flattenFleetLaunchTemplateSpecification(v)} + } + + if v := apiObject.Overrides; v != nil { + tfMap["overrides"] = flattenLaunchTemplateOverrideses(v) + } + + return tfMap +} + +func flattenLaunchTemplateConfigs(apiObjects []*ec2.LaunchTemplateConfig) []interface{} { + if len(apiObjects) == 0 { + return nil + } - if ltc.Overrides != nil { - ltcRes["overrides"] = flattenLaunchTemplateOverrides(ltc.Overrides) + var tfList []interface{} + + for _, apiObject := range apiObjects { + if apiObject == nil { + continue } - result = append(result, ltcRes) + tfList = append(tfList, flattenLaunchTemplateConfig(apiObject)) } - return result + return tfList } -func flattenFleetLaunchTemplateSpecification(flt *ec2.FleetLaunchTemplateSpecification) []map[string]interface{} { - attrs := map[string]interface{}{} - result := make([]map[string]interface{}, 0) +func flattenFleetLaunchTemplateSpecification(apiObject *ec2.FleetLaunchTemplateSpecification) map[string]interface{} { + if apiObject == nil { + return nil + } + + tfMap := map[string]interface{}{} - // unlike autoscaling.LaunchTemplateConfiguration, FleetLaunchTemplateSpecs only return what was set - if flt.LaunchTemplateId != nil { - attrs["id"] = aws.StringValue(flt.LaunchTemplateId) + if v := apiObject.LaunchTemplateId; v != nil { + tfMap["id"] = aws.StringValue(v) } - if flt.LaunchTemplateName != nil { - attrs["name"] = aws.StringValue(flt.LaunchTemplateName) + if v := apiObject.LaunchTemplateName; v != nil { + tfMap["name"] = aws.StringValue(v) } - // version is returned only if it was previously set - if flt.Version != nil { - attrs["version"] = aws.StringValue(flt.Version) - } else { - attrs["version"] = nil + if v := apiObject.Version; v != nil { + tfMap["version"] = aws.StringValue(v) + } + + return tfMap +} + +func flattenLaunchTemplateOverrides(apiObject *ec2.LaunchTemplateOverrides) map[string]interface{} { + if apiObject == nil { + return nil } - result = append(result, attrs) + tfMap := map[string]interface{}{} + + if v := apiObject.AvailabilityZone; v != nil { + tfMap["availability_zone"] = aws.StringValue(v) + } + + if v := apiObject.InstanceRequirements; v != nil { + tfMap["instance_requirements"] = []interface{}{flattenInstanceRequirements(v)} + } + + if v := apiObject.InstanceType; v != nil { + tfMap["instance_type"] = aws.StringValue(v) + } + + if v := apiObject.Priority; v != nil { + tfMap["priority"] = aws.Float64Value(v) + } - return result + if v := apiObject.SpotPrice; v != nil { + tfMap["spot_price"] = aws.StringValue(v) + } + + if v := apiObject.SubnetId; v != nil { + tfMap["subnet_id"] = aws.StringValue(v) + } + + if v := apiObject.WeightedCapacity; v != nil { + tfMap["weighted_capacity"] = aws.Float64Value(v) + } + + return tfMap } -func flattenLaunchTemplateOverrides(overrides []*ec2.LaunchTemplateOverrides) *schema.Set { - overrideSet := &schema.Set{F: hashLaunchTemplateOverrides} - for _, override := range overrides { - overrideSet.Add(flattenSpotFleetRequestLaunchTemplateOverrides(override)) +func flattenLaunchTemplateOverrideses(apiObjects []*ec2.LaunchTemplateOverrides) []interface{} { + if len(apiObjects) == 0 { + return nil } - return overrideSet + + var tfList []interface{} + + for _, apiObject := range apiObjects { + if apiObject == nil { + continue + } + + tfList = append(tfList, flattenLaunchTemplateOverrides(apiObject)) + } + + return tfList } func flattenSpotMaintenanceStrategies(spotMaintenanceStrategies *ec2.SpotMaintenanceStrategies) []interface{} { diff --git a/internal/service/ec2/ec2_spot_fleet_request_test.go b/internal/service/ec2/ec2_spot_fleet_request_test.go index 5ea739860721..846994b6080a 100644 --- a/internal/service/ec2/ec2_spot_fleet_request_test.go +++ b/internal/service/ec2/ec2_spot_fleet_request_test.go @@ -265,6 +265,18 @@ func TestAccEC2SpotFleetRequest_launchTemplateWithOverrides(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "spot_request_state", "active"), resource.TestCheckResourceAttr(resourceName, "launch_specification.#", "0"), resource.TestCheckResourceAttr(resourceName, "launch_template_config.#", "1"), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "launch_template_config.*", map[string]string{ + "overrides.#": "2", + }), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "launch_template_config.*.overrides.*", map[string]string{ + "instance_type": "t1.micro", + "weighted_capacity": "2", + }), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "launch_template_config.*.overrides.*", map[string]string{ + "instance_type": "m3.medium", + "priority": "1", + "spot_price": "0.26", + }), ), }, { From 907ac956a1b03e71cb789ec9ddf08b19ea96853f Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Mon, 9 May 2022 15:30:23 -0400 Subject: [PATCH 6/9] r/aws_spot_fleet_request: Document 'instance_requirements'. --- .../docs/r/spot_fleet_request.html.markdown | 100 ++++++++++++++++++ 1 file changed, 100 insertions(+) diff --git a/website/docs/r/spot_fleet_request.html.markdown b/website/docs/r/spot_fleet_request.html.markdown index 0b54e51303cd..d42c1af645ff 100644 --- a/website/docs/r/spot_fleet_request.html.markdown +++ b/website/docs/r/spot_fleet_request.html.markdown @@ -276,12 +276,112 @@ The `launch_template_config` block supports the following: ### Overrides * `availability_zone` - (Optional) The availability zone in which to place the request. +* `instance_requirements` - (Optional) The instance requirements. See below. * `instance_type` - (Optional) The type of instance to request. * `priority` - (Optional) The priority for the launch template override. The lower the number, the higher the priority. If no number is set, the launch template override has the lowest priority. * `spot_price` - (Optional) The maximum spot bid for this override request. * `subnet_id` - (Optional) The subnet in which to launch the requested instance. * `weighted_capacity` - (Optional) The capacity added to the fleet by a fulfilled request. +### Instance Requirements + +This configuration block supports the following: + +* `accelerator_count` - (Optional) Block describing the minimum and maximum number of accelerators (GPUs, FPGAs, or AWS Inferentia chips). Default is no minimum or maximum. + * `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. + + ``` + Valid types: + * fpga + * gpu + * inference + ``` + +* `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. + + ~> **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. + + ``` + 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. + ``` + +* `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 + ``` + +* `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` - (Optional) Block describing the minimum and maximum amount of memory (MiB). Default is no maximum. + * `min` - (Optional) Minimum. + * `max` - (Optional) Maximum. +* `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. + + 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`. +* `vcpu_count` - (Optional) Block describing the minimum and maximum number of vCPUs. Default is no maximum. + * `min` - (Optional) Minimum. + * `max` - (Optional) Maximum. + ### Timeouts The `timeouts` block allows you to specify [timeouts](https://www.terraform.io/docs/configuration/blocks/resources/syntax.html#operation-timeouts) for certain actions: From 30ebe8cccbc2c8daa6cb2bdecffe06e6c5ac6500 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Mon, 9 May 2022 15:38:31 -0400 Subject: [PATCH 7/9] r/aws_spot_fleet_request: No 'instance_requirement' attributes are Required. --- internal/service/ec2/ec2_spot_fleet_request.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/internal/service/ec2/ec2_spot_fleet_request.go b/internal/service/ec2/ec2_spot_fleet_request.go index 6c628c79f194..75e73b06c90f 100644 --- a/internal/service/ec2/ec2_spot_fleet_request.go +++ b/internal/service/ec2/ec2_spot_fleet_request.go @@ -565,7 +565,7 @@ func ResourceSpotFleetRequest() *schema.Resource { }, "memory_mib": { Type: schema.TypeList, - Required: true, + Optional: true, ForceNew: true, MaxItems: 1, Elem: &schema.Resource{ @@ -578,7 +578,7 @@ func ResourceSpotFleetRequest() *schema.Resource { }, "min": { Type: schema.TypeInt, - Required: true, + Optional: true, ForceNew: true, ValidateFunc: validation.IntAtLeast(1), }, @@ -648,7 +648,7 @@ func ResourceSpotFleetRequest() *schema.Resource { }, "vcpu_count": { Type: schema.TypeList, - Required: true, + Optional: true, ForceNew: true, MaxItems: 1, Elem: &schema.Resource{ @@ -661,7 +661,7 @@ func ResourceSpotFleetRequest() *schema.Resource { }, "min": { Type: schema.TypeInt, - Required: true, + Optional: true, ForceNew: true, ValidateFunc: validation.IntAtLeast(1), }, From 188c2961f4fed647df15cab8720290849be1f8a6 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Mon, 9 May 2022 16:13:55 -0400 Subject: [PATCH 8/9] Add 'TestAccEC2SpotFleetRequest_launchTemplateWithInstanceRequirementsOverrides'. --- .../ec2/ec2_spot_fleet_request_test.go | 124 ++++++++++++++++-- 1 file changed, 116 insertions(+), 8 deletions(-) diff --git a/internal/service/ec2/ec2_spot_fleet_request_test.go b/internal/service/ec2/ec2_spot_fleet_request_test.go index 846994b6080a..0df16d02b645 100644 --- a/internal/service/ec2/ec2_spot_fleet_request_test.go +++ b/internal/service/ec2/ec2_spot_fleet_request_test.go @@ -241,7 +241,7 @@ func TestAccEC2SpotFleetRequest_LaunchTemplate_multiple(t *testing.T) { }) } -func TestAccEC2SpotFleetRequest_launchTemplateWithOverrides(t *testing.T) { +func TestAccEC2SpotFleetRequest_launchTemplateWithInstanceTypeOverrides(t *testing.T) { var sfr ec2.SpotFleetRequestConfig rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) validUntil := time.Now().UTC().Add(24 * time.Hour).Format(time.RFC3339) @@ -259,7 +259,7 @@ func TestAccEC2SpotFleetRequest_launchTemplateWithOverrides(t *testing.T) { CheckDestroy: testAccCheckSpotFleetRequestDestroy, Steps: []resource.TestStep{ { - Config: testAccSpotFleetRequestLaunchTemplateWithOverridesConfig(rName, publicKey, validUntil), + Config: testAccSpotFleetRequestLaunchTemplateWithInstanceTypeOverridesConfig(rName, publicKey, validUntil), Check: resource.ComposeAggregateTestCheckFunc( testAccCheckSpotFleetRequestExists(resourceName, &sfr), resource.TestCheckResourceAttr(resourceName, "spot_request_state", "active"), @@ -269,13 +269,65 @@ func TestAccEC2SpotFleetRequest_launchTemplateWithOverrides(t *testing.T) { "overrides.#": "2", }), resource.TestCheckTypeSetElemNestedAttrs(resourceName, "launch_template_config.*.overrides.*", map[string]string{ - "instance_type": "t1.micro", - "weighted_capacity": "2", + "instance_requirements.#": "0", + "instance_type": "t1.micro", + "weighted_capacity": "2", }), resource.TestCheckTypeSetElemNestedAttrs(resourceName, "launch_template_config.*.overrides.*", map[string]string{ - "instance_type": "m3.medium", - "priority": "1", - "spot_price": "0.26", + "instance_requirements.#": "0", + "instance_type": "m3.medium", + "priority": "1", + "spot_price": "0.26", + }), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"wait_for_fulfillment"}, + }, + }, + }) +} + +func TestAccEC2SpotFleetRequest_launchTemplateWithInstanceRequirementsOverrides(t *testing.T) { + var sfr ec2.SpotFleetRequestConfig + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + validUntil := time.Now().UTC().Add(24 * time.Hour).Format(time.RFC3339) + resourceName := "aws_spot_fleet_request.test" + + publicKey, _, err := sdkacctest.RandSSHKeyPair(acctest.DefaultEmailAddress) + if err != nil { + t.Fatalf("error generating random SSH key: %s", err) + } + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t); testAccPreCheckSpotFleetRequest(t) }, + ErrorCheck: acctest.ErrorCheck(t, ec2.EndpointsID), + ProviderFactories: acctest.ProviderFactories, + CheckDestroy: testAccCheckSpotFleetRequestDestroy, + Steps: []resource.TestStep{ + { + Config: testAccSpotFleetRequestLaunchTemplateWithInstanceRequirementsOverridesConfig(rName, publicKey, validUntil), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckSpotFleetRequestExists(resourceName, &sfr), + resource.TestCheckResourceAttr(resourceName, "spot_request_state", "active"), + resource.TestCheckResourceAttr(resourceName, "launch_specification.#", "0"), + resource.TestCheckResourceAttr(resourceName, "launch_template_config.#", "1"), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "launch_template_config.*", map[string]string{ + "overrides.#": "1", + }), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "launch_template_config.*.overrides.*", map[string]string{ + "instance_requirements.#": "1", + "instance_requirements.0.instance_generations.#": "1", + "instance_requirements.0.memory_mib.#": "1", + "instance_requirements.0.memory_mib.0.max": "50000", + "instance_requirements.0.memory_mib.0.min": "500", + "instance_requirements.0.vcpu_count.#": "1", + "instance_requirements.0.vcpu_count.0.max": "8", + "instance_requirements.0.vcpu_count.0.min": "1", + "instance_type": "", }), ), }, @@ -2062,7 +2114,7 @@ resource "aws_spot_fleet_request" "test" { `, rName, validUntil)) } -func testAccSpotFleetRequestLaunchTemplateWithOverridesConfig(rName, publicKey, validUntil string) string { +func testAccSpotFleetRequestLaunchTemplateWithInstanceTypeOverridesConfig(rName, publicKey, validUntil string) string { return acctest.ConfigCompose(testAccSpotFleetRequestBaseConfig(rName, publicKey), fmt.Sprintf(` resource "aws_launch_template" "test" { name = %[1]q @@ -2111,6 +2163,62 @@ resource "aws_spot_fleet_request" "test" { `, rName, validUntil)) } +func testAccSpotFleetRequestLaunchTemplateWithInstanceRequirementsOverridesConfig(rName, publicKey, validUntil string) string { + return acctest.ConfigCompose(testAccSpotFleetRequestBaseConfig(rName, publicKey), fmt.Sprintf(` +resource "aws_launch_template" "test" { + name = %[1]q + image_id = data.aws_ami.amzn-ami-minimal-hvm-ebs.id + instance_type = data.aws_ec2_instance_type_offering.available.instance_type + key_name = aws_key_pair.test.key_name + + tag_specifications { + resource_type = "instance" + + tags = { + Name = %[1]q + } + } +} + +resource "aws_spot_fleet_request" "test" { + iam_fleet_role = aws_iam_role.test.arn + spot_price = "0.05" + target_capacity = 2 + valid_until = %[2]q + terminate_instances_with_expiration = true + instance_interruption_behaviour = "stop" + wait_for_fulfillment = true + + launch_template_config { + launch_template_specification { + name = aws_launch_template.test.name + version = aws_launch_template.test.latest_version + } + + overrides { + availability_zone = data.aws_availability_zones.available.names[2] + + instance_requirements { + vcpu_count { + min = 1 + max = 8 + } + + memory_mib { + min = 500 + max = 50000 + } + + instance_generations = ["current"] + } + } + } + + depends_on = [aws_iam_policy_attachment.test] +} +`, rName, validUntil)) +} + func testAccSpotFleetRequestExcessCapacityTerminationConfig(rName, publicKey, validUntil string) string { return acctest.ConfigCompose(testAccSpotFleetRequestBaseConfig(rName, publicKey), fmt.Sprintf(` resource "aws_spot_fleet_request" "test" { From 3dba816998149cdb23344efeec8877fe51c124df Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Mon, 9 May 2022 16:21:57 -0400 Subject: [PATCH 9/9] Remove 'hashLaunchTemplateOverrides'. --- .../service/ec2/ec2_spot_fleet_request.go | 25 ------------------- 1 file changed, 25 deletions(-) diff --git a/internal/service/ec2/ec2_spot_fleet_request.go b/internal/service/ec2/ec2_spot_fleet_request.go index 75e73b06c90f..08fe624012a2 100644 --- a/internal/service/ec2/ec2_spot_fleet_request.go +++ b/internal/service/ec2/ec2_spot_fleet_request.go @@ -2088,31 +2088,6 @@ func hashLaunchSpecification(v interface{}) int { return create.StringHashcode(buf.String()) } -func hashLaunchTemplateOverrides(v interface{}) int { - var buf bytes.Buffer - m := v.(map[string]interface{}) - if m["availability_zone"] != nil { - buf.WriteString(fmt.Sprintf("%s-", m["availability_zone"].(string))) - } - if m["subnet_id"] != nil { - buf.WriteString(fmt.Sprintf("%s-", m["subnet_id"].(string))) - } - if m["spot_price"] != nil { - buf.WriteString(fmt.Sprintf("%s-", m["spot_price"].(string))) - } - if m["instance_type"] != nil { - buf.WriteString(fmt.Sprintf("%s-", m["instance_type"].(string))) - } - if m["weighted_capacity"] != nil { - buf.WriteString(fmt.Sprintf("%f-", m["weighted_capacity"].(float64))) - } - if m["priority"] != nil { - buf.WriteString(fmt.Sprintf("%f-", m["priority"].(float64))) - } - - return create.StringHashcode(buf.String()) -} - func hashEbsBlockDevice(v interface{}) int { var buf bytes.Buffer m := v.(map[string]interface{})