Merge pull request #297 from lorenzomigliorero/feat/static-build-phase

feat: new publish directory flag
This commit is contained in:
Mauricio Siu 2024-08-01 02:28:37 -06:00 committed by GitHub
commit 6299385bb4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 3162 additions and 11 deletions

View File

@ -40,6 +40,7 @@ const baseApp: ApplicationNested = {
placementSwarm: null,
ports: [],
projectId: "",
publishDirectory: null,
redirects: [],
refreshToken: "",
registry: null,

View File

@ -3,6 +3,7 @@ import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import {
Form,
FormControl,
FormDescription,
FormField,
FormItem,
FormLabel,
@ -43,6 +44,7 @@ const mySchema = z.discriminatedUnion("buildType", [
}),
z.object({
buildType: z.literal("nixpacks"),
publishDirectory: z.string().optional(),
}),
]);
@ -84,6 +86,7 @@ export const ShowBuildChooseForm = ({ applicationId }: Props) => {
} else {
form.reset({
buildType: data.buildType,
publishDirectory: data.publishDirectory || undefined,
});
}
}
@ -93,6 +96,8 @@ export const ShowBuildChooseForm = ({ applicationId }: Props) => {
await mutateAsync({
applicationId,
buildType: data.buildType,
publishDirectory:
data.buildType === "nixpacks" ? data.publishDirectory : null,
dockerfile: data.buildType === "dockerfile" ? data.dockerfile : null,
})
.then(async () => {
@ -200,6 +205,36 @@ export const ShowBuildChooseForm = ({ applicationId }: Props) => {
}}
/>
)}
{buildType === "nixpacks" && (
<FormField
control={form.control}
name="publishDirectory"
render={({ field }) => {
return (
<FormItem>
<div className="space-y-0.5">
<FormLabel>Publish Directory</FormLabel>
<FormDescription>
Allows you to serve a single directory via NGINX after
the build phase. Useful if the final build assets
should be served as a static site.
</FormDescription>
</div>
<FormControl>
<Input
placeholder={"Publish Directory"}
{...field}
value={field.value ?? ""}
/>
</FormControl>
<FormMessage />
</FormItem>
);
}}
/>
)}
<div className="flex w-full justify-end">
<Button isLoading={isLoading} type="submit">
Save

View File

@ -0,0 +1 @@
ALTER TABLE "application" ADD COLUMN "publishDirectory" text;

File diff suppressed because it is too large Load Diff

View File

@ -190,6 +190,13 @@
"when": 1721979220929,
"tag": "0026_known_dormammu",
"breakpoints": true
},
{
"idx": 27,
"version": "6",
"when": 1722445099203,
"tag": "0027_red_lady_bullseye",
"breakpoints": true
}
]
}

View File

@ -191,6 +191,7 @@ export const applicationRouter = createTRPCRouter({
await updateApplication(input.applicationId, {
buildType: input.buildType,
dockerfile: input.dockerfile,
publishDirectory: input.publishDirectory,
});
return true;

View File

@ -157,6 +157,7 @@ export const applications = pgTable("application", {
.notNull()
.default("idle"),
buildType: buildType("buildType").notNull().default("nixpacks"),
publishDirectory: text("publishDirectory"),
createdAt: text("createdAt")
.notNull()
.$defaultFn(() => new Date().toISOString()),
@ -316,6 +317,7 @@ const createSchema = createInsertSchema(applications, {
"paketo_buildpacks",
"nixpacks",
]),
publishDirectory: z.string().optional(),
owner: z.string(),
healthCheckSwarm: HealthCheckSwarmSchema.nullable(),
restartPolicySwarm: RestartPolicySwarmSchema.nullable(),
@ -353,7 +355,8 @@ export const apiSaveBuildType = createSchema
buildType: true,
dockerfile: true,
})
.required();
.required()
.merge(createSchema.pick({ publishDirectory: true }));
export const apiSaveGithubProvider = createSchema
.pick({

View File

@ -9,7 +9,7 @@ export const buildCustomDocker = async (
application: ApplicationNested,
writeStream: WriteStream,
) => {
const { appName, env, buildArgs } = application;
const { appName, env, publishDirectory, buildArgs } = application;
const dockerFilePath = getBuildAppDirectory(application);
try {
const image = `${appName}`;
@ -24,7 +24,15 @@ export const buildCustomDocker = async (
commandArgs.push("--build-arg", arg);
}
createEnvFile(dockerFilePath, env);
/*
Do not generate an environment file when publishDirectory is specified,
as it could be publicly exposed.
*/
if (!publishDirectory) {
createEnvFile(dockerFilePath, env);
}
await spawnAsync(
"docker",
commandArgs,

View File

@ -1,18 +1,28 @@
import type { WriteStream } from "node:fs";
import path from "node:path";
import { buildStatic } from "@/server/utils/builders/static";
import { nanoid } from "nanoid";
import type { ApplicationNested } from ".";
import { prepareEnvironmentVariables } from "../docker/utils";
import { getBuildAppDirectory } from "../filesystem/directory";
import { spawnAsync } from "../process/spawnAsync";
// TODO: integrate in the vps sudo chown -R $(whoami) ~/.docker
export const buildNixpacks = async (
application: ApplicationNested,
writeStream: WriteStream,
) => {
const { env, appName } = application;
const buildAppDirectory = getBuildAppDirectory(application);
const { env, appName, publishDirectory } = application;
const buildAppDirectory = getBuildAppDirectory(application);
const buildContainerId = `${appName}-${nanoid(10)}`;
const envVariables = prepareEnvironmentVariables(env);
const writeToStream = (data: string) => {
if (writeStream.writable) {
writeStream.write(data);
}
};
try {
const args = ["build", buildAppDirectory, "--name", appName];
@ -20,13 +30,44 @@ export const buildNixpacks = async (
args.push("--env", env);
}
await spawnAsync("nixpacks", args, (data) => {
if (writeStream.writable) {
writeStream.write(data);
}
});
if (publishDirectory) {
/* No need for any start command, since we'll use nginx later on */
args.push("--no-error-without-start");
}
await spawnAsync("nixpacks", args, writeToStream);
/*
Run the container with the image created by nixpacks,
and copy the artifacts on the host filesystem.
Then, remove the container and create a static build.
*/
if (publishDirectory) {
await spawnAsync(
"docker",
["create", "--name", buildContainerId, appName],
writeToStream,
);
await spawnAsync(
"docker",
[
"cp",
`${buildContainerId}:/app/${publishDirectory}`,
path.join(buildAppDirectory, publishDirectory),
],
writeToStream,
);
await spawnAsync("docker", ["rm", buildContainerId], writeToStream);
await buildStatic(application, writeStream);
}
return true;
} catch (e) {
await spawnAsync("docker", ["rm", buildContainerId], writeToStream);
throw e;
}
};

View File

@ -0,0 +1,38 @@
import type { WriteStream } from "node:fs";
import { buildCustomDocker } from "@/server/utils/builders/docker-file";
import type { ApplicationNested } from ".";
import { createFile } from "../docker/utils";
import { getBuildAppDirectory } from "../filesystem/directory";
export const buildStatic = async (
application: ApplicationNested,
writeStream: WriteStream,
) => {
const { publishDirectory } = application;
const buildAppDirectory = getBuildAppDirectory(application);
try {
createFile(
buildAppDirectory,
"Dockerfile",
[
"FROM nginx:alpine",
"WORKDIR /usr/share/nginx/html/",
`COPY ${publishDirectory || "."} .`,
].join("\n"),
);
await buildCustomDocker(
{
...application,
buildType: "dockerfile",
dockerfile: "Dockerfile",
},
writeStream,
);
return true;
} catch (e) {
throw e;
}
};