diff --git a/.gitignore b/.gitignore index 87bc9f8e1..a560c2465 100644 --- a/.gitignore +++ b/.gitignore @@ -16,4 +16,5 @@ target .classpath .project .settings/ -bin/ \ No newline at end of file +bin/ +core/.vscode/ \ No newline at end of file diff --git a/README.md b/README.md index d3808da94..b04d1f31e 100644 --- a/README.md +++ b/README.md @@ -131,6 +131,8 @@ See the README.md file in each main sample directory for cut/paste Gradle comman - [**Configure WorkflowClient to use mTLS**](/core/src/main/java/io/temporal/samples/ssl): Demonstrates how to configure WorkflowClient when using mTLS. +- [**Configure WorkflowClient to use API Key**](/core/src/main/java/io/temporal/samples/apikey): Demonstrates how to configure WorkflowClient when using API Keys. + - [**Payload Codec**](/core/src/main/java/io/temporal/samples/encodefailures): Demonstrates how to use simple codec to encode/decode failure messages. - [**Exclude Workflow/ActivityTypes from Interceptors**](/core/src/main/java/io/temporal/samples/excludefrominterceptor): Demonstrates how to exclude certain workflow / activity types from interceptors. diff --git a/core/src/main/java/io/temporal/samples/apikey/ApiKeyWorker.java b/core/src/main/java/io/temporal/samples/apikey/ApiKeyWorker.java new file mode 100644 index 000000000..7425b88d6 --- /dev/null +++ b/core/src/main/java/io/temporal/samples/apikey/ApiKeyWorker.java @@ -0,0 +1,67 @@ +package io.temporal.samples.apikey; + +import io.temporal.client.WorkflowClient; +import io.temporal.client.WorkflowClientOptions; +import io.temporal.serviceclient.WorkflowServiceStubs; +import io.temporal.serviceclient.WorkflowServiceStubsOptions; +import io.temporal.worker.Worker; +import io.temporal.worker.WorkerFactory; + +public class ApiKeyWorker { + static final String TASK_QUEUE = "MyTaskQueue"; + + public static void main(String[] args) throws Exception { + // For temporal cloud this would be ${cloud-region}.{cloud}.api.temporal.io:7233 + // Example us-east-1.aws.api.temporal.io:7233 + String targetEndpoint = System.getenv("TEMPORAL_ENDPOINT"); + // Your registered namespace. + String namespace = System.getenv("TEMPORAL_NAMESPACE"); + // Your API Key + String apiKey = System.getenv("TEMPORAL_API_KEY"); + + if (targetEndpoint == null || namespace == null || apiKey == null) { + throw new IllegalArgumentException( + "TEMPORAL_ENDPOINT, TEMPORAL_NAMESPACE, and TEMPORAL_API_KEY environment variables must be set"); + } + + // Create API Key enabled client + WorkflowServiceStubs service = + WorkflowServiceStubs.newServiceStubs( + WorkflowServiceStubsOptions.newBuilder() + .setTarget(targetEndpoint) + .setEnableHttps(true) + .addApiKey(() -> apiKey) + .build()); + + // Now setup and start workflow worker + WorkflowClient client = + WorkflowClient.newInstance( + service, WorkflowClientOptions.newBuilder().setNamespace(namespace).build()); + + // worker factory that can be used to create workers for specific task queues + WorkerFactory factory = WorkerFactory.newInstance(client); + + /* + * Define the workflow worker. Workflow workers listen to a defined task queue and process + * workflows and activities. + */ + Worker worker = factory.newWorker(TASK_QUEUE); + + /* + * Register our workflow implementation with the worker. + * Workflow implementations must be known to the worker at runtime in + * order to dispatch workflow tasks. + */ + worker.registerWorkflowImplementationTypes(MyWorkflowImpl.class); + + /* + * Start all the workers registered for a specific task queue. + * The started workers then start polling for workflows and activities. + */ + factory.start(); + + System.out.println("Worker started. Press Ctrl+C to exit."); + // Keep the worker running + Thread.currentThread().join(); + } +} diff --git a/core/src/main/java/io/temporal/samples/apikey/MyWorkflow.java b/core/src/main/java/io/temporal/samples/apikey/MyWorkflow.java new file mode 100644 index 000000000..d02953d73 --- /dev/null +++ b/core/src/main/java/io/temporal/samples/apikey/MyWorkflow.java @@ -0,0 +1,10 @@ +package io.temporal.samples.apikey; + +import io.temporal.workflow.WorkflowInterface; +import io.temporal.workflow.WorkflowMethod; + +@WorkflowInterface +public interface MyWorkflow { + @WorkflowMethod + String execute(); +} diff --git a/core/src/main/java/io/temporal/samples/apikey/MyWorkflowImpl.java b/core/src/main/java/io/temporal/samples/apikey/MyWorkflowImpl.java new file mode 100644 index 000000000..f6fea0fe7 --- /dev/null +++ b/core/src/main/java/io/temporal/samples/apikey/MyWorkflowImpl.java @@ -0,0 +1,8 @@ +package io.temporal.samples.apikey; + +public class MyWorkflowImpl implements MyWorkflow { + @Override + public String execute() { + return "done"; + } +} diff --git a/core/src/main/java/io/temporal/samples/apikey/README.md b/core/src/main/java/io/temporal/samples/apikey/README.md new file mode 100644 index 000000000..494aa0458 --- /dev/null +++ b/core/src/main/java/io/temporal/samples/apikey/README.md @@ -0,0 +1,83 @@ +# Workflow execution with API Key + +This example shows how to secure your Temporal application with API Key authentication. +This is required to connect with Temporal Cloud or any production Temporal deployment that uses API Key authentication. + +## Prerequisites + +1. A Temporal Cloud account +2. A namespace in Temporal Cloud +3. An API Key for your namespace + +## Getting your API Key + +1. Log in to your Temporal Cloud account +2. Navigate to your namespace +3. Go to Namespace Settings > API Keys +4. Click "Create API Key" +5. Give your API Key a name and select the appropriate permissions +6. Copy the API Key value (you won't be able to see it again) + +## Export env variables + +Before running the example you need to export the following env variables: + +```bash +# Your Temporal Cloud endpoint (e.g., us-east-1.aws.api.temporal.io:7233) +export TEMPORAL_ENDPOINT="us-east-1.aws.api.temporal.io:7233" + +# Your Temporal Cloud namespace +export TEMPORAL_NAMESPACE="your-namespace" + +# Your API Key from Temporal Cloud +export TEMPORAL_API_KEY="your-api-key" +``` + +## Running this sample + +This sample consists of two components that need to be run in separate terminals: + +1. First, start the worker: +```bash +./gradlew -q execute -PmainClass=io.temporal.samples.apikey.ApiKeyWorker +``` + +2. Then, in a new terminal, run the starter: +```bash +./gradlew -q execute -PmainClass=io.temporal.samples.apikey.Starter +``` + +## Expected result + +When running the worker, you should see: +```text +[main] INFO i.t.s.WorkflowServiceStubsImpl - Created WorkflowServiceStubs for channel: ManagedChannelOrphanWrapper{delegate=ManagedChannelImpl{logId=1, target=us-east-1.aws.api.temporal.io:7233}} +[main] INFO io.temporal.internal.worker.Poller - start: Poller{name=Workflow Poller taskQueue="MyTaskQueue", namespace="your-namespace"} +Worker started. Press Ctrl+C to exit. +``` + +When running the starter, you should see: +```text +[main] INFO i.t.s.WorkflowServiceStubsImpl - Created WorkflowServiceStubs for channel: ManagedChannelOrphanWrapper{delegate=ManagedChannelImpl{logId=1, target=us-east-1.aws.api.temporal.io:7233}} +[main] INFO io.temporal.internal.worker.Poller - start: Poller{name=Workflow Poller taskQueue="MyTaskQueue", namespace="your-namespace"} +done +``` + +## Troubleshooting + +If you encounter any issues: + +1. Verify your environment variables are set correctly: + ```bash + echo $TEMPORAL_ENDPOINT + echo $TEMPORAL_NAMESPACE + echo $TEMPORAL_API_KEY + ``` + +2. Check that your API Key has the correct permissions for your namespace + +3. Ensure your namespace is active and accessible + +4. If you get connection errors, verify your endpoint is correct and accessible from your network + +5. Make sure you're running the commands from the correct directory (where the `gradlew` script is located) diff --git a/core/src/main/java/io/temporal/samples/apikey/Starter.java b/core/src/main/java/io/temporal/samples/apikey/Starter.java new file mode 100644 index 000000000..60afe7c21 --- /dev/null +++ b/core/src/main/java/io/temporal/samples/apikey/Starter.java @@ -0,0 +1,66 @@ +package io.temporal.samples.apikey; + +import io.temporal.client.WorkflowClient; +import io.temporal.client.WorkflowClientOptions; +import io.temporal.client.WorkflowOptions; +import io.temporal.serviceclient.WorkflowServiceStubs; +import io.temporal.serviceclient.WorkflowServiceStubsOptions; +import io.temporal.worker.Worker; +import io.temporal.worker.WorkerFactory; + +public class Starter { + + static final String TASK_QUEUE = "MyTaskQueue"; + static final String WORKFLOW_ID = "HelloAPIKeyWorkflow"; + + public static void main(String[] args) throws Exception { + // For temporal cloud this would be ${cloud-region}.{cloud}.api.temporal.io:7233 + // Example us-east-1.aws.api.temporal.io:7233 + String targetEndpoint = System.getenv("TEMPORAL_ENDPOINT"); + // Your registered namespace. + String namespace = System.getenv("TEMPORAL_NAMESPACE"); + // Your API Key + String apiKey = System.getenv("TEMPORAL_API_KEY"); + + if (targetEndpoint == null || namespace == null || apiKey == null) { + throw new IllegalArgumentException( + "TEMPORAL_ENDPOINT, TEMPORAL_NAMESPACE, and TEMPORAL_API_KEY environment variables must be set"); + } + + // Create API Key enabled client + WorkflowServiceStubs service = + WorkflowServiceStubs.newServiceStubs( + WorkflowServiceStubsOptions.newBuilder() + .setTarget(targetEndpoint) + .setEnableHttps(true) + .addApiKey(() -> apiKey) + .build()); + + WorkflowClient client = + WorkflowClient.newInstance( + service, WorkflowClientOptions.newBuilder().setNamespace(namespace).build()); + + WorkerFactory factory = WorkerFactory.newInstance(client); + + Worker worker = factory.newWorker(TASK_QUEUE); + + worker.registerWorkflowImplementationTypes(MyWorkflowImpl.class); + + factory.start(); + + // Create the workflow client stub. It is used to start our workflow execution. + MyWorkflow workflow = + client.newWorkflowStub( + MyWorkflow.class, + WorkflowOptions.newBuilder() + .setWorkflowId(WORKFLOW_ID) + .setTaskQueue(TASK_QUEUE) + .build()); + + String greeting = workflow.execute(); + + // Display workflow execution results + System.out.println(greeting); + System.exit(0); + } +}