diff --git a/aws/internal/service/servicediscovery/waiter/status.go b/aws/internal/service/servicediscovery/waiter/status.go new file mode 100644 index 000000000000..be6d4cfcad65 --- /dev/null +++ b/aws/internal/service/servicediscovery/waiter/status.go @@ -0,0 +1,35 @@ +package waiter + +import ( + "fmt" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/servicediscovery" + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" +) + +// OperationStatus fetches the Operation and its Status +func OperationStatus(conn *servicediscovery.ServiceDiscovery, operationID string) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + input := &servicediscovery.GetOperationInput{ + OperationId: aws.String(operationID), + } + + output, err := conn.GetOperation(input) + + if err != nil { + return nil, servicediscovery.OperationStatusFail, err + } + + // Error messages can also be contained in the response with FAIL status + // "ErrorCode":"CANNOT_CREATE_HOSTED_ZONE", + // "ErrorMessage":"The VPC that you chose, vpc-xxx in region xxx, is already associated with another private hosted zone that has an overlapping name space, xxx.. (Service: AmazonRoute53; Status Code: 400; Error Code: ConflictingDomainExists; Request ID: xxx)" + // "Status":"FAIL", + + if aws.StringValue(output.Operation.Status) == servicediscovery.OperationStatusFail { + return output, servicediscovery.OperationStatusFail, fmt.Errorf("%s: %s", aws.StringValue(output.Operation.ErrorCode), aws.StringValue(output.Operation.ErrorMessage)) + } + + return output, aws.StringValue(output.Operation.Status), nil + } +} diff --git a/aws/internal/service/servicediscovery/waiter/waiter.go b/aws/internal/service/servicediscovery/waiter/waiter.go new file mode 100644 index 000000000000..ccba30a9bdff --- /dev/null +++ b/aws/internal/service/servicediscovery/waiter/waiter.go @@ -0,0 +1,31 @@ +package waiter + +import ( + "time" + + "github.com/aws/aws-sdk-go/service/servicediscovery" + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" +) + +const ( + // Maximum amount of time to wait for an Operation to return Success + OperationSuccessTimeout = 5 * time.Minute +) + +// OperationSuccess waits for an Operation to return Success +func OperationSuccess(conn *servicediscovery.ServiceDiscovery, operationID string) (*servicediscovery.GetOperationOutput, error) { + stateConf := &resource.StateChangeConf{ + Pending: []string{servicediscovery.OperationStatusSubmitted, servicediscovery.OperationStatusPending}, + Target: []string{servicediscovery.OperationStatusSuccess}, + Refresh: OperationStatus(conn, operationID), + Timeout: OperationSuccessTimeout, + } + + outputRaw, err := stateConf.WaitForState() + + if output, ok := outputRaw.(*servicediscovery.GetOperationOutput); ok { + return output, err + } + + return nil, err +} diff --git a/aws/resource_aws_service_discovery_http_namespace.go b/aws/resource_aws_service_discovery_http_namespace.go index c83dcdb03987..29b0e6f4bead 100644 --- a/aws/resource_aws_service_discovery_http_namespace.go +++ b/aws/resource_aws_service_discovery_http_namespace.go @@ -2,12 +2,12 @@ package aws import ( "fmt" - "time" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/servicediscovery" "github.com/hashicorp/terraform-plugin-sdk/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/helper/schema" + "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/servicediscovery/waiter" ) func resourceAwsServiceDiscoveryHttpNamespace() *schema.Resource { @@ -54,24 +54,34 @@ func resourceAwsServiceDiscoveryHttpNamespaceCreate(d *schema.ResourceData, meta input.Description = aws.String(v.(string)) } - resp, err := conn.CreateHttpNamespace(input) + output, err := conn.CreateHttpNamespace(input) + if err != nil { - return fmt.Errorf("error creating Service Discovery HTTP Namespace (%s): %s", name, err) + return fmt.Errorf("error creating Service Discovery HTTP Namespace (%s): %w", name, err) } - stateConf := &resource.StateChangeConf{ - Pending: []string{servicediscovery.OperationStatusSubmitted, servicediscovery.OperationStatusPending}, - Target: []string{servicediscovery.OperationStatusSuccess}, - Refresh: servicediscoveryOperationRefreshStatusFunc(conn, *resp.OperationId), - Timeout: 5 * time.Minute, + if output == nil || output.OperationId == nil { + return fmt.Errorf("error creating Service Discovery HTTP Namespace (%s): creation response missing Operation ID", name) } - opresp, err := stateConf.WaitForState() + operationOutput, err := waiter.OperationSuccess(conn, aws.StringValue(output.OperationId)) + if err != nil { - return fmt.Errorf("error waiting for Service Discovery HTTP Namespace (%s) creation: %s", name, err) + return fmt.Errorf("error waiting for Service Discovery HTTP Namespace (%s) creation: %w", name, err) } - d.SetId(*opresp.(*servicediscovery.GetOperationOutput).Operation.Targets["NAMESPACE"]) + if operationOutput == nil || operationOutput.Operation == nil { + return fmt.Errorf("error creating Service Discovery HTTP Namespace (%s): operation response missing Operation information", name) + } + + namespaceID, ok := operationOutput.Operation.Targets[servicediscovery.OperationTargetTypeNamespace] + + if !ok { + return fmt.Errorf("error creating Service Discovery HTTP Namespace (%s): operation response missing Namespace ID", name) + } + + d.SetId(aws.StringValue(namespaceID)) + return resourceAwsServiceDiscoveryHttpNamespaceRead(d, meta) } @@ -105,25 +115,20 @@ func resourceAwsServiceDiscoveryHttpNamespaceDelete(d *schema.ResourceData, meta Id: aws.String(d.Id()), } - resp, err := conn.DeleteNamespace(input) - if err != nil { - if isAWSErr(err, servicediscovery.ErrCodeNamespaceNotFound, "") { - d.SetId("") - return nil - } - return fmt.Errorf("error deleting Service Discovery HTTP Namespace (%s): %s", d.Id(), err) - } + output, err := conn.DeleteNamespace(input) - stateConf := &resource.StateChangeConf{ - Pending: []string{servicediscovery.OperationStatusSubmitted, servicediscovery.OperationStatusPending}, - Target: []string{servicediscovery.OperationStatusSuccess}, - Refresh: servicediscoveryOperationRefreshStatusFunc(conn, *resp.OperationId), - Timeout: 5 * time.Minute, + if isAWSErr(err, servicediscovery.ErrCodeNamespaceNotFound, "") { + return nil } - _, err = stateConf.WaitForState() if err != nil { - return fmt.Errorf("error waiting for Service Discovery HTTP Namespace (%s) deletion: %s", d.Id(), err) + return fmt.Errorf("error deleting Service Discovery HTTP Namespace (%s): %w", d.Id(), err) + } + + if output != nil && output.OperationId != nil { + if _, err := waiter.OperationSuccess(conn, aws.StringValue(output.OperationId)); err != nil { + return fmt.Errorf("error waiting for Service Discovery HTTP Namespace (%s) deletion: %w", d.Id(), err) + } } return nil diff --git a/aws/resource_aws_service_discovery_http_namespace_test.go b/aws/resource_aws_service_discovery_http_namespace_test.go index 242a89151d4d..771471f522fc 100644 --- a/aws/resource_aws_service_discovery_http_namespace_test.go +++ b/aws/resource_aws_service_discovery_http_namespace_test.go @@ -2,15 +2,96 @@ package aws import ( "fmt" + "log" "testing" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/servicediscovery" + "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/servicediscovery/waiter" ) +func init() { + resource.AddTestSweepers("aws_service_discovery_http_namespace", &resource.Sweeper{ + Name: "aws_service_discovery_http_namespace", + F: testSweepServiceDiscoveryHttpNamespaces, + Dependencies: []string{ + "aws_service_discovery_service", + }, + }) +} + +func testSweepServiceDiscoveryHttpNamespaces(region string) error { + client, err := sharedClientForRegion(region) + if err != nil { + return fmt.Errorf("error getting client: %s", err) + } + conn := client.(*AWSClient).sdconn + var sweeperErrs *multierror.Error + + input := &servicediscovery.ListNamespacesInput{ + Filters: []*servicediscovery.NamespaceFilter{ + { + Condition: aws.String(servicediscovery.FilterConditionEq), + Name: aws.String(servicediscovery.NamespaceFilterNameType), + Values: aws.StringSlice([]string{servicediscovery.NamespaceTypeHttp}), + }, + }, + } + + err = conn.ListNamespacesPages(input, func(page *servicediscovery.ListNamespacesOutput, isLast bool) bool { + if page == nil { + return !isLast + } + + for _, namespace := range page.Namespaces { + if namespace == nil { + continue + } + + id := aws.StringValue(namespace.Id) + input := &servicediscovery.DeleteNamespaceInput{ + Id: namespace.Id, + } + + log.Printf("[INFO] Deleting Service Discovery HTTP Namespace: %s", id) + output, err := conn.DeleteNamespace(input) + + if err != nil { + sweeperErr := fmt.Errorf("error deleting Service Discovery HTTP Namespace (%s): %w", id, err) + log.Printf("[ERROR] %s", sweeperErr) + sweeperErrs = multierror.Append(sweeperErrs, sweeperErr) + continue + } + + if output != nil && output.OperationId != nil { + if _, err := waiter.OperationSuccess(conn, aws.StringValue(output.OperationId)); err != nil { + sweeperErr := fmt.Errorf("error waiting for Service Discovery HTTP Namespace (%s) deletion: %w", id, err) + log.Printf("[ERROR] %s", sweeperErr) + sweeperErrs = multierror.Append(sweeperErrs, sweeperErr) + continue + } + } + } + + return !isLast + }) + + if testSweepSkipSweepError(err) { + log.Printf("[WARN] Skipping Service Discovery HTTP Namespaces sweep for %s: %s", region, err) + return sweeperErrs.ErrorOrNil() // In case we have completed some pages, but had errors + } + + if err != nil { + sweeperErrs = multierror.Append(sweeperErrs, fmt.Errorf("error retrieving Service Discovery HTTP Namespaces: %w", err)) + } + + return sweeperErrs.ErrorOrNil() +} + func TestAccAWSServiceDiscoveryHttpNamespace_basic(t *testing.T) { resourceName := "aws_service_discovery_http_namespace.test" rName := fmt.Sprintf("tf-acc-test-%s", acctest.RandStringFromCharSet(8, acctest.CharSetAlpha)) diff --git a/aws/resource_aws_service_discovery_private_dns_namespace.go b/aws/resource_aws_service_discovery_private_dns_namespace.go index 52da5e4907bd..2c9339cd4204 100644 --- a/aws/resource_aws_service_discovery_private_dns_namespace.go +++ b/aws/resource_aws_service_discovery_private_dns_namespace.go @@ -1,12 +1,13 @@ package aws import ( - "time" + "fmt" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/servicediscovery" "github.com/hashicorp/terraform-plugin-sdk/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/helper/schema" + "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/servicediscovery/waiter" ) func resourceAwsServiceDiscoveryPrivateDnsNamespace() *schema.Resource { @@ -65,24 +66,34 @@ func resourceAwsServiceDiscoveryPrivateDnsNamespaceCreate(d *schema.ResourceData input.Description = aws.String(v.(string)) } - resp, err := conn.CreatePrivateDnsNamespace(input) + output, err := conn.CreatePrivateDnsNamespace(input) + if err != nil { - return err + return fmt.Errorf("error creating Service Discovery Private DNS Namespace (%s): %w", name, err) } - stateConf := &resource.StateChangeConf{ - Pending: []string{servicediscovery.OperationStatusSubmitted, servicediscovery.OperationStatusPending}, - Target: []string{servicediscovery.OperationStatusSuccess}, - Refresh: servicediscoveryOperationRefreshStatusFunc(conn, *resp.OperationId), - Timeout: 5 * time.Minute, + if output == nil || output.OperationId == nil { + return fmt.Errorf("error creating Service Discovery Private DNS Namespace (%s): creation response missing Operation ID", name) } - opresp, err := stateConf.WaitForState() + operationOutput, err := waiter.OperationSuccess(conn, aws.StringValue(output.OperationId)) + if err != nil { - return err + return fmt.Errorf("error waiting for Service Discovery Private DNS Namespace (%s) creation: %w", name, err) + } + + if operationOutput == nil || operationOutput.Operation == nil { + return fmt.Errorf("error creating Service Discovery Private DNS Namespace (%s): operation response missing Operation information", name) + } + + namespaceID, ok := operationOutput.Operation.Targets[servicediscovery.OperationTargetTypeNamespace] + + if !ok { + return fmt.Errorf("error creating Service Discovery Private DNS Namespace (%s): operation response missing Namespace ID", name) } - d.SetId(*opresp.(*servicediscovery.GetOperationOutput).Operation.Targets["NAMESPACE"]) + d.SetId(aws.StringValue(namespaceID)) + return resourceAwsServiceDiscoveryPrivateDnsNamespaceRead(d, meta) } @@ -117,18 +128,17 @@ func resourceAwsServiceDiscoveryPrivateDnsNamespaceDelete(d *schema.ResourceData Id: aws.String(d.Id()), } - resp, err := conn.DeleteNamespace(input) + output, err := conn.DeleteNamespace(input) + if err != nil { - return err + return fmt.Errorf("error deleting Service Discovery Private DNS Namespace (%s): %w", d.Id(), err) } - stateConf := &resource.StateChangeConf{ - Pending: []string{servicediscovery.OperationStatusSubmitted, servicediscovery.OperationStatusPending}, - Target: []string{servicediscovery.OperationStatusSuccess}, - Refresh: servicediscoveryOperationRefreshStatusFunc(conn, *resp.OperationId), - Timeout: 5 * time.Minute, + if output != nil && output.OperationId != nil { + if _, err := waiter.OperationSuccess(conn, aws.StringValue(output.OperationId)); err != nil { + return fmt.Errorf("error waiting for Service Discovery Private DNS Namespace (%s) deletion: %w", d.Id(), err) + } } - _, err = stateConf.WaitForState() - return err + return nil } diff --git a/aws/resource_aws_service_discovery_private_dns_namespace_test.go b/aws/resource_aws_service_discovery_private_dns_namespace_test.go index 18364ebca65d..5980249682c1 100644 --- a/aws/resource_aws_service_discovery_private_dns_namespace_test.go +++ b/aws/resource_aws_service_discovery_private_dns_namespace_test.go @@ -2,16 +2,97 @@ package aws import ( "fmt" + "log" "regexp" "testing" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/servicediscovery" + "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/servicediscovery/waiter" ) +func init() { + resource.AddTestSweepers("aws_service_discovery_private_dns_namespace", &resource.Sweeper{ + Name: "aws_service_discovery_private_dns_namespace", + F: testSweepServiceDiscoveryPrivateDnsNamespaces, + Dependencies: []string{ + "aws_service_discovery_service", + }, + }) +} + +func testSweepServiceDiscoveryPrivateDnsNamespaces(region string) error { + client, err := sharedClientForRegion(region) + if err != nil { + return fmt.Errorf("error getting client: %s", err) + } + conn := client.(*AWSClient).sdconn + var sweeperErrs *multierror.Error + + input := &servicediscovery.ListNamespacesInput{ + Filters: []*servicediscovery.NamespaceFilter{ + { + Condition: aws.String(servicediscovery.FilterConditionEq), + Name: aws.String(servicediscovery.NamespaceFilterNameType), + Values: aws.StringSlice([]string{servicediscovery.NamespaceTypeDnsPrivate}), + }, + }, + } + + err = conn.ListNamespacesPages(input, func(page *servicediscovery.ListNamespacesOutput, isLast bool) bool { + if page == nil { + return !isLast + } + + for _, namespace := range page.Namespaces { + if namespace == nil { + continue + } + + id := aws.StringValue(namespace.Id) + input := &servicediscovery.DeleteNamespaceInput{ + Id: namespace.Id, + } + + log.Printf("[INFO] Deleting Service Discovery Private DNS Namespace: %s", id) + output, err := conn.DeleteNamespace(input) + + if err != nil { + sweeperErr := fmt.Errorf("error deleting Service Discovery Private DNS Namespace (%s): %w", id, err) + log.Printf("[ERROR] %s", sweeperErr) + sweeperErrs = multierror.Append(sweeperErrs, sweeperErr) + continue + } + + if output != nil && output.OperationId != nil { + if _, err := waiter.OperationSuccess(conn, aws.StringValue(output.OperationId)); err != nil { + sweeperErr := fmt.Errorf("error waiting for Service Discovery Private DNS Namespace (%s) deletion: %w", id, err) + log.Printf("[ERROR] %s", sweeperErr) + sweeperErrs = multierror.Append(sweeperErrs, sweeperErr) + continue + } + } + } + + return !isLast + }) + + if testSweepSkipSweepError(err) { + log.Printf("[WARN] Skipping Service Discovery Private DNS Namespaces sweep for %s: %s", region, err) + return sweeperErrs.ErrorOrNil() // In case we have completed some pages, but had errors + } + + if err != nil { + sweeperErrs = multierror.Append(sweeperErrs, fmt.Errorf("error retrieving Service Discovery Private DNS Namespaces: %w", err)) + } + + return sweeperErrs.ErrorOrNil() +} + func TestAccAWSServiceDiscoveryPrivateDnsNamespace_basic(t *testing.T) { rName := acctest.RandString(5) + ".example.com" @@ -65,7 +146,7 @@ func TestAccAWSServiceDiscoveryPrivateDnsNamespace_error_Overlap(t *testing.T) { Steps: []resource.TestStep{ { Config: testAccServiceDiscoveryPrivateDnsNamespaceConfigOverlapping(rName), - ExpectError: regexp.MustCompile(`overlapping name space`), + ExpectError: regexp.MustCompile(`ConflictingDomainExists`), }, }, }) diff --git a/aws/resource_aws_service_discovery_public_dns_namespace.go b/aws/resource_aws_service_discovery_public_dns_namespace.go index 7aa649ed0363..7536c3b73b1d 100644 --- a/aws/resource_aws_service_discovery_public_dns_namespace.go +++ b/aws/resource_aws_service_discovery_public_dns_namespace.go @@ -2,12 +2,12 @@ package aws import ( "fmt" - "time" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/servicediscovery" "github.com/hashicorp/terraform-plugin-sdk/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/helper/schema" + "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/servicediscovery/waiter" ) func resourceAwsServiceDiscoveryPublicDnsNamespace() *schema.Resource { @@ -64,24 +64,34 @@ func resourceAwsServiceDiscoveryPublicDnsNamespaceCreate(d *schema.ResourceData, input.Description = aws.String(v.(string)) } - resp, err := conn.CreatePublicDnsNamespace(input) + output, err := conn.CreatePublicDnsNamespace(input) + if err != nil { - return err + return fmt.Errorf("error creating Service Discovery Public DNS Namespace (%s): %w", name, err) } - stateConf := &resource.StateChangeConf{ - Pending: []string{servicediscovery.OperationStatusSubmitted, servicediscovery.OperationStatusPending}, - Target: []string{servicediscovery.OperationStatusSuccess}, - Refresh: servicediscoveryOperationRefreshStatusFunc(conn, *resp.OperationId), - Timeout: 5 * time.Minute, + if output == nil || output.OperationId == nil { + return fmt.Errorf("error creating Service Discovery Public DNS Namespace (%s): creation response missing Operation ID", name) } - opresp, err := stateConf.WaitForState() + operationOutput, err := waiter.OperationSuccess(conn, aws.StringValue(output.OperationId)) + if err != nil { - return err + return fmt.Errorf("error waiting for Service Discovery Public DNS Namespace (%s) creation: %w", name, err) + } + + if operationOutput == nil || operationOutput.Operation == nil { + return fmt.Errorf("error creating Service Discovery Public DNS Namespace (%s): operation response missing Operation information", name) } - d.SetId(*opresp.(*servicediscovery.GetOperationOutput).Operation.Targets["NAMESPACE"]) + namespaceID, ok := operationOutput.Operation.Targets[servicediscovery.OperationTargetTypeNamespace] + + if !ok { + return fmt.Errorf("error creating Service Discovery Public DNS Namespace (%s): operation response missing Namespace ID", name) + } + + d.SetId(aws.StringValue(namespaceID)) + return resourceAwsServiceDiscoveryPublicDnsNamespaceRead(d, meta) } @@ -117,42 +127,17 @@ func resourceAwsServiceDiscoveryPublicDnsNamespaceDelete(d *schema.ResourceData, Id: aws.String(d.Id()), } - resp, err := conn.DeleteNamespace(input) - if err != nil { - return err - } + output, err := conn.DeleteNamespace(input) - stateConf := &resource.StateChangeConf{ - Pending: []string{servicediscovery.OperationStatusSubmitted, servicediscovery.OperationStatusPending}, - Target: []string{servicediscovery.OperationStatusSuccess}, - Refresh: servicediscoveryOperationRefreshStatusFunc(conn, *resp.OperationId), - Timeout: 5 * time.Minute, + if err != nil { + return fmt.Errorf("error deleting Service Discovery Public DNS Namespace (%s): %w", d.Id(), err) } - _, err = stateConf.WaitForState() - return err -} - -func servicediscoveryOperationRefreshStatusFunc(conn *servicediscovery.ServiceDiscovery, oid string) resource.StateRefreshFunc { - return func() (interface{}, string, error) { - input := &servicediscovery.GetOperationInput{ - OperationId: aws.String(oid), - } - resp, err := conn.GetOperation(input) - - if err != nil { - return nil, servicediscovery.OperationStatusFail, err + if output != nil && output.OperationId != nil { + if _, err := waiter.OperationSuccess(conn, aws.StringValue(output.OperationId)); err != nil { + return fmt.Errorf("error waiting for Service Discovery Public DNS Namespace (%s) deletion: %w", d.Id(), err) } - - // Error messages can also be contained in the response with FAIL status - // "ErrorCode":"CANNOT_CREATE_HOSTED_ZONE", - // "ErrorMessage":"The VPC that you chose, vpc-xxx in region xxx, is already associated with another private hosted zone that has an overlapping name space, xxx.. (Service: AmazonRoute53; Status Code: 400; Error Code: ConflictingDomainExists; Request ID: xxx)" - // "Status":"FAIL", - - if aws.StringValue(resp.Operation.Status) == servicediscovery.OperationStatusFail { - return resp, servicediscovery.OperationStatusFail, fmt.Errorf("%s: %s", aws.StringValue(resp.Operation.ErrorCode), aws.StringValue(resp.Operation.ErrorMessage)) - } - - return resp, aws.StringValue(resp.Operation.Status), nil } + + return nil } diff --git a/aws/resource_aws_service_discovery_public_dns_namespace_test.go b/aws/resource_aws_service_discovery_public_dns_namespace_test.go index dfbfae7ee0a0..424aef4a63c6 100644 --- a/aws/resource_aws_service_discovery_public_dns_namespace_test.go +++ b/aws/resource_aws_service_discovery_public_dns_namespace_test.go @@ -2,15 +2,96 @@ package aws import ( "fmt" + "log" "testing" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/servicediscovery" + "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/servicediscovery/waiter" ) +func init() { + resource.AddTestSweepers("aws_service_discovery_public_dns_namespace", &resource.Sweeper{ + Name: "aws_service_discovery_public_dns_namespace", + F: testSweepServiceDiscoveryPublicDnsNamespaces, + Dependencies: []string{ + "aws_service_discovery_service", + }, + }) +} + +func testSweepServiceDiscoveryPublicDnsNamespaces(region string) error { + client, err := sharedClientForRegion(region) + if err != nil { + return fmt.Errorf("error getting client: %s", err) + } + conn := client.(*AWSClient).sdconn + var sweeperErrs *multierror.Error + + input := &servicediscovery.ListNamespacesInput{ + Filters: []*servicediscovery.NamespaceFilter{ + { + Condition: aws.String(servicediscovery.FilterConditionEq), + Name: aws.String(servicediscovery.NamespaceFilterNameType), + Values: aws.StringSlice([]string{servicediscovery.NamespaceTypeDnsPublic}), + }, + }, + } + + err = conn.ListNamespacesPages(input, func(page *servicediscovery.ListNamespacesOutput, isLast bool) bool { + if page == nil { + return !isLast + } + + for _, namespace := range page.Namespaces { + if namespace == nil { + continue + } + + id := aws.StringValue(namespace.Id) + input := &servicediscovery.DeleteNamespaceInput{ + Id: namespace.Id, + } + + log.Printf("[INFO] Deleting Service Discovery Public DNS Namespace: %s", id) + output, err := conn.DeleteNamespace(input) + + if err != nil { + sweeperErr := fmt.Errorf("error deleting Service Discovery Public DNS Namespace (%s): %w", id, err) + log.Printf("[ERROR] %s", sweeperErr) + sweeperErrs = multierror.Append(sweeperErrs, sweeperErr) + continue + } + + if output != nil && output.OperationId != nil { + if _, err := waiter.OperationSuccess(conn, aws.StringValue(output.OperationId)); err != nil { + sweeperErr := fmt.Errorf("error waiting for Service Discovery Public DNS Namespace (%s) deletion: %w", id, err) + log.Printf("[ERROR] %s", sweeperErr) + sweeperErrs = multierror.Append(sweeperErrs, sweeperErr) + continue + } + } + } + + return !isLast + }) + + if testSweepSkipSweepError(err) { + log.Printf("[WARN] Skipping Service Discovery Public DNS Namespaces sweep for %s: %s", region, err) + return sweeperErrs.ErrorOrNil() // In case we have completed some pages, but had errors + } + + if err != nil { + sweeperErrs = multierror.Append(sweeperErrs, fmt.Errorf("error retrieving Service Discovery Public DNS Namespaces: %w", err)) + } + + return sweeperErrs.ErrorOrNil() +} + func TestAccAWSServiceDiscoveryPublicDnsNamespace_basic(t *testing.T) { resourceName := "aws_service_discovery_public_dns_namespace.test" rName := acctest.RandStringFromCharSet(5, acctest.CharSetAlpha) + ".terraformtesting.com" diff --git a/aws/resource_aws_service_discovery_service.go b/aws/resource_aws_service_discovery_service.go index 70812c95a098..a898663dbcd7 100644 --- a/aws/resource_aws_service_discovery_service.go +++ b/aws/resource_aws_service_discovery_service.go @@ -1,14 +1,14 @@ package aws import ( + "fmt" "log" - "time" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/servicediscovery" - "github.com/hashicorp/terraform-plugin-sdk/helper/resource" "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/service/servicediscovery/waiter" ) func resourceAwsServiceDiscoveryService() *schema.Resource { @@ -225,23 +225,16 @@ func resourceAwsServiceDiscoveryServiceUpdate(d *schema.ResourceData, meta inter input.Service = sc - resp, err := conn.UpdateService(input) - if err != nil { - return err - } + output, err := conn.UpdateService(input) - stateConf := &resource.StateChangeConf{ - Pending: []string{servicediscovery.OperationStatusSubmitted, servicediscovery.OperationStatusPending}, - Target: []string{servicediscovery.OperationStatusSuccess}, - Refresh: servicediscoveryOperationRefreshStatusFunc(conn, *resp.OperationId), - Timeout: 5 * time.Minute, - Delay: 10 * time.Second, - MinTimeout: 3 * time.Second, + if err != nil { + return fmt.Errorf("error updating Service Discovery Service (%s): %w", d.Id(), err) } - _, err = stateConf.WaitForState() - if err != nil { - return err + if output != nil && output.OperationId != nil { + if _, err := waiter.OperationSuccess(conn, aws.StringValue(output.OperationId)); err != nil { + return fmt.Errorf("error waiting for Service Discovery Service (%s) update: %w", d.Id(), err) + } } return resourceAwsServiceDiscoveryServiceRead(d, meta) @@ -255,7 +248,16 @@ func resourceAwsServiceDiscoveryServiceDelete(d *schema.ResourceData, meta inter } _, err := conn.DeleteService(input) - return err + + if isAWSErr(err, servicediscovery.ErrCodeServiceNotFound, "") { + return nil + } + + if err != nil { + return fmt.Errorf("error deleting Service Discovery Service (%s): %w", d.Id(), err) + } + + return nil } func expandServiceDiscoveryDnsConfig(configured map[string]interface{}) *servicediscovery.DnsConfig { diff --git a/aws/resource_aws_service_discovery_service_test.go b/aws/resource_aws_service_discovery_service_test.go index 79792dc10125..024f04aa6543 100644 --- a/aws/resource_aws_service_discovery_service_test.go +++ b/aws/resource_aws_service_discovery_service_test.go @@ -2,15 +2,116 @@ package aws import ( "fmt" + "log" "testing" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/servicediscovery" + "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" ) +func init() { + resource.AddTestSweepers("aws_service_discovery_service", &resource.Sweeper{ + Name: "aws_service_discovery_service", + F: testSweepServiceDiscoveryServices, + }) +} + +func testSweepServiceDiscoveryServices(region string) error { + client, err := sharedClientForRegion(region) + if err != nil { + return fmt.Errorf("error getting client: %s", err) + } + conn := client.(*AWSClient).sdconn + var sweeperErrs *multierror.Error + + input := &servicediscovery.ListServicesInput{} + + err = conn.ListServicesPages(input, func(page *servicediscovery.ListServicesOutput, isLast bool) bool { + if page == nil { + return !isLast + } + + for _, service := range page.Services { + if service == nil { + continue + } + + serviceID := aws.StringValue(service.Id) + input := &servicediscovery.DeleteServiceInput{ + Id: service.Id, + } + + if aws.Int64Value(service.InstanceCount) > 0 { + input := &servicediscovery.ListInstancesInput{} + + err := conn.ListInstancesPages(input, func(page *servicediscovery.ListInstancesOutput, isLast bool) bool { + if page == nil { + return !isLast + } + + for _, instance := range page.Instances { + if instance == nil { + continue + } + + instanceID := aws.StringValue(instance.Id) + input := &servicediscovery.DeregisterInstanceInput{ + InstanceId: instance.Id, + ServiceId: service.Id, + } + + log.Printf("[INFO] Deregistering Service Discovery Service (%s) Instance: %s", serviceID, instanceID) + _, err := conn.DeregisterInstance(input) + + if err != nil { + sweeperErr := fmt.Errorf("error deregistering Service Discovery Service (%s) Instance (%s): %w", serviceID, instanceID, err) + log.Printf("[ERROR] %s", sweeperErr) + sweeperErrs = multierror.Append(sweeperErrs, sweeperErr) + continue + } + } + + return !isLast + }) + + if err != nil { + sweeperErr := fmt.Errorf("error listing Service Discovery Service (%s) Instances: %w", serviceID, err) + log.Printf("[ERROR] %s", sweeperErr) + sweeperErrs = multierror.Append(sweeperErrs, sweeperErr) + continue + } + } + + log.Printf("[INFO] Deleting Service Discovery Service: %s", serviceID) + _, err := conn.DeleteService(input) + + if err != nil { + sweeperErr := fmt.Errorf("error deleting Service Discovery Service (%s): %w", serviceID, err) + log.Printf("[ERROR] %s", sweeperErr) + sweeperErrs = multierror.Append(sweeperErrs, sweeperErr) + continue + } + } + + return !isLast + }) + + if testSweepSkipSweepError(err) { + log.Printf("[WARN] Skipping Service Discovery Services sweep for %s: %s", region, err) + return sweeperErrs.ErrorOrNil() // In case we have completed some pages, but had errors + } + + if err != nil { + sweeperErrs = multierror.Append(sweeperErrs, fmt.Errorf("error retrieving Service Discovery Services: %w", err)) + } + + return sweeperErrs.ErrorOrNil() +} + func TestAccAWSServiceDiscoveryService_private(t *testing.T) { resourceName := "aws_service_discovery_service.test" rName := acctest.RandString(5)