feat: add update registry and fix the docker url markup

This commit is contained in:
Mauricio Siu
2024-05-14 03:09:07 -06:00
parent d19dec8010
commit 08517d6f36
11 changed files with 366 additions and 67 deletions

View File

@@ -3,6 +3,7 @@ import {
apiEnableSelfHostedRegistry,
apiFindOneRegistry,
apiRemoveRegistry,
apiTestRegistry,
apiUpdateRegistry,
} from "@/server/db/schema";
import {
@@ -10,7 +11,7 @@ import {
findAllRegistry,
findRegistryById,
removeRegistry,
updaterRegistry,
updateRegistry,
} from "../services/registry";
import { adminProcedure, createTRPCRouter, protectedProcedure } from "../trpc";
import { TRPCError } from "@trpc/server";
@@ -33,7 +34,7 @@ export const registryRouter = createTRPCRouter({
.input(apiUpdateRegistry)
.mutation(async ({ input }) => {
const { registryId, ...rest } = input;
const application = await updaterRegistry(registryId, {
const application = await updateRegistry(registryId, {
...rest,
});
@@ -49,11 +50,11 @@ export const registryRouter = createTRPCRouter({
all: protectedProcedure.query(async () => {
return await findAllRegistry();
}),
findOne: adminProcedure.input(apiFindOneRegistry).query(async ({ input }) => {
one: adminProcedure.input(apiFindOneRegistry).query(async ({ input }) => {
return await findRegistryById(input.registryId);
}),
testRegistry: protectedProcedure
.input(apiCreateRegistry)
.input(apiTestRegistry)
.mutation(async ({ input }) => {
try {
const result = await docker.checkAuth({
@@ -76,6 +77,10 @@ export const registryRouter = createTRPCRouter({
...input,
registryName: "Self Hosted Registry",
registryType: "selfHosted",
registryUrl:
process.env.NODE_ENV === "production"
? input.registryUrl
: "dokploy-registry.docker.localhost",
imagePrefix: null,
});

View File

@@ -3,8 +3,12 @@ import { TRPCError } from "@trpc/server";
import { db } from "@/server/db";
import { eq } from "drizzle-orm";
import { findAdmin } from "./admin";
import { removeSelfHostedRegistry } from "@/server/utils/traefik/registry";
import {
manageRegistry,
removeSelfHostedRegistry,
} from "@/server/utils/traefik/registry";
import { removeService } from "@/server/utils/docker/utils";
import { initializeRegistry } from "@/server/setup/registry-setup";
export type Registry = typeof registry.$inferSelect;
@@ -59,7 +63,7 @@ export const removeRegistry = async (registryId: string) => {
}
};
export const updaterRegistry = async (
export const updateRegistry = async (
registryId: string,
registryData: Partial<Registry>,
) => {
@@ -70,9 +74,15 @@ export const updaterRegistry = async (
...registryData,
})
.where(eq(registry.registryId, registryId))
.returning();
.returning()
.then((res) => res[0]);
return response[0];
if (response?.registryType === "selfHosted") {
await manageRegistry(response);
await initializeRegistry(response.username, response.password);
}
return response;
} catch (error) {
throw new TRPCError({
code: "BAD_REQUEST",
@@ -84,6 +94,9 @@ export const updaterRegistry = async (
export const findRegistryById = async (registryId: string) => {
const registryResponse = await db.query.registry.findFirst({
where: eq(registry.registryId, registryId),
columns: {
password: false,
},
});
if (!registryResponse) {
throw new TRPCError({

View File

@@ -64,6 +64,15 @@ export const apiCreateRegistry = createSchema
})
.required();
export const apiTestRegistry = createSchema.pick({}).extend({
registryName: z.string().min(1),
username: z.string().min(1),
password: z.string().min(1),
registryUrl: z.string(),
registryType: z.enum(["selfHosted", "cloud"]),
imagePrefix: z.string().nullable().optional(),
});
export const apiRemoveRegistry = createSchema
.pick({
registryId: true,
@@ -76,15 +85,9 @@ export const apiFindOneRegistry = createSchema
})
.required();
export const apiUpdateRegistry = createSchema
.pick({
password: true,
registryName: true,
username: true,
registryUrl: true,
registryId: true,
})
.required();
export const apiUpdateRegistry = createSchema.partial().extend({
registryId: z.string().min(1),
});
export const apiEnableSelfHostedRegistry = createSchema
.pick({

View File

@@ -10,7 +10,7 @@ export const initializeRegistry = async (
) => {
const imageName = "registry:2.8.3";
const containerName = "dokploy-registry";
await generatePassword(username, password);
await generateRegistryPassword(username, password);
const randomPass = await generateRandomPassword();
const settings: CreateServiceOptions = {
Name: containerName,
@@ -76,7 +76,7 @@ export const initializeRegistry = async (
}
};
const generatePassword = async (username: string, password: string) => {
const generateRegistryPassword = async (username: string, password: string) => {
try {
const command = `htpasswd -nbB ${username} "${password}" > ${REGISTRY_PATH}/htpasswd`;
const result = await execAsync(command);

View File

@@ -89,12 +89,15 @@ export const mechanizeDockerContainer = async (
const registry = application.registry;
const image =
sourceType === "docker"
? dockerImage!
: registry
? `${registry.registryUrl}/${appName}`
: `${appName}:latest`;
let image = sourceType === "docker" ? dockerImage! : `${appName}:latest`;
if (registry) {
image = `${registry.registryUrl}/${appName}`;
if (registry.imagePrefix) {
image = `${registry.registryUrl}/${registry.imagePrefix}/${appName}`;
}
}
const settings: CreateServiceOptions = {
authconfig: {

View File

@@ -1,5 +1,4 @@
import type { ApplicationNested } from "../builders";
import { execAsync } from "../process/execAsync";
import { spawnAsync } from "../process/spawnAsync";
import type { WriteStream } from "node:fs";
@@ -13,25 +12,20 @@ export const uploadImage = async (
throw new Error("Registry not found");
}
const { registryUrl, imagePrefix } = registry;
const { registryUrl, imagePrefix, registryType } = registry;
const { appName } = application;
const imageName = `${appName}:latest`;
let finalURL = registryUrl;
const finalURL =
registryType === "selfHosted"
? process.env.NODE_ENV === "development"
? "localhost:5000"
: registryUrl
: registryUrl;
let registryTag = `${registryUrl}/${imageName}`;
if (imagePrefix) {
registryTag = `${registryUrl}/${imagePrefix}/${imageName}`;
}
// registry.digitalocean.com/<my-registry>/<my-image>
// index.docker.io/siumauricio/app-parse-multi-byte-port-e32uh7:latest
if (registry.registryType === "selfHosted") {
finalURL =
process.env.NODE_ENV === "development" ? "localhost:5000" : registryUrl;
registryTag = `${finalURL}/${imageName}`;
}
const registryTag = imagePrefix
? `${registryUrl}/${imagePrefix}/${imageName}`
: `${finalURL}/${imageName}`;
try {
console.log(finalURL, registryTag);

View File

@@ -1,18 +1,19 @@
import { loadOrCreateConfig } from "./application";
import type { FileConfig, HttpRouter } from "./file-types";
import type { Registry } from "@/server/api/services/registry";
import { removeDirectoryIfExistsContent } from "../filesystem/directory";
import { REGISTRY_PATH } from "@/server/constants";
import { dump } from "js-yaml";
import { dump, load } from "js-yaml";
import { join } from "node:path";
import { existsSync, mkdirSync, writeFileSync } from "node:fs";
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
export const manageRegistry = async (registry: Registry) => {
if (!existsSync(REGISTRY_PATH)) {
mkdirSync(REGISTRY_PATH, { recursive: true });
}
const appName = "dokploy-registry";
const config: FileConfig = loadOrCreateConfig(appName);
const config: FileConfig = loadOrCreateConfig();
const serviceName = `${appName}-service`;
const routerName = `${appName}-router`;
@@ -40,12 +41,8 @@ export const removeSelfHostedRegistry = async () => {
const createRegistryRouterConfig = async (registry: Registry) => {
const { registryUrl } = registry;
const url =
process.env.NODE_ENV === "production"
? registryUrl
: "dokploy-registry.docker.localhost";
const routerConfig: HttpRouter = {
rule: `Host(\`${url}\`)`,
rule: `Host(\`${registryUrl}\`)`,
service: "dokploy-registry-service",
...(process.env.NODE_ENV === "production"
? {
@@ -65,3 +62,15 @@ const createRegistryRouterConfig = async (registry: Registry) => {
return routerConfig;
};
const loadOrCreateConfig = (): FileConfig => {
const configPath = join(REGISTRY_PATH, "registry.yml");
if (existsSync(configPath)) {
const yamlStr = readFileSync(configPath, "utf8");
const parsedConfig = (load(yamlStr) as FileConfig) || {
http: { routers: {}, services: {} },
};
return parsedConfig;
}
return { http: { routers: {}, services: {} } };
};