diff --git a/aws/internal/service/ecs/waiter/status.go b/aws/internal/service/ecs/waiter/status.go new file mode 100644 index 00000000000..034af60b4e5 --- /dev/null +++ b/aws/internal/service/ecs/waiter/status.go @@ -0,0 +1,36 @@ +package waiter + +import ( + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/ecs" + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" +) + +const ( + // EventSubscription NotFound + CapacityProviderStatusNotFound = "NotFound" + + // EventSubscription Unknown + CapacityProviderStatusUnknown = "Unknown" +) + +// CapacityProviderStatus fetches the Capacity Provider and its Status +func CapacityProviderStatus(conn *ecs.ECS, capacityProvider string) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + input := &ecs.DescribeCapacityProvidersInput{ + CapacityProviders: aws.StringSlice([]string{capacityProvider}), + } + + output, err := conn.DescribeCapacityProviders(input) + + if err != nil { + return nil, CapacityProviderStatusUnknown, err + } + + if len(output.CapacityProviders) == 0 { + return nil, CapacityProviderStatusNotFound, nil + } + + return output.CapacityProviders[0], aws.StringValue(output.CapacityProviders[0].Status), nil + } +} diff --git a/aws/internal/service/ecs/waiter/waiter.go b/aws/internal/service/ecs/waiter/waiter.go new file mode 100644 index 00000000000..33b956fd1ef --- /dev/null +++ b/aws/internal/service/ecs/waiter/waiter.go @@ -0,0 +1,31 @@ +package waiter + +import ( + "time" + + "github.com/aws/aws-sdk-go/service/ecs" + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" +) + +const ( + // Maximum amount of time to wait for a Capacity Provider to return INACTIVE + CapacityProviderInactiveTimeout = 20 * time.Minute +) + +// CapacityProviderInactive waits for a Capacity Provider to return INACTIVE +func CapacityProviderInactive(conn *ecs.ECS, capacityProvider string) (*ecs.CapacityProvider, error) { + stateConf := &resource.StateChangeConf{ + Pending: []string{ecs.CapacityProviderStatusActive}, + Target: []string{ecs.CapacityProviderStatusInactive}, + Refresh: CapacityProviderStatus(conn, capacityProvider), + Timeout: CapacityProviderInactiveTimeout, + } + + outputRaw, err := stateConf.WaitForState() + + if v, ok := outputRaw.(*ecs.CapacityProvider); ok { + return v, err + } + + return nil, err +} diff --git a/aws/resource_aws_ecs_capacity_provider.go b/aws/resource_aws_ecs_capacity_provider.go index 8d806d293d1..cd3dc032443 100644 --- a/aws/resource_aws_ecs_capacity_provider.go +++ b/aws/resource_aws_ecs_capacity_provider.go @@ -10,6 +10,7 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/helper/validation" "github.com/terraform-providers/terraform-provider-aws/aws/internal/keyvaluetags" + "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/ecs/waiter" ) func resourceAwsEcsCapacityProvider() *schema.Resource { @@ -159,6 +160,12 @@ func resourceAwsEcsCapacityProviderRead(d *schema.ResourceData, meta interface{} return nil } + if aws.StringValue(provider.Status) == ecs.CapacityProviderStatusInactive { + log.Printf("[WARN] ECS Capacity Provider (%s) is INACTIVE, removing from state", d.Id()) + d.SetId("") + return nil + } + d.Set("arn", provider.CapacityProviderArn) d.Set("name", provider.Name) @@ -188,8 +195,22 @@ func resourceAwsEcsCapacityProviderUpdate(d *schema.ResourceData, meta interface } func resourceAwsEcsCapacityProviderDelete(d *schema.ResourceData, meta interface{}) error { - // Reference: https://github.com/aws/containers-roadmap/issues/632 - log.Printf("[WARN] delete is not yet implemented for ECS capacity providers") + conn := meta.(*AWSClient).ecsconn + + input := &ecs.DeleteCapacityProviderInput{ + CapacityProvider: aws.String(d.Id()), + } + + _, err := conn.DeleteCapacityProvider(input) + + if err != nil { + return fmt.Errorf("error deleting ECS Capacity Provider (%s): %w", d.Id(), err) + } + + if _, err := waiter.CapacityProviderInactive(conn, d.Id()); err != nil { + return fmt.Errorf("error waiting for ECS Capacity Provider (%s) to delete: %w", d.Id(), err) + } + return nil } diff --git a/aws/resource_aws_ecs_capacity_provider_test.go b/aws/resource_aws_ecs_capacity_provider_test.go index f31b9a331f3..7d692b53109 100644 --- a/aws/resource_aws_ecs_capacity_provider_test.go +++ b/aws/resource_aws_ecs_capacity_provider_test.go @@ -2,16 +2,100 @@ package aws import ( "fmt" + "log" "testing" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/ecs" + multierror "github.com/hashicorp/go-multierror" "github.com/hashicorp/terraform-plugin-sdk/helper/acctest" "github.com/hashicorp/terraform-plugin-sdk/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/terraform" + "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/ecs/waiter" ) -// TODO sweepers once Delete is implemented +func init() { + resource.AddTestSweepers("aws_ecs_capacity_provider", &resource.Sweeper{ + Name: "aws_ecs_capacity_provider", + F: testSweepEcsCapacityProviders, + Dependencies: []string{ + "aws_ecs_cluster", + "aws_ecs_service", + }, + }) +} + +func testSweepEcsCapacityProviders(region string) error { + client, err := sharedClientForRegion(region) + + if err != nil { + return fmt.Errorf("error getting client: %w", err) + } + + conn := client.(*AWSClient).ecsconn + input := &ecs.DescribeCapacityProvidersInput{} + var sweeperErrs *multierror.Error + + for { + output, err := conn.DescribeCapacityProviders(input) + + if testSweepSkipSweepError(err) { + log.Printf("[WARN] Skipping ECS Capacity Provider sweep for %s: %s", region, err) + return sweeperErrs.ErrorOrNil() + } + + if err != nil { + sweeperErrs = multierror.Append(sweeperErrs, fmt.Errorf("error retrieving ECS Capacity Provider: %w", err)) + return sweeperErrs + } + + for _, capacityProvider := range output.CapacityProviders { + if capacityProvider == nil { + continue + } + + arn := aws.StringValue(capacityProvider.CapacityProviderArn) + input := &ecs.DeleteCapacityProviderInput{ + CapacityProvider: capacityProvider.CapacityProviderArn, + } + + if aws.StringValue(capacityProvider.Name) == "FARGATE" || aws.StringValue(capacityProvider.Name) == "FARGATE_SPOT" { + log.Printf("[INFO] Skipping AWS managed ECS Capacity Provider: %s", arn) + continue + } + + if aws.StringValue(capacityProvider.Status) == ecs.CapacityProviderStatusInactive { + log.Printf("[INFO] Skipping ECS Capacity Provider with INACTIVE status: %s", arn) + continue + } + + log.Printf("[INFO] Deleting ECS Capacity Provider: %s", arn) + _, err := conn.DeleteCapacityProvider(input) + + if err != nil { + sweeperErr := fmt.Errorf("error deleting ECS Capacity Provider (%s): %w", arn, err) + log.Printf("[ERROR] %s", sweeperErr) + sweeperErrs = multierror.Append(sweeperErrs, sweeperErr) + continue + } + + if _, err := waiter.CapacityProviderInactive(conn, arn); err != nil { + sweeperErr := fmt.Errorf("error waiting for ECS Capacity Provider (%s) to delete: %w", arn, err) + log.Printf("[ERROR] %s", sweeperErr) + sweeperErrs = multierror.Append(sweeperErrs, sweeperErr) + continue + } + } + + if aws.StringValue(output.NextToken) == "" { + break + } + + input.NextToken = output.NextToken + } + + return sweeperErrs.ErrorOrNil() +} func TestAccAWSEcsCapacityProvider_basic(t *testing.T) { var provider ecs.CapacityProvider @@ -49,6 +133,28 @@ func TestAccAWSEcsCapacityProvider_basic(t *testing.T) { }) } +func TestAccAWSEcsCapacityProvider_disappears(t *testing.T) { + var provider ecs.CapacityProvider + rName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_ecs_capacity_provider.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSEcsCapacityProviderDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSEcsCapacityProviderConfig(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSEcsCapacityProviderExists(resourceName, &provider), + testAccCheckResourceDisappears(testAccProvider, resourceAwsEcsCapacityProvider(), resourceName), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + func TestAccAWSEcsCapacityProvider_ManagedScaling(t *testing.T) { var provider ecs.CapacityProvider rName := acctest.RandomWithPrefix("tf-acc-test") @@ -163,7 +269,30 @@ func TestAccAWSEcsCapacityProvider_Tags(t *testing.T) { // TODO add an update test config - Reference: https://github.com/aws/containers-roadmap/issues/633 func testAccCheckAWSEcsCapacityProviderDestroy(s *terraform.State) error { - // Reference: https://github.com/aws/containers-roadmap/issues/632 + conn := testAccProvider.Meta().(*AWSClient).ecsconn + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_ecs_capacity_provider" { + continue + } + + input := &ecs.DescribeCapacityProvidersInput{ + CapacityProviders: aws.StringSlice([]string{rs.Primary.ID}), + } + + output, err := conn.DescribeCapacityProviders(input) + + if err != nil { + return err + } + + for _, capacityProvider := range output.CapacityProviders { + if aws.StringValue(capacityProvider.CapacityProviderArn) == rs.Primary.ID && aws.StringValue(capacityProvider.Status) != ecs.CapacityProviderStatusInactive { + return fmt.Errorf("ECS Capacity Provider (%s) still exists", rs.Primary.ID) + } + } + } + return nil } diff --git a/website/docs/r/ecs_capacity_provider.html.markdown b/website/docs/r/ecs_capacity_provider.html.markdown index 127938f292e..9eedeb39802 100644 --- a/website/docs/r/ecs_capacity_provider.html.markdown +++ b/website/docs/r/ecs_capacity_provider.html.markdown @@ -10,8 +10,6 @@ description: |- Provides an ECS cluster capacity provider. More information can be found on the [ECS Developer Guide](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/cluster-capacity-providers.html). -~> **NOTE:** The AWS API does not currently support deleting ECS cluster capacity providers. Removing this Terraform resource will only remove the Terraform state for it. - ## Example Usage ```hcl