mirror of
https://github.com/Dokploy/dokploy.git
synced 2026-06-15 20:25:23 +02:00
Merge pull request #249 from lorenzomigliorero/feat/buildargs
feat: buildargs
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -52,6 +52,7 @@ yarn-error.log*
|
|||||||
# otros
|
# otros
|
||||||
/.data
|
/.data
|
||||||
/.main
|
/.main
|
||||||
|
.vscode
|
||||||
|
|
||||||
*.lockb
|
*.lockb
|
||||||
*.rdb
|
*.rdb
|
||||||
|
|||||||
@@ -1,30 +1,16 @@
|
|||||||
import { CodeEditor } from "@/components/shared/code-editor";
|
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import {
|
import { Card, CardContent } from "@/components/ui/card";
|
||||||
Card,
|
import { Form } from "@/components/ui/form";
|
||||||
CardContent,
|
import { Secrets } from "@/components/ui/secrets";
|
||||||
CardDescription,
|
|
||||||
CardHeader,
|
|
||||||
CardTitle,
|
|
||||||
} from "@/components/ui/card";
|
|
||||||
import {
|
|
||||||
Form,
|
|
||||||
FormControl,
|
|
||||||
FormField,
|
|
||||||
FormItem,
|
|
||||||
FormMessage,
|
|
||||||
} from "@/components/ui/form";
|
|
||||||
import { Toggle } from "@/components/ui/toggle";
|
|
||||||
import { api } from "@/utils/api";
|
import { api } from "@/utils/api";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
import { EyeIcon, EyeOffIcon } from "lucide-react";
|
|
||||||
import React, { useEffect, useState } from "react";
|
|
||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
const addEnvironmentSchema = z.object({
|
const addEnvironmentSchema = z.object({
|
||||||
environment: z.string(),
|
env: z.string(),
|
||||||
|
buildArgs: z.string(),
|
||||||
});
|
});
|
||||||
|
|
||||||
type EnvironmentSchema = z.infer<typeof addEnvironmentSchema>;
|
type EnvironmentSchema = z.infer<typeof addEnvironmentSchema>;
|
||||||
@@ -34,7 +20,6 @@ interface Props {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const ShowEnvironment = ({ applicationId }: Props) => {
|
export const ShowEnvironment = ({ applicationId }: Props) => {
|
||||||
const [isEnvVisible, setIsEnvVisible] = useState(true);
|
|
||||||
const { mutateAsync, isLoading } =
|
const { mutateAsync, isLoading } =
|
||||||
api.application.saveEnvironment.useMutation();
|
api.application.saveEnvironment.useMutation();
|
||||||
|
|
||||||
@@ -46,24 +31,19 @@ export const ShowEnvironment = ({ applicationId }: Props) => {
|
|||||||
enabled: !!applicationId,
|
enabled: !!applicationId,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
const form = useForm<EnvironmentSchema>({
|
const form = useForm<EnvironmentSchema>({
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
environment: "",
|
env: data?.env || "",
|
||||||
|
buildArgs: data?.buildArgs || "",
|
||||||
},
|
},
|
||||||
resolver: zodResolver(addEnvironmentSchema),
|
resolver: zodResolver(addEnvironmentSchema),
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (data) {
|
|
||||||
form.reset({
|
|
||||||
environment: data.env || "",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}, [form.reset, data, form]);
|
|
||||||
|
|
||||||
const onSubmit = async (data: EnvironmentSchema) => {
|
const onSubmit = async (data: EnvironmentSchema) => {
|
||||||
mutateAsync({
|
mutateAsync({
|
||||||
env: data.environment,
|
env: data.env,
|
||||||
|
buildArgs: data.buildArgs,
|
||||||
applicationId,
|
applicationId,
|
||||||
})
|
})
|
||||||
.then(async () => {
|
.then(async () => {
|
||||||
@@ -74,94 +54,50 @@ export const ShowEnvironment = ({ applicationId }: Props) => {
|
|||||||
toast.error("Error to add environment");
|
toast.error("Error to add environment");
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
useEffect(() => {
|
|
||||||
if (isEnvVisible) {
|
|
||||||
if (data?.env) {
|
|
||||||
const maskedLines = data.env
|
|
||||||
.split("\n")
|
|
||||||
.map((line) => "*".repeat(line.length))
|
|
||||||
.join("\n");
|
|
||||||
form.reset({
|
|
||||||
environment: maskedLines,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
form.reset({
|
|
||||||
environment: "",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
form.reset({
|
|
||||||
environment: data?.env || "",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}, [form.reset, data, form, isEnvVisible]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex w-full flex-col gap-5 ">
|
<Form {...form}>
|
||||||
<Card className="bg-background">
|
<form
|
||||||
<CardHeader className="flex flex-row w-full items-center justify-between">
|
onSubmit={form.handleSubmit(onSubmit)}
|
||||||
<div>
|
className="flex w-full flex-col gap-5 "
|
||||||
<CardTitle className="text-xl">Environment Settings</CardTitle>
|
>
|
||||||
<CardDescription>
|
<Card className="bg-background">
|
||||||
You can add environment variables to your resource.
|
<Secrets
|
||||||
</CardDescription>
|
name="env"
|
||||||
</div>
|
title="Environment Settings"
|
||||||
|
description="You can add environment variables to your resource."
|
||||||
<Toggle
|
placeholder={["NODE_ENV=production", "PORT=3000"].join("\n")}
|
||||||
aria-label="Toggle bold"
|
/>
|
||||||
pressed={isEnvVisible}
|
{data?.buildType === "dockerfile" && (
|
||||||
onPressedChange={setIsEnvVisible}
|
<Secrets
|
||||||
>
|
name="buildArgs"
|
||||||
{isEnvVisible ? (
|
title="Build-time Variables"
|
||||||
<EyeOffIcon className="h-4 w-4 text-muted-foreground" />
|
description={
|
||||||
) : (
|
<span>
|
||||||
<EyeIcon className="h-4 w-4 text-muted-foreground" />
|
Available only at build-time. See documentation
|
||||||
)}
|
<a
|
||||||
</Toggle>
|
className="text-primary"
|
||||||
</CardHeader>
|
href="https://docs.docker.com/build/guide/build-args/"
|
||||||
<CardContent>
|
target="_blank"
|
||||||
<Form {...form}>
|
rel="noopener noreferrer"
|
||||||
<form
|
>
|
||||||
id="hook-form"
|
here
|
||||||
onSubmit={form.handleSubmit(onSubmit)}
|
</a>
|
||||||
className="w-full space-y-4"
|
.
|
||||||
>
|
</span>
|
||||||
<FormField
|
}
|
||||||
control={form.control}
|
placeholder="NPM_TOKEN=xyz"
|
||||||
name="environment"
|
/>
|
||||||
render={({ field }) => (
|
)}
|
||||||
<FormItem className="w-full">
|
<CardContent>
|
||||||
<FormControl>
|
<div className="flex flex-row justify-end">
|
||||||
<CodeEditor
|
<Button isLoading={isLoading} className="w-fit" type="submit">
|
||||||
language="properties"
|
Save
|
||||||
disabled={isEnvVisible}
|
</Button>
|
||||||
placeholder={`NODE_ENV=production
|
</div>
|
||||||
PORT=3000
|
</CardContent>
|
||||||
`}
|
</Card>
|
||||||
className="h-96 font-mono"
|
</form>
|
||||||
{...field}
|
</Form>
|
||||||
/>
|
|
||||||
</FormControl>
|
|
||||||
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<div className="flex flex-row justify-end">
|
|
||||||
<Button
|
|
||||||
disabled={isEnvVisible}
|
|
||||||
isLoading={isLoading}
|
|
||||||
className="w-fit"
|
|
||||||
type="submit"
|
|
||||||
>
|
|
||||||
Save
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</Form>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
78
components/ui/secrets.tsx
Normal file
78
components/ui/secrets.tsx
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
import { CodeEditor } from "@/components/shared/code-editor";
|
||||||
|
import {
|
||||||
|
CardContent,
|
||||||
|
CardDescription,
|
||||||
|
CardHeader,
|
||||||
|
CardTitle,
|
||||||
|
} from "@/components/ui/card";
|
||||||
|
import {
|
||||||
|
FormControl,
|
||||||
|
FormField,
|
||||||
|
FormItem,
|
||||||
|
FormMessage,
|
||||||
|
} from "@/components/ui/form";
|
||||||
|
import { Toggle } from "@/components/ui/toggle";
|
||||||
|
import { EyeIcon, EyeOffIcon } from "lucide-react";
|
||||||
|
import { type CSSProperties, type ReactNode, useState } from "react";
|
||||||
|
import { useFormContext } from "react-hook-form";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
name: string;
|
||||||
|
title: string;
|
||||||
|
description: ReactNode;
|
||||||
|
placeholder: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Secrets = (props: Props) => {
|
||||||
|
const [isVisible, setIsVisible] = useState(true);
|
||||||
|
const form = useFormContext<Record<string, string>>();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<CardHeader className="flex flex-row w-full items-center justify-between">
|
||||||
|
<div>
|
||||||
|
<CardTitle className="text-xl">{props.title}</CardTitle>
|
||||||
|
<CardDescription>{props.description}</CardDescription>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Toggle
|
||||||
|
aria-label="Toggle bold"
|
||||||
|
pressed={isVisible}
|
||||||
|
onPressedChange={setIsVisible}
|
||||||
|
>
|
||||||
|
{isVisible ? (
|
||||||
|
<EyeOffIcon className="h-4 w-4 text-muted-foreground" />
|
||||||
|
) : (
|
||||||
|
<EyeIcon className="h-4 w-4 text-muted-foreground" />
|
||||||
|
)}
|
||||||
|
</Toggle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="w-full space-y-4">
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name={props.name}
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem className="w-full">
|
||||||
|
<FormControl>
|
||||||
|
<CodeEditor
|
||||||
|
style={
|
||||||
|
{
|
||||||
|
WebkitTextSecurity: isVisible ? "disc" : null,
|
||||||
|
} as CSSProperties
|
||||||
|
}
|
||||||
|
language="properties"
|
||||||
|
disabled={isVisible}
|
||||||
|
placeholder={props.placeholder}
|
||||||
|
className="h-96 font-mono"
|
||||||
|
{...field}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</CardContent>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
1
drizzle/0025_lying_mephisto.sql
Normal file
1
drizzle/0025_lying_mephisto.sql
Normal file
@@ -0,0 +1 @@
|
|||||||
|
ALTER TABLE "application" ADD COLUMN "buildArgs" text;
|
||||||
2938
drizzle/meta/0025_snapshot.json
Normal file
2938
drizzle/meta/0025_snapshot.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -176,6 +176,13 @@
|
|||||||
"when": 1721603595092,
|
"when": 1721603595092,
|
||||||
"tag": "0024_dapper_supernaut",
|
"tag": "0024_dapper_supernaut",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 25,
|
||||||
|
"version": "6",
|
||||||
|
"when": 1721633853118,
|
||||||
|
"tag": "0025_lying_mephisto",
|
||||||
|
"breakpoints": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -17,7 +17,7 @@
|
|||||||
"migration:run": "tsx -r dotenv/config migration.ts",
|
"migration:run": "tsx -r dotenv/config migration.ts",
|
||||||
"migration:up": "drizzle-kit up --config ./server/db/drizzle.config.ts",
|
"migration:up": "drizzle-kit up --config ./server/db/drizzle.config.ts",
|
||||||
"migration:drop": "drizzle-kit drop --config ./server/db/drizzle.config.ts",
|
"migration:drop": "drizzle-kit drop --config ./server/db/drizzle.config.ts",
|
||||||
"db:push": "drizzle-kit --config ./server/db/drizzle.config.ts",
|
"db:push": "drizzle-kit push --config ./server/db/drizzle.config.ts",
|
||||||
"db:truncate": "tsx -r dotenv/config ./server/db/reset.ts",
|
"db:truncate": "tsx -r dotenv/config ./server/db/reset.ts",
|
||||||
"db:studio": "drizzle-kit studio --config ./server/db/drizzle.config.ts",
|
"db:studio": "drizzle-kit studio --config ./server/db/drizzle.config.ts",
|
||||||
"check": "biome check --write --no-errors-on-unmatched --files-ignore-unknown=true",
|
"check": "biome check --write --no-errors-on-unmatched --files-ignore-unknown=true",
|
||||||
|
|||||||
@@ -187,6 +187,7 @@ export const applicationRouter = createTRPCRouter({
|
|||||||
.mutation(async ({ input }) => {
|
.mutation(async ({ input }) => {
|
||||||
await updateApplication(input.applicationId, {
|
await updateApplication(input.applicationId, {
|
||||||
env: input.env,
|
env: input.env,
|
||||||
|
buildArgs: input.buildArgs,
|
||||||
});
|
});
|
||||||
return true;
|
return true;
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -107,6 +107,7 @@ export const applications = pgTable("application", {
|
|||||||
.unique(),
|
.unique(),
|
||||||
description: text("description"),
|
description: text("description"),
|
||||||
env: text("env"),
|
env: text("env"),
|
||||||
|
buildArgs: text("buildArgs"),
|
||||||
memoryReservation: integer("memoryReservation"),
|
memoryReservation: integer("memoryReservation"),
|
||||||
memoryLimit: integer("memoryLimit"),
|
memoryLimit: integer("memoryLimit"),
|
||||||
cpuReservation: integer("cpuReservation"),
|
cpuReservation: integer("cpuReservation"),
|
||||||
@@ -275,6 +276,7 @@ const createSchema = createInsertSchema(applications, {
|
|||||||
applicationId: z.string(),
|
applicationId: z.string(),
|
||||||
autoDeploy: z.boolean(),
|
autoDeploy: z.boolean(),
|
||||||
env: z.string().optional(),
|
env: z.string().optional(),
|
||||||
|
buildArgs: z.string().optional(),
|
||||||
name: z.string().min(1),
|
name: z.string().min(1),
|
||||||
description: z.string().optional(),
|
description: z.string().optional(),
|
||||||
memoryReservation: z.number().optional(),
|
memoryReservation: z.number().optional(),
|
||||||
@@ -375,6 +377,7 @@ export const apiSaveEnvironmentVariables = createSchema
|
|||||||
.pick({
|
.pick({
|
||||||
applicationId: true,
|
applicationId: true,
|
||||||
env: true,
|
env: true,
|
||||||
|
buildArgs: true,
|
||||||
})
|
})
|
||||||
.required();
|
.required();
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import type { WriteStream } from "node:fs";
|
import type { WriteStream } from "node:fs";
|
||||||
import { docker } from "@/server/constants";
|
import { docker } from "@/server/constants";
|
||||||
|
import { prepareBuildArgs } from "@/server/utils/docker/utils";
|
||||||
import * as tar from "tar-fs";
|
import * as tar from "tar-fs";
|
||||||
import type { ApplicationNested } from ".";
|
import type { ApplicationNested } from ".";
|
||||||
import { getBuildAppDirectory } from "../filesystem/directory";
|
import { getBuildAppDirectory } from "../filesystem/directory";
|
||||||
@@ -9,7 +10,7 @@ export const buildCustomDocker = async (
|
|||||||
application: ApplicationNested,
|
application: ApplicationNested,
|
||||||
writeStream: WriteStream,
|
writeStream: WriteStream,
|
||||||
) => {
|
) => {
|
||||||
const { appName, env } = application;
|
const { appName, env, buildArgs } = application;
|
||||||
const dockerFilePath = getBuildAppDirectory(application);
|
const dockerFilePath = getBuildAppDirectory(application);
|
||||||
try {
|
try {
|
||||||
const image = `${appName}`;
|
const image = `${appName}`;
|
||||||
@@ -21,6 +22,7 @@ export const buildCustomDocker = async (
|
|||||||
|
|
||||||
const stream = await docker.buildImage(tarStream, {
|
const stream = await docker.buildImage(tarStream, {
|
||||||
t: image,
|
t: image,
|
||||||
|
buildargs: prepareBuildArgs(buildArgs),
|
||||||
dockerfile: dockerFilePath.substring(dockerFilePath.lastIndexOf("/") + 1),
|
dockerfile: dockerFilePath.substring(dockerFilePath.lastIndexOf("/") + 1),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -161,6 +161,21 @@ export const removeService = async (appName: string) => {
|
|||||||
export const prepareEnvironmentVariables = (env: string | null) =>
|
export const prepareEnvironmentVariables = (env: string | null) =>
|
||||||
Object.entries(parse(env ?? "")).map(([key, value]) => `${key}=${value}`);
|
Object.entries(parse(env ?? "")).map(([key, value]) => `${key}=${value}`);
|
||||||
|
|
||||||
|
export const prepareBuildArgs = (input: string | null) => {
|
||||||
|
const pairs = (input ?? "").split("\n");
|
||||||
|
|
||||||
|
const jsonObject: Record<string, string> = {};
|
||||||
|
|
||||||
|
for (const pair of pairs) {
|
||||||
|
const [key, value] = pair.split("=");
|
||||||
|
if (key && value) {
|
||||||
|
jsonObject[key] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return jsonObject;
|
||||||
|
};
|
||||||
|
|
||||||
export const generateVolumeMounts = (mounts: ApplicationNested["mounts"]) => {
|
export const generateVolumeMounts = (mounts: ApplicationNested["mounts"]) => {
|
||||||
if (!mounts || mounts.length === 0) {
|
if (!mounts || mounts.length === 0) {
|
||||||
return [];
|
return [];
|
||||||
|
|||||||
Reference in New Issue
Block a user