Skip to content

Commit

Permalink
buildx: history load .dockerbuild
Browse files Browse the repository at this point in the history
Signed-off-by: CrazyMax <1951866+crazy-max@users.noreply.github.com>
  • Loading branch information
crazy-max committed Jun 7, 2024
1 parent 6a4479e commit 78d87a9
Show file tree
Hide file tree
Showing 12 changed files with 571 additions and 2 deletions.
49 changes: 49 additions & 0 deletions __tests__/buildx/history.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/**
* Copyright 2024 actions-toolkit authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import {afterEach, beforeEach, describe, expect, jest, test} from '@jest/globals';
import path from 'path';
import * as rimraf from 'rimraf';

import {History} from '../../src/buildx/history';

const fixturesDir = path.join(__dirname, '..', 'fixtures');

// prettier-ignore
const tmpDir = path.join(process.env.TEMP || '/tmp', 'docker-jest');

beforeEach(() => {
jest.clearAllMocks();
});

afterEach(function () {
rimraf.sync(tmpDir);
});

describe('load', () => {
// prettier-ignore
test.each([
['crazy-max~docker-alpine-s6~II9A63.dockerbuild'],
['docker~login-action~T0XYYW.dockerbuild'],
['moby~buildkit~LWDOW6.dockerbuild'],
])('loading %p', async (filename) => {
const res = await History.load({
file: path.join(fixturesDir, 'oci-archive', filename)
});
// console.log(JSON.stringify(res, null, 2));
expect(res).toBeDefined();
});
});
Binary file not shown.
88 changes: 86 additions & 2 deletions src/buildx/history.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,18 @@ import {Context} from '../context';
import {Docker} from '../docker/docker';
import {Exec} from '../exec';
import {GitHub} from '../github';

import {ExportRecordOpts, ExportRecordResponse, Summaries} from '../types/buildx/history';
import {OCI} from '../oci/oci';

import {ExportRecordOpts, ExportRecordResponse, LoadRecordOpts, Summaries} from '../types/buildx/history';
import {Index} from '../types/oci';
import {MEDIATYPE_IMAGE_INDEX_V1, MEDIATYPE_IMAGE_MANIFEST_V1} from '../types/oci/mediatype';
import {Archive} from '../types/oci/oci';
import {BuildRecord} from '../types/buildx/buildx';
import {Descriptor} from '../types/oci/descriptor';
import {MEDIATYPE_PAYLOAD as MEDIATYPE_INTOTO_PAYLOAD, MEDIATYPE_PREDICATE} from '../types/intoto/intoto';
import {ProvenancePredicate} from '../types/intoto/slsa_provenance/v0.2/provenance';
import {ANNOTATION_REF_KEY, MEDIATYPE_HISTORY_RECORD_V0, MEDIATYPE_SOLVE_STATUS_V0} from '../types/buildkit/buildkit';
import {SolveStatus} from '../types/buildkit/client';

export interface HistoryOpts {
buildx?: Buildx;
Expand All @@ -42,6 +52,80 @@ export class History {
this.buildx = opts?.buildx || new Buildx();
}

public static async load(opts: LoadRecordOpts): Promise<Record<string, BuildRecord>> {
const ociArchive = await OCI.loadArchive({
file: opts.file
});
return History.readRecords(ociArchive.root.index, ociArchive);
}

private static readRecords(index: Index, archive: Archive): Record<string, BuildRecord> {
const res: Record<string, BuildRecord> = {};
index.manifests.forEach(desc => {
switch (desc.mediaType) {
case MEDIATYPE_IMAGE_MANIFEST_V1: {
const record = History.readRecord(desc, archive);
res[record.Ref] = record;
break;
}
case MEDIATYPE_IMAGE_INDEX_V1: {
if (!Object.prototype.hasOwnProperty.call(archive.indexes, desc.digest)) {
throw new Error(`Missing index: ${desc.digest}`);
}
const records = History.readRecords(archive.indexes[desc.digest], archive);
for (const ref in records) {
if (!Object.prototype.hasOwnProperty.call(records, ref)) {
continue;
}
res[ref] = records[ref];
}
break;
}
}
});
return res;
}

private static readRecord(desc: Descriptor, archive: Archive): BuildRecord {
if (!Object.prototype.hasOwnProperty.call(archive.manifests, desc.digest)) {
throw new Error(`Missing manifest: ${desc.digest}`);
}
const manifest = archive.manifests[desc.digest];
if (manifest.config.mediaType !== MEDIATYPE_HISTORY_RECORD_V0) {
throw new Error(`Unexpected config media type: ${manifest.config.mediaType}`);
}
if (!Object.prototype.hasOwnProperty.call(archive.blobs, manifest.config.digest)) {
throw new Error(`Missing config blob: ${manifest.config.digest}`);
}
const record = <BuildRecord>JSON.parse(archive.blobs[manifest.config.digest]);
if (manifest.annotations && ANNOTATION_REF_KEY in manifest.annotations) {
if (record.Ref !== manifest.annotations[ANNOTATION_REF_KEY]) {
throw new Error(`Mismatched ref ${desc.digest}: ${record.Ref} != ${manifest.annotations[ANNOTATION_REF_KEY]}`);
}
}
manifest.layers.forEach(layer => {
switch (layer.mediaType) {
case MEDIATYPE_SOLVE_STATUS_V0: {
if (!Object.prototype.hasOwnProperty.call(archive.blobs, layer.digest)) {
throw new Error(`Missing blob: ${layer.digest}`);
}
record.solveStatus = <SolveStatus>JSON.parse(archive.blobs[layer.digest]);
break;
}
case MEDIATYPE_INTOTO_PAYLOAD: {
if (!Object.prototype.hasOwnProperty.call(archive.blobs, layer.digest)) {
throw new Error(`Missing blob: ${layer.digest}`);
}
if (layer.annotations && MEDIATYPE_PREDICATE in layer.annotations && layer.annotations[MEDIATYPE_PREDICATE].startsWith('https://slsa.dev/provenance/')) {
record.provenance = <ProvenancePredicate>JSON.parse(archive.blobs[layer.digest]);
}
break;
}
}
});
return record;
}

public async export(opts: ExportRecordOpts): Promise<ExportRecordResponse> {
if (os.platform() === 'win32') {
throw new Error('Exporting a build record is currently not supported on Windows');
Expand Down
24 changes: 24 additions & 0 deletions src/types/buildkit/buildkit.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/**
* Copyright 2024 actions-toolkit authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

export const ANNOTATION_REF_KEY = 'vnd.buildkit.history.reference';

export const MEDIATYPE_SOLVE_STATUS_V0 = 'application/vnd.buildkit.solvestatus.v0';

export const MEDIATYPE_HISTORY_RECORD_V0 = 'application/vnd.buildkit.historyrecord.v0';

// https://github.com/moby/buildkit/blob/593aad1ea909b978d2c54fef2a138c6d3a9107e6/solver/llbsolver/history.go#L672
export const MEDIATYPE_STATUS_V0 = 'application/vnd.buildkit.status.v0';
78 changes: 78 additions & 0 deletions src/types/buildkit/client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
/**
* Copyright 2024 actions-toolkit authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import {Digest} from '../oci/digest';
import {ProgressGroup, Range, SourceInfo} from './ops';

// https://github.com/moby/buildkit/blob/593aad1ea909b978d2c54fef2a138c6d3a9107e6/client/graph.go#L10-L19
export interface Vertex {
digest?: Digest;
inputs?: Array<Digest>;
name?: string;
started?: Date;
completed?: Date;
cached?: boolean;
error?: string;
progressGroup?: ProgressGroup;
}

// https://github.com/moby/buildkit/blob/593aad1ea909b978d2c54fef2a138c6d3a9107e6/client/graph.go#L21-L30
export interface VertexStatus {
id: string;
vertex?: Digest;
name?: string;
total?: number;
current: number;
timestamp?: Date;
started?: Date;
completed?: Date;
}

// https://github.com/moby/buildkit/blob/593aad1ea909b978d2c54fef2a138c6d3a9107e6/client/graph.go#L32-L37
export interface VertexLog {
vertex?: Digest;
stream?: number;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
data: any;
timestamp: Date;
}

// https://github.com/moby/buildkit/blob/593aad1ea909b978d2c54fef2a138c6d3a9107e6/client/graph.go#L39-L48
export interface VertexWarning {
vertex?: Digest;
level?: number;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
short?: any;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
detail?: Array<any>;
url?: string;

sourceInfo?: SourceInfo;
range?: Range[];
}

// https://github.com/moby/buildkit/blob/593aad1ea909b978d2c54fef2a138c6d3a9107e6/client/graph.go#L50-L55
export interface SolveStatus {
vertexes?: Vertex[];
statuses?: VertexStatus[];
logs?: VertexLog[];
warnings?: VertexWarning[];
}

// https://github.com/moby/buildkit/blob/593aad1ea909b978d2c54fef2a138c6d3a9107e6/client/graph.go#L57-L60
export interface SolveResponse {
exporterResponse: Record<string, string>;
}
108 changes: 108 additions & 0 deletions src/types/buildkit/control.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
/**
* Copyright 2024 actions-toolkit authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import {Descriptor} from '../oci/descriptor';
import {Digest} from '../oci/digest';
import {ProgressGroup, Range, SourceInfo} from './ops';
import {RpcStatus} from './rpc';

// https://github.com/moby/buildkit/blob/593aad1ea909b978d2c54fef2a138c6d3a9107e6/api/services/control/control.pb.go#L1504-L1525
export interface BuildHistoryRecord {
Ref: string;
Frontend: string;
FrontendAttrs: Record<string, string>;
Exporters: Exporter[];
error?: RpcStatus;
CreatedAt?: Date;
CompletedAt?: Date;
logs?: Descriptor;
ExporterResponse: Record<string, string>;
Result?: BuildResultInfo;
Results: Record<string, BuildResultInfo>;
Generation: number;
trace?: Descriptor;
pinned: boolean;
numCachedSteps: number;
numTotalSteps: number;
numCompletedSteps: number;
}

// https://github.com/moby/buildkit/blob/593aad1ea909b978d2c54fef2a138c6d3a9107e6/api/services/control/control.pb.go#L1909-L1917
export interface Exporter {
Type: string;
Attrs: Record<string, string>;
}

// https://github.com/moby/buildkit/blob/593aad1ea909b978d2c54fef2a138c6d3a9107e6/api/services/control/control.pb.go#L1845-L1852
export interface BuildResultInfo {
ResultDeprecated?: Descriptor;
Attestations?: Descriptor[];
Results?: Record<number, Descriptor>;
}

// https://github.com/moby/buildkit/blob/593aad1ea909b978d2c54fef2a138c6d3a9107e6/api/services/control/control.pb.go#L751-L759
export interface StatusResponse {
vertexes?: Vertex[];
statuses?: VertexStatus[];
logs?: VertexLog[];
warnings?: VertexWarning[];
}

// https://github.com/moby/buildkit/blob/593aad1ea909b978d2c54fef2a138c6d3a9107e6/api/services/control/control.pb.go#L822-L834
export interface Vertex {
digest: Digest;
inputs: Digest[];
name?: string;
cached?: boolean;
started?: Date;
completed?: Date;
error?: string;
progressGroup?: ProgressGroup;
}

// https://github.com/moby/buildkit/blob/593aad1ea909b978d2c54fef2a138c6d3a9107e6/api/services/control/control.pb.go#L911-L923
export interface VertexStatus {
ID?: string;
vertex: Digest;
name?: string;
current?: number;
total?: number;
timestamp: Date;
started?: Date;
completed?: Date;
}

// https://github.com/moby/buildkit/blob/593aad1ea909b978d2c54fef2a138c6d3a9107e6/api/services/control/control.pb.go#L1007-L1015
export interface VertexLog {
vertex: Digest;
timestamp: Date;
stream?: number;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
msg?: any;
}

// https://github.com/moby/buildkit/blob/593aad1ea909b978d2c54fef2a138c6d3a9107e6/api/services/control/control.pb.go#L1071-L1082
export interface VertexWarning {
vertex: Digest;
level?: number;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
short?: any;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
detail?: any[];
url?: string;
info?: SourceInfo;
ranges?: Range[];
}
Loading

0 comments on commit 78d87a9

Please sign in to comment.