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 ( 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 {...form}>
<form <form
id="hook-form-save-compose-file"
onSubmit={form.handleSubmit(onSubmit)} onSubmit={form.handleSubmit(onSubmit)}
className="w-full relative space-y-4" className="w-full relative space-y-4"
> >
@@ -121,21 +122,21 @@ services:
</FormItem> </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>
</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> </div>
</> </>
); );

View File

@@ -1,5 +1,7 @@
import { AlertBlock } from "@/components/shared/alert-block"; import { AlertBlock } from "@/components/shared/alert-block";
import { CodeEditor } from "@/components/shared/code-editor";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { CardTitle } from "@/components/ui/card";
import { import {
Dialog, Dialog,
DialogContent, DialogContent,
@@ -8,25 +10,91 @@ import {
DialogTitle, DialogTitle,
DialogTrigger, DialogTrigger,
} from "@/components/ui/dialog"; } from "@/components/ui/dialog";
import {
FormField,
FormItem,
FormLabel,
FormControl,
FormDescription,
FormMessage,
Form,
} from "@/components/ui/form";
import { Input } from "@/components/ui/input"; 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 { api } from "@/utils/api";
import { Dices } from "lucide-react"; import { zodResolver } from "@hookform/resolvers/zod";
import { useState } from "react"; import { AlertTriangle, Dices } from "lucide-react";
import { useEffect, useState } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner"; import { toast } from "sonner";
import { z } from "zod";
interface Props { interface Props {
composeId: string; composeId: string;
} }
const schema = z.object({
prefix: z.string(),
randomize: z.boolean().optional(),
});
type Schema = z.infer<typeof schema>;
export const RandomizeCompose = ({ composeId }: Props) => { export const RandomizeCompose = ({ composeId }: Props) => {
const utils = api.useUtils(); const utils = api.useUtils();
const [prefix, setPrefix] = useState<string>("");
const [compose, setCompose] = useState<string>(""); const [compose, setCompose] = useState<string>("");
const [isOpen, setIsOpen] = useState(false); const [isOpen, setIsOpen] = useState(false);
const { mutateAsync, error, isError } = const { mutateAsync, error, isError } =
api.compose.randomizeCompose.useMutation(); 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({ await mutateAsync({
composeId, composeId,
prefix, prefix,
@@ -43,7 +111,7 @@ export const RandomizeCompose = ({ composeId }: Props) => {
return ( return (
<Dialog open={isOpen} onOpenChange={setIsOpen}> <Dialog open={isOpen} onOpenChange={setIsOpen}>
<DialogTrigger asChild onClick={() => onSubmit()}> <DialogTrigger asChild onClick={() => randomizeCompose()}>
<Button className="max-lg:w-full" variant="outline"> <Button className="max-lg:w-full" variant="outline">
<Dices className="h-4 w-4" /> <Dices className="h-4 w-4" />
Randomize Compose Randomize Compose
@@ -69,29 +137,98 @@ export const RandomizeCompose = ({ composeId }: Props) => {
<li>configs</li> <li>configs</li>
<li>secrets</li> <li>secrets</li>
</ul> </ul>
</div> <AlertBlock type="info">
<div className="flex flex-col lg:flex-row gap-2"> When you activate this option, we will include a env
<Input `COMPOSE_PREFIX` variable to the compose file so you can use it in
placeholder="Enter a prefix (Optional, example: prod)" your compose file.
onChange={(e) => setPrefix(e.target.value)} </AlertBlock>
/>
<Button
type="submit"
onClick={async () => {
await onSubmit();
}}
className="lg:w-fit w-full"
>
Random
</Button>
</div> </div>
{isError && <AlertBlock type="error">{error?.message}</AlertBlock>} {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"> <div className="flex flex-col lg:flex-col gap-4 w-full ">
<pre> <div>
<code className="language-yaml">{compose}</code> <FormField
</pre> control={form.control}
</div> 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> </DialogContent>
</Dialog> </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, "when": 1725256397019,
"tag": "0034_aspiring_secret_warriors", "tag": "0034_aspiring_secret_warriors",
"breakpoints": true "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"; } from "@/server/queues/deployments-queue";
import { myQueue } from "@/server/queues/queueSetup"; import { myQueue } from "@/server/queues/queueSetup";
import { createCommand } from "@/server/utils/builders/compose"; 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 { addDomainToCompose, cloneCompose } from "@/server/utils/docker/domain";
import { removeComposeDirectory } from "@/server/utils/filesystem/directory"; import { removeComposeDirectory } from "@/server/utils/filesystem/directory";
import { templates } from "@/templates/templates"; import { templates } from "@/templates/templates";
@@ -149,7 +152,12 @@ export const composeRouter = createTRPCRouter({
const compose = await findComposeById(input.composeId); const compose = await findComposeById(input.composeId);
const domains = await findDomainsByComposeId(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, { return dump(composeFile, {
lineWidth: 1000, lineWidth: 1000,
}); });

View File

@@ -64,6 +64,8 @@ export const compose = pgTable("compose", {
command: text("command").notNull().default(""), command: text("command").notNull().default(""),
// //
composePath: text("composePath").notNull().default("./docker-compose.yml"), 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"), composeStatus: applicationStatus("composeStatus").notNull().default("idle"),
projectId: text("projectId") projectId: text("projectId")
.notNull() .notNull()

View File

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

View File

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

View File

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