feat: update service route
This commit is contained in:
parent
3c60018ca2
commit
6027de29e0
|
@ -51,6 +51,7 @@
|
|||
"dockerode": "^4.0.2",
|
||||
"dotenv": "^16.3.1",
|
||||
"drizzle-orm": "^0.29.3",
|
||||
"drizzle-zod": "^0.5.1",
|
||||
"extensionless": "^1.9.6",
|
||||
"framer-motion": "^10.17.6",
|
||||
"ipaddr.js": "^2.1.0",
|
||||
|
|
|
@ -107,6 +107,9 @@ dependencies:
|
|||
drizzle-orm:
|
||||
specifier: ^0.29.3
|
||||
version: 0.29.3(@types/better-sqlite3@7.6.8)(@types/react@18.2.46)(better-sqlite3@9.2.2)(react@18.2.0)
|
||||
drizzle-zod:
|
||||
specifier: ^0.5.1
|
||||
version: 0.5.1(drizzle-orm@0.29.3)(zod@3.22.4)
|
||||
extensionless:
|
||||
specifier: ^1.9.6
|
||||
version: 1.9.6
|
||||
|
@ -4287,6 +4290,16 @@ packages:
|
|||
react: 18.2.0
|
||||
dev: false
|
||||
|
||||
/drizzle-zod@0.5.1(drizzle-orm@0.29.3)(zod@3.22.4):
|
||||
resolution: {integrity: sha512-C/8bvzUH/zSnVfwdSibOgFjLhtDtbKYmkbPbUCq46QZyZCH6kODIMSOgZ8R7rVjoI+tCj3k06MRJMDqsIeoS4A==}
|
||||
peerDependencies:
|
||||
drizzle-orm: '>=0.23.13'
|
||||
zod: '*'
|
||||
dependencies:
|
||||
drizzle-orm: 0.29.3(@types/better-sqlite3@7.6.8)(@types/react@18.2.46)(better-sqlite3@9.2.2)(react@18.2.0)
|
||||
zod: 3.22.4
|
||||
dev: false
|
||||
|
||||
/eastasianwidth@0.2.0:
|
||||
resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==}
|
||||
|
||||
|
|
|
@ -1,20 +1,15 @@
|
|||
import assert from "assert";
|
||||
import { randomBytes } from "crypto";
|
||||
import { eq } from "drizzle-orm";
|
||||
import { createInsertSchema } from "drizzle-zod";
|
||||
import { z } from "zod";
|
||||
import { env } from "~/env";
|
||||
import { projectMiddleware } from "~/server/api/middleware/project";
|
||||
import { serviceMiddleware } from "~/server/api/middleware/service";
|
||||
import { authenticatedProcedure, createTRPCRouter } from "~/server/api/trpc";
|
||||
import { service } from "~/server/db/schema";
|
||||
import {
|
||||
DOCKER_DEPLOY_MODE_MAP,
|
||||
DockerDeployMode,
|
||||
DockerRestartCondition,
|
||||
ServiceBuildMethod,
|
||||
ServiceSource,
|
||||
} from "~/server/db/types";
|
||||
import { zDockerName } from "~/server/utils/zod";
|
||||
import { DOCKER_DEPLOY_MODE_MAP, ServiceSource } from "~/server/db/types";
|
||||
import { zDockerDuration, zDockerImage, zDockerName } from "~/server/utils/zod";
|
||||
import { getServiceContainers } from "./containers";
|
||||
|
||||
export const serviceRouter = createTRPCRouter({
|
||||
|
@ -61,39 +56,44 @@ export const serviceRouter = createTRPCRouter({
|
|||
},
|
||||
})
|
||||
.input(
|
||||
// sometimes i forget how powerful zod is
|
||||
z
|
||||
.object({
|
||||
projectId: z.string(),
|
||||
serviceId: z.string(),
|
||||
})
|
||||
.merge(
|
||||
z
|
||||
.object({
|
||||
source: z.nativeEnum(ServiceSource),
|
||||
environment: z.string(),
|
||||
dockerImage: z.string(),
|
||||
dockerRegistryUsername: z.string(),
|
||||
dockerRegistryPassword: z.string(),
|
||||
// TODO: restrict to valid github url
|
||||
githubUrl: z.string().url(),
|
||||
githubBranch: z.string(),
|
||||
gitUrl: z.string(),
|
||||
gitBranch: z.string(),
|
||||
buildMethod: z.nativeEnum(ServiceBuildMethod),
|
||||
buildPath: z.string(),
|
||||
command: z.string(),
|
||||
entrypoint: z.string(),
|
||||
replicas: z.number(),
|
||||
maxReplicasPerNode: z.number(),
|
||||
deployMode: z.nativeEnum(DockerDeployMode),
|
||||
zeroDowntime: z.boolean(),
|
||||
max_cpu: z.number(),
|
||||
max_memory: z.string(),
|
||||
max_pids: z.number(),
|
||||
restart: z.nativeEnum(DockerRestartCondition),
|
||||
createInsertSchema(service, {
|
||||
dockerImage: zDockerImage,
|
||||
gitUrl: (schema) =>
|
||||
schema.gitUrl.regex(
|
||||
// https://www.debuggex.com/r/fFggA8Uc4YYKjl34 from https://stackoverflow.com/a/22312124
|
||||
// /(?P<host>(git@|https:\/\/)([\w\.@]+)(\/|:))(?P<owner>[\w,\-,\_]+)\/(?P<repo>[\w,\-,\_]+)(.git){0,1}((\/){0,1})/,
|
||||
/(git@|https:\/\/)([\w\.@]+)(\/|:)([\w,\-,\_]+)\/([\w,\-,\_]+)(.git){0,1}(\/{0,1})/,
|
||||
{
|
||||
message: "Must be a valid git url. (Regex failed)",
|
||||
},
|
||||
),
|
||||
zeroDowntime: z.boolean().transform((val) => (val ? 1 : 0)),
|
||||
restartDelay: zDockerDuration,
|
||||
healthcheckEnabled: z.boolean().transform((val) => (val ? 1 : 0)),
|
||||
healthcheckInterval: zDockerDuration,
|
||||
healthcheckTimeout: zDockerDuration,
|
||||
healthcheckStartPeriod: zDockerDuration,
|
||||
loggingMaxSize: (schema) =>
|
||||
schema.loggingMaxSize.regex(/^\d+[kmg]$/, {
|
||||
message: "Must be an integer plus a modifier (k, m, or g).",
|
||||
}),
|
||||
loggingMaxFiles: (schema) => schema.loggingMaxFiles.positive(),
|
||||
})
|
||||
.omit({
|
||||
id: true,
|
||||
projectId: true,
|
||||
name: true,
|
||||
})
|
||||
.partial(),
|
||||
),
|
||||
)
|
||||
.strict(),
|
||||
)
|
||||
.use(projectMiddleware)
|
||||
.use(serviceMiddleware)
|
||||
|
@ -101,12 +101,14 @@ export const serviceRouter = createTRPCRouter({
|
|||
await ctx.db
|
||||
.update(service)
|
||||
.set({
|
||||
name: input.name,
|
||||
zeroDowntime: input.zeroDowntime ? 1 : 0,
|
||||
deployMode: input.deployMode,
|
||||
...input,
|
||||
projectId: undefined,
|
||||
id: undefined,
|
||||
})
|
||||
.where(eq(service.id, ctx.service.id))
|
||||
.execute();
|
||||
|
||||
return true;
|
||||
}),
|
||||
|
||||
create: authenticatedProcedure
|
||||
|
|
|
@ -10,7 +10,7 @@ import {
|
|||
} from "drizzle-orm/sqlite-core";
|
||||
import {
|
||||
DockerDeployMode,
|
||||
type DockerRestartCondition,
|
||||
DockerRestartCondition,
|
||||
type DockerVolumeType,
|
||||
type ServiceBuildMethod,
|
||||
type ServicePortType,
|
||||
|
@ -147,7 +147,9 @@ export const service = sqliteTable(
|
|||
dockerRegistryPassword: text("docker_registry_password"),
|
||||
|
||||
// for github source
|
||||
githubUrl: text("github_url"),
|
||||
// https://github.com/{username}/{repo}
|
||||
githubUsername: text("github_username"),
|
||||
githubRepository: text("github_repository"),
|
||||
githubBranch: text("github_branch"),
|
||||
|
||||
// for git source
|
||||
|
@ -170,26 +172,34 @@ export const service = sqliteTable(
|
|||
zeroDowntime: integer("zero_downtime").default(0).notNull(),
|
||||
|
||||
// deployment usage limits
|
||||
max_cpu: real("max_cpu"),
|
||||
max_memory: text("max_memory"),
|
||||
max_pids: integer("max_pids"),
|
||||
max_cpu: real("max_cpu").default(0).notNull(),
|
||||
max_memory: text("max_memory").default("0").notNull(),
|
||||
max_pids: integer("max_pids").default(0).notNull(),
|
||||
|
||||
// restart policy
|
||||
restart: integer("restart").$type<DockerRestartCondition>(),
|
||||
restartDelay: text("restart_delay"),
|
||||
restartMaxAttempts: integer("restart_max_attempts"),
|
||||
restart: integer("restart")
|
||||
.$type<DockerRestartCondition>()
|
||||
.default(DockerRestartCondition.OnFailure)
|
||||
.notNull(),
|
||||
|
||||
// format: https://docs.docker.com/compose/compose-file/compose-file-v3/#specifying-durations
|
||||
restartDelay: text("restart_delay").default("5s"),
|
||||
restartMaxAttempts: integer("restart_max_attempts").default(-1).notNull(),
|
||||
|
||||
// healthcheck
|
||||
healtcheckEnabled: integer("healthcheck_enabled").default(0).notNull(),
|
||||
healthcheckEnabled: integer("healthcheck_enabled").default(0).notNull(),
|
||||
healthcheckCommand: text("healthcheck_command"),
|
||||
healthcheckInterval: text("healthcheck_interval"),
|
||||
healthcheckTimeout: text("healthcheck_timeout"),
|
||||
healthcheckRetries: integer("healthcheck_retries"),
|
||||
healthcheckStartPeriod: text("healthcheck_start_period"),
|
||||
healthcheckInterval: text("healthcheck_interval").default("30s").notNull(),
|
||||
healthcheckTimeout: text("healthcheck_timeout").default("30s").notNull(),
|
||||
healthcheckRetries: integer("healthcheck_retries").default(3).notNull(),
|
||||
healthcheckStartPeriod: text("healthcheck_start_period")
|
||||
.default("0s")
|
||||
.notNull(),
|
||||
|
||||
// logging
|
||||
loggingMaxSize: text("logging_max_size"),
|
||||
loggingMaxFiles: integer("logging_max_files"),
|
||||
// https://docs.docker.com/config/containers/logging/json-file/#options
|
||||
loggingMaxSize: text("logging_max_size").default("-1").notNull(),
|
||||
loggingMaxFiles: integer("logging_max_files").default(1).notNull(),
|
||||
|
||||
createdAt: integer("created_at").default(now).notNull(),
|
||||
},
|
||||
|
|
|
@ -3,3 +3,18 @@ import z from "zod";
|
|||
export const zDockerName = z.string().regex(/^[a-z0-9-]+$/, {
|
||||
message: "Must be lowercase alphanumeric with dashes.",
|
||||
});
|
||||
|
||||
export const zDockerImage = z
|
||||
.string()
|
||||
.regex(
|
||||
/^(?:(?=[^:\/]{1,253})(?!-)[a-zA-Z0-9-]{1,63}(?<!-)(?:\.(?!-)[a-zA-Z0-9-]{1,63}(?<!-))*(?::[0-9]{1,5})?\/)?((?![._-])(?:[a-z0-9._-]*)(?<![._-])(?:\/(?![._-])[a-z0-9._-]*(?<![._-]))*)(?::(?![.-])[a-zA-Z0-9_.-]{1,128})?$/,
|
||||
{
|
||||
message: "Must be a valid Docker image name. (Regex failed)",
|
||||
},
|
||||
);
|
||||
|
||||
export const zDockerDuration = z
|
||||
.string()
|
||||
.regex(/(?:[\d.]+h)?(?:[\d.]+m)?(?:[\d.]+s)?/, {
|
||||
message: "Must be a valid duration. (Regex failed)",
|
||||
});
|
||||
|
|
Loading…
Reference in a new issue