@@ -26,8 +26,18 @@ import {Context} from '../context';
26
26
import { Docker } from '../docker/docker' ;
27
27
import { Exec } from '../exec' ;
28
28
import { GitHub } from '../github' ;
29
+ import { OCI } from '../oci/oci' ;
29
30
30
- import { ExportRecordOpts , ExportRecordResponse , Summaries } from '../types/buildx/history' ;
31
+ import { ExportRecordOpts , ExportRecordResponse , LoadRecordOpts , Summaries } from '../types/buildx/history' ;
32
+ import { Index } from '../types/oci' ;
33
+ import { MEDIATYPE_IMAGE_INDEX_V1 , MEDIATYPE_IMAGE_MANIFEST_V1 } from '../types/oci/mediatype' ;
34
+ import { Archive } from '../types/oci/oci' ;
35
+ import { BuildRecord } from '../types/buildx/buildx' ;
36
+ import { Descriptor } from '../types/oci/descriptor' ;
37
+ import { MEDIATYPE_PAYLOAD as MEDIATYPE_INTOTO_PAYLOAD , MEDIATYPE_PREDICATE } from '../types/intoto/intoto' ;
38
+ import { ProvenancePredicate } from '../types/intoto/slsa_provenance/v0.2/provenance' ;
39
+ import { ANNOTATION_REF_KEY , MEDIATYPE_HISTORY_RECORD_V0 , MEDIATYPE_SOLVE_STATUS_V0 } from '../types/buildkit/buildkit' ;
40
+ import { SolveStatus } from '../types/buildkit/client' ;
31
41
32
42
export interface HistoryOpts {
33
43
buildx ?: Buildx ;
@@ -42,6 +52,80 @@ export class History {
42
52
this . buildx = opts ?. buildx || new Buildx ( ) ;
43
53
}
44
54
55
+ public static async load ( opts : LoadRecordOpts ) : Promise < Record < string , BuildRecord > > {
56
+ const ociArchive = await OCI . loadArchive ( {
57
+ file : opts . file
58
+ } ) ;
59
+ return History . readRecords ( ociArchive . root . index , ociArchive ) ;
60
+ }
61
+
62
+ private static readRecords ( index : Index , archive : Archive ) : Record < string , BuildRecord > {
63
+ const res : Record < string , BuildRecord > = { } ;
64
+ index . manifests . forEach ( desc => {
65
+ switch ( desc . mediaType ) {
66
+ case MEDIATYPE_IMAGE_MANIFEST_V1 : {
67
+ const record = History . readRecord ( desc , archive ) ;
68
+ res [ record . Ref ] = record ;
69
+ break ;
70
+ }
71
+ case MEDIATYPE_IMAGE_INDEX_V1 : {
72
+ if ( ! Object . prototype . hasOwnProperty . call ( archive . indexes , desc . digest ) ) {
73
+ throw new Error ( `Missing index: ${ desc . digest } ` ) ;
74
+ }
75
+ const records = History . readRecords ( archive . indexes [ desc . digest ] , archive ) ;
76
+ for ( const ref in records ) {
77
+ if ( ! Object . prototype . hasOwnProperty . call ( records , ref ) ) {
78
+ continue ;
79
+ }
80
+ res [ ref ] = records [ ref ] ;
81
+ }
82
+ break ;
83
+ }
84
+ }
85
+ } ) ;
86
+ return res ;
87
+ }
88
+
89
+ private static readRecord ( desc : Descriptor , archive : Archive ) : BuildRecord {
90
+ if ( ! Object . prototype . hasOwnProperty . call ( archive . manifests , desc . digest ) ) {
91
+ throw new Error ( `Missing manifest: ${ desc . digest } ` ) ;
92
+ }
93
+ const manifest = archive . manifests [ desc . digest ] ;
94
+ if ( manifest . config . mediaType !== MEDIATYPE_HISTORY_RECORD_V0 ) {
95
+ throw new Error ( `Unexpected config media type: ${ manifest . config . mediaType } ` ) ;
96
+ }
97
+ if ( ! Object . prototype . hasOwnProperty . call ( archive . blobs , manifest . config . digest ) ) {
98
+ throw new Error ( `Missing config blob: ${ manifest . config . digest } ` ) ;
99
+ }
100
+ const record = < BuildRecord > JSON . parse ( archive . blobs [ manifest . config . digest ] ) ;
101
+ if ( manifest . annotations && ANNOTATION_REF_KEY in manifest . annotations ) {
102
+ if ( record . Ref !== manifest . annotations [ ANNOTATION_REF_KEY ] ) {
103
+ throw new Error ( `Mismatched ref ${ desc . digest } : ${ record . Ref } != ${ manifest . annotations [ ANNOTATION_REF_KEY ] } ` ) ;
104
+ }
105
+ }
106
+ manifest . layers . forEach ( layer => {
107
+ switch ( layer . mediaType ) {
108
+ case MEDIATYPE_SOLVE_STATUS_V0 : {
109
+ if ( ! Object . prototype . hasOwnProperty . call ( archive . blobs , layer . digest ) ) {
110
+ throw new Error ( `Missing blob: ${ layer . digest } ` ) ;
111
+ }
112
+ record . solveStatus = < SolveStatus > JSON . parse ( archive . blobs [ layer . digest ] ) ;
113
+ break ;
114
+ }
115
+ case MEDIATYPE_INTOTO_PAYLOAD : {
116
+ if ( ! Object . prototype . hasOwnProperty . call ( archive . blobs , layer . digest ) ) {
117
+ throw new Error ( `Missing blob: ${ layer . digest } ` ) ;
118
+ }
119
+ if ( layer . annotations && MEDIATYPE_PREDICATE in layer . annotations && layer . annotations [ MEDIATYPE_PREDICATE ] . startsWith ( 'https://slsa.dev/provenance/' ) ) {
120
+ record . provenance = < ProvenancePredicate > JSON . parse ( archive . blobs [ layer . digest ] ) ;
121
+ }
122
+ break ;
123
+ }
124
+ }
125
+ } ) ;
126
+ return record ;
127
+ }
128
+
45
129
public async export ( opts : ExportRecordOpts ) : Promise < ExportRecordResponse > {
46
130
if ( os . platform ( ) === 'win32' ) {
47
131
throw new Error ( 'Exporting a build record is currently not supported on Windows' ) ;
0 commit comments