feat: release 2.7.0-rc2
This commit is contained in:
+122
-4
@@ -4,12 +4,18 @@ import { join } from "node:path";
|
||||
import {
|
||||
BUILD_PROTOCOL_VERSION,
|
||||
assertSha256Digest,
|
||||
validateBuildpackUri,
|
||||
type BuildEvent,
|
||||
type BuildRequest,
|
||||
type BuildStatus,
|
||||
type Sha256Digest,
|
||||
} from "../shared/build-protocol";
|
||||
import { createBuildJob, type KubernetesJob } from "./build-job";
|
||||
import {
|
||||
createBuildpacksJob,
|
||||
DEFAULT_BUILDPACKS_IMAGE,
|
||||
validateBuildpacksImage,
|
||||
} from "./buildpacks-job";
|
||||
import {
|
||||
BUILD_RECORD_API_VERSION,
|
||||
BuildStoreConflictError,
|
||||
@@ -26,6 +32,7 @@ import {
|
||||
type MaterializeTiming,
|
||||
} from "./materialize";
|
||||
import { parseImageReference, resolveRegistryDigest } from "./registry";
|
||||
import { stageBuildpackPackage } from "./buildpack-package";
|
||||
|
||||
export const DEFAULT_MAX_BLOB_BYTES = 1024 * 1024 * 1024;
|
||||
export const DEFAULT_MAX_UPLOAD_CHUNK_BYTES = 8 * 1024 * 1024;
|
||||
@@ -40,7 +47,12 @@ export interface BuildCas extends MaterializeCas {
|
||||
}
|
||||
|
||||
export type JobPhase =
|
||||
"queued" | "creating" | "starting" | "running" | "succeeded" | "failed";
|
||||
| "queued"
|
||||
| "creating"
|
||||
| "starting"
|
||||
| "running"
|
||||
| "succeeded"
|
||||
| "failed";
|
||||
|
||||
export interface BuildJobObservation {
|
||||
phase: JobPhase;
|
||||
@@ -79,6 +91,8 @@ export interface BuildControllerOptions {
|
||||
pushRegistryInsecure?: boolean;
|
||||
cacheRegistryInsecure?: boolean;
|
||||
buildkitImage?: string;
|
||||
buildpacksImage?: string;
|
||||
stageBuildpack?: typeof stageBuildpackPackage;
|
||||
serviceAccountName?: string;
|
||||
registrySecretName?: string;
|
||||
nodeSelector?: Record<string, string>;
|
||||
@@ -182,6 +196,33 @@ function validateRequest(request: BuildRequest): void {
|
||||
throw new BuildValidationError("Build ID is too long");
|
||||
if (!request.spec || !["amd64", "arm64"].includes(request.spec.architecture))
|
||||
throw new BuildValidationError("Invalid build architecture");
|
||||
if (
|
||||
request.spec.builder !== undefined &&
|
||||
request.spec.builder !== "buildkit" &&
|
||||
request.spec.builder !== "buildpacks"
|
||||
)
|
||||
throw new BuildValidationError("Invalid build builder");
|
||||
if (request.spec.buildpackUri !== undefined) {
|
||||
if (request.spec.builder !== "buildpacks")
|
||||
throw new BuildValidationError(
|
||||
"buildpackUri requires the buildpacks builder",
|
||||
);
|
||||
try {
|
||||
validateBuildpackUri(request.spec.buildpackUri);
|
||||
} catch (error) {
|
||||
throw new BuildValidationError((error as Error).message);
|
||||
}
|
||||
}
|
||||
if (request.spec.builder === "buildpacks") {
|
||||
if (
|
||||
request.spec.dockerfile !== undefined ||
|
||||
request.spec.target !== undefined ||
|
||||
request.spec.buildArgs?.length
|
||||
)
|
||||
throw new BuildValidationError(
|
||||
"Buildpacks does not support dockerfile, target, or buildArgs",
|
||||
);
|
||||
}
|
||||
if (
|
||||
!Array.isArray(request.spec.buildArgs) ||
|
||||
!request.spec.buildArgs.every((value) => typeof value === "string")
|
||||
@@ -446,6 +487,15 @@ export class BuildController {
|
||||
}),
|
||||
}));
|
||||
validateRequest(request);
|
||||
if (request.spec.builder === "buildpacks") {
|
||||
try {
|
||||
validateBuildpacksImage(
|
||||
this.options.buildpacksImage ?? DEFAULT_BUILDPACKS_IMAGE,
|
||||
);
|
||||
} catch (error) {
|
||||
throw new BuildValidationError((error as Error).message);
|
||||
}
|
||||
}
|
||||
const imageKey = `${request.project}\0${request.service}\0${request.spec.image}`;
|
||||
const started = performance.now();
|
||||
const previous =
|
||||
@@ -585,6 +635,13 @@ export class BuildController {
|
||||
join(this.options.workspaceRoot, superseded.spec.workspaceSubPath),
|
||||
{ recursive: true, force: true },
|
||||
).catch(() => undefined);
|
||||
await rm(
|
||||
join(
|
||||
this.options.workspaceRoot,
|
||||
`${superseded.spec.workspaceSubPath}-buildpack`,
|
||||
),
|
||||
{ recursive: true, force: true },
|
||||
).catch(() => undefined);
|
||||
}
|
||||
});
|
||||
if (!result.created) return recordStatus(stored);
|
||||
@@ -615,6 +672,15 @@ export class BuildController {
|
||||
materialize,
|
||||
),
|
||||
);
|
||||
const packageSubPath = `${stored.spec.workspaceSubPath}-buildpack`;
|
||||
if (request.spec.buildpackUri)
|
||||
await timed("buildpackStageMs", () =>
|
||||
(this.options.stageBuildpack ?? stageBuildpackPackage)(
|
||||
request.spec.buildpackUri!,
|
||||
request.spec.architecture,
|
||||
join(this.options.workspaceRoot, packageSubPath),
|
||||
),
|
||||
);
|
||||
if (
|
||||
this.options.store.ownsBuild &&
|
||||
!(await timed("finalOwnershipMs", () =>
|
||||
@@ -625,10 +691,29 @@ export class BuildController {
|
||||
`Build '${request.id}' lost image lock before Job creation`,
|
||||
);
|
||||
const job = await timed("jobSpecMs", async () => {
|
||||
const cacheImage =
|
||||
let cacheImage =
|
||||
typeof this.options.cacheImage === "function"
|
||||
? this.options.cacheImage(request)
|
||||
: this.options.cacheImage;
|
||||
if (request.spec.buildpackUri) {
|
||||
const suffix = createHash("sha256")
|
||||
.update(
|
||||
JSON.stringify([
|
||||
cacheImage,
|
||||
request.spec.architecture,
|
||||
request.spec.buildpackUri,
|
||||
]),
|
||||
)
|
||||
.digest("hex")
|
||||
.slice(0, 24);
|
||||
const colon = cacheImage.lastIndexOf(":");
|
||||
const slash = cacheImage.lastIndexOf("/");
|
||||
if (cacheImage.includes("@"))
|
||||
throw new BuildValidationError(
|
||||
"Build cache must be a tag, not a digest",
|
||||
);
|
||||
cacheImage = `${colon > slash ? cacheImage.slice(0, colon) : cacheImage}:buildpack-${suffix}`;
|
||||
}
|
||||
const pushImage = this.options.pushImage
|
||||
? this.options.pushImage(request)
|
||||
: request.spec.image;
|
||||
@@ -642,7 +727,7 @@ export class BuildController {
|
||||
})
|
||||
: destination.image,
|
||||
);
|
||||
return createBuildJob({
|
||||
const jobOptions = {
|
||||
name: jobName,
|
||||
namespace: this.options.namespace,
|
||||
spec: request.spec,
|
||||
@@ -663,7 +748,22 @@ export class BuildController {
|
||||
registrySecretName: this.options.registrySecretName,
|
||||
nodeSelector: this.options.nodeSelector,
|
||||
tolerations: this.options.tolerations,
|
||||
});
|
||||
};
|
||||
return request.spec.builder === "buildpacks"
|
||||
? createBuildpacksJob({
|
||||
...jobOptions,
|
||||
buildpacksImage: this.options.buildpacksImage,
|
||||
...(request.spec.buildpackUri && {
|
||||
buildpackSubPath: jobWorkspaceSubPath(
|
||||
this.options.workspaceRoot,
|
||||
packageSubPath,
|
||||
),
|
||||
}),
|
||||
cacheRegistryInsecure:
|
||||
this.options.cacheRegistryInsecure ??
|
||||
this.options.pushRegistryInsecure,
|
||||
})
|
||||
: createBuildJob(jobOptions);
|
||||
});
|
||||
await timed("jobCreateMs", () => this.options.kubernetes.createJob(job));
|
||||
markJobCreated();
|
||||
@@ -680,6 +780,17 @@ export class BuildController {
|
||||
} catch {
|
||||
// Job cleanup is best effort; the record still releases its lock.
|
||||
}
|
||||
await rm(
|
||||
join(this.options.workspaceRoot, stored.spec.workspaceSubPath),
|
||||
{ recursive: true, force: true },
|
||||
).catch(() => undefined);
|
||||
await rm(
|
||||
join(
|
||||
this.options.workspaceRoot,
|
||||
`${stored.spec.workspaceSubPath}-buildpack`,
|
||||
),
|
||||
{ recursive: true, force: true },
|
||||
).catch(() => undefined);
|
||||
await this.failBuild(
|
||||
stored.metadata.name,
|
||||
error instanceof Error ? error.message : String(error),
|
||||
@@ -975,6 +1086,13 @@ export class BuildController {
|
||||
recursive: true,
|
||||
force: true,
|
||||
});
|
||||
await rm(
|
||||
join(
|
||||
this.options.workspaceRoot,
|
||||
`${record.spec.workspaceSubPath}-buildpack`,
|
||||
),
|
||||
{ recursive: true, force: true },
|
||||
);
|
||||
}
|
||||
|
||||
async getBuildResult(id: string): Promise<{
|
||||
|
||||
Reference in New Issue
Block a user