From b510b704a7478a7f3afb486ac54b5a8053a2949f Mon Sep 17 00:00:00 2001 From: seanbollin Date: Tue, 25 Aug 2026 13:55:41 -0700 Subject: [PATCH 1/6] Add Google Cloud Run worker sample Add a long-lived Temporal Worker sample for Google Cloud Run that uses the new Temporalio.Extensions.Gcp.CloudRun helper to derive the worker identity and a pinned Worker Deployment Version from Cloud Run metadata. It reads TEMPORAL_ADDRESS / TEMPORAL_NAMESPACE / TEMPORAL_TASK_QUEUE from the environment, registers a greeting workflow and activity, and polls until the container is stopped. The helper is unreleased, so the project temporarily references the SDK from a sibling sdk-dotnet checkout via ProjectReference; the README and csproj explain how to switch to the released package. This is why the PR is a draft. Co-Authored-By: Claude Opus 4.8 --- README.md | 1 + src/CloudRunWorker/Activities.cs | 16 ++ src/CloudRunWorker/Program.cs | 82 ++++++++ src/CloudRunWorker/README.md | 181 ++++++++++++++++++ src/CloudRunWorker/SampleWorkflow.workflow.cs | 22 +++ .../TemporalioSamples.CloudRunWorker.csproj | 32 ++++ 6 files changed, 334 insertions(+) create mode 100644 src/CloudRunWorker/Activities.cs create mode 100644 src/CloudRunWorker/Program.cs create mode 100644 src/CloudRunWorker/README.md create mode 100644 src/CloudRunWorker/SampleWorkflow.workflow.cs create mode 100644 src/CloudRunWorker/TemporalioSamples.CloudRunWorker.csproj diff --git a/README.md b/README.md index 13ccd52..24edf58 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,7 @@ Prerequisites: * [AspNet](src/AspNet) - Demonstration of a generic host worker and an ASP.NET workflow starter. * [Bedrock](src/Bedrock) - Orchestrate a chatbot with Amazon Bedrock. * [ClientMtls](src/ClientMtls) - How to use client certificate authentication, e.g. for Temporal Cloud. +* [CloudRunWorker](src/CloudRunWorker) - Run a long-lived Temporal Worker on a Google Cloud Run worker pool, deriving worker identity and deployment version from Cloud Run metadata. * [ContextPropagation](src/ContextPropagation) - Context propagation via interceptors. * [CounterInterceptor](src/CounterInterceptor/) - Simple Workflow and Client Interceptors example. * [DependencyInjection](src/DependencyInjection) - How to inject dependencies in activities and use generic hosts for workers diff --git a/src/CloudRunWorker/Activities.cs b/src/CloudRunWorker/Activities.cs new file mode 100644 index 0000000..3461f10 --- /dev/null +++ b/src/CloudRunWorker/Activities.cs @@ -0,0 +1,16 @@ +namespace TemporalioSamples.CloudRunWorker; + +using Microsoft.Extensions.Logging; +using Temporalio.Activities; + +public static class Activities +{ + [Activity] + public static string SayHello(string name) + { + ActivityExecutionContext.Current.Logger.LogInformation( + "SayHello activity invoked with name: {Name}", + name); + return $"Hello, {name}!"; + } +} diff --git a/src/CloudRunWorker/Program.cs b/src/CloudRunWorker/Program.cs new file mode 100644 index 0000000..e1d0511 --- /dev/null +++ b/src/CloudRunWorker/Program.cs @@ -0,0 +1,82 @@ +using System.Runtime.InteropServices; +using Microsoft.Extensions.Logging; +using Temporalio.Client; +using Temporalio.Extensions.Gcp.CloudRun; +using Temporalio.Worker; +using TemporalioSamples.CloudRunWorker; + +// Cloud Run injects these via `--set-env-vars`; fall back to a local dev server for convenience. +var address = GetEnvironmentVariable("TEMPORAL_ADDRESS") ?? "localhost:7233"; +var temporalNamespace = GetEnvironmentVariable("TEMPORAL_NAMESPACE") ?? "default"; +var taskQueue = GetEnvironmentVariable("TEMPORAL_TASK_QUEUE") ?? "cloud-run-worker-sample"; + +using var loggerFactory = LoggerFactory.Create(builder => builder. + AddSimpleConsole(options => options.TimestampFormat = "[HH:mm:ss] "). + SetMinimumLevel(LogLevel.Information)); +var logger = loggerFactory.CreateLogger("CloudRunWorker"); + +var clientOptions = new TemporalClientConnectOptions(address) +{ + Namespace = temporalNamespace, + LoggerFactory = loggerFactory, +}; + +// Reads the Cloud Run instance id from the metadata server, and the worker pool / service name and +// revision from the environment. This also sets the client Identity to "{instanceId}@{revision}" +// (unless one was already configured). The returned metadata is reused for the worker below. +// +// NOTE: this requires the process to be running on a Cloud Run worker pool or service. Running it +// elsewhere throws because the metadata server is unreachable. +var metadata = await clientOptions.ApplyGoogleCloudRunDefaultsAsync(); +logger.LogInformation( + "Resolved Cloud Run worker identity {Identity} (name={Name}, revision={Revision})", + metadata.WorkerIdentity, + metadata.Name, + metadata.Revision); + +var client = await TemporalClient.ConnectAsync(clientOptions); + +var workerOptions = new TemporalWorkerOptions(taskQueue). + AddWorkflow(). + AddActivity(Activities.SayHello); + +// Enables worker versioning using the Cloud Run deployment version (deployment name = worker pool / +// service name, build id = revision) and pins workflows to this version by default. +workerOptions.ApplyGoogleCloudRunDefaults(metadata); + +using var worker = new TemporalWorker(client, workerOptions); + +// Cloud Run sends SIGTERM before stopping an instance; shut the worker down gracefully on that and +// on Ctrl+C so in-flight tasks can finish. +using var cancellationSource = new CancellationTokenSource(); +Console.CancelKeyPress += (_, eventArgs) => +{ + eventArgs.Cancel = true; + cancellationSource.Cancel(); +}; +using var sigterm = PosixSignalRegistration.Create( + PosixSignal.SIGTERM, + context => + { + context.Cancel = true; + cancellationSource.Cancel(); + }); + +logger.LogInformation( + "Worker started on task queue '{TaskQueue}' against {Address} (namespace '{Namespace}').", + taskQueue, + address, + temporalNamespace); +try +{ + await worker.ExecuteAsync(cancellationSource.Token); +} +catch (OperationCanceledException) +{ + logger.LogInformation("Shutdown signal received; worker stopped."); +} + +static string? GetEnvironmentVariable(string name) => + Environment.GetEnvironmentVariable(name) is { } value && !string.IsNullOrWhiteSpace(value) + ? value + : null; diff --git a/src/CloudRunWorker/README.md b/src/CloudRunWorker/README.md new file mode 100644 index 0000000..17d24ea --- /dev/null +++ b/src/CloudRunWorker/README.md @@ -0,0 +1,181 @@ +# Cloud Run Worker + +This sample demonstrates how to run a long-lived Temporal Worker on +[Google Cloud Run](https://cloud.google.com/run) and derive its identity and +[Worker Deployment Version](https://docs.temporal.io/worker-deployments) from +Cloud Run metadata using the `Temporalio.Extensions.Gcp.CloudRun` package. + +The sample registers a greeting Workflow and Activity and polls until the +container is stopped. + +## How it works + +Unlike the AWS Lambda extension, Cloud Run runs a long-lived container, so this +is a small metadata helper rather than a worker wrapper. At startup the sample: + +1. Calls `TemporalClientConnectOptions.ApplyGoogleCloudRunDefaultsAsync()`, which + reads the instance id from the Cloud Run metadata server and the + deployment name and revision from the environment, then sets the client + `Identity` to `{instanceId}@{revision}` (only when an identity is not already + set). +2. Calls `TemporalWorkerOptions.ApplyGoogleCloudRunDefaults(metadata)`, which + turns on Worker Versioning with a Worker Deployment Version whose deployment + name is the Cloud Run name and whose build id is the Cloud Run revision, and + sets the default versioning behavior to `Pinned`. + +The name and revision come from the environment that Cloud Run injects: + +| Value | Worker pool variable | Service variable | +| -------- | ---------------------- | ---------------- | +| Name | `CLOUD_RUN_WORKER_POOL`| `K_SERVICE` | +| Revision | `CLOUD_RUN_REVISION` | `K_REVISION` | + +The worker-pool variables take precedence, so the same code works on a Cloud Run +[worker pool](https://cloud.google.com/run/docs/deploy-worker-pools) or a Cloud +Run service. + +### Why worker pools + +A Cloud Run **worker pool** runs one or more long-lived container instances that +receive no inbound HTTP requests and are not scaled by request traffic, which is +exactly the shape of a Temporal Worker that polls a Task Queue. Each revision of +a worker pool maps cleanly onto a Temporal Worker Deployment Version: deploying a +new revision creates a new build id, and pinning keeps in-flight Workflows on the +revision that started them until you roll traffic forward. + +## Unreleased dependency + +`Temporalio.Extensions.Gcp.CloudRun` is not published to NuGet yet, so this +sample cannot be built or deployed from a released package. To let it build +locally, `TemporalioSamples.CloudRunWorker.csproj` references the SDK from a +sibling checkout of [sdk-dotnet](https://github.com/temporalio/sdk-dotnet) laid +out next to this repository: + +```text +temporalio/ + samples-dotnet/ # this repo + sdk-dotnet-2/ # https://github.com/temporalio/sdk-dotnet on branch cloud-run-worker-id +``` + +Once the package is released, delete the temporary item groups in the `.csproj` +(the `Temporalio*` `PackageReference Remove` entries and the `ProjectReference` +entries) and replace them with: + +```xml + + + +``` + +This is why the pull request that adds this sample is a draft. + +## Prerequisites + +- A [Temporal Cloud](https://temporal.io/cloud) namespace, or a self-hosted + Temporal cluster the worker pool can reach +- A Google Cloud project with billing enabled and the Cloud Run API enabled +- The [`gcloud` CLI](https://cloud.google.com/sdk/docs/install), authenticated + (`gcloud auth login`) with the project set (`gcloud config set project ...`) +- The [Temporal CLI](https://docs.temporal.io/cli) +- .NET 8 to build locally + +## Configuration + +The worker reads its connection settings from the environment; Cloud Run injects +these through `--set-env-vars`: + +| Variable | Default | Description | +| --------------------- | -------------------------- | ----------------------------------- | +| `TEMPORAL_ADDRESS` | `localhost:7233` | Temporal frontend `host:port`. | +| `TEMPORAL_NAMESPACE` | `default` | Temporal namespace. | +| `TEMPORAL_TASK_QUEUE` | `cloud-run-worker-sample` | Task Queue the worker polls. | + +This sample uses a plaintext connection for brevity. For Temporal Cloud, add API +key / mTLS configuration to `Program.cs` before deploying. + +## 1. Deploy to a Cloud Run worker pool + +Deploy the sample as a worker pool from source (Cloud Build packages the +container with the .NET buildpack). Worker pools are currently a preview feature +and may require the `beta`/`alpha` track: + +```bash +export REGION="us-central1" +export WORKER_POOL="temporal-dotnet-worker" + +gcloud run worker-pools deploy "$WORKER_POOL" \ + --source . \ + --region "$REGION" \ + --set-env-vars \ +TEMPORAL_ADDRESS=..tmprl.cloud:7233,TEMPORAL_NAMESPACE=.,TEMPORAL_TASK_QUEUE=cloud-run-worker-sample +``` + +Run this from `src/CloudRunWorker`. Cloud Run sets `CLOUD_RUN_WORKER_POOL` to +`$WORKER_POOL` and `CLOUD_RUN_REVISION` to the revision it creates, which the +helper turns into the worker identity and Worker Deployment Version. Deploying +again creates a new revision, and therefore a new build id. + +## 2. Route the Worker Deployment Version + +Once the worker pool is polling, point the deployment's current version at the +revision so pinned Workflows are routed to it. The deployment name is the worker +pool name and the build id is the Cloud Run revision, which +`gcloud run worker-pools describe` reports: + +```bash +export DEPLOYMENT_NAME="$WORKER_POOL" +export BUILD_ID="$(gcloud run worker-pools describe "$WORKER_POOL" \ + --region "$REGION" \ + --format 'value(status.latestReadyRevisionName)')" + +temporal worker deployment set-current-version \ + --deployment-name "$DEPLOYMENT_NAME" \ + --build-id "$BUILD_ID" \ + --yes +``` + +Verify the routing state: + +```bash +temporal worker deployment describe --name "$DEPLOYMENT_NAME" +``` + +## 3. Start a Workflow + +Start the greeting Workflow on the same Task Queue and wait for the result: + +```bash +temporal workflow execute \ + --task-queue cloud-run-worker-sample \ + --type SampleWorkflow \ + --workflow-id cloud-run-worker-sample-1 \ + --input '"Cloud Run"' +``` + +A successful run returns `"Hello, Cloud Run!"`. + +## 4. Clean up + +Reset Temporal routing and delete the worker pool: + +```bash +temporal worker deployment set-current-version \ + --deployment-name "$DEPLOYMENT_NAME" \ + --unversioned \ + --yes + +gcloud run worker-pools delete "$WORKER_POOL" --region "$REGION" +``` + +## Build locally + +With the sibling `sdk-dotnet-2` checkout in place (see +[Unreleased dependency](#unreleased-dependency)): + +```bash +dotnet build src/CloudRunWorker +``` + +Running the worker outside Cloud Run fails at startup because the Cloud Run +metadata server is unreachable; deploy it to a worker pool (or service) to run +it. diff --git a/src/CloudRunWorker/SampleWorkflow.workflow.cs b/src/CloudRunWorker/SampleWorkflow.workflow.cs new file mode 100644 index 0000000..dee4a71 --- /dev/null +++ b/src/CloudRunWorker/SampleWorkflow.workflow.cs @@ -0,0 +1,22 @@ +namespace TemporalioSamples.CloudRunWorker; + +using Microsoft.Extensions.Logging; +using Temporalio.Workflows; + +// Worker versioning is turned on by TemporalWorkerOptions.ApplyGoogleCloudRunDefaults, which sets the +// deployment's default versioning behavior to Pinned. A workflow can still override that with +// [Workflow(VersioningBehavior = ...)]; this sample relies on the pinned default from the helper. +[Workflow] +public class SampleWorkflow +{ + [WorkflowRun] + public async Task RunAsync(string name) + { + Workflow.Logger.LogInformation("SampleWorkflow started with name: {Name}", name); + var result = await Workflow.ExecuteActivityAsync( + () => Activities.SayHello(name), + new() { StartToCloseTimeout = TimeSpan.FromSeconds(10) }); + Workflow.Logger.LogInformation("SampleWorkflow completed with result: {Result}", result); + return result; + } +} diff --git a/src/CloudRunWorker/TemporalioSamples.CloudRunWorker.csproj b/src/CloudRunWorker/TemporalioSamples.CloudRunWorker.csproj new file mode 100644 index 0000000..ce999f0 --- /dev/null +++ b/src/CloudRunWorker/TemporalioSamples.CloudRunWorker.csproj @@ -0,0 +1,32 @@ + + + + Exe + + + + + + + + + + + + + + + From 81955b5719b663a1906407050f6b6b6e6d68723c Mon Sep 17 00:00:00 2001 From: seanbollin Date: Mon, 31 Aug 2026 13:23:09 -0700 Subject: [PATCH 2/6] Use CloudRunPlugin in the Cloud Run worker sample Register a single CloudRunPlugin on TemporalClientConnectOptions.Plugins instead of calling the removed ApplyGoogleCloudRunDefaults* helpers. The plugin sets the client identity at connect time and pins the worker to the Cloud Run deployment version automatically. Co-Authored-By: Claude Opus 4.8 --- src/CloudRunWorker/Program.cs | 27 +++++++------------ src/CloudRunWorker/README.md | 25 ++++++++--------- src/CloudRunWorker/SampleWorkflow.workflow.cs | 4 +-- 3 files changed, 25 insertions(+), 31 deletions(-) diff --git a/src/CloudRunWorker/Program.cs b/src/CloudRunWorker/Program.cs index e1d0511..dfb47e1 100644 --- a/src/CloudRunWorker/Program.cs +++ b/src/CloudRunWorker/Program.cs @@ -15,35 +15,28 @@ SetMinimumLevel(LogLevel.Information)); var logger = loggerFactory.CreateLogger("CloudRunWorker"); +// Register the Cloud Run plugin once on the client. At connect time it reads the Cloud Run instance +// id from the metadata server, and the worker pool / service name and revision from the environment, +// then sets the client Identity to "{instanceId}@{revision}" (unless one was already configured). +// Because it is also a worker plugin, it later enables worker versioning and pins the worker below +// to the Cloud Run deployment version automatically (deployment name = worker pool / service name, +// build id = revision). +// +// NOTE: this requires the process to be running on a Cloud Run worker pool or service. Running it +// elsewhere throws at connect time because the metadata server is unreachable. var clientOptions = new TemporalClientConnectOptions(address) { Namespace = temporalNamespace, LoggerFactory = loggerFactory, + Plugins = new[] { new CloudRunPlugin() }, }; -// Reads the Cloud Run instance id from the metadata server, and the worker pool / service name and -// revision from the environment. This also sets the client Identity to "{instanceId}@{revision}" -// (unless one was already configured). The returned metadata is reused for the worker below. -// -// NOTE: this requires the process to be running on a Cloud Run worker pool or service. Running it -// elsewhere throws because the metadata server is unreachable. -var metadata = await clientOptions.ApplyGoogleCloudRunDefaultsAsync(); -logger.LogInformation( - "Resolved Cloud Run worker identity {Identity} (name={Name}, revision={Revision})", - metadata.WorkerIdentity, - metadata.Name, - metadata.Revision); - var client = await TemporalClient.ConnectAsync(clientOptions); var workerOptions = new TemporalWorkerOptions(taskQueue). AddWorkflow(). AddActivity(Activities.SayHello); -// Enables worker versioning using the Cloud Run deployment version (deployment name = worker pool / -// service name, build id = revision) and pins workflows to this version by default. -workerOptions.ApplyGoogleCloudRunDefaults(metadata); - using var worker = new TemporalWorker(client, workerOptions); // Cloud Run sends SIGTERM before stopping an instance; shut the worker down gracefully on that and diff --git a/src/CloudRunWorker/README.md b/src/CloudRunWorker/README.md index 17d24ea..77a5e24 100644 --- a/src/CloudRunWorker/README.md +++ b/src/CloudRunWorker/README.md @@ -11,17 +11,18 @@ container is stopped. ## How it works Unlike the AWS Lambda extension, Cloud Run runs a long-lived container, so this -is a small metadata helper rather than a worker wrapper. At startup the sample: - -1. Calls `TemporalClientConnectOptions.ApplyGoogleCloudRunDefaultsAsync()`, which - reads the instance id from the Cloud Run metadata server and the - deployment name and revision from the environment, then sets the client - `Identity` to `{instanceId}@{revision}` (only when an identity is not already - set). -2. Calls `TemporalWorkerOptions.ApplyGoogleCloudRunDefaults(metadata)`, which - turns on Worker Versioning with a Worker Deployment Version whose deployment - name is the Cloud Run name and whose build id is the Cloud Run revision, and - sets the default versioning behavior to `Pinned`. +is a metadata-driven plugin rather than a worker wrapper. The sample registers a +single `CloudRunPlugin` on `TemporalClientConnectOptions.Plugins`. Because it is +both a client and a worker plugin, registering it once is enough: + +1. At connect time its client hook reads the instance id from the Cloud Run + metadata server and the deployment name and revision from the environment, + then sets the client `Identity` to `{instanceId}@{revision}` (only when an + identity is not already set). +2. When the worker is created its worker hook turns on Worker Versioning with a + Worker Deployment Version whose deployment name is the Cloud Run name and + whose build id is the Cloud Run revision, and sets the default versioning + behavior to `Pinned`. The name and revision come from the environment that Cloud Run injects: @@ -112,7 +113,7 @@ TEMPORAL_ADDRESS=..tmprl.cloud:7233,TEMPORAL_NAMESPACE= Run this from `src/CloudRunWorker`. Cloud Run sets `CLOUD_RUN_WORKER_POOL` to `$WORKER_POOL` and `CLOUD_RUN_REVISION` to the revision it creates, which the -helper turns into the worker identity and Worker Deployment Version. Deploying +plugin turns into the worker identity and Worker Deployment Version. Deploying again creates a new revision, and therefore a new build id. ## 2. Route the Worker Deployment Version diff --git a/src/CloudRunWorker/SampleWorkflow.workflow.cs b/src/CloudRunWorker/SampleWorkflow.workflow.cs index dee4a71..dd2104e 100644 --- a/src/CloudRunWorker/SampleWorkflow.workflow.cs +++ b/src/CloudRunWorker/SampleWorkflow.workflow.cs @@ -3,9 +3,9 @@ namespace TemporalioSamples.CloudRunWorker; using Microsoft.Extensions.Logging; using Temporalio.Workflows; -// Worker versioning is turned on by TemporalWorkerOptions.ApplyGoogleCloudRunDefaults, which sets the +// Worker versioning is turned on by the CloudRunPlugin registered in Program.cs, which sets the // deployment's default versioning behavior to Pinned. A workflow can still override that with -// [Workflow(VersioningBehavior = ...)]; this sample relies on the pinned default from the helper. +// [Workflow(VersioningBehavior = ...)]; this sample relies on the pinned default from the plugin. [Workflow] public class SampleWorkflow { From 91d8588e360baf7ee5cae4eff2c280bfd919031b Mon Sep 17 00:00:00 2001 From: seanbollin Date: Mon, 31 Aug 2026 15:38:09 -0700 Subject: [PATCH 3/6] Rename CloudRunPlugin to WorkerIdPlugin in Cloud Run sample Follows the sdk-dotnet rename: the Cloud Run worker-identity plugin is now WorkerIdPlugin (Cloud Run can host multiple Temporal plugins, so the generic CloudRunPlugin name is no longer used). Update the sample's registration, workflow comment, and README. Co-Authored-By: Claude Opus 4.8 --- src/CloudRunWorker/Program.cs | 2 +- src/CloudRunWorker/README.md | 2 +- src/CloudRunWorker/SampleWorkflow.workflow.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/CloudRunWorker/Program.cs b/src/CloudRunWorker/Program.cs index dfb47e1..87febf0 100644 --- a/src/CloudRunWorker/Program.cs +++ b/src/CloudRunWorker/Program.cs @@ -28,7 +28,7 @@ { Namespace = temporalNamespace, LoggerFactory = loggerFactory, - Plugins = new[] { new CloudRunPlugin() }, + Plugins = new[] { new WorkerIdPlugin() }, }; var client = await TemporalClient.ConnectAsync(clientOptions); diff --git a/src/CloudRunWorker/README.md b/src/CloudRunWorker/README.md index 77a5e24..e60bd87 100644 --- a/src/CloudRunWorker/README.md +++ b/src/CloudRunWorker/README.md @@ -12,7 +12,7 @@ container is stopped. Unlike the AWS Lambda extension, Cloud Run runs a long-lived container, so this is a metadata-driven plugin rather than a worker wrapper. The sample registers a -single `CloudRunPlugin` on `TemporalClientConnectOptions.Plugins`. Because it is +single `WorkerIdPlugin` on `TemporalClientConnectOptions.Plugins`. Because it is both a client and a worker plugin, registering it once is enough: 1. At connect time its client hook reads the instance id from the Cloud Run diff --git a/src/CloudRunWorker/SampleWorkflow.workflow.cs b/src/CloudRunWorker/SampleWorkflow.workflow.cs index dd2104e..4b6fdf8 100644 --- a/src/CloudRunWorker/SampleWorkflow.workflow.cs +++ b/src/CloudRunWorker/SampleWorkflow.workflow.cs @@ -3,7 +3,7 @@ namespace TemporalioSamples.CloudRunWorker; using Microsoft.Extensions.Logging; using Temporalio.Workflows; -// Worker versioning is turned on by the CloudRunPlugin registered in Program.cs, which sets the +// Worker versioning is turned on by the WorkerIdPlugin registered in Program.cs, which sets the // deployment's default versioning behavior to Pinned. A workflow can still override that with // [Workflow(VersioningBehavior = ...)]; this sample relies on the pinned default from the plugin. [Workflow] From 4a87ecfb847b4410b2d4f1f6d3583f1afdb881f2 Mon Sep 17 00:00:00 2001 From: seanbollin Date: Mon, 31 Aug 2026 16:22:29 -0700 Subject: [PATCH 4/6] Repoint Cloud Run sample at the .WorkerId extension project The Google Cloud Run worker-ID plugin moved from the root Temporalio.Extensions.Gcp.CloudRun project into a new Temporalio.Extensions.Gcp.CloudRun.WorkerId sibling. Update the project reference, the using directive, and the README to match. Co-Authored-By: Claude Opus 4.8 --- src/CloudRunWorker/Program.cs | 2 +- src/CloudRunWorker/README.md | 6 +++--- .../TemporalioSamples.CloudRunWorker.csproj | 8 ++++---- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/CloudRunWorker/Program.cs b/src/CloudRunWorker/Program.cs index 87febf0..eaf7ba0 100644 --- a/src/CloudRunWorker/Program.cs +++ b/src/CloudRunWorker/Program.cs @@ -1,7 +1,7 @@ using System.Runtime.InteropServices; using Microsoft.Extensions.Logging; using Temporalio.Client; -using Temporalio.Extensions.Gcp.CloudRun; +using Temporalio.Extensions.Gcp.CloudRun.WorkerId; using Temporalio.Worker; using TemporalioSamples.CloudRunWorker; diff --git a/src/CloudRunWorker/README.md b/src/CloudRunWorker/README.md index e60bd87..383ab33 100644 --- a/src/CloudRunWorker/README.md +++ b/src/CloudRunWorker/README.md @@ -3,7 +3,7 @@ This sample demonstrates how to run a long-lived Temporal Worker on [Google Cloud Run](https://cloud.google.com/run) and derive its identity and [Worker Deployment Version](https://docs.temporal.io/worker-deployments) from -Cloud Run metadata using the `Temporalio.Extensions.Gcp.CloudRun` package. +Cloud Run metadata using the `Temporalio.Extensions.Gcp.CloudRun.WorkerId` package. The sample registers a greeting Workflow and Activity and polls until the container is stopped. @@ -46,7 +46,7 @@ revision that started them until you roll traffic forward. ## Unreleased dependency -`Temporalio.Extensions.Gcp.CloudRun` is not published to NuGet yet, so this +`Temporalio.Extensions.Gcp.CloudRun.WorkerId` is not published to NuGet yet, so this sample cannot be built or deployed from a released package. To let it build locally, `TemporalioSamples.CloudRunWorker.csproj` references the SDK from a sibling checkout of [sdk-dotnet](https://github.com/temporalio/sdk-dotnet) laid @@ -64,7 +64,7 @@ entries) and replace them with: ```xml - + ``` diff --git a/src/CloudRunWorker/TemporalioSamples.CloudRunWorker.csproj b/src/CloudRunWorker/TemporalioSamples.CloudRunWorker.csproj index ce999f0..df24435 100644 --- a/src/CloudRunWorker/TemporalioSamples.CloudRunWorker.csproj +++ b/src/CloudRunWorker/TemporalioSamples.CloudRunWorker.csproj @@ -7,15 +7,15 @@ @@ -26,7 +26,7 @@ - + From c362521c5f463fec5f67a49028f139127feaa809 Mon Sep 17 00:00:00 2001 From: seanbollin Date: Wed, 9 Sep 2026 15:38:19 -0700 Subject: [PATCH 5/6] Restructure Cloud Run worker-identity sample and scrub versioning Move src/CloudRunWorker to src/Gcp/CloudRun/WorkerId to mirror the sibling OpenTelemetry Cloud Run sample, and rename the project and namespace to TemporalioSamples.Gcp.CloudRun.WorkerId. Register the moved project in the solution and update the root README sample entry. Remove all Worker Deployment Versioning content (deployment name, build id, versioning behavior, pinned) from the code, comments, and README. The sample now only registers WorkerIdPlugin to set the worker identity from Cloud Run metadata, and logs GoogleCloudRunMetadata.WorkerIdentity. Co-Authored-By: Claude Opus 4.8 --- README.md | 2 +- TemporalioSamples.sln | 14 +++ .../CloudRun/WorkerId}/Activities.cs | 2 +- .../CloudRun/WorkerId}/Program.cs | 16 ++-- .../CloudRun/WorkerId}/README.md | 88 ++++++------------- .../WorkerId}/SampleWorkflow.workflow.cs | 7 +- ...ralioSamples.Gcp.CloudRun.WorkerId.csproj} | 6 +- 7 files changed, 58 insertions(+), 77 deletions(-) rename src/{CloudRunWorker => Gcp/CloudRun/WorkerId}/Activities.cs (87%) rename src/{CloudRunWorker => Gcp/CloudRun/WorkerId}/Program.cs (79%) rename src/{CloudRunWorker => Gcp/CloudRun/WorkerId}/README.md (61%) rename src/{CloudRunWorker => Gcp/CloudRun/WorkerId}/SampleWorkflow.workflow.cs (63%) rename src/{CloudRunWorker/TemporalioSamples.CloudRunWorker.csproj => Gcp/CloudRun/WorkerId/TemporalioSamples.Gcp.CloudRun.WorkerId.csproj} (81%) diff --git a/README.md b/README.md index 24edf58..a437e15 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ Prerequisites: * [AspNet](src/AspNet) - Demonstration of a generic host worker and an ASP.NET workflow starter. * [Bedrock](src/Bedrock) - Orchestrate a chatbot with Amazon Bedrock. * [ClientMtls](src/ClientMtls) - How to use client certificate authentication, e.g. for Temporal Cloud. -* [CloudRunWorker](src/CloudRunWorker) - Run a long-lived Temporal Worker on a Google Cloud Run worker pool, deriving worker identity and deployment version from Cloud Run metadata. +* [Gcp/CloudRun/WorkerId](src/Gcp/CloudRun/WorkerId) - Run a long-lived Temporal Worker on a Google Cloud Run worker pool, deriving the worker identity from Cloud Run metadata. * [ContextPropagation](src/ContextPropagation) - Context propagation via interceptors. * [CounterInterceptor](src/CounterInterceptor/) - Simple Workflow and Client Interceptors example. * [DependencyInjection](src/DependencyInjection) - How to inject dependencies in activities and use generic hosts for workers diff --git a/TemporalioSamples.sln b/TemporalioSamples.sln index a3ede5c..8ab220c 100644 --- a/TemporalioSamples.sln +++ b/TemporalioSamples.sln @@ -141,6 +141,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "SearchAttributes", "SearchA EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TemporalioSamples.SearchAttributes", "src\SearchAttributes\TemporalioSamples.SearchAttributes.csproj", "{97376F57-BA10-464B-AFBD-583E187DE947}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TemporalioSamples.Gcp.CloudRun.WorkerId", "src\Gcp\CloudRun\WorkerId\TemporalioSamples.Gcp.CloudRun.WorkerId.csproj", "{995322ED-0CF3-40F5-B521-380A9591DACC}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -799,6 +801,18 @@ Global {97376F57-BA10-464B-AFBD-583E187DE947}.Release|x64.Build.0 = Release|Any CPU {97376F57-BA10-464B-AFBD-583E187DE947}.Release|x86.ActiveCfg = Release|Any CPU {97376F57-BA10-464B-AFBD-583E187DE947}.Release|x86.Build.0 = Release|Any CPU + {995322ED-0CF3-40F5-B521-380A9591DACC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {995322ED-0CF3-40F5-B521-380A9591DACC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {995322ED-0CF3-40F5-B521-380A9591DACC}.Debug|x64.ActiveCfg = Debug|Any CPU + {995322ED-0CF3-40F5-B521-380A9591DACC}.Debug|x64.Build.0 = Debug|Any CPU + {995322ED-0CF3-40F5-B521-380A9591DACC}.Debug|x86.ActiveCfg = Debug|Any CPU + {995322ED-0CF3-40F5-B521-380A9591DACC}.Debug|x86.Build.0 = Debug|Any CPU + {995322ED-0CF3-40F5-B521-380A9591DACC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {995322ED-0CF3-40F5-B521-380A9591DACC}.Release|Any CPU.Build.0 = Release|Any CPU + {995322ED-0CF3-40F5-B521-380A9591DACC}.Release|x64.ActiveCfg = Release|Any CPU + {995322ED-0CF3-40F5-B521-380A9591DACC}.Release|x64.Build.0 = Release|Any CPU + {995322ED-0CF3-40F5-B521-380A9591DACC}.Release|x86.ActiveCfg = Release|Any CPU + {995322ED-0CF3-40F5-B521-380A9591DACC}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/src/CloudRunWorker/Activities.cs b/src/Gcp/CloudRun/WorkerId/Activities.cs similarity index 87% rename from src/CloudRunWorker/Activities.cs rename to src/Gcp/CloudRun/WorkerId/Activities.cs index 3461f10..2ba663b 100644 --- a/src/CloudRunWorker/Activities.cs +++ b/src/Gcp/CloudRun/WorkerId/Activities.cs @@ -1,4 +1,4 @@ -namespace TemporalioSamples.CloudRunWorker; +namespace TemporalioSamples.Gcp.CloudRun.WorkerId; using Microsoft.Extensions.Logging; using Temporalio.Activities; diff --git a/src/CloudRunWorker/Program.cs b/src/Gcp/CloudRun/WorkerId/Program.cs similarity index 79% rename from src/CloudRunWorker/Program.cs rename to src/Gcp/CloudRun/WorkerId/Program.cs index eaf7ba0..0630fbc 100644 --- a/src/CloudRunWorker/Program.cs +++ b/src/Gcp/CloudRun/WorkerId/Program.cs @@ -3,7 +3,7 @@ using Temporalio.Client; using Temporalio.Extensions.Gcp.CloudRun.WorkerId; using Temporalio.Worker; -using TemporalioSamples.CloudRunWorker; +using TemporalioSamples.Gcp.CloudRun.WorkerId; // Cloud Run injects these via `--set-env-vars`; fall back to a local dev server for convenience. var address = GetEnvironmentVariable("TEMPORAL_ADDRESS") ?? "localhost:7233"; @@ -13,14 +13,13 @@ using var loggerFactory = LoggerFactory.Create(builder => builder. AddSimpleConsole(options => options.TimestampFormat = "[HH:mm:ss] "). SetMinimumLevel(LogLevel.Information)); -var logger = loggerFactory.CreateLogger("CloudRunWorker"); +var logger = loggerFactory.CreateLogger("CloudRunWorkerId"); // Register the Cloud Run plugin once on the client. At connect time it reads the Cloud Run instance // id from the metadata server, and the worker pool / service name and revision from the environment, -// then sets the client Identity to "{instanceId}@{revision}" (unless one was already configured). -// Because it is also a worker plugin, it later enables worker versioning and pins the worker below -// to the Cloud Run deployment version automatically (deployment name = worker pool / service name, -// build id = revision). +// then sets the client Identity to the worker identity "{instanceId}@{revision}" (unless one was +// already configured). Every worker created from this client inherits that identity. The plugin only +// sets the worker identity; it does not configure anything else. // // NOTE: this requires the process to be running on a Cloud Run worker pool or service. Running it // elsewhere throws at connect time because the metadata server is unreachable. @@ -33,6 +32,11 @@ var client = await TemporalClient.ConnectAsync(clientOptions); +// The plugin already applied this identity to the client above; read the metadata directly to log +// the worker identity this process runs under. +var metadata = await GoogleCloudRunMetadata.FetchAsync(); +logger.LogInformation("Cloud Run worker identity: {WorkerIdentity}", metadata.WorkerIdentity); + var workerOptions = new TemporalWorkerOptions(taskQueue). AddWorkflow(). AddActivity(Activities.SayHello); diff --git a/src/CloudRunWorker/README.md b/src/Gcp/CloudRun/WorkerId/README.md similarity index 61% rename from src/CloudRunWorker/README.md rename to src/Gcp/CloudRun/WorkerId/README.md index 383ab33..a8ab613 100644 --- a/src/CloudRunWorker/README.md +++ b/src/Gcp/CloudRun/WorkerId/README.md @@ -1,9 +1,9 @@ -# Cloud Run Worker +# Cloud Run Worker Identity This sample demonstrates how to run a long-lived Temporal Worker on -[Google Cloud Run](https://cloud.google.com/run) and derive its identity and -[Worker Deployment Version](https://docs.temporal.io/worker-deployments) from -Cloud Run metadata using the `Temporalio.Extensions.Gcp.CloudRun.WorkerId` package. +[Google Cloud Run](https://cloud.google.com/run) and derive its worker identity +from Cloud Run metadata using the `Temporalio.Extensions.Gcp.CloudRun.WorkerId` +package. The sample registers a greeting Workflow and Activity and polls until the container is stopped. @@ -12,43 +12,38 @@ container is stopped. Unlike the AWS Lambda extension, Cloud Run runs a long-lived container, so this is a metadata-driven plugin rather than a worker wrapper. The sample registers a -single `WorkerIdPlugin` on `TemporalClientConnectOptions.Plugins`. Because it is -both a client and a worker plugin, registering it once is enough: - -1. At connect time its client hook reads the instance id from the Cloud Run - metadata server and the deployment name and revision from the environment, - then sets the client `Identity` to `{instanceId}@{revision}` (only when an - identity is not already set). -2. When the worker is created its worker hook turns on Worker Versioning with a - Worker Deployment Version whose deployment name is the Cloud Run name and - whose build id is the Cloud Run revision, and sets the default versioning - behavior to `Pinned`. +single `WorkerIdPlugin` on `TemporalClientConnectOptions.Plugins`. At connect +time the plugin reads the instance id from the Cloud Run metadata server and the +name and revision from the environment, then sets the client `Identity` to +`{instanceId}@{revision}` (only when an identity is not already set). Every +Worker created from that client inherits the identity. The name and revision come from the environment that Cloud Run injects: -| Value | Worker pool variable | Service variable | -| -------- | ---------------------- | ---------------- | -| Name | `CLOUD_RUN_WORKER_POOL`| `K_SERVICE` | -| Revision | `CLOUD_RUN_REVISION` | `K_REVISION` | +| Value | Worker pool variable | Service variable | +| -------- | ----------------------- | ---------------- | +| Name | `CLOUD_RUN_WORKER_POOL` | `K_SERVICE` | +| Revision | `CLOUD_RUN_REVISION` | `K_REVISION` | The worker-pool variables take precedence, so the same code works on a Cloud Run [worker pool](https://cloud.google.com/run/docs/deploy-worker-pools) or a Cloud Run service. +`Program.cs` also reads `GoogleCloudRunMetadata` directly and logs its +`WorkerIdentity`, so the identity the Worker runs under is visible in the logs. + ### Why worker pools A Cloud Run **worker pool** runs one or more long-lived container instances that receive no inbound HTTP requests and are not scaled by request traffic, which is -exactly the shape of a Temporal Worker that polls a Task Queue. Each revision of -a worker pool maps cleanly onto a Temporal Worker Deployment Version: deploying a -new revision creates a new build id, and pinning keeps in-flight Workflows on the -revision that started them until you roll traffic forward. +exactly the shape of a Temporal Worker that polls a Task Queue. Each instance +gets a stable worker identity from its Cloud Run instance id and revision. ## Unreleased dependency `Temporalio.Extensions.Gcp.CloudRun.WorkerId` is not published to NuGet yet, so this sample cannot be built or deployed from a released package. To let it build -locally, `TemporalioSamples.CloudRunWorker.csproj` references the SDK from a +locally, `TemporalioSamples.Gcp.CloudRun.WorkerId.csproj` references the SDK from a sibling checkout of [sdk-dotnet](https://github.com/temporalio/sdk-dotnet) laid out next to this repository: @@ -111,37 +106,11 @@ gcloud run worker-pools deploy "$WORKER_POOL" \ TEMPORAL_ADDRESS=..tmprl.cloud:7233,TEMPORAL_NAMESPACE=.,TEMPORAL_TASK_QUEUE=cloud-run-worker-sample ``` -Run this from `src/CloudRunWorker`. Cloud Run sets `CLOUD_RUN_WORKER_POOL` to -`$WORKER_POOL` and `CLOUD_RUN_REVISION` to the revision it creates, which the -plugin turns into the worker identity and Worker Deployment Version. Deploying -again creates a new revision, and therefore a new build id. - -## 2. Route the Worker Deployment Version +Run this from `src/Gcp/CloudRun/WorkerId`. Cloud Run sets `CLOUD_RUN_WORKER_POOL` +to `$WORKER_POOL` and `CLOUD_RUN_REVISION` to the revision it creates, which the +plugin turns into the worker identity `{instanceId}@{revision}`. -Once the worker pool is polling, point the deployment's current version at the -revision so pinned Workflows are routed to it. The deployment name is the worker -pool name and the build id is the Cloud Run revision, which -`gcloud run worker-pools describe` reports: - -```bash -export DEPLOYMENT_NAME="$WORKER_POOL" -export BUILD_ID="$(gcloud run worker-pools describe "$WORKER_POOL" \ - --region "$REGION" \ - --format 'value(status.latestReadyRevisionName)')" - -temporal worker deployment set-current-version \ - --deployment-name "$DEPLOYMENT_NAME" \ - --build-id "$BUILD_ID" \ - --yes -``` - -Verify the routing state: - -```bash -temporal worker deployment describe --name "$DEPLOYMENT_NAME" -``` - -## 3. Start a Workflow +## 2. Start a Workflow Start the greeting Workflow on the same Task Queue and wait for the result: @@ -155,16 +124,11 @@ temporal workflow execute \ A successful run returns `"Hello, Cloud Run!"`. -## 4. Clean up +## 3. Clean up -Reset Temporal routing and delete the worker pool: +Delete the worker pool: ```bash -temporal worker deployment set-current-version \ - --deployment-name "$DEPLOYMENT_NAME" \ - --unversioned \ - --yes - gcloud run worker-pools delete "$WORKER_POOL" --region "$REGION" ``` @@ -174,7 +138,7 @@ With the sibling `sdk-dotnet-2` checkout in place (see [Unreleased dependency](#unreleased-dependency)): ```bash -dotnet build src/CloudRunWorker +dotnet build src/Gcp/CloudRun/WorkerId ``` Running the worker outside Cloud Run fails at startup because the Cloud Run diff --git a/src/CloudRunWorker/SampleWorkflow.workflow.cs b/src/Gcp/CloudRun/WorkerId/SampleWorkflow.workflow.cs similarity index 63% rename from src/CloudRunWorker/SampleWorkflow.workflow.cs rename to src/Gcp/CloudRun/WorkerId/SampleWorkflow.workflow.cs index 4b6fdf8..ce42ac1 100644 --- a/src/CloudRunWorker/SampleWorkflow.workflow.cs +++ b/src/Gcp/CloudRun/WorkerId/SampleWorkflow.workflow.cs @@ -1,11 +1,10 @@ -namespace TemporalioSamples.CloudRunWorker; +namespace TemporalioSamples.Gcp.CloudRun.WorkerId; using Microsoft.Extensions.Logging; using Temporalio.Workflows; -// Worker versioning is turned on by the WorkerIdPlugin registered in Program.cs, which sets the -// deployment's default versioning behavior to Pinned. A workflow can still override that with -// [Workflow(VersioningBehavior = ...)]; this sample relies on the pinned default from the plugin. +// A minimal greeting workflow that runs one activity. The WorkerIdPlugin only sets the worker +// identity, so this workflow runs exactly as it would on any other worker. [Workflow] public class SampleWorkflow { diff --git a/src/CloudRunWorker/TemporalioSamples.CloudRunWorker.csproj b/src/Gcp/CloudRun/WorkerId/TemporalioSamples.Gcp.CloudRun.WorkerId.csproj similarity index 81% rename from src/CloudRunWorker/TemporalioSamples.CloudRunWorker.csproj rename to src/Gcp/CloudRun/WorkerId/TemporalioSamples.Gcp.CloudRun.WorkerId.csproj index df24435..b942dd1 100644 --- a/src/CloudRunWorker/TemporalioSamples.CloudRunWorker.csproj +++ b/src/Gcp/CloudRun/WorkerId/TemporalioSamples.Gcp.CloudRun.WorkerId.csproj @@ -10,7 +10,7 @@ This sample depends on Temporalio.Extensions.Gcp.CloudRun.WorkerId, which is not published to NuGet yet. While it is unreleased we reference the SDK from a sibling checkout of https://github.com/temporalio/sdk-dotnet (branch `cloud-run-worker-id`) laid out next to this - repo (../../../sdk-dotnet-2), and remove the Temporalio* package references that + repo (../../../../../sdk-dotnet-2), and remove the Temporalio* package references that Directory.Build.props adds by default so the local project references are used instead. Once Temporalio.Extensions.Gcp.CloudRun.WorkerId is released, DELETE both item groups below and add: @@ -25,8 +25,8 @@ - - + + From c6ac449de1c1406bce39778dba097f191d75bfe5 Mon Sep 17 00:00:00 2001 From: Lenny Chen Date: Wed, 9 Sep 2026 17:12:36 -0700 Subject: [PATCH 6/6] Add Snipsync marker for Cloud Run Worker Id --- src/Gcp/CloudRun/WorkerId/Program.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Gcp/CloudRun/WorkerId/Program.cs b/src/Gcp/CloudRun/WorkerId/Program.cs index 0630fbc..9aa8ec7 100644 --- a/src/Gcp/CloudRun/WorkerId/Program.cs +++ b/src/Gcp/CloudRun/WorkerId/Program.cs @@ -6,6 +6,7 @@ using TemporalioSamples.Gcp.CloudRun.WorkerId; // Cloud Run injects these via `--set-env-vars`; fall back to a local dev server for convenience. +// @@@SNIPSTART dotnet-cloud-run-worker-id var address = GetEnvironmentVariable("TEMPORAL_ADDRESS") ?? "localhost:7233"; var temporalNamespace = GetEnvironmentVariable("TEMPORAL_NAMESPACE") ?? "default"; var taskQueue = GetEnvironmentVariable("TEMPORAL_TASK_QUEUE") ?? "cloud-run-worker-sample"; @@ -31,6 +32,7 @@ }; var client = await TemporalClient.ConnectAsync(clientOptions); +// @@@SNIPEND // The plugin already applied this identity to the client above; read the metadata directly to log // the worker identity this process runs under.