diff --git a/.ghjk/lock.json b/.ghjk/lock.json index d7e652d31d..03cd54ba4a 100644 --- a/.ghjk/lock.json +++ b/.ghjk/lock.json @@ -1978,7 +1978,7 @@ ] } }, - "defaultEnv": "ci", + "defaultEnv": "dev", "envsNamed": { "main": "bciqkx2kodz5dx5pv3hjcc4vpirphyetattk4oks5pucb7dsqrgwzqwa", "_wasm": "bciqk7aif25pqtsyncbnbdapte4lixsodypfxvmkepsuey74vgr4zb5q", diff --git a/CHANGELOG.md b/CHANGELOG.md index 3cbc599c5f..d1d29691dd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3591,6 +3591,3 @@ _N/A_ - [ ] End-user documentation is updated to reflect the change - - - diff --git a/src/typegate/src/config/types.ts b/src/typegate/src/config/types.ts index 5584bb6e7f..af2b863280 100644 --- a/src/typegate/src/config/types.ts +++ b/src/typegate/src/config/types.ts @@ -1,16 +1,41 @@ // Copyright Metatype OÜ, licensed under the Elastic License 2.0. // SPDX-License-Identifier: Elastic-2.0 -import { z } from "zod"; +import { z, RefinementCtx } from "zod"; import { decodeBase64 } from "@std/encoding/base64"; import type { RedisConnectOptions } from "redis"; import type { S3ClientConfig } from "aws-sdk/client-s3"; const zBooleanString = z.preprocess( (a: unknown) => z.coerce.string().parse(a) === "true", - z.boolean(), + z.boolean() ); +const addMissingEnvVarIssue = (envVar: string, ctx: RefinementCtx) => { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: `Error: Env var ${envVar} is not configured.`, + }); + + return z.NEVER; +}; + +const refineEnvVar = (envVar: string) => z.string().optional().transform((s: string | undefined, ctx) => { + if (s === undefined) { + return addMissingEnvVarIssue(envVar, ctx); + } + + return s; +}); + +const refineURLEnvVar = (envVar: string) => z.string().optional().transform((s: string | undefined, ctx) => { + if (s === undefined) { + return addMissingEnvVarIssue(envVar, ctx); + } + + return new URL(s); +}); + export const globalConfigSchema = z.object({ debug: zBooleanString, // To be set to false when running from source. @@ -37,21 +62,14 @@ export const typegateConfigBaseSchema = z.object({ .optional() .transform((s: string | undefined, ctx) => { if (s === undefined) { - ctx.addIssue({ - code: z.ZodIssueCode.custom, - message: - "Error: Env var TG_SECRET is not configured. Exiting from the application.", - }); - - return z.NEVER; + return addMissingEnvVarIssue("TG_SECRET", ctx); } const bytes = decodeBase64(s); if (bytes.length != 64) { ctx.addIssue({ code: z.ZodIssueCode.custom, - message: - `Base64 contains ${bytes.length} instead of 64 bytes (use openssl rand -base64 64 | tr -d '\n')`, + message: `Base64 contains ${bytes.length} instead of 64 bytes (use openssl rand -base64 64 | tr -d '\n')`, }); } return bytes; @@ -59,22 +77,7 @@ export const typegateConfigBaseSchema = z.object({ timer_max_timeout_ms: z.coerce.number().positive().max(60000), timer_destroy_resources: z.boolean(), timer_policy_eval_retries: z.number().nonnegative().max(5), - tg_admin_password: z - .string() - .optional() - .transform((s: string | undefined, ctx) => { - if (s === undefined) { - ctx.addIssue({ - code: z.ZodIssueCode.custom, - message: - "Error: Env var TG_ADMIN_PASSWORD is not configured. Exiting from the application.", - }); - - return z.NEVER; - } - - return s; - }), + tg_admin_password: refineEnvVar("TG_ADMIN_PASSWORD"), tmp_dir: z.string(), jwt_max_duration_sec: z.coerce.number().positive(), jwt_refresh_duration_sec: z.coerce.number().positive(), @@ -88,12 +91,12 @@ export type TypegateConfigBase = z.infer; // These config entries are only accessible on a Typegate instance. // They are not read from a global variable to enable test isolation and configurability. export const syncConfigSchema = z.object({ - redis_url: z.string().transform((s) => new URL(s)), - s3_host: z.string().transform((s) => new URL(s)), - s3_region: z.string(), - s3_bucket: z.string(), - s3_access_key: z.string(), - s3_secret_key: z.string(), + redis_url: refineURLEnvVar("SYNC_REDIS_URL"), + s3_host: refineURLEnvVar("SYNC_S3_HOST"), + s3_region: refineEnvVar("SYNC_S3_REGION"), + s3_bucket: refineEnvVar("SYNC_S3_BUCKET"), + s3_access_key: refineEnvVar("SYNC_S3_ACCESS_KEY"), + s3_secret_key: refineEnvVar("SYNC_S3_SECRET_KEY"), s3_path_style: zBooleanString.default(false), }); export type SyncConfig = z.infer; diff --git a/tests/sync/sync_config_test.ts b/tests/sync/sync_config_test.ts index 7f067af540..81ab66a516 100644 --- a/tests/sync/sync_config_test.ts +++ b/tests/sync/sync_config_test.ts @@ -72,11 +72,9 @@ Deno.test("test sync config", async (t) => { "s3_access_key", "s3_secret_key", ].map((k) => ({ - code: "invalid_type", - expected: "string", - message: "Required", + code: "custom", + message: `Error: Env var SYNC_${k.toUpperCase()} is not configured.`, path: [k], - received: "undefined", })), ); @@ -90,11 +88,9 @@ Deno.test("test sync config", async (t) => { Deno.env.set("SYNC_S3_BUCKET", "bucket"); assertInvalidSyncConfig([ { - code: "invalid_type", - expected: "string", - message: "Required", + code: "custom", + message: "Error: Env var SYNC_S3_SECRET_KEY is not configured.", path: ["s3_secret_key"], - received: "undefined", }, ]); });