Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New Resource: aws_ecs_cluster_capacity_providers #22672

Merged
Merged
7 changes: 7 additions & 0 deletions .changelog/22672.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
```release-note:new-resource
aws_ecs_cluster_capacity_providers
```

```release-note:bug
resource/aws_ecs_cluster: Provide new resource `aws_ecs_cluster_capacity_providers` to avoid bugs using `capacity_providers` and `default_capacity_provider_strategy`, which arguments will be deprecated in a future version
```
15 changes: 8 additions & 7 deletions internal/provider/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -1188,13 +1188,14 @@ func Provider() *schema.Provider {
"aws_ecrpublic_repository": ecrpublic.ResourceRepository(),
"aws_ecrpublic_repository_policy": ecrpublic.ResourceRepositoryPolicy(),

"aws_ecs_account_setting_default": ecs.ResourceAccountSettingDefault(),
"aws_ecs_capacity_provider": ecs.ResourceCapacityProvider(),
"aws_ecs_cluster": ecs.ResourceCluster(),
"aws_ecs_service": ecs.ResourceService(),
"aws_ecs_tag": ecs.ResourceTag(),
"aws_ecs_task_definition": ecs.ResourceTaskDefinition(),
"aws_ecs_task_set": ecs.ResourceTaskSet(),
"aws_ecs_account_setting_default": ecs.ResourceAccountSettingDefault(),
"aws_ecs_capacity_provider": ecs.ResourceCapacityProvider(),
"aws_ecs_cluster": ecs.ResourceCluster(),
"aws_ecs_cluster_capacity_providers": ecs.ResourceClusterCapacityProviders(),
roberth-k marked this conversation as resolved.
Show resolved Hide resolved
"aws_ecs_service": ecs.ResourceService(),
"aws_ecs_tag": ecs.ResourceTag(),
"aws_ecs_task_definition": ecs.ResourceTaskDefinition(),
"aws_ecs_task_set": ecs.ResourceTaskSet(),

"aws_efs_access_point": efs.ResourceAccessPoint(),
"aws_efs_backup_policy": efs.ResourceBackupPolicy(),
Expand Down
65 changes: 17 additions & 48 deletions internal/service/ecs/cluster.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package ecs

import (
"context"
"fmt"
"log"
"time"
Expand Down Expand Up @@ -42,7 +43,7 @@ func ResourceCluster() *schema.Resource {
Type: schema.TypeString,
Required: true,
ForceNew: true,
ValidateFunc: validation.StringLenBetween(1, 255),
ValidateFunc: validateClusterName,
},
"arn": {
Type: schema.TypeString,
Expand All @@ -51,6 +52,7 @@ func ResourceCluster() *schema.Resource {
"capacity_providers": {
Type: schema.TypeSet,
Optional: true,
Computed: true,
Elem: &schema.Schema{
Type: schema.TypeString,
},
Expand Down Expand Up @@ -114,6 +116,7 @@ func ResourceCluster() *schema.Resource {
"default_capacity_provider_strategy": {
Type: schema.TypeSet,
Optional: true,
Computed: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"base": {
Expand Down Expand Up @@ -220,7 +223,7 @@ func resourceClusterCreate(d *schema.ResourceData, meta interface{}) error {

d.SetId(aws.StringValue(out.Cluster.ClusterArn))

if _, err := waitClusterAvailable(conn, d.Id()); err != nil {
if _, err := waitClusterAvailable(context.Background(), conn, d.Id()); err != nil {
return fmt.Errorf("error waiting for ECS Cluster (%s) to become Available: %w", d.Id(), err)
}

Expand All @@ -247,26 +250,23 @@ func resourceClusterRead(d *schema.ResourceData, meta interface{}) error {
defaultTagsConfig := meta.(*conns.AWSClient).DefaultTagsConfig
ignoreTagsConfig := meta.(*conns.AWSClient).IgnoreTagsConfig

var out *ecs.DescribeClustersOutput
err := resource.Retry(2*time.Minute, func() *resource.RetryError {
var cluster *ecs.Cluster
err := resource.Retry(clusterReadTimeout, func() *resource.RetryError {
var err error
out, err = FindClusterByNameOrARN(conn, d.Id())
cluster, err = FindClusterByNameOrARN(context.Background(), conn, d.Id())

if err != nil {
return resource.NonRetryableError(err)
if d.IsNewResource() && tfresource.NotFound(err) {
return resource.RetryableError(err)
}

if out == nil || len(out.Failures) > 0 {
if d.IsNewResource() {
return resource.RetryableError(&resource.NotFoundError{})
}
return resource.NonRetryableError(&resource.NotFoundError{})
if err != nil {
return resource.NonRetryableError(err)
}

return nil
})
if tfresource.TimedOut(err) {
out, err = FindClusterByNameOrARN(conn, d.Id())
cluster, err = FindClusterByNameOrARN(context.Background(), conn, d.Id())
}

if tfresource.NotFound(err) {
Expand All @@ -279,20 +279,6 @@ func resourceClusterRead(d *schema.ResourceData, meta interface{}) error {
return fmt.Errorf("error reading ECS Cluster (%s): %s", d.Id(), err)
}

var cluster *ecs.Cluster
for _, c := range out.Clusters {
if aws.StringValue(c.ClusterArn) == d.Id() {
cluster = c
break
}
}

if cluster == nil {
log.Printf("[WARN] ECS Cluster (%s) not found, removing from state", d.Id())
d.SetId("")
return nil
}

// Status==INACTIVE means deleted cluster
if aws.StringValue(cluster.Status) == "INACTIVE" {
log.Printf("[WARN] ECS Cluster (%s) deleted, removing from state", d.Id())
Expand Down Expand Up @@ -361,7 +347,7 @@ func resourceClusterUpdate(d *schema.ResourceData, meta interface{}) error {
return fmt.Errorf("error changing ECS cluster (%s): %w", d.Id(), err)
}

if _, err := waitClusterAvailable(conn, d.Id()); err != nil {
if _, err := waitClusterAvailable(context.Background(), conn, d.Id()); err != nil {
return fmt.Errorf("error waiting for ECS Cluster (%s) to become Available: %w", d.Id(), err)
}
}
Expand All @@ -373,30 +359,13 @@ func resourceClusterUpdate(d *schema.ResourceData, meta interface{}) error {
DefaultCapacityProviderStrategy: expandCapacityProviderStrategy(d.Get("default_capacity_provider_strategy").(*schema.Set)),
}

err := resource.Retry(ecsClusterTimeoutUpdate, func() *resource.RetryError {
_, err := conn.PutClusterCapacityProviders(&input)
if err != nil {
if tfawserr.ErrMessageContains(err, ecs.ErrCodeClientException, "Cluster was not ACTIVE") {
return resource.RetryableError(err)
}
if tfawserr.ErrMessageContains(err, ecs.ErrCodeResourceInUseException, "") {
return resource.RetryableError(err)
}
if tfawserr.ErrMessageContains(err, ecs.ErrCodeUpdateInProgressException, "") {
return resource.RetryableError(err)
}
return resource.NonRetryableError(err)
}
return nil
})
if tfresource.TimedOut(err) {
_, err = conn.PutClusterCapacityProviders(&input)
}
err := retryClusterCapacityProvidersPut(context.Background(), conn, &input)

if err != nil {
return fmt.Errorf("error changing ECS cluster capacity provider settings (%s): %w", d.Id(), err)
}

if _, err := waitClusterAvailable(conn, d.Id()); err != nil {
if _, err := waitClusterAvailable(context.Background(), conn, d.Id()); err != nil {
return fmt.Errorf("error waiting for ECS Cluster (%s) to become Available: %w", d.Id(), err)
}
}
Expand Down
189 changes: 189 additions & 0 deletions internal/service/ecs/cluster_capacity_providers.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
package ecs

import (
"context"
"log"

"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/ecs"
"github.com/hashicorp/aws-sdk-go-base/tfawserr"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
"github.com/hashicorp/terraform-provider-aws/internal/conns"
"github.com/hashicorp/terraform-provider-aws/internal/flex"
"github.com/hashicorp/terraform-provider-aws/internal/tfresource"
)

func ResourceClusterCapacityProviders() *schema.Resource {
return &schema.Resource{
CreateContext: resourceClusterCapacityProvidersPut,
ReadContext: resourceClusterCapacityProvidersRead,
UpdateContext: resourceClusterCapacityProvidersPut,
DeleteContext: resourceClusterCapacityProvidersDelete,

Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},

Schema: map[string]*schema.Schema{
"capacity_providers": {
Type: schema.TypeSet,
Optional: true,
Elem: &schema.Schema{
Type: schema.TypeString,
},
},
"cluster_name": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
// The API accepts both an ARN and a name in a generic "cluster"
// parameter, but allowing that would force the resource to guess
// which one to return on read.
ValidateFunc: validateClusterName,
},
"default_capacity_provider_strategy": {
Type: schema.TypeSet,
Optional: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"base": {
Type: schema.TypeInt,
Default: 0,
Optional: true,
ValidateFunc: validation.IntBetween(0, 100000),
},
"capacity_provider": {
Type: schema.TypeString,
Required: true,
},
"weight": {
Type: schema.TypeInt,
Default: 0,
Optional: true,
ValidateFunc: validation.IntBetween(0, 1000),
},
},
},
},
},
}
}

func resourceClusterCapacityProvidersPut(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
conn := meta.(*conns.AWSClient).ECSConn

clusterName := d.Get("cluster_name").(string)

input := &ecs.PutClusterCapacityProvidersInput{
Cluster: aws.String(clusterName),
CapacityProviders: flex.ExpandStringSet(d.Get("capacity_providers").(*schema.Set)),
DefaultCapacityProviderStrategy: expandCapacityProviderStrategy(d.Get("default_capacity_provider_strategy").(*schema.Set)),
}

log.Printf("[DEBUG] Updating ECS cluster capacity providers: %s", input)

err := retryClusterCapacityProvidersPut(ctx, conn, input)

if err != nil {
return diag.Errorf("error updating ECS Cluster (%s) capacity providers: %s", clusterName, err)
}

if _, err := waitClusterAvailable(ctx, conn, clusterName); err != nil {
return diag.Errorf("error waiting for ECS Cluster (%s) to become available: %s", clusterName, err)
}

d.SetId(clusterName)
roberth-k marked this conversation as resolved.
Show resolved Hide resolved

return resourceClusterCapacityProvidersRead(ctx, d, meta)
}

func resourceClusterCapacityProvidersRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
conn := meta.(*conns.AWSClient).ECSConn

cluster, err := FindClusterByNameOrARN(ctx, conn, d.Id())

if tfresource.NotFound(err) {
diag.Errorf("[WARN] ECS Cluster (%s) not found, removing from state", d.Id())
d.SetId("")
return nil
}

if err != nil {
return diag.Errorf("error reading ECS Cluster (%s): %s", d.Id(), err)
}

// Status==INACTIVE means deleted cluster
if aws.StringValue(cluster.Status) == "INACTIVE" {
diag.Errorf("[WARN] ECS Cluster (%s) deleted, removing from state", d.Id())
d.SetId("")
return nil
}

if err := d.Set("capacity_providers", aws.StringValueSlice(cluster.CapacityProviders)); err != nil {
return diag.Errorf("error setting capacity_providers: %s", err)
}

d.Set("cluster_name", cluster.ClusterName)

if err := d.Set("default_capacity_provider_strategy", flattenCapacityProviderStrategy(cluster.DefaultCapacityProviderStrategy)); err != nil {
return diag.Errorf("error setting default_capacity_provider_strategy: %s", err)
}

return nil
}

func resourceClusterCapacityProvidersDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
conn := meta.(*conns.AWSClient).ECSConn

input := &ecs.PutClusterCapacityProvidersInput{
Cluster: aws.String(d.Id()),
CapacityProviders: []*string{},
DefaultCapacityProviderStrategy: []*ecs.CapacityProviderStrategyItem{},
}

log.Printf("[DEBUG] Removing ECS cluster (%s) capacity providers", d.Id())

err := retryClusterCapacityProvidersPut(ctx, conn, input)

if tfawserr.ErrCodeEquals(err, ecs.ErrCodeClusterNotFoundException) {
return nil
}

if err != nil {
return diag.Errorf("error deleting ECS Cluster (%s) capacity providers: %s", d.Id(), err)
}

if _, err := waitClusterAvailable(ctx, conn, d.Id()); err != nil {
return diag.Errorf("error waiting for ECS Cluster (%s) to become available: %s", d.Id(), err)
}

return nil
}

func retryClusterCapacityProvidersPut(ctx context.Context, conn *ecs.ECS, input *ecs.PutClusterCapacityProvidersInput) error {
roberth-k marked this conversation as resolved.
Show resolved Hide resolved
err := resource.RetryContext(ctx, ecsClusterTimeoutUpdate, func() *resource.RetryError {
_, err := conn.PutClusterCapacityProvidersWithContext(ctx, input)
if err != nil {
if tfawserr.ErrMessageContains(err, ecs.ErrCodeClientException, "Cluster was not ACTIVE") {
return resource.RetryableError(err)
}
if tfawserr.ErrMessageContains(err, ecs.ErrCodeResourceInUseException, "") {
return resource.RetryableError(err)
}
if tfawserr.ErrMessageContains(err, ecs.ErrCodeUpdateInProgressException, "") {
return resource.RetryableError(err)
}
return resource.NonRetryableError(err)
}
return nil
})

if tfresource.TimedOut(err) {
_, err = conn.PutClusterCapacityProvidersWithContext(ctx, input)
}

return err
}
Loading