From 3e367ecb26677064ec4328419052ae98e67680d5 Mon Sep 17 00:00:00 2001 From: Cassie Coyle Date: Fri, 15 Aug 2025 14:36:23 -0500 Subject: [PATCH 01/22] process deps too for dapr-spring (#1503) Signed-off-by: Cassandra Coyle Signed-off-by: Javier Aliaga --- .github/scripts/update_sdk_version.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/scripts/update_sdk_version.sh b/.github/scripts/update_sdk_version.sh index f66d2adf20..4bf194d13d 100755 --- a/.github/scripts/update_sdk_version.sh +++ b/.github/scripts/update_sdk_version.sh @@ -25,7 +25,7 @@ mvn versions:set -DnewVersion=$DAPR_JAVA_SDK_ALPHA_VERSION -f sdk-workflows/pom. mvn versions:set -DnewVersion=$DAPR_JAVA_SDK_ALPHA_VERSION -f testcontainers-dapr/pom.xml # dapr-spring -mvn versions:set -DnewVersion=$DAPR_JAVA_SDK_ALPHA_VERSION -f dapr-spring/pom.xml +mvn versions:set -DnewVersion=$DAPR_JAVA_SDK_ALPHA_VERSION -DprocessDependencies=true -f dapr-spring/pom.xml # spring-boot-examples mvn versions:set -DnewVersion=$DAPR_JAVA_SDK_ALPHA_VERSION -f spring-boot-examples/pom.xml From acf505f14fac45ee79569a1274244401850e64df Mon Sep 17 00:00:00 2001 From: Matheus Cruz <56329339+mcruzdev@users.noreply.github.com> Date: Sat, 16 Aug 2025 04:00:44 -0300 Subject: [PATCH 02/22] Use camelCase on properties (#1470) * Use camelCase on properties Signed-off-by: Matheus Cruz * Add test for cameCalse properties Signed-off-by: Matheus Cruz * Update daprdocs/content/en/java-sdk-docs/spring-boot/_index.md Co-authored-by: Cassie Coyle Signed-off-by: Matheus Cruz <56329339+mcruzdev@users.noreply.github.com> --------- Signed-off-by: Matheus Cruz Signed-off-by: Matheus Cruz <56329339+mcruzdev@users.noreply.github.com> Co-authored-by: artur-ciocanu Co-authored-by: Cassie Coyle Signed-off-by: Javier Aliaga --- .../client/DaprClientPropertiesTest.java | 19 +++++++++++++++++++ .../en/java-sdk-docs/spring-boot/_index.md | 12 ++++++------ 2 files changed, 25 insertions(+), 6 deletions(-) diff --git a/dapr-spring/dapr-spring-boot-autoconfigure/src/test/java/io/dapr/spring/boot/autoconfigure/client/DaprClientPropertiesTest.java b/dapr-spring/dapr-spring-boot-autoconfigure/src/test/java/io/dapr/spring/boot/autoconfigure/client/DaprClientPropertiesTest.java index 7a1781d132..33ea73989f 100644 --- a/dapr-spring/dapr-spring-boot-autoconfigure/src/test/java/io/dapr/spring/boot/autoconfigure/client/DaprClientPropertiesTest.java +++ b/dapr-spring/dapr-spring-boot-autoconfigure/src/test/java/io/dapr/spring/boot/autoconfigure/client/DaprClientPropertiesTest.java @@ -83,7 +83,26 @@ public void shouldMapDaprClientProperties() { }); }); + } + @Test + @DisplayName("Should map DaprClient properties correctly (camelCase)") + public void shouldMapDaprClientPropertiesCamelCase() { + runner.withSystemProperties( + "dapr.client.httpEndpoint=http://localhost", + "dapr.client.httpPort=3500", + "dapr.client.grpcEndpoint=localhost", + "dapr.client.grpcPort=50001" + ).run(context -> { + DaprClientProperties properties = context.getBean(DaprClientProperties.class); + SoftAssertions.assertSoftly(softly -> { + softly.assertThat(properties.getGrpcEndpoint()).isEqualTo("localhost"); + softly.assertThat(properties.getHttpEndpoint()).isEqualTo("http://localhost"); + softly.assertThat(properties.getHttpPort()).isEqualTo(3500); + softly.assertThat(properties.getGrpcPort()).isEqualTo(50001); + }); + + }); } @EnableConfigurationProperties(DaprClientProperties.class) diff --git a/daprdocs/content/en/java-sdk-docs/spring-boot/_index.md b/daprdocs/content/en/java-sdk-docs/spring-boot/_index.md index b530d06cd3..270d9899b5 100644 --- a/daprdocs/content/en/java-sdk-docs/spring-boot/_index.md +++ b/daprdocs/content/en/java-sdk-docs/spring-boot/_index.md @@ -56,13 +56,13 @@ This will connect to the default Dapr gRPC endpoint `localhost:50001`, requiring {{% alert title="Note" color="primary" %}} By default, the following properties are preconfigured for `DaprClient` and `DaprWorkflowClient`: ```properties -dapr.client.http-endpoint=http://localhost -dapr.client.http-port=3500 -dapr.client.grpc-endpoint=localhost -dapr.client.grpc-port=50001 -dapr.client.api-token= +dapr.client.httpEndpoint=http://localhost +dapr.client.httpPort=3500 +dapr.client.grpcEndpoint=localhost +dapr.client.grpcPort=50001 +dapr.client.apiToken= ``` -These values are used by default, but you can override them in your `application.properties` file to suit your environment. +These values are used by default, but you can override them in your `application.properties` file to suit your environment. Please note that both kebab case and camel case are supported. {{% /alert %}} You can use the `DaprClient` to interact with the Dapr APIs anywhere in your application, for example from inside a REST endpoint: From 72bad12c58bdd8fa34b114d97936529cd355dcbd Mon Sep 17 00:00:00 2001 From: Cassie Coyle Date: Mon, 18 Aug 2025 15:31:43 -0500 Subject: [PATCH 03/22] fix script (#1506) Signed-off-by: Cassandra Coyle Signed-off-by: Javier Aliaga --- .github/scripts/update_sdk_version.sh | 1 + dapr-spring/pom.xml | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/scripts/update_sdk_version.sh b/.github/scripts/update_sdk_version.sh index 4bf194d13d..6d07850a92 100755 --- a/.github/scripts/update_sdk_version.sh +++ b/.github/scripts/update_sdk_version.sh @@ -26,6 +26,7 @@ mvn versions:set -DnewVersion=$DAPR_JAVA_SDK_ALPHA_VERSION -f testcontainers-dap # dapr-spring mvn versions:set -DnewVersion=$DAPR_JAVA_SDK_ALPHA_VERSION -DprocessDependencies=true -f dapr-spring/pom.xml +mvn versions:set-property -Dproperty=dapr.spring.version -DnewVersion=$DAPR_JAVA_SDK_ALPHA_VERSION -f dapr-spring/pom.xml # spring-boot-examples mvn versions:set -DnewVersion=$DAPR_JAVA_SDK_ALPHA_VERSION -f spring-boot-examples/pom.xml diff --git a/dapr-spring/pom.xml b/dapr-spring/pom.xml index 4e1738394d..4b9df127c0 100644 --- a/dapr-spring/pom.xml +++ b/dapr-spring/pom.xml @@ -33,7 +33,8 @@ 11 1.19.8 5.11.2 - 0.16.0-SNAPSHOT + + 0.17.0-SNAPSHOT From bbbcd5a1117af748603af2a2443a0b67924c73e5 Mon Sep 17 00:00:00 2001 From: salaboy Date: Wed, 20 Aug 2025 09:37:24 +0100 Subject: [PATCH 04/22] add support for custom status (#1505) Signed-off-by: salaboy Co-authored-by: Siri Varma Vegiraju Signed-off-by: Javier Aliaga --- .../java/io/dapr/workflows/WorkflowContext.java | 8 ++++++++ .../workflows/runtime/DefaultWorkflowContext.java | 15 ++++++++++++--- .../workflows/DefaultWorkflowContextTest.java | 14 ++++++++++++++ 3 files changed, 34 insertions(+), 3 deletions(-) diff --git a/sdk-workflows/src/main/java/io/dapr/workflows/WorkflowContext.java b/sdk-workflows/src/main/java/io/dapr/workflows/WorkflowContext.java index 9994cb7792..8608e96937 100644 --- a/sdk-workflows/src/main/java/io/dapr/workflows/WorkflowContext.java +++ b/sdk-workflows/src/main/java/io/dapr/workflows/WorkflowContext.java @@ -526,4 +526,12 @@ default void continueAsNew(Object input) { default UUID newUuid() { throw new RuntimeException("No implementation found."); } + + /** + * Set a custom status to a workflow execution. + * + * @param status to be set to the current execution + */ + void setCustomStatus(Object status); + } diff --git a/sdk-workflows/src/main/java/io/dapr/workflows/runtime/DefaultWorkflowContext.java b/sdk-workflows/src/main/java/io/dapr/workflows/runtime/DefaultWorkflowContext.java index d11e1fe771..d12346ca0f 100644 --- a/sdk-workflows/src/main/java/io/dapr/workflows/runtime/DefaultWorkflowContext.java +++ b/sdk-workflows/src/main/java/io/dapr/workflows/runtime/DefaultWorkflowContext.java @@ -59,7 +59,7 @@ public DefaultWorkflowContext(TaskOrchestrationContext context) throws IllegalAr * @throws IllegalArgumentException if context or logger is null */ public DefaultWorkflowContext(TaskOrchestrationContext context, Logger logger) - throws IllegalArgumentException { + throws IllegalArgumentException { if (context == null) { throw new IllegalArgumentException("Context cannot be null"); } @@ -114,7 +114,7 @@ public void complete(Object output) { */ @Override public Task waitForExternalEvent(String name, Duration timeout, Class dataType) - throws TaskCanceledException { + throws TaskCanceledException { return this.innerContext.waitForExternalEvent(name, timeout, dataType); } @@ -130,7 +130,7 @@ public Task waitForExternalEvent(String name, Duration timeout, Class * @param timeout the amount of time to wait before canceling the returned * {@code Task} * @return a new {@link Task} that completes when the external event is received - * or when {@code timeout} expires + * or when {@code timeout} expires * @throws TaskCanceledException if the specified {@code timeout} value expires * before the event is received */ @@ -294,4 +294,13 @@ private RetryHandler toRetryHandler(WorkflowTaskRetryHandler workflowTaskRetryHa return workflowTaskRetryHandler.handle(workflowRetryContext); }; } + + /** + * Set custom status to a workflow execution. + * + * @param status to set to the execution + */ + public void setCustomStatus(Object status) { + innerContext.setCustomStatus(status); + } } diff --git a/sdk-workflows/src/test/java/io/dapr/workflows/DefaultWorkflowContextTest.java b/sdk-workflows/src/test/java/io/dapr/workflows/DefaultWorkflowContextTest.java index f0eaa9ee1a..837bf4c7db 100644 --- a/sdk-workflows/src/test/java/io/dapr/workflows/DefaultWorkflowContextTest.java +++ b/sdk-workflows/src/test/java/io/dapr/workflows/DefaultWorkflowContextTest.java @@ -135,6 +135,11 @@ public Task callChildWorkflow(String name, @Nullable Object input, @Nulla @Override public void continueAsNew(Object input, boolean preserveUnprocessedEvents) { } + + @Override + public void setCustomStatus(Object status) { + + } }; } @@ -403,6 +408,15 @@ public void callChildWorkflow() { verify(mockInnerContext, times(1)).callSubOrchestrator(expectedName, expectedInput, null, null, String.class); } + @Test + public void setCustomStatusWorkflow() { + String customStatus = "CustomStatus"; + + context.setCustomStatus(customStatus); + verify(mockInnerContext, times(1)).setCustomStatus(customStatus); + + } + @Test public void newUuidTest() { context.newUuid(); From da628e1357ff5be4e33b928b0bb018cf4013ae82 Mon Sep 17 00:00:00 2001 From: Cassie Coyle Date: Wed, 20 Aug 2025 19:45:27 -0500 Subject: [PATCH 05/22] try 1.5.7 (#1487) Signed-off-by: Cassandra Coyle Co-authored-by: Siri Varma Vegiraju Co-authored-by: artur-ciocanu Co-authored-by: Dapr Bot <56698301+dapr-bot@users.noreply.github.com> Signed-off-by: Javier Aliaga --- sdk-workflows/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk-workflows/pom.xml b/sdk-workflows/pom.xml index 883489fd01..720a512908 100644 --- a/sdk-workflows/pom.xml +++ b/sdk-workflows/pom.xml @@ -47,7 +47,7 @@ io.dapr durabletask-client - 1.5.6 + 1.5.7 Execute the following script in order to run DemoChainWorker: ```sh -dapr run --app-id demoworkflowworker --resources-path ./components/workflows --dapr-grpc-port 50001 -- java -jar target/dapr-java-sdk-examples-exec.jar io.dapr.examples.workflows.chain.DemoChainWorker 50001 +dapr run --app-id chainingworker --resources-path ./components/workflows --dapr-grpc-port 50001 -- java -jar target/dapr-java-sdk-examples-exec.jar io.dapr.examples.workflows.chain.DemoChainWorker 50001 ``` Once running, the logs will start displaying the different steps: First, you can see workflow is starting: @@ -169,7 +169,8 @@ timeout_seconds: 20 --> Then, execute the following script in order to run DemoChainClient: ```sh -sleep 10 && java -jar target/dapr-java-sdk-examples-exec.jar io.dapr.examples.workflows.chain.DemoChainClient 50001 +java -jar target/dapr-java-sdk-examples-exec.jar io.dapr.examples.workflows.chain.DemoChainClient 50001 +dapr stop --app-id chainingworker ``` @@ -266,7 +267,7 @@ background: true Execute the following script in order to run DemoFanInOutWorker: ```sh -dapr run --app-id demoworkflowworker --resources-path ./components/workflows --dapr-grpc-port 50002 -- java -jar target/dapr-java-sdk-examples-exec.jar io.dapr.examples.workflows.faninout.DemoFanInOutWorker 50002 +dapr run --app-id faninoutworker --resources-path ./components/workflows --dapr-grpc-port 50002 -- java -jar target/dapr-java-sdk-examples-exec.jar io.dapr.examples.workflows.faninout.DemoFanInOutWorker 50002 ``` @@ -282,7 +283,8 @@ timeout_seconds: 20 Execute the following script in order to run DemoFanInOutClient: ```sh -sleep 10 && java -jar target/dapr-java-sdk-examples-exec.jar io.dapr.examples.workflows.faninout.DemoFanInOutClient 50002 +java -jar target/dapr-java-sdk-examples-exec.jar io.dapr.examples.workflows.faninout.DemoFanInOutClient 50002 +dapr stop --app-id faninoutworker ``` @@ -660,7 +662,8 @@ timeout_seconds: 30 --> Once running, execute the following script to run the BookTripClient: ```sh -sleep 15 && java -jar target/dapr-java-sdk-examples-exec.jar io.dapr.examples.workflows.compensation.BookTripClient 50003 +java -jar target/dapr-java-sdk-examples-exec.jar io.dapr.examples.workflows.compensation.BookTripClient 50003 +dapr stop --app-id book-trip-worker ``` @@ -702,7 +705,7 @@ timeout_seconds: 30 --> ```sh -dapr run --app-id demoworkflowworker --resources-path ./components/workflows --dapr-grpc-port 50004 -- java -jar target/dapr-java-sdk-examples-exec.jar io.dapr.examples.workflows.suspendresume.DemoSuspendResumeWorker 50004 +dapr run --app-id suspendresumeworker --resources-path ./components/workflows --dapr-grpc-port 50004 -- java -jar target/dapr-java-sdk-examples-exec.jar io.dapr.examples.workflows.suspendresume.DemoSuspendResumeWorker 50004 ``` @@ -720,7 +723,8 @@ expected_stdout_lines: timeout_seconds: 30 --> ```sh -sleep 15 && java -jar target/dapr-java-sdk-examples-exec.jar io.dapr.examples.workflows.suspendresume.DemoSuspendResumeClient 50004 +java -jar target/dapr-java-sdk-examples-exec.jar io.dapr.examples.workflows.suspendresume.DemoSuspendResumeClient 50004 +dapr stop --app-id suspendresumeworker ``` diff --git a/examples/src/main/java/io/dapr/examples/workflows/chain/DemoChainClient.java b/examples/src/main/java/io/dapr/examples/workflows/chain/DemoChainClient.java index 6c9eedbdc6..334e40f8df 100644 --- a/examples/src/main/java/io/dapr/examples/workflows/chain/DemoChainClient.java +++ b/examples/src/main/java/io/dapr/examples/workflows/chain/DemoChainClient.java @@ -14,9 +14,11 @@ package io.dapr.examples.workflows.chain; import io.dapr.examples.workflows.utils.PropertyUtils; +import io.dapr.examples.workflows.utils.RetryUtils; import io.dapr.workflows.client.DaprWorkflowClient; import io.dapr.workflows.client.WorkflowInstanceStatus; +import java.time.Duration; import java.util.concurrent.TimeoutException; public class DemoChainClient { @@ -28,15 +30,15 @@ public class DemoChainClient { */ public static void main(String[] args) { try (DaprWorkflowClient client = new DaprWorkflowClient(PropertyUtils.getProperties(args))) { - String instanceId = client.scheduleNewWorkflow(DemoChainWorkflow.class); + String instanceId = RetryUtils.callWithRetry(() -> client.scheduleNewWorkflow(DemoChainWorkflow.class), + Duration.ofSeconds(60)); + System.out.printf("Started a new chaining model workflow with instance ID: %s%n", instanceId); WorkflowInstanceStatus workflowInstanceStatus = client.waitForInstanceCompletion(instanceId, null, true); String result = workflowInstanceStatus.readOutputAs(String.class); System.out.printf("workflow instance with ID: %s completed with result: %s%n", instanceId, result); - - } catch (TimeoutException | InterruptedException e) { throw new RuntimeException(e); } diff --git a/examples/src/main/java/io/dapr/examples/workflows/compensation/BookTripClient.java b/examples/src/main/java/io/dapr/examples/workflows/compensation/BookTripClient.java index ce76d5de11..b7c4760e52 100644 --- a/examples/src/main/java/io/dapr/examples/workflows/compensation/BookTripClient.java +++ b/examples/src/main/java/io/dapr/examples/workflows/compensation/BookTripClient.java @@ -14,6 +14,7 @@ package io.dapr.examples.workflows.compensation; import io.dapr.examples.workflows.utils.PropertyUtils; +import io.dapr.examples.workflows.utils.RetryUtils; import io.dapr.workflows.client.DaprWorkflowClient; import io.dapr.workflows.client.WorkflowInstanceStatus; @@ -23,7 +24,7 @@ public class BookTripClient { public static void main(String[] args) { try (DaprWorkflowClient client = new DaprWorkflowClient(PropertyUtils.getProperties(args))) { - String instanceId = client.scheduleNewWorkflow(BookTripWorkflow.class); + String instanceId = RetryUtils.callWithRetry(() -> client.scheduleNewWorkflow(BookTripWorkflow.class), Duration.ofSeconds(60)); System.out.printf("Started a new trip booking workflow with instance ID: %s%n", instanceId); WorkflowInstanceStatus status = client.waitForInstanceCompletion(instanceId, Duration.ofMinutes(30), true); diff --git a/examples/src/main/java/io/dapr/examples/workflows/faninout/DemoFanInOutClient.java b/examples/src/main/java/io/dapr/examples/workflows/faninout/DemoFanInOutClient.java index 612a8979d3..871b15cfe4 100644 --- a/examples/src/main/java/io/dapr/examples/workflows/faninout/DemoFanInOutClient.java +++ b/examples/src/main/java/io/dapr/examples/workflows/faninout/DemoFanInOutClient.java @@ -14,6 +14,7 @@ package io.dapr.examples.workflows.faninout; import io.dapr.examples.workflows.utils.PropertyUtils; +import io.dapr.examples.workflows.utils.RetryUtils; import io.dapr.workflows.client.DaprWorkflowClient; import io.dapr.workflows.client.WorkflowInstanceStatus; @@ -40,9 +41,10 @@ public static void main(String[] args) throws InterruptedException { "Always remember that you are absolutely unique. Just like everyone else."); // Schedule an orchestration which will reliably count the number of words in all the given sentences. - String instanceId = client.scheduleNewWorkflow( + String instanceId = RetryUtils.callWithRetry(() -> client.scheduleNewWorkflow( DemoFanInOutWorkflow.class, - listOfStrings); + listOfStrings), Duration.ofSeconds(60)); + System.out.printf("Started a new fan out/fan in model workflow with instance ID: %s%n", instanceId); // Block until the orchestration completes. Then print the final status, which includes the output. diff --git a/examples/src/main/java/io/dapr/examples/workflows/suspendresume/DemoSuspendResumeClient.java b/examples/src/main/java/io/dapr/examples/workflows/suspendresume/DemoSuspendResumeClient.java index 5880c64f2d..5b94b5fa5a 100644 --- a/examples/src/main/java/io/dapr/examples/workflows/suspendresume/DemoSuspendResumeClient.java +++ b/examples/src/main/java/io/dapr/examples/workflows/suspendresume/DemoSuspendResumeClient.java @@ -15,9 +15,11 @@ import io.dapr.examples.workflows.externalevent.DemoExternalEventWorkflow; import io.dapr.examples.workflows.utils.PropertyUtils; +import io.dapr.examples.workflows.utils.RetryUtils; import io.dapr.workflows.client.DaprWorkflowClient; import io.dapr.workflows.client.WorkflowInstanceStatus; +import java.time.Duration; import java.util.concurrent.TimeoutException; public class DemoSuspendResumeClient { @@ -29,7 +31,7 @@ public class DemoSuspendResumeClient { */ public static void main(String[] args) { try (DaprWorkflowClient client = new DaprWorkflowClient(PropertyUtils.getProperties(args))) { - String instanceId = client.scheduleNewWorkflow(DemoExternalEventWorkflow.class); + String instanceId = RetryUtils.callWithRetry(() -> client.scheduleNewWorkflow(DemoExternalEventWorkflow.class), Duration.ofSeconds(60)); System.out.printf("Started a new external-event workflow with instance ID: %s%n", instanceId); diff --git a/examples/src/main/java/io/dapr/examples/workflows/utils/RetryUtils.java b/examples/src/main/java/io/dapr/examples/workflows/utils/RetryUtils.java new file mode 100644 index 0000000000..42651b06b5 --- /dev/null +++ b/examples/src/main/java/io/dapr/examples/workflows/utils/RetryUtils.java @@ -0,0 +1,47 @@ +/* + * Copyright 2025 The Dapr Authors + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and +limitations under the License. +*/ + +package io.dapr.examples.workflows.utils; + +import java.time.Duration; +import java.util.concurrent.Callable; + +public class RetryUtils { + private static final long RETRY_WAIT_MILLISECONDS = 1000; + + public static String callWithRetry(Callable function, Duration retryTimeout) throws InterruptedException { + var retryTimeoutMilliseconds = retryTimeout.toMillis(); + long started = System.currentTimeMillis(); + while (true) { + Throwable exception; + try { + return function.call(); + } catch (Exception | AssertionError e) { + exception = e; + } + + long elapsed = System.currentTimeMillis() - started; + if (elapsed >= retryTimeoutMilliseconds) { + if (exception instanceof RuntimeException) { + throw (RuntimeException) exception; + } + + throw new RuntimeException(exception); + } + + long remaining = retryTimeoutMilliseconds - elapsed; + Thread.sleep(Math.min(remaining, RETRY_WAIT_MILLISECONDS)); + } + } + +} diff --git a/pom.xml b/pom.xml index 11a209ae7f..f0e09d18b3 100644 --- a/pom.xml +++ b/pom.xml @@ -658,6 +658,18 @@ sdk-tests + + + + org.apache.maven.plugins + maven-surefire-plugin + + true + + + + + diff --git a/sdk-tests/pom.xml b/sdk-tests/pom.xml index 17146622a8..d3c45804fb 100644 --- a/sdk-tests/pom.xml +++ b/sdk-tests/pom.xml @@ -315,18 +315,6 @@ org.apache.maven.plugins maven-failsafe-plugin 3.2.2 - - - - integration-test - verify - - - - ${skipITs} - - - diff --git a/spring-boot-examples/workflows/README.md b/spring-boot-examples/workflows/README.md index 595857cb10..d62eded716 100644 --- a/spring-boot-examples/workflows/README.md +++ b/spring-boot-examples/workflows/README.md @@ -65,7 +65,6 @@ match_order: none output_match_mode: substring expected_stdout_lines: - 'TOKYO, LONDON, SEATTLE' -background: true timeout_seconds: 90 --> @@ -73,7 +72,7 @@ timeout_seconds: 90 To start the workflow with the three chained activities you can run: ```sh -sleep 35 && curl -X POST localhost:8080/wfp/chain -H 'Content-Type: application/json' +curl -X POST localhost:8080/wfp/chain -H 'Content-Type: application/json' --retry 10 --max-time 20 --retry-all-errors --retry-max-time 90 ``` @@ -142,7 +141,6 @@ match_order: none output_match_mode: substring expected_stdout_lines: - '!wolfkroW rpaD olleH' -background: true timeout_seconds: 90 --> @@ -150,7 +148,7 @@ timeout_seconds: 90 To start the workflow with the three chained activities you can run: ```sh -sleep 35 && curl -X POST localhost:8080/wfp/child -H 'Content-Type: application/json' +curl -X POST localhost:8080/wfp/child -H 'Content-Type: application/json' --retry 10 --max-time 20 --retry-all-errors --retry-max-time 90 ``` @@ -191,13 +189,12 @@ match_order: none output_match_mode: substring expected_stdout_lines: - '{"cleanUpTimes":5}' -background: true timeout_seconds: 90 --> ```sh -sleep 30 && curl -X POST localhost:8080/wfp/continueasnew -H 'Content-Type: application/json' +curl -X POST localhost:8080/wfp/continueasnew -H 'Content-Type: application/json' --retry 10 --max-time 60 --retry-all-errors --retry-max-time 90 ``` @@ -260,13 +257,12 @@ To start the workflow you can run: name: Start External Event Workflow match_order: none output_match_mode: substring -background: true timeout_seconds: 90 --> ```sh -sleep 30 && curl -X POST "localhost:8080/wfp/externalevent?orderId=123" -H 'Content-Type: application/json' +curl -X POST "localhost:8080/wfp/externalevent?orderId=123" -H 'Content-Type: application/json' --retry 10 --max-time 20 --retry-all-errors --retry-max-time 90 ``` @@ -290,7 +286,6 @@ match_order: none output_match_mode: substring expected_stdout_lines: - '{"approved":true}' -background: true timeout_seconds: 90 --> @@ -298,7 +293,7 @@ timeout_seconds: 90 To send the event you can run: ```sh -sleep 42 && curl -X POST "localhost:8080/wfp/externalevent-continue?orderId=123&decision=true" -H 'Content-Type: application/json' +curl -X POST "localhost:8080/wfp/externalevent-continue?orderId=123&decision=true" -H 'Content-Type: application/json' --retry 10 --max-time 20 --retry-all-errors --retry-max-time 90 ``` @@ -346,13 +341,12 @@ match_order: none output_match_mode: substring expected_stdout_lines: - '{"wordCount":60}' -background: true timeout_seconds: 90 --> ```sh -sleep 45 && curl -X POST localhost:8080/wfp/fanoutin -H 'Content-Type: application/json' -d @body.json +curl -X POST localhost:8080/wfp/fanoutin -H 'Content-Type: application/json' -d @body.json --retry 10 --max-time 20 --retry-all-errors --retry-max-time 90 ``` @@ -398,13 +392,12 @@ To start the workflow, you can run: name: Start Suspend/Resume Workflow match_order: none output_match_mode: substring -background: true timeout_seconds: 90 --> ```sh -sleep 50 && curl -X POST "localhost:8080/wfp/suspendresume?orderId=456" -H 'Content-Type: application/json' +curl -X POST "localhost:8080/wfp/suspendresume?orderId=456" -H 'Content-Type: application/json' --retry 10 --max-time 20 --retry-all-errors --retry-max-time 90 ``` @@ -431,7 +424,6 @@ match_order: none output_match_mode: substring expected_stdout_lines: - 'SUSPENDED' -background: true timeout_seconds: 90 --> @@ -439,7 +431,7 @@ timeout_seconds: 90 Let's suspend the workflow instance by sending the following request: ```sh -sleep 55 && curl -X POST "localhost:8080/wfp/suspendresume/suspend?orderId=456" -H 'Content-Type: application/json' +curl -X POST "localhost:8080/wfp/suspendresume/suspend?orderId=456" -H 'Content-Type: application/json' --retry 10 --max-time 20 --retry-all-errors --retry-max-time 90 ``` @@ -459,7 +451,6 @@ match_order: none output_match_mode: substring expected_stdout_lines: - 'RUNNING' -background: true timeout_seconds: 90 --> @@ -467,7 +458,7 @@ timeout_seconds: 90 To send the event you can run: ```sh -sleep 60 && curl -X POST "localhost:8080/wfp/suspendresume/resume?orderId=456" -H 'Content-Type: application/json' +curl -X POST "localhost:8080/wfp/suspendresume/resume?orderId=456" -H 'Content-Type: application/json' --retry 10 --max-time 20 --retry-all-errors --retry-max-time 90 ``` @@ -487,7 +478,6 @@ match_order: none output_match_mode: substring expected_stdout_lines: - '{"approved":true}' -background: true timeout_seconds: 90 --> @@ -495,7 +485,7 @@ timeout_seconds: 90 To send the event you can run: ```sh -sleep 65 && curl -X POST "localhost:8080/wfp/suspendresume/continue?orderId=456&decision=true" -H 'Content-Type: application/json' +curl -X POST "localhost:8080/wfp/suspendresume/continue?orderId=456&decision=true" -H 'Content-Type: application/json' --retry 10 --max-time 20 --retry-all-errors --retry-max-time 90 ``` From f8fcfd2574510d205590167680785944ec15a070 Mon Sep 17 00:00:00 2001 From: Javier Aliaga Date: Fri, 29 Aug 2025 09:56:06 +0200 Subject: [PATCH 13/22] Fix CVEs (#1529) * chore: Read test certs from resources folder This will avoid importing bouncy castle library just for tests Signed-off-by: Javier Aliaga * ci: Split unit test and integration tests Signed-off-by: Javier Aliaga --------- Signed-off-by: Javier Aliaga --- .github/workflows/build.yml | 47 ++- sdk/pom.xml | 12 - .../java/io/dapr/utils/NetworkUtilsTest.java | 371 +++++------------- sdk/src/test/resources/certs/test-ca-cert.key | 52 +++ sdk/src/test/resources/certs/test-ca-cert.pem | 31 ++ sdk/src/test/resources/certs/test-cert.key | 52 +++ sdk/src/test/resources/certs/test-cert.pem | 31 ++ 7 files changed, 303 insertions(+), 293 deletions(-) create mode 100644 sdk/src/test/resources/certs/test-ca-cert.key create mode 100644 sdk/src/test/resources/certs/test-ca-cert.pem create mode 100644 sdk/src/test/resources/certs/test-cert.key create mode 100644 sdk/src/test/resources/certs/test-cert.pem diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 58e986dd4b..186370aae3 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -15,6 +15,35 @@ on: - release-* jobs: + test: + name: "Unit tests" + runs-on: ubuntu-latest + timeout-minutes: 30 + continue-on-error: false + env: + JDK_VER: 17 + steps: + - uses: actions/checkout@v5 + - name: Set up OpenJDK ${{ env.JDK_VER }} + uses: actions/setup-java@v4 + with: + distribution: 'temurin' + java-version: ${{ env.JDK_VER }} + - name: Run tests + run: ./mvnw clean install -B -q + - name: Codecov + uses: codecov/codecov-action@v5.5.0 + - name: Upload test report for sdk + uses: actions/upload-artifact@v4 + with: + name: test-dapr-java-sdk-jdk${{ env.JDK_VER }} + path: sdk/target/jacoco-report/ + - name: Upload test report for sdk-actors + uses: actions/upload-artifact@v4 + with: + name: report-dapr-java-sdk-actors-jdk${{ env.JDK_VER }} + path: sdk-actors/target/jacoco-report/ + build: name: "Build jdk:${{ matrix.java }} sb:${{ matrix.spring-boot-display-version }} exp:${{ matrix.experimental }}" runs-on: ubuntu-latest @@ -114,27 +143,11 @@ jobs: /home/runner/.local/bin/toxiproxy-server --version - name: Clean up and install sdk run: ./mvnw clean install -B -q -DskipTests - - name: Unit tests - run: ./mvnw test # making it temporarily verbose. - env: - DOCKER_HOST: ${{steps.setup_docker.outputs.sock}} - - name: Codecov - uses: codecov/codecov-action@v5.5.0 - name: Integration tests using spring boot version ${{ matrix.spring-boot-version }} id: integration_tests run: PRODUCT_SPRING_BOOT_VERSION=${{ matrix.spring-boot-version }} ./mvnw -B -Pintegration-tests verify env: DOCKER_HOST: ${{steps.setup_docker.outputs.sock}} - - name: Upload test report for sdk - uses: actions/upload-artifact@v4 - with: - name: report-dapr-java-sdk-jdk${{ matrix.java }}-sb${{ matrix.spring-boot-version }} - path: sdk/target/jacoco-report/ - - name: Upload test report for sdk-actors - uses: actions/upload-artifact@v4 - with: - name: report-dapr-java-sdk-actors-jdk${{ matrix.java }}-sb${{ matrix.spring-boot-version }} - path: sdk-actors/target/jacoco-report/ - name: Upload failsafe test report for sdk-tests on failure if: ${{ failure() && steps.integration_tests.conclusion == 'failure' }} uses: actions/upload-artifact@v4 @@ -150,7 +163,7 @@ jobs: publish: runs-on: ubuntu-latest - needs: build + needs: [ build, test ] timeout-minutes: 30 env: JDK_VER: 17 diff --git a/sdk/pom.xml b/sdk/pom.xml index 14cb9e08d5..08070b4f0b 100644 --- a/sdk/pom.xml +++ b/sdk/pom.xml @@ -132,18 +132,6 @@ assertj-core ${assertj.version} - - org.bouncycastle - bcprov-jdk15on - 1.70 - test - - - org.bouncycastle - bcpkix-jdk15on - 1.70 - test - io.grpc grpc-netty-shaded diff --git a/sdk/src/test/java/io/dapr/utils/NetworkUtilsTest.java b/sdk/src/test/java/io/dapr/utils/NetworkUtilsTest.java index 3823bef3bd..a8bd189e30 100644 --- a/sdk/src/test/java/io/dapr/utils/NetworkUtilsTest.java +++ b/sdk/src/test/java/io/dapr/utils/NetworkUtilsTest.java @@ -27,20 +27,9 @@ import org.junit.jupiter.api.condition.OS; import java.io.File; -import java.nio.file.Files; -import java.security.KeyPair; -import java.security.KeyPairGenerator; -import java.security.cert.X509Certificate; -import java.util.Date; +import java.util.ArrayList; import java.util.List; import java.util.Map; -import java.util.ArrayList; -import org.bouncycastle.asn1.x500.X500Name; -import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo; -import org.bouncycastle.cert.X509v3CertificateBuilder; -import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter; -import org.bouncycastle.operator.ContentSigner; -import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder; public class NetworkUtilsTest { private final int defaultGrpcPort = 50001; @@ -48,47 +37,6 @@ public class NetworkUtilsTest { private ManagedChannel channel; private static final List channels = new ArrayList<>(); - // Helper method to generate a self-signed certificate for testing - private static KeyPair generateKeyPair() throws Exception { - KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA"); - keyPairGenerator.initialize(2048); - return keyPairGenerator.generateKeyPair(); - } - - private static X509Certificate generateCertificate(KeyPair keyPair) throws Exception { - X500Name issuer = new X500Name("CN=Test Certificate"); - X500Name subject = new X500Name("CN=Test Certificate"); - Date notBefore = new Date(System.currentTimeMillis() - 24 * 60 * 60 * 1000); - Date notAfter = new Date(System.currentTimeMillis() + 365 * 24 * 60 * 60 * 1000L); - SubjectPublicKeyInfo publicKeyInfo = SubjectPublicKeyInfo.getInstance(keyPair.getPublic().getEncoded()); - X509v3CertificateBuilder certBuilder = new X509v3CertificateBuilder( - issuer, - java.math.BigInteger.valueOf(System.currentTimeMillis()), - notBefore, - notAfter, - subject, - publicKeyInfo - ); - - ContentSigner signer = new JcaContentSignerBuilder("SHA256withRSA").build(keyPair.getPrivate()); - X509Certificate cert = new JcaX509CertificateConverter().getCertificate(certBuilder.build(signer)); - return cert; - } - - private static void writeCertificateToFile(X509Certificate cert, File file) throws Exception { - String certPem = "-----BEGIN CERTIFICATE-----\n" + - java.util.Base64.getEncoder().encodeToString(cert.getEncoded()) + - "\n-----END CERTIFICATE-----"; - Files.write(file.toPath(), certPem.getBytes()); - } - - private static void writePrivateKeyToFile(KeyPair keyPair, File file) throws Exception { - String keyPem = "-----BEGIN PRIVATE KEY-----\n" + - java.util.Base64.getEncoder().encodeToString(keyPair.getPrivate().getEncoded()) + - "\n-----END PRIVATE KEY-----"; - Files.write(file.toPath(), keyPem.getBytes()); - } - @AfterEach public void tearDown() { if (channel != null && !channel.isShutdown()) { @@ -157,56 +105,35 @@ public void testBuildGrpcManagedChannel_httpsEndpointWithPort() { @Test public void testBuildGrpcManagedChannelWithTls() throws Exception { - // Generate test certificate and key - KeyPair keyPair = generateKeyPair(); - X509Certificate cert = generateCertificate(keyPair); - - File certFile = File.createTempFile("test-cert", ".pem"); - File keyFile = File.createTempFile("test-key", ".pem"); - try { - writeCertificateToFile(cert, certFile); - writePrivateKeyToFile(keyPair, keyFile); + File certFile = new File(this.getClass().getResource("/certs/test-cert.pem").getFile()); + File keyFile = new File(this.getClass().getResource("/certs/test-cert.key").getFile()); - var properties = new Properties(Map.of( + var properties = new Properties(Map.of( Properties.GRPC_TLS_CERT_PATH.getName(), certFile.getAbsolutePath(), Properties.GRPC_TLS_KEY_PATH.getName(), keyFile.getAbsolutePath() )); - channel = NetworkUtils.buildGrpcManagedChannel(properties); - channels.add(channel); - String expectedAuthority = String.format("%s:%s", defaultSidecarIP, defaultGrpcPort); - Assertions.assertEquals(expectedAuthority, channel.authority()); - } finally { - certFile.delete(); - keyFile.delete(); - } + channel = NetworkUtils.buildGrpcManagedChannel(properties); + channels.add(channel); + String expectedAuthority = String.format("%s:%s", defaultSidecarIP, defaultGrpcPort); + Assertions.assertEquals(expectedAuthority, channel.authority()); + } @Test public void testBuildGrpcManagedChannelWithTlsAndEndpoint() throws Exception { - // Generate test certificate and key - KeyPair keyPair = generateKeyPair(); - X509Certificate cert = generateCertificate(keyPair); - - File certFile = File.createTempFile("test-cert", ".pem"); - File keyFile = File.createTempFile("test-key", ".pem"); - try { - writeCertificateToFile(cert, certFile); - writePrivateKeyToFile(keyPair, keyFile); + File certFile = new File(this.getClass().getResource("/certs/test-cert.pem").getFile()); + File keyFile = new File(this.getClass().getResource("/certs/test-cert.key").getFile()); - var properties = new Properties(Map.of( - Properties.GRPC_TLS_CERT_PATH.getName(), certFile.getAbsolutePath(), - Properties.GRPC_TLS_KEY_PATH.getName(), keyFile.getAbsolutePath(), - Properties.GRPC_ENDPOINT.getName(), "https://example.com:443" - )); + var properties = new Properties(Map.of( + Properties.GRPC_TLS_CERT_PATH.getName(), certFile.getAbsolutePath(), + Properties.GRPC_TLS_KEY_PATH.getName(), keyFile.getAbsolutePath(), + Properties.GRPC_ENDPOINT.getName(), "https://example.com:443" + )); - channel = NetworkUtils.buildGrpcManagedChannel(properties); - channels.add(channel); - Assertions.assertEquals("example.com:443", channel.authority()); - } finally { - certFile.delete(); - keyFile.delete(); - } + channel = NetworkUtils.buildGrpcManagedChannel(properties); + channels.add(channel); + Assertions.assertEquals("example.com:443", channel.authority()); } @Test @@ -229,49 +156,32 @@ public void testBuildGrpcManagedChannelWithTlsAndUnixSocket() throws Exception { System.getProperty("os.name").toLowerCase().contains("mac")); // Generate test certificate and key - KeyPair keyPair = generateKeyPair(); - X509Certificate cert = generateCertificate(keyPair); - - File certFile = File.createTempFile("test-cert", ".pem"); - File keyFile = File.createTempFile("test-key", ".pem"); - try { - writeCertificateToFile(cert, certFile); - writePrivateKeyToFile(keyPair, keyFile); + File certFile = new File(this.getClass().getResource("/certs/test-cert.pem").getFile()); + File keyFile = new File(this.getClass().getResource("/certs/test-cert.key").getFile()); - var properties = new Properties(Map.of( - Properties.GRPC_TLS_CERT_PATH.getName(), certFile.getAbsolutePath(), - Properties.GRPC_TLS_KEY_PATH.getName(), keyFile.getAbsolutePath(), - Properties.GRPC_ENDPOINT.getName(), "unix:/tmp/test.sock" - )); + var properties = new Properties(Map.of( + Properties.GRPC_TLS_CERT_PATH.getName(), certFile.getAbsolutePath(), + Properties.GRPC_TLS_KEY_PATH.getName(), keyFile.getAbsolutePath(), + Properties.GRPC_ENDPOINT.getName(), "unix:/tmp/test.sock" + )); - // For Unix sockets, we expect an exception if the platform doesn't support it - try { - channel = NetworkUtils.buildGrpcManagedChannel(properties); - channels.add(channel); - // If we get here, Unix sockets are supported - Assertions.assertNotNull(channel.authority(), "Channel authority should not be null"); - } catch (Exception e) { - // If we get here, Unix sockets are not supported - Assertions.assertTrue(e.getMessage().contains("DomainSocketAddress")); - } - } finally { - certFile.delete(); - keyFile.delete(); + // For Unix sockets, we expect an exception if the platform doesn't support it + try { + channel = NetworkUtils.buildGrpcManagedChannel(properties); + channels.add(channel); + // If we get here, Unix sockets are supported + Assertions.assertNotNull(channel.authority(), "Channel authority should not be null"); + } catch (Exception e) { + // If we get here, Unix sockets are not supported + Assertions.assertTrue(e.getMessage().contains("DomainSocketAddress")); } + } @Test public void testBuildGrpcManagedChannelWithTlsAndDnsAuthority() throws Exception { - // Generate test certificate and key - KeyPair keyPair = generateKeyPair(); - X509Certificate cert = generateCertificate(keyPair); - - File certFile = File.createTempFile("test-cert", ".pem"); - File keyFile = File.createTempFile("test-key", ".pem"); - try { - writeCertificateToFile(cert, certFile); - writePrivateKeyToFile(keyPair, keyFile); - + File certFile = new File(this.getClass().getResource("/certs/test-cert.pem").getFile()); + File keyFile = new File(this.getClass().getResource("/certs/test-cert.key").getFile()); var properties = new Properties(Map.of( Properties.GRPC_TLS_CERT_PATH.getName(), certFile.getAbsolutePath(), Properties.GRPC_TLS_KEY_PATH.getName(), keyFile.getAbsolutePath(), @@ -281,44 +191,26 @@ public void testBuildGrpcManagedChannelWithTlsAndDnsAuthority() throws Exception channel = NetworkUtils.buildGrpcManagedChannel(properties); channels.add(channel); Assertions.assertEquals("example.com:443", channel.authority()); - } finally { - certFile.delete(); - keyFile.delete(); - } + } @Test public void testBuildGrpcManagedChannelWithTlsAndCaCert() throws Exception { - // Generate test CA certificate - KeyPair caKeyPair = generateKeyPair(); - X509Certificate caCert = generateCertificate(caKeyPair); - - File caCertFile = File.createTempFile("test-ca-cert", ".pem"); - try { - writeCertificateToFile(caCert, caCertFile); + File caCertFile = new File(this.getClass().getResource("/certs/test-ca-cert.pem").getFile()); - var properties = new Properties(Map.of( - Properties.GRPC_TLS_CA_PATH.getName(), caCertFile.getAbsolutePath() - )); + var properties = new Properties(Map.of( + Properties.GRPC_TLS_CA_PATH.getName(), caCertFile.getAbsolutePath() + )); - channel = NetworkUtils.buildGrpcManagedChannel(properties); - channels.add(channel); - String expectedAuthority = String.format("%s:%s", defaultSidecarIP, defaultGrpcPort); - Assertions.assertEquals(expectedAuthority, channel.authority()); - } finally { - caCertFile.delete(); - } + channel = NetworkUtils.buildGrpcManagedChannel(properties); + channels.add(channel); + String expectedAuthority = String.format("%s:%s", defaultSidecarIP, defaultGrpcPort); + Assertions.assertEquals(expectedAuthority, channel.authority()); } @Test public void testBuildGrpcManagedChannelWithTlsAndCaCertAndEndpoint() throws Exception { - // Generate test CA certificate - KeyPair caKeyPair = generateKeyPair(); - X509Certificate caCert = generateCertificate(caKeyPair); - - File caCertFile = File.createTempFile("test-ca-cert", ".pem"); - try { - writeCertificateToFile(caCert, caCertFile); + File caCertFile = new File(this.getClass().getResource("/certs/test-ca-cert.pem").getFile()); var properties = new Properties(Map.of( Properties.GRPC_TLS_CA_PATH.getName(), caCertFile.getAbsolutePath(), @@ -328,9 +220,7 @@ public void testBuildGrpcManagedChannelWithTlsAndCaCertAndEndpoint() throws Exce channel = NetworkUtils.buildGrpcManagedChannel(properties); channels.add(channel); Assertions.assertEquals("example.com:443", channel.authority()); - } finally { - caCertFile.delete(); - } + } @Test @@ -346,37 +236,22 @@ public void testBuildGrpcManagedChannelWithInvalidCaCert() { @Test public void testBuildGrpcManagedChannelWithMtlsAndCaCert() throws Exception { - // Generate test certificates - KeyPair caKeyPair = generateKeyPair(); - X509Certificate caCert = generateCertificate(caKeyPair); - KeyPair clientKeyPair = generateKeyPair(); - X509Certificate clientCert = generateCertificate(clientKeyPair); - - File caCertFile = File.createTempFile("test-ca-cert", ".pem"); - File clientCertFile = File.createTempFile("test-client-cert", ".pem"); - File clientKeyFile = File.createTempFile("test-client-key", ".pem"); - try { - writeCertificateToFile(caCert, caCertFile); - writeCertificateToFile(clientCert, clientCertFile); - writePrivateKeyToFile(clientKeyPair, clientKeyFile); + File caCertFile = new File(this.getClass().getResource("/certs/test-ca-cert.pem").getFile()); + File clientCertFile = new File(this.getClass().getResource("/certs/test-cert.pem").getFile()); + File clientKeyFile = new File(this.getClass().getResource("/certs/test-cert.key").getFile()); - // Test mTLS with both client certs and CA cert - var properties = new Properties(Map.of( - Properties.GRPC_TLS_CA_PATH.getName(), caCertFile.getAbsolutePath(), - Properties.GRPC_TLS_CERT_PATH.getName(), clientCertFile.getAbsolutePath(), - Properties.GRPC_TLS_KEY_PATH.getName(), clientKeyFile.getAbsolutePath() - )); + // Test mTLS with both client certs and CA cert + var properties = new Properties(Map.of( + Properties.GRPC_TLS_CA_PATH.getName(), caCertFile.getAbsolutePath(), + Properties.GRPC_TLS_CERT_PATH.getName(), clientCertFile.getAbsolutePath(), + Properties.GRPC_TLS_KEY_PATH.getName(), clientKeyFile.getAbsolutePath() + )); - channel = NetworkUtils.buildGrpcManagedChannel(properties); - channels.add(channel); - String expectedAuthority = String.format("%s:%s", defaultSidecarIP, defaultGrpcPort); - Assertions.assertEquals(expectedAuthority, channel.authority()); - Assertions.assertFalse(channel.isTerminated(), "Channel should be active"); - } finally { - caCertFile.delete(); - clientCertFile.delete(); - clientKeyFile.delete(); - } + channel = NetworkUtils.buildGrpcManagedChannel(properties); + channels.add(channel); + String expectedAuthority = String.format("%s:%s", defaultSidecarIP, defaultGrpcPort); + Assertions.assertEquals(expectedAuthority, channel.authority()); + Assertions.assertFalse(channel.isTerminated(), "Channel should be active"); } @Test @@ -463,57 +338,40 @@ private static void testGrpcEndpointParsingErrorScenario(String grpcEndpointEnvV @Test public void testBuildGrpcManagedChannelWithCaCertAndUnixSocket() throws Exception { // Skip test if Unix domain sockets are not supported - Assumptions.assumeTrue(System.getProperty("os.name").toLowerCase().contains("linux") || - System.getProperty("os.name").toLowerCase().contains("mac")); + Assumptions.assumeTrue(System.getProperty("os.name").toLowerCase().contains("linux") || + System.getProperty("os.name").toLowerCase().contains("mac")); - // Generate test CA certificate - KeyPair caKeyPair = generateKeyPair(); - X509Certificate caCert = generateCertificate(caKeyPair); - - File caCertFile = File.createTempFile("test-ca-cert", ".pem"); - try { - writeCertificateToFile(caCert, caCertFile); + File caCertFile = new File(this.getClass().getResource("/certs/test-ca-cert.pem").getFile()); - var properties = new Properties(Map.of( - Properties.GRPC_TLS_CA_PATH.getName(), caCertFile.getAbsolutePath(), - Properties.GRPC_ENDPOINT.getName(), "unix:/tmp/test.sock" - )); + var properties = new Properties(Map.of( + Properties.GRPC_TLS_CA_PATH.getName(), caCertFile.getAbsolutePath(), + Properties.GRPC_ENDPOINT.getName(), "unix:/tmp/test.sock" + )); - // For Unix sockets, we expect an exception if the platform doesn't support it - try { - channel = NetworkUtils.buildGrpcManagedChannel(properties); - channels.add(channel); - Assertions.assertNotNull(channel.authority(), "Channel authority should not be null"); - } catch (Exception e) { - // If we get here, Unix sockets are not supported - Assertions.assertTrue(e.getMessage().contains("DomainSocketAddress")); - } - } finally { - caCertFile.delete(); + // For Unix sockets, we expect an exception if the platform doesn't support it + try { + channel = NetworkUtils.buildGrpcManagedChannel(properties); + channels.add(channel); + Assertions.assertNotNull(channel.authority(), "Channel authority should not be null"); + } catch (Exception e) { + // If we get here, Unix sockets are not supported + Assertions.assertTrue(e.getMessage().contains("DomainSocketAddress")); } + } @Test public void testBuildGrpcManagedChannelWithCaCertAndDnsAuthority() throws Exception { - // Generate test CA certificate - KeyPair caKeyPair = generateKeyPair(); - X509Certificate caCert = generateCertificate(caKeyPair); - - File caCertFile = File.createTempFile("test-ca-cert", ".pem"); - try { - writeCertificateToFile(caCert, caCertFile); + File caCertFile = new File(this.getClass().getResource("/certs/test-ca-cert.pem").getFile()); - var properties = new Properties(Map.of( - Properties.GRPC_TLS_CA_PATH.getName(), caCertFile.getAbsolutePath(), - Properties.GRPC_ENDPOINT.getName(), "dns://authority:53/example.com:443" - )); + var properties = new Properties(Map.of( + Properties.GRPC_TLS_CA_PATH.getName(), caCertFile.getAbsolutePath(), + Properties.GRPC_ENDPOINT.getName(), "dns://authority:53/example.com:443" + )); - channel = NetworkUtils.buildGrpcManagedChannel(properties); - channels.add(channel); - Assertions.assertEquals("example.com:443", channel.authority()); - } finally { - caCertFile.delete(); - } + channel = NetworkUtils.buildGrpcManagedChannel(properties); + channels.add(channel); + Assertions.assertEquals("example.com:443", channel.authority()); } @Test @@ -536,43 +394,28 @@ public void testBuildGrpcManagedChannelWithInsecureTls() throws Exception { @Test public void testBuildGrpcManagedChannelWithInsecureTlsAndMtls() throws Exception { - // Generate test certificates - KeyPair caKeyPair = generateKeyPair(); - X509Certificate caCert = generateCertificate(caKeyPair); - KeyPair clientKeyPair = generateKeyPair(); - X509Certificate clientCert = generateCertificate(clientKeyPair); - - File caCertFile = File.createTempFile("test-ca-cert", ".pem"); - File clientCertFile = File.createTempFile("test-client-cert", ".pem"); - File clientKeyFile = File.createTempFile("test-client-key", ".pem"); - try { - writeCertificateToFile(caCert, caCertFile); - writeCertificateToFile(clientCert, clientCertFile); - writePrivateKeyToFile(clientKeyPair, clientKeyFile); + File caCertFile = new File(this.getClass().getResource("/certs/test-ca-cert.pem").getFile()); + File clientCertFile = new File(this.getClass().getResource("/certs/test-cert.pem").getFile()); + File clientKeyFile = new File(this.getClass().getResource("/certs/test-cert.key").getFile()); - // Test that insecure TLS still works with mTLS settings - // The client certs should be ignored since we're using InsecureTrustManagerFactory - var properties = new Properties(Map.of( - Properties.GRPC_TLS_INSECURE.getName(), "true", - Properties.GRPC_TLS_CA_PATH.getName(), caCertFile.getAbsolutePath(), - Properties.GRPC_TLS_CERT_PATH.getName(), clientCertFile.getAbsolutePath(), - Properties.GRPC_TLS_KEY_PATH.getName(), clientKeyFile.getAbsolutePath(), - Properties.GRPC_ENDPOINT.getName(), "dns:///example.com:443?tls=true" - )); + // Test that insecure TLS still works with mTLS settings + // The client certs should be ignored since we're using InsecureTrustManagerFactory + var properties = new Properties(Map.of( + Properties.GRPC_TLS_INSECURE.getName(), "true", + Properties.GRPC_TLS_CA_PATH.getName(), caCertFile.getAbsolutePath(), + Properties.GRPC_TLS_CERT_PATH.getName(), clientCertFile.getAbsolutePath(), + Properties.GRPC_TLS_KEY_PATH.getName(), clientKeyFile.getAbsolutePath(), + Properties.GRPC_ENDPOINT.getName(), "dns:///example.com:443?tls=true" + )); - channel = NetworkUtils.buildGrpcManagedChannel(properties); - channels.add(channel); - - // Verify the channel is created with the correct authority - Assertions.assertEquals("example.com:443", channel.authority()); - - // Verify the channel is active and using TLS (not plaintext) - Assertions.assertFalse(channel.isTerminated(), "Channel should be active"); - } finally { - caCertFile.delete(); - clientCertFile.delete(); - clientKeyFile.delete(); - } + channel = NetworkUtils.buildGrpcManagedChannel(properties); + channels.add(channel); + + // Verify the channel is created with the correct authority + Assertions.assertEquals("example.com:443", channel.authority()); + + // Verify the channel is active and using TLS (not plaintext) + Assertions.assertFalse(channel.isTerminated(), "Channel should be active"); } @Test diff --git a/sdk/src/test/resources/certs/test-ca-cert.key b/sdk/src/test/resources/certs/test-ca-cert.key new file mode 100644 index 0000000000..eac281d7aa --- /dev/null +++ b/sdk/src/test/resources/certs/test-ca-cert.key @@ -0,0 +1,52 @@ +-----BEGIN PRIVATE KEY----- +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 +fg8QEv831uJb0uItcjDy5Yr0TEYMqg== +-----END PRIVATE KEY----- diff --git a/sdk/src/test/resources/certs/test-ca-cert.pem b/sdk/src/test/resources/certs/test-ca-cert.pem new file mode 100644 index 0000000000..087998d230 --- /dev/null +++ b/sdk/src/test/resources/certs/test-ca-cert.pem @@ -0,0 +1,31 @@ +-----BEGIN CERTIFICATE----- +MIIFUzCCAzugAwIBAgIUFHmlZNi1305lvonC4ePOulB207UwDQYJKoZIhvcNAQEL +BQAwOTENMAsGA1UECgwEREFQUjENMAsGA1UECwwEREFQUjEZMBcGA1UEAwwQVEVT +VCBDRVJUSUZJQ0FURTAeFw0yNTA4MjgxNTI4MTlaFw0yNzA4MjgxNTI4MTlaMDkx +DTALBgNVBAoMBERBUFIxDTALBgNVBAsMBERBUFIxGTAXBgNVBAMMEFRFU1QgQ0VS +VElGSUNBVEUwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDbzg+ow2pe ++tWKSISVIF6kFi9VYReq0sJ8gJ+mQRkBK7Hs8G0WD9KS+Ebg15CTEulNbyp3sqPa +28tD1bq69ST8aMjUxD8NyWyYd7NQSd7pWHuR+NMDsh908W+JDyTxGgonXFaU5s8m ++FAS25nC8dzEgolxrQlVraXld1ZxMVW/OnOdl4dlSsXpGR4Zwv2S4BrppQvGhGTK +aURuOI1mot0IZVnV4acg5iK+2wPvDD2UJp6fMR6PplKrgb8dV2SqEEEVPwsXY0nb +idRb3yJ6VRtibkbu7u987BpEEaNeSmsC0BOIeHj90Jc+ilDQEgVRkMvP9zSPh6yY +4kOuMevof2J86FTRZWo7b9u1BG/2WoLeOPk00kAzN2jmk4hnytknWz1G0dxVZSJr +OcLHv9tWKk4TI3l54DrKw+n3pQA2Qs22P97n6d151mi3Yr45YtypmQhn4f4tsLxT +16mp2Vs/D4vU1M4jldpi9vDin+R4wIgDxXThUUd3c6NqrhyRiLteRaZVVsWeudi/ +jZt7H676S5lVdLTsU1jmOxef1mTzJxNAhw7VClq8YXeq8Z65Mj84fARxEh+WgN31 +5+JlIE6gPdYz7A+h5pupDVXJdhOViafQMBsAkUWautz4n47F5X+oB53rKEvRjjgV +sSVNB3oLBWCoXPxfpANQjNXky9QRb2Ib1wIDAQABo1MwUTAdBgNVHQ4EFgQUnBy0 +t/3jxb0LK0SWaKjOlRlebmowHwYDVR0jBBgwFoAUnBy0t/3jxb0LK0SWaKjOlRle +bmowDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAgEAh/5qDtYaiTWN +3cX0H/ucdsT1fzVCiWmniRBrvGgLOJ4VfBpcGAeyt3nTOCGBpLIHi6M47mv6+DA8 +GVA+k4FgAbFRWgSR5zDsOOC0/jTTEMpcrz13DgpCeh/Jj0MdNe3nzkC+eAT2bp07 +ZYLrO++N+IEpDEuDz3YlqIpgKpEREoGKWXrXcCEdsAUbIjOSWBfLBTuF8x11IVi7 +V4y3UUVTg6aA8ILmtWStoBuVroOH/HY/8RuQztUejjtd3PIEzPRgITekpWEDxI1D +Ycc7ZgdxEYmzXTvRAmLkIAoSPR5U61i09SSHmCs9hoWUGQGxSsxBFloTH6IoJFls +VDeWw3Shzu1PSPP5NedGgdRC8GKjOVDAgXzYPSoTYab5mGHTbL3HtRELthCmQSaC +SrNwKjd1MmhJ6yrjxKruE9mpSHUu50IDIP+PyMokWMRf504eGvPJstvD6+I4sUS3 +/Owu0eGM3incgG/ulAWpRDCXRq49JpAoq7evbyBQyzDfrCSN0U6sgjBVlJuoEuXD +t1cNmvPxf0730HxQYrq1nYahdt5Mi9+Pv8ublylKuGeYiWqiSeSE6wRRTktL7Xmf +BotM8yvJBhpY00GAPSxXQ2Kl+OielJq9QSCOgtQkMCMzKMsfbO+YA+WvMICe+GNJ +sfrgLw0imYT1npKJbNtg/vRxTZFj9hc= +-----END CERTIFICATE----- diff --git a/sdk/src/test/resources/certs/test-cert.key b/sdk/src/test/resources/certs/test-cert.key new file mode 100644 index 0000000000..87b958ab6d --- /dev/null +++ b/sdk/src/test/resources/certs/test-cert.key @@ -0,0 +1,52 @@ +-----BEGIN PRIVATE KEY----- +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 +mfPgTPwyRZiGKrz43GpgD0Zg/eytPw== +-----END PRIVATE KEY----- diff --git a/sdk/src/test/resources/certs/test-cert.pem b/sdk/src/test/resources/certs/test-cert.pem new file mode 100644 index 0000000000..00a8155f78 --- /dev/null +++ b/sdk/src/test/resources/certs/test-cert.pem @@ -0,0 +1,31 @@ +-----BEGIN CERTIFICATE----- +MIIFUzCCAzugAwIBAgIUbPytgC1pfbqeBSbdOok03mPcoU8wDQYJKoZIhvcNAQEL +BQAwOTENMAsGA1UECgwEREFQUjENMAsGA1UECwwEREFQUjEZMBcGA1UEAwwQVGVz +dCBDZXJ0aWZpY2F0ZTAeFw0yNTA4MjgxNTIzMzJaFw0yNzA4MjgxNTIzMzJaMDkx +DTALBgNVBAoMBERBUFIxDTALBgNVBAsMBERBUFIxGTAXBgNVBAMMEFRlc3QgQ2Vy +dGlmaWNhdGUwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCX9WC+9qfC +u7LhLTkm1XpYnIFowqlIAARHxWv08xbOIm/4bUtruuy0nOEVzzYiu8fdRP2LsBp9 +iKuRJzBw6QOeASeepCorOFnU24bG3oAdjPHp7Bj76tM1GW5H8izOlln22My4asaO +P4kxC+LQt7aolF1Ca7A9J3TNEBxSaowUpCCDtwNnBTPoFa28vhFjga32BnNEZCTP +WCrn97WfDKj4trb+lQIt77jSBpPmwBJIqsOaM02uIlnF8MElX2TEUfMFGWAzDBml +lB80cZHybC2Ic2nrYKuuHtpZrtMM6atGstxiHDt002p6yxUUEbcXE1SvB6ZGuBDw +9frpF7J5xnRQ7PAaKl17hXinnOPG/b3xtvOrx2rewWHHUxfCDzzaDcr1+31zfoW8 +3wcwfcuuS8E9iO4mIIYKoB+3C0430IMlqv61BVlHB9FajSEXhg8KIRDB8dZZGX4Y +95teRGv4s69Wr0gFFrPG5XJ0m4gS7mpkJeYwTrvtgb2Rm+D7vjxKR4xW8zSRnj4W +644YSpBI9n/fCw3wmWAvs+2wt1m6JcvXyOFJPY96BQWE3eIGV052OCc69rXUhEV/ +vFRVNn4UJMCGptpJZiTM+/jvC94YEY2GzMu0jfuBYrnzeeasRnnVh6gfFKXui3xK +GApmMoqfd/2DogdmMXilZQ4SHinS6Bj0yQIDAQABo1MwUTAdBgNVHQ4EFgQUuj4S +u+I/pBSJFGVoCK4y+QvGm/QwHwYDVR0jBBgwFoAUuj4Su+I/pBSJFGVoCK4y+QvG +m/QwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAgEAR18zFnyf22+h +bLT8ab0fD6pLQUW0/yr0PpsNNrsnNbTMr+tgOQDZgi/jpnG8eQmyA5aVjBxgxBNq +BhXFMixVputyafbb+fCIGvwWAMGtKL3Jf2yYfn6IhMftMB2e3xp4hUNBfRC92fVT +McypKFf/bWPxk7ZDeZGwNK0CViB2XNDCPWvR+RxQI4stilpN47/fAQdpnAmcDVi6 +wVljILTaPhgpWj0Q3c6ccdgFE8ETRQK46dDW6C2KstIjqOxP4Go5HQw5bGTQVRPw +bX2v7kadLfFDJJDwmCRUNzQJfWM+8qROy8YgexFe5rBUkOOFCz2Wd2I0LjiN8SrP +aY3iEoZQO/bIUPJsi3qtLgb9HDZ6iXeB1SHEXnn/l0b1zpb2kumdhiNif2s1NXsw +LQV7xai3xrdT96fnWElqD39gHunLO2hCE4ra7YJ3yZnXyi21EdErhpCaD1aPo96d +0m/2rbIfafrBZFdcu4hvS56qtnVajOfXaN5bCKyRsByA8Ebv2XlWWWCRDii5ft/W +RYrRDZhC6t9dZNv1ObhDLzx/2FNq82lxhi4VCwlAy6Qdc8kY7uh92IWUUQoLonQB +QI0QuROI9W9Vc/DUJFvts/qCKWtD0XdoTVlZc/1B2WFbiwZ5U04x5inFBtudvyEL +ZEyQNL0MVlOgpxK3igY4xKM8r+m4bi4= +-----END CERTIFICATE----- From 8a83b68df3cc561901af5e6165aa6e0ae4217223 Mon Sep 17 00:00:00 2001 From: Javier Aliaga Date: Fri, 29 Aug 2025 11:40:14 +0200 Subject: [PATCH 14/22] fix: Use correct snapshot url (#1530) Signed-off-by: Javier Aliaga --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index f0e09d18b3..3c8178010a 100644 --- a/pom.xml +++ b/pom.xml @@ -70,7 +70,7 @@ ossrh - https://ossrh-staging-api.central.sonatype.com/content/repositories/snapshots + https://central.sonatype.com/repository/maven-snapshots/ localDocsDirectory From 1f496a8bf3884b9f8cf34f70e7e70989c70a2330 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 31 Aug 2025 01:34:25 +0300 Subject: [PATCH 15/22] Bump actions/setup-java from 4 to 5 (#1512) Signed-off-by: Javier Aliaga --- .github/workflows/build.yml | 4 ++-- .github/workflows/create-release.yml | 2 +- .github/workflows/validate-docs.yml | 2 +- .github/workflows/validate.yml | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 186370aae3..4f7fed9069 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -81,7 +81,7 @@ jobs: run: docker version - uses: actions/checkout@v5 - name: Set up OpenJDK ${{ env.JDK_VER }} - uses: actions/setup-java@v4 + uses: actions/setup-java@v5 with: distribution: 'temurin' java-version: ${{ env.JDK_VER }} @@ -174,7 +174,7 @@ jobs: steps: - uses: actions/checkout@v5 - name: Set up OpenJDK ${{ env.JDK_VER }} - uses: actions/setup-java@v4 + uses: actions/setup-java@v5 with: distribution: 'temurin' java-version: ${{ env.JDK_VER }} diff --git a/.github/workflows/create-release.yml b/.github/workflows/create-release.yml index fe67d1fe88..397aea3528 100644 --- a/.github/workflows/create-release.yml +++ b/.github/workflows/create-release.yml @@ -35,7 +35,7 @@ jobs: token: ${{ secrets.DAPR_BOT_TOKEN }} persist-credentials: false - name: Set up OpenJDK ${{ env.JDK_VER }} - uses: actions/setup-java@v4 + uses: actions/setup-java@v5 with: distribution: 'temurin' java-version: ${{ env.JDK_VER }} diff --git a/.github/workflows/validate-docs.yml b/.github/workflows/validate-docs.yml index e0c50c9f87..20c9f6c382 100644 --- a/.github/workflows/validate-docs.yml +++ b/.github/workflows/validate-docs.yml @@ -24,7 +24,7 @@ jobs: steps: - uses: actions/checkout@v5 - name: Set up OpenJDK ${{ env.JDK_VER }} - uses: actions/setup-java@v4 + uses: actions/setup-java@v5 with: distribution: 'temurin' java-version: ${{ env.JDK_VER }} diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index 165d44e20d..5532b167bb 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -45,7 +45,7 @@ jobs: steps: - uses: actions/checkout@v5 - name: Set up OpenJDK ${{ env.JDK_VER }} - uses: actions/setup-java@v4 + uses: actions/setup-java@v5 with: distribution: 'temurin' java-version: ${{ env.JDK_VER }} From bea7cf8875abd23c0b88718b260c4684d03741f1 Mon Sep 17 00:00:00 2001 From: Javier Aliaga Date: Mon, 1 Sep 2025 12:58:05 +0200 Subject: [PATCH 16/22] chore: Bump dapr version (#1539) * chore: Bump dapr version Signed-off-by: Javier Aliaga * chore: Fix conversation tests Only one response expected Signed-off-by: Javier Aliaga --------- Signed-off-by: Javier Aliaga --- .github/workflows/build.yml | 2 +- .github/workflows/validate.yml | 2 +- .../content/en/java-sdk-docs/spring-boot/_index.md | 4 ++-- .../conversations/DaprConversationIT.java | 10 +++------- spring-boot-examples/kubernetes/README.md | 2 +- .../io/dapr/testcontainers/DaprContainerConstants.java | 2 +- 6 files changed, 9 insertions(+), 13 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 4f7fed9069..824ce07549 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -68,7 +68,7 @@ jobs: GOPROXY: https://proxy.golang.org JDK_VER: ${{ matrix.java }} DAPR_CLI_VER: 1.15.0 - DAPR_RUNTIME_VER: 1.16.0-rc.3 + DAPR_RUNTIME_VER: 1.16.0-rc.5 DAPR_INSTALL_URL: https://raw.githubusercontent.com/dapr/cli/v1.15.0/install/install.sh DAPR_CLI_REF: DAPR_REF: diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index 5532b167bb..0a73978c7a 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -38,7 +38,7 @@ jobs: GOPROXY: https://proxy.golang.org JDK_VER: ${{ matrix.java }} DAPR_CLI_VER: 1.15.0 - DAPR_RUNTIME_VER: 1.16.0-rc.3 + DAPR_RUNTIME_VER: 1.16.0-rc.5 DAPR_INSTALL_URL: https://raw.githubusercontent.com/dapr/cli/v1.15.0/install/install.sh DAPR_CLI_REF: DAPR_REF: diff --git a/daprdocs/content/en/java-sdk-docs/spring-boot/_index.md b/daprdocs/content/en/java-sdk-docs/spring-boot/_index.md index 270d9899b5..c6bb4605ef 100644 --- a/daprdocs/content/en/java-sdk-docs/spring-boot/_index.md +++ b/daprdocs/content/en/java-sdk-docs/spring-boot/_index.md @@ -95,7 +95,7 @@ public class DaprTestContainersConfig { @ServiceConnection public DaprContainer daprContainer(Network daprNetwork, PostgreSQLContainer postgreSQLContainer){ - return new DaprContainer("daprio/daprd:1.16.0-rc.3") + return new DaprContainer("daprio/daprd:1.16.0-rc.5") .withAppName("producer-app") .withNetwork(daprNetwork) .withComponent(new Component("kvstore", "state.postgresql", "v1", STATE_STORE_PROPERTIES)) @@ -250,7 +250,7 @@ Finally, because Dapr PubSub requires a bidirectional connection between your ap @ServiceConnection public DaprContainer daprContainer(Network daprNetwork, PostgreSQLContainer postgreSQLContainer, RabbitMQContainer rabbitMQContainer){ - return new DaprContainer("daprio/daprd:1.16.0-rc.3") + return new DaprContainer("daprio/daprd:1.16.0-rc.5") .withAppName("producer-app") .withNetwork(daprNetwork) .withComponent(new Component("kvstore", "state.postgresql", "v1", STATE_STORE_PROPERTIES)) diff --git a/sdk-tests/src/test/java/io/dapr/it/testcontainers/conversations/DaprConversationIT.java b/sdk-tests/src/test/java/io/dapr/it/testcontainers/conversations/DaprConversationIT.java index 2301e6fc36..7177d2aab5 100644 --- a/sdk-tests/src/test/java/io/dapr/it/testcontainers/conversations/DaprConversationIT.java +++ b/sdk-tests/src/test/java/io/dapr/it/testcontainers/conversations/DaprConversationIT.java @@ -109,11 +109,9 @@ public void testConversationSDKShouldScrubPIIWhenScrubPIIIsSetInRequestBody() { this.daprPreviewClient.converse(new ConversationRequest("echo", conversationInputList) .setScrubPii(true)).block(); - Assertions.assertEquals("", response.getContextId()); - Assertions.assertEquals("input this ", + Assertions.assertEquals("", response.getContextId()); + Assertions.assertEquals("input this \ninput this ", response.getConversationOutputs().get(0).getResult()); - Assertions.assertEquals("input this ", - response.getConversationOutputs().get(1).getResult()); } @Test @@ -126,9 +124,7 @@ public void testConversationSDKShouldScrubPIIOnlyForTheInputWhereScrubPIIIsSet() this.daprPreviewClient.converse(new ConversationRequest("echo", conversationInputList)).block(); Assertions.assertEquals("", response.getContextId()); - Assertions.assertEquals("input this abcd@gmail.com", + Assertions.assertEquals("input this abcd@gmail.com\ninput this ", response.getConversationOutputs().get(0).getResult()); - Assertions.assertEquals("input this ", - response.getConversationOutputs().get(1).getResult()); } } diff --git a/spring-boot-examples/kubernetes/README.md b/spring-boot-examples/kubernetes/README.md index 2f3fddac8d..d780c2eb82 100644 --- a/spring-boot-examples/kubernetes/README.md +++ b/spring-boot-examples/kubernetes/README.md @@ -30,7 +30,7 @@ Once you have the cluster up and running you can install Dapr: helm repo add dapr https://dapr.github.io/helm-charts/ helm repo update helm upgrade --install dapr dapr/dapr \ ---version=1.16.0-rc.3 \ +--version=1.16.0-rc.5 \ --namespace dapr-system \ --create-namespace \ --wait diff --git a/testcontainers-dapr/src/main/java/io/dapr/testcontainers/DaprContainerConstants.java b/testcontainers-dapr/src/main/java/io/dapr/testcontainers/DaprContainerConstants.java index dd24e0a467..f326073366 100644 --- a/testcontainers-dapr/src/main/java/io/dapr/testcontainers/DaprContainerConstants.java +++ b/testcontainers-dapr/src/main/java/io/dapr/testcontainers/DaprContainerConstants.java @@ -14,7 +14,7 @@ package io.dapr.testcontainers; public interface DaprContainerConstants { - String DAPR_VERSION = "1.16.0-rc.3"; + String DAPR_VERSION = "1.16.0-rc.5"; String DAPR_RUNTIME_IMAGE_TAG = "daprio/daprd:" + DAPR_VERSION; String DAPR_PLACEMENT_IMAGE_TAG = "daprio/placement:" + DAPR_VERSION; String DAPR_SCHEDULER_IMAGE_TAG = "daprio/scheduler:" + DAPR_VERSION; From 7e7351b58499b568334592d88883fa4cf4dbf8d3 Mon Sep 17 00:00:00 2001 From: artur-ciocanu Date: Mon, 1 Sep 2025 19:04:59 +0300 Subject: [PATCH 17/22] Adding logger to WorkflowActivityContext (#1534) * Adding logger to WorkflowActivityContext Signed-off-by: Artur Ciocanu * Fixing a styling issue Signed-off-by: Artur Ciocanu * Add unit tests for workflow activity context Signed-off-by: Artur Ciocanu * Add more unit tests to make Codecov happy Signed-off-by: Artur Ciocanu * Add more unit tests Signed-off-by: Artur Ciocanu * Improve code coverage Signed-off-by: Artur Ciocanu * Improve test names Signed-off-by: Artur Ciocanu --------- Signed-off-by: Artur Ciocanu Co-authored-by: Artur Ciocanu Co-authored-by: salaboy Signed-off-by: Javier Aliaga --- .../DaprClientAutoConfigurationTest.java | 26 +++++++++- .../workflows/WorkflowActivityContext.java | 4 ++ .../DefaultWorkflowActivityContext.java | 29 +++++++++++ .../runtime/DefaultWorkflowContext.java | 1 + .../DefaultWorkflowActivityContextTest.java | 52 +++++++++++++++++++ 5 files changed, 110 insertions(+), 2 deletions(-) create mode 100644 sdk-workflows/src/test/java/io/dapr/workflows/runtime/DefaultWorkflowActivityContextTest.java diff --git a/dapr-spring/dapr-spring-boot-autoconfigure/src/test/java/io/dapr/spring/boot/autoconfigure/client/DaprClientAutoConfigurationTest.java b/dapr-spring/dapr-spring-boot-autoconfigure/src/test/java/io/dapr/spring/boot/autoconfigure/client/DaprClientAutoConfigurationTest.java index f7b3a999ad..139dfc0828 100644 --- a/dapr-spring/dapr-spring-boot-autoconfigure/src/test/java/io/dapr/spring/boot/autoconfigure/client/DaprClientAutoConfigurationTest.java +++ b/dapr-spring/dapr-spring-boot-autoconfigure/src/test/java/io/dapr/spring/boot/autoconfigure/client/DaprClientAutoConfigurationTest.java @@ -25,8 +25,6 @@ import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.test.context.runner.ApplicationContextRunner; -import java.util.Map; - import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -111,6 +109,18 @@ void shouldOverrideGrpcPortIfExists() { verify(builder).withPropertyOverride(Properties.GRPC_PORT, String.valueOf(grpcPort)); } + @Test + @DisplayName("Should override API token if it exists") + void shouldOverrideApiTokenIfExists() { + String apiToken = "token"; + + when(connectionDetails.getApiToken()).thenReturn(apiToken); + + configuration.daprClientBuilder(connectionDetails); + + verify(builder).withPropertyOverride(Properties.API_TOKEN, apiToken); + } + @Test @DisplayName("Should override HTTP endpoint in properties if it exists") void shouldOverrideHttpEndpointInPropertiesIfExists() { @@ -159,6 +169,18 @@ void shouldOverrideGrpcPortPropertiesIfExists() { assertThat(result.getValue(Properties.GRPC_PORT)).isEqualTo(grpcPort); } + @Test + @DisplayName("Should override API token in properties if it exists") + void shouldOverrideApiTokenPropertiesIfExists() { + String apiToken = "token"; + + when(connectionDetails.getApiToken()).thenReturn(apiToken); + + Properties result = configuration.createPropertiesFromConnectionDetails(connectionDetails); + + assertThat(result.getValue(Properties.API_TOKEN)).isEqualTo(apiToken); + } + private static class TestDaprClientAutoConfiguration extends DaprClientAutoConfiguration { private final DaprClientBuilder daprClientBuilder; diff --git a/sdk-workflows/src/main/java/io/dapr/workflows/WorkflowActivityContext.java b/sdk-workflows/src/main/java/io/dapr/workflows/WorkflowActivityContext.java index dedde8901b..2391b8f635 100644 --- a/sdk-workflows/src/main/java/io/dapr/workflows/WorkflowActivityContext.java +++ b/sdk-workflows/src/main/java/io/dapr/workflows/WorkflowActivityContext.java @@ -13,8 +13,12 @@ package io.dapr.workflows; +import org.slf4j.Logger; + public interface WorkflowActivityContext { + Logger getLogger(); + String getName(); String getTaskExecutionId(); diff --git a/sdk-workflows/src/main/java/io/dapr/workflows/runtime/DefaultWorkflowActivityContext.java b/sdk-workflows/src/main/java/io/dapr/workflows/runtime/DefaultWorkflowActivityContext.java index 8de4f7e747..dd30dd8053 100644 --- a/sdk-workflows/src/main/java/io/dapr/workflows/runtime/DefaultWorkflowActivityContext.java +++ b/sdk-workflows/src/main/java/io/dapr/workflows/runtime/DefaultWorkflowActivityContext.java @@ -15,12 +15,15 @@ import io.dapr.durabletask.TaskActivityContext; import io.dapr.workflows.WorkflowActivityContext; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * Wrapper for Durable Task Framework {@link TaskActivityContext}. */ class DefaultWorkflowActivityContext implements WorkflowActivityContext { private final TaskActivityContext innerContext; + private final Logger logger; /** * Constructor for WorkflowActivityContext. @@ -29,10 +32,36 @@ class DefaultWorkflowActivityContext implements WorkflowActivityContext { * @throws IllegalArgumentException if context is null */ public DefaultWorkflowActivityContext(TaskActivityContext context) throws IllegalArgumentException { + this(context, LoggerFactory.getLogger(WorkflowActivityContext.class)); + } + + /** + * Constructor for WorkflowActivityContext. + * + * @param context TaskActivityContext + * @throws IllegalArgumentException if context is null + */ + public DefaultWorkflowActivityContext(TaskActivityContext context, Logger logger) throws IllegalArgumentException { if (context == null) { throw new IllegalArgumentException("Context cannot be null"); } + + if (logger == null) { + throw new IllegalArgumentException("Logger cannot be null"); + } + this.innerContext = context; + this.logger = logger; + } + + /** + * Gets the logger for the current activity. + * + * @return the logger for the current activity + */ + @Override + public Logger getLogger() { + return this.logger; } /** diff --git a/sdk-workflows/src/main/java/io/dapr/workflows/runtime/DefaultWorkflowContext.java b/sdk-workflows/src/main/java/io/dapr/workflows/runtime/DefaultWorkflowContext.java index d12346ca0f..f76eb00445 100644 --- a/sdk-workflows/src/main/java/io/dapr/workflows/runtime/DefaultWorkflowContext.java +++ b/sdk-workflows/src/main/java/io/dapr/workflows/runtime/DefaultWorkflowContext.java @@ -63,6 +63,7 @@ public DefaultWorkflowContext(TaskOrchestrationContext context, Logger logger) if (context == null) { throw new IllegalArgumentException("Context cannot be null"); } + if (logger == null) { throw new IllegalArgumentException("Logger cannot be null"); } diff --git a/sdk-workflows/src/test/java/io/dapr/workflows/runtime/DefaultWorkflowActivityContextTest.java b/sdk-workflows/src/test/java/io/dapr/workflows/runtime/DefaultWorkflowActivityContextTest.java new file mode 100644 index 0000000000..90dfa565ca --- /dev/null +++ b/sdk-workflows/src/test/java/io/dapr/workflows/runtime/DefaultWorkflowActivityContextTest.java @@ -0,0 +1,52 @@ +package io.dapr.workflows.runtime; + +import io.dapr.durabletask.TaskActivityContext; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +class DefaultWorkflowActivityContextTest { + + @Test + @DisplayName("Should successfully create context and return correct values for all methods") + void shouldSuccessfullyCreateContextAndReturnCorrectValuesForAllMethods() { + TaskActivityContext mockInnerContext = mock(TaskActivityContext.class); + DefaultWorkflowActivityContext context = new DefaultWorkflowActivityContext(mockInnerContext); + + when(mockInnerContext.getName()).thenReturn("TestActivity"); + when(mockInnerContext.getInput(any())).thenReturn("TestInput"); + when(mockInnerContext.getTaskExecutionId()).thenReturn("TestExecutionId"); + + assertNotNull(context.getLogger()); + assertEquals("TestActivity", context.getName()); + + String input = context.getInput(String.class); + + assertEquals("TestInput", input); + assertEquals("TestExecutionId", context.getTaskExecutionId()); + } + + @Test + @DisplayName("Should throw IllegalArgumentException when context parameter is null") + void shouldThrowIllegalArgumentExceptionWhenContextParameterIsNull() { + IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> { + new DefaultWorkflowActivityContext(null); + }); + assertEquals("Context cannot be null", exception.getMessage()); + } + + @Test + @DisplayName("Should throw IllegalArgumentException when logger parameter is null") + void shouldThrowIllegalArgumentExceptionWhenLoggerParameterIsNull() { + TaskActivityContext mockInnerContext = mock(TaskActivityContext.class); + + IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> { + new DefaultWorkflowActivityContext(mockInnerContext, null); + }); + assertEquals("Logger cannot be null", exception.getMessage()); + } +} From a13b89d544e86dcb30363cb8c5e91e66fc2186ef Mon Sep 17 00:00:00 2001 From: artur-ciocanu Date: Mon, 1 Sep 2025 20:59:48 +0300 Subject: [PATCH 18/22] Use notimestamp in Javadoc Plugin to disable timestamps in Javadocs (#1538) Signed-off-by: Javier Aliaga --- dapr-spring/pom.xml | 3 +++ pom.xml | 1 + sdk-actors/pom.xml | 3 +++ sdk-autogen/pom.xml | 3 +++ sdk-springboot/pom.xml | 3 +++ sdk-workflows/pom.xml | 3 +++ sdk/pom.xml | 3 +++ testcontainers-dapr/pom.xml | 3 +++ 8 files changed, 22 insertions(+) diff --git a/dapr-spring/pom.xml b/dapr-spring/pom.xml index 4b9df127c0..7f1d28e1c1 100644 --- a/dapr-spring/pom.xml +++ b/dapr-spring/pom.xml @@ -187,6 +187,9 @@ org.apache.maven.plugins maven-javadoc-plugin 3.2.0 + + true + attach-javadocs diff --git a/pom.xml b/pom.xml index 3c8178010a..207808f53b 100644 --- a/pom.xml +++ b/pom.xml @@ -610,6 +610,7 @@ false true site + true io.dapr.examples:io.dapr.springboot:io.dapr.examples.*:io.dapr.springboot.* diff --git a/sdk-actors/pom.xml b/sdk-actors/pom.xml index 9c0a816941..2bcf338120 100644 --- a/sdk-actors/pom.xml +++ b/sdk-actors/pom.xml @@ -86,6 +86,9 @@ org.apache.maven.plugins maven-javadoc-plugin 3.2.0 + + true + attach-javadocs diff --git a/sdk-autogen/pom.xml b/sdk-autogen/pom.xml index cc53cb3ad5..3d504ec029 100644 --- a/sdk-autogen/pom.xml +++ b/sdk-autogen/pom.xml @@ -176,6 +176,9 @@ org.apache.maven.plugins maven-javadoc-plugin 3.2.0 + + true + attach-javadocs diff --git a/sdk-springboot/pom.xml b/sdk-springboot/pom.xml index b43d63e8e0..1e1e0ab5f4 100644 --- a/sdk-springboot/pom.xml +++ b/sdk-springboot/pom.xml @@ -97,6 +97,9 @@ org.apache.maven.plugins maven-javadoc-plugin 3.7.0 + + true + attach-javadocs diff --git a/sdk-workflows/pom.xml b/sdk-workflows/pom.xml index 720a512908..9475e159dd 100644 --- a/sdk-workflows/pom.xml +++ b/sdk-workflows/pom.xml @@ -100,6 +100,9 @@ org.apache.maven.plugins maven-javadoc-plugin 3.2.0 + + true + attach-javadocs diff --git a/sdk/pom.xml b/sdk/pom.xml index 08070b4f0b..b0419bbfa0 100644 --- a/sdk/pom.xml +++ b/sdk/pom.xml @@ -182,6 +182,9 @@ org.apache.maven.plugins maven-javadoc-plugin 3.2.0 + + true + attach-javadocs diff --git a/testcontainers-dapr/pom.xml b/testcontainers-dapr/pom.xml index f404849df2..542de4b89e 100644 --- a/testcontainers-dapr/pom.xml +++ b/testcontainers-dapr/pom.xml @@ -56,6 +56,9 @@ org.apache.maven.plugins maven-javadoc-plugin + + true + attach-javadocs From ae6cd3e37952a2030f2d84e20001763ed7ed8658 Mon Sep 17 00:00:00 2001 From: Cassie Coyle Date: Tue, 2 Sep 2025 07:48:50 -0400 Subject: [PATCH 19/22] Feat Cross App CallActivity (#1468) * cross app ex Signed-off-by: Cassandra Coyle * update protoc cmd Signed-off-by: Cassandra Coyle * feedback Signed-off-by: Cassandra Coyle * builder pattern Signed-off-by: Cassandra Coyle * fix protoc Signed-off-by: Cassandra Coyle * debug log levels for test containers Signed-off-by: Cassandra Coyle * update readme and add debugging info Signed-off-by: Cassandra Coyle * add IT test for cross app call activity Signed-off-by: Cassandra Coyle * cleanup test Signed-off-by: Cassandra Coyle * sysout -> ctx.logger Signed-off-by: Cassandra Coyle * reset pom Signed-off-by: Cassandra Coyle * rm debug lines from readme Signed-off-by: Cassandra Coyle * fix header + rm customports Signed-off-by: Cassandra Coyle * use consts Signed-off-by: Cassandra Coyle * rm waitfor call Signed-off-by: Cassandra Coyle * rm pubsub Signed-off-by: Cassandra Coyle * rm timeout Signed-off-by: Cassandra Coyle * reset empty lines added Signed-off-by: Cassandra Coyle * reset appname for daprcontainer Signed-off-by: Cassandra Coyle * reset empty line diff Signed-off-by: Cassandra Coyle * rm constructor info from readme Signed-off-by: Cassandra Coyle * debug -> info Signed-off-by: Cassandra Coyle * rm super.start Signed-off-by: Cassandra Coyle * reset dapr container diff Signed-off-by: Cassandra Coyle * add test for codecov Signed-off-by: Cassandra Coyle * up timeout time to unblock PR Signed-off-by: Cassandra Coyle * deps: Update durabletask-client to 1.5.10 Signed-off-by: Javier Aliaga * ci: Revert build timeout Signed-off-by: Javier Aliaga * review: Use ctx.getLogger Signed-off-by: Javier Aliaga * chore: Fix review comments Signed-off-by: Javier Aliaga * chore: more review comments fixes Signed-off-by: Javier Aliaga * test: Use testcontainers in CrossApp IT test Signed-off-by: Javier Aliaga * chore: Load classpath for IT with all dependencies Signed-off-by: Javier Aliaga --------- Signed-off-by: Cassandra Coyle Signed-off-by: Javier Aliaga Co-authored-by: Javier Aliaga Signed-off-by: Javier Aliaga --- .github/workflows/build.yml | 2 +- .../java/io/dapr/examples/workflows/README.md | 153 ++++++++++++++ .../crossapp/App2TransformActivity.java | 34 ++++ .../workflows/crossapp/App2Worker.java | 37 ++++ .../crossapp/App3FinalizeActivity.java | 34 ++++ .../workflows/crossapp/App3Worker.java | 37 ++++ .../workflows/crossapp/CrossAppWorker.java | 32 +++ .../workflows/crossapp/CrossAppWorkflow.java | 64 ++++++ .../crossapp/CrossAppWorkflowClient.java | 66 ++++++ .../crossapp/App2TransformActivity.java | 36 ++++ .../workflows/crossapp/App2Worker.java | 40 ++++ .../crossapp/App3FinalizeActivity.java | 36 ++++ .../workflows/crossapp/App3Worker.java | 40 ++++ .../workflows/crossapp/CrossAppWorker.java | 40 ++++ .../workflows/crossapp/CrossAppWorkflow.java | 55 +++++ .../WorkflowsCrossAppCallActivityIT.java | 191 ++++++++++++++++++ sdk-workflows/pom.xml | 2 +- .../dapr/workflows/WorkflowTaskOptions.java | 42 +++- .../runtime/DefaultWorkflowContext.java | 6 +- .../workflows/WorkflowTaskOptionsTest.java | 102 ++++++++++ .../wfp/DaprTestContainersConfig.java | 11 + .../wfp/WorkflowPatternsAppTests.java | 23 +++ 22 files changed, 1076 insertions(+), 7 deletions(-) create mode 100644 examples/src/main/java/io/dapr/examples/workflows/crossapp/App2TransformActivity.java create mode 100644 examples/src/main/java/io/dapr/examples/workflows/crossapp/App2Worker.java create mode 100644 examples/src/main/java/io/dapr/examples/workflows/crossapp/App3FinalizeActivity.java create mode 100644 examples/src/main/java/io/dapr/examples/workflows/crossapp/App3Worker.java create mode 100644 examples/src/main/java/io/dapr/examples/workflows/crossapp/CrossAppWorker.java create mode 100644 examples/src/main/java/io/dapr/examples/workflows/crossapp/CrossAppWorkflow.java create mode 100644 examples/src/main/java/io/dapr/examples/workflows/crossapp/CrossAppWorkflowClient.java create mode 100644 sdk-tests/src/test/java/io/dapr/it/testcontainers/workflows/crossapp/App2TransformActivity.java create mode 100644 sdk-tests/src/test/java/io/dapr/it/testcontainers/workflows/crossapp/App2Worker.java create mode 100644 sdk-tests/src/test/java/io/dapr/it/testcontainers/workflows/crossapp/App3FinalizeActivity.java create mode 100644 sdk-tests/src/test/java/io/dapr/it/testcontainers/workflows/crossapp/App3Worker.java create mode 100644 sdk-tests/src/test/java/io/dapr/it/testcontainers/workflows/crossapp/CrossAppWorker.java create mode 100644 sdk-tests/src/test/java/io/dapr/it/testcontainers/workflows/crossapp/CrossAppWorkflow.java create mode 100644 sdk-tests/src/test/java/io/dapr/it/testcontainers/workflows/crossapp/WorkflowsCrossAppCallActivityIT.java create mode 100644 sdk-workflows/src/test/java/io/dapr/workflows/WorkflowTaskOptionsTest.java diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 824ce07549..96fa4729e4 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -145,7 +145,7 @@ jobs: run: ./mvnw clean install -B -q -DskipTests - name: Integration tests using spring boot version ${{ matrix.spring-boot-version }} id: integration_tests - run: PRODUCT_SPRING_BOOT_VERSION=${{ matrix.spring-boot-version }} ./mvnw -B -Pintegration-tests verify + run: PRODUCT_SPRING_BOOT_VERSION=${{ matrix.spring-boot-version }} ./mvnw -B -Pintegration-tests dependency:copy-dependencies verify env: DOCKER_HOST: ${{steps.setup_docker.outputs.sock}} - name: Upload failsafe test report for sdk-tests on failure diff --git a/examples/src/main/java/io/dapr/examples/workflows/README.md b/examples/src/main/java/io/dapr/examples/workflows/README.md index f867dbd4c2..6cede9c010 100644 --- a/examples/src/main/java/io/dapr/examples/workflows/README.md +++ b/examples/src/main/java/io/dapr/examples/workflows/README.md @@ -53,6 +53,7 @@ Those examples contain the following workflow patterns: 4. [External Event Pattern](#external-event-pattern) 5. [Child-workflow Pattern](#child-workflow-pattern) 6. [Compensation Pattern](#compensation-pattern) +7. [Cross-App Pattern](#cross-app-pattern) ### Chaining Pattern In the chaining pattern, a sequence of activities executes in a specific order. @@ -681,6 +682,158 @@ Key Points: 4. Each activity simulates work with a short delay for demonstration purposes +### Cross-App Pattern + +The cross-app pattern allows workflows to call activities that are hosted in different Dapr applications. This is useful for microservices architectures allowing multiple applications to host activities that can be orchestrated by Dapr Workflows. + +The `CrossAppWorkflow` class defines the workflow. It demonstrates calling activities in different apps using the `appId` parameter in `WorkflowTaskOptions`. See the code snippet below: +```java +public class CrossAppWorkflow implements Workflow { + @Override + public WorkflowStub create() { + return ctx -> { + var logger = ctx.getLogger(); + logger.info("=== WORKFLOW STARTING ==="); + logger.info("Starting CrossAppWorkflow: {}", ctx.getName()); + logger.info("Workflow name: {}", ctx.getName()); + logger.info("Workflow instance ID: {}", ctx.getInstanceId()); + + String input = ctx.getInput(String.class); + logger.info("CrossAppWorkflow received input: {}", input); + logger.info("Workflow input: {}", input); + + // Call an activity in another app by passing in an active appID to the WorkflowTaskOptions + logger.info("Calling cross-app activity in 'app2'..."); + logger.info("About to call cross-app activity in app2..."); + String crossAppResult = ctx.callActivity( + App2TransformActivity.class.getName(), + input, + new WorkflowTaskOptions("app2"), + String.class + ).await(); + + // Call another activity in a different app + logger.info("Calling cross-app activity in 'app3'..."); + logger.info("About to call cross-app activity in app3..."); + String finalResult = ctx.callActivity( + App3FinalizeActivity.class.getName(), + crossAppResult, + new WorkflowTaskOptions("app3"), + String.class + ).await(); + logger.info("Final cross-app activity result: {}", finalResult); + logger.info("Final cross-app activity result: {}", finalResult); + + logger.info("CrossAppWorkflow finished with: {}", finalResult); + logger.info("=== WORKFLOW COMPLETING WITH: {} ===" , finalResult); + ctx.complete(finalResult); + }; + } +} + +``` + +The `App2TransformActivity` class defines an activity in app2 that transforms the input string. See the code snippet below: +```java +public class App2TransformActivity implements WorkflowActivity { + @Override + public Object run(WorkflowActivityContext ctx) { + var logger = ctx.getLogger(); + logger.info("=== App2: TransformActivity called ==="); + String input = ctx.getInput(String.class); + logger.info("Input: {}", input); + + // Transform the input + String result = input.toUpperCase() + " [TRANSFORMED BY APP2]"; + + logger.info("Output: {}", result); + return result; + } +} +``` + +The `App3FinalizeActivity` class defines an activity in app3 that finalizes the processing. See the code snippet below: +```java +public class App3FinalizeActivity implements WorkflowActivity { + @Override + public Object run(WorkflowActivityContext ctx) { + var logger = ctx.getLogger(); + logger.info("=== App3: FinalizeActivity called ==="); + String input = ctx.getInput(String.class); + logger.info("Input: ", input); + + // Finalize the processing + String result = input + " [FINALIZED BY APP3]"; + + logger.info("Output: {}", result); + return result; + } +} +``` + +**Key Features:** +- **Cross-app activity calls**: Call activities in different Dapr applications specifying the appID in the WorkflowTaskOptions +- **WorkflowTaskOptions with appId**: Specify which app should handle the activity +- **Combined with retry policies**: Use app ID along with retry policies and handlers +- **Error handling**: Works the same as local activity calls + +**Requirements:** +- Multiple Dapr applications running with different app IDs +- Activities registered in the target applications +- Proper Dapr workflow runtime configuration + +**Important Limitations:** +- **Cross-app calls are currently supported for activities only** +- **Child workflow cross-app calls (suborchestration) are NOT supported** +- The app ID must match the Dapr application ID of the target service + +**Running the Cross-App Example:** + +This example requires running multiple Dapr applications simultaneously. You'll need to run the following commands in separate terminals: + +1. **Start the main workflow worker (crossapp-worker):** +```sh +dapr run --app-id crossapp-worker --resources-path ./components/workflows --dapr-grpc-port 50001 -- java -jar target/dapr-java-sdk-examples-exec.jar io.dapr.examples.workflows.crossapp.CrossAppWorker +``` + +2. **Start app2 worker (handles App2TransformActivity):** +```sh +dapr run --app-id app2 --resources-path ./components/workflows --dapr-grpc-port 50002 -- java -jar target/dapr-java-sdk-examples-exec.jar io.dapr.examples.workflows.crossapp.App2Worker +``` + +3. **Start app3 worker (handles App3FinalizeActivity):** +```sh +dapr run --app-id app3 --resources-path ./components/workflows --dapr-grpc-port 50003 -- java -jar target/dapr-java-sdk-examples-exec.jar io.dapr.examples.workflows.crossapp.App3Worker +``` + +4. **Run the workflow client:** +```sh +java -Djava.util.logging.ConsoleHandler.level=FINE -Dio.dapr.durabletask.level=FINE -jar target/dapr-java-sdk-examples-exec.jar io.dapr.examples.workflows.crossapp.CrossAppWorkflowClient "Hello World" +``` + +**Expected Output:** + +The client will show: +```text +=== Starting Cross-App Workflow Client === +Input: Hello World +Created DaprWorkflowClient successfully +Attempting to start new workflow... +Started a new cross-app workflow with instance ID: 001113f3-b9d9-438c-932a-a9a9b70b9460 +Waiting for workflow completion... +Workflow instance with ID: 001113f3-b9d9-438c-932a-a9a9b70b9460 completed with result: HELLO WORLD [TRANSFORMED BY APP2] [FINALIZED BY APP3] +``` + +The workflow demonstrates: +1. The workflow starts in the main worker (crossapp-worker) +2. Calls an activity in 'app2' using cross-app functionality +3. Calls an activity in 'app3' using cross-app functionality +4. The workflow completes with the final result from all activities + +This pattern is particularly useful for: +- Microservices architectures where activities are distributed across multiple services +- Multi-tenant applications where activities are isolated by app ID + ### Suspend/Resume Pattern Workflow instances can be suspended and resumed. This example shows how to use the suspend and resume commands. diff --git a/examples/src/main/java/io/dapr/examples/workflows/crossapp/App2TransformActivity.java b/examples/src/main/java/io/dapr/examples/workflows/crossapp/App2TransformActivity.java new file mode 100644 index 0000000000..62db807d18 --- /dev/null +++ b/examples/src/main/java/io/dapr/examples/workflows/crossapp/App2TransformActivity.java @@ -0,0 +1,34 @@ +/* + * Copyright 2025 The Dapr Authors + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and +limitations under the License. +*/ + +package io.dapr.examples.workflows.crossapp; + +import io.dapr.workflows.WorkflowActivity; +import io.dapr.workflows.WorkflowActivityContext; + +/** + * TransformActivity for App2 - transforms input to uppercase. + * This activity is called cross-app from the main workflow. + */ +public class App2TransformActivity implements WorkflowActivity { + @Override + public Object run(WorkflowActivityContext context) { + String input = context.getInput(String.class); + var logger = context.getLogger(); + logger.info("=== App2: TransformActivity called ==="); + logger.info("Input: {}", input); + String result = input.toUpperCase() + " [TRANSFORMED BY APP2]"; + logger.info("Output: {}", result); + return result; + } +} diff --git a/examples/src/main/java/io/dapr/examples/workflows/crossapp/App2Worker.java b/examples/src/main/java/io/dapr/examples/workflows/crossapp/App2Worker.java new file mode 100644 index 0000000000..8bad2b8f5b --- /dev/null +++ b/examples/src/main/java/io/dapr/examples/workflows/crossapp/App2Worker.java @@ -0,0 +1,37 @@ +/* + * Copyright 2025 The Dapr Authors + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and +limitations under the License. +*/ + +package io.dapr.examples.workflows.crossapp; + +import io.dapr.workflows.runtime.WorkflowRuntime; +import io.dapr.workflows.runtime.WorkflowRuntimeBuilder; + +/** + * App2 Worker - registers only the TransformActivity. + * This app will handle cross-app activity calls from the main workflow. + */ +public class App2Worker { + + public static void main(String[] args) throws Exception { + System.out.println("=== Starting App2Worker ==="); + // Register the Workflow with the builder + WorkflowRuntimeBuilder builder = new WorkflowRuntimeBuilder() + .registerActivity(App2TransformActivity.class); + + // Build and start the workflow runtime + try (WorkflowRuntime runtime = builder.build()) { + System.out.println("App2 is ready to receive cross-app activity calls..."); + runtime.start(); + } + } +} diff --git a/examples/src/main/java/io/dapr/examples/workflows/crossapp/App3FinalizeActivity.java b/examples/src/main/java/io/dapr/examples/workflows/crossapp/App3FinalizeActivity.java new file mode 100644 index 0000000000..0f7c0ddad1 --- /dev/null +++ b/examples/src/main/java/io/dapr/examples/workflows/crossapp/App3FinalizeActivity.java @@ -0,0 +1,34 @@ +/* + * Copyright 2025 The Dapr Authors + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and +limitations under the License. +*/ + +package io.dapr.examples.workflows.crossapp; + +import io.dapr.workflows.WorkflowActivity; +import io.dapr.workflows.WorkflowActivityContext; + +/** + * FinalizeActivity for App3 - adds final processing. + * This activity is called cross-app from the main workflow. + */ +public class App3FinalizeActivity implements WorkflowActivity { + @Override + public Object run(WorkflowActivityContext context) { + String input = context.getInput(String.class); + var logger = context.getLogger(); + logger.info("=== App3: FinalizeActivity called ==="); + logger.info("Input: {}", input); + String result = input + " [FINALIZED BY APP3]"; + logger.info("Output: {}", result); + return result; + } +} diff --git a/examples/src/main/java/io/dapr/examples/workflows/crossapp/App3Worker.java b/examples/src/main/java/io/dapr/examples/workflows/crossapp/App3Worker.java new file mode 100644 index 0000000000..dc49baa76e --- /dev/null +++ b/examples/src/main/java/io/dapr/examples/workflows/crossapp/App3Worker.java @@ -0,0 +1,37 @@ +/* + * Copyright 2025 The Dapr Authors + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and +limitations under the License. +*/ + +package io.dapr.examples.workflows.crossapp; + +import io.dapr.workflows.runtime.WorkflowRuntime; +import io.dapr.workflows.runtime.WorkflowRuntimeBuilder; + +/** + * App3 Worker - registers only the FinalizeActivity. + * This app will handle cross-app activity calls from the main workflow. + */ +public class App3Worker { + + public static void main(String[] args) throws Exception { + System.out.println("=== Starting App3Worker ==="); + // Register the Workflow with the builder + WorkflowRuntimeBuilder builder = new WorkflowRuntimeBuilder() + .registerActivity(App3FinalizeActivity.class); + + // Build and start the workflow runtime + try (WorkflowRuntime runtime = builder.build()) { + System.out.println("App3 is ready to receive cross-app activity calls..."); + runtime.start(); + } + } +} diff --git a/examples/src/main/java/io/dapr/examples/workflows/crossapp/CrossAppWorker.java b/examples/src/main/java/io/dapr/examples/workflows/crossapp/CrossAppWorker.java new file mode 100644 index 0000000000..ecf7dfcb6f --- /dev/null +++ b/examples/src/main/java/io/dapr/examples/workflows/crossapp/CrossAppWorker.java @@ -0,0 +1,32 @@ +/* + * Copyright 2025 The Dapr Authors + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and +limitations under the License. +*/ + +package io.dapr.examples.workflows.crossapp; + +import io.dapr.workflows.runtime.WorkflowRuntime; +import io.dapr.workflows.runtime.WorkflowRuntimeBuilder; + +public class CrossAppWorker { + + public static void main(String[] args) throws Exception { + // Register the Workflow with the builder + WorkflowRuntimeBuilder builder = new WorkflowRuntimeBuilder() + .registerWorkflow(CrossAppWorkflow.class); + + // Build and start the workflow runtime + try (WorkflowRuntime runtime = builder.build()) { + System.out.println("CrossAppWorker started - registered CrossAppWorkflow only"); + runtime.start(); + } + } +} diff --git a/examples/src/main/java/io/dapr/examples/workflows/crossapp/CrossAppWorkflow.java b/examples/src/main/java/io/dapr/examples/workflows/crossapp/CrossAppWorkflow.java new file mode 100644 index 0000000000..d7eeb9d88e --- /dev/null +++ b/examples/src/main/java/io/dapr/examples/workflows/crossapp/CrossAppWorkflow.java @@ -0,0 +1,64 @@ +/* + * Copyright 2025 The Dapr Authors + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and +limitations under the License. +*/ + +package io.dapr.examples.workflows.crossapp; + +import io.dapr.workflows.Workflow; +import io.dapr.workflows.WorkflowStub; +import io.dapr.workflows.WorkflowTaskOptions; + +/** + * Example workflow that demonstrates cross-app activity calls. + * This workflow calls activities in different apps using the appId parameter. + */ +public class CrossAppWorkflow implements Workflow { + @Override + public WorkflowStub create() { + return ctx -> { + var logger = ctx.getLogger(); + logger.info("=== WORKFLOW STARTING ==="); + logger.info("Starting CrossAppWorkflow: {}", ctx.getName()); + logger.info("Workflow name: {}", ctx.getName()); + logger.info("Workflow instance ID: {}", ctx.getInstanceId()); + + String input = ctx.getInput(String.class); + logger.info("CrossAppWorkflow received input: {}", input); + logger.info("Workflow input: {}", input); + + // Call an activity in another app by passing in an active appID to the WorkflowTaskOptions + logger.info("Calling cross-app activity in 'app2'..."); + logger.info("About to call cross-app activity in app2..."); + String crossAppResult = ctx.callActivity( + App2TransformActivity.class.getName(), + input, + new WorkflowTaskOptions("app2"), + String.class + ).await(); + + // Call another activity in a different app + logger.info("Calling cross-app activity in 'app3'..."); + logger.info("About to call cross-app activity in app3..."); + String finalResult = ctx.callActivity( + App3FinalizeActivity.class.getName(), + crossAppResult, + new WorkflowTaskOptions("app3"), + String.class + ).await(); + logger.info("Final cross-app activity result: {}", finalResult); + + logger.info("CrossAppWorkflow finished with: {}", finalResult); + logger.info("=== WORKFLOW COMPLETING WITH: {} ===", finalResult); + ctx.complete(finalResult); + }; + } +} diff --git a/examples/src/main/java/io/dapr/examples/workflows/crossapp/CrossAppWorkflowClient.java b/examples/src/main/java/io/dapr/examples/workflows/crossapp/CrossAppWorkflowClient.java new file mode 100644 index 0000000000..1e7910cd2c --- /dev/null +++ b/examples/src/main/java/io/dapr/examples/workflows/crossapp/CrossAppWorkflowClient.java @@ -0,0 +1,66 @@ +/* + * Copyright 2025 The Dapr Authors + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and +limitations under the License. +*/ + +package io.dapr.examples.workflows.crossapp; + +import io.dapr.workflows.client.DaprWorkflowClient; +import io.dapr.workflows.client.WorkflowInstanceStatus; + +import java.util.concurrent.TimeoutException; + +/** + * Cross-App Workflow Client - starts and monitors workflows. + * + * 1. Create a workflow client + * 2. Start a new workflow instance + * 3. Wait for completion and get results + */ +public class CrossAppWorkflowClient { + + public static void main(String[] args) { + if (args.length < 1) { + System.out.println("Usage: CrossAppWorkflowClientExample "); + System.out.println("Example: CrossAppWorkflowClientExample \"Hello World\""); + return; + } + + String input = args[0]; + System.out.println("=== Starting Cross-App Workflow Client ==="); + System.out.println("Input: " + input); + + try (DaprWorkflowClient client = new DaprWorkflowClient()) { + System.out.println("Created DaprWorkflowClient successfully"); + + // Start a new workflow instance + System.out.println("Attempting to start new workflow..."); + String instanceId = client.scheduleNewWorkflow(CrossAppWorkflow.class, input); + System.out.printf("Started a new cross-app workflow with instance ID: %s%n", instanceId); + + // Wait for the workflow to complete + System.out.println("Waiting for workflow completion..."); + WorkflowInstanceStatus workflowInstanceStatus = + client.waitForInstanceCompletion(instanceId, null, true); + + // Get the result + String result = workflowInstanceStatus.readOutputAs(String.class); + System.out.printf("Workflow instance with ID: %s completed with result: %s%n", instanceId, result); + + } catch (TimeoutException | InterruptedException e) { + System.err.println("Error waiting for workflow completion: " + e.getMessage()); + e.printStackTrace(); + } catch (Exception e) { + System.err.println("Error creating workflow client or starting workflow: " + e.getMessage()); + e.printStackTrace(); + } + } +} diff --git a/sdk-tests/src/test/java/io/dapr/it/testcontainers/workflows/crossapp/App2TransformActivity.java b/sdk-tests/src/test/java/io/dapr/it/testcontainers/workflows/crossapp/App2TransformActivity.java new file mode 100644 index 0000000000..653e14b59f --- /dev/null +++ b/sdk-tests/src/test/java/io/dapr/it/testcontainers/workflows/crossapp/App2TransformActivity.java @@ -0,0 +1,36 @@ +/* + * Copyright 2025 The Dapr Authors + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +package io.dapr.it.testcontainers.workflows.crossapp; + +import io.dapr.workflows.WorkflowActivity; +import io.dapr.workflows.WorkflowActivityContext; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class App2TransformActivity implements WorkflowActivity { + + private static final Logger logger = LoggerFactory.getLogger(App2TransformActivity.class); + + @Override + public Object run(WorkflowActivityContext ctx) { + String input = ctx.getInput(String.class); + logger.info("=== App2: TransformActivity called ==="); + logger.info("Input: {}", input); + + String output = input.toUpperCase() + " [TRANSFORMED BY APP2]"; + logger.info("Output: {}", output); + + return output; + } +} diff --git a/sdk-tests/src/test/java/io/dapr/it/testcontainers/workflows/crossapp/App2Worker.java b/sdk-tests/src/test/java/io/dapr/it/testcontainers/workflows/crossapp/App2Worker.java new file mode 100644 index 0000000000..f9748f302f --- /dev/null +++ b/sdk-tests/src/test/java/io/dapr/it/testcontainers/workflows/crossapp/App2Worker.java @@ -0,0 +1,40 @@ +/* + * Copyright 2025 The Dapr Authors + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +package io.dapr.it.testcontainers.workflows.crossapp; + +import io.dapr.workflows.runtime.WorkflowRuntime; +import io.dapr.workflows.runtime.WorkflowRuntimeBuilder; + +/** + * App2Worker - registers the App2TransformActivity. + * This app will handle cross-app activity calls from the main workflow. + */ +public class App2Worker { + + public static void main(String[] args) throws Exception { + System.out.println("=== Starting App2Worker (App2TransformActivity) ==="); + + // Register the Activity with the builder + WorkflowRuntimeBuilder builder = new WorkflowRuntimeBuilder() + .registerActivity(App2TransformActivity.class); + + // Build and start the workflow runtime + try (WorkflowRuntime runtime = builder.build()) { + System.out.println("App2Worker started - registered App2TransformActivity only"); + System.out.println("App2 is ready to receive cross-app activity calls..."); + System.out.println("Waiting for cross-app activity calls..."); + runtime.start(); + } + } +} diff --git a/sdk-tests/src/test/java/io/dapr/it/testcontainers/workflows/crossapp/App3FinalizeActivity.java b/sdk-tests/src/test/java/io/dapr/it/testcontainers/workflows/crossapp/App3FinalizeActivity.java new file mode 100644 index 0000000000..106db9a1fb --- /dev/null +++ b/sdk-tests/src/test/java/io/dapr/it/testcontainers/workflows/crossapp/App3FinalizeActivity.java @@ -0,0 +1,36 @@ +/* + * Copyright 2025 The Dapr Authors + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +package io.dapr.it.testcontainers.workflows.crossapp; + +import io.dapr.workflows.WorkflowActivity; +import io.dapr.workflows.WorkflowActivityContext; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class App3FinalizeActivity implements WorkflowActivity { + + private static final Logger logger = LoggerFactory.getLogger(App3FinalizeActivity.class); + + @Override + public Object run(WorkflowActivityContext ctx) { + String input = ctx.getInput(String.class); + logger.info("=== App3: FinalizeActivity called ==="); + logger.info("Input: {}", input); + + String output = input + " [FINALIZED BY APP3]"; + logger.info("Output: {}", output); + + return output; + } +} diff --git a/sdk-tests/src/test/java/io/dapr/it/testcontainers/workflows/crossapp/App3Worker.java b/sdk-tests/src/test/java/io/dapr/it/testcontainers/workflows/crossapp/App3Worker.java new file mode 100644 index 0000000000..be1d8de468 --- /dev/null +++ b/sdk-tests/src/test/java/io/dapr/it/testcontainers/workflows/crossapp/App3Worker.java @@ -0,0 +1,40 @@ +/* + * Copyright 2025 The Dapr Authors + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +package io.dapr.it.testcontainers.workflows.crossapp; + +import io.dapr.workflows.runtime.WorkflowRuntime; +import io.dapr.workflows.runtime.WorkflowRuntimeBuilder; + +/** + * App3Worker - registers the App3FinalizeActivity. + * This app will handle cross-app activity calls from the main workflow. + */ +public class App3Worker { + + public static void main(String[] args) throws Exception { + System.out.println("=== Starting App3Worker (App3FinalizeActivity) ==="); + + // Register the Activity with the builder + WorkflowRuntimeBuilder builder = new WorkflowRuntimeBuilder() + .registerActivity(App3FinalizeActivity.class); + + // Build and start the workflow runtime + try (WorkflowRuntime runtime = builder.build()) { + System.out.println("App3Worker started - registered App3FinalizeActivity only"); + System.out.println("App3 is ready to receive cross-app activity calls..."); + System.out.println("Waiting for cross-app activity calls..."); + runtime.start(); + } + } +} diff --git a/sdk-tests/src/test/java/io/dapr/it/testcontainers/workflows/crossapp/CrossAppWorker.java b/sdk-tests/src/test/java/io/dapr/it/testcontainers/workflows/crossapp/CrossAppWorker.java new file mode 100644 index 0000000000..36a2fdf791 --- /dev/null +++ b/sdk-tests/src/test/java/io/dapr/it/testcontainers/workflows/crossapp/CrossAppWorker.java @@ -0,0 +1,40 @@ +/* + * Copyright 2025 The Dapr Authors + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +package io.dapr.it.testcontainers.workflows.crossapp; + +import io.dapr.workflows.runtime.WorkflowRuntime; +import io.dapr.workflows.runtime.WorkflowRuntimeBuilder; + +/** + * CrossAppWorker - registers only the CrossAppWorkflow. + * This is the main workflow orchestrator that will call activities in other apps. + */ +public class CrossAppWorker { + + public static void main(String[] args) throws Exception { + System.out.println("=== Starting CrossAppWorker (Workflow Orchestrator) ==="); + + // Register the Workflow with the builder + WorkflowRuntimeBuilder builder = new WorkflowRuntimeBuilder() + .registerWorkflow(CrossAppWorkflow.class); + + // Build and start the workflow runtime + try (WorkflowRuntime runtime = builder.build()) { + System.out.println("CrossAppWorker started - registered CrossAppWorkflow only"); + System.out.println("Waiting for workflow orchestration requests..."); + runtime.start(); + } + } +} + diff --git a/sdk-tests/src/test/java/io/dapr/it/testcontainers/workflows/crossapp/CrossAppWorkflow.java b/sdk-tests/src/test/java/io/dapr/it/testcontainers/workflows/crossapp/CrossAppWorkflow.java new file mode 100644 index 0000000000..806408621e --- /dev/null +++ b/sdk-tests/src/test/java/io/dapr/it/testcontainers/workflows/crossapp/CrossAppWorkflow.java @@ -0,0 +1,55 @@ +/* + * Copyright 2025 The Dapr Authors + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +package io.dapr.it.testcontainers.workflows.crossapp; + +import io.dapr.workflows.Workflow; +import io.dapr.workflows.WorkflowStub; +import io.dapr.workflows.WorkflowTaskOptions; +import org.slf4j.Logger; + +public class CrossAppWorkflow implements Workflow { + @Override + public WorkflowStub create() { + return ctx -> { + Logger logger = ctx.getLogger(); + String instanceId = ctx.getInstanceId(); + logger.info("Starting CrossAppWorkflow: {}", ctx.getName()); + logger.info("Instance ID: {}", instanceId); + + String input = ctx.getInput(String.class); + logger.info("Workflow input: {}", input); + + // Call App2TransformActivity in app2 + logger.info("Calling cross-app activity in 'app2'..."); + String transformedByApp2 = ctx.callActivity( + App2TransformActivity.class.getName(), + input, + new WorkflowTaskOptions("app2"), + String.class + ).await(); + + // Call App3FinalizeActivity in app3 + logger.info("Calling cross-app activity in 'app3'..."); + String finalizedByApp3 = ctx.callActivity( + App3FinalizeActivity.class.getName(), + transformedByApp2, + new WorkflowTaskOptions("app3"), + String.class + ).await(); + + logger.info("Final cross-app activity result: {}", finalizedByApp3); + ctx.complete(finalizedByApp3); + }; + } +} diff --git a/sdk-tests/src/test/java/io/dapr/it/testcontainers/workflows/crossapp/WorkflowsCrossAppCallActivityIT.java b/sdk-tests/src/test/java/io/dapr/it/testcontainers/workflows/crossapp/WorkflowsCrossAppCallActivityIT.java new file mode 100644 index 0000000000..868d0adec1 --- /dev/null +++ b/sdk-tests/src/test/java/io/dapr/it/testcontainers/workflows/crossapp/WorkflowsCrossAppCallActivityIT.java @@ -0,0 +1,191 @@ +/* + * Copyright 2025 The Dapr Authors + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +package io.dapr.it.testcontainers.workflows.crossapp; + +import io.dapr.testcontainers.Component; +import io.dapr.testcontainers.DaprContainer; +import io.dapr.testcontainers.DaprLogLevel; +import io.dapr.testcontainers.DaprPlacementContainer; +import io.dapr.testcontainers.DaprSchedulerContainer; +import io.dapr.workflows.client.DaprWorkflowClient; +import io.dapr.workflows.client.WorkflowInstanceStatus; +import io.dapr.workflows.client.WorkflowRuntimeStatus; +import io.dapr.config.Properties; +import net.bytebuddy.utility.dispatcher.JavaDispatcher; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.testcontainers.containers.Network; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; +import org.testcontainers.utility.MountableFile; +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.containers.wait.strategy.Wait; + +import java.time.Duration; +import java.util.Map; + +import static io.dapr.it.testcontainers.ContainerConstants.DAPR_RUNTIME_IMAGE_TAG; +import static io.dapr.testcontainers.DaprContainerConstants.DAPR_PLACEMENT_IMAGE_TAG; +import static io.dapr.testcontainers.DaprContainerConstants.DAPR_SCHEDULER_IMAGE_TAG; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +/** + * Cross-App Pattern integration test. + * + * This test demonstrates the cross-app pattern by: + * 1. Starting 3 Dapr containers (crossapp-worker, app2, app3) + * 2. Launching Java processes that register workflows/activities in separate apps + * 3. Executing a cross-app workflow + * 4. Asserting successful completion + */ +@Testcontainers +@Tag("testcontainers") +public class WorkflowsCrossAppCallActivityIT { + + private static final Network DAPR_NETWORK = Network.newNetwork(); + + @Container + private final static DaprPlacementContainer sharedPlacementContainer = new DaprPlacementContainer(DAPR_PLACEMENT_IMAGE_TAG) + .withNetwork(DAPR_NETWORK) + .withNetworkAliases("placement") + .withReuse(false); + + @Container + private final static DaprSchedulerContainer sharedSchedulerContainer = new DaprSchedulerContainer(DAPR_SCHEDULER_IMAGE_TAG) + .withNetwork(DAPR_NETWORK) + .withNetworkAliases("scheduler") + .withReuse(false); + + // Main workflow orchestrator container + @Container + private final static DaprContainer MAIN_WORKFLOW_SIDECAR = new DaprContainer(DAPR_RUNTIME_IMAGE_TAG) + .withAppName("crossapp-worker") + .withNetwork(DAPR_NETWORK) + .withNetworkAliases("main-workflow-sidecar") + .withPlacementContainer(sharedPlacementContainer) + .withSchedulerContainer(sharedSchedulerContainer) + .withComponent(new Component("kvstore", "state.in-memory", "v1", Map.of("actorStateStore", "true"))) + .withDaprLogLevel(DaprLogLevel.DEBUG) + .dependsOn(sharedPlacementContainer, sharedSchedulerContainer) + .withLogConsumer(outputFrame -> System.out.println("MAIN_WORKFLOW: " + outputFrame.getUtf8String())) + .withAppChannelAddress("host.testcontainers.internal"); + + // App2 container for App2TransformActivity + @Container + private final static DaprContainer APP2_SIDECAR = new DaprContainer(DAPR_RUNTIME_IMAGE_TAG) + .withAppName("app2") + .withNetwork(DAPR_NETWORK) + .withNetworkAliases("app2-sidecar") + .withPlacementContainer(sharedPlacementContainer) + .withSchedulerContainer(sharedSchedulerContainer) + .withAppChannelAddress("main-workflow-sidecar:3500") + .withDaprLogLevel(DaprLogLevel.DEBUG) + .dependsOn(sharedPlacementContainer, sharedSchedulerContainer, MAIN_WORKFLOW_SIDECAR) + .withComponent(new Component("kvstore", "state.in-memory", "v1", Map.of("actorStateStore", "true"))) + .withLogConsumer(outputFrame -> System.out.println("APP2: " + outputFrame.getUtf8String())); + + // App3 container for App3FinalizeActivity + @Container + private final static DaprContainer APP3_SIDECAR = new DaprContainer(DAPR_RUNTIME_IMAGE_TAG) + .withAppName("app3") + .withNetwork(DAPR_NETWORK) + .withNetworkAliases("app3-sidecar") + .withPlacementContainer(sharedPlacementContainer) + .withSchedulerContainer(sharedSchedulerContainer) + .withAppChannelAddress("main-workflow-sidecar:3500") + .withDaprLogLevel(DaprLogLevel.DEBUG) + .dependsOn(sharedPlacementContainer, sharedSchedulerContainer, MAIN_WORKFLOW_SIDECAR) + .withComponent(new Component("kvstore", "state.in-memory", "v1", Map.of("actorStateStore", "true"))) + .withLogConsumer(outputFrame -> System.out.println("APP3: " + outputFrame.getUtf8String())); + + + // TestContainers for each app + @Container + private static GenericContainer crossappWorker = new GenericContainer<>("openjdk:17-jdk-slim") + .withCopyFileToContainer(MountableFile.forHostPath("target"), "/app") + .withWorkingDirectory("/app") + .withCommand("java", "-cp", "test-classes:classes:dependency/*:*", + "-Ddapr.app.id=crossapp-worker", + "-Ddapr.grpc.endpoint=main-workflow-sidecar:50001", + "-Ddapr.http.endpoint=main-workflow-sidecar:3500", + "io.dapr.it.testcontainers.workflows.crossapp.CrossAppWorker") + .withNetwork(DAPR_NETWORK) + .dependsOn(MAIN_WORKFLOW_SIDECAR) + .waitingFor(Wait.forLogMessage(".*CrossAppWorker started.*", 1)) + .withLogConsumer(outputFrame -> System.out.println("CrossAppWorker: " + outputFrame.getUtf8String())); + + @Container + private final static GenericContainer app2Worker = new GenericContainer<>("openjdk:17-jdk-slim") + .withCopyFileToContainer(MountableFile.forHostPath("target"), "/app") + .withWorkingDirectory("/app") + .withCommand("java", "-cp", "test-classes:classes:dependency/*:*", + "-Ddapr.app.id=app2", + "-Ddapr.grpc.endpoint=app2-sidecar:50001", + "-Ddapr.http.endpoint=app2-sidecar:3500", + "io.dapr.it.testcontainers.workflows.crossapp.App2Worker") + .withNetwork(DAPR_NETWORK) + .dependsOn(APP2_SIDECAR) + .waitingFor(Wait.forLogMessage(".*App2Worker started.*", 1)) + .withLogConsumer(outputFrame -> System.out.println("App2Worker: " + outputFrame.getUtf8String())); + + @Container + private final static GenericContainer app3Worker = new GenericContainer<>("openjdk:17-jdk-slim") + .withCopyFileToContainer(MountableFile.forHostPath("target"), "/app") + .withWorkingDirectory("/app") + .withCommand("java", "-cp", "test-classes:classes:dependency/*:*", + "-Ddapr.app.id=app3", + "-Ddapr.grpc.endpoint=app3-sidecar:50001", + "-Ddapr.http.endpoint=app3-sidecar:3500", + "io.dapr.it.testcontainers.workflows.crossapp.App3Worker") + .withNetwork(DAPR_NETWORK) + .dependsOn(APP3_SIDECAR) + .waitingFor(Wait.forLogMessage(".*App3Worker started.*", 1)) + .withLogConsumer(outputFrame -> System.out.println("App3Worker: " + outputFrame.getUtf8String())); + + @Test + public void testCrossAppWorkflow() throws Exception { + // TestContainers wait strategies ensure all containers are ready before this test runs + + String input = "Hello World"; + String expectedOutput = "HELLO WORLD [TRANSFORMED BY APP2] [FINALIZED BY APP3]"; + + // Create workflow client connected to the main workflow orchestrator + // Use the same endpoint configuration that the workers use + // The workers use host.testcontainers.internal:50001 + Map propertyOverrides = Map.of( + "dapr.grpc.endpoint", MAIN_WORKFLOW_SIDECAR.getGrpcEndpoint(), + "dapr.http.endpoint", MAIN_WORKFLOW_SIDECAR.getHttpEndpoint() + ); + + Properties clientProperties = new Properties(propertyOverrides); + DaprWorkflowClient workflowClient = new DaprWorkflowClient(clientProperties); + + try { + String instanceId = workflowClient.scheduleNewWorkflow(CrossAppWorkflow.class, input); + assertNotNull(instanceId, "Workflow instance ID should not be null"); + workflowClient.waitForInstanceStart(instanceId, Duration.ofSeconds(30), false); + + WorkflowInstanceStatus workflowStatus = workflowClient.waitForInstanceCompletion(instanceId, null, true); + assertNotNull(workflowStatus, "Workflow status should not be null"); + assertEquals(WorkflowRuntimeStatus.COMPLETED, workflowStatus.getRuntimeStatus(), + "Workflow should complete successfully"); + String workflowOutput = workflowStatus.readOutputAs(String.class); + assertEquals(expectedOutput, workflowOutput, "Workflow output should match expected result"); + } finally { + workflowClient.close(); + } + } + +} diff --git a/sdk-workflows/pom.xml b/sdk-workflows/pom.xml index 9475e159dd..1404abad61 100644 --- a/sdk-workflows/pom.xml +++ b/sdk-workflows/pom.xml @@ -47,7 +47,7 @@ io.dapr durabletask-client - 1.5.7 + 1.5.10 - 0.17.0-SNAPSHOT + 0.16.0-rc-1 From 84bd6a53fe83b789084ff75d6ad5cf2d65b8a493 Mon Sep 17 00:00:00 2001 From: Javier Aliaga Date: Wed, 3 Sep 2025 09:36:46 +0200 Subject: [PATCH 22/22] chore: Alpha components to stable Signed-off-by: Javier Aliaga --- .github/scripts/update_sdk_version.sh | 18 ++---------------- .../dapr-spring-boot-autoconfigure/pom.xml | 2 +- .../dapr-spring-boot-starter-test/pom.xml | 2 +- .../dapr-spring-boot-starter/pom.xml | 2 +- dapr-spring/dapr-spring-boot-tests/pom.xml | 2 +- dapr-spring/dapr-spring-data/pom.xml | 2 +- dapr-spring/dapr-spring-messaging/pom.xml | 2 +- dapr-spring/dapr-spring-workflows/pom.xml | 2 +- dapr-spring/pom.xml | 15 +++++++-------- examples/pom.xml | 2 +- pom.xml | 8 ++++---- sdk-tests/pom.xml | 2 -- sdk-workflows/pom.xml | 2 +- spring-boot-examples/consumer-app/pom.xml | 2 +- spring-boot-examples/pom.xml | 2 +- spring-boot-examples/producer-app/pom.xml | 2 +- spring-boot-examples/workflows/pom.xml | 2 +- testcontainers-dapr/pom.xml | 2 +- 18 files changed, 27 insertions(+), 44 deletions(-) diff --git a/.github/scripts/update_sdk_version.sh b/.github/scripts/update_sdk_version.sh index 6d07850a92..bc573345e3 100755 --- a/.github/scripts/update_sdk_version.sh +++ b/.github/scripts/update_sdk_version.sh @@ -9,26 +9,12 @@ DAPR_JAVA_SDK_VERSION=$1 DAPR_JAVA_SDK_ALPHA_VERSION=`echo $DAPR_JAVA_SDK_VERSION | sed 's/^[0-9]*\./0./'` mvn versions:set -DnewVersion=$DAPR_JAVA_SDK_VERSION -mvn versions:set-property -Dproperty=dapr.sdk.alpha.version -DnewVersion=$DAPR_JAVA_SDK_ALPHA_VERSION + mvn versions:set-property -Dproperty=dapr.sdk.version -DnewVersion=$DAPR_JAVA_SDK_VERSION -mvn versions:set-property -Dproperty=dapr.sdk.version -DnewVersion=$DAPR_JAVA_SDK_VERSION -f sdk-tests/pom.xml -mvn versions:set-property -Dproperty=dapr.sdk.alpha.version -DnewVersion=$DAPR_JAVA_SDK_ALPHA_VERSION -f sdk-tests/pom.xml +mvn versions:set-property -Dproperty=dapr.sdk.alpha.version -DnewVersion=$DAPR_JAVA_SDK_ALPHA_VERSION ################### # Alpha artifacts # ################### -# sdk-workflows -mvn versions:set -DnewVersion=$DAPR_JAVA_SDK_ALPHA_VERSION -f sdk-workflows/pom.xml - -# testcontainers-dapr -mvn versions:set -DnewVersion=$DAPR_JAVA_SDK_ALPHA_VERSION -f testcontainers-dapr/pom.xml - -# dapr-spring -mvn versions:set -DnewVersion=$DAPR_JAVA_SDK_ALPHA_VERSION -DprocessDependencies=true -f dapr-spring/pom.xml -mvn versions:set-property -Dproperty=dapr.spring.version -DnewVersion=$DAPR_JAVA_SDK_ALPHA_VERSION -f dapr-spring/pom.xml - -# spring-boot-examples -mvn versions:set -DnewVersion=$DAPR_JAVA_SDK_ALPHA_VERSION -f spring-boot-examples/pom.xml - git clean -f diff --git a/dapr-spring/dapr-spring-boot-autoconfigure/pom.xml b/dapr-spring/dapr-spring-boot-autoconfigure/pom.xml index bc8742b97b..234825356b 100644 --- a/dapr-spring/dapr-spring-boot-autoconfigure/pom.xml +++ b/dapr-spring/dapr-spring-boot-autoconfigure/pom.xml @@ -6,7 +6,7 @@ io.dapr.spring dapr-spring-parent - 0.16.0-rc-1 + 1.16.0-rc-1 dapr-spring-boot-autoconfigure diff --git a/dapr-spring/dapr-spring-boot-starters/dapr-spring-boot-starter-test/pom.xml b/dapr-spring/dapr-spring-boot-starters/dapr-spring-boot-starter-test/pom.xml index 771047a990..da26fc4117 100644 --- a/dapr-spring/dapr-spring-boot-starters/dapr-spring-boot-starter-test/pom.xml +++ b/dapr-spring/dapr-spring-boot-starters/dapr-spring-boot-starter-test/pom.xml @@ -6,7 +6,7 @@ io.dapr.spring dapr-spring-parent - 0.16.0-rc-1 + 1.16.0-rc-1 ../../pom.xml diff --git a/dapr-spring/dapr-spring-boot-starters/dapr-spring-boot-starter/pom.xml b/dapr-spring/dapr-spring-boot-starters/dapr-spring-boot-starter/pom.xml index 8cd8be1b3d..71fd4442ff 100644 --- a/dapr-spring/dapr-spring-boot-starters/dapr-spring-boot-starter/pom.xml +++ b/dapr-spring/dapr-spring-boot-starters/dapr-spring-boot-starter/pom.xml @@ -6,7 +6,7 @@ io.dapr.spring dapr-spring-parent - 0.16.0-rc-1 + 1.16.0-rc-1 ../../pom.xml diff --git a/dapr-spring/dapr-spring-boot-tests/pom.xml b/dapr-spring/dapr-spring-boot-tests/pom.xml index f0c5b2d233..b388377c53 100644 --- a/dapr-spring/dapr-spring-boot-tests/pom.xml +++ b/dapr-spring/dapr-spring-boot-tests/pom.xml @@ -6,7 +6,7 @@ io.dapr.spring dapr-spring-parent - 0.16.0-rc-1 + 1.16.0-rc-1 dapr-spring-boot-tests diff --git a/dapr-spring/dapr-spring-data/pom.xml b/dapr-spring/dapr-spring-data/pom.xml index e7f36664df..6ccdefbb04 100644 --- a/dapr-spring/dapr-spring-data/pom.xml +++ b/dapr-spring/dapr-spring-data/pom.xml @@ -6,7 +6,7 @@ io.dapr.spring dapr-spring-parent - 0.16.0-rc-1 + 1.16.0-rc-1 dapr-spring-data diff --git a/dapr-spring/dapr-spring-messaging/pom.xml b/dapr-spring/dapr-spring-messaging/pom.xml index 0c2445c642..f84a7927b0 100644 --- a/dapr-spring/dapr-spring-messaging/pom.xml +++ b/dapr-spring/dapr-spring-messaging/pom.xml @@ -6,7 +6,7 @@ io.dapr.spring dapr-spring-parent - 0.16.0-rc-1 + 1.16.0-rc-1 dapr-spring-messaging diff --git a/dapr-spring/dapr-spring-workflows/pom.xml b/dapr-spring/dapr-spring-workflows/pom.xml index 0c4915a077..39a6777809 100644 --- a/dapr-spring/dapr-spring-workflows/pom.xml +++ b/dapr-spring/dapr-spring-workflows/pom.xml @@ -6,7 +6,7 @@ io.dapr.spring dapr-spring-parent - 0.16.0-rc-1 + 1.16.0-rc-1 dapr-spring-workflows diff --git a/dapr-spring/pom.xml b/dapr-spring/pom.xml index b34f924aec..f6f31b15f4 100644 --- a/dapr-spring/pom.xml +++ b/dapr-spring/pom.xml @@ -13,7 +13,7 @@ io.dapr.spring dapr-spring-parent pom - 0.16.0-rc-1 + 1.16.0-rc-1 dapr-spring-parent SDK extension for Spring and Spring Boot @@ -33,7 +33,6 @@ 11 1.19.8 5.11.2 - 0.16.0-rc-1 @@ -57,27 +56,27 @@ io.dapr dapr-sdk-workflows - ${dapr.sdk.alpha.version} + ${dapr.sdk.version} io.dapr.spring dapr-spring-data - ${dapr.spring.version} + ${dapr.sdk.version} io.dapr.spring dapr-spring-messaging - ${dapr.spring.version} + ${dapr.sdk.version} io.dapr.spring dapr-spring-workflows - ${dapr.spring.version} + ${dapr.sdk.version} io.dapr.spring dapr-spring-boot-autoconfigure - ${dapr.spring.version} + ${dapr.sdk.version} io.dapr @@ -119,7 +118,7 @@ io.dapr.spring dapr-spring-boot-tests - ${dapr.spring.version} + ${dapr.sdk.version} diff --git a/examples/pom.xml b/examples/pom.xml index f9a52382f5..fb42dd1482 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -127,7 +127,7 @@ io.dapr dapr-sdk-workflows - ${dapr.sdk.alpha.version} + ${dapr.sdk.version} io.dapr diff --git a/pom.xml b/pom.xml index d39ed34c7e..01e39c0cef 100644 --- a/pom.xml +++ b/pom.xml @@ -207,12 +207,12 @@ io.dapr.spring dapr-spring-boot-starter - ${dapr.sdk.alpha.version} + ${dapr.sdk.version} io.dapr.spring dapr-spring-boot-starter-test - ${dapr.sdk.alpha.version} + ${dapr.sdk.version} org.springframework.boot @@ -247,7 +247,7 @@ io.dapr testcontainers-dapr - ${dapr.sdk.alpha.version} + ${dapr.sdk.version} org.testcontainers @@ -365,7 +365,7 @@ io.dapr dapr-sdk-workflows - ${dapr.sdk.alpha.version} + ${dapr.sdk.version} org.wiremock diff --git a/sdk-tests/pom.xml b/sdk-tests/pom.xml index 5bb9bca626..0ca71bd5e7 100644 --- a/sdk-tests/pom.xml +++ b/sdk-tests/pom.xml @@ -22,8 +22,6 @@ 17 17 true - 1.16.0-rc-1 - 0.16.0-rc-1 ${project.build.directory}/generated-sources ${project.basedir}/proto diff --git a/sdk-workflows/pom.xml b/sdk-workflows/pom.xml index 6f34899c98..83d3add6a5 100644 --- a/sdk-workflows/pom.xml +++ b/sdk-workflows/pom.xml @@ -12,7 +12,7 @@ dapr-sdk-workflows jar - 0.16.0-rc-1 + 1.16.0-rc-1 dapr-sdk-workflows SDK for Workflows on Dapr diff --git a/spring-boot-examples/consumer-app/pom.xml b/spring-boot-examples/consumer-app/pom.xml index 224f037265..0ba4b8b228 100644 --- a/spring-boot-examples/consumer-app/pom.xml +++ b/spring-boot-examples/consumer-app/pom.xml @@ -5,7 +5,7 @@ io.dapr spring-boot-examples - 0.16.0-rc-1 + 1.16.0-rc-1 consumer-app diff --git a/spring-boot-examples/pom.xml b/spring-boot-examples/pom.xml index abcd1afab1..ab67f32304 100644 --- a/spring-boot-examples/pom.xml +++ b/spring-boot-examples/pom.xml @@ -10,7 +10,7 @@ spring-boot-examples - 0.16.0-rc-1 + 1.16.0-rc-1 pom diff --git a/spring-boot-examples/producer-app/pom.xml b/spring-boot-examples/producer-app/pom.xml index f0ec102728..468e14d9c9 100644 --- a/spring-boot-examples/producer-app/pom.xml +++ b/spring-boot-examples/producer-app/pom.xml @@ -6,7 +6,7 @@ io.dapr spring-boot-examples - 0.16.0-rc-1 + 1.16.0-rc-1 producer-app diff --git a/spring-boot-examples/workflows/pom.xml b/spring-boot-examples/workflows/pom.xml index 5a664c7f2f..e6d71acc11 100644 --- a/spring-boot-examples/workflows/pom.xml +++ b/spring-boot-examples/workflows/pom.xml @@ -6,7 +6,7 @@ io.dapr spring-boot-examples - 0.16.0-rc-1 + 1.16.0-rc-1 workflows diff --git a/testcontainers-dapr/pom.xml b/testcontainers-dapr/pom.xml index 20cb2324d1..f55145b01f 100644 --- a/testcontainers-dapr/pom.xml +++ b/testcontainers-dapr/pom.xml @@ -11,7 +11,7 @@ testcontainers-dapr testcontainers-dapr Testcontainers Dapr Module - 0.16.0-rc-1 + 1.16.0-rc-1 jar