Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
<PackageVersion Include="Temporalio.Extensions.Aws.Lambda" Version="1.19.0" />
<PackageVersion Include="Temporalio.Extensions.Aws.Lambda.OpenTelemetry" Version="1.19.0" />
<PackageVersion Include="Temporalio.Extensions.DiagnosticSource" Version="1.19.0" />
<PackageVersion Include="Temporalio.Extensions.Gcp.CloudRun.Id" Version="1.19.0" />
<PackageVersion Include="Temporalio.Extensions.Hosting" Version="1.19.0" />
<PackageVersion Include="Temporalio.Extensions.OpenTelemetry" Version="1.19.0" />
<PackageVersion Include="TemporalCommunity.Aspire.Hosting" Version="0.1.0" />
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ Prerequisites:
* [EagerWorkflowStart](src/EagerWorkflowStart) - Demonstrates usage of Eager Workflow Start to reduce latency for workflows that start with a local activity.
* [Encryption](src/Encryption) - End-to-end encryption with Temporal payload codecs.
* [EnvConfig](src/EnvConfig) - Load client configuration from TOML files with programmatic overrides
* [Gcp/CloudRun/Id](src/Gcp/CloudRun/Id) - Run a Temporal Worker on a Google Cloud Run worker pool, deriving the client identity from Cloud Run metadata.
* [LambdaWorker](src/LambdaWorker) - Run a Temporal Worker inside an AWS Lambda function.
* [Mutex](src/Mutex) - How to implement a mutex as a workflow. Demonstrates how to avoid race conditions or parallel mutually exclusive operations on the same resource.
* [NexusCancellation](src/NexusCancellation) - Demonstrates how to cancel a running Nexus operation from a caller workflow.
Expand Down
14 changes: 14 additions & 0 deletions TemporalioSamples.sln
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "NexusStandaloneActivity", "
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TemporalioSamples.NexusStandaloneActivity", "src\NexusStandaloneActivity\TemporalioSamples.NexusStandaloneActivity.csproj", "{4D8C9F9B-F8E3-4160-9286-32C3966A0125}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TemporalioSamples.Gcp.CloudRun.Id", "src\Gcp\CloudRun\Id\TemporalioSamples.Gcp.CloudRun.Id.csproj", "{995322ED-0CF3-40F5-B521-380A9591DACC}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -815,6 +817,18 @@ Global
{4D8C9F9B-F8E3-4160-9286-32C3966A0125}.Release|x64.Build.0 = Release|Any CPU
{4D8C9F9B-F8E3-4160-9286-32C3966A0125}.Release|x86.ActiveCfg = Release|Any CPU
{4D8C9F9B-F8E3-4160-9286-32C3966A0125}.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
Expand Down
17 changes: 17 additions & 0 deletions nuget.config
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Restores the unreleased Temporalio.Extensions.Gcp.CloudRun.Id from a committed local feed; remove once it ships on nuget.org. -->
<configuration>
<packageSources>
<clear />
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" />
<add key="local" value="./src/Gcp/CloudRun/Id/local-packages" />
</packageSources>
<packageSourceMapping>
<packageSource key="nuget.org">
<package pattern="*" />
</packageSource>
<packageSource key="local">
<package pattern="Temporalio.Extensions.Gcp.CloudRun.Id" />
</packageSource>
</packageSourceMapping>
</configuration>
14 changes: 14 additions & 0 deletions src/Gcp/CloudRun/Id/Activities.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
namespace TemporalioSamples.Gcp.CloudRun.Id;

using Microsoft.Extensions.Logging;
using Temporalio.Activities;

public static class Activities
{
[Activity]
public static string SayHello(string name)
{
ActivityExecutionContext.Current.Logger.LogInformation("SayHello activity: {Name}", name);
return $"Hello, {name}!";
}
}
14 changes: 14 additions & 0 deletions src/Gcp/CloudRun/Id/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# syntax=docker/dockerfile:1
# Build from the repo root: docker build -f src/Gcp/CloudRun/Id/Dockerfile -t <image> .
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
WORKDIR /src
COPY global.json Directory.Build.props Directory.Packages.props .editorconfig nuget.config ./
COPY src/Gcp/CloudRun/Id/ ./src/Gcp/CloudRun/Id/
RUN dotnet publish src/Gcp/CloudRun/Id/TemporalioSamples.Gcp.CloudRun.Id.csproj -c Release -o /app

FROM mcr.microsoft.com/dotnet/runtime:8.0
RUN useradd --create-home --uid 10001 worker
WORKDIR /app
COPY --from=build --chown=worker:worker /app ./
USER 10001
ENTRYPOINT ["dotnet", "TemporalioSamples.Gcp.CloudRun.Id.dll"]
69 changes: 69 additions & 0 deletions src/Gcp/CloudRun/Id/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
using System.Runtime.InteropServices;
using Microsoft.Extensions.Logging;
using Temporalio.Client;
using Temporalio.Extensions.Gcp.CloudRun.Id;
using Temporalio.Worker;
using TemporalioSamples.Gcp.CloudRun.Id;

// @@@SNIPSTART dotnet-cloud-run-id
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("CloudRunId");

// Derives the client identity "{instanceId}@{revision}" from Cloud Run metadata at connect time.
var clientOptions = new TemporalClientConnectOptions(address)
{
Namespace = temporalNamespace,
LoggerFactory = loggerFactory,
Plugins = new[] { new CloudRunIdPlugin() },
};

var client = await TemporalClient.ConnectAsync(clientOptions);
// @@@SNIPEND
var metadata = await GoogleCloudRunMetadata.FetchAsync();
logger.LogInformation("Cloud Run identity: {Identity}", metadata.Identity);

var workerOptions = new TemporalWorkerOptions(taskQueue).
AddWorkflow<SampleWorkflow>().
AddActivity(Activities.SayHello);

using var worker = new TemporalWorker(client, workerOptions);

// Cloud Run sends SIGTERM before stopping an instance; shut down gracefully on that and Ctrl+C.
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;
49 changes: 49 additions & 0 deletions src/Gcp/CloudRun/Id/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# Cloud Run Id

Run a Temporal Worker on a
[Google Cloud Run worker pool](https://cloud.google.com/run/docs/deploy-worker-pools) and derive its
identity from Cloud Run instance metadata using the `Temporalio.Extensions.Gcp.CloudRun.Id` package.

`Program.cs` registers a `CloudRunIdPlugin` on `TemporalClientConnectOptions.Plugins`. At connect
time the plugin sets the client `Identity` to `{instanceId}@{revision}` from the Cloud Run metadata
server and environment, and every Worker created from the client inherits it. A greeting workflow and
activity poll the task queue until the container stops. Worker pools run instances that take no HTTP
traffic, which suits a Temporal Worker; the same code also works on a Cloud Run service.

> The extension is not on nuget.org yet, so the sample restores it from the committed
> `local-packages/` feed (see `nuget.config`) until it ships.

## Prerequisites

- A Temporal server the worker pool can reach
- A Google Cloud project with billing and the Cloud Run and Artifact Registry APIs enabled
- [`gcloud`](https://cloud.google.com/sdk/docs/install), authenticated with the project set
- The [Temporal CLI](https://docs.temporal.io/cli) and .NET 8

## Deploy

Build the image from the repo root (the Dockerfile pulls in the shared props and the local feed),
push it, then deploy a worker pool. Worker pools may require the `beta` track.

```bash
export REGION=us-central1 WORKER_POOL=temporal-dotnet-worker
export WORKER_IMAGE=$REGION-docker.pkg.dev/$(gcloud config get-value project)/samples/cloud-run-id

docker build -f src/Gcp/CloudRun/Id/Dockerfile -t "$WORKER_IMAGE" . && docker push "$WORKER_IMAGE"

gcloud run worker-pools deploy "$WORKER_POOL" --image "$WORKER_IMAGE" --region "$REGION" \
--set-env-vars TEMPORAL_ADDRESS=<host:7233>,TEMPORAL_NAMESPACE=<namespace>,TEMPORAL_TASK_QUEUE=cloud-run-worker-sample
```

Cloud Run sets `CLOUD_RUN_WORKER_POOL` and `CLOUD_RUN_REVISION`, which the plugin turns into the
client identity. The sample connects in plaintext; for Temporal Cloud add API key / mTLS to
`Program.cs`.

## Run a workflow

```bash
temporal workflow execute --task-queue cloud-run-worker-sample --type SampleWorkflow --input '"Cloud Run"'
```

A successful run returns `"Hello, Cloud Run!"`. Delete the pool with
`gcloud run worker-pools delete "$WORKER_POOL" --region "$REGION"`.
19 changes: 19 additions & 0 deletions src/Gcp/CloudRun/Id/SampleWorkflow.workflow.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
namespace TemporalioSamples.Gcp.CloudRun.Id;

using Microsoft.Extensions.Logging;
using Temporalio.Workflows;

[Workflow]
public class SampleWorkflow
{
[WorkflowRun]
public async Task<string> RunAsync(string name)
{
Workflow.Logger.LogInformation("SampleWorkflow started: {Name}", name);
var result = await Workflow.ExecuteActivityAsync(
() => Activities.SayHello(name),
new() { StartToCloseTimeout = TimeSpan.FromSeconds(10) });
Workflow.Logger.LogInformation("SampleWorkflow completed: {Result}", result);
return result;
}
}
11 changes: 11 additions & 0 deletions src/Gcp/CloudRun/Id/TemporalioSamples.Gcp.CloudRun.Id.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Temporalio.Extensions.Gcp.CloudRun.Id" />
</ItemGroup>

</Project>
Binary file not shown.
Loading