diff --git a/.changelog/45359.txt b/.changelog/45359.txt new file mode 100644 index 00000000000..3cf990bf9c5 --- /dev/null +++ b/.changelog/45359.txt @@ -0,0 +1,7 @@ +```release-note:enhancement +resource/aws_lambda_function: Add `durable_config` argument +``` + +```release-note:enhancement +data-source/aws_lambda_function: Add `durable_config` attribute +``` \ No newline at end of file diff --git a/internal/service/lambda/function.go b/internal/service/lambda/function.go index e0c627f6d4f..2cfebb169d0 100644 --- a/internal/service/lambda/function.go +++ b/internal/service/lambda/function.go @@ -149,6 +149,26 @@ func resourceFunction() *schema.Resource { Type: schema.TypeString, Optional: true, }, + "durable_config": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "execution_timeout": { + Type: schema.TypeInt, + Required: true, + ValidateFunc: validation.IntBetween(1, 31622400), + }, + names.AttrRetentionPeriod: { + Type: schema.TypeInt, + Optional: true, + Default: 14, + ValidateFunc: validation.IntBetween(1, 90), + }, + }, + }, + }, names.AttrEnvironment: { Type: schema.TypeList, Optional: true, @@ -528,6 +548,13 @@ func resourceFunction() *schema.Resource { CustomizeDiff: customdiff.Sequence( checkHandlerRuntimeForZipFunction, updateComputedAttributesOnPublish, + customdiff.ForceNewIfChange("durable_config", func(_ context.Context, old, new, meta any) bool { + // Force new when durable_config is being added (from empty to non-empty) or removed (from non-empty to empty) + // Allow updates to execution_timeout and retention_period when durable_config already exists + oldLen := len(old.([]any)) + newLen := len(new.([]any)) + return (oldLen == 0 && newLen > 0) || (oldLen > 0 && newLen == 0) + }), ), } } @@ -595,6 +622,10 @@ func resourceFunctionCreate(ctx context.Context, d *schema.ResourceData, meta an } } + if v, ok := d.GetOk("durable_config"); ok && len(v.([]any)) > 0 { + input.DurableConfig = expandDurableConfigs(v.([]any)) + } + if v, ok := d.GetOk(names.AttrEnvironment); ok && len(v.([]any)) > 0 && v.([]any)[0] != nil { if v, ok := v.([]any)[0].(map[string]any)["variables"].(map[string]any); ok && len(v) > 0 { input.Environment = &awstypes.Environment{ @@ -765,6 +796,13 @@ func resourceFunctionRead(ctx context.Context, d *schema.ResourceData, meta any) } else { d.Set("dead_letter_config", []any{}) } + if function.DurableConfig != nil { + if err := d.Set("durable_config", flattenDurableConfig(function.DurableConfig)); err != nil { + return sdkdiag.AppendErrorf(diags, "setting durable_config: %s", err) + } + } else { + d.Set("durable_config", []any{}) + } d.Set(names.AttrDescription, function.Description) if err := d.Set(names.AttrEnvironment, flattenEnvironment(function.Environment)); err != nil { return sdkdiag.AppendErrorf(diags, "setting environment: %s", err) @@ -951,6 +989,12 @@ func resourceFunctionUpdate(ctx context.Context, d *schema.ResourceData, meta an } } + if d.HasChange("durable_config") { + if v, ok := d.GetOk("durable_config"); ok && len(v.([]any)) > 0 { + input.DurableConfig = expandDurableConfigs(v.([]any)) + } + } + if d.HasChange(names.AttrDescription) { input.Description = aws.String(d.Get(names.AttrDescription).(string)) } @@ -1190,6 +1234,13 @@ func resourceFunctionDelete(ctx context.Context, d *schema.ResourceData, meta an } } + // Stop any running durable executions before deleting the function + if v, ok := d.GetOk("durable_config"); ok && len(v.([]any)) > 0 { + if err := stopDurableExecutions(ctx, conn, d.Id(), d.Timeout(schema.TimeoutDelete)); err != nil { + return sdkdiag.AppendErrorf(diags, "stopping durable executions for Lambda Function (%s): %s", d.Id(), err) + } + } + log.Printf("[INFO] Deleting Lambda Function: %s", d.Id()) input := lambda.DeleteFunctionInput{ FunctionName: aws.String(d.Id()), @@ -1206,9 +1257,102 @@ func resourceFunctionDelete(ctx context.Context, d *schema.ResourceData, meta an return sdkdiag.AppendErrorf(diags, "deleting Lambda Function (%s): %s", d.Id(), err) } + if _, err := waitFunctionDeleted(ctx, conn, d.Id(), d.Timeout(schema.TimeoutDelete)); err != nil { + return sdkdiag.AppendErrorf(diags, "waiting for Lambda Function (%s) delete: %s", d.Id(), err) + } + return diags } +func stopDurableExecutions(ctx context.Context, conn *lambda.Client, functionName string, timeout time.Duration) error { + input := &lambda.ListDurableExecutionsByFunctionInput{ + FunctionName: aws.String(functionName), + Statuses: []awstypes.ExecutionStatus{awstypes.ExecutionStatusRunning}, + } + + paginator := lambda.NewListDurableExecutionsByFunctionPaginator(conn, input) + for paginator.HasMorePages() { + page, err := paginator.NextPage(ctx) + if err != nil { + return err + } + + for _, execution := range page.DurableExecutions { + _, err := conn.StopDurableExecution(ctx, &lambda.StopDurableExecutionInput{ + DurableExecutionArn: execution.DurableExecutionArn, + }) + if err != nil { + return err + } + + if _, err := waitDurableExecutionStopped(ctx, conn, aws.ToString(execution.DurableExecutionArn), timeout); err != nil { + return fmt.Errorf("waiting for durable execution (%s) to stop: %w", aws.ToString(execution.DurableExecutionArn), err) + } + } + } + + return nil +} + +func findDurableExecution(ctx context.Context, conn *lambda.Client, arn string) (*lambda.GetDurableExecutionOutput, error) { + input := &lambda.GetDurableExecutionInput{ + DurableExecutionArn: aws.String(arn), + } + + output, err := conn.GetDurableExecution(ctx, input) + + if errs.IsA[*awstypes.ResourceNotFoundException](err) { + return nil, &retry.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return nil, err + } + + if output == nil { + return nil, tfresource.NewEmptyResultError(input) + } + + return output, nil +} + +func statusDurableExecution(ctx context.Context, conn *lambda.Client, arn string) retry.StateRefreshFunc { + return func() (any, string, error) { + output, err := findDurableExecution(ctx, conn, arn) + + if tfresource.NotFound(err) { + return nil, "", nil + } + + if err != nil { + return nil, "", err + } + + return output, string(output.Status), nil + } +} + +func waitDurableExecutionStopped(ctx context.Context, conn *lambda.Client, arn string, timeout time.Duration) (*lambda.GetDurableExecutionOutput, error) { + stateConf := &retry.StateChangeConf{ + Pending: enum.Slice(awstypes.ExecutionStatusRunning), + Target: enum.Slice(awstypes.ExecutionStatusStopped), + Refresh: statusDurableExecution(ctx, conn, arn), + Timeout: timeout, + Delay: 2 * time.Second, + } + + outputRaw, err := stateConf.WaitForStateContext(ctx) + + if output, ok := outputRaw.(*lambda.GetDurableExecutionOutput); ok { + return output, err + } + + return nil, err +} + func findFunctionByName(ctx context.Context, conn *lambda.Client, name string) (*lambda.GetFunctionOutput, error) { input := lambda.GetFunctionInput{ FunctionName: aws.String(name), @@ -1503,6 +1647,24 @@ func waitFunctionConfigurationUpdated(ctx context.Context, conn *lambda.Client, return nil, err } +func waitFunctionDeleted(ctx context.Context, conn *lambda.Client, name string, timeout time.Duration) (*lambda.GetFunctionOutput, error) { + stateConf := &retry.StateChangeConf{ + Pending: enum.Slice(awstypes.StateActive, awstypes.StateActiveNonInvocable, awstypes.StatePending, awstypes.StateInactive, awstypes.StateFailed, awstypes.StateDeleting), + Target: []string{}, + Refresh: statusFunctionState(ctx, conn, name), + Timeout: timeout, + Delay: 5 * time.Second, + } + + outputRaw, err := stateConf.WaitForStateContext(ctx) + + if output, ok := outputRaw.(*awstypes.FunctionConfiguration); ok { + return &lambda.GetFunctionOutput{Configuration: output}, err + } + + return nil, err +} + // retryFunctionOp retries a Lambda Function Create or Update operation. // It handles IAM eventual consistency and EC2 throttling. type functionCU interface { @@ -1627,6 +1789,7 @@ func needsFunctionConfigUpdate(d sdkv2.ResourceDiffer) bool { d.HasChange(names.AttrKMSKeyARN) || d.HasChange("layers") || d.HasChange("dead_letter_config") || + d.HasChange("durable_config") || d.HasChange("snap_start") || d.HasChange("tracing_config") || d.HasChange("vpc_config.0.ipv6_allowed_for_dual_stack") || @@ -1776,6 +1939,40 @@ func expandFileSystemConfigs(tfList []any) []awstypes.FileSystemConfig { return apiObjects } +func expandDurableConfigs(tfList []any) *awstypes.DurableConfig { + if len(tfList) == 0 || tfList[0] == nil { + return nil + } + + tfMap := tfList[0].(map[string]any) + return &awstypes.DurableConfig{ + ExecutionTimeout: aws.Int32(int32(tfMap["execution_timeout"].(int))), + RetentionPeriodInDays: aws.Int32(int32(tfMap[names.AttrRetentionPeriod].(int))), + } +} + +func flattenDurableConfig(apiObject *awstypes.DurableConfig) []any { + if apiObject == nil { + return nil + } + + tfMap := map[string]any{} + + if v := apiObject.ExecutionTimeout; v != nil { + tfMap["execution_timeout"] = aws.ToInt32(v) + } + + if v := apiObject.RetentionPeriodInDays; v != nil { + tfMap[names.AttrRetentionPeriod] = aws.ToInt32(v) + } + + if len(tfMap) == 0 { + return nil + } + + return []any{tfMap} +} + func flattenImageConfig(apiObject *awstypes.ImageConfigResponse) []any { tfMap := make(map[string]any) diff --git a/internal/service/lambda/function_data_source.go b/internal/service/lambda/function_data_source.go index bd8e859fae7..380aa000680 100644 --- a/internal/service/lambda/function_data_source.go +++ b/internal/service/lambda/function_data_source.go @@ -89,6 +89,22 @@ func dataSourceFunction() *schema.Resource { Type: schema.TypeString, Computed: true, }, + "durable_config": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "execution_timeout": { + Type: schema.TypeInt, + Computed: true, + }, + names.AttrRetentionPeriod: { + Type: schema.TypeInt, + Computed: true, + }, + }, + }, + }, names.AttrEnvironment: { Type: schema.TypeList, Computed: true, @@ -370,6 +386,11 @@ func dataSourceFunctionRead(ctx context.Context, d *schema.ResourceData, meta an d.Set("dead_letter_config", []any{}) } d.Set(names.AttrDescription, function.Description) + if function.DurableConfig != nil { + if err := d.Set("durable_config", flattenDurableConfig(function.DurableConfig)); err != nil { + return sdkdiag.AppendErrorf(diags, "setting durable_config: %s", err) + } + } if err := d.Set(names.AttrEnvironment, flattenEnvironment(function.Environment)); err != nil { return sdkdiag.AppendErrorf(diags, "setting environment: %s", err) } diff --git a/internal/service/lambda/function_data_source_test.go b/internal/service/lambda/function_data_source_test.go index a2a54d3b2b8..cbe3b76f6e0 100644 --- a/internal/service/lambda/function_data_source_test.go +++ b/internal/service/lambda/function_data_source_test.go @@ -421,6 +421,30 @@ func TestAccLambdaFunctionDataSource_tenancyConfig(t *testing.T) { }) } +func TestAccLambdaFunctionDataSource_durableConfig(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_durableConfig(rName), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttrPair(dataSourceName, names.AttrARN, resourceName, names.AttrARN), + resource.TestCheckResourceAttrPair(dataSourceName, "durable_config.#", resourceName, "durable_config.#"), + resource.TestCheckResourceAttrPair(dataSourceName, "durable_config.0.execution_timeout", resourceName, "durable_config.0.execution_timeout"), + resource.TestCheckResourceAttrPair(dataSourceName, "durable_config.0.retention_period", resourceName, "durable_config.0.retention_period"), + ), + }, + }, + }) +} + func TestAccLambdaFunctionDataSource_capacityProvider(t *testing.T) { ctx := acctest.Context(t) rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) @@ -944,3 +968,22 @@ data "aws_lambda_function" "test" { } `, rName)) } + +func testAccFunctionDataSourceConfig_durableConfig(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 + handler = "exports.example" + role = aws_iam_role.lambda.arn + runtime = "nodejs22.x" + durable_config { + execution_timeout = 300 + retention_period = 7 + } +} +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 1ceb3486ed1..d1570f87bf3 100644 --- a/internal/service/lambda/function_test.go +++ b/internal/service/lambda/function_test.go @@ -2476,6 +2476,98 @@ func TestAccLambdaFunction_tenancyConfigForceNew(t *testing.T) { }) } +func TestAccLambdaFunction_durableConfig(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_durableConfig(rName, "", 300, 7), + Check: resource.ComposeTestCheckFunc( + testAccCheckFunctionExists(ctx, resourceName, &conf), + resource.TestCheckResourceAttr(resourceName, "durable_config.#", "1"), + resource.TestCheckResourceAttr(resourceName, "durable_config.0.execution_timeout", "300"), + resource.TestCheckResourceAttr(resourceName, "durable_config.0.retention_period", "7"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"filename", "publish"}, + }, + { + Config: testAccFunctionConfig_durableConfig(rName, "Updated description", 300, 7), + Check: resource.ComposeTestCheckFunc( + testAccCheckFunctionExists(ctx, resourceName, &conf), + resource.TestCheckResourceAttr(resourceName, "durable_config.#", "1"), + resource.TestCheckResourceAttr(resourceName, "durable_config.0.execution_timeout", "300"), + resource.TestCheckResourceAttr(resourceName, "durable_config.0.retention_period", "7"), + resource.TestCheckResourceAttr(resourceName, names.AttrDescription, "Updated description"), + ), + }, + { + Config: testAccFunctionConfig_durableConfig(rName, "Updated description", 600, 14), + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectResourceAction(resourceName, plancheck.ResourceActionUpdate), + }, + }, + Check: resource.ComposeTestCheckFunc( + testAccCheckFunctionExists(ctx, resourceName, &conf), + resource.TestCheckResourceAttr(resourceName, "durable_config.#", "1"), + resource.TestCheckResourceAttr(resourceName, "durable_config.0.execution_timeout", "600"), + resource.TestCheckResourceAttr(resourceName, "durable_config.0.retention_period", "14"), + ), + }, + }, + }) +} + +func TestAccLambdaFunction_durableConfigForceNew(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, "durable_config.#", "0"), + ), + }, + { + Config: testAccFunctionConfig_durableConfig(rName, "", 300, 7), + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectResourceAction(resourceName, plancheck.ResourceActionReplace), + }, + }, + Check: resource.ComposeTestCheckFunc( + testAccCheckFunctionExists(ctx, resourceName, &conf), + resource.TestCheckResourceAttr(resourceName, "durable_config.#", "1"), + resource.TestCheckResourceAttr(resourceName, "durable_config.0.execution_timeout", "300"), + resource.TestCheckResourceAttr(resourceName, "durable_config.0.retention_period", "7"), + ), + }, + }, + }) +} + func TestAccLambdaFunction_resetNonRefreshableAttributesAfterUpdateFailure(t *testing.T) { ctx := acctest.Context(t) var conf lambda.GetFunctionOutput @@ -4540,6 +4632,34 @@ resource "aws_lambda_function" "test" { `, rName, zipFileS3, zipFileLambda)) } +func testAccFunctionConfig_durableConfig(rName, description string, executionTimeout, retentionPeriod int) string { + descriptionLine := "" + if description != "" { + descriptionLine = fmt.Sprintf(" description = %q", description) + } + + 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 = "nodejs22.x" +%[2]s + durable_config { + execution_timeout = %[3]d + retention_period = %[4]d + } + + timeouts { + delete = "60m" + } +} +`, rName, descriptionLine, executionTimeout, retentionPeriod)) +} + func testAccPreCheckSignerSigningProfile(ctx context.Context, t *testing.T, platformID string) { conn := acctest.Provider.Meta().(*conns.AWSClient).SignerClient(ctx) diff --git a/website/docs/d/lambda_function.html.markdown b/website/docs/d/lambda_function.html.markdown index b3593f064e6..dc515347c8e 100644 --- a/website/docs/d/lambda_function.html.markdown +++ b/website/docs/d/lambda_function.html.markdown @@ -73,6 +73,15 @@ resource "aws_lambda_function" "example" { environment { variables = data.aws_lambda_function.reference.environment[0].variables } + + # Copy durable configuration if present + dynamic "durable_config" { + for_each = data.aws_lambda_function.reference.durable_config + content { + execution_timeout = durable_config.value.execution_timeout + retention_period = durable_config.value.retention_period + } + } } ``` @@ -101,6 +110,22 @@ output "version_comparison" { } ``` +### Accessing Durable Configuration + +```terraform +data "aws_lambda_function" "durable_function" { + function_name = "my-durable-function" +} +# Output durable configuration details +output "durable_settings" { + value = { + has_durable_config = length(data.aws_lambda_function.durable_function.durable_config) > 0 + execution_timeout = length(data.aws_lambda_function.durable_function.durable_config) > 0 ? data.aws_lambda_function.durable_function.durable_config[0].execution_timeout : null + retention_period = length(data.aws_lambda_function.durable_function.durable_config) > 0 ? data.aws_lambda_function.durable_function.durable_config[0].retention_period : null + } +} +``` + ## Argument Reference The following arguments are required: @@ -123,6 +148,7 @@ This data source exports the following attributes in addition to the arguments a * `code_signing_config_arn` - ARN for a Code Signing Configuration. * `dead_letter_config` - Configuration for the function's dead letter queue. [See below](#dead_letter_config-attribute-reference). * `description` - Description of what your Lambda Function does. +* `durable_config` - Configuration for the function's durable settings. [See below](#durable_config-attribute-reference). * `environment` - Lambda environment's configuration settings. [See below](#environment-attribute-reference). * `ephemeral_storage` - Amount of ephemeral storage (`/tmp`) allocated for the Lambda Function. [See below](#ephemeral_storage-attribute-reference). * `file_system_config` - Connection settings for an Amazon EFS file system. [See below](#file_system_config-attribute-reference). @@ -162,6 +188,11 @@ This data source exports the following attributes in addition to the arguments a * `target_arn` - ARN of an SNS topic or SQS queue to notify when an invocation fails. +### durable_config + +* `execution_timeout` - Maximum execution time in seconds for the durable function. +* `retention_period` - Number of days to retain the function's execution state. + ### environment * `variables` - Map of environment variables that are accessible from the function code during execution. diff --git a/website/docs/r/lambda_function.html.markdown b/website/docs/r/lambda_function.html.markdown index bcd871909e0..14ced16a7e1 100644 --- a/website/docs/r/lambda_function.html.markdown +++ b/website/docs/r/lambda_function.html.markdown @@ -476,6 +476,43 @@ resource "aws_lambda_function" "example" { } ``` +### Function with Durable Configuration + +Stopping durable executions and deleting the Lambda function may take up to `60m`. Use configured `timeouts` as shown below. + +```terraform +resource "aws_lambda_function" "example" { + filename = "function.zip" + function_name = "example_durable_function" + role = aws_iam_role.example.arn + handler = "index.handler" + runtime = "nodejs22.x" + memory_size = 512 + timeout = 30 + + # Durable function configuration for long-running processes + durable_config { + execution_timeout = 3600 # 1 hour maximum execution time + retention_period = 7 # Retain execution state for 7 days + } + + environment { + variables = { + DURABLE_MODE = "enabled" + } + } + + timeouts { + delete = "60m" + } + + tags = { + Environment = "production" + Type = "durable" + } +} +``` + ### Capacity Provider Configuration ```terraform @@ -532,6 +569,7 @@ The following arguments are optional: * `code_signing_config_arn` - (Optional) ARN of a code-signing configuration to enable code signing for this function. * `dead_letter_config` - (Optional) Configuration block for dead letter queue. [See below](#dead_letter_config-configuration-block). * `description` - (Optional) Description of what your Lambda Function does. +* `durable_config` - (Optional) Configuration block for durable function settings. [See below](#durable_config-configuration-block). `durable_config` may only be available in [limited regions](https://builder.aws.com/build/capabilities), including `us-east-2`. * `environment` - (Optional) Configuration block for environment variables. [See below](#environment-configuration-block). * `ephemeral_storage` - (Optional) Amount of ephemeral storage (`/tmp`) to allocate for the Lambda Function. [See below](#ephemeral_storage-configuration-block). * `file_system_config` - (Optional) Configuration block for EFS file system. [See below](#file_system_config-configuration-block). @@ -578,6 +616,13 @@ The following arguments are optional: * `target_arn` - (Required) ARN of an SNS topic or SQS queue to notify when an invocation fails. +### durable_config Configuration Block + +`durable_config` may only be available in [limited regions](https://builder.aws.com/build/capabilities), including `us-east-2`. + +* `execution_timeout` - (Required) Maximum execution time in seconds for the durable function. Valid value between 1 and 31622400 (366 days). +* `retention_period` - (Optional) Number of days to retain the function's execution state. Valid value between 1 and 90. If not specified, the function's execution state is not retained. Defaults to 14. + ### environment Configuration Block * `variables` - (Optional) Map of environment variables available to your Lambda function during execution.