From 65e0876a46e453020fda50d2e8ac10cc4b3e207c Mon Sep 17 00:00:00 2001 From: Luke Davison Date: Wed, 15 Oct 2025 15:10:16 +0100 Subject: [PATCH 1/4] Rework of aws_lambda_layer_version_permission resource. --- internal/service/lambda/exports_test.go | 2 +- .../lambda/layer_version_permission.go | 134 ++++++++++-------- .../lambda/layer_version_permission_test.go | 41 +++++- 3 files changed, 111 insertions(+), 66 deletions(-) diff --git a/internal/service/lambda/exports_test.go b/internal/service/lambda/exports_test.go index 4b6488686d57..1566360321a8 100644 --- a/internal/service/lambda/exports_test.go +++ b/internal/service/lambda/exports_test.go @@ -24,7 +24,7 @@ var ( FindFunctionEventInvokeConfigByTwoPartKey = findFunctionEventInvokeConfigByTwoPartKey FindFunctionRecursionConfigByName = findFunctionRecursionConfigByName FindFunctionURLByTwoPartKey = findFunctionURLByTwoPartKey - FindLayerVersionByTwoPartKey = findLayerVersionByTwoPartKey + FindLayerVersionPermissionByThreePartKey = findLayerVersionPermissionByThreePartKey FindLayerVersionPolicyByTwoPartKey = findLayerVersionPolicyByTwoPartKey FindPolicyStatementByTwoPartKey = findPolicyStatementByTwoPartKey FindProvisionedConcurrencyConfigByTwoPartKey = findProvisionedConcurrencyConfigByTwoPartKey diff --git a/internal/service/lambda/layer_version_permission.go b/internal/service/lambda/layer_version_permission.go index 11f9ccb1668c..b1416e17c80e 100644 --- a/internal/service/lambda/layer_version_permission.go +++ b/internal/service/lambda/layer_version_permission.go @@ -8,6 +8,7 @@ import ( "encoding/json" "log" "reflect" + "slices" "strconv" "github.com/YakDriver/regexache" @@ -29,7 +30,7 @@ import ( ) const ( - layerVersionPermissionResourceIDPartCount = 2 + layerVersionPermissionResourceIDPartCount = 3 ) // @SDKResource("aws_lambda_layer_version_permission", name="Layer Version Permission") @@ -102,7 +103,8 @@ func resourceLayerVersionPermissionCreate(ctx context.Context, d *schema.Resourc layerName := d.Get("layer_name").(string) versionNumber := d.Get("version_number").(int) - id, err := flex.FlattenResourceId([]string{layerName, strconv.FormatInt(int64(versionNumber), 10)}, layerVersionPermissionResourceIDPartCount, true) + statementId := d.Get("statement_id").(string) + id, err := flex.FlattenResourceId([]string{layerName, strconv.FormatInt(int64(versionNumber), 10), statementId}, layerVersionPermissionResourceIDPartCount, true) if err != nil { return sdkdiag.AppendFromErr(diags, err) } @@ -110,7 +112,7 @@ func resourceLayerVersionPermissionCreate(ctx context.Context, d *schema.Resourc Action: aws.String(d.Get(names.AttrAction).(string)), LayerName: aws.String(layerName), Principal: aws.String(d.Get(names.AttrPrincipal).(string)), - StatementId: aws.String(d.Get("statement_id").(string)), + StatementId: aws.String(statementId), VersionNumber: aws.Int64(int64(versionNumber)), } @@ -131,15 +133,15 @@ func resourceLayerVersionPermissionRead(ctx context.Context, d *schema.ResourceD var diags diag.Diagnostics conn := meta.(*conns.AWSClient).LambdaClient(ctx) - layerName, versionNumber, err := layerVersionPermissionParseResourceID(d.Id()) + layerName, versionNumber, statementId, err := layerVersionPermissionParseResourceID(d.Id()) if err != nil { return sdkdiag.AppendFromErr(diags, err) } - output, err := findLayerVersionPolicyByTwoPartKey(ctx, conn, layerName, versionNumber) + policy, statement, err := findLayerVersionPermissionByThreePartKey(ctx, conn, layerName, versionNumber, statementId) if !d.IsNewResource() && tfresource.NotFound(err) { - log.Printf("[WARN] Lambda Layer Version Permission (%s) not found, removing from state", d.Id()) + log.Printf("[WARN] Lambda Layer Version Permission (%s, %s) not found, removing from state", d.Id(), statementId) d.SetId("") return diags } @@ -148,61 +150,54 @@ func resourceLayerVersionPermissionRead(ctx context.Context, d *schema.ResourceD return sdkdiag.AppendErrorf(diags, "reading Lambda Layer Version Permission (%s): %s", d.Id(), err) } - policyDoc := &IAMPolicyDoc{} - if err := json.Unmarshal([]byte(aws.ToString(output.Policy)), policyDoc); err != nil { - return sdkdiag.AppendFromErr(diags, err) - } d.Set("layer_name", layerName) - d.Set(names.AttrPolicy, output.Policy) - d.Set("revision_id", output.RevisionId) + d.Set(names.AttrPolicy, policy.Policy) + d.Set("revision_id", policy.RevisionId) d.Set("version_number", versionNumber) + d.Set("statement_id", statementId) + + if actions := statement.Actions; actions != nil { + var action string + + if t := reflect.TypeOf(actions); t.String() == "[]string" && len(actions.([]string)) > 0 { + action = actions.([]string)[0] + } else if t.String() == "string" { + action = actions.(string) + } - if len(policyDoc.Statements) > 0 { - d.Set("statement_id", policyDoc.Statements[0].Sid) + d.Set(names.AttrAction, action) + } - if actions := policyDoc.Statements[0].Actions; actions != nil { - var action string + if len(statement.Conditions) > 0 { + if values := statement.Conditions[0].Values; values != nil { + var organizationID string - if t := reflect.TypeOf(actions); t.String() == "[]string" && len(actions.([]string)) > 0 { - action = actions.([]string)[0] + if t := reflect.TypeOf(values); t.String() == "[]string" && len(values.([]string)) > 0 { + organizationID = values.([]string)[0] } else if t.String() == "string" { - action = actions.(string) + organizationID = values.(string) } - d.Set(names.AttrAction, action) + d.Set("organization_id", organizationID) } + } - if len(policyDoc.Statements[0].Conditions) > 0 { - if values := policyDoc.Statements[0].Conditions[0].Values; values != nil { - var organizationID string + if len(statement.Principals) > 0 { + if identifiers := statement.Principals[0].Identifiers; identifiers != nil { + var principal string - if t := reflect.TypeOf(values); t.String() == "[]string" && len(values.([]string)) > 0 { - organizationID = values.([]string)[0] - } else if t.String() == "string" { - organizationID = values.(string) + if t := reflect.TypeOf(identifiers); t.String() == "[]string" && len(identifiers.([]string)) > 0 && identifiers.([]string)[0] == "*" { + principal = "*" + } else if t.String() == "string" { + policyPrincipalARN, err := arn.Parse(identifiers.(string)) + if err != nil { + return sdkdiag.AppendFromErr(diags, err) } - - d.Set("organization_id", organizationID) + principal = policyPrincipalARN.AccountID } - } - if len(policyDoc.Statements[0].Principals) > 0 { - if identifiers := policyDoc.Statements[0].Principals[0].Identifiers; identifiers != nil { - var principal string - - if t := reflect.TypeOf(identifiers); t.String() == "[]string" && len(identifiers.([]string)) > 0 && identifiers.([]string)[0] == "*" { - principal = "*" - } else if t.String() == "string" { - policyPrincipalARN, err := arn.Parse(identifiers.(string)) - if err != nil { - return sdkdiag.AppendFromErr(diags, err) - } - principal = policyPrincipalARN.AccountID - } - - d.Set(names.AttrPrincipal, principal) - } + d.Set(names.AttrPrincipal, principal) } } @@ -213,7 +208,7 @@ func resourceLayerVersionPermissionDelete(ctx context.Context, d *schema.Resourc var diags diag.Diagnostics conn := meta.(*conns.AWSClient).LambdaClient(ctx) - layerName, versionNumber, err := layerVersionPermissionParseResourceID(d.Id()) + layerName, versionNumber, statementId, err := layerVersionPermissionParseResourceID(d.Id()) if err != nil { return sdkdiag.AppendFromErr(diags, err) } @@ -226,7 +221,7 @@ func resourceLayerVersionPermissionDelete(ctx context.Context, d *schema.Resourc log.Printf("[INFO] Deleting Lambda Layer Permission Version: %s", d.Id()) _, err = conn.RemoveLayerVersionPermission(ctx, &lambda.RemoveLayerVersionPermissionInput{ LayerName: aws.String(layerName), - StatementId: aws.String(d.Get("statement_id").(string)), + StatementId: aws.String(statementId), VersionNumber: aws.Int64(versionNumber), }) @@ -241,49 +236,66 @@ func resourceLayerVersionPermissionDelete(ctx context.Context, d *schema.Resourc return diags } -func layerVersionPermissionParseResourceID(id string) (string, int64, error) { +func layerVersionPermissionParseResourceID(id string) (string, int64, string, error) { parts, err := flex.ExpandResourceId(id, layerVersionPermissionResourceIDPartCount, true) if err != nil { - return "", 0, err + return "", 0, "", err } layerName := parts[0] versionNumber, err := strconv.ParseInt(parts[1], 10, 64) + statementId := parts[2] if err != nil { - return "", 0, err + return "", 0, "", err } - return layerName, versionNumber, nil + return layerName, versionNumber, statementId, nil } -func findLayerVersionPolicy(ctx context.Context, conn *lambda.Client, input *lambda.GetLayerVersionPolicyInput) (*lambda.GetLayerVersionPolicyOutput, error) { - output, err := conn.GetLayerVersionPolicy(ctx, input) +func findLayerVersionPermission(ctx context.Context, conn *lambda.Client, input *lambda.GetLayerVersionPolicyInput, statementId string) (*lambda.GetLayerVersionPolicyOutput, *IAMPolicyStatement, error) { + policyOutput, err := conn.GetLayerVersionPolicy(ctx, input) if errs.IsA[*awstypes.ResourceNotFoundException](err) { - return nil, &retry.NotFoundError{ + return nil, nil, &retry.NotFoundError{ LastError: err, LastRequest: input, } } if err != nil { - return nil, err + return nil, nil, err + } + + if policyOutput == nil { + return nil, nil, tfresource.NewEmptyResultError(input) } - if output == nil { - return nil, tfresource.NewEmptyResultError(input) + policyDoc := &IAMPolicyDoc{} + if err := json.Unmarshal([]byte(aws.ToString(policyOutput.Policy)), policyDoc); err != nil { + return nil, nil, err } - return output, nil + statementIndex := slices.IndexFunc(policyDoc.Statements, func(statement *IAMPolicyStatement) bool { + return (*statement).Sid == statementId + }) + + if statementIndex == -1 { + return nil, nil, &retry.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + return policyOutput, policyDoc.Statements[statementIndex], nil } -func findLayerVersionPolicyByTwoPartKey(ctx context.Context, conn *lambda.Client, layerName string, versionNumber int64) (*lambda.GetLayerVersionPolicyOutput, error) { +func findLayerVersionPermissionByThreePartKey(ctx context.Context, conn *lambda.Client, layerName string, versionNumber int64, statementId string) (*lambda.GetLayerVersionPolicyOutput, *IAMPolicyStatement, error) { input := &lambda.GetLayerVersionPolicyInput{ LayerName: aws.String(layerName), VersionNumber: aws.Int64(versionNumber), } - return findLayerVersionPolicy(ctx, conn, input) + return findLayerVersionPermission(ctx, conn, input, statementId) } diff --git a/internal/service/lambda/layer_version_permission_test.go b/internal/service/lambda/layer_version_permission_test.go index c90346a00217..3cd0b7784e6d 100644 --- a/internal/service/lambda/layer_version_permission_test.go +++ b/internal/service/lambda/layer_version_permission_test.go @@ -21,6 +21,10 @@ import ( func TestAccLambdaLayerVersionPermission_basic_byARN(t *testing.T) { ctx := acctest.Context(t) resourceName := "aws_lambda_layer_version_permission.test" + // Used to ensure the correct resource is read when other statements exist before and after. + resourceNameFoo := "aws_lambda_layer_version_permission.foo" + resourceNameBar := "aws_lambda_layer_version_permission.bar" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resource.ParallelTest(t, resource.TestCase{ @@ -33,12 +37,23 @@ func TestAccLambdaLayerVersionPermission_basic_byARN(t *testing.T) { Config: testAccLayerVersionPermissionConfig_basicARN(rName), Check: resource.ComposeTestCheckFunc( testAccCheckLayerVersionPermissionExists(ctx, resourceName), + testAccCheckLayerVersionPermissionExists(ctx, resourceNameFoo), + testAccCheckLayerVersionPermissionExists(ctx, resourceNameBar), resource.TestCheckResourceAttr(resourceName, names.AttrAction, "lambda:GetLayerVersion"), resource.TestCheckResourceAttr(resourceName, names.AttrPrincipal, "*"), resource.TestCheckResourceAttr(resourceName, "statement_id", "xaccount"), resource.TestCheckResourceAttrPair(resourceName, "layer_name", "aws_lambda_layer_version.test", "layer_arn"), ), }, + { + /* Each permission resource keeps track of the overall policy and policy + * revision separately. This means that when another permission is added, + * these attributes become out of date in the state of prior existing + * permission resources. Therefore, for an accurate ImportStateVerify + * test of multiple permission resources, each one must be refreshed prior + * to the test. */ + RefreshState: true, + }, { ResourceName: resourceName, ImportState: true, @@ -205,12 +220,30 @@ resource "aws_lambda_layer_version" "test" { layer_name = %[1]q } +resource "aws_lambda_layer_version_permission" "foo" { + layer_name = aws_lambda_layer_version.test.layer_arn + version_number = aws_lambda_layer_version.test.version + action = "lambda:GetLayerVersion" + statement_id = "fooaccount" + principal = "*" +} + resource "aws_lambda_layer_version_permission" "test" { layer_name = aws_lambda_layer_version.test.layer_arn version_number = aws_lambda_layer_version.test.version action = "lambda:GetLayerVersion" statement_id = "xaccount" principal = "*" + depends_on = [aws_lambda_layer_version_permission.foo] +} + +resource "aws_lambda_layer_version_permission" "bar" { + layer_name = aws_lambda_layer_version.test.layer_arn + version_number = aws_lambda_layer_version.test.version + action = "lambda:GetLayerVersion" + statement_id = "baraccount" + principal = "*" + depends_on = [aws_lambda_layer_version_permission.test] } `, layerName) } @@ -294,14 +327,14 @@ func testAccCheckLayerVersionPermissionExists(ctx context.Context, n string) res return fmt.Errorf("Not found: %s", n) } - layerName, versionNumber, err := tflambda.LayerVersionPermissionParseResourceID(rs.Primary.ID) + layerName, versionNumber, statementId, err := tflambda.LayerVersionPermissionParseResourceID(rs.Primary.ID) if err != nil { return err } conn := acctest.Provider.Meta().(*conns.AWSClient).LambdaClient(ctx) - _, err = tflambda.FindLayerVersionPolicyByTwoPartKey(ctx, conn, layerName, versionNumber) + _, _, err = tflambda.FindLayerVersionPermissionByThreePartKey(ctx, conn, layerName, versionNumber, statementId) return err } @@ -316,12 +349,12 @@ func testAccCheckLayerVersionPermissionDestroy(ctx context.Context) resource.Tes continue } - layerName, versionNumber, err := tflambda.LayerVersionPermissionParseResourceID(rs.Primary.ID) + layerName, versionNumber, statementId, err := tflambda.LayerVersionPermissionParseResourceID(rs.Primary.ID) if err != nil { return err } - _, err = tflambda.FindLayerVersionPolicyByTwoPartKey(ctx, conn, layerName, versionNumber) + _, _, err = tflambda.FindLayerVersionPermissionByThreePartKey(ctx, conn, layerName, versionNumber, statementId) if tfresource.NotFound(err) { continue From 0d7399167afabdcec6b7be3eba9cb16f2304d775 Mon Sep 17 00:00:00 2001 From: Luke Davison Date: Wed, 15 Oct 2025 18:04:07 +0100 Subject: [PATCH 2/4] Update docs and changelog. --- .changelog/44661.txt | 6 ++++++ .../r/lambda_layer_version_permission.html.markdown | 8 ++++---- .../r/lambda_layer_version_permission.html.markdown | 8 ++++---- .../docs/r/lambda_layer_version_permission.html.markdown | 6 +++--- 4 files changed, 17 insertions(+), 11 deletions(-) create mode 100644 .changelog/44661.txt diff --git a/.changelog/44661.txt b/.changelog/44661.txt new file mode 100644 index 000000000000..f1a245cd09e3 --- /dev/null +++ b/.changelog/44661.txt @@ -0,0 +1,6 @@ +```release-note:bug +resource/aws_lambda_layer_version_permission: Fix wrong permissions being read into state file during refresh +``` +```release-note:breaking-change +resource/aws_lambda_layer_version_permission: Import syntax adjusted to contain statement ID for correct targeting during read +``` diff --git a/website/docs/cdktf/python/r/lambda_layer_version_permission.html.markdown b/website/docs/cdktf/python/r/lambda_layer_version_permission.html.markdown index 7868d9a80c51..255154902928 100644 --- a/website/docs/cdktf/python/r/lambda_layer_version_permission.html.markdown +++ b/website/docs/cdktf/python/r/lambda_layer_version_permission.html.markdown @@ -161,7 +161,7 @@ This resource exports the following attributes in addition to the arguments abov ## Import -In Terraform v1.5.0 and later, use an [`import` block](https://developer.hashicorp.com/terraform/language/import) to import Lambda Layer Permissions using `layer_name` and `version_number`, separated by a comma (`,`). For example: +In Terraform v1.5.0 and later, use an [`import` block](https://developer.hashicorp.com/terraform/language/import) to import Lambda Layer Permissions using `layer_name`, `version_number` and `statement_id`, separated by a comma (`,`). For example: ```python # DO NOT EDIT. Code generated by 'cdktf convert' - Please report bugs at https://cdk.tf/bug @@ -175,13 +175,13 @@ from imports.aws.lambda_layer_version_permission import LambdaLayerVersionPermis class MyConvertedCode(TerraformStack): def __init__(self, scope, name): super().__init__(scope, name) - LambdaLayerVersionPermission.generate_config_for_import(self, "example", "arn:aws:lambda:us-west-2:123456789012:layer:shared_utilities,1") + LambdaLayerVersionPermission.generate_config_for_import(self, "example", "arn:aws:lambda:us-west-2:123456789012:layer:shared_utilities,1,statement1") ``` For backwards compatibility, the following legacy `terraform import` command is also supported: ```console -% terraform import aws_lambda_layer_version_permission.example arn:aws:lambda:us-west-2:123456789012:layer:shared_utilities,1 +% terraform import aws_lambda_layer_version_permission.example arn:aws:lambda:us-west-2:123456789012:layer:shared_utilities,1,statement1 ``` - \ No newline at end of file + diff --git a/website/docs/cdktf/typescript/r/lambda_layer_version_permission.html.markdown b/website/docs/cdktf/typescript/r/lambda_layer_version_permission.html.markdown index 4753fa6e3517..0e98d29f98b1 100644 --- a/website/docs/cdktf/typescript/r/lambda_layer_version_permission.html.markdown +++ b/website/docs/cdktf/typescript/r/lambda_layer_version_permission.html.markdown @@ -173,7 +173,7 @@ This resource exports the following attributes in addition to the arguments abov ## Import -In Terraform v1.5.0 and later, use an [`import` block](https://developer.hashicorp.com/terraform/language/import) to import Lambda Layer Permissions using `layerName` and `versionNumber`, separated by a comma (`,`). For example: +In Terraform v1.5.0 and later, use an [`import` block](https://developer.hashicorp.com/terraform/language/import) to import Lambda Layer Permissions using `layerName`, `versionNumber` and `statementId`, separated by a comma (`,`). For example: ```typescript // DO NOT EDIT. Code generated by 'cdktf convert' - Please report bugs at https://cdk.tf/bug @@ -190,7 +190,7 @@ class MyConvertedCode extends TerraformStack { LambdaLayerVersionPermission.generateConfigForImport( this, "example", - "arn:aws:lambda:us-west-2:123456789012:layer:shared_utilities,1" + "arn:aws:lambda:us-west-2:123456789012:layer:shared_utilities,1,statement1" ); } } @@ -200,7 +200,7 @@ class MyConvertedCode extends TerraformStack { For backwards compatibility, the following legacy `terraform import` command is also supported: ```console -% terraform import aws_lambda_layer_version_permission.example arn:aws:lambda:us-west-2:123456789012:layer:shared_utilities,1 +% terraform import aws_lambda_layer_version_permission.example arn:aws:lambda:us-west-2:123456789012:layer:shared_utilities,1,statement1 ``` - \ No newline at end of file + diff --git a/website/docs/r/lambda_layer_version_permission.html.markdown b/website/docs/r/lambda_layer_version_permission.html.markdown index a18d80c70c63..50f16eb3a27d 100644 --- a/website/docs/r/lambda_layer_version_permission.html.markdown +++ b/website/docs/r/lambda_layer_version_permission.html.markdown @@ -117,17 +117,17 @@ This resource exports the following attributes in addition to the arguments abov ## Import -In Terraform v1.5.0 and later, use an [`import` block](https://developer.hashicorp.com/terraform/language/import) to import Lambda Layer Permissions using `layer_name` and `version_number`, separated by a comma (`,`). For example: +In Terraform v1.5.0 and later, use an [`import` block](https://developer.hashicorp.com/terraform/language/import) to import Lambda Layer Permissions using `layer_name`, `version_number` and `statement_id`, separated by a comma (`,`). For example: ```terraform import { to = aws_lambda_layer_version_permission.example - id = "arn:aws:lambda:us-west-2:123456789012:layer:shared_utilities,1" + id = "arn:aws:lambda:us-west-2:123456789012:layer:shared_utilities,1,statement1" } ``` For backwards compatibility, the following legacy `terraform import` command is also supported: ```console -% terraform import aws_lambda_layer_version_permission.example arn:aws:lambda:us-west-2:123456789012:layer:shared_utilities,1 +% terraform import aws_lambda_layer_version_permission.example arn:aws:lambda:us-west-2:123456789012:layer:shared_utilities,1,statement1 ``` From 8a4d682e76f98c2c2c91ce06b6450e2a7343b31b Mon Sep 17 00:00:00 2001 From: Luke Davison Date: Wed, 15 Oct 2025 18:58:57 +0100 Subject: [PATCH 3/4] Remove changes to debug message left over from previous iterations. --- internal/service/lambda/layer_version_permission.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/service/lambda/layer_version_permission.go b/internal/service/lambda/layer_version_permission.go index b1416e17c80e..28e29635e002 100644 --- a/internal/service/lambda/layer_version_permission.go +++ b/internal/service/lambda/layer_version_permission.go @@ -141,7 +141,7 @@ func resourceLayerVersionPermissionRead(ctx context.Context, d *schema.ResourceD policy, statement, err := findLayerVersionPermissionByThreePartKey(ctx, conn, layerName, versionNumber, statementId) if !d.IsNewResource() && tfresource.NotFound(err) { - log.Printf("[WARN] Lambda Layer Version Permission (%s, %s) not found, removing from state", d.Id(), statementId) + log.Printf("[WARN] Lambda Layer Version Permission (%s) not found, removing from state", d.Id()) d.SetId("") return diags } From b8915ca3f786e5b6289a027267a5a48ffd5ed534 Mon Sep 17 00:00:00 2001 From: Luke Davison Date: Wed, 15 Oct 2025 19:46:13 +0100 Subject: [PATCH 4/4] Rename changelog file due to new PR. --- .changelog/{44661.txt => 44668.txt} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .changelog/{44661.txt => 44668.txt} (100%) diff --git a/.changelog/44661.txt b/.changelog/44668.txt similarity index 100% rename from .changelog/44661.txt rename to .changelog/44668.txt