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

[#19212] Fix secretsmanager_secret_version update #19943

Merged
3 changes: 3 additions & 0 deletions .changelog/19943.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:bug
resource/aws_secretsmanager_secret_version: Fix `InvalidParameterException: The parameter RemoveFromVersionId can't be empty. Staging label AWSCURRENT is currently attached to version ..., so you must explicitly reference that version in RemoveFromVersionId` errors when a secret is updated outside Terraform
```
46 changes: 42 additions & 4 deletions internal/service/secretsmanager/secret_version.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ import (
)

const (
secretVersionStageCurrent = "AWSCURRENT"
secretVersionStageCurrent = "AWSCURRENT"
secretVersionStagePrevious = "AWSPREVIOUS"
)

// @SDKResource("aws_secretsmanager_secret_version", name="Secret Version")
Expand Down Expand Up @@ -169,14 +170,46 @@ func resourceSecretVersionUpdate(ctx context.Context, d *schema.ResourceData, me
os, ns := o.(*schema.Set), n.(*schema.Set)
add, del := flex.ExpandStringValueSet(ns.Difference(os)), flex.ExpandStringValueSet(os.Difference(ns))

var listedVersionIDs bool
for _, stage := range add {
input := &secretsmanager.UpdateSecretVersionStageInput{
inputU := &secretsmanager.UpdateSecretVersionStageInput{
MoveToVersionId: aws.String(versionID),
SecretId: aws.String(secretID),
VersionStage: aws.String(stage),
}

_, err := conn.UpdateSecretVersionStage(ctx, input)
if !listedVersionIDs {
if stage == secretVersionStageCurrent {
inputL := &secretsmanager.ListSecretVersionIdsInput{
SecretId: aws.String(secretID),
}
var versionStageCurrentVersionID string

paginator := secretsmanager.NewListSecretVersionIdsPaginator(conn, inputL)
listVersionIDs:
for paginator.HasMorePages() {
page, err := paginator.NextPage(ctx)

if err != nil {
return sdkdiag.AppendErrorf(diags, "listing Secrets Manager Secret (%s) version IDs: %s", secretID, err)
}

for _, version := range page.Versions {
for _, versionStage := range version.VersionStages {
if versionStage == secretVersionStageCurrent {
versionStageCurrentVersionID = aws.ToString(version.VersionId)
break listVersionIDs
}
}
}
}

inputU.RemoveFromVersionId = aws.String(versionStageCurrentVersionID)
listedVersionIDs = true
}
}

_, err := conn.UpdateSecretVersionStage(ctx, inputU)

if err != nil {
return sdkdiag.AppendErrorf(diags, "adding Secrets Manager Secret Version (%s) stage (%s): %s", d.Id(), stage, err)
Expand All @@ -190,6 +223,11 @@ func resourceSecretVersionUpdate(ctx context.Context, d *schema.ResourceData, me
continue
}

// If we added AWSCURRENT to this version then any AWSPREVIOUS label will have been moved to another version.
if listedVersionIDs && stage == secretVersionStagePrevious {
continue
}

input := &secretsmanager.UpdateSecretVersionStageInput{
RemoveFromVersionId: aws.String(versionID),
SecretId: aws.String(secretID),
Expand Down Expand Up @@ -251,7 +289,7 @@ func resourceSecretVersionDelete(ctx context.Context, d *schema.ResourceData, me
return nil, err
}

if len(output.VersionStages) == 0 || (len(output.VersionStages) == 1 && output.VersionStages[0] == secretVersionStageCurrent) {
if len(output.VersionStages) == 0 || (len(output.VersionStages) == 1 && (output.VersionStages[0] == secretVersionStageCurrent || output.VersionStages[0] == secretVersionStagePrevious)) {
return nil, &retry.NotFoundError{}
}

Expand Down
83 changes: 69 additions & 14 deletions internal/service/secretsmanager/secret_version_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"fmt"
"testing"

"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/secretsmanager"
sdkacctest "github.com/hashicorp/terraform-plugin-testing/helper/acctest"
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
Expand Down Expand Up @@ -138,6 +139,54 @@ func TestAccSecretsManagerSecretVersion_versionStages(t *testing.T) {
})
}

func TestAccSecretsManagerSecretVersion_versionStagesExternalUpdate(t *testing.T) {
ctx := acctest.Context(t)
var version secretsmanager.GetSecretValueOutput
rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix)
resourceName := "aws_secretsmanager_secret_version.test"

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { acctest.PreCheck(ctx, t); testAccPreCheck(ctx, t) },
ErrorCheck: acctest.ErrorCheck(t, names.SecretsManagerEndpointID),
ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories,
CheckDestroy: testAccCheckSecretVersionDestroy(ctx),
Steps: []resource.TestStep{
{
Config: testAccSecretVersionConfig_stagesSingle(rName),
Check: resource.ComposeTestCheckFunc(
testAccCheckSecretVersionExists(ctx, resourceName, &version),
resource.TestCheckResourceAttr(resourceName, "secret_string", "test-string"),
resource.TestCheckResourceAttr(resourceName, "version_stages.#", "2"),
resource.TestCheckTypeSetElemAttr(resourceName, "version_stages.*", "AWSCURRENT"),
resource.TestCheckTypeSetElemAttr(resourceName, "version_stages.*", "one"),
),
},
{
PreConfig: func() {
conn := acctest.Provider.Meta().(*conns.AWSClient).SecretsManagerClient(ctx)

_, err := conn.PutSecretValue(ctx, &secretsmanager.PutSecretValueInput{
SecretId: version.ARN,
SecretString: aws.String("external_update"),
})

if err != nil {
t.Fatalf("externally updating Secrets Manager Secret Version: %s", err)
}
},
Config: testAccSecretVersionConfig_stagesSingle(rName),
Check: resource.ComposeTestCheckFunc(
testAccCheckSecretVersionExists(ctx, resourceName, &version),
resource.TestCheckResourceAttr(resourceName, "secret_string", "test-string"),
resource.TestCheckResourceAttr(resourceName, "version_stages.#", "2"),
resource.TestCheckTypeSetElemAttr(resourceName, "version_stages.*", "AWSCURRENT"),
resource.TestCheckTypeSetElemAttr(resourceName, "version_stages.*", "one"),
),
},
},
})
}

func TestAccSecretsManagerSecretVersion_disappears(t *testing.T) {
ctx := acctest.Context(t)
var version secretsmanager.GetSecretValueOutput
Expand Down Expand Up @@ -188,10 +237,6 @@ func TestAccSecretsManagerSecretVersion_Disappears_secret(t *testing.T) {
})
}

/*

Need to handle 'AWSPREVIOUS' better.

func TestAccSecretsManagerSecretVersion_multipleVersions(t *testing.T) {
ctx := acctest.Context(t)
var version1, version2, version3 secretsmanager.GetSecretValueOutput
Expand All @@ -212,19 +257,19 @@ func TestAccSecretsManagerSecretVersion_multipleVersions(t *testing.T) {
testAccCheckSecretVersionExists(ctx, resource1Name, &version1),
resource.TestCheckResourceAttr(resource1Name, "version_stages.#", "1"),
resource.TestCheckTypeSetElemAttr(resource1Name, "version_stages.*", "one"),
testAccCheckSecretVersionExists(ctx, resource1Name, &version2),
testAccCheckSecretVersionExists(ctx, resource2Name, &version2),
resource.TestCheckResourceAttr(resource2Name, "version_stages.#", "2"),
resource.TestCheckTypeSetElemAttr(resource2Name, "version_stages.*", "two"),
resource.TestCheckTypeSetElemAttr(resource2Name, "version_stages.*", "AWSCURRENT"),
testAccCheckSecretVersionExists(ctx, resource1Name, &version3),
resource.TestCheckResourceAttr(resource3Name, "version_stages.#", "1"),
resource.TestCheckTypeSetElemAttr(resource2Name, "version_stages.*", "2"),
testAccCheckSecretVersionExists(ctx, resource3Name, &version3),
resource.TestCheckResourceAttr(resource3Name, "version_stages.#", "2"),
resource.TestCheckTypeSetElemAttr(resource3Name, "version_stages.*", "three"),
resource.TestCheckTypeSetElemAttr(resource3Name, "version_stages.*", "AWSCURRENT"),
),
},
},
})
}
*/

func testAccCheckSecretVersionDestroy(ctx context.Context) resource.TestCheckFunc {
return func(s *terraform.State) error {
Expand All @@ -245,7 +290,7 @@ func testAccCheckSecretVersionDestroy(ctx context.Context) resource.TestCheckFun
return err
}

if len(output.VersionStages) == 0 || (len(output.VersionStages) == 1 && output.VersionStages[0] == "AWSCURRENT") {
if len(output.VersionStages) == 0 || (len(output.VersionStages) == 1 && (output.VersionStages[0] == "AWSCURRENT" || output.VersionStages[0] == "AWSPREVIOUS")) {
continue
}

Expand Down Expand Up @@ -348,7 +393,6 @@ resource "aws_secretsmanager_secret_version" "test" {
`, rName)
}

/*
func testAccSecretVersionConfig_multipleVersions(rName string) string {
return fmt.Sprintf(`
resource "aws_secretsmanager_secret" "test" {
Expand All @@ -360,21 +404,32 @@ resource "aws_secretsmanager_secret_version" "test1" {
secret_string = "test1"

version_stages = ["one"]

lifecycle {
ignore_changes = [version_stages] # "AWSPREVIOUS"
}
}

resource "aws_secretsmanager_secret_version" "test2" {
secret_id = aws_secretsmanager_secret.test.id
secret_string = "test2"

version_stages = ["two", "AWSCURRENT"]
version_stages = ["two", "2"]

depends_on = [aws_secretsmanager_secret_version.test1]

lifecycle {
ignore_changes = [version_stages] # "AWSPREVIOUS"
}
}

resource "aws_secretsmanager_secret_version" "test3" {
secret_id = aws_secretsmanager_secret.test.id
secret_string = "test3"

version_stages = ["three"]
version_stages = ["three", "AWSCURRENT"]

depends_on = [aws_secretsmanager_secret_version.test2]
}
`, rName)
}
*/
Loading