diff --git a/.github/workflows/conformance-tests.yml b/.github/workflows/conformance-tests.yml index 93dad53ca..557f01d02 100644 --- a/.github/workflows/conformance-tests.yml +++ b/.github/workflows/conformance-tests.yml @@ -2,9 +2,10 @@ name: Conformance Tests # Full-integration conformance run: builds the SDK + Java handlers, installs the # pinned language-agnostic runner from PyPI, then deploys + invokes + validates -# one SAM stack per suite. Each suite runs as its own parallel matrix job. To add -# a suite later, add its name to `strategy.matrix.suite` (and ship its -# template_.yaml + handlers under conformance-tests/). +# one SAM stack per suite. Suites are discovered from the module's +# template_.yaml files (see conformance-tests/scripts/discover_suites.py) +# and each runs as its own parallel matrix job, so adding a suite only requires +# shipping its template + handlers under conformance-tests/. on: pull_request: @@ -28,24 +29,30 @@ permissions: id-token: write # Required for AWS OIDC credentials jobs: + discover_suites: + name: discover conformance suites + runs-on: ubuntu-latest + outputs: + suites: ${{ steps.discover.outputs.suites }} + steps: + - name: Checkout repository + uses: actions/checkout@v7 + + - name: Discover suites from templates + id: discover + working-directory: conformance-tests + run: echo "suites=$(python3 scripts/discover_suites.py)" >> "$GITHUB_OUTPUT" + conformance: name: conformance (${{ matrix.suite }}) + needs: discover_suites runs-on: ubuntu-latest env: AWS_REGION: us-west-2 strategy: fail-fast: false matrix: - suite: - - step - - wait - - child - - callback - - invoke - - parallel - - wait_for_callback - - wait_for_condition - - map + suite: ${{ fromJSON(needs.discover_suites.outputs.suites) }} defaults: run: working-directory: conformance-tests diff --git a/conformance-tests/scripts/discover_suites.py b/conformance-tests/scripts/discover_suites.py new file mode 100644 index 000000000..8a307f0b9 --- /dev/null +++ b/conformance-tests/scripts/discover_suites.py @@ -0,0 +1,47 @@ +#!/usr/bin/env python3 +"""Discover conformance suites from template files.""" + +from __future__ import annotations + +import json +from pathlib import Path + + +MODULE_DIR = Path(__file__).resolve().parents[1] +TEMPLATE_PREFIX = "template_" +TEMPLATE_SUFFIX = ".yaml" +SOURCE_ROOT = Path("src") / "main" / "java" + + +def discover_suites(module_dir: Path = MODULE_DIR) -> tuple[str, ...]: + """Return sorted suites with matching templates and non-empty handler packages.""" + templates = sorted(module_dir.glob(f"{TEMPLATE_PREFIX}*{TEMPLATE_SUFFIX}")) + if not templates: + raise SystemExit(f"No {TEMPLATE_PREFIX}{TEMPLATE_SUFFIX} files found") + + suites: list[str] = [] + for template in templates: + suite = template.name[len(TEMPLATE_PREFIX) : -len(TEMPLATE_SUFFIX)] + if not suite: + raise SystemExit(f"Invalid conformance template name: {template.name}") + + handlers_dir = module_dir / SOURCE_ROOT / suite + if not handlers_dir.is_dir(): + raise SystemExit( + f"Template {template.name} has no matching handler package: {handlers_dir}" + ) + + if not list(handlers_dir.glob("*.java")): + raise SystemExit(f"No handler classes found for suite {suite}: {handlers_dir}") + + suites.append(suite) + + return tuple(suites) + + +def main() -> None: + print(json.dumps(discover_suites(), separators=(",", ":"))) + + +if __name__ == "__main__": + main() diff --git a/conformance-tests/src/main/java/plugin/ConformanceLoggingPlugin.java b/conformance-tests/src/main/java/plugin/ConformanceLoggingPlugin.java new file mode 100644 index 000000000..0315128de --- /dev/null +++ b/conformance-tests/src/main/java/plugin/ConformanceLoggingPlugin.java @@ -0,0 +1,93 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +package plugin; + +import software.amazon.lambda.durable.plugin.DurableExecutionPlugin; +import software.amazon.lambda.durable.plugin.InvocationEndInfo; +import software.amazon.lambda.durable.plugin.InvocationInfo; +import software.amazon.lambda.durable.plugin.OperationEndInfo; +import software.amazon.lambda.durable.plugin.OperationInfo; +import software.amazon.lambda.durable.plugin.UserFunctionEndInfo; +import software.amazon.lambda.durable.plugin.UserFunctionStartInfo; + +/** + * Shared instrumentation plugin for the plugin conformance suite. + * + *

Emits lifecycle log lines with a configurable prefix (e.g. {@code CONFPLUGIN}, {@code CONFPLUGIN-A}) so one + * plugin — or two, for the multiple-plugins case — can be registered on a handler. Operation- and attempt-level hooks + * are filtered to step-type operations to match the requirement vocabulary. All lines are emitted from the real SDK + * plugin hooks; nothing is hand-rolled. + */ +@SuppressWarnings("deprecation") +public class ConformanceLoggingPlugin implements DurableExecutionPlugin { + + private final String prefix; + + /** Captured from onInvocationStart; read by later hooks that may run on other threads. */ + private volatile String executionArn; + + public ConformanceLoggingPlugin(String prefix) { + this.prefix = prefix; + } + + private static boolean isStep(String type) { + return "STEP".equals(type); + } + + /** Returns {@code , "durableExecutionArn": ""} when captured, otherwise an empty string. */ + private String arnField() { + return executionArn == null ? "" : String.format(", \"durableExecutionArn\": \"%s\"", executionArn); + } + + @Override + public void onInvocationStart(InvocationInfo info) { + this.executionArn = info.durableExecutionArn(); + System.out.println(String.format( + "{\"plugin\": \"%s\", \"hook\": \"invocation-start\", \"first\": %b%s}", + prefix, info.isFirstInvocation(), arnField())); + } + + @Override + public void onInvocationEnd(InvocationEndInfo info) { + System.out.println(String.format( + "{\"plugin\": \"%s\", \"hook\": \"invocation-end\", \"status\": \"%s\"%s}", + prefix, info.invocationStatus().name(), arnField())); + } + + @Override + public void onOperationStart(OperationInfo info) { + if (isStep(info.type())) { + System.out.println(String.format( + "{\"plugin\": \"%s\", \"hook\": \"operation-start\", \"op\": \"%s\"%s}", + prefix, info.id(), arnField())); + } + } + + @Override + public void onOperationEnd(OperationEndInfo info) { + if (isStep(info.type())) { + System.out.println(String.format( + "{\"plugin\": \"%s\", \"hook\": \"operation-end\", \"op\": \"%s\", \"status\": \"%s\"%s}", + prefix, info.id(), info.status(), arnField())); + } + } + + @Override + public void onUserFunctionStart(UserFunctionStartInfo info) { + if (isStep(info.type()) && info.attempt() != null) { + System.out.println(String.format( + "{\"plugin\": \"%s\", \"hook\": \"attempt-start\", \"n\": %d, \"op\": \"%s\"%s}", + prefix, info.attempt(), info.id(), arnField())); + } + } + + @Override + public void onUserFunctionEnd(UserFunctionEndInfo info) { + if (isStep(info.type()) && info.attempt() != null) { + String outcome = info.succeeded() ? "SUCCEEDED" : "FAILED"; + System.out.println(String.format( + "{\"plugin\": \"%s\", \"hook\": \"attempt-end\", \"n\": %d, \"outcome\": \"%s\", \"op\": \"%s\"%s}", + prefix, info.attempt(), outcome, info.id(), arnField())); + } + } +} diff --git a/conformance-tests/src/main/java/plugin/FaultyConformancePlugin.java b/conformance-tests/src/main/java/plugin/FaultyConformancePlugin.java new file mode 100644 index 000000000..2b5e2306c --- /dev/null +++ b/conformance-tests/src/main/java/plugin/FaultyConformancePlugin.java @@ -0,0 +1,85 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +package plugin; + +import software.amazon.lambda.durable.plugin.DurableExecutionPlugin; +import software.amazon.lambda.durable.plugin.InvocationEndInfo; +import software.amazon.lambda.durable.plugin.InvocationInfo; +import software.amazon.lambda.durable.plugin.OperationEndInfo; +import software.amazon.lambda.durable.plugin.OperationInfo; +import software.amazon.lambda.durable.plugin.UserFunctionEndInfo; +import software.amazon.lambda.durable.plugin.UserFunctionStartInfo; + +/** + * Instrumentation plugin whose every hook logs a line and then throws. + * + *

Used by requirement 10-4 to verify the SDK isolates plugin exceptions: each hook must run (log its line) and the + * thrown exception must be swallowed by the SDK so the execution result and history are identical to running without + * the plugin. Operation- and attempt-level hooks are filtered to step-type operations. + */ +@SuppressWarnings("deprecation") +public class FaultyConformancePlugin implements DurableExecutionPlugin { + + private static boolean isStep(String type) { + return "STEP".equals(type); + } + + /** Captured from onInvocationStart (before the throw); read by later hooks that may run on other threads. */ + private volatile String executionArn; + + /** Returns {@code , "durableExecutionArn": ""} when captured, otherwise an empty string. */ + private String arnField() { + return executionArn == null ? "" : String.format(", \"durableExecutionArn\": \"%s\"", executionArn); + } + + @Override + public void onInvocationStart(InvocationInfo info) { + this.executionArn = info.durableExecutionArn(); + System.out.println(String.format( + "{\"plugin\": \"CONFPLUGIN-FAULTY\", \"hook\": \"invocation-start\"%s}", arnField())); + throw new RuntimeException("faulty invocation-start"); + } + + @Override + public void onInvocationEnd(InvocationEndInfo info) { + System.out.println(String.format( + "{\"plugin\": \"CONFPLUGIN-FAULTY\", \"hook\": \"invocation-end\"%s}", arnField())); + throw new RuntimeException("faulty invocation-end"); + } + + @Override + public void onOperationStart(OperationInfo info) { + if (isStep(info.type())) { + System.out.println(String.format( + "{\"plugin\": \"CONFPLUGIN-FAULTY\", \"hook\": \"operation-start\"%s}", arnField())); + throw new RuntimeException("faulty operation-start"); + } + } + + @Override + public void onOperationEnd(OperationEndInfo info) { + if (isStep(info.type())) { + System.out.println(String.format( + "{\"plugin\": \"CONFPLUGIN-FAULTY\", \"hook\": \"operation-end\"%s}", arnField())); + throw new RuntimeException("faulty operation-end"); + } + } + + @Override + public void onUserFunctionStart(UserFunctionStartInfo info) { + if (isStep(info.type())) { + System.out.println(String.format( + "{\"plugin\": \"CONFPLUGIN-FAULTY\", \"hook\": \"attempt-start\"%s}", arnField())); + throw new RuntimeException("faulty attempt-start"); + } + } + + @Override + public void onUserFunctionEnd(UserFunctionEndInfo info) { + if (isStep(info.type())) { + System.out.println(String.format( + "{\"plugin\": \"CONFPLUGIN-FAULTY\", \"hook\": \"attempt-end\"%s}", arnField())); + throw new RuntimeException("faulty attempt-end"); + } + } +} diff --git a/conformance-tests/src/main/java/plugin/PluginAttemptHooksRetry.java b/conformance-tests/src/main/java/plugin/PluginAttemptHooksRetry.java new file mode 100644 index 000000000..d9c1659a9 --- /dev/null +++ b/conformance-tests/src/main/java/plugin/PluginAttemptHooksRetry.java @@ -0,0 +1,50 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +package plugin; + +import java.time.Duration; +import software.amazon.lambda.durable.DurableConfig; +import software.amazon.lambda.durable.DurableContext; +import software.amazon.lambda.durable.DurableHandler; +import software.amazon.lambda.durable.config.StepConfig; +import software.amazon.lambda.durable.retry.RetryDecision; + +/** + * 10-3: Plugin attempt hooks fire per step attempt with attempt number and outcome. + * + *

A single step that fails on the first attempt and succeeds on the second (driven by the SDK's built-in + * {@code getAttempt()} and a real retry strategy), configured with {@link ConformanceLoggingPlugin}. The plugin logs + * {@code attempt-start n=} / {@code attempt-end n= outcome=} from the user-function hooks, + * which run on the same thread as the step body so their order is deterministic. + */ +@SuppressWarnings("deprecation") +public class PluginAttemptHooksRetry extends DurableHandler { + + @Override + protected DurableConfig createConfiguration() { + return DurableConfig.builder() + .withPlugins(new ConformanceLoggingPlugin("CONFPLUGIN")) + .build(); + } + + @Override + public String handleRequest(Object input, DurableContext context) { + return context.step( + "retry-step", + String.class, + stepCtx -> { + // Fail on the first attempt, succeed on the second, using the SDK's + // built-in 1-based attempt number. + if (stepCtx.getAttempt() < 2) { + throw new RuntimeException("Attempt " + stepCtx.getAttempt() + " failed"); + } + return "Operation succeeded"; + }, + StepConfig.builder() + .retryStrategy((error, attempt) -> { + if (attempt >= 3) return RetryDecision.fail(); + return RetryDecision.retry(Duration.ofSeconds(1)); + }) + .build()); + } +} diff --git a/conformance-tests/src/main/java/plugin/PluginErrorIsolation.java b/conformance-tests/src/main/java/plugin/PluginErrorIsolation.java new file mode 100644 index 000000000..59aeb80ce --- /dev/null +++ b/conformance-tests/src/main/java/plugin/PluginErrorIsolation.java @@ -0,0 +1,28 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +package plugin; + +import software.amazon.lambda.durable.DurableConfig; +import software.amazon.lambda.durable.DurableContext; +import software.amazon.lambda.durable.DurableHandler; + +/** + * 10-4: Plugin exceptions are swallowed and never affect the execution outcome. + * + *

A single greeting step configured with {@link FaultyConformancePlugin}, whose every hook logs a line and then + * throws. The SDK must catch and ignore every plugin exception so the execution result and history are identical to + * running without the plugin. + */ +@SuppressWarnings("deprecation") +public class PluginErrorIsolation extends DurableHandler { + + @Override + protected DurableConfig createConfiguration() { + return DurableConfig.builder().withPlugins(new FaultyConformancePlugin()).build(); + } + + @Override + public String handleRequest(String input, DurableContext context) { + return context.step("greet", String.class, stepCtx -> "Hello, " + input + "!"); + } +} diff --git a/conformance-tests/src/main/java/plugin/PluginFirstInvocationFlag.java b/conformance-tests/src/main/java/plugin/PluginFirstInvocationFlag.java new file mode 100644 index 000000000..31e136446 --- /dev/null +++ b/conformance-tests/src/main/java/plugin/PluginFirstInvocationFlag.java @@ -0,0 +1,32 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +package plugin; + +import java.time.Duration; +import software.amazon.lambda.durable.DurableConfig; +import software.amazon.lambda.durable.DurableContext; +import software.amazon.lambda.durable.DurableHandler; + +/** + * 10-6: Plugin sees is-first-invocation true once, then false on replay. + * + *

A single 2-second wait configured with {@link ConformanceLoggingPlugin}. The first invocation reports + * {@code first=true} (then suspends), and the replay invocation reports {@code first=false} and finalizes with + * {@code status=SUCCEEDED}. + */ +@SuppressWarnings("deprecation") +public class PluginFirstInvocationFlag extends DurableHandler { + + @Override + protected DurableConfig createConfiguration() { + return DurableConfig.builder() + .withPlugins(new ConformanceLoggingPlugin("CONFPLUGIN")) + .build(); + } + + @Override + public String handleRequest(Object input, DurableContext context) { + context.wait(null, Duration.ofSeconds(2)); + return "Wait completed"; + } +} diff --git a/conformance-tests/src/main/java/plugin/PluginInvocationLifecycle.java b/conformance-tests/src/main/java/plugin/PluginInvocationLifecycle.java new file mode 100644 index 000000000..a407f2674 --- /dev/null +++ b/conformance-tests/src/main/java/plugin/PluginInvocationLifecycle.java @@ -0,0 +1,33 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +package plugin; + +import software.amazon.lambda.durable.DurableConfig; +import software.amazon.lambda.durable.DurableContext; +import software.amazon.lambda.durable.DurableHandler; + +/** + * 10-1: Plugin invocation lifecycle hooks (start and end on a single invocation). + * + *

A single greeting step configured with {@link ConformanceLoggingPlugin}. The plugin's invocation-start hook logs + * {@code first=true} before the handler runs and its invocation-end hook logs {@code status=SUCCEEDED} after the + * result is finalized. The step body logs its running line via the context logger. + */ +@SuppressWarnings("deprecation") +public class PluginInvocationLifecycle extends DurableHandler { + + @Override + protected DurableConfig createConfiguration() { + return DurableConfig.builder() + .withPlugins(new ConformanceLoggingPlugin("CONFPLUGIN")) + .build(); + } + + @Override + public String handleRequest(String input, DurableContext context) { + return context.step("greet", String.class, stepCtx -> { + stepCtx.getLogger().info("Greeting step running for: {}", input); + return "Hello, " + input + "!"; + }); + } +} diff --git a/conformance-tests/src/main/java/plugin/PluginMultiplePlugins.java b/conformance-tests/src/main/java/plugin/PluginMultiplePlugins.java new file mode 100644 index 000000000..4dde9f6b3 --- /dev/null +++ b/conformance-tests/src/main/java/plugin/PluginMultiplePlugins.java @@ -0,0 +1,65 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +package plugin; + +import software.amazon.lambda.durable.DurableConfig; +import software.amazon.lambda.durable.DurableContext; +import software.amazon.lambda.durable.DurableHandler; +import software.amazon.lambda.durable.plugin.DurableExecutionPlugin; +import software.amazon.lambda.durable.plugin.InvocationEndInfo; +import software.amazon.lambda.durable.plugin.InvocationInfo; + +/** + * 10-5: Multiple registered plugins all receive lifecycle hooks. + * + *

A single greeting step configured with TWO invocation-logging plugins registered together in order A, B. Plugin A + * logs with prefix {@code CONFPLUGIN-A}, plugin B with prefix {@code CONFPLUGIN-B}, emitting exactly the lines the + * requirement documents: {@code invocation-start} and {@code invocation-end status=}. + */ +@SuppressWarnings("deprecation") +public class PluginMultiplePlugins extends DurableHandler { + + /** Minimal plugin emitting exactly the 10-5 documented invocation lines. */ + static final class InvocationLoggingPlugin implements DurableExecutionPlugin { + private final String prefix; + + /** Captured from onInvocationStart; read by onInvocationEnd which may run on another thread. */ + private volatile String executionArn; + + InvocationLoggingPlugin(String prefix) { + this.prefix = prefix; + } + + /** Returns {@code , "durableExecutionArn": ""} when captured, otherwise an empty string. */ + private String arnField() { + return executionArn == null ? "" : String.format(", \"durableExecutionArn\": \"%s\"", executionArn); + } + + @Override + public void onInvocationStart(InvocationInfo info) { + this.executionArn = info.durableExecutionArn(); + System.out.println(String.format( + "{\"plugin\": \"%s\", \"hook\": \"invocation-start\"%s}", prefix, arnField())); + } + + @Override + public void onInvocationEnd(InvocationEndInfo info) { + System.out.println(String.format( + "{\"plugin\": \"%s\", \"hook\": \"invocation-end\", \"status\": \"%s\"%s}", + prefix, info.invocationStatus().name(), arnField())); + } + } + + @Override + protected DurableConfig createConfiguration() { + return DurableConfig.builder() + .withPlugins( + new InvocationLoggingPlugin("CONFPLUGIN-A"), new InvocationLoggingPlugin("CONFPLUGIN-B")) + .build(); + } + + @Override + public String handleRequest(String input, DurableContext context) { + return context.step("greet", String.class, stepCtx -> "Hello, " + input + "!"); + } +} diff --git a/conformance-tests/src/main/java/plugin/PluginOperationLifecycle.java b/conformance-tests/src/main/java/plugin/PluginOperationLifecycle.java new file mode 100644 index 000000000..ecbb39bb9 --- /dev/null +++ b/conformance-tests/src/main/java/plugin/PluginOperationLifecycle.java @@ -0,0 +1,30 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +package plugin; + +import software.amazon.lambda.durable.DurableConfig; +import software.amazon.lambda.durable.DurableContext; +import software.amazon.lambda.durable.DurableHandler; + +/** + * 10-2: Plugin operation lifecycle hooks (step start and terminal end). + * + *

A single greeting step configured with {@link ConformanceLoggingPlugin}. The plugin, filtering to step-type + * operations, logs {@code operation-start} when the step's STARTED checkpoint is observed and + * {@code operation-end status=SUCCEEDED} when the step reaches its terminal status. + */ +@SuppressWarnings("deprecation") +public class PluginOperationLifecycle extends DurableHandler { + + @Override + protected DurableConfig createConfiguration() { + return DurableConfig.builder() + .withPlugins(new ConformanceLoggingPlugin("CONFPLUGIN")) + .build(); + } + + @Override + public String handleRequest(String input, DurableContext context) { + return context.step("greet", String.class, stepCtx -> "Hello, " + input + "!"); + } +} diff --git a/conformance-tests/src/main/java/plugin/PluginTerminalFailure.java b/conformance-tests/src/main/java/plugin/PluginTerminalFailure.java new file mode 100644 index 000000000..9ba9cf7da --- /dev/null +++ b/conformance-tests/src/main/java/plugin/PluginTerminalFailure.java @@ -0,0 +1,40 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +package plugin; + +import software.amazon.lambda.durable.DurableConfig; +import software.amazon.lambda.durable.DurableContext; +import software.amazon.lambda.durable.DurableHandler; +import software.amazon.lambda.durable.config.StepConfig; +import software.amazon.lambda.durable.retry.RetryStrategies; + +/** + * 10-7: Plugin invocation-end hook receives FAILED status when execution fails. + * + *

A single step that always throws, configured with no retries and {@link ConformanceLoggingPlugin}. The plugin + * logs {@code invocation-start first=true}; the step throws, no retry is attempted, and the plugin's invocation-end + * hook fires with {@code status=FAILED}. + */ +@SuppressWarnings("deprecation") +public class PluginTerminalFailure extends DurableHandler { + + @Override + protected DurableConfig createConfiguration() { + return DurableConfig.builder() + .withPlugins(new ConformanceLoggingPlugin("CONFPLUGIN")) + .build(); + } + + @Override + public String handleRequest(Object input, DurableContext context) { + return context.step( + "failing-step", + String.class, + stepCtx -> { + throw new RuntimeException("Something went wrong"); + }, + StepConfig.builder() + .retryStrategy(RetryStrategies.Presets.NO_RETRY) + .build()); + } +} diff --git a/conformance-tests/template_plugin.yaml b/conformance-tests/template_plugin.yaml new file mode 100644 index 000000000..902223ddd --- /dev/null +++ b/conformance-tests/template_plugin.yaml @@ -0,0 +1,163 @@ +AWSTemplateFormatVersion: '2010-09-09' +Transform: AWS::Serverless-2016-10-31 +Description: Durable Execution Conformance Test Examples - Java (Plugin) + +Parameters: + Architecture: + Type: String + Default: arm64 + Description: Lambda Function Architecture + AllowedValues: + - x86_64 + - arm64 + JavaVersion: + Type: String + Default: 'java21' + Description: Java runtime version + +Globals: + Function: + Timeout: 60 + MemorySize: 512 + Runtime: + Ref: JavaVersion + Architectures: + - Ref: Architecture + LoggingConfig: + LogFormat: JSON + +Resources: + DurableFunctionRole: + Type: AWS::IAM::Role + Properties: + AssumeRolePolicyDocument: + Version: '2012-10-17' + Statement: + - Effect: Allow + Principal: + Service: lambda.amazonaws.com + Action: sts:AssumeRole + ManagedPolicyArns: + - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole + Policies: + - PolicyName: DurableExecutionPolicy + PolicyDocument: + Version: '2012-10-17' + Statement: + - Effect: Allow + Action: + - lambda:CheckpointDurableExecution + - lambda:GetDurableExecutionState + Resource: '*' + + PluginInvocationLifecycle: + Type: AWS::Serverless::Function + TestingMetadata: + TestDescription: ["10-1"] + Properties: + CodeUri: . + Handler: plugin.PluginInvocationLifecycle + Description: Plugin invocation lifecycle hooks (start and end on a single invocation) + Role: + Fn::GetAtt: + - DurableFunctionRole + - Arn + DurableConfig: + RetentionPeriodInDays: 7 + ExecutionTimeout: 300 + + PluginOperationLifecycle: + Type: AWS::Serverless::Function + TestingMetadata: + TestDescription: ["10-2"] + Properties: + CodeUri: . + Handler: plugin.PluginOperationLifecycle + Description: Plugin operation lifecycle hooks (step start and terminal end) + Role: + Fn::GetAtt: + - DurableFunctionRole + - Arn + DurableConfig: + RetentionPeriodInDays: 7 + ExecutionTimeout: 300 + + PluginAttemptHooksRetry: + Type: AWS::Serverless::Function + TestingMetadata: + TestDescription: ["10-3"] + Properties: + CodeUri: . + Handler: plugin.PluginAttemptHooksRetry + Description: Plugin attempt hooks fire per step attempt with attempt number and outcome + Role: + Fn::GetAtt: + - DurableFunctionRole + - Arn + DurableConfig: + RetentionPeriodInDays: 7 + ExecutionTimeout: 300 + + PluginErrorIsolation: + Type: AWS::Serverless::Function + TestingMetadata: + TestDescription: ["10-4"] + Properties: + CodeUri: . + Handler: plugin.PluginErrorIsolation + Description: Plugin exceptions are swallowed and never affect the execution outcome + Role: + Fn::GetAtt: + - DurableFunctionRole + - Arn + DurableConfig: + RetentionPeriodInDays: 7 + ExecutionTimeout: 300 + + PluginMultiplePlugins: + Type: AWS::Serverless::Function + TestingMetadata: + TestDescription: ["10-5"] + Properties: + CodeUri: . + Handler: plugin.PluginMultiplePlugins + Description: Multiple registered plugins all receive lifecycle hooks + Role: + Fn::GetAtt: + - DurableFunctionRole + - Arn + DurableConfig: + RetentionPeriodInDays: 7 + ExecutionTimeout: 300 + + PluginFirstInvocationFlag: + Type: AWS::Serverless::Function + TestingMetadata: + TestDescription: ["10-6"] + Properties: + CodeUri: . + Handler: plugin.PluginFirstInvocationFlag + Description: Plugin sees is-first-invocation true once, then false on replay + Role: + Fn::GetAtt: + - DurableFunctionRole + - Arn + DurableConfig: + RetentionPeriodInDays: 7 + ExecutionTimeout: 300 + + PluginTerminalFailure: + Type: AWS::Serverless::Function + TestingMetadata: + TestDescription: ["10-7"] + Properties: + CodeUri: . + Handler: plugin.PluginTerminalFailure + Description: Plugin invocation-end hook receives FAILED status when execution fails + Role: + Fn::GetAtt: + - DurableFunctionRole + - Arn + DurableConfig: + RetentionPeriodInDays: 7 + ExecutionTimeout: 300