diff --git a/apps/supervisor/src/env.ts b/apps/supervisor/src/env.ts index cd9bf5bead..2921e5e35b 100644 --- a/apps/supervisor/src/env.ts +++ b/apps/supervisor/src/env.ts @@ -1,7 +1,7 @@ import { randomUUID } from "crypto"; import { env as stdEnv } from "std-env"; import { z } from "zod"; -import { AdditionalEnvVars, BoolEnv } from "./envUtil.js"; +import { AdditionalEnvVars, BoolEnv, CoercedBoolean } from "./envUtil.js"; const Env = z.object({ // This will come from `spec.nodeName` in k8s @@ -45,7 +45,7 @@ const Env = z.object({ // Used by the workload manager, e.g docker/k8s DOCKER_NETWORK: z.string().default("host"), OTEL_EXPORTER_OTLP_ENDPOINT: z.string().url(), - ENFORCE_MACHINE_PRESETS: z.coerce.boolean().default(false), + ENFORCE_MACHINE_PRESETS: CoercedBoolean.default(false), KUBERNETES_IMAGE_PULL_SECRETS: z.string().optional(), // csv // Used by the resource monitor diff --git a/apps/supervisor/src/envUtil.ts b/apps/supervisor/src/envUtil.ts index 41dd5ca22a..623af865b6 100644 --- a/apps/supervisor/src/envUtil.ts +++ b/apps/supervisor/src/envUtil.ts @@ -37,3 +37,12 @@ export const AdditionalEnvVars = z.preprocess((val) => { return undefined; } }, z.record(z.string(), z.string()).optional()); + +/** + * Zod's `z.coerce.boolean()` doesn't work as _expected_ with "true" and "false" strings. + * as it coerces both to `true`. This type is a workaround for that. + */ +export const CoercedBoolean = z.union([ + z.boolean(), + z.enum(["true", "false"]).transform((v) => v === "true"), +]); diff --git a/apps/webapp/app/components/runs/v3/RunFilters.tsx b/apps/webapp/app/components/runs/v3/RunFilters.tsx index 393acb7616..8c6fa9c0b4 100644 --- a/apps/webapp/app/components/runs/v3/RunFilters.tsx +++ b/apps/webapp/app/components/runs/v3/RunFilters.tsx @@ -53,6 +53,7 @@ import { TaskRunStatusCombo, } from "./TaskRunStatus"; import { TaskTriggerSourceIcon } from "./TaskTriggerSource"; +import { CoercedBoolean } from "~/utils/zod"; export const TaskAttemptStatus = z.enum(allTaskRunStatuses); @@ -83,7 +84,7 @@ export const TaskRunListSearchFilters = z.object({ period: z.preprocess((value) => (value === "all" ? undefined : value), z.string().optional()), from: z.coerce.number().optional(), to: z.coerce.number().optional(), - rootOnly: z.coerce.boolean().optional(), + rootOnly: CoercedBoolean.optional(), batchId: z.string().optional(), runId: z.string().optional(), scheduleId: z.string().optional(), diff --git a/apps/webapp/app/env.server.ts b/apps/webapp/app/env.server.ts index beceac9a5f..b869274501 100644 --- a/apps/webapp/app/env.server.ts +++ b/apps/webapp/app/env.server.ts @@ -2,6 +2,7 @@ import { z } from "zod"; import { SecretStoreOptionsSchema } from "./services/secrets/secretStoreOptionsSchema.server"; import { isValidDatabaseUrl } from "./utils/db"; import { isValidRegex } from "./utils/regex"; +import { CoercedBoolean } from "./utils/zod"; const EnvironmentSchema = z.object({ NODE_ENV: z.union([z.literal("development"), z.literal("production"), z.literal("test")]), @@ -50,7 +51,7 @@ const EnvironmentSchema = z.object({ RESEND_API_KEY: z.string().optional(), SMTP_HOST: z.string().optional(), SMTP_PORT: z.coerce.number().optional(), - SMTP_SECURE: z.coerce.boolean().optional(), + SMTP_SECURE: CoercedBoolean.optional(), SMTP_USER: z.string().optional(), SMTP_PASSWORD: z.string().optional(), @@ -338,7 +339,7 @@ const EnvironmentSchema = z.object({ ALERT_RESEND_API_KEY: z.string().optional(), ALERT_SMTP_HOST: z.string().optional(), ALERT_SMTP_PORT: z.coerce.number().optional(), - ALERT_SMTP_SECURE: z.coerce.boolean().optional(), + ALERT_SMTP_SECURE: CoercedBoolean.optional(), ALERT_SMTP_USER: z.string().optional(), ALERT_SMTP_PASSWORD: z.string().optional(), ALERT_RATE_LIMITER_EMISSION_INTERVAL: z.coerce.number().int().default(2_500), @@ -378,7 +379,7 @@ const EnvironmentSchema = z.object({ MAX_SEQUENTIAL_INDEX_FAILURE_COUNT: z.coerce.number().default(96), LOOPS_API_KEY: z.string().optional(), - MARQS_DISABLE_REBALANCING: z.coerce.boolean().default(false), + MARQS_DISABLE_REBALANCING: CoercedBoolean.default(false), MARQS_VISIBILITY_TIMEOUT_MS: z.coerce .number() .int() @@ -452,7 +453,7 @@ const EnvironmentSchema = z.object({ RUN_ENGINE_TIMEOUT_PENDING_CANCEL: z.coerce.number().int().default(60_000), RUN_ENGINE_TIMEOUT_EXECUTING: z.coerce.number().int().default(60_000), RUN_ENGINE_TIMEOUT_EXECUTING_WITH_WAITPOINTS: z.coerce.number().int().default(60_000), - RUN_ENGINE_DEBUG_WORKER_NOTIFICATIONS: z.coerce.boolean().default(false), + RUN_ENGINE_DEBUG_WORKER_NOTIFICATIONS: CoercedBoolean.default(false), RUN_ENGINE_PARENT_QUEUE_LIMIT: z.coerce.number().int().default(1000), RUN_ENGINE_CONCURRENCY_LIMIT_BIAS: z.coerce.number().default(0.75), RUN_ENGINE_AVAILABLE_CAPACITY_BIAS: z.coerce.number().default(0.3), diff --git a/apps/webapp/app/utils/zod.ts b/apps/webapp/app/utils/zod.ts index 950b2ddc25..c932b9d6d6 100644 --- a/apps/webapp/app/utils/zod.ts +++ b/apps/webapp/app/utils/zod.ts @@ -20,3 +20,12 @@ export const CoercedDate = z.preprocess((arg) => { return arg; }, z.date().optional()); + +/** + * Zod's `z.coerce.boolean()` doesn't work as _expected_ with "true" and "false" strings. + * as it coerces both to `true`. This type is a workaround for that. + */ +export const CoercedBoolean = z.union([ + z.boolean(), + z.enum(["true", "false"]).transform((v) => v === "true"), +]);