add push to deploy command for self-hosting

This commit is contained in:
nicktrn
2024-03-19 16:24:32 +00:00
parent d39aca8820
commit 752f3f3d25
+97 -38
View File
@@ -33,6 +33,7 @@ import { detectPackageNameFromImportPath } from "../utilities/installPackages";
import { logger } from "../utilities/logger.js";
import { createTaskFileImports, gatherTaskFiles } from "../utilities/taskFiles";
import { login } from "./login";
import { SetOptional } from "type-fest";
const DeployCommandOptions = CommonCommandOptions.extend({
skipTypecheck: z.boolean().default(false),
@@ -42,7 +43,7 @@ const DeployCommandOptions = CommonCommandOptions.extend({
buildPlatform: z.enum(["linux/amd64", "linux/arm64"]).default("linux/amd64"),
selfHosted: z.boolean().default(false),
registry: z.string().optional(),
pushImage: z.boolean().default(false),
push: z.boolean().default(false),
config: z.string().optional(),
projectRef: z.string().optional(),
outputMetafile: z.string().optional(),
@@ -81,14 +82,14 @@ export function configureDeployCommand(program: Command) {
)
.addOption(
new CommandOption(
"--push-image",
"(Coming soon) When using the --self-hosted flag, push the image to the default registry. (defaults to false when not using --registry)"
"--push",
"When using the --self-hosted flag, push the image to the default registry. (defaults to false when not using --registry)"
).hideHelp()
)
.addOption(
new CommandOption(
"--registry <registry>",
"(Coming soon) The registry to push the image to when using --self-hosted"
"The registry to push the image to when using --self-hosted"
).hideHelp()
)
.addOption(
@@ -134,7 +135,11 @@ async function _deployCommand(dir: string, options: DeployCommandOptions) {
intro("Deploying project");
const authorization = await login({ embedded: true, defaultApiUrl: options.apiUrl, profile: options.profile });
const authorization = await login({
embedded: true,
defaultApiUrl: options.apiUrl,
profile: options.profile,
});
if (!authorization.ok) {
if (authorization.error === "fetch failed") {
@@ -228,11 +233,13 @@ async function _deployCommand(dir: string, options: DeployCommandOptions) {
const deploymentSpinner = spinner();
deploymentSpinner.start(`Deploying version ${version}`);
const registryHost = deploymentResponse.data.registryHost ?? options.registry ?? "registry.trigger.dev";
const selfHostedRegistryHost = deploymentResponse.data.registryHost ?? options.registry;
const registryHost = selfHostedRegistryHost ?? "registry.trigger.dev";
const buildImage = async () => {
if (options.selfHosted) {
return buildAndPushSelfHostedImage({
registryHost: selfHostedRegistryHost,
imageTag: deploymentResponse.data.imageTag,
cwd: compilation.path,
projectId: resolvedConfig.config.project,
@@ -241,6 +248,8 @@ async function _deployCommand(dir: string, options: DeployCommandOptions) {
contentHash: deploymentResponse.data.contentHash,
projectRef: resolvedConfig.config.project,
buildPlatform: options.buildPlatform,
pushImage: options.push,
selfHostedRegistry: !!options.registry,
});
}
@@ -277,7 +286,9 @@ async function _deployCommand(dir: string, options: DeployCommandOptions) {
}
const imageReference = options.selfHosted
? `${image.image}${image.digest ? `@${image.digest}` : ""}`
? `${selfHostedRegistryHost ? `${selfHostedRegistryHost}/` : ""}${image.image}${
image.digest ? `@${image.digest}` : ""
}`
: `${registryHost}/${image.image}${image.digest ? `@${image.digest}` : ""}`;
span?.setAttributes({
@@ -330,7 +341,7 @@ async function _deployCommand(dir: string, options: DeployCommandOptions) {
const deploymentLink = terminalLink(
"View deployment",
`${authorization.dashboardUrl}/projects/v3/${resolvedConfig.config.project}/deployments/${finishedDeployment.id}`
`${authorization.dashboardUrl}/projects/v3/${resolvedConfig.config.project}/deployments/${finishedDeployment.shortCode}`
);
switch (finishedDeployment.status) {
@@ -345,7 +356,8 @@ async function _deployCommand(dir: string, options: DeployCommandOptions) {
);
} else {
outro(
`Version ${version} deployed with ${taskCount} detected task${taskCount === 1 ? "" : "s"
`Version ${version} deployed with ${taskCount} detected task${
taskCount === 1 ? "" : "s"
} ${deploymentLink}`
);
}
@@ -509,14 +521,14 @@ type BuildAndPushImageOptions = {
type BuildAndPushImageResults =
| {
ok: true;
image: string;
digest?: string;
}
ok: true;
image: string;
digest?: string;
}
| {
ok: false;
error: string;
};
ok: false;
error: string;
};
async function buildAndPushImage(
options: BuildAndPushImageOptions
@@ -624,10 +636,16 @@ async function buildAndPushImage(
});
}
type BuildAndPushSelfHostedImageOptions = Omit<
BuildAndPushImageOptions,
"registryHost" | "buildId" | "buildToken" | "buildProjectId" | "auth" | "loadImage"
>;
type BuildAndPushSelfHostedImageOptions = SetOptional<
Omit<
BuildAndPushImageOptions,
"buildId" | "buildToken" | "buildProjectId" | "auth" | "loadImage"
>,
"registryHost"
> & {
pushImage: boolean;
selfHostedRegistry: boolean;
};
async function buildAndPushSelfHostedImage(
options: BuildAndPushSelfHostedImageOptions
@@ -643,7 +661,9 @@ async function buildAndPushSelfHostedImage(
"options.projectRef": options.projectRef,
});
const args = [
const imageRef = `${options.registryHost ? `${options.registryHost}/` : ""}${options.imageTag}`;
const buildArgs = [
"build",
"-f",
"Containerfile",
@@ -660,48 +680,41 @@ async function buildAndPushSelfHostedImage(
"--build-arg",
`TRIGGER_PROJECT_REF=${options.projectRef}`,
"-t",
`${options.imageTag}`,
imageRef,
".", // The build context
].filter(Boolean) as string[];
logger.debug(`docker ${args.join(" ")}`);
logger.debug(`docker ${buildArgs.join(" ")}`);
span.setAttribute("docker.command", `docker ${args.join(" ")}`);
span.setAttribute("docker.command.build", `docker ${buildArgs.join(" ")}`);
// Step 4: Build and push the image
const childProcess = execa("docker", args, {
// Build the image
const buildProcess = execa("docker", buildArgs, {
cwd: options.cwd,
});
const errors: string[] = [];
let digest: string | undefined;
try {
await new Promise<void>((res, rej) => {
// For some reason everything is output on stderr, not stdout
childProcess.stderr?.on("data", (data: Buffer) => {
buildProcess.stderr?.on("data", (data: Buffer) => {
const text = data.toString();
errors.push(text);
logger.debug(text);
});
childProcess.on("error", (e) => rej(e));
childProcess.on("close", () => res());
buildProcess.on("error", (e) => rej(e));
buildProcess.on("close", () => res());
});
const digest = extractImageDigest(errors);
digest = extractImageDigest(errors);
span.setAttributes({
"image.digest": digest,
});
span.end();
return {
ok: true as const,
image: options.imageTag,
digest,
};
} catch (e) {
recordSpanException(span, e);
@@ -712,6 +725,52 @@ async function buildAndPushSelfHostedImage(
error: e instanceof Error ? e.message : JSON.stringify(e),
};
}
const pushArgs = ["push", imageRef].filter(Boolean) as string[];
logger.debug(`docker ${pushArgs.join(" ")}`);
span.setAttribute("docker.command.push", `docker ${pushArgs.join(" ")}`);
if (options.selfHostedRegistry || options.pushImage) {
// Push the image
const pushProcess = execa("docker", pushArgs, {
cwd: options.cwd,
});
try {
await new Promise<void>((res, rej) => {
// For some reason everything is output on stderr, not stdout
pushProcess.stderr?.on("data", (data: Buffer) => {
const text = data.toString();
logger.debug(text);
});
pushProcess.on("error", (e) => rej(e));
pushProcess.on("close", () => res());
});
span.end();
} catch (e) {
recordSpanException(span, e);
span.end();
return {
ok: false as const,
error: e instanceof Error ? e.message : JSON.stringify(e),
};
}
}
span.end();
return {
ok: true as const,
image: options.imageTag,
digest,
};
});
}