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

provider: Introduce shared IAM propagation timeout, refactor KMS Key creation to that timeout and add KMS Key deletion to internal waiter package #12863

Merged
merged 1 commit into from
Apr 29, 2020
Merged
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
14 changes: 14 additions & 0 deletions aws/internal/service/iam/waiter/waiter.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package waiter

import (
"time"
)

const (
// Maximum amount of time to wait for IAM changes to propagate
// This timeout should not be increased without strong consideration
// as this will negatively impact user experience when configurations
// have incorrect references or permissions.
// Reference: https://docs.aws.amazon.com/IAM/latest/UserGuide/troubleshoot_general.html#troubleshoot_general_eventual-consistency
PropagationTimeout = 2 * time.Minute
)
28 changes: 28 additions & 0 deletions aws/internal/service/kms/waiter/status.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package waiter

import (
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/kms"
"github.com/hashicorp/terraform-plugin-sdk/helper/resource"
)

// KeyState fetches the Key and its State
func KeyState(conn *kms.KMS, keyID string) resource.StateRefreshFunc {
return func() (interface{}, string, error) {
input := &kms.DescribeKeyInput{
KeyId: aws.String(keyID),
}

output, err := conn.DescribeKey(input)

if err != nil {
return nil, kms.KeyStateUnavailable, err
}

if output == nil || output.KeyMetadata == nil {
return output, kms.KeyStateUnavailable, nil
}

return output, aws.StringValue(output.KeyMetadata.KeyState), nil
}
}
34 changes: 34 additions & 0 deletions aws/internal/service/kms/waiter/waiter.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package waiter

import (
"time"

"github.com/aws/aws-sdk-go/service/kms"
"github.com/hashicorp/terraform-plugin-sdk/helper/resource"
)

const (
// Maximum amount of time to wait for KeyState to return PendingDeletion
KeyStatePendingDeletionTimeout = 20 * time.Minute
)

// KeyStatePendingDeletion waits for KeyState to return PendingDeletion
func KeyStatePendingDeletion(conn *kms.KMS, keyID string) (*kms.DescribeKeyOutput, error) {
stateConf := &resource.StateChangeConf{
Pending: []string{
kms.KeyStateDisabled,
kms.KeyStateEnabled,
},
Target: []string{kms.KeyStatePendingDeletion},
Refresh: KeyState(conn, keyID),
Timeout: KeyStatePendingDeletionTimeout,
}

outputRaw, err := stateConf.WaitForState()

if output, ok := outputRaw.(*kms.DescribeKeyOutput); ok {
return output, err
}

return nil, err
}
52 changes: 12 additions & 40 deletions aws/resource_aws_kms_external_key.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ import (
"github.com/hashicorp/terraform-plugin-sdk/helper/structure"
"github.com/hashicorp/terraform-plugin-sdk/helper/validation"
"github.com/terraform-providers/terraform-provider-aws/aws/internal/keyvaluetags"
iamwaiter "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/iam/waiter"
"github.com/terraform-providers/terraform-provider-aws/aws/internal/service/kms/waiter"
)

func resourceAwsKmsExternalKey() *schema.Resource {
Expand Down Expand Up @@ -110,7 +112,7 @@ func resourceAwsKmsExternalKeyCreate(d *schema.ResourceData, meta interface{}) e
}

var output *kms.CreateKeyOutput
err := resource.Retry(1*time.Minute, func() *resource.RetryError {
err := resource.Retry(iamwaiter.PropagationTimeout, func() *resource.RetryError {
var err error

output, err = conn.CreateKey(input)
Expand Down Expand Up @@ -332,11 +334,17 @@ func resourceAwsKmsExternalKeyDelete(d *schema.ResourceData, meta interface{}) e
}

if err != nil {
return fmt.Errorf("error scheduling KMS External Key (%s) deletion: %s", d.Id(), err)
return fmt.Errorf("error scheduling deletion for KMS Key (%s): %w", d.Id(), err)
}

_, err = waiter.KeyStatePendingDeletion(conn, d.Id())

if isAWSErr(err, kms.ErrCodeNotFoundException, "") {
return nil
}

if err := waitForKmsKeyScheduleDeletion(conn, d.Id()); err != nil {
return fmt.Errorf("error waiting for KMS External Key (%s) deletion scheduling: %s", d.Id(), err)
if err != nil {
return fmt.Errorf("error waiting for KMS Key (%s) to schedule deletion: %w", d.Id(), err)
}

return nil
Expand Down Expand Up @@ -440,39 +448,3 @@ func importKmsExternalKeyMaterial(conn *kms.KMS, keyID, keyMaterialBase64, valid

return nil
}

func waitForKmsKeyScheduleDeletion(conn *kms.KMS, keyID string) error {
// Wait for propagation since KMS is eventually consistent
input := &kms.DescribeKeyInput{
KeyId: aws.String(keyID),
}

wait := resource.StateChangeConf{
Pending: []string{kms.KeyStateDisabled, kms.KeyStateEnabled},
Target: []string{kms.KeyStatePendingDeletion},
Timeout: 20 * time.Minute,
MinTimeout: 2 * time.Second,
ContinuousTargetOccurence: 10,
Refresh: func() (interface{}, string, error) {
output, err := conn.DescribeKey(input)

if isAWSErr(err, kms.ErrCodeNotFoundException, "") {
return 42, kms.KeyStatePendingDeletion, nil
}

if err != nil {
return nil, kms.KeyStateUnavailable, err
}

if output == nil || output.KeyMetadata == nil {
return 42, kms.KeyStatePendingDeletion, nil
}

return output, aws.StringValue(output.KeyMetadata.KeyState), nil
},
}

_, err := wait.WaitForState()

return err
}
7 changes: 5 additions & 2 deletions aws/resource_aws_kms_external_key_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ import (
"github.com/aws/aws-sdk-go/service/kms"
"github.com/hashicorp/terraform-plugin-sdk/helper/resource"
"github.com/hashicorp/terraform-plugin-sdk/terraform"
"github.com/jen20/awspolicyequivalence"
awspolicy "github.com/jen20/awspolicyequivalence"
"github.com/terraform-providers/terraform-provider-aws/aws/internal/service/kms/waiter"
)

func TestAccAWSKmsExternalKey_basic(t *testing.T) {
Expand Down Expand Up @@ -483,7 +484,9 @@ func testAccCheckAWSKmsExternalKeyDisappears(key *kms.KeyMetadata) resource.Test
return err
}

return waitForKmsKeyScheduleDeletion(conn, aws.StringValue(key.KeyId))
_, err = waiter.KeyStatePendingDeletion(conn, aws.StringValue(key.KeyId))

return err
}
}

Expand Down
39 changes: 13 additions & 26 deletions aws/resource_aws_kms_key.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import (
"github.com/hashicorp/terraform-plugin-sdk/helper/structure"
"github.com/hashicorp/terraform-plugin-sdk/helper/validation"
"github.com/terraform-providers/terraform-provider-aws/aws/internal/keyvaluetags"
iamwaiter "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/iam/waiter"
"github.com/terraform-providers/terraform-provider-aws/aws/internal/service/kms/waiter"
)

func resourceAwsKmsKey() *schema.Resource {
Expand Down Expand Up @@ -116,7 +118,7 @@ func resourceAwsKmsKeyCreate(d *schema.ResourceData, meta interface{}) error {
// The KMS service's awareness of principals is limited by "eventual consistency".
// They acknowledge this here:
// http://docs.aws.amazon.com/kms/latest/APIReference/API_CreateKey.html
err := resource.Retry(30*time.Second, func() *resource.RetryError {
err := resource.Retry(iamwaiter.PropagationTimeout, func() *resource.RetryError {
var err error
resp, err = conn.CreateKey(req)
if isAWSErr(err, kms.ErrCodeMalformedPolicyDocumentException, "") {
Expand Down Expand Up @@ -471,39 +473,24 @@ func resourceAwsKmsKeyDelete(d *schema.ResourceData, meta interface{}) error {
req.PendingWindowInDays = aws.Int64(int64(v.(int)))
}
_, err := conn.ScheduleKeyDeletion(req)
if err != nil {
return err

if isAWSErr(err, kms.ErrCodeNotFoundException, "") {
return nil
}

// Wait for propagation since KMS is eventually consistent
wait := resource.StateChangeConf{
Pending: []string{kms.KeyStateEnabled, kms.KeyStateDisabled},
Target: []string{kms.KeyStatePendingDeletion},
Timeout: 20 * time.Minute,
MinTimeout: 2 * time.Second,
ContinuousTargetOccurence: 10,
Refresh: func() (interface{}, string, error) {
log.Printf("[DEBUG] Checking if KMS key %s state is PendingDeletion", keyId)
resp, err := conn.DescribeKey(&kms.DescribeKeyInput{
KeyId: aws.String(keyId),
})
if err != nil {
return resp, "Failed", err
}
if err != nil {
return fmt.Errorf("error scheduling deletion for KMS Key (%s): %w", d.Id(), err)
}

metadata := *resp.KeyMetadata
log.Printf("[DEBUG] KMS key %s state is %s, retrying", keyId, *metadata.KeyState)
_, err = waiter.KeyStatePendingDeletion(conn, d.Id())

return resp, *metadata.KeyState, nil
},
if isAWSErr(err, kms.ErrCodeNotFoundException, "") {
return nil
}

_, err = wait.WaitForState()
if err != nil {
return fmt.Errorf("Failed deactivating KMS key %s: %s", keyId, err)
return fmt.Errorf("error waiting for KMS Key (%s) to schedule deletion: %w", d.Id(), err)
}

log.Printf("[DEBUG] KMS Key %s deactivated.", keyId)

return nil
}
Loading