feat: add randomize compose by prefix

This commit is contained in:
Mauricio Siu
2024-09-03 22:56:38 -06:00
parent 39f6fb5af3
commit a3be030fac
10 changed files with 3776 additions and 42 deletions

View File

@@ -83,9 +83,10 @@ export const ComposeFileEditor = ({ composeId }: Props) => {
};
return (
<>
<div className="w-full flex flex-col lg:flex-row gap-4 ">
<div className="w-full flex flex-col gap-4 ">
<Form {...form}>
<form
id="hook-form-save-compose-file"
onSubmit={form.handleSubmit(onSubmit)}
className="w-full relative space-y-4"
>
@@ -121,21 +122,21 @@ services:
</FormItem>
)}
/>
<div className="flex justify-between flex-col lg:flex-row gap-2">
<div className="w-full flex flex-col lg:flex-row gap-4 items-end">
<RandomizeCompose composeId={composeId} />
</div>
<Button
type="submit"
isLoading={isLoading}
className="lg:w-fit w-full"
>
Save
</Button>
</div>
</form>
</Form>
<div className="flex justify-between flex-col lg:flex-row gap-2">
<div className="w-full flex flex-col lg:flex-row gap-4 items-end">
<RandomizeCompose composeId={composeId} />
</div>
<Button
type="submit"
form="hook-form-save-compose-file"
isLoading={isLoading}
className="lg:w-fit w-full"
>
Save
</Button>
</div>
</div>
</>
);

View File

@@ -1,5 +1,7 @@
import { AlertBlock } from "@/components/shared/alert-block";
import { CodeEditor } from "@/components/shared/code-editor";
import { Button } from "@/components/ui/button";
import { CardTitle } from "@/components/ui/card";
import {
Dialog,
DialogContent,
@@ -8,25 +10,91 @@ import {
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import {
FormField,
FormItem,
FormLabel,
FormControl,
FormDescription,
FormMessage,
Form,
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import {
InputOTP,
InputOTPGroup,
InputOTPSlot,
} from "@/components/ui/input-otp";
import { Switch } from "@/components/ui/switch";
import { api } from "@/utils/api";
import { Dices } from "lucide-react";
import { useState } from "react";
import { zodResolver } from "@hookform/resolvers/zod";
import { AlertTriangle, Dices } from "lucide-react";
import { useEffect, useState } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import { z } from "zod";
interface Props {
composeId: string;
}
const schema = z.object({
prefix: z.string(),
randomize: z.boolean().optional(),
});
type Schema = z.infer<typeof schema>;
export const RandomizeCompose = ({ composeId }: Props) => {
const utils = api.useUtils();
const [prefix, setPrefix] = useState<string>("");
const [compose, setCompose] = useState<string>("");
const [isOpen, setIsOpen] = useState(false);
const { mutateAsync, error, isError } =
api.compose.randomizeCompose.useMutation();
const onSubmit = async () => {
const { mutateAsync: updateCompose } = api.compose.update.useMutation();
const { data, refetch } = api.compose.one.useQuery(
{ composeId },
{ enabled: !!composeId },
);
const form = useForm<Schema>({
defaultValues: {
prefix: "",
randomize: false,
},
resolver: zodResolver(schema),
});
const prefix = form.watch("prefix");
useEffect(() => {
if (data) {
form.reset({
prefix: data?.prefix || "",
randomize: data?.randomize || false,
});
}
}, [form, form.reset, form.formState.isSubmitSuccessful, data]);
const onSubmit = async (formData: Schema) => {
await updateCompose({
composeId,
prefix: formData?.prefix || "",
randomize: formData?.randomize || false,
})
.then(async (data) => {
randomizeCompose();
refetch();
toast.success("Compose updated");
})
.catch(() => {
toast.error("Error to randomize the compose");
});
};
const randomizeCompose = async () => {
await mutateAsync({
composeId,
prefix,
@@ -43,7 +111,7 @@ export const RandomizeCompose = ({ composeId }: Props) => {
return (
<Dialog open={isOpen} onOpenChange={setIsOpen}>
<DialogTrigger asChild onClick={() => onSubmit()}>
<DialogTrigger asChild onClick={() => randomizeCompose()}>
<Button className="max-lg:w-full" variant="outline">
<Dices className="h-4 w-4" />
Randomize Compose
@@ -69,29 +137,98 @@ export const RandomizeCompose = ({ composeId }: Props) => {
<li>configs</li>
<li>secrets</li>
</ul>
</div>
<div className="flex flex-col lg:flex-row gap-2">
<Input
placeholder="Enter a prefix (Optional, example: prod)"
onChange={(e) => setPrefix(e.target.value)}
/>
<Button
type="submit"
onClick={async () => {
await onSubmit();
}}
className="lg:w-fit w-full"
>
Random
</Button>
<AlertBlock type="info">
When you activate this option, we will include a env
`COMPOSE_PREFIX` variable to the compose file so you can use it in
your compose file.
</AlertBlock>
</div>
{isError && <AlertBlock type="error">{error?.message}</AlertBlock>}
<Form {...form}>
<form
onSubmit={form.handleSubmit(onSubmit)}
id="hook-form-add-project"
className="grid w-full gap-4"
>
{isError && (
<div className="flex flex-row gap-4 rounded-lg items-center bg-red-50 p-2 dark:bg-red-950">
<AlertTriangle className="text-red-600 dark:text-red-400" />
<span className="text-sm text-red-600 dark:text-red-400">
{error?.message}
</span>
</div>
)}
<div className="p-4 bg-secondary rounded-lg">
<pre>
<code className="language-yaml">{compose}</code>
</pre>
</div>
<div className="flex flex-col lg:flex-col gap-4 w-full ">
<div>
<FormField
control={form.control}
name="prefix"
render={({ field }) => (
<FormItem className="flex flex-col justify-center max-sm:items-center w-full">
<FormLabel>Prefix</FormLabel>
<FormControl>
<Input
placeholder="Enter a prefix (Optional, example: prod)"
{...field}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="randomize"
render={({ field }) => (
<FormItem className="mt-4 flex flex-row items-center justify-between rounded-lg border p-3 shadow-sm">
<div className="space-y-0.5">
<FormLabel>Apply Randomize</FormLabel>
<FormDescription>
Apply randomize to the compose file.
</FormDescription>
</div>
<FormControl>
<Switch
checked={field.value}
onCheckedChange={field.onChange}
/>
</FormControl>
</FormItem>
)}
/>
</div>
<div className="flex flex-col lg:flex-row gap-4 w-full items-end justify-end">
<Button
form="hook-form-add-project"
type="submit"
className="lg:w-fit"
>
Save
</Button>
<Button
type="button"
variant="secondary"
onClick={async () => {
await randomizeCompose();
}}
className="lg:w-fit"
>
Random
</Button>
</div>
</div>
<pre>
<CodeEditor
value={compose || ""}
language="yaml"
readOnly
height="50rem"
/>
</pre>
</form>
</Form>
</DialogContent>
</Dialog>
);

View File

@@ -0,0 +1,2 @@
ALTER TABLE "compose" ADD COLUMN "prefix" text DEFAULT '' NOT NULL;--> statement-breakpoint
ALTER TABLE "compose" ADD COLUMN "randomize" boolean DEFAULT false NOT NULL;

File diff suppressed because it is too large Load Diff

View File

@@ -246,6 +246,13 @@
"when": 1725256397019,
"tag": "0034_aspiring_secret_warriors",
"breakpoints": true
},
{
"idx": 35,
"version": "6",
"when": 1725421774684,
"tag": "0035_tired_kinsey_walden",
"breakpoints": true
}
]
}

View File

@@ -15,7 +15,10 @@ import {
} from "@/server/queues/deployments-queue";
import { myQueue } from "@/server/queues/queueSetup";
import { createCommand } from "@/server/utils/builders/compose";
import { randomizeComposeFile } from "@/server/utils/docker/compose";
import {
randomizeComposeFile,
randomizeSpecificationFile,
} from "@/server/utils/docker/compose";
import { addDomainToCompose, cloneCompose } from "@/server/utils/docker/domain";
import { removeComposeDirectory } from "@/server/utils/filesystem/directory";
import { templates } from "@/templates/templates";
@@ -149,7 +152,12 @@ export const composeRouter = createTRPCRouter({
const compose = await findComposeById(input.composeId);
const domains = await findDomainsByComposeId(input.composeId);
const composeFile = await addDomainToCompose(compose, domains);
let composeFile = await addDomainToCompose(compose, domains);
if (compose.randomize && composeFile && compose.prefix) {
const result = randomizeSpecificationFile(composeFile, compose.prefix);
composeFile = result;
}
return dump(composeFile, {
lineWidth: 1000,
});

View File

@@ -64,6 +64,8 @@ export const compose = pgTable("compose", {
command: text("command").notNull().default(""),
//
composePath: text("composePath").notNull().default("./docker-compose.yml"),
prefix: text("prefix").notNull().default(""),
randomize: boolean("randomize").notNull().default(false),
composeStatus: applicationStatus("composeStatus").notNull().default("idle"),
projectId: text("projectId")
.notNull()

View File

@@ -109,6 +109,10 @@ const createEnvFile = (compose: ComposeNested) => {
envContent += "\nDOCKER_CONFIG=/root/.docker/config.json";
}
if (compose.randomize) {
envContent += `\nCOMPOSE_PREFIX=${compose.prefix}`;
}
const envFileContent = prepareEnvironmentVariables(envContent).join("\n");
if (!existsSync(dirname(envFilePath))) {

View File

@@ -27,6 +27,14 @@ export const randomizeComposeFile = async (
return dump(newComposeFile);
};
export const randomizeSpecificationFile = (
composeSpec: ComposeSpecification,
prefix: string,
) => {
const newComposeFile = addPrefixToAllProperties(composeSpec, prefix);
return newComposeFile;
};
export const addPrefixToAllProperties = (
composeData: ComposeSpecification,
prefix: string,

View File

@@ -15,6 +15,7 @@ import type {
DefinitionsService,
PropertiesNetworks,
} from "./types";
import { randomizeSpecificationFile } from "./compose";
export const cloneCompose = async (compose: Compose) => {
if (compose.sourceType === "github") {
@@ -72,7 +73,12 @@ export const writeDomainsToCompose = async (
if (!domains.length) {
return;
}
const composeConverted = await addDomainToCompose(compose, domains);
let composeConverted = await addDomainToCompose(compose, domains);
if (compose.randomize && composeConverted && compose.prefix) {
const result = randomizeSpecificationFile(composeConverted, compose.prefix);
composeConverted = result;
}
const path = getComposePath(compose);
const composeString = dump(composeConverted, { lineWidth: 1000 });