diff --git a/.changelog/45170.txt b/.changelog/45170.txt new file mode 100644 index 000000000000..fc0c3644c521 --- /dev/null +++ b/.changelog/45170.txt @@ -0,0 +1,19 @@ +```release-note:enhancement +resource/aws_lambda_function: Add `tenancy_config` argument +``` + +```release-note:enhancement +data-source/aws_lambda_function: Add `tenancy_config` attribute +``` + +```release-note:enhancement +resource/aws_lambda_invocation: Add `tenant_id` argument +``` + +```release-note:enhancement +data-source/aws_lambda_invocation: Add `tenant_id` argument +``` + +```release-note:enhancement +action/aws_lambda_invoke: Add `tenant_id` argument +``` \ No newline at end of file diff --git a/internal/service/lambda/function.go b/internal/service/lambda/function.go index 2d590d6f048b..5bbb05a39d0c 100644 --- a/internal/service/lambda/function.go +++ b/internal/service/lambda/function.go @@ -398,6 +398,21 @@ func resourceFunction() *schema.Resource { }, names.AttrTags: tftags.TagsSchema(), names.AttrTagsAll: tftags.TagsSchemaComputed(), + "tenancy_config": { + Type: schema.TypeList, + MaxItems: 1, + Optional: true, + ForceNew: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "tenant_isolation_mode": { + Type: schema.TypeString, + Required: true, + ValidateDiagFunc: enum.Validate[awstypes.TenantIsolationMode](), + }, + }, + }, + }, names.AttrTimeout: { Type: schema.TypeInt, Optional: true, @@ -585,6 +600,12 @@ func resourceFunctionCreate(ctx context.Context, d *schema.ResourceData, meta an input.Code.SourceKMSKeyArn = aws.String(v.(string)) } + if v, ok := d.GetOk("tenancy_config"); ok && len(v.([]any)) > 0 && v.([]any)[0] != nil { + input.TenancyConfig = &awstypes.TenancyConfig{ + TenantIsolationMode: awstypes.TenantIsolationMode(v.([]any)[0].(map[string]any)["tenant_isolation_mode"].(string)), + } + } + if v, ok := d.GetOk("tracing_config"); ok && len(v.([]any)) > 0 && v.([]any)[0] != nil { input.TracingConfig = &awstypes.TracingConfig{ Mode: awstypes.TracingMode(v.([]any)[0].(map[string]any)[names.AttrMode].(string)), @@ -752,6 +773,15 @@ func resourceFunctionRead(ctx context.Context, d *schema.ResourceData, meta any) }); err != nil { return sdkdiag.AppendErrorf(diags, "setting tracing_config: %s", err) } + if function.TenancyConfig != nil { + if err := d.Set("tenancy_config", []any{ + map[string]any{ + "tenant_isolation_mode": string(function.TenancyConfig.TenantIsolationMode), + }, + }); err != nil { + return sdkdiag.AppendErrorf(diags, "setting tenancy_config: %s", err) + } + } if err := d.Set(names.AttrVPCConfig, flattenVPCConfigResponse(function.VpcConfig)); err != nil { return sdkdiag.AppendErrorf(diags, "setting vpc_config: %s", err) } diff --git a/internal/service/lambda/function_data_source.go b/internal/service/lambda/function_data_source.go index 82d3f2e71e41..dfe3fa87a319 100644 --- a/internal/service/lambda/function_data_source.go +++ b/internal/service/lambda/function_data_source.go @@ -209,6 +209,18 @@ func dataSourceFunction() *schema.Resource { Type: schema.TypeInt, Computed: true, }, + "tenancy_config": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "tenant_isolation_mode": { + Type: schema.TypeString, + Computed: true, + }, + }, + }, + }, "tracing_config": { Type: schema.TypeList, Computed: true, @@ -365,6 +377,15 @@ func dataSourceFunctionRead(ctx context.Context, d *schema.ResourceData, meta an d.Set("source_code_size", function.CodeSize) d.Set("source_kms_key_arn", functionCode.SourceKMSKeyArn) d.Set(names.AttrTimeout, function.Timeout) + if function.TenancyConfig != nil { + if err := d.Set("tenancy_config", []any{ + map[string]any{ + "tenant_isolation_mode": string(function.TenancyConfig.TenantIsolationMode), + }, + }); err != nil { + return sdkdiag.AppendErrorf(diags, "setting tenancy_config: %s", err) + } + } tracingConfigMode := awstypes.TracingModePassThrough if function.TracingConfig != nil { tracingConfigMode = function.TracingConfig.Mode diff --git a/internal/service/lambda/function_data_source_test.go b/internal/service/lambda/function_data_source_test.go index b98bdcc8a708..fef211dfc576 100644 --- a/internal/service/lambda/function_data_source_test.go +++ b/internal/service/lambda/function_data_source_test.go @@ -398,6 +398,29 @@ func TestAccLambdaFunctionDataSource_loggingConfig(t *testing.T) { }) } +func TestAccLambdaFunctionDataSource_tenancyConfig(t *testing.T) { + ctx := acctest.Context(t) + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + dataSourceName := "data.aws_lambda_function.test" + resourceName := "aws_lambda_function.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.LambdaServiceID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + Steps: []resource.TestStep{ + { + Config: testAccFunctionDataSourceConfig_tenancyConfig(rName), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttrPair(dataSourceName, names.AttrARN, resourceName, names.AttrARN), + resource.TestCheckResourceAttrPair(dataSourceName, "tenancy_config.#", resourceName, "tenancy_config.#"), + resource.TestCheckResourceAttrPair(dataSourceName, "tenancy_config.0.tenant_isolation_mode", resourceName, "tenancy_config.0.tenant_isolation_mode"), + ), + }, + }, + }) +} + func testAccImageLatestPreCheck(t *testing.T) { if os.Getenv("AWS_LAMBDA_IMAGE_LATEST_ID") == "" { t.Skip("AWS_LAMBDA_IMAGE_LATEST_ID env var must be set for Lambda Function Data Source Image Support acceptance tests.") @@ -850,3 +873,23 @@ data "aws_lambda_function" "test" { } `, rName, rName+"_custom")) } + +func testAccFunctionDataSourceConfig_tenancyConfig(rName string) string { + return acctest.ConfigCompose(testAccFunctionDataSourceConfig_base(rName), fmt.Sprintf(` +resource "aws_lambda_function" "test" { + filename = "test-fixtures/lambdatest.zip" + function_name = %[1]q + role = aws_iam_role.lambda.arn + handler = "exports.example" + runtime = "nodejs20.x" + + tenancy_config { + tenant_isolation_mode = "PER_TENANT" + } +} + +data "aws_lambda_function" "test" { + function_name = aws_lambda_function.test.function_name +} +`, rName)) +} diff --git a/internal/service/lambda/function_test.go b/internal/service/lambda/function_test.go index b4b6624f6664..6419ae3caa26 100644 --- a/internal/service/lambda/function_test.go +++ b/internal/service/lambda/function_test.go @@ -2410,6 +2410,72 @@ func TestAccLambdaFunction_sourceKMSKeyARN(t *testing.T) { }) } +func TestAccLambdaFunction_tenancyConfig(t *testing.T) { + ctx := acctest.Context(t) + var conf lambda.GetFunctionOutput + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_lambda_function.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.LambdaServiceID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckFunctionDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccFunctionConfig_tenancyConfig(rName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckFunctionExists(ctx, resourceName, &conf), + resource.TestCheckResourceAttr(resourceName, "tenancy_config.#", "1"), + resource.TestCheckResourceAttr(resourceName, "tenancy_config.0.tenant_isolation_mode", "PER_TENANT"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"filename", "publish"}, + }, + }, + }) +} + +func TestAccLambdaFunction_tenancyConfigForceNew(t *testing.T) { + ctx := acctest.Context(t) + var conf lambda.GetFunctionOutput + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_lambda_function.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.LambdaServiceID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckFunctionDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccFunctionConfig_basic(rName, rName, rName, rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckFunctionExists(ctx, resourceName, &conf), + resource.TestCheckResourceAttr(resourceName, "tenancy_config.#", "0"), + ), + }, + { + Config: testAccFunctionConfig_tenancyConfig(rName), + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectResourceAction(resourceName, plancheck.ResourceActionReplace), + }, + }, + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckFunctionExists(ctx, resourceName, &conf), + resource.TestCheckResourceAttr(resourceName, "tenancy_config.#", "1"), + resource.TestCheckResourceAttr(resourceName, "tenancy_config.0.tenant_isolation_mode", "PER_TENANT"), + ), + }, + }, + }) +} + func TestAccLambdaFunction_resetNonRefreshableAttributesAfterUpdateFailure(t *testing.T) { ctx := acctest.Context(t) var conf lambda.GetFunctionOutput @@ -4364,6 +4430,24 @@ resource "aws_lambda_function" "test" { `, rName, kmsIdentifier)) } +func testAccFunctionConfig_tenancyConfig(rName string) string { + return acctest.ConfigCompose( + acctest.ConfigLambdaBase(rName, rName, rName), + fmt.Sprintf(` +resource "aws_lambda_function" "test" { + filename = "test-fixtures/lambdatest.zip" + function_name = %[1]q + role = aws_iam_role.iam_for_lambda.arn + handler = "exports.example" + runtime = "nodejs20.x" + + tenancy_config { + tenant_isolation_mode = "PER_TENANT" + } +} +`, rName)) +} + func testAccFunctionConfig_skipDestroy(rName string) string { return acctest.ConfigCompose(acctest.ConfigLambdaBase(rName, rName, rName), fmt.Sprintf(` resource "aws_lambda_function" "test" { diff --git a/internal/service/lambda/invocation.go b/internal/service/lambda/invocation.go index d13e63c8b8c4..b1e795a13fa4 100644 --- a/internal/service/lambda/invocation.go +++ b/internal/service/lambda/invocation.go @@ -88,6 +88,10 @@ func resourceInvocation() *schema.Resource { Type: schema.TypeString, Computed: true, }, + "tenant_id": { + Type: schema.TypeString, + Optional: true, + }, "terraform_key": { Type: schema.TypeString, Optional: true, @@ -213,6 +217,9 @@ func invoke(ctx context.Context, conn *lambda.Client, d *schema.ResourceData, ac Payload: payload, Qualifier: aws.String(qualifier), } + if v, ok := d.GetOk("tenant_id"); ok { + input.TenantId = aws.String(v.(string)) + } output, err := conn.Invoke(ctx, input) diff --git a/internal/service/lambda/invocation_data_source.go b/internal/service/lambda/invocation_data_source.go index 2b848bc336d5..2eb061311147 100644 --- a/internal/service/lambda/invocation_data_source.go +++ b/internal/service/lambda/invocation_data_source.go @@ -42,6 +42,10 @@ func dataSourceInvocation() *schema.Resource { Type: schema.TypeString, Computed: true, }, + "tenant_id": { + Type: schema.TypeString, + Optional: true, + }, }, } } @@ -61,6 +65,10 @@ func dataSourceInvocationRead(ctx context.Context, d *schema.ResourceData, meta Qualifier: aws.String(qualifier), } + if v, ok := d.GetOk("tenant_id"); ok { + input.TenantId = aws.String(v.(string)) + } + output, err := conn.Invoke(ctx, input) if err != nil { diff --git a/internal/service/lambda/invocation_data_source_test.go b/internal/service/lambda/invocation_data_source_test.go index adf622ec172e..0b38207ed9e8 100644 --- a/internal/service/lambda/invocation_data_source_test.go +++ b/internal/service/lambda/invocation_data_source_test.go @@ -100,6 +100,26 @@ func TestAccLambdaInvocationDataSource_complex(t *testing.T) { }) } +func TestAccLambdaInvocationDataSource_tenantId(t *testing.T) { + ctx := acctest.Context(t) + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + testData := "value3" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.LambdaServiceID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + Steps: []resource.TestStep{ + { + Config: testAccInvocationDataSourceConfig_tenantId(rName, testData), + Check: resource.ComposeTestCheckFunc( + testAccCheckInvocationResult("data.aws_lambda_invocation.invocation_test", `{"key1":"value1","key2":"value2","key3":"`+testData+`"}`), + ), + }, + }, + }) +} + func testAccInvocationDataSource_base_config(roleName string) string { return fmt.Sprintf(` data "aws_iam_policy_document" "lambda_assume_role_policy" { @@ -230,3 +250,37 @@ JSON } `, rName, testData) } + +func testAccInvocationDataSourceConfig_tenantId(rName, testData string) string { + return fmt.Sprintf(testAccInvocationDataSource_base_config(rName)+` +resource "aws_lambda_function" "lambda" { + depends_on = [aws_iam_role_policy_attachment.lambda_role_policy] + + filename = "test-fixtures/lambda_invocation.zip" + function_name = "%s" + role = aws_iam_role.lambda_role.arn + handler = "lambda_invocation.handler" + runtime = "nodejs20.x" + tenancy_config { + tenant_isolation_mode = "PER_TENANT" + } + + environment { + variables = { + TEST_DATA = "%s" + } + } +} + +data "aws_lambda_invocation" "invocation_test" { + function_name = aws_lambda_function.lambda.function_name + tenant_id = "tenant-1" + input = <