-
Notifications
You must be signed in to change notification settings - Fork 9.4k
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
service/servicediscovery: Refactor waiter logic into separate package, add test sweepers #12765
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
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() | ||
|
||
switch output := outputRaw.(type) { | ||
case *servicediscovery.GetOperationOutput: | ||
return output, err | ||
default: | ||
return nil, err | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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)) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Much better naming 👍 |
||
|
||
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 | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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 | ||
} | ||
} | ||
Comment on lines
+56
to
+77
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The deletion logic is repeated here and in the resource itself. Would it make sense to have it in one place and call it for the resource and the sweeper? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Most likely, yes. Ideally we can setup the sweepers to call the existing resource deletion logic rather than re-inventing the wheel each sweeper. Probably worth a followup design proposal though. 👍 |
||
} | ||
|
||
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)) | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since there are only two possibilities, would
if v, ok := outputRaw.(*servicediscovery.GetOperationOutput); ok { ... }
work better here?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
They are functionally equivalent, yes. Will update.