Files
dokploy/packages/server/src/db/schema/registry.ts
Rafael Dias Zendron ec9dd28924 fix(registry): preserve username case for ECR compatibility (#4632) (#4647)
Remove .toLowerCase() transform from registryUsernameSchema.
AWS ECR requires the username to be exactly 'AWS' (uppercase) for
authentication. Docker Hub usernames are case-insensitive for login,
so preserving case is safe for all providers.

Closes #4632
2026-06-30 16:07:15 -06:00

130 lines
3.8 KiB
TypeScript

import { relations } from "drizzle-orm";
import { pgEnum, pgTable, text } from "drizzle-orm/pg-core";
import { createInsertSchema } from "drizzle-zod";
import { nanoid } from "nanoid";
import { z } from "zod";
import { organization } from "./account";
import { applications } from "./application";
/**
* This is an example of how to use the multi-project schema feature of Drizzle ORM. Use the same
* database instance for multiple projects.
*
* @see https://orm.drizzle.team/docs/goodies#multi-project-schema
*/
export const registryType = pgEnum("RegistryType", ["selfHosted", "cloud"]);
export const registry = pgTable("registry", {
registryId: text("registryId")
.notNull()
.primaryKey()
.$defaultFn(() => nanoid()),
registryName: text("registryName").notNull(),
imagePrefix: text("imagePrefix"),
username: text("username").notNull(),
password: text("password").notNull(),
registryUrl: text("registryUrl").notNull().default(""),
createdAt: text("createdAt")
.notNull()
.$defaultFn(() => new Date().toISOString()),
registryType: registryType("selfHosted").notNull().default("cloud"),
organizationId: text("organizationId")
.notNull()
.references(() => organization.id, { onDelete: "cascade" }),
});
export const registryRelations = relations(registry, ({ many }) => ({
applications: many(applications, {
relationName: "applicationRegistry",
}),
buildApplications: many(applications, {
relationName: "applicationBuildRegistry",
}),
rollbackApplications: many(applications, {
relationName: "applicationRollbackRegistry",
}),
}));
// Registry usernames should NOT be lowercased.
// Some registries (e.g. AWS ECR) require a specific case: the username must be
// exactly "AWS" (uppercase) for ECR authentication. Docker Hub usernames are
// case-insensitive for login, so preserving case is safe for all providers.
const registryUsernameSchema = z.string().trim().min(1);
// Registry URLs must be hostname[:port] only — no shell metacharacters
// Empty string is allowed (means default/Docker Hub registry)
const registryUrlSchema = z
.string()
.refine(
(val) =>
val === "" ||
/^[a-zA-Z0-9]([a-zA-Z0-9._-]*[a-zA-Z0-9])?(:\d{1,5})?$/.test(val),
"Registry URL must be a valid hostname or hostname:port (e.g. registry.example.com or localhost:5000)",
);
const createSchema = createInsertSchema(registry, {
registryName: z.string().min(1),
username: registryUsernameSchema,
password: z.string().min(1),
registryUrl: registryUrlSchema,
organizationId: z.string().min(1),
registryId: z.string().min(1),
registryType: z.enum(["cloud"]),
imagePrefix: z.string().nullable().optional(),
});
export const apiCreateRegistry = createSchema
.pick({})
.extend({
registryName: z.string().min(1),
username: registryUsernameSchema,
password: z.string().min(1),
registryUrl: registryUrlSchema,
registryType: z.enum(["cloud"]),
imagePrefix: z.string().nullable().optional(),
})
.required()
.extend({
serverId: z.string().optional(),
});
export const apiTestRegistry = createSchema.pick({}).extend({
registryName: z.string().optional(),
username: registryUsernameSchema,
password: z.string().min(1),
registryUrl: registryUrlSchema,
registryType: z.enum(["cloud"]),
imagePrefix: z.string().nullable().optional(),
serverId: z.string().optional(),
});
export const apiTestRegistryById = createSchema
.pick({
registryId: true,
})
.extend({
serverId: z.string().optional(),
});
export const apiRemoveRegistry = createSchema
.pick({
registryId: true,
})
.required();
export const apiFindOneRegistry = z.object({
registryId: z.string().min(1),
});
export const apiUpdateRegistry = createSchema.partial().extend({
registryId: z.string().min(1),
serverId: z.string().optional(),
});
export const apiEnableSelfHostedRegistry = createSchema
.pick({
registryUrl: true,
username: true,
password: true,
})
.required();