Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .changelog/47471.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:enhancement
resource/aws_ecs_capacity_provider: Add `capacity_reservations` configuration block and `RESERVED` value for `capacity_option_type` to support On-Demand Capacity Reservations (ODCR) for managed instances
```
121 changes: 119 additions & 2 deletions internal/service/ecs/capacity_provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,57 @@ func resourceCapacityProvider() *schema.Resource {
}
}

// Validate CapacityOptionType is not changed on update
if diff.Id() != "" {
oldVal, newVal := diff.GetChange("managed_instances_provider.0.instance_launch_template.0.capacity_option_type")
oldType, _ := oldVal.(string)
newType, _ := newVal.(string)

if oldType != "" && newType != "" && oldType != newType {
return errors.New("capacity_option_type cannot be changed after creation")
}
}

// Validate capacity reservation rules for managed instances providers
if len(managedProvider) > 0 {
capacityOptionType := diff.Get("managed_instances_provider.0.instance_launch_template.0.capacity_option_type").(string)
capacityReservations := diff.Get("managed_instances_provider.0.instance_launch_template.0.capacity_reservations").([]any)
instanceRequirements := diff.Get("managed_instances_provider.0.instance_launch_template.0.instance_requirements").([]any)

hasCapacityReservations := len(capacityReservations) > 0

// capacity_reservations requires capacity_option_type = RESERVED
if hasCapacityReservations && capacityOptionType != string(awstypes.CapacityOptionTypeReserved) {
return errors.New("capacity_reservations can only be set when capacity_option_type is RESERVED")
}

// capacity_option_type = RESERVED requires capacity_reservations
if capacityOptionType == string(awstypes.CapacityOptionTypeReserved) && !hasCapacityReservations {
return errors.New("capacity_reservations must be set when capacity_option_type is RESERVED")
}

// capacity_reservations not allowed with SPOT
if hasCapacityReservations && capacityOptionType == string(awstypes.CapacityOptionTypeSpot) {
return errors.New("capacity_reservations cannot be used with SPOT capacity option type")
}

if hasCapacityReservations {
reservationPreference := diff.Get("managed_instances_provider.0.instance_launch_template.0.capacity_reservations.0.reservation_preference").(string)
reservationGroupArn := diff.Get("managed_instances_provider.0.instance_launch_template.0.capacity_reservations.0.reservation_group_arn").(string)

// reservation_group_arn only allowed with RESERVATIONS_ONLY or unset preference
if reservationGroupArn != "" && reservationPreference != "" && reservationPreference != string(awstypes.CapacityReservationPreferenceReservationsOnly) {
return errors.New("reservation_group_arn can only be set when reservation_preference is RESERVATIONS_ONLY")
}

// instance_requirements required for RESERVATIONS_ONLY and RESERVATIONS_FIRST
if (reservationPreference == string(awstypes.CapacityReservationPreferenceReservationsOnly) ||
reservationPreference == string(awstypes.CapacityReservationPreferenceReservationsFirst)) && len(instanceRequirements) == 0 {
return errors.New("instance_requirements must be provided when reservation_preference is RESERVATIONS_ONLY or RESERVATIONS_FIRST")
}
}
}

return nil
},

Expand Down Expand Up @@ -184,10 +235,29 @@ func resourceCapacityProvider() *schema.Resource {
"capacity_option_type": {
Type: schema.TypeString,
Optional: true,
ForceNew: true,
Computed: true,
ValidateDiagFunc: enum.Validate[awstypes.CapacityOptionType](),
},
"capacity_reservations": {
Type: schema.TypeList,
MaxItems: 1,
Optional: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"reservation_group_arn": {
Type: schema.TypeString,
Optional: true,
ValidateFunc: verify.ValidARN,
},
"reservation_preference": {
Type: schema.TypeString,
Optional: true,
Computed: true,
ValidateDiagFunc: enum.Validate[awstypes.CapacityReservationPreference](),
},
},
},
},
"ec2_instance_profile_arn": {
Type: schema.TypeString,
Required: true,
Expand Down Expand Up @@ -1017,6 +1087,10 @@ func expandInstanceLaunchTemplateCreate(tfList []any) *awstypes.InstanceLaunchTe
apiObject.CapacityOptionType = awstypes.CapacityOptionType(v)
}

if v, ok := tfMap["capacity_reservations"].([]any); ok && len(v) > 0 {
apiObject.CapacityReservations = expandCapacityReservationRequest(v)
}

if v, ok := tfMap["ec2_instance_profile_arn"].(string); ok && v != "" {
apiObject.Ec2InstanceProfileArn = aws.String(v)
}
Expand Down Expand Up @@ -1048,6 +1122,10 @@ func expandInstanceLaunchTemplateUpdate(tfList []any) *awstypes.InstanceLaunchTe
tfMap := tfList[0].(map[string]any)
apiObject := &awstypes.InstanceLaunchTemplateUpdate{}

if v, ok := tfMap["capacity_reservations"].([]any); ok && len(v) > 0 {
apiObject.CapacityReservations = expandCapacityReservationRequest(v)
}

if v, ok := tfMap["ec2_instance_profile_arn"].(string); ok && v != "" {
apiObject.Ec2InstanceProfileArn = aws.String(v)
}
Expand Down Expand Up @@ -1105,6 +1183,25 @@ func expandManagedInstancesStorageConfiguration(tfList []any) *awstypes.ManagedI
return apiObject
}

func expandCapacityReservationRequest(tfList []any) *awstypes.CapacityReservationRequest {
if len(tfList) == 0 || tfList[0] == nil {
return nil
}

tfMap := tfList[0].(map[string]any)
apiObject := &awstypes.CapacityReservationRequest{}

if v, ok := tfMap["reservation_group_arn"].(string); ok && v != "" {
apiObject.ReservationGroupArn = aws.String(v)
}

if v, ok := tfMap["reservation_preference"].(string); ok && v != "" {
apiObject.ReservationPreference = awstypes.CapacityReservationPreference(v)
}

return apiObject
}

func expandInstanceRequirementsRequest(tfList []any) *awstypes.InstanceRequirementsRequest {
if len(tfList) == 0 || tfList[0] == nil {
return nil
Expand Down Expand Up @@ -1422,11 +1519,15 @@ func flattenInstanceLaunchTemplate(template *awstypes.InstanceLaunchTemplate) []
}

tfMap := map[string]any{
"capacity_option_type": template.CapacityOptionType,
"capacity_option_type": string(template.CapacityOptionType),
"ec2_instance_profile_arn": aws.ToString(template.Ec2InstanceProfileArn),
"monitoring": template.Monitoring,
}

if template.CapacityReservations != nil {
tfMap["capacity_reservations"] = flattenCapacityReservationRequest(template.CapacityReservations)
}

if template.InstanceRequirements != nil {
tfMap["instance_requirements"] = flattenInstanceRequirementsRequest(template.InstanceRequirements)
}
Expand All @@ -1450,6 +1551,22 @@ func flattenInstanceLaunchTemplate(template *awstypes.InstanceLaunchTemplate) []
return []map[string]any{tfMap}
}

func flattenCapacityReservationRequest(req *awstypes.CapacityReservationRequest) []map[string]any {
if req == nil {
return nil
}

tfMap := map[string]any{
"reservation_preference": string(req.ReservationPreference),
}

if req.ReservationGroupArn != nil {
tfMap["reservation_group_arn"] = aws.ToString(req.ReservationGroupArn)
}

return []map[string]any{tfMap}
}

func flattenInstanceRequirementsRequest(req *awstypes.InstanceRequirementsRequest) []map[string]any {
if req == nil {
return nil
Expand Down
Loading