create-svc 0.1.69 → 0.1.71
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/service-runtime/cloudrun/bootstrap.ts +19 -6
- package/src/service-runtime/cloudrun/cleanup.ts +3 -0
- package/src/service-runtime/cloudrun/cli.ts +53 -10
- package/src/service-runtime/cloudrun/config.ts +3 -0
- package/src/service-runtime/cloudrun/lib.ts +26 -0
- package/src/service-runtime/cloudrun/sdk-state.ts +21 -0
- package/src/service-runtime/cloudrun/sdk.test.ts +73 -2
- package/src/service-runtime/cloudrun/temporal-config.test.ts +66 -0
- package/src/service-runtime/cloudrun/temporal-config.ts +52 -2
- package/templates/shared/service.jsonc +4 -1
- package/templates/shared/service.yaml +1 -0
- package/templates/variants/go-chi/cmd/server/main.go +3 -0
- package/templates/variants/go-chi/cmd/worker/main.go +3 -0
- package/templates/variants/go-chi/internal/config/config.go +6 -0
- package/templates/variants/go-chi/internal/temporal/client.go +3 -6
- package/templates/variants/go-chi/internal/temporal/worker.go +52 -6
- package/templates/variants/go-connectrpc/cmd/server/main.go +3 -0
- package/templates/variants/go-connectrpc/cmd/worker/main.go +3 -0
- package/templates/variants/go-connectrpc/internal/config/config.go +6 -0
- package/templates/variants/go-connectrpc/internal/temporal/client.go +3 -6
- package/templates/variants/go-connectrpc/internal/temporal/worker.go +52 -6
package/package.json
CHANGED
|
@@ -79,18 +79,31 @@ export async function prepareGcpProject() {
|
|
|
79
79
|
|
|
80
80
|
function publishTemporalSecrets() {
|
|
81
81
|
const temporal = resolveTemporalRuntimeConfig();
|
|
82
|
-
|
|
83
|
-
|
|
82
|
+
const secrets = [
|
|
83
|
+
{ name: temporal.apiKeySecretName, value: temporal.apiKey },
|
|
84
|
+
{ name: temporal.tlsCaCertSecretName, value: temporal.tlsCaCert },
|
|
85
|
+
{ name: temporal.tlsCertSecretName, value: temporal.tlsCert },
|
|
86
|
+
{ name: temporal.tlsKeySecretName, value: temporal.tlsKey },
|
|
87
|
+
].filter((secret) => secret.name && secret.value);
|
|
88
|
+
|
|
89
|
+
if (secrets.length === 0) {
|
|
90
|
+
return "No Temporal credentials configured";
|
|
84
91
|
}
|
|
85
92
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
93
|
+
for (const secret of secrets) {
|
|
94
|
+
addSecretVersion(secret.name, secret.value);
|
|
95
|
+
ensureSecretAccessor(secret.name, `serviceAccount:${config.runtimeServiceAccount}`);
|
|
96
|
+
}
|
|
97
|
+
return secrets.map((secret) => secret.name).join(", ");
|
|
89
98
|
}
|
|
90
99
|
|
|
91
100
|
function shouldPublishTemporalSecrets() {
|
|
92
101
|
const temporal = resolveTemporalRuntimeConfig();
|
|
93
|
-
return Boolean(
|
|
102
|
+
return Boolean(
|
|
103
|
+
temporal.enabled &&
|
|
104
|
+
((temporal.apiKey && temporal.apiKeySecretName) ||
|
|
105
|
+
(temporal.tlsCaCert && temporal.tlsCaCertSecretName && temporal.tlsCert && temporal.tlsCertSecretName && temporal.tlsKey && temporal.tlsKeySecretName))
|
|
106
|
+
);
|
|
94
107
|
}
|
|
95
108
|
|
|
96
109
|
if (import.meta.main) {
|
|
@@ -41,6 +41,9 @@ function matchesSecretResource(name: string) {
|
|
|
41
41
|
return (
|
|
42
42
|
name === `${config.serviceName}-database-url` ||
|
|
43
43
|
name === config.temporal.apiKeySecretName ||
|
|
44
|
+
name === config.temporal.tlsCaCertSecretName ||
|
|
45
|
+
name === config.temporal.tlsCertSecretName ||
|
|
46
|
+
name === config.temporal.tlsKeySecretName ||
|
|
44
47
|
name.startsWith(`${config.serviceName}-pr-`) ||
|
|
45
48
|
name.startsWith(`${config.serviceName}-dev-`)
|
|
46
49
|
);
|
|
@@ -8,6 +8,7 @@ import { cleanup } from "./cleanup";
|
|
|
8
8
|
import { deploy } from "./deploy";
|
|
9
9
|
import { observabilityBootstrap } from "./observability";
|
|
10
10
|
import { config } from "./config";
|
|
11
|
+
import { formatSdkModeDetail, type SdkState } from "./sdk-state";
|
|
11
12
|
import {
|
|
12
13
|
accessSecretVersion,
|
|
13
14
|
assertProductionDomainAvailable,
|
|
@@ -295,11 +296,8 @@ async function runDoctor() {
|
|
|
295
296
|
});
|
|
296
297
|
await record(results, "SDK mode", "warn", async () => {
|
|
297
298
|
const text = await Bun.file(".service/sdk.json").text();
|
|
298
|
-
const state = JSON.parse(text) as
|
|
299
|
-
|
|
300
|
-
throw new Error("SDK mode must be local or remote");
|
|
301
|
-
}
|
|
302
|
-
return `${state.mode}: ${state.module || bufModule()}`;
|
|
299
|
+
const state = JSON.parse(text) as SdkState;
|
|
300
|
+
return formatSdkModeDetail(state, bufModule());
|
|
303
301
|
});
|
|
304
302
|
}
|
|
305
303
|
|
|
@@ -350,8 +348,9 @@ async function runSdk(args: string[]) {
|
|
|
350
348
|
if (subcommand === "publish") {
|
|
351
349
|
requireCommand("buf");
|
|
352
350
|
run("buf", ["push"]);
|
|
353
|
-
|
|
354
|
-
|
|
351
|
+
const published = resolvePublishedSdk();
|
|
352
|
+
await writeSdkMode("remote", published);
|
|
353
|
+
return `Schema pushed to Buf Schema Registry and recorded for consumers: ${published.commit}`;
|
|
355
354
|
}
|
|
356
355
|
|
|
357
356
|
if (subcommand === "build") {
|
|
@@ -371,8 +370,10 @@ async function runSdk(args: string[]) {
|
|
|
371
370
|
}
|
|
372
371
|
|
|
373
372
|
if (subcommand === "use-remote") {
|
|
374
|
-
|
|
375
|
-
|
|
373
|
+
requireCommand("buf");
|
|
374
|
+
const published = resolvePublishedSdk();
|
|
375
|
+
await writeSdkMode("remote", published);
|
|
376
|
+
return `Remote Buf SDK recorded for consumers: ${bufModule()}@${published.commit}`;
|
|
376
377
|
}
|
|
377
378
|
|
|
378
379
|
throw new Error("Usage: service sdk <build|publish|use-local|use-remote>");
|
|
@@ -385,7 +386,40 @@ async function assertLocalSdkArtifacts() {
|
|
|
385
386
|
}
|
|
386
387
|
}
|
|
387
388
|
|
|
388
|
-
|
|
389
|
+
type PublishedSdk = {
|
|
390
|
+
commit: string;
|
|
391
|
+
digest?: string;
|
|
392
|
+
createTime?: string;
|
|
393
|
+
};
|
|
394
|
+
|
|
395
|
+
function resolvePublishedSdk(): PublishedSdk {
|
|
396
|
+
const module = bufModule();
|
|
397
|
+
const result = run("buf", ["registry", "module", "commit", "list", module, "--format", "json", "--page-size", "1"]);
|
|
398
|
+
const parsed = JSON.parse(result.stdout) as {
|
|
399
|
+
commits?: Array<Record<string, unknown>>;
|
|
400
|
+
commit?: Record<string, unknown>;
|
|
401
|
+
};
|
|
402
|
+
const commit = parsed.commits?.[0] ?? parsed.commit;
|
|
403
|
+
if (!commit) {
|
|
404
|
+
throw new Error(`Could not resolve the published Buf commit for ${module}`);
|
|
405
|
+
}
|
|
406
|
+
const name = stringField(commit, "name") ?? stringField(commit, "commit") ?? stringField(commit, "id");
|
|
407
|
+
if (!name) {
|
|
408
|
+
throw new Error(`Buf commit response for ${module} did not include a commit identifier`);
|
|
409
|
+
}
|
|
410
|
+
return {
|
|
411
|
+
commit: name.includes(":") ? name.slice(name.lastIndexOf(":") + 1) : name,
|
|
412
|
+
digest: stringField(commit, "digest"),
|
|
413
|
+
createTime: stringField(commit, "create_time") ?? stringField(commit, "createTime"),
|
|
414
|
+
};
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
function stringField(source: Record<string, unknown>, key: string) {
|
|
418
|
+
const value = source[key];
|
|
419
|
+
return typeof value === "string" && value.length > 0 ? value : undefined;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
async function writeSdkMode(mode: "local" | "remote", published?: PublishedSdk) {
|
|
389
423
|
await mkdir(".service", { recursive: true });
|
|
390
424
|
const localPath = await resolveLocalSdkPath();
|
|
391
425
|
await Bun.write(
|
|
@@ -395,6 +429,15 @@ async function writeSdkMode(mode: "local" | "remote") {
|
|
|
395
429
|
mode,
|
|
396
430
|
module: bufModule(),
|
|
397
431
|
localPath,
|
|
432
|
+
...(published
|
|
433
|
+
? {
|
|
434
|
+
remote: {
|
|
435
|
+
commit: published.commit,
|
|
436
|
+
digest: published.digest,
|
|
437
|
+
createTime: published.createTime,
|
|
438
|
+
},
|
|
439
|
+
}
|
|
440
|
+
: {}),
|
|
398
441
|
updatedAt: new Date().toISOString(),
|
|
399
442
|
},
|
|
400
443
|
null,
|
|
@@ -40,6 +40,9 @@ export const config = {
|
|
|
40
40
|
namespace: serviceConfig.temporal.namespace,
|
|
41
41
|
taskQueue: serviceConfig.temporal.task_queue,
|
|
42
42
|
apiKeySecretName: serviceConfig.temporal.api_key_secret_name,
|
|
43
|
+
tlsCaCertSecretName: serviceConfig.temporal.tls_ca_cert_secret_name,
|
|
44
|
+
tlsCertSecretName: serviceConfig.temporal.tls_cert_secret_name,
|
|
45
|
+
tlsKeySecretName: serviceConfig.temporal.tls_key_secret_name,
|
|
43
46
|
vaultMount: vault.mount || "secret",
|
|
44
47
|
vaultPath: vault.temporal_path || "prod/providers/temporal",
|
|
45
48
|
},
|
|
@@ -537,6 +537,7 @@ export async function renderManifest(image: string, target: DeploymentTarget, pr
|
|
|
537
537
|
" key: latest",
|
|
538
538
|
].join("\n")
|
|
539
539
|
: "",
|
|
540
|
+
TEMPORAL_MTLS_ENV: renderTemporalMtlsEnv(temporal),
|
|
540
541
|
AUTH_ISSUER: config.auth.issuer,
|
|
541
542
|
AUTH_AUDIENCE: config.auth.audience,
|
|
542
543
|
AUTH_JWKS_URL: config.auth.jwksUrl,
|
|
@@ -560,9 +561,34 @@ function readTemporalProviderFields(mount: string, path: string) {
|
|
|
560
561
|
address: readVaultField(mount, path, ["TEMPORAL_ADDRESS", "address"]),
|
|
561
562
|
namespace: readVaultField(mount, path, ["TEMPORAL_NAMESPACE", "namespace"]),
|
|
562
563
|
apiKey: readVaultField(mount, path, ["TEMPORAL_API_KEY", "api_key"]),
|
|
564
|
+
tlsCaCert: readVaultField(mount, path, ["TEMPORAL_TLS_CA_CERT", "tls_ca_cert", "ca_cert"]),
|
|
565
|
+
tlsCert: readVaultField(mount, path, ["TEMPORAL_TLS_CERT", "tls_cert", "client_cert"]),
|
|
566
|
+
tlsKey: readVaultField(mount, path, ["TEMPORAL_TLS_KEY", "tls_key", "client_key"]),
|
|
563
567
|
};
|
|
564
568
|
}
|
|
565
569
|
|
|
570
|
+
function renderTemporalMtlsEnv(temporal: ReturnType<typeof resolveTemporalRuntimeConfig>) {
|
|
571
|
+
const entries = [
|
|
572
|
+
["TEMPORAL_TLS_CA_CERT", temporal.tlsCaCertSecretName],
|
|
573
|
+
["TEMPORAL_TLS_CERT", temporal.tlsCertSecretName],
|
|
574
|
+
["TEMPORAL_TLS_KEY", temporal.tlsKeySecretName],
|
|
575
|
+
].filter((entry): entry is [string, string] => Boolean(entry[1]));
|
|
576
|
+
|
|
577
|
+
if (entries.length === 0) {
|
|
578
|
+
return "";
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
return entries
|
|
582
|
+
.flatMap(([envName, secretName]) => [
|
|
583
|
+
` - name: ${envName}`,
|
|
584
|
+
" valueFrom:",
|
|
585
|
+
" secretKeyRef:",
|
|
586
|
+
` name: ${secretName}`,
|
|
587
|
+
" key: latest",
|
|
588
|
+
])
|
|
589
|
+
.join("\n");
|
|
590
|
+
}
|
|
591
|
+
|
|
566
592
|
function readVaultField(mount: string, path: string, fields: string[]) {
|
|
567
593
|
const vault = Bun.which("vault");
|
|
568
594
|
if (!vault || !path) {
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export type SdkState = {
|
|
2
|
+
mode?: string;
|
|
3
|
+
module?: string;
|
|
4
|
+
remote?: {
|
|
5
|
+
commit?: string;
|
|
6
|
+
digest?: string;
|
|
7
|
+
};
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export function formatSdkModeDetail(state: SdkState, fallbackModule: string) {
|
|
11
|
+
if (state.mode !== "local" && state.mode !== "remote") {
|
|
12
|
+
throw new Error("SDK mode must be local or remote");
|
|
13
|
+
}
|
|
14
|
+
const module = state.module || fallbackModule;
|
|
15
|
+
if (state.mode === "remote") {
|
|
16
|
+
const version = state.remote?.commit ? `@${state.remote.commit}` : "without recorded commit";
|
|
17
|
+
const digest = state.remote?.digest ? ` (${state.remote.digest})` : "";
|
|
18
|
+
return `${state.mode}: ${module}${version}${digest}`;
|
|
19
|
+
}
|
|
20
|
+
return `${state.mode}: ${module}`;
|
|
21
|
+
}
|
|
@@ -3,6 +3,7 @@ import { chmod, mkdir, mkdtemp, readFile, writeFile } from "node:fs/promises";
|
|
|
3
3
|
import { tmpdir } from "node:os";
|
|
4
4
|
import { join } from "node:path";
|
|
5
5
|
import { scaffoldProject, type ScaffoldConfig } from "../../scaffold";
|
|
6
|
+
import { formatSdkModeDetail } from "./sdk-state";
|
|
6
7
|
|
|
7
8
|
function baseConfig(directory: string): ScaffoldConfig {
|
|
8
9
|
return {
|
|
@@ -42,7 +43,15 @@ test("service sdk publish pushes the named Buf module and selects remote SDK mod
|
|
|
42
43
|
await mkdir(fakeBin);
|
|
43
44
|
await writeFile(
|
|
44
45
|
join(fakeBin, "buf"),
|
|
45
|
-
[
|
|
46
|
+
[
|
|
47
|
+
"#!/bin/sh",
|
|
48
|
+
`echo "$@" >> "${bufLog}"`,
|
|
49
|
+
'if [ "$1 $2 $3 $4" = "registry module commit list" ]; then',
|
|
50
|
+
' printf \'{"commits":[{"name":"buf.build/anmho/sdk-proof:commit-123","digest":"b5:abc123","create_time":"2026-05-25T12:00:00Z"}]}\'',
|
|
51
|
+
"fi",
|
|
52
|
+
"exit 0",
|
|
53
|
+
"",
|
|
54
|
+
].join("\n")
|
|
46
55
|
);
|
|
47
56
|
await chmod(join(fakeBin, "buf"), 0o755);
|
|
48
57
|
|
|
@@ -55,17 +64,79 @@ test("service sdk publish pushes the named Buf module and selects remote SDK mod
|
|
|
55
64
|
|
|
56
65
|
expect(result.success, [result.stdout.toString(), result.stderr.toString()].join("\n")).toBeTrue();
|
|
57
66
|
expect(result.stdout.toString()).toContain("recorded for consumers");
|
|
58
|
-
expect((await readFile(bufLog, "utf8")).trim()).toBe(
|
|
67
|
+
expect((await readFile(bufLog, "utf8")).trim()).toBe(
|
|
68
|
+
["push", "registry module commit list buf.build/anmho/sdk-proof --format json --page-size 1"].join("\n")
|
|
69
|
+
);
|
|
59
70
|
const sdkState = JSON.parse(await Bun.file(join(generatedRoot, ".service", "sdk.json")).text());
|
|
60
71
|
expect(sdkState).toMatchObject({
|
|
61
72
|
mode: "remote",
|
|
62
73
|
module: "buf.build/anmho/sdk-proof",
|
|
63
74
|
localPath: "./gen/waitlist/v1",
|
|
75
|
+
remote: {
|
|
76
|
+
commit: "commit-123",
|
|
77
|
+
digest: "b5:abc123",
|
|
78
|
+
createTime: "2026-05-25T12:00:00Z",
|
|
79
|
+
},
|
|
64
80
|
});
|
|
65
81
|
const bufConfig = await Bun.file(join(generatedRoot, "buf.yaml")).text();
|
|
66
82
|
expect(bufConfig).toContain("name: buf.build/anmho/sdk-proof");
|
|
67
83
|
});
|
|
68
84
|
|
|
85
|
+
test("formatSdkModeDetail reports the recorded remote SDK commit", () => {
|
|
86
|
+
expect(
|
|
87
|
+
formatSdkModeDetail(
|
|
88
|
+
{
|
|
89
|
+
mode: "remote",
|
|
90
|
+
module: "buf.build/anmho/sdk-proof",
|
|
91
|
+
remote: {
|
|
92
|
+
commit: "commit-123",
|
|
93
|
+
digest: "b5:abc123",
|
|
94
|
+
},
|
|
95
|
+
},
|
|
96
|
+
"buf.build/anmho/fallback"
|
|
97
|
+
)
|
|
98
|
+
).toBe("remote: buf.build/anmho/sdk-proof@commit-123 (b5:abc123)");
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
test("service sdk use-remote records the current Buf commit", async () => {
|
|
102
|
+
const root = await mkdtemp(join(tmpdir(), "create-svc-sdk-"));
|
|
103
|
+
const generatedRoot = join(root, "sdk-proof");
|
|
104
|
+
const fakeBin = join(root, "bin");
|
|
105
|
+
|
|
106
|
+
await scaffoldProject(baseConfig(generatedRoot));
|
|
107
|
+
await mkdir(join(generatedRoot, "node_modules"));
|
|
108
|
+
await mkdir(fakeBin);
|
|
109
|
+
await writeFile(
|
|
110
|
+
join(fakeBin, "buf"),
|
|
111
|
+
[
|
|
112
|
+
"#!/bin/sh",
|
|
113
|
+
'if [ "$1 $2 $3 $4" = "registry module commit list" ]; then',
|
|
114
|
+
' printf \'{"commits":[{"name":"buf.build/anmho/sdk-proof:commit-456","digest":"b5:def456"}]}\'',
|
|
115
|
+
"fi",
|
|
116
|
+
"exit 0",
|
|
117
|
+
"",
|
|
118
|
+
].join("\n")
|
|
119
|
+
);
|
|
120
|
+
await chmod(join(fakeBin, "buf"), 0o755);
|
|
121
|
+
|
|
122
|
+
const result = Bun.spawnSync(["bun", join(import.meta.dir, "..", "..", "..", "index.ts"), "sdk", "use-remote"], {
|
|
123
|
+
cwd: generatedRoot,
|
|
124
|
+
env: { ...process.env, PATH: `${fakeBin}:${process.env.PATH ?? ""}` },
|
|
125
|
+
stdout: "pipe",
|
|
126
|
+
stderr: "pipe",
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
expect(result.success, [result.stdout.toString(), result.stderr.toString()].join("\n")).toBeTrue();
|
|
130
|
+
const sdkState = JSON.parse(await Bun.file(join(generatedRoot, ".service", "sdk.json")).text());
|
|
131
|
+
expect(sdkState).toMatchObject({
|
|
132
|
+
mode: "remote",
|
|
133
|
+
remote: {
|
|
134
|
+
commit: "commit-456",
|
|
135
|
+
digest: "b5:def456",
|
|
136
|
+
},
|
|
137
|
+
});
|
|
138
|
+
});
|
|
139
|
+
|
|
69
140
|
test("service sdk publish leaves local SDK mode when Buf push fails", async () => {
|
|
70
141
|
const root = await mkdtemp(join(tmpdir(), "create-svc-sdk-"));
|
|
71
142
|
const generatedRoot = join(root, "sdk-proof");
|
|
@@ -7,6 +7,9 @@ const baseConfig = {
|
|
|
7
7
|
namespace: "default",
|
|
8
8
|
taskQueue: "orders",
|
|
9
9
|
apiKeySecretName: "orders-temporal-api-key",
|
|
10
|
+
tlsCaCertSecretName: "orders-temporal-ca-cert",
|
|
11
|
+
tlsCertSecretName: "orders-temporal-client-cert",
|
|
12
|
+
tlsKeySecretName: "orders-temporal-client-key",
|
|
10
13
|
vaultMount: "secret",
|
|
11
14
|
vaultPath: "prod/providers/temporal",
|
|
12
15
|
};
|
|
@@ -25,6 +28,59 @@ test("resolveTemporalRuntimeConfigValues reads production Temporal config from V
|
|
|
25
28
|
taskQueue: "orders",
|
|
26
29
|
apiKeySecretName: "orders-temporal-api-key",
|
|
27
30
|
apiKey: "secret-key",
|
|
31
|
+
tlsCaCertSecretName: "",
|
|
32
|
+
tlsCertSecretName: "",
|
|
33
|
+
tlsKeySecretName: "",
|
|
34
|
+
tlsCaCert: "",
|
|
35
|
+
tlsCert: "",
|
|
36
|
+
tlsKey: "",
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
test("resolveTemporalRuntimeConfigValues reads self-hosted mTLS config from Vault fields", () => {
|
|
41
|
+
const resolved = resolveTemporalRuntimeConfigValues(baseConfig, {}, () => ({
|
|
42
|
+
address: "temporal-grpc.anmho.com:7233",
|
|
43
|
+
namespace: "default",
|
|
44
|
+
tlsCaCert: "ca-pem",
|
|
45
|
+
tlsCert: "cert-pem",
|
|
46
|
+
tlsKey: "key-pem",
|
|
47
|
+
}));
|
|
48
|
+
|
|
49
|
+
expect(resolved).toMatchObject({
|
|
50
|
+
enabled: true,
|
|
51
|
+
address: "temporal-grpc.anmho.com:7233",
|
|
52
|
+
namespace: "default",
|
|
53
|
+
taskQueue: "orders",
|
|
54
|
+
apiKey: "",
|
|
55
|
+
apiKeySecretName: "",
|
|
56
|
+
tlsCaCertSecretName: "orders-temporal-ca-cert",
|
|
57
|
+
tlsCertSecretName: "orders-temporal-client-cert",
|
|
58
|
+
tlsKeySecretName: "orders-temporal-client-key",
|
|
59
|
+
tlsCaCert: "ca-pem",
|
|
60
|
+
tlsCert: "cert-pem",
|
|
61
|
+
tlsKey: "key-pem",
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
test("resolveTemporalRuntimeConfigValues renders configured mTLS secret names without raw credentials", () => {
|
|
66
|
+
const resolved = resolveTemporalRuntimeConfigValues(
|
|
67
|
+
{ ...baseConfig, address: "temporal-grpc.anmho.com:7233" },
|
|
68
|
+
{},
|
|
69
|
+
() => ({
|
|
70
|
+
namespace: "default",
|
|
71
|
+
})
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
expect(resolved).toMatchObject({
|
|
75
|
+
enabled: true,
|
|
76
|
+
address: "temporal-grpc.anmho.com:7233",
|
|
77
|
+
namespace: "default",
|
|
78
|
+
tlsCaCertSecretName: "orders-temporal-ca-cert",
|
|
79
|
+
tlsCertSecretName: "orders-temporal-client-cert",
|
|
80
|
+
tlsKeySecretName: "orders-temporal-client-key",
|
|
81
|
+
tlsCaCert: "",
|
|
82
|
+
tlsCert: "",
|
|
83
|
+
tlsKey: "",
|
|
28
84
|
});
|
|
29
85
|
});
|
|
30
86
|
|
|
@@ -52,6 +108,15 @@ test("resolveTemporalRuntimeConfigValues prefers explicit environment overrides"
|
|
|
52
108
|
expect(resolved.apiKeySecretName).toBe("env-secret-name");
|
|
53
109
|
});
|
|
54
110
|
|
|
111
|
+
test("resolveTemporalRuntimeConfigValues rejects partial mTLS config", () => {
|
|
112
|
+
expect(() =>
|
|
113
|
+
resolveTemporalRuntimeConfigValues({ ...baseConfig, address: "temporal-grpc.anmho.com:7233" }, {}, () => ({
|
|
114
|
+
namespace: "default",
|
|
115
|
+
tlsCaCert: "ca-pem",
|
|
116
|
+
}))
|
|
117
|
+
).toThrow("Temporal mTLS is partially configured");
|
|
118
|
+
});
|
|
119
|
+
|
|
55
120
|
test("resolveTemporalRuntimeConfigValues fails clearly when enabled Temporal resolves to localhost", () => {
|
|
56
121
|
expect(() => resolveTemporalRuntimeConfigValues(baseConfig, {}, () => ({}))).toThrow(
|
|
57
122
|
"Temporal is enabled for this Cloud Run service, but the resolved Temporal address is local"
|
|
@@ -63,4 +128,5 @@ test("resolveTemporalRuntimeConfigValues allows explicit Temporal disable", () =
|
|
|
63
128
|
|
|
64
129
|
expect(resolved.enabled).toBeFalse();
|
|
65
130
|
expect(resolved.apiKeySecretName).toBe("");
|
|
131
|
+
expect(resolved.tlsCaCertSecretName).toBe("");
|
|
66
132
|
});
|
|
@@ -4,6 +4,9 @@ type TemporalConfigInput = {
|
|
|
4
4
|
namespace: string;
|
|
5
5
|
taskQueue: string;
|
|
6
6
|
apiKeySecretName: string;
|
|
7
|
+
tlsCaCertSecretName?: string;
|
|
8
|
+
tlsCertSecretName?: string;
|
|
9
|
+
tlsKeySecretName?: string;
|
|
7
10
|
vaultMount: string;
|
|
8
11
|
vaultPath: string;
|
|
9
12
|
};
|
|
@@ -12,6 +15,9 @@ type TemporalProviderFields = {
|
|
|
12
15
|
address?: string;
|
|
13
16
|
namespace?: string;
|
|
14
17
|
apiKey?: string;
|
|
18
|
+
tlsCaCert?: string;
|
|
19
|
+
tlsCert?: string;
|
|
20
|
+
tlsKey?: string;
|
|
15
21
|
};
|
|
16
22
|
|
|
17
23
|
export type TemporalRuntimeConfig = {
|
|
@@ -21,6 +27,12 @@ export type TemporalRuntimeConfig = {
|
|
|
21
27
|
taskQueue: string;
|
|
22
28
|
apiKeySecretName: string;
|
|
23
29
|
apiKey: string;
|
|
30
|
+
tlsCaCertSecretName: string;
|
|
31
|
+
tlsCertSecretName: string;
|
|
32
|
+
tlsKeySecretName: string;
|
|
33
|
+
tlsCaCert: string;
|
|
34
|
+
tlsCert: string;
|
|
35
|
+
tlsKey: string;
|
|
24
36
|
};
|
|
25
37
|
|
|
26
38
|
export function resolveTemporalRuntimeConfigValues(
|
|
@@ -40,6 +52,12 @@ export function resolveTemporalRuntimeConfigValues(
|
|
|
40
52
|
taskQueue,
|
|
41
53
|
apiKeySecretName: "",
|
|
42
54
|
apiKey: "",
|
|
55
|
+
tlsCaCertSecretName: "",
|
|
56
|
+
tlsCertSecretName: "",
|
|
57
|
+
tlsKeySecretName: "",
|
|
58
|
+
tlsCaCert: "",
|
|
59
|
+
tlsCert: "",
|
|
60
|
+
tlsKey: "",
|
|
43
61
|
};
|
|
44
62
|
}
|
|
45
63
|
|
|
@@ -48,13 +66,29 @@ export function resolveTemporalRuntimeConfigValues(
|
|
|
48
66
|
const namespace = env.TEMPORAL_NAMESPACE?.trim() || provider.namespace || config.namespace;
|
|
49
67
|
const apiKey = env.TEMPORAL_API_KEY?.trim() || provider.apiKey || "";
|
|
50
68
|
const apiKeySecretName = env.TEMPORAL_API_KEY_SECRET?.trim() || (apiKey ? config.apiKeySecretName : "");
|
|
69
|
+
const tlsCaCert = env.TEMPORAL_TLS_CA_CERT?.trim() || provider.tlsCaCert || "";
|
|
70
|
+
const tlsCert = env.TEMPORAL_TLS_CERT?.trim() || provider.tlsCert || "";
|
|
71
|
+
const tlsKey = env.TEMPORAL_TLS_KEY?.trim() || provider.tlsKey || "";
|
|
72
|
+
const tlsCaCertSecretName =
|
|
73
|
+
env.TEMPORAL_TLS_CA_CERT_SECRET?.trim() || (tlsCaCert ? config.tlsCaCertSecretName || `${config.taskQueue}-temporal-ca-cert` : "");
|
|
74
|
+
const tlsCertSecretName =
|
|
75
|
+
env.TEMPORAL_TLS_CERT_SECRET?.trim() || (tlsCert ? config.tlsCertSecretName || `${config.taskQueue}-temporal-client-cert` : "");
|
|
76
|
+
const tlsKeySecretName =
|
|
77
|
+
env.TEMPORAL_TLS_KEY_SECRET?.trim() || (tlsKey ? config.tlsKeySecretName || `${config.taskQueue}-temporal-client-key` : "");
|
|
78
|
+
const configuredTLSSecretNames = Boolean(config.tlsCaCertSecretName && config.tlsCertSecretName && config.tlsKeySecretName);
|
|
79
|
+
const shouldRenderTLSSecretNames = Boolean(tlsCaCert || (!apiKey && configuredTLSSecretNames));
|
|
80
|
+
const resolvedTLSSecretNames = {
|
|
81
|
+
ca: shouldRenderTLSSecretNames ? tlsCaCertSecretName || config.tlsCaCertSecretName || "" : "",
|
|
82
|
+
cert: shouldRenderTLSSecretNames ? tlsCertSecretName || config.tlsCertSecretName || "" : "",
|
|
83
|
+
key: shouldRenderTLSSecretNames ? tlsKeySecretName || config.tlsKeySecretName || "" : "",
|
|
84
|
+
};
|
|
51
85
|
|
|
52
86
|
if (isLocalTemporalAddress(address)) {
|
|
53
87
|
throw new Error(
|
|
54
88
|
[
|
|
55
89
|
"Temporal is enabled for this Cloud Run service, but the resolved Temporal address is local.",
|
|
56
|
-
`Set TEMPORAL_ADDRESS, TEMPORAL_NAMESPACE, and TEMPORAL_API_KEY, or populate Vault at ${config.vaultMount}/${config.vaultPath}`,
|
|
57
|
-
"with TEMPORAL_ADDRESS, TEMPORAL_NAMESPACE, and TEMPORAL_API_KEY before running service create or service deploy.",
|
|
90
|
+
`Set TEMPORAL_ADDRESS, TEMPORAL_NAMESPACE, and TEMPORAL_API_KEY or TEMPORAL_TLS_* credentials, or populate Vault at ${config.vaultMount}/${config.vaultPath}`,
|
|
91
|
+
"with TEMPORAL_ADDRESS, TEMPORAL_NAMESPACE, and either TEMPORAL_API_KEY or TEMPORAL_TLS_CA_CERT/TEMPORAL_TLS_CERT/TEMPORAL_TLS_KEY before running service create or service deploy.",
|
|
58
92
|
"Set TEMPORAL_ENABLED=false only for services that should deploy without Temporal.",
|
|
59
93
|
].join(" ")
|
|
60
94
|
);
|
|
@@ -63,6 +97,16 @@ export function resolveTemporalRuntimeConfigValues(
|
|
|
63
97
|
if (!namespace) {
|
|
64
98
|
throw new Error(`Temporal is enabled but TEMPORAL_NAMESPACE is missing; set it in env or Vault at ${config.vaultMount}/${config.vaultPath}`);
|
|
65
99
|
}
|
|
100
|
+
if (!apiKey && (Boolean(tlsCaCert) || Boolean(tlsCert) || Boolean(tlsKey)) && (!tlsCaCert || !tlsCert || !tlsKey)) {
|
|
101
|
+
throw new Error(
|
|
102
|
+
`Temporal mTLS is partially configured; set TEMPORAL_TLS_CA_CERT, TEMPORAL_TLS_CERT, and TEMPORAL_TLS_KEY together in env or Vault at ${config.vaultMount}/${config.vaultPath}`
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
if (!apiKey && !apiKeySecretName && !tlsCaCert && !configuredTLSSecretNames) {
|
|
106
|
+
throw new Error(
|
|
107
|
+
`Temporal is enabled but no credentials were found; set TEMPORAL_API_KEY or TEMPORAL_TLS_CA_CERT/TEMPORAL_TLS_CERT/TEMPORAL_TLS_KEY in env or Vault at ${config.vaultMount}/${config.vaultPath}`
|
|
108
|
+
);
|
|
109
|
+
}
|
|
66
110
|
|
|
67
111
|
return {
|
|
68
112
|
enabled,
|
|
@@ -71,6 +115,12 @@ export function resolveTemporalRuntimeConfigValues(
|
|
|
71
115
|
taskQueue,
|
|
72
116
|
apiKeySecretName,
|
|
73
117
|
apiKey,
|
|
118
|
+
tlsCaCertSecretName: resolvedTLSSecretNames.ca,
|
|
119
|
+
tlsCertSecretName: resolvedTLSSecretNames.cert,
|
|
120
|
+
tlsKeySecretName: resolvedTLSSecretNames.key,
|
|
121
|
+
tlsCaCert,
|
|
122
|
+
tlsCert,
|
|
123
|
+
tlsKey,
|
|
74
124
|
};
|
|
75
125
|
}
|
|
76
126
|
|
|
@@ -57,7 +57,10 @@
|
|
|
57
57
|
"address": "localhost:7233",
|
|
58
58
|
"namespace": "default",
|
|
59
59
|
"task_queue": "{{SERVICE_ID}}",
|
|
60
|
-
"api_key_secret_name": "{{SERVICE_ID}}-temporal-api-key"
|
|
60
|
+
"api_key_secret_name": "{{SERVICE_ID}}-temporal-api-key",
|
|
61
|
+
"tls_ca_cert_secret_name": "{{SERVICE_ID}}-temporal-ca-cert",
|
|
62
|
+
"tls_cert_secret_name": "{{SERVICE_ID}}-temporal-client-cert",
|
|
63
|
+
"tls_key_secret_name": "{{SERVICE_ID}}-temporal-client-key"
|
|
61
64
|
},
|
|
62
65
|
|
|
63
66
|
"providers": {
|
|
@@ -36,6 +36,9 @@ func main() {
|
|
|
36
36
|
Namespace: cfg.TemporalNamespace,
|
|
37
37
|
TaskQueue: cfg.TemporalTaskQueue,
|
|
38
38
|
APIKey: cfg.TemporalAPIKey,
|
|
39
|
+
TLSCACert: cfg.TemporalTLSCACert,
|
|
40
|
+
TLSCert: cfg.TemporalTLSCert,
|
|
41
|
+
TLSKey: cfg.TemporalTLSKey,
|
|
39
42
|
}
|
|
40
43
|
dispatcher, err := temporalapp.NewTriggerDispatcher(temporalConfig)
|
|
41
44
|
if err != nil {
|
|
@@ -23,6 +23,9 @@ func main() {
|
|
|
23
23
|
Namespace: cfg.TemporalNamespace,
|
|
24
24
|
TaskQueue: cfg.TemporalTaskQueue,
|
|
25
25
|
APIKey: cfg.TemporalAPIKey,
|
|
26
|
+
TLSCACert: cfg.TemporalTLSCACert,
|
|
27
|
+
TLSCert: cfg.TemporalTLSCert,
|
|
28
|
+
TLSKey: cfg.TemporalTLSKey,
|
|
26
29
|
})
|
|
27
30
|
if err != nil {
|
|
28
31
|
log.Fatal(err)
|
|
@@ -14,6 +14,9 @@ type Config struct {
|
|
|
14
14
|
TemporalNamespace string
|
|
15
15
|
TemporalTaskQueue string
|
|
16
16
|
TemporalAPIKey string
|
|
17
|
+
TemporalTLSCACert string
|
|
18
|
+
TemporalTLSCert string
|
|
19
|
+
TemporalTLSKey string
|
|
17
20
|
AuthEnabled bool
|
|
18
21
|
AuthIssuer string
|
|
19
22
|
AuthAudience string
|
|
@@ -29,6 +32,9 @@ func Load() (Config, error) {
|
|
|
29
32
|
TemporalNamespace: envOrRuntime("TEMPORAL_NAMESPACE", "default"),
|
|
30
33
|
TemporalTaskQueue: envOr("TEMPORAL_TASK_QUEUE", "{{SERVICE_NAME}}"),
|
|
31
34
|
TemporalAPIKey: strings.TrimSpace(os.Getenv("TEMPORAL_API_KEY")),
|
|
35
|
+
TemporalTLSCACert: strings.TrimSpace(os.Getenv("TEMPORAL_TLS_CA_CERT")),
|
|
36
|
+
TemporalTLSCert: strings.TrimSpace(os.Getenv("TEMPORAL_TLS_CERT")),
|
|
37
|
+
TemporalTLSKey: strings.TrimSpace(os.Getenv("TEMPORAL_TLS_KEY")),
|
|
32
38
|
AuthEnabled: envBool("AUTH_ENABLED"),
|
|
33
39
|
AuthIssuer: envOr("AUTH_ISSUER", "{{AUTH_ISSUER}}"),
|
|
34
40
|
AuthAudience: envOr("AUTH_AUDIENCE", "{{AUTH_AUDIENCE}}"),
|
|
@@ -14,12 +14,9 @@ type TriggerDispatcher struct {
|
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
func NewTriggerDispatcher(cfg WorkerConfig) (*TriggerDispatcher, error) {
|
|
17
|
-
options :=
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
}
|
|
21
|
-
if cfg.APIKey != "" {
|
|
22
|
-
options.Credentials = client.NewAPIKeyStaticCredentials(cfg.APIKey)
|
|
17
|
+
options, err := temporalClientOptions(cfg)
|
|
18
|
+
if err != nil {
|
|
19
|
+
return nil, err
|
|
23
20
|
}
|
|
24
21
|
|
|
25
22
|
temporalClient, err := client.Dial(options)
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
package temporalapp
|
|
2
2
|
|
|
3
3
|
import (
|
|
4
|
+
"crypto/tls"
|
|
5
|
+
"crypto/x509"
|
|
6
|
+
"fmt"
|
|
7
|
+
"net"
|
|
4
8
|
"go.temporal.io/sdk/client"
|
|
5
9
|
"go.temporal.io/sdk/worker"
|
|
6
10
|
)
|
|
@@ -10,15 +14,15 @@ type WorkerConfig struct {
|
|
|
10
14
|
Namespace string
|
|
11
15
|
TaskQueue string
|
|
12
16
|
APIKey string
|
|
17
|
+
TLSCACert string
|
|
18
|
+
TLSCert string
|
|
19
|
+
TLSKey string
|
|
13
20
|
}
|
|
14
21
|
|
|
15
22
|
func StartWorker(cfg WorkerConfig) (func(), error) {
|
|
16
|
-
options :=
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
}
|
|
20
|
-
if cfg.APIKey != "" {
|
|
21
|
-
options.Credentials = client.NewAPIKeyStaticCredentials(cfg.APIKey)
|
|
23
|
+
options, err := temporalClientOptions(cfg)
|
|
24
|
+
if err != nil {
|
|
25
|
+
return nil, err
|
|
22
26
|
}
|
|
23
27
|
|
|
24
28
|
temporalClient, err := client.Dial(options)
|
|
@@ -40,3 +44,45 @@ func StartWorker(cfg WorkerConfig) (func(), error) {
|
|
|
40
44
|
temporalClient.Close()
|
|
41
45
|
}, nil
|
|
42
46
|
}
|
|
47
|
+
|
|
48
|
+
func temporalClientOptions(cfg WorkerConfig) (client.Options, error) {
|
|
49
|
+
options := client.Options{
|
|
50
|
+
HostPort: cfg.Address,
|
|
51
|
+
Namespace: cfg.Namespace,
|
|
52
|
+
}
|
|
53
|
+
if cfg.APIKey != "" {
|
|
54
|
+
options.Credentials = client.NewAPIKeyStaticCredentials(cfg.APIKey)
|
|
55
|
+
}
|
|
56
|
+
if cfg.TLSCACert != "" || cfg.TLSCert != "" || cfg.TLSKey != "" {
|
|
57
|
+
tlsConfig, err := temporalTLSConfig(cfg)
|
|
58
|
+
if err != nil {
|
|
59
|
+
return client.Options{}, err
|
|
60
|
+
}
|
|
61
|
+
options.ConnectionOptions.TLS = tlsConfig
|
|
62
|
+
}
|
|
63
|
+
return options, nil
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
func temporalTLSConfig(cfg WorkerConfig) (*tls.Config, error) {
|
|
67
|
+
if cfg.TLSCACert == "" || cfg.TLSCert == "" || cfg.TLSKey == "" {
|
|
68
|
+
return nil, fmt.Errorf("TEMPORAL_TLS_CA_CERT, TEMPORAL_TLS_CERT, and TEMPORAL_TLS_KEY must be set together")
|
|
69
|
+
}
|
|
70
|
+
certificate, err := tls.X509KeyPair([]byte(cfg.TLSCert), []byte(cfg.TLSKey))
|
|
71
|
+
if err != nil {
|
|
72
|
+
return nil, fmt.Errorf("parse Temporal client certificate: %w", err)
|
|
73
|
+
}
|
|
74
|
+
roots := x509.NewCertPool()
|
|
75
|
+
if !roots.AppendCertsFromPEM([]byte(cfg.TLSCACert)) {
|
|
76
|
+
return nil, fmt.Errorf("parse Temporal CA certificate")
|
|
77
|
+
}
|
|
78
|
+
serverName, _, err := net.SplitHostPort(cfg.Address)
|
|
79
|
+
if err != nil {
|
|
80
|
+
serverName = cfg.Address
|
|
81
|
+
}
|
|
82
|
+
return &tls.Config{
|
|
83
|
+
Certificates: []tls.Certificate{certificate},
|
|
84
|
+
RootCAs: roots,
|
|
85
|
+
ServerName: serverName,
|
|
86
|
+
MinVersion: tls.VersionTLS12,
|
|
87
|
+
}, nil
|
|
88
|
+
}
|
|
@@ -40,6 +40,9 @@ func main() {
|
|
|
40
40
|
Namespace: cfg.TemporalNamespace,
|
|
41
41
|
TaskQueue: cfg.TemporalTaskQueue,
|
|
42
42
|
APIKey: cfg.TemporalAPIKey,
|
|
43
|
+
TLSCACert: cfg.TemporalTLSCACert,
|
|
44
|
+
TLSCert: cfg.TemporalTLSCert,
|
|
45
|
+
TLSKey: cfg.TemporalTLSKey,
|
|
43
46
|
}
|
|
44
47
|
dispatcher, err := temporalapp.NewTriggerDispatcher(temporalConfig)
|
|
45
48
|
if err != nil {
|
|
@@ -23,6 +23,9 @@ func main() {
|
|
|
23
23
|
Namespace: cfg.TemporalNamespace,
|
|
24
24
|
TaskQueue: cfg.TemporalTaskQueue,
|
|
25
25
|
APIKey: cfg.TemporalAPIKey,
|
|
26
|
+
TLSCACert: cfg.TemporalTLSCACert,
|
|
27
|
+
TLSCert: cfg.TemporalTLSCert,
|
|
28
|
+
TLSKey: cfg.TemporalTLSKey,
|
|
26
29
|
})
|
|
27
30
|
if err != nil {
|
|
28
31
|
log.Fatal(err)
|
|
@@ -14,6 +14,9 @@ type Config struct {
|
|
|
14
14
|
TemporalNamespace string
|
|
15
15
|
TemporalTaskQueue string
|
|
16
16
|
TemporalAPIKey string
|
|
17
|
+
TemporalTLSCACert string
|
|
18
|
+
TemporalTLSCert string
|
|
19
|
+
TemporalTLSKey string
|
|
17
20
|
AuthEnabled bool
|
|
18
21
|
AuthIssuer string
|
|
19
22
|
AuthAudience string
|
|
@@ -29,6 +32,9 @@ func Load() (Config, error) {
|
|
|
29
32
|
TemporalNamespace: envOrRuntime("TEMPORAL_NAMESPACE", "default"),
|
|
30
33
|
TemporalTaskQueue: envOr("TEMPORAL_TASK_QUEUE", "{{SERVICE_NAME}}"),
|
|
31
34
|
TemporalAPIKey: strings.TrimSpace(os.Getenv("TEMPORAL_API_KEY")),
|
|
35
|
+
TemporalTLSCACert: strings.TrimSpace(os.Getenv("TEMPORAL_TLS_CA_CERT")),
|
|
36
|
+
TemporalTLSCert: strings.TrimSpace(os.Getenv("TEMPORAL_TLS_CERT")),
|
|
37
|
+
TemporalTLSKey: strings.TrimSpace(os.Getenv("TEMPORAL_TLS_KEY")),
|
|
32
38
|
AuthEnabled: envBool("AUTH_ENABLED"),
|
|
33
39
|
AuthIssuer: envOr("AUTH_ISSUER", "{{AUTH_ISSUER}}"),
|
|
34
40
|
AuthAudience: envOr("AUTH_AUDIENCE", "{{AUTH_AUDIENCE}}"),
|
|
@@ -14,12 +14,9 @@ type TriggerDispatcher struct {
|
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
func NewTriggerDispatcher(cfg WorkerConfig) (*TriggerDispatcher, error) {
|
|
17
|
-
options :=
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
}
|
|
21
|
-
if cfg.APIKey != "" {
|
|
22
|
-
options.Credentials = client.NewAPIKeyStaticCredentials(cfg.APIKey)
|
|
17
|
+
options, err := temporalClientOptions(cfg)
|
|
18
|
+
if err != nil {
|
|
19
|
+
return nil, err
|
|
23
20
|
}
|
|
24
21
|
|
|
25
22
|
temporalClient, err := client.Dial(options)
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
package temporalapp
|
|
2
2
|
|
|
3
3
|
import (
|
|
4
|
+
"crypto/tls"
|
|
5
|
+
"crypto/x509"
|
|
6
|
+
"fmt"
|
|
7
|
+
"net"
|
|
4
8
|
"go.temporal.io/sdk/client"
|
|
5
9
|
"go.temporal.io/sdk/worker"
|
|
6
10
|
)
|
|
@@ -10,15 +14,15 @@ type WorkerConfig struct {
|
|
|
10
14
|
Namespace string
|
|
11
15
|
TaskQueue string
|
|
12
16
|
APIKey string
|
|
17
|
+
TLSCACert string
|
|
18
|
+
TLSCert string
|
|
19
|
+
TLSKey string
|
|
13
20
|
}
|
|
14
21
|
|
|
15
22
|
func StartWorker(cfg WorkerConfig) (func(), error) {
|
|
16
|
-
options :=
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
}
|
|
20
|
-
if cfg.APIKey != "" {
|
|
21
|
-
options.Credentials = client.NewAPIKeyStaticCredentials(cfg.APIKey)
|
|
23
|
+
options, err := temporalClientOptions(cfg)
|
|
24
|
+
if err != nil {
|
|
25
|
+
return nil, err
|
|
22
26
|
}
|
|
23
27
|
|
|
24
28
|
temporalClient, err := client.Dial(options)
|
|
@@ -40,3 +44,45 @@ func StartWorker(cfg WorkerConfig) (func(), error) {
|
|
|
40
44
|
temporalClient.Close()
|
|
41
45
|
}, nil
|
|
42
46
|
}
|
|
47
|
+
|
|
48
|
+
func temporalClientOptions(cfg WorkerConfig) (client.Options, error) {
|
|
49
|
+
options := client.Options{
|
|
50
|
+
HostPort: cfg.Address,
|
|
51
|
+
Namespace: cfg.Namespace,
|
|
52
|
+
}
|
|
53
|
+
if cfg.APIKey != "" {
|
|
54
|
+
options.Credentials = client.NewAPIKeyStaticCredentials(cfg.APIKey)
|
|
55
|
+
}
|
|
56
|
+
if cfg.TLSCACert != "" || cfg.TLSCert != "" || cfg.TLSKey != "" {
|
|
57
|
+
tlsConfig, err := temporalTLSConfig(cfg)
|
|
58
|
+
if err != nil {
|
|
59
|
+
return client.Options{}, err
|
|
60
|
+
}
|
|
61
|
+
options.ConnectionOptions.TLS = tlsConfig
|
|
62
|
+
}
|
|
63
|
+
return options, nil
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
func temporalTLSConfig(cfg WorkerConfig) (*tls.Config, error) {
|
|
67
|
+
if cfg.TLSCACert == "" || cfg.TLSCert == "" || cfg.TLSKey == "" {
|
|
68
|
+
return nil, fmt.Errorf("TEMPORAL_TLS_CA_CERT, TEMPORAL_TLS_CERT, and TEMPORAL_TLS_KEY must be set together")
|
|
69
|
+
}
|
|
70
|
+
certificate, err := tls.X509KeyPair([]byte(cfg.TLSCert), []byte(cfg.TLSKey))
|
|
71
|
+
if err != nil {
|
|
72
|
+
return nil, fmt.Errorf("parse Temporal client certificate: %w", err)
|
|
73
|
+
}
|
|
74
|
+
roots := x509.NewCertPool()
|
|
75
|
+
if !roots.AppendCertsFromPEM([]byte(cfg.TLSCACert)) {
|
|
76
|
+
return nil, fmt.Errorf("parse Temporal CA certificate")
|
|
77
|
+
}
|
|
78
|
+
serverName, _, err := net.SplitHostPort(cfg.Address)
|
|
79
|
+
if err != nil {
|
|
80
|
+
serverName = cfg.Address
|
|
81
|
+
}
|
|
82
|
+
return &tls.Config{
|
|
83
|
+
Certificates: []tls.Certificate{certificate},
|
|
84
|
+
RootCAs: roots,
|
|
85
|
+
ServerName: serverName,
|
|
86
|
+
MinVersion: tls.VersionTLS12,
|
|
87
|
+
}, nil
|
|
88
|
+
}
|