diff --git a/README.md b/README.md index ff2feb31317f..25e2fb01eed3 100644 --- a/README.md +++ b/README.md @@ -390,6 +390,7 @@ buildctl build ... \ * `compression=`: choose compression type for layers newly created and cached, gzip is default value. estargz and zstd should be used with `oci-mediatypes=true` * `compression-level=`: choose compression level for gzip, estargz (0-9) and zstd (0-22) * `force-compression=true`: forcibly apply `compression` option to all layers +* `ignore-error=`: specify if error is ignored in case cache export fails (default: `false`) `--import-cache` options: * `type=registry` @@ -415,6 +416,7 @@ The directory layout conforms to OCI Image Spec v1.0. * `compression=`: choose compression type for layers newly created and cached, gzip is default value. estargz and zstd should be used with `oci-mediatypes=true`. * `compression-level=`: compression level for gzip, estargz (0-9) and zstd (0-22) * `force-compression=true`: forcibly apply `compression` option to all layers +* `ignore-error=`: specify if error is ignored in case cache export fails (default: `false`) `--import-cache` options: * `type=local` @@ -449,6 +451,7 @@ in your workflow to expose the runtime. * `min`: only export layers for the resulting image * `max`: export all the layers of all intermediate steps * `scope=`: which scope cache object belongs to (default `buildkit`) +* `ignore-error=`: specify if error is ignored in case cache export fails (default: `false`) `--import-cache` options: * `type=gha` @@ -496,6 +499,7 @@ Others options are: * `prefix=`: set global prefix to store / read files on s3 (default: empty) * `name=`: specify name of the manifest to use (default `buildkit`) * Multiple manifest names can be specified at the same time, separated by `;`. The standard use case is to use the git sha1 as name, and the branch name as duplicate, and load both with 2 `import-cache` commands. +* `ignore-error=`: specify if error is ignored in case cache export fails (default: `false`) `--import-cache` options: * `type=s3` @@ -540,6 +544,7 @@ There are 2 options supported for Azure Blob Storage authentication: * `prefix=`: set global prefix to store / read files on the Azure Blob Storage container (``) (default: empty) * `name=`: specify name of the manifest to use (default: `buildkit`) * Multiple manifest names can be specified at the same time, separated by `;`. The standard use case is to use the git sha1 as name, and the branch name as duplicate, and load both with 2 `import-cache` commands. +* `ignore-error=`: specify if error is ignored in case cache export fails (default: `false`) `--import-cache` options: * `type=azblob` diff --git a/api/services/control/control.pb.go b/api/services/control/control.pb.go index 625b554a744d..db97db6141ec 100644 --- a/api/services/control/control.pb.go +++ b/api/services/control/control.pb.go @@ -1505,6 +1505,8 @@ type BuildHistoryRecord struct { Generation int32 `protobuf:"varint,12,opt,name=Generation,proto3" json:"Generation,omitempty"` Trace *Descriptor `protobuf:"bytes,13,opt,name=trace,proto3" json:"trace,omitempty"` Pinned bool `protobuf:"varint,14,opt,name=pinned,proto3" json:"pinned,omitempty"` + NumCachedSteps int32 `protobuf:"varint,15,opt,name=numCachedSteps,proto3" json:"numCachedSteps,omitempty"` + NumTotalSteps int32 `protobuf:"varint,16,opt,name=numTotalSteps,proto3" json:"numTotalSteps,omitempty"` XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_unrecognized []byte `json:"-"` XXX_sizecache int32 `json:"-"` @@ -1641,6 +1643,20 @@ func (m *BuildHistoryRecord) GetPinned() bool { return false } +func (m *BuildHistoryRecord) GetNumCachedSteps() int32 { + if m != nil { + return m.NumCachedSteps + } + return 0 +} + +func (m *BuildHistoryRecord) GetNumTotalSteps() int32 { + if m != nil { + return m.NumTotalSteps + } + return 0 +} + type UpdateBuildHistoryRequest struct { Ref string `protobuf:"bytes,1,opt,name=Ref,proto3" json:"Ref,omitempty"` Pinned bool `protobuf:"varint,2,opt,name=Pinned,proto3" json:"Pinned,omitempty"` @@ -1962,146 +1978,148 @@ func init() { func init() { proto.RegisterFile("control.proto", fileDescriptor_0c5120591600887d) } var fileDescriptor_0c5120591600887d = []byte{ - // 2213 bytes of a gzipped FileDescriptorProto + // 2246 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x59, 0xcd, 0x6e, 0x1b, 0xc9, - 0x11, 0xf6, 0x90, 0x12, 0x7f, 0x8a, 0x94, 0x56, 0x6e, 0x7b, 0x8d, 0xc9, 0xc4, 0x2b, 0xc9, 0xb3, - 0x36, 0x22, 0x38, 0xf6, 0x50, 0xab, 0xac, 0x63, 0xaf, 0x9c, 0x38, 0x16, 0x45, 0x66, 0x2d, 0xc7, - 0x86, 0xb5, 0x2d, 0x79, 0x0d, 0x2c, 0xe0, 0x04, 0x23, 0xb2, 0x45, 0x0f, 0x34, 0x9c, 0x99, 0x74, - 0x37, 0xb5, 0xe6, 0x3e, 0x40, 0x80, 0xcd, 0x21, 0xc8, 0x25, 0xc8, 0x25, 0xf7, 0x9c, 0x72, 0xce, - 0x13, 0x04, 0xf0, 0x31, 0xe7, 0x3d, 0x38, 0x81, 0x1f, 0x20, 0xc8, 0x31, 0x97, 0x00, 0x8b, 0xfe, - 0x19, 0xb2, 0x49, 0x0e, 0x25, 0xca, 0xf6, 0x89, 0x5d, 0xdd, 0x55, 0x35, 0x55, 0xd5, 0xd5, 0xd5, - 0x5f, 0x35, 0x61, 0xa1, 0x15, 0x47, 0x9c, 0xc6, 0xa1, 0x97, 0xd0, 0x98, 0xc7, 0x68, 0xa9, 0x1b, - 0x1f, 0xf4, 0xbd, 0x83, 0x5e, 0x10, 0xb6, 0x8f, 0x02, 0xee, 0x1d, 0x7f, 0xe2, 0xdc, 0xec, 0x04, - 0xfc, 0x45, 0xef, 0xc0, 0x6b, 0xc5, 0xdd, 0x5a, 0x27, 0xee, 0xc4, 0x35, 0xc9, 0x78, 0xd0, 0x3b, - 0x94, 0x94, 0x24, 0xe4, 0x48, 0x29, 0x70, 0x56, 0x3a, 0x71, 0xdc, 0x09, 0xc9, 0x90, 0x8b, 0x07, - 0x5d, 0xc2, 0xb8, 0xdf, 0x4d, 0x34, 0xc3, 0x0d, 0x43, 0x9f, 0xf8, 0x58, 0x2d, 0xfd, 0x58, 0x8d, - 0xc5, 0xe1, 0x31, 0xa1, 0xb5, 0xe4, 0xa0, 0x16, 0x27, 0x4c, 0x73, 0xd7, 0xa6, 0x72, 0xfb, 0x49, - 0x50, 0xe3, 0xfd, 0x84, 0xb0, 0xda, 0xd7, 0x31, 0x3d, 0x22, 0x54, 0x0b, 0x6c, 0x8c, 0x9b, 0xab, - 0xec, 0xf1, 0x93, 0x80, 0xe9, 0x61, 0x8d, 0x26, 0xad, 0x1a, 0xe3, 0x3e, 0xef, 0xa5, 0x1f, 0xb9, - 0x75, 0x82, 0x49, 0x3d, 0xda, 0x22, 0x49, 0x1c, 0x06, 0xad, 0xbe, 0x30, 0x4c, 0x8d, 0x94, 0x98, - 0xfb, 0x3b, 0x0b, 0xaa, 0xbb, 0xb4, 0x17, 0x11, 0x4c, 0x7e, 0xdb, 0x23, 0x8c, 0xa3, 0x4b, 0x50, - 0x38, 0x0c, 0x42, 0x4e, 0xa8, 0x6d, 0xad, 0xe6, 0xd7, 0xca, 0x58, 0x53, 0x68, 0x09, 0xf2, 0x7e, - 0x18, 0xda, 0xb9, 0x55, 0x6b, 0xad, 0x84, 0xc5, 0x10, 0xad, 0x41, 0xf5, 0x88, 0x90, 0xa4, 0xd1, - 0xa3, 0x3e, 0x0f, 0xe2, 0xc8, 0xce, 0xaf, 0x5a, 0x6b, 0xf9, 0xfa, 0xdc, 0xab, 0xd7, 0x2b, 0x16, - 0x1e, 0x59, 0x41, 0x2e, 0x94, 0x05, 0x5d, 0xef, 0x73, 0xc2, 0xec, 0x39, 0x83, 0x6d, 0x38, 0xed, - 0x5e, 0x87, 0xa5, 0x46, 0xc0, 0x8e, 0x9e, 0x32, 0xbf, 0x73, 0x9a, 0x2d, 0xee, 0x43, 0x38, 0x6f, - 0xf0, 0xb2, 0x24, 0x8e, 0x18, 0x41, 0xb7, 0xa0, 0x40, 0x49, 0x2b, 0xa6, 0x6d, 0xc9, 0x5c, 0xd9, - 0xf8, 0xc8, 0x1b, 0x4f, 0x03, 0x4f, 0x0b, 0x08, 0x26, 0xac, 0x99, 0xdd, 0x3f, 0xe7, 0xa1, 0x62, - 0xcc, 0xa3, 0x45, 0xc8, 0xed, 0x34, 0x6c, 0x6b, 0xd5, 0x5a, 0x2b, 0xe3, 0xdc, 0x4e, 0x03, 0xd9, - 0x50, 0x7c, 0xdc, 0xe3, 0xfe, 0x41, 0x48, 0xb4, 0xef, 0x29, 0x89, 0x2e, 0xc2, 0xfc, 0x4e, 0xf4, - 0x94, 0x11, 0xe9, 0x78, 0x09, 0x2b, 0x02, 0x21, 0x98, 0xdb, 0x0b, 0xbe, 0x21, 0xca, 0x4d, 0x2c, - 0xc7, 0xc8, 0x81, 0xc2, 0xae, 0x4f, 0x49, 0xc4, 0xed, 0x79, 0xa1, 0xb7, 0x9e, 0xb3, 0x2d, 0xac, - 0x67, 0x50, 0x1d, 0xca, 0xdb, 0x94, 0xf8, 0x9c, 0xb4, 0xb7, 0xb8, 0x5d, 0x58, 0xb5, 0xd6, 0x2a, - 0x1b, 0x8e, 0xa7, 0x36, 0xd9, 0x4b, 0xf3, 0xcf, 0xdb, 0x4f, 0xf3, 0xaf, 0x5e, 0x7a, 0xf5, 0x7a, - 0xe5, 0xdc, 0x1f, 0xff, 0x25, 0x62, 0x37, 0x10, 0x43, 0xf7, 0x01, 0x1e, 0xf9, 0x8c, 0x3f, 0x65, - 0x52, 0x49, 0xf1, 0x54, 0x25, 0x73, 0x52, 0x81, 0x21, 0x83, 0x96, 0x01, 0x64, 0x10, 0xb6, 0xe3, - 0x5e, 0xc4, 0xed, 0x92, 0xb4, 0xdd, 0x98, 0x41, 0xab, 0x50, 0x69, 0x10, 0xd6, 0xa2, 0x41, 0x22, - 0xb7, 0xba, 0x2c, 0xc3, 0x63, 0x4e, 0x09, 0x0d, 0x2a, 0x82, 0xfb, 0xfd, 0x84, 0xd8, 0x20, 0x19, - 0x8c, 0x19, 0xb1, 0x97, 0x7b, 0x2f, 0x7c, 0x4a, 0xda, 0x76, 0x45, 0x86, 0x4b, 0x53, 0x22, 0xbe, - 0x2a, 0x12, 0xcc, 0xae, 0xca, 0x4d, 0x4e, 0x49, 0xf7, 0xf7, 0x45, 0xa8, 0xee, 0x89, 0xe3, 0x94, - 0xa6, 0xc3, 0x12, 0xe4, 0x31, 0x39, 0xd4, 0x7b, 0x23, 0x86, 0xc8, 0x03, 0x68, 0x90, 0xc3, 0x20, - 0x0a, 0xa4, 0x55, 0x39, 0xe9, 0xf8, 0xa2, 0x97, 0x1c, 0x78, 0xc3, 0x59, 0x6c, 0x70, 0x20, 0x07, - 0x4a, 0xcd, 0x97, 0x49, 0x4c, 0x45, 0x4a, 0xe5, 0xa5, 0x9a, 0x01, 0x8d, 0x9e, 0xc1, 0x42, 0x3a, - 0xde, 0xe2, 0x9c, 0x8a, 0x44, 0x15, 0x69, 0xf4, 0xc9, 0x64, 0x1a, 0x99, 0x46, 0x79, 0x23, 0x32, - 0xcd, 0x88, 0xd3, 0x3e, 0x1e, 0xd5, 0x23, 0x3c, 0xdc, 0x23, 0x8c, 0x09, 0x0b, 0xe5, 0xf6, 0xe3, - 0x94, 0x14, 0xe6, 0xfc, 0x92, 0xc6, 0x11, 0x27, 0x51, 0x5b, 0x6e, 0x7d, 0x19, 0x0f, 0x68, 0x61, - 0x4e, 0x3a, 0x56, 0xe6, 0x14, 0x67, 0x32, 0x67, 0x44, 0x46, 0x9b, 0x33, 0x32, 0x87, 0x36, 0x61, - 0x7e, 0xdb, 0x6f, 0xbd, 0x20, 0x72, 0x97, 0x2b, 0x1b, 0xcb, 0x93, 0x0a, 0xe5, 0xf2, 0x13, 0xb9, - 0xad, 0x4c, 0x1e, 0xd4, 0x73, 0x58, 0x89, 0xa0, 0x5f, 0x43, 0xb5, 0x19, 0xf1, 0x80, 0x87, 0xa4, - 0x2b, 0x77, 0xac, 0x2c, 0x76, 0xac, 0xbe, 0xf9, 0xdd, 0xeb, 0x95, 0x9f, 0x4e, 0x2d, 0x3f, 0x3d, - 0x1e, 0x84, 0x35, 0x62, 0x48, 0x79, 0x86, 0x0a, 0x3c, 0xa2, 0x0f, 0x7d, 0x05, 0x8b, 0xa9, 0xb1, - 0x3b, 0x51, 0xd2, 0xe3, 0xcc, 0x06, 0xe9, 0xf5, 0xc6, 0x8c, 0x5e, 0x2b, 0x21, 0xe5, 0xf6, 0x98, - 0x26, 0x11, 0xec, 0x9d, 0x88, 0x13, 0x1a, 0xf9, 0xa1, 0x4e, 0xc1, 0x01, 0x8d, 0x76, 0x44, 0xa6, - 0x89, 0x2a, 0xb9, 0x2b, 0x6b, 0xa3, 0x5d, 0x95, 0xa1, 0xb9, 0x36, 0xf9, 0x55, 0xb3, 0x96, 0x7a, - 0x8a, 0x19, 0x8f, 0x88, 0x3a, 0xf7, 0x01, 0x4d, 0xa6, 0x84, 0x48, 0xdd, 0x23, 0xd2, 0x4f, 0x53, - 0xf7, 0x88, 0xf4, 0x45, 0xf5, 0x38, 0xf6, 0xc3, 0x9e, 0xaa, 0x2a, 0x65, 0xac, 0x88, 0xcd, 0xdc, - 0x1d, 0x4b, 0x68, 0x98, 0xdc, 0xc5, 0x33, 0x69, 0xf8, 0x02, 0x2e, 0x64, 0x44, 0x24, 0x43, 0xc5, - 0x55, 0x53, 0xc5, 0xe4, 0xd1, 0x19, 0xaa, 0x74, 0xff, 0x96, 0x87, 0xaa, 0x99, 0x17, 0x68, 0x1d, - 0x2e, 0x28, 0x3f, 0x31, 0x39, 0x6c, 0x90, 0x84, 0x92, 0x96, 0x28, 0x46, 0x5a, 0x79, 0xd6, 0x12, - 0xda, 0x80, 0x8b, 0x3b, 0x5d, 0x3d, 0xcd, 0x0c, 0x91, 0x9c, 0x3c, 0xf6, 0x99, 0x6b, 0x28, 0x86, - 0x0f, 0x95, 0x2a, 0x19, 0x09, 0x43, 0x28, 0x2f, 0xf3, 0xe2, 0xb3, 0x93, 0x93, 0xd7, 0xcb, 0x94, - 0x55, 0xe9, 0x91, 0xad, 0x17, 0xfd, 0x1c, 0x8a, 0x6a, 0x21, 0x3d, 0xff, 0x1f, 0x9f, 0xfc, 0x09, - 0xa5, 0x2c, 0x95, 0x11, 0xe2, 0xca, 0x0f, 0x66, 0xcf, 0x9f, 0x41, 0x5c, 0xcb, 0x38, 0x0f, 0xc0, - 0x99, 0x6e, 0xf2, 0x59, 0x52, 0xc0, 0xfd, 0xab, 0x05, 0xe7, 0x27, 0x3e, 0x24, 0x2e, 0x27, 0x59, - 0x9e, 0x95, 0x0a, 0x39, 0x46, 0x0d, 0x98, 0x57, 0x05, 0x26, 0x27, 0x0d, 0xf6, 0x66, 0x30, 0xd8, - 0x33, 0xaa, 0x8b, 0x12, 0x76, 0xee, 0x00, 0xbc, 0x5d, 0xb2, 0xba, 0x7f, 0xb7, 0x60, 0x41, 0x1f, - 0x66, 0x7d, 0x93, 0xfb, 0xb0, 0x94, 0x1e, 0xa1, 0x74, 0x4e, 0xdf, 0xe9, 0xb7, 0xa6, 0xd6, 0x01, - 0xc5, 0xe6, 0x8d, 0xcb, 0x29, 0x1b, 0x27, 0xd4, 0x39, 0xdb, 0x69, 0x5e, 0x8d, 0xb1, 0x9e, 0xc9, - 0xf2, 0x2b, 0xb0, 0xb0, 0x27, 0x21, 0xd8, 0xd4, 0x0b, 0xca, 0xfd, 0xaf, 0x05, 0x8b, 0x29, 0x8f, - 0xf6, 0xee, 0x53, 0x28, 0x1d, 0x13, 0xca, 0xc9, 0x4b, 0xc2, 0xb4, 0x57, 0xf6, 0xa4, 0x57, 0x5f, - 0x4a, 0x0e, 0x3c, 0xe0, 0x44, 0x9b, 0x50, 0x52, 0x70, 0x8f, 0xa4, 0x1b, 0xb5, 0x3c, 0x4d, 0x4a, - 0x7f, 0x6f, 0xc0, 0x8f, 0x6a, 0x30, 0x17, 0xc6, 0x1d, 0xa6, 0xcf, 0xcc, 0x0f, 0xa7, 0xc9, 0x3d, - 0x8a, 0x3b, 0x58, 0x32, 0xa2, 0xbb, 0x50, 0xfa, 0xda, 0xa7, 0x51, 0x10, 0x75, 0xd2, 0x53, 0xb0, - 0x32, 0x4d, 0xe8, 0x99, 0xe2, 0xc3, 0x03, 0x01, 0x01, 0xa8, 0x0a, 0x6a, 0x0d, 0x3d, 0x84, 0x42, - 0x3b, 0xe8, 0x10, 0xc6, 0x55, 0x48, 0xea, 0x1b, 0xe2, 0x2e, 0xf9, 0xee, 0xf5, 0xca, 0x75, 0xe3, - 0xb2, 0x88, 0x13, 0x12, 0x09, 0xf8, 0xee, 0x07, 0x11, 0xa1, 0x02, 0xde, 0xde, 0x54, 0x22, 0x5e, - 0x43, 0xfe, 0x60, 0xad, 0x41, 0xe8, 0x0a, 0xd4, 0x95, 0x20, 0xeb, 0xc5, 0xdb, 0xe9, 0x52, 0x1a, - 0xc4, 0x31, 0x88, 0xfc, 0x2e, 0xd1, 0x10, 0x40, 0x8e, 0x05, 0x3e, 0x69, 0x89, 0x3c, 0x6f, 0x4b, - 0xe4, 0x56, 0xc2, 0x9a, 0x42, 0x9b, 0x50, 0x64, 0xdc, 0xa7, 0xa2, 0xe6, 0xcc, 0xcf, 0x08, 0xac, - 0x52, 0x01, 0x74, 0x0f, 0xca, 0xad, 0xb8, 0x9b, 0x84, 0x44, 0x48, 0x17, 0x66, 0x94, 0x1e, 0x8a, - 0x88, 0xd4, 0x23, 0x94, 0xc6, 0x54, 0x42, 0xba, 0x32, 0x56, 0x04, 0xba, 0x0d, 0x0b, 0x09, 0x8d, - 0x3b, 0x94, 0x30, 0xf6, 0x39, 0x8d, 0x7b, 0x89, 0xbe, 0xc8, 0xcf, 0x8b, 0xe2, 0xbd, 0x6b, 0x2e, - 0xe0, 0x51, 0x3e, 0xf7, 0x3f, 0x39, 0xa8, 0x9a, 0x29, 0x32, 0x81, 0x75, 0x1f, 0x42, 0x41, 0x25, - 0x9c, 0xca, 0xf5, 0xb7, 0x8b, 0xb1, 0xd2, 0x90, 0x19, 0x63, 0x1b, 0x8a, 0xad, 0x1e, 0x95, 0x40, - 0x58, 0xc1, 0xe3, 0x94, 0x14, 0x9e, 0xf2, 0x98, 0xfb, 0xa1, 0x8c, 0x71, 0x1e, 0x2b, 0x42, 0x60, - 0xe3, 0x41, 0xe7, 0x75, 0x36, 0x6c, 0x3c, 0x10, 0x33, 0xf7, 0xaf, 0xf8, 0x4e, 0xfb, 0x57, 0x3a, - 0xf3, 0xfe, 0xb9, 0xff, 0xb0, 0xa0, 0x3c, 0x38, 0x5b, 0x46, 0x74, 0xad, 0x77, 0x8e, 0xee, 0x48, - 0x64, 0x72, 0x6f, 0x17, 0x99, 0x4b, 0x50, 0x60, 0x9c, 0x12, 0xbf, 0xab, 0x3a, 0x37, 0xac, 0x29, - 0x51, 0xc5, 0xba, 0xac, 0x23, 0x77, 0xa8, 0x8a, 0xc5, 0xd0, 0xfd, 0x9f, 0x05, 0x0b, 0x23, 0xc7, - 0xfd, 0xbd, 0xfa, 0x72, 0x11, 0xe6, 0x43, 0x72, 0x4c, 0x54, 0x6f, 0x99, 0xc7, 0x8a, 0x10, 0xb3, - 0xec, 0x45, 0x4c, 0xb9, 0x34, 0xae, 0x8a, 0x15, 0x21, 0x6c, 0x6e, 0x13, 0xee, 0x07, 0xa1, 0xac, - 0x4b, 0x55, 0xac, 0x29, 0x61, 0x73, 0x8f, 0x86, 0x1a, 0x5f, 0x8b, 0x21, 0x72, 0x61, 0x2e, 0x88, - 0x0e, 0x63, 0x9d, 0x36, 0x12, 0xd9, 0x28, 0x9c, 0xb6, 0x13, 0x1d, 0xc6, 0x58, 0xae, 0xa1, 0x2b, - 0x50, 0xa0, 0x7e, 0xd4, 0x21, 0x29, 0xb8, 0x2e, 0x0b, 0x2e, 0x2c, 0x66, 0xb0, 0x5e, 0x70, 0x5d, - 0xa8, 0xca, 0xfe, 0xf4, 0x31, 0x61, 0xa2, 0x1b, 0x12, 0x69, 0xdd, 0xf6, 0xb9, 0x2f, 0xdd, 0xae, - 0x62, 0x39, 0x76, 0x6f, 0x00, 0x7a, 0x14, 0x30, 0xfe, 0x4c, 0xb6, 0xf0, 0xec, 0xb4, 0xe6, 0x75, - 0x0f, 0x2e, 0x8c, 0x70, 0xeb, 0x6b, 0xe1, 0x67, 0x63, 0xed, 0xeb, 0xd5, 0xc9, 0x8a, 0x2b, 0x5f, - 0x0a, 0x3c, 0x25, 0x38, 0xd6, 0xc5, 0x2e, 0x40, 0x45, 0xfa, 0xa5, 0xbe, 0xed, 0xfa, 0x50, 0x55, - 0xa4, 0x56, 0xfe, 0x05, 0x7c, 0x90, 0x2a, 0xfa, 0x92, 0x50, 0xd9, 0x8a, 0x58, 0x32, 0x2e, 0x3f, - 0x9a, 0xf6, 0x95, 0xfa, 0x28, 0x3b, 0x1e, 0x97, 0x77, 0x09, 0x5c, 0x90, 0x3c, 0x0f, 0x02, 0xc6, - 0x63, 0xda, 0x4f, 0xbd, 0x5e, 0x06, 0xd8, 0x6a, 0xf1, 0xe0, 0x98, 0x3c, 0x89, 0x42, 0x75, 0x8d, - 0x96, 0xb0, 0x31, 0x93, 0x5e, 0x91, 0xb9, 0x61, 0x0f, 0x77, 0x19, 0xca, 0x4d, 0x9f, 0x86, 0xfd, - 0xe6, 0xcb, 0x80, 0xeb, 0x56, 0x7a, 0x38, 0xe1, 0xfe, 0xc1, 0x82, 0xf3, 0xe6, 0x77, 0x9a, 0xc7, - 0xa2, 0x5c, 0xdc, 0x85, 0x39, 0x9e, 0xe2, 0x98, 0xc5, 0x2c, 0x27, 0x26, 0x44, 0x04, 0xd4, 0xc1, - 0x52, 0xc8, 0x88, 0xb4, 0x3a, 0x38, 0x57, 0x4f, 0x16, 0x1f, 0x8b, 0xf4, 0xff, 0x8b, 0x80, 0x26, - 0x97, 0x33, 0x7a, 0x53, 0xb3, 0xb9, 0xcb, 0x8d, 0x35, 0x77, 0xcf, 0xc7, 0x9b, 0x3b, 0x75, 0x35, - 0xdf, 0x9e, 0xc5, 0x92, 0x19, 0x5a, 0xbc, 0x3b, 0x50, 0x4e, 0xd1, 0x4d, 0x7a, 0x81, 0x3b, 0x93, - 0xaa, 0x07, 0x00, 0x68, 0xc8, 0x8c, 0xd6, 0xd2, 0x1b, 0x47, 0xdd, 0x75, 0x28, 0xad, 0x29, 0x34, - 0x69, 0x79, 0x1a, 0x57, 0xe8, 0x5b, 0xe8, 0xde, 0xd9, 0xde, 0x2d, 0xe6, 0xc6, 0xdf, 0x2c, 0xea, - 0x50, 0xd9, 0x4e, 0x0b, 0xe5, 0x19, 0x1e, 0x2d, 0x4c, 0x21, 0xb4, 0xae, 0x81, 0x8d, 0x2a, 0xcd, - 0x97, 0x27, 0x5d, 0x4c, 0x1f, 0x28, 0x62, 0xaa, 0x91, 0xcd, 0x61, 0x06, 0xb4, 0x2c, 0xcb, 0x00, - 0x6d, 0xce, 0x14, 0xfb, 0x19, 0xf1, 0x25, 0xfa, 0x0c, 0x0a, 0x98, 0xb0, 0x5e, 0xc8, 0xe5, 0x4b, - 0x48, 0x65, 0xe3, 0xca, 0x14, 0xed, 0x8a, 0x49, 0x9e, 0x55, 0x2d, 0x80, 0x7e, 0x05, 0x45, 0x35, - 0x62, 0x76, 0x65, 0x5a, 0xcb, 0x9f, 0x61, 0x99, 0x96, 0xd1, 0x0d, 0x85, 0xa6, 0xc4, 0x71, 0xfc, - 0x9c, 0x44, 0x44, 0xbf, 0xd0, 0x89, 0xb6, 0x76, 0x1e, 0x1b, 0x33, 0x68, 0x03, 0xe6, 0x39, 0xf5, - 0x5b, 0xc4, 0x5e, 0x98, 0x21, 0x84, 0x8a, 0x55, 0x14, 0xb6, 0x24, 0x88, 0x22, 0xd2, 0xb6, 0x17, - 0x15, 0x52, 0x52, 0xd4, 0x7b, 0xe8, 0x5b, 0xdf, 0x07, 0x2a, 0x77, 0x9e, 0x43, 0xd5, 0x8c, 0x45, - 0x86, 0xec, 0xed, 0xd1, 0xae, 0x77, 0x86, 0xbd, 0x31, 0x40, 0xff, 0x73, 0xf8, 0xc1, 0xd3, 0xa4, - 0xed, 0x73, 0x92, 0x55, 0xfd, 0x26, 0xab, 0xc0, 0x25, 0x28, 0xec, 0xaa, 0x60, 0xa9, 0xd7, 0x43, - 0x4d, 0x89, 0xf9, 0x06, 0x11, 0x79, 0xac, 0x4b, 0x9e, 0xa6, 0xdc, 0xcb, 0xe0, 0x64, 0xa9, 0x57, - 0xc1, 0x70, 0xff, 0x92, 0x03, 0x18, 0x6e, 0x08, 0xfa, 0x08, 0xa0, 0x4b, 0xda, 0x81, 0xff, 0x1b, - 0x3e, 0x6c, 0xea, 0xca, 0x72, 0x46, 0x76, 0x76, 0x43, 0xf8, 0x9d, 0x7b, 0x67, 0xf8, 0x8d, 0x60, - 0x8e, 0x05, 0xdf, 0x10, 0x0d, 0x15, 0xe4, 0x18, 0x3d, 0x81, 0x8a, 0x1f, 0x45, 0x31, 0x97, 0xa9, - 0x94, 0x36, 0xbc, 0x37, 0x4f, 0x4a, 0x21, 0x6f, 0x6b, 0xc8, 0xaf, 0x32, 0xd5, 0xd4, 0xe0, 0xdc, - 0x83, 0xa5, 0x71, 0x86, 0x33, 0x35, 0x64, 0xdf, 0x5a, 0xf0, 0xc1, 0xd8, 0xd6, 0xa1, 0x4f, 0x07, - 0x27, 0xd1, 0x9a, 0x21, 0xc5, 0xd3, 0x43, 0x78, 0x1f, 0xaa, 0x5b, 0x9c, 0x8b, 0xca, 0xa3, 0x7c, - 0x53, 0x2d, 0xd7, 0xc9, 0xb2, 0x23, 0x12, 0xee, 0x9f, 0xac, 0xe1, 0x5b, 0x63, 0x66, 0xdf, 0x7d, - 0x77, 0xb4, 0xef, 0xbe, 0x36, 0xbd, 0x40, 0xbf, 0xcf, 0x76, 0xfb, 0xfa, 0x2f, 0xe0, 0xc3, 0xcc, - 0xcb, 0x11, 0x55, 0xa0, 0xb8, 0xb7, 0xbf, 0x85, 0xf7, 0x9b, 0x8d, 0xa5, 0x73, 0xa8, 0x0a, 0xa5, - 0xed, 0x27, 0x8f, 0x77, 0x1f, 0x35, 0xf7, 0x9b, 0x4b, 0x96, 0x58, 0x6a, 0x34, 0xc5, 0xb8, 0xb1, - 0x94, 0xdb, 0xf8, 0xb6, 0x00, 0xc5, 0x6d, 0xf5, 0x7f, 0x0b, 0xda, 0x87, 0xf2, 0xe0, 0x21, 0x1e, - 0xb9, 0x19, 0xd1, 0x19, 0x7b, 0xd1, 0x77, 0x3e, 0x3e, 0x91, 0x47, 0x17, 0xcf, 0x07, 0x30, 0x2f, - 0xff, 0x92, 0x40, 0x19, 0x2d, 0xae, 0xf9, 0x5f, 0x85, 0x73, 0xf2, 0x13, 0xff, 0xba, 0x25, 0x34, - 0xc9, 0xf7, 0x81, 0x2c, 0x4d, 0xe6, 0x03, 0xa2, 0xb3, 0x72, 0xca, 0xc3, 0x02, 0x7a, 0x0c, 0x05, - 0xdd, 0x34, 0x65, 0xb1, 0x9a, 0xaf, 0x00, 0xce, 0xea, 0x74, 0x06, 0xa5, 0x6c, 0xdd, 0x42, 0x8f, - 0x07, 0x6f, 0xc2, 0x59, 0xa6, 0x99, 0x88, 0xd3, 0x39, 0x65, 0x7d, 0xcd, 0x5a, 0xb7, 0xd0, 0x57, - 0x50, 0x31, 0x30, 0x25, 0xca, 0x40, 0x34, 0x93, 0x00, 0xd5, 0xb9, 0x76, 0x0a, 0x97, 0xf6, 0xbc, - 0x09, 0x73, 0xf2, 0x20, 0x65, 0x04, 0xdb, 0x80, 0x9c, 0x59, 0x66, 0x8e, 0x40, 0xd0, 0x03, 0x05, - 0x92, 0x49, 0x64, 0x66, 0x1f, 0xba, 0x76, 0xda, 0xdd, 0x36, 0x35, 0x6d, 0x26, 0x92, 0x78, 0xdd, - 0x42, 0x31, 0xa0, 0xc9, 0xe2, 0x89, 0x7e, 0x9c, 0x91, 0x25, 0xd3, 0x2a, 0xb8, 0x73, 0x63, 0x36, - 0x66, 0xe5, 0x54, 0xbd, 0xfa, 0xea, 0xcd, 0xb2, 0xf5, 0xcf, 0x37, 0xcb, 0xd6, 0xbf, 0xdf, 0x2c, - 0x5b, 0x07, 0x05, 0x89, 0x5a, 0x7e, 0xf2, 0x7d, 0x00, 0x00, 0x00, 0xff, 0xff, 0x1f, 0xdc, 0xb1, - 0x33, 0x8f, 0x1c, 0x00, 0x00, + 0x11, 0xde, 0xe1, 0x3f, 0x8b, 0x94, 0x4c, 0xb7, 0xbd, 0xc6, 0x64, 0xe2, 0x95, 0xe4, 0x59, 0x3b, + 0x11, 0x1c, 0x7b, 0xa8, 0x65, 0xd6, 0xb1, 0x57, 0x4e, 0x1c, 0x8b, 0x22, 0xb3, 0x96, 0x63, 0xc3, + 0xda, 0x96, 0xbc, 0x06, 0x16, 0x70, 0x82, 0x11, 0xd9, 0xa2, 0x07, 0x1a, 0xce, 0x4c, 0xba, 0x9b, + 0x5a, 0x73, 0x1f, 0x20, 0xc0, 0xe6, 0x10, 0xe4, 0x12, 0xe4, 0x92, 0x7b, 0x4e, 0x39, 0xe7, 0x09, + 0x02, 0x18, 0xc8, 0x25, 0xe7, 0x3d, 0x38, 0x81, 0x1f, 0x20, 0xc8, 0x31, 0xc7, 0xa0, 0x7f, 0x86, + 0x1c, 0x92, 0x43, 0x89, 0xb2, 0x7d, 0x62, 0x57, 0x77, 0x55, 0x4d, 0x55, 0x75, 0x75, 0xf5, 0x57, + 0x4d, 0x58, 0xea, 0x84, 0x01, 0xa7, 0xa1, 0xef, 0x44, 0x34, 0xe4, 0x21, 0xaa, 0xf5, 0xc3, 0x83, + 0xa1, 0x73, 0x30, 0xf0, 0xfc, 0xee, 0x91, 0xc7, 0x9d, 0xe3, 0x4f, 0xac, 0x9b, 0x3d, 0x8f, 0xbf, + 0x18, 0x1c, 0x38, 0x9d, 0xb0, 0x5f, 0xef, 0x85, 0xbd, 0xb0, 0x2e, 0x19, 0x0f, 0x06, 0x87, 0x92, + 0x92, 0x84, 0x1c, 0x29, 0x05, 0xd6, 0x6a, 0x2f, 0x0c, 0x7b, 0x3e, 0x19, 0x73, 0x71, 0xaf, 0x4f, + 0x18, 0x77, 0xfb, 0x91, 0x66, 0xb8, 0x91, 0xd0, 0x27, 0x3e, 0x56, 0x8f, 0x3f, 0x56, 0x67, 0xa1, + 0x7f, 0x4c, 0x68, 0x3d, 0x3a, 0xa8, 0x87, 0x11, 0xd3, 0xdc, 0xf5, 0xb9, 0xdc, 0x6e, 0xe4, 0xd5, + 0xf9, 0x30, 0x22, 0xac, 0xfe, 0x75, 0x48, 0x8f, 0x08, 0xd5, 0x02, 0x8d, 0x69, 0x73, 0x95, 0x3d, + 0x6e, 0xe4, 0x31, 0x3d, 0xac, 0xd3, 0xa8, 0x53, 0x67, 0xdc, 0xe5, 0x83, 0xf8, 0x23, 0xb7, 0x4e, + 0x30, 0x69, 0x40, 0x3b, 0x24, 0x0a, 0x7d, 0xaf, 0x33, 0x14, 0x86, 0xa9, 0x91, 0x12, 0xb3, 0x7f, + 0x6b, 0x40, 0x75, 0x97, 0x0e, 0x02, 0x82, 0xc9, 0x6f, 0x06, 0x84, 0x71, 0x74, 0x09, 0x0a, 0x87, + 0x9e, 0xcf, 0x09, 0x35, 0x8d, 0xb5, 0xec, 0x7a, 0x19, 0x6b, 0x0a, 0xd5, 0x20, 0xeb, 0xfa, 0xbe, + 0x99, 0x59, 0x33, 0xd6, 0x4b, 0x58, 0x0c, 0xd1, 0x3a, 0x54, 0x8f, 0x08, 0x89, 0x5a, 0x03, 0xea, + 0x72, 0x2f, 0x0c, 0xcc, 0xec, 0x9a, 0xb1, 0x9e, 0x6d, 0xe6, 0x5e, 0xbd, 0x5e, 0x35, 0xf0, 0xc4, + 0x0a, 0xb2, 0xa1, 0x2c, 0xe8, 0xe6, 0x90, 0x13, 0x66, 0xe6, 0x12, 0x6c, 0xe3, 0x69, 0xfb, 0x3a, + 0xd4, 0x5a, 0x1e, 0x3b, 0x7a, 0xca, 0xdc, 0xde, 0x69, 0xb6, 0xd8, 0x0f, 0xe1, 0x7c, 0x82, 0x97, + 0x45, 0x61, 0xc0, 0x08, 0xba, 0x05, 0x05, 0x4a, 0x3a, 0x21, 0xed, 0x4a, 0xe6, 0x4a, 0xe3, 0x23, + 0x67, 0x3a, 0x0d, 0x1c, 0x2d, 0x20, 0x98, 0xb0, 0x66, 0xb6, 0xff, 0x94, 0x85, 0x4a, 0x62, 0x1e, + 0x2d, 0x43, 0x66, 0xa7, 0x65, 0x1a, 0x6b, 0xc6, 0x7a, 0x19, 0x67, 0x76, 0x5a, 0xc8, 0x84, 0xe2, + 0xe3, 0x01, 0x77, 0x0f, 0x7c, 0xa2, 0x7d, 0x8f, 0x49, 0x74, 0x11, 0xf2, 0x3b, 0xc1, 0x53, 0x46, + 0xa4, 0xe3, 0x25, 0xac, 0x08, 0x84, 0x20, 0xb7, 0xe7, 0x7d, 0x43, 0x94, 0x9b, 0x58, 0x8e, 0x91, + 0x05, 0x85, 0x5d, 0x97, 0x92, 0x80, 0x9b, 0x79, 0xa1, 0xb7, 0x99, 0x31, 0x0d, 0xac, 0x67, 0x50, + 0x13, 0xca, 0xdb, 0x94, 0xb8, 0x9c, 0x74, 0xb7, 0xb8, 0x59, 0x58, 0x33, 0xd6, 0x2b, 0x0d, 0xcb, + 0x51, 0x9b, 0xec, 0xc4, 0xf9, 0xe7, 0xec, 0xc7, 0xf9, 0xd7, 0x2c, 0xbd, 0x7a, 0xbd, 0xfa, 0xc1, + 0x1f, 0xfe, 0x25, 0x62, 0x37, 0x12, 0x43, 0xf7, 0x01, 0x1e, 0xb9, 0x8c, 0x3f, 0x65, 0x52, 0x49, + 0xf1, 0x54, 0x25, 0x39, 0xa9, 0x20, 0x21, 0x83, 0x56, 0x00, 0x64, 0x10, 0xb6, 0xc3, 0x41, 0xc0, + 0xcd, 0x92, 0xb4, 0x3d, 0x31, 0x83, 0xd6, 0xa0, 0xd2, 0x22, 0xac, 0x43, 0xbd, 0x48, 0x6e, 0x75, + 0x59, 0x86, 0x27, 0x39, 0x25, 0x34, 0xa8, 0x08, 0xee, 0x0f, 0x23, 0x62, 0x82, 0x64, 0x48, 0xcc, + 0x88, 0xbd, 0xdc, 0x7b, 0xe1, 0x52, 0xd2, 0x35, 0x2b, 0x32, 0x5c, 0x9a, 0x12, 0xf1, 0x55, 0x91, + 0x60, 0x66, 0x55, 0x6e, 0x72, 0x4c, 0xda, 0xbf, 0x2b, 0x42, 0x75, 0x4f, 0x1c, 0xa7, 0x38, 0x1d, + 0x6a, 0x90, 0xc5, 0xe4, 0x50, 0xef, 0x8d, 0x18, 0x22, 0x07, 0xa0, 0x45, 0x0e, 0xbd, 0xc0, 0x93, + 0x56, 0x65, 0xa4, 0xe3, 0xcb, 0x4e, 0x74, 0xe0, 0x8c, 0x67, 0x71, 0x82, 0x03, 0x59, 0x50, 0x6a, + 0xbf, 0x8c, 0x42, 0x2a, 0x52, 0x2a, 0x2b, 0xd5, 0x8c, 0x68, 0xf4, 0x0c, 0x96, 0xe2, 0xf1, 0x16, + 0xe7, 0x54, 0x24, 0xaa, 0x48, 0xa3, 0x4f, 0x66, 0xd3, 0x28, 0x69, 0x94, 0x33, 0x21, 0xd3, 0x0e, + 0x38, 0x1d, 0xe2, 0x49, 0x3d, 0xc2, 0xc3, 0x3d, 0xc2, 0x98, 0xb0, 0x50, 0x6e, 0x3f, 0x8e, 0x49, + 0x61, 0xce, 0x2f, 0x68, 0x18, 0x70, 0x12, 0x74, 0xe5, 0xd6, 0x97, 0xf1, 0x88, 0x16, 0xe6, 0xc4, + 0x63, 0x65, 0x4e, 0x71, 0x21, 0x73, 0x26, 0x64, 0xb4, 0x39, 0x13, 0x73, 0x68, 0x13, 0xf2, 0xdb, + 0x6e, 0xe7, 0x05, 0x91, 0xbb, 0x5c, 0x69, 0xac, 0xcc, 0x2a, 0x94, 0xcb, 0x4f, 0xe4, 0xb6, 0x32, + 0x79, 0x50, 0x3f, 0xc0, 0x4a, 0x04, 0xfd, 0x0a, 0xaa, 0xed, 0x80, 0x7b, 0xdc, 0x27, 0x7d, 0xb9, + 0x63, 0x65, 0xb1, 0x63, 0xcd, 0xcd, 0xef, 0x5e, 0xaf, 0xfe, 0x64, 0x6e, 0xf9, 0x19, 0x70, 0xcf, + 0xaf, 0x93, 0x84, 0x94, 0x93, 0x50, 0x81, 0x27, 0xf4, 0xa1, 0xaf, 0x60, 0x39, 0x36, 0x76, 0x27, + 0x88, 0x06, 0x9c, 0x99, 0x20, 0xbd, 0x6e, 0x2c, 0xe8, 0xb5, 0x12, 0x52, 0x6e, 0x4f, 0x69, 0x12, + 0xc1, 0xde, 0x09, 0x38, 0xa1, 0x81, 0xeb, 0xeb, 0x14, 0x1c, 0xd1, 0x68, 0x47, 0x64, 0x9a, 0xa8, + 0x92, 0xbb, 0xb2, 0x36, 0x9a, 0x55, 0x19, 0x9a, 0x6b, 0xb3, 0x5f, 0x4d, 0xd6, 0x52, 0x47, 0x31, + 0xe3, 0x09, 0x51, 0xeb, 0x3e, 0xa0, 0xd9, 0x94, 0x10, 0xa9, 0x7b, 0x44, 0x86, 0x71, 0xea, 0x1e, + 0x91, 0xa1, 0xa8, 0x1e, 0xc7, 0xae, 0x3f, 0x50, 0x55, 0xa5, 0x8c, 0x15, 0xb1, 0x99, 0xb9, 0x63, + 0x08, 0x0d, 0xb3, 0xbb, 0x78, 0x26, 0x0d, 0x5f, 0xc0, 0x85, 0x94, 0x88, 0xa4, 0xa8, 0xb8, 0x9a, + 0x54, 0x31, 0x7b, 0x74, 0xc6, 0x2a, 0xed, 0xbf, 0x66, 0xa1, 0x9a, 0xcc, 0x0b, 0xb4, 0x01, 0x17, + 0x94, 0x9f, 0x98, 0x1c, 0xb6, 0x48, 0x44, 0x49, 0x47, 0x14, 0x23, 0xad, 0x3c, 0x6d, 0x09, 0x35, + 0xe0, 0xe2, 0x4e, 0x5f, 0x4f, 0xb3, 0x84, 0x48, 0x46, 0x1e, 0xfb, 0xd4, 0x35, 0x14, 0xc2, 0x87, + 0x4a, 0x95, 0x8c, 0x44, 0x42, 0x28, 0x2b, 0xf3, 0xe2, 0xb3, 0x93, 0x93, 0xd7, 0x49, 0x95, 0x55, + 0xe9, 0x91, 0xae, 0x17, 0xfd, 0x0c, 0x8a, 0x6a, 0x21, 0x3e, 0xff, 0x1f, 0x9f, 0xfc, 0x09, 0xa5, + 0x2c, 0x96, 0x11, 0xe2, 0xca, 0x0f, 0x66, 0xe6, 0xcf, 0x20, 0xae, 0x65, 0xac, 0x07, 0x60, 0xcd, + 0x37, 0xf9, 0x2c, 0x29, 0x60, 0xff, 0xc5, 0x80, 0xf3, 0x33, 0x1f, 0x12, 0x97, 0x93, 0x2c, 0xcf, + 0x4a, 0x85, 0x1c, 0xa3, 0x16, 0xe4, 0x55, 0x81, 0xc9, 0x48, 0x83, 0x9d, 0x05, 0x0c, 0x76, 0x12, + 0xd5, 0x45, 0x09, 0x5b, 0x77, 0x00, 0xde, 0x2e, 0x59, 0xed, 0xbf, 0x19, 0xb0, 0xa4, 0x0f, 0xb3, + 0xbe, 0xc9, 0x5d, 0xa8, 0xc5, 0x47, 0x28, 0x9e, 0xd3, 0x77, 0xfa, 0xad, 0xb9, 0x75, 0x40, 0xb1, + 0x39, 0xd3, 0x72, 0xca, 0xc6, 0x19, 0x75, 0xd6, 0x76, 0x9c, 0x57, 0x53, 0xac, 0x67, 0xb2, 0xfc, + 0x0a, 0x2c, 0xed, 0x49, 0x08, 0x36, 0xf7, 0x82, 0xb2, 0xff, 0x6b, 0xc0, 0x72, 0xcc, 0xa3, 0xbd, + 0xfb, 0x14, 0x4a, 0xc7, 0x84, 0x72, 0xf2, 0x92, 0x30, 0xed, 0x95, 0x39, 0xeb, 0xd5, 0x97, 0x92, + 0x03, 0x8f, 0x38, 0xd1, 0x26, 0x94, 0x14, 0xdc, 0x23, 0xf1, 0x46, 0xad, 0xcc, 0x93, 0xd2, 0xdf, + 0x1b, 0xf1, 0xa3, 0x3a, 0xe4, 0xfc, 0xb0, 0xc7, 0xf4, 0x99, 0xf9, 0xfe, 0x3c, 0xb9, 0x47, 0x61, + 0x0f, 0x4b, 0x46, 0x74, 0x17, 0x4a, 0x5f, 0xbb, 0x34, 0xf0, 0x82, 0x5e, 0x7c, 0x0a, 0x56, 0xe7, + 0x09, 0x3d, 0x53, 0x7c, 0x78, 0x24, 0x20, 0x00, 0x55, 0x41, 0xad, 0xa1, 0x87, 0x50, 0xe8, 0x7a, + 0x3d, 0xc2, 0xb8, 0x0a, 0x49, 0xb3, 0x21, 0xee, 0x92, 0xef, 0x5e, 0xaf, 0x5e, 0x4f, 0x5c, 0x16, + 0x61, 0x44, 0x02, 0x01, 0xdf, 0x5d, 0x2f, 0x20, 0x54, 0xc0, 0xdb, 0x9b, 0x4a, 0xc4, 0x69, 0xc9, + 0x1f, 0xac, 0x35, 0x08, 0x5d, 0x9e, 0xba, 0x12, 0x64, 0xbd, 0x78, 0x3b, 0x5d, 0x4a, 0x83, 0x38, + 0x06, 0x81, 0xdb, 0x27, 0x1a, 0x02, 0xc8, 0xb1, 0xc0, 0x27, 0x1d, 0x91, 0xe7, 0x5d, 0x89, 0xdc, + 0x4a, 0x58, 0x53, 0x68, 0x13, 0x8a, 0x8c, 0xbb, 0x54, 0xd4, 0x9c, 0xfc, 0x82, 0xc0, 0x2a, 0x16, + 0x40, 0xf7, 0xa0, 0xdc, 0x09, 0xfb, 0x91, 0x4f, 0x84, 0x74, 0x61, 0x41, 0xe9, 0xb1, 0x88, 0x48, + 0x3d, 0x42, 0x69, 0x48, 0x25, 0xa4, 0x2b, 0x63, 0x45, 0xa0, 0xdb, 0xb0, 0x14, 0xd1, 0xb0, 0x47, + 0x09, 0x63, 0x9f, 0xd3, 0x70, 0x10, 0xe9, 0x8b, 0xfc, 0xbc, 0x28, 0xde, 0xbb, 0xc9, 0x05, 0x3c, + 0xc9, 0x67, 0xff, 0x27, 0x03, 0xd5, 0x64, 0x8a, 0xcc, 0x60, 0xdd, 0x87, 0x50, 0x50, 0x09, 0xa7, + 0x72, 0xfd, 0xed, 0x62, 0xac, 0x34, 0xa4, 0xc6, 0xd8, 0x84, 0x62, 0x67, 0x40, 0x25, 0x10, 0x56, + 0xf0, 0x38, 0x26, 0x85, 0xa7, 0x3c, 0xe4, 0xae, 0x2f, 0x63, 0x9c, 0xc5, 0x8a, 0x10, 0xd8, 0x78, + 0xd4, 0x79, 0x9d, 0x0d, 0x1b, 0x8f, 0xc4, 0x92, 0xfb, 0x57, 0x7c, 0xa7, 0xfd, 0x2b, 0x9d, 0x79, + 0xff, 0xec, 0xbf, 0x1b, 0x50, 0x1e, 0x9d, 0xad, 0x44, 0x74, 0x8d, 0x77, 0x8e, 0xee, 0x44, 0x64, + 0x32, 0x6f, 0x17, 0x99, 0x4b, 0x50, 0x60, 0x9c, 0x12, 0xb7, 0xaf, 0x3a, 0x37, 0xac, 0x29, 0x51, + 0xc5, 0xfa, 0xac, 0x27, 0x77, 0xa8, 0x8a, 0xc5, 0xd0, 0xfe, 0x9f, 0x01, 0x4b, 0x13, 0xc7, 0xfd, + 0xbd, 0xfa, 0x72, 0x11, 0xf2, 0x3e, 0x39, 0x26, 0xaa, 0xb7, 0xcc, 0x62, 0x45, 0x88, 0x59, 0xf6, + 0x22, 0xa4, 0x5c, 0x1a, 0x57, 0xc5, 0x8a, 0x10, 0x36, 0x77, 0x09, 0x77, 0x3d, 0x5f, 0xd6, 0xa5, + 0x2a, 0xd6, 0x94, 0xb0, 0x79, 0x40, 0x7d, 0x8d, 0xaf, 0xc5, 0x10, 0xd9, 0x90, 0xf3, 0x82, 0xc3, + 0x50, 0xa7, 0x8d, 0x44, 0x36, 0x0a, 0xa7, 0xed, 0x04, 0x87, 0x21, 0x96, 0x6b, 0xe8, 0x0a, 0x14, + 0xa8, 0x1b, 0xf4, 0x48, 0x0c, 0xae, 0xcb, 0x82, 0x0b, 0x8b, 0x19, 0xac, 0x17, 0x6c, 0x1b, 0xaa, + 0xb2, 0x3f, 0x7d, 0x4c, 0x98, 0xe8, 0x86, 0x44, 0x5a, 0x77, 0x5d, 0xee, 0x4a, 0xb7, 0xab, 0x58, + 0x8e, 0xed, 0x1b, 0x80, 0x1e, 0x79, 0x8c, 0x3f, 0x93, 0x2d, 0x3c, 0x3b, 0xad, 0x79, 0xdd, 0x83, + 0x0b, 0x13, 0xdc, 0xfa, 0x5a, 0xf8, 0xe9, 0x54, 0xfb, 0x7a, 0x75, 0xb6, 0xe2, 0xca, 0x97, 0x02, + 0x47, 0x09, 0x4e, 0x75, 0xb1, 0x4b, 0x50, 0x91, 0x7e, 0xa9, 0x6f, 0xdb, 0x2e, 0x54, 0x15, 0xa9, + 0x95, 0x7f, 0x01, 0xe7, 0x62, 0x45, 0x5f, 0x12, 0x2a, 0x5b, 0x11, 0x43, 0xc6, 0xe5, 0x87, 0xf3, + 0xbe, 0xd2, 0x9c, 0x64, 0xc7, 0xd3, 0xf2, 0x36, 0x81, 0x0b, 0x92, 0xe7, 0x81, 0xc7, 0x78, 0x48, + 0x87, 0xb1, 0xd7, 0x2b, 0x00, 0x5b, 0x1d, 0xee, 0x1d, 0x93, 0x27, 0x81, 0xaf, 0xae, 0xd1, 0x12, + 0x4e, 0xcc, 0xc4, 0x57, 0x64, 0x66, 0xdc, 0xc3, 0x5d, 0x86, 0x72, 0xdb, 0xa5, 0xfe, 0xb0, 0xfd, + 0xd2, 0xe3, 0xba, 0x95, 0x1e, 0x4f, 0xd8, 0xbf, 0x37, 0xe0, 0x7c, 0xf2, 0x3b, 0xed, 0x63, 0x51, + 0x2e, 0xee, 0x42, 0x8e, 0xc7, 0x38, 0x66, 0x39, 0xcd, 0x89, 0x19, 0x11, 0x01, 0x75, 0xb0, 0x14, + 0x4a, 0x44, 0x5a, 0x1d, 0x9c, 0xab, 0x27, 0x8b, 0x4f, 0x45, 0xfa, 0x1f, 0x25, 0x40, 0xb3, 0xcb, + 0x29, 0xbd, 0x69, 0xb2, 0xb9, 0xcb, 0x4c, 0x35, 0x77, 0xcf, 0xa7, 0x9b, 0x3b, 0x75, 0x35, 0xdf, + 0x5e, 0xc4, 0x92, 0x05, 0x5a, 0xbc, 0x3b, 0x50, 0x8e, 0xd1, 0x4d, 0x7c, 0x81, 0x5b, 0xb3, 0xaa, + 0x47, 0x00, 0x68, 0xcc, 0x8c, 0xd6, 0xe3, 0x1b, 0x47, 0xdd, 0x75, 0x28, 0xae, 0x29, 0x34, 0xea, + 0x38, 0x1a, 0x57, 0xe8, 0x5b, 0xe8, 0xde, 0xd9, 0xde, 0x2d, 0x72, 0xd3, 0x6f, 0x16, 0x4d, 0xa8, + 0x6c, 0xc7, 0x85, 0xf2, 0x0c, 0x8f, 0x16, 0x49, 0x21, 0xb4, 0xa1, 0x81, 0x8d, 0x2a, 0xcd, 0x97, + 0x67, 0x5d, 0x8c, 0x1f, 0x28, 0x42, 0xaa, 0x91, 0xcd, 0x61, 0x0a, 0xb4, 0x2c, 0xcb, 0x00, 0x6d, + 0x2e, 0x14, 0xfb, 0x05, 0xf1, 0x25, 0xfa, 0x0c, 0x0a, 0x98, 0xb0, 0x81, 0xcf, 0xe5, 0x4b, 0x48, + 0xa5, 0x71, 0x65, 0x8e, 0x76, 0xc5, 0x24, 0xcf, 0xaa, 0x16, 0x40, 0xbf, 0x84, 0xa2, 0x1a, 0x31, + 0xb3, 0x32, 0xaf, 0xe5, 0x4f, 0xb1, 0x4c, 0xcb, 0xe8, 0x86, 0x42, 0x53, 0xe2, 0x38, 0x7e, 0x4e, + 0x02, 0xa2, 0x5f, 0xe8, 0x44, 0x5b, 0x9b, 0xc7, 0x89, 0x19, 0xd4, 0x80, 0x3c, 0xa7, 0x6e, 0x87, + 0x98, 0x4b, 0x0b, 0x84, 0x50, 0xb1, 0x8a, 0xc2, 0x16, 0x79, 0x41, 0x40, 0xba, 0xe6, 0xb2, 0x42, + 0x4a, 0x8a, 0x42, 0x3f, 0x80, 0xe5, 0x60, 0xd0, 0x97, 0xcd, 0x42, 0x77, 0x8f, 0x93, 0x88, 0x99, + 0xe7, 0xe4, 0xf7, 0xa6, 0x66, 0xd1, 0x55, 0x58, 0x0a, 0x06, 0xfd, 0x7d, 0x71, 0xc3, 0x2b, 0xb6, + 0x9a, 0x64, 0x9b, 0x9c, 0x7c, 0x0f, 0x5d, 0xf0, 0xfb, 0xc0, 0xf8, 0xd6, 0x73, 0xa8, 0x26, 0x23, + 0x9b, 0x22, 0x7b, 0x7b, 0xb2, 0x87, 0x5e, 0x60, 0xa7, 0x13, 0x2d, 0xc4, 0x73, 0xf8, 0xde, 0xd3, + 0xa8, 0xeb, 0x72, 0x92, 0x56, 0x4b, 0x67, 0x6b, 0xca, 0x25, 0x28, 0xec, 0xaa, 0xd0, 0xab, 0xb7, + 0x48, 0x4d, 0x89, 0xf9, 0x16, 0x11, 0xa7, 0x42, 0x17, 0x50, 0x4d, 0xd9, 0x97, 0xc1, 0x4a, 0x53, + 0xaf, 0x82, 0x61, 0xff, 0x39, 0x03, 0x30, 0xde, 0x5e, 0xf4, 0x11, 0x40, 0x9f, 0x74, 0x3d, 0xf7, + 0xd7, 0x7c, 0xdc, 0x22, 0x96, 0xe5, 0x8c, 0xec, 0x13, 0xc7, 0x60, 0x3e, 0xf3, 0xce, 0x60, 0x1e, + 0x41, 0x8e, 0x79, 0xdf, 0x10, 0x0d, 0x3c, 0xe4, 0x18, 0x3d, 0x81, 0x8a, 0x1b, 0x04, 0x21, 0x97, + 0x89, 0x19, 0xb7, 0xcf, 0x37, 0x4f, 0x4a, 0x48, 0x67, 0x6b, 0xcc, 0xaf, 0xf2, 0x3e, 0xa9, 0xc1, + 0xba, 0x07, 0xb5, 0x69, 0x86, 0x33, 0xb5, 0x77, 0xdf, 0x1a, 0x70, 0x6e, 0x6a, 0xeb, 0xd0, 0xa7, + 0xa3, 0x73, 0x6d, 0x2c, 0x70, 0x60, 0xe2, 0x23, 0x7d, 0x1f, 0xaa, 0x5b, 0x9c, 0x8b, 0x3a, 0xa6, + 0x7c, 0x53, 0x0d, 0xdc, 0xc9, 0xb2, 0x13, 0x12, 0xf6, 0x1f, 0x8d, 0xf1, 0xcb, 0x65, 0x6a, 0x17, + 0x7f, 0x77, 0xb2, 0x8b, 0xbf, 0x36, 0xbf, 0xdc, 0xbf, 0xcf, 0xe6, 0xfd, 0xfa, 0xcf, 0xe1, 0xc3, + 0xd4, 0xab, 0x16, 0x55, 0xa0, 0xb8, 0xb7, 0xbf, 0x85, 0xf7, 0xdb, 0xad, 0xda, 0x07, 0xa8, 0x0a, + 0xa5, 0xed, 0x27, 0x8f, 0x77, 0x1f, 0xb5, 0xf7, 0xdb, 0x35, 0x43, 0x2c, 0xb5, 0xda, 0x62, 0xdc, + 0xaa, 0x65, 0x1a, 0xdf, 0x16, 0xa0, 0xb8, 0xad, 0xfe, 0xbd, 0x41, 0xfb, 0x50, 0x1e, 0x3d, 0xeb, + 0x23, 0x3b, 0x25, 0x3a, 0x53, 0xff, 0x0f, 0x58, 0x1f, 0x9f, 0xc8, 0xa3, 0x4b, 0xf1, 0x03, 0xc8, + 0xcb, 0x3f, 0x38, 0x50, 0x4a, 0xc3, 0x9c, 0xfc, 0xe7, 0xc3, 0x3a, 0xf9, 0x0f, 0x83, 0x0d, 0x43, + 0x68, 0x92, 0xaf, 0x0d, 0x69, 0x9a, 0x92, 0xcf, 0x91, 0xd6, 0xea, 0x29, 0xcf, 0x14, 0xe8, 0x31, + 0x14, 0x74, 0x0b, 0x96, 0xc6, 0x9a, 0x7c, 0x53, 0xb0, 0xd6, 0xe6, 0x33, 0x28, 0x65, 0x1b, 0x06, + 0x7a, 0x3c, 0x7a, 0x61, 0x4e, 0x33, 0x2d, 0x89, 0x5f, 0xad, 0x53, 0xd6, 0xd7, 0x8d, 0x0d, 0x03, + 0x7d, 0x05, 0x95, 0x04, 0x42, 0x45, 0x29, 0xf8, 0x68, 0x16, 0xee, 0x5a, 0xd7, 0x4e, 0xe1, 0xd2, + 0x9e, 0xb7, 0x21, 0x27, 0x0f, 0x52, 0x4a, 0xb0, 0x13, 0x00, 0x36, 0xcd, 0xcc, 0x09, 0x40, 0x7b, + 0xa0, 0x20, 0x37, 0x09, 0x92, 0xd9, 0x87, 0xae, 0x9d, 0x76, 0x53, 0xce, 0x4d, 0x9b, 0x99, 0x24, + 0xde, 0x30, 0x50, 0x08, 0x68, 0xb6, 0x78, 0xa2, 0x1f, 0xa5, 0x64, 0xc9, 0xbc, 0x0a, 0x6e, 0xdd, + 0x58, 0x8c, 0x59, 0x39, 0xd5, 0xac, 0xbe, 0x7a, 0xb3, 0x62, 0xfc, 0xf3, 0xcd, 0x8a, 0xf1, 0xef, + 0x37, 0x2b, 0xc6, 0x41, 0x41, 0x62, 0xa0, 0x1f, 0xff, 0x3f, 0x00, 0x00, 0xff, 0xff, 0xca, 0xd0, + 0xd7, 0x6e, 0xdd, 0x1c, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -3945,6 +3963,18 @@ func (m *BuildHistoryRecord) MarshalToSizedBuffer(dAtA []byte) (int, error) { i -= len(m.XXX_unrecognized) copy(dAtA[i:], m.XXX_unrecognized) } + if m.NumTotalSteps != 0 { + i = encodeVarintControl(dAtA, i, uint64(m.NumTotalSteps)) + i-- + dAtA[i] = 0x1 + i-- + dAtA[i] = 0x80 + } + if m.NumCachedSteps != 0 { + i = encodeVarintControl(dAtA, i, uint64(m.NumCachedSteps)) + i-- + dAtA[i] = 0x78 + } if m.Pinned { i-- if m.Pinned { @@ -5071,6 +5101,12 @@ func (m *BuildHistoryRecord) Size() (n int) { if m.Pinned { n += 2 } + if m.NumCachedSteps != 0 { + n += 1 + sovControl(uint64(m.NumCachedSteps)) + } + if m.NumTotalSteps != 0 { + n += 2 + sovControl(uint64(m.NumTotalSteps)) + } if m.XXX_unrecognized != nil { n += len(m.XXX_unrecognized) } @@ -9989,6 +10025,44 @@ func (m *BuildHistoryRecord) Unmarshal(dAtA []byte) error { } } m.Pinned = bool(v != 0) + case 15: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field NumCachedSteps", wireType) + } + m.NumCachedSteps = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowControl + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.NumCachedSteps |= int32(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 16: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field NumTotalSteps", wireType) + } + m.NumTotalSteps = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowControl + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.NumTotalSteps |= int32(b&0x7F) << shift + if b < 0x80 { + break + } + } default: iNdEx = preIndex skippy, err := skipControl(dAtA[iNdEx:]) diff --git a/api/services/control/control.proto b/api/services/control/control.proto index 6bb692d839eb..9289d05cb15d 100644 --- a/api/services/control/control.proto +++ b/api/services/control/control.proto @@ -204,8 +204,9 @@ message BuildHistoryRecord { int32 Generation = 12; Descriptor trace = 13; bool pinned = 14; + int32 numCachedSteps = 15; + int32 numTotalSteps = 16; // TODO: tags - // TODO: steps/cache summary // TODO: unclipped logs } diff --git a/client/client_test.go b/client/client_test.go index 62c61aa5641a..5d7dc1615a7d 100644 --- a/client/client_test.go +++ b/client/client_test.go @@ -168,6 +168,7 @@ func TestIntegration(t *testing.T) { testBuildInfoInline, testBuildInfoNoExport, testZstdLocalCacheExport, + testCacheExportIgnoreError, testZstdRegistryCacheImportExport, testZstdLocalCacheImportExport, testUncompressedLocalCacheImportExport, @@ -4351,6 +4352,113 @@ func testZstdLocalCacheExport(t *testing.T, sb integration.Sandbox) { require.Equal(t, dt[:4], []byte{0x28, 0xb5, 0x2f, 0xfd}) } +func testCacheExportIgnoreError(t *testing.T, sb integration.Sandbox) { + integration.CheckFeatureCompat(t, sb, integration.FeatureCacheExport) + c, err := New(sb.Context(), sb.Address()) + require.NoError(t, err) + defer c.Close() + + busybox := llb.Image("busybox:latest") + cmd := `sh -e -c "echo -n ignore-error > data"` + + st := llb.Scratch() + st = busybox.Run(llb.Shlex(cmd), llb.Dir("/wd")).AddMount("/wd", st) + + def, err := st.Marshal(sb.Context()) + require.NoError(t, err) + + tests := map[string]struct { + Exports []ExportEntry + CacheExports []CacheOptionsEntry + expectedErrors []string + }{ + "local-ignore-error": { + Exports: []ExportEntry{ + { + Type: ExporterLocal, + OutputDir: t.TempDir(), + }, + }, + CacheExports: []CacheOptionsEntry{ + { + Type: "local", + Attrs: map[string]string{ + "dest": "éèç", + }, + }, + }, + expectedErrors: []string{"failed to solve", "contains value with non-printable ASCII characters"}, + }, + "registry-ignore-error": { + Exports: []ExportEntry{ + { + Type: ExporterImage, + Attrs: map[string]string{ + "name": "test-registry-ignore-error", + "push": "false", + }, + }, + }, + CacheExports: []CacheOptionsEntry{ + { + Type: "registry", + Attrs: map[string]string{ + "ref": "fake-url:5000/myrepo:buildcache", + }, + }, + }, + expectedErrors: []string{"failed to solve", "dial tcp: lookup fake-url", "no such host"}, + }, + "s3-ignore-error": { + Exports: []ExportEntry{ + { + Type: ExporterLocal, + OutputDir: t.TempDir(), + }, + }, + CacheExports: []CacheOptionsEntry{ + { + Type: "s3", + Attrs: map[string]string{ + "endpoint_url": "http://fake-url:9000", + "bucket": "my-bucket", + "region": "us-east-1", + "access_key_id": "minioadmin", + "secret_access_key": "minioadmin", + "use_path_style": "true", + }, + }, + }, + expectedErrors: []string{"failed to solve", "dial tcp: lookup fake-url", "no such host"}, + }, + } + ignoreErrorValues := []bool{true, false} + for _, ignoreError := range ignoreErrorValues { + ignoreErrStr := strconv.FormatBool(ignoreError) + for n, test := range tests { + require.Equal(t, 1, len(test.Exports)) + require.Equal(t, 1, len(test.CacheExports)) + require.NotEmpty(t, test.CacheExports[0].Attrs) + test.CacheExports[0].Attrs["ignore-error"] = ignoreErrStr + testName := fmt.Sprintf("%s-%s", n, ignoreErrStr) + t.Run(testName, func(t *testing.T) { + _, err = c.Solve(sb.Context(), def, SolveOpt{ + Exports: test.Exports, + CacheExports: test.CacheExports, + }, nil) + if ignoreError { + require.NoError(t, err) + } else { + require.Error(t, err) + for _, errStr := range test.expectedErrors { + require.Contains(t, err.Error(), errStr) + } + } + }) + } + } +} + func testUncompressedLocalCacheImportExport(t *testing.T, sb integration.Sandbox) { integration.CheckFeatureCompat(t, sb, integration.FeatureCacheExport) dir := t.TempDir() diff --git a/client/ociindex/ociindex.go b/client/ociindex/ociindex.go index ef1e1cf4c65e..3731ff36bb23 100644 --- a/client/ociindex/ociindex.go +++ b/client/ociindex/ociindex.go @@ -4,6 +4,7 @@ import ( "encoding/json" "io" "os" + "path" "github.com/gofrs/flock" ocispecs "github.com/opencontainers/image-spec/specs-go/v1" @@ -11,69 +12,86 @@ import ( ) const ( - // IndexJSONLockFileSuffix is the suffix of the lock file - IndexJSONLockFileSuffix = ".lock" + // indexFile is the name of the index file + indexFile = "index.json" + + // lockFileSuffix is the suffix of the lock file + lockFileSuffix = ".lock" ) -// PutDescToIndex puts desc to index with tag. -// Existing manifests with the same tag will be removed from the index. -func PutDescToIndex(index *ocispecs.Index, desc ocispecs.Descriptor, tag string) error { - if index == nil { - index = &ocispecs.Index{} +type StoreIndex struct { + indexPath string + lockPath string +} + +func NewStoreIndex(storePath string) StoreIndex { + indexPath := path.Join(storePath, indexFile) + return StoreIndex{ + indexPath: indexPath, + lockPath: indexPath + lockFileSuffix, } - if index.SchemaVersion == 0 { - index.SchemaVersion = 2 +} + +func (s StoreIndex) Read() (*ocispecs.Index, error) { + lock := flock.New(s.lockPath) + locked, err := lock.TryRLock() + if err != nil { + return nil, errors.Wrapf(err, "could not lock %s", s.lockPath) } - if tag != "" { - if desc.Annotations == nil { - desc.Annotations = make(map[string]string) - } - desc.Annotations[ocispecs.AnnotationRefName] = tag - // remove existing manifests with the same tag - var manifests []ocispecs.Descriptor - for _, m := range index.Manifests { - if m.Annotations[ocispecs.AnnotationRefName] != tag { - manifests = append(manifests, m) - } - } - index.Manifests = manifests + if !locked { + return nil, errors.Errorf("could not lock %s", s.lockPath) } - index.Manifests = append(index.Manifests, desc) - return nil + defer func() { + lock.Unlock() + os.RemoveAll(s.lockPath) + }() + + b, err := os.ReadFile(s.indexPath) + if err != nil { + return nil, errors.Wrapf(err, "could not read %s", s.indexPath) + } + var idx ocispecs.Index + if err := json.Unmarshal(b, &idx); err != nil { + return nil, errors.Wrapf(err, "could not unmarshal %s (%q)", s.indexPath, string(b)) + } + return &idx, nil } -func PutDescToIndexJSONFileLocked(indexJSONPath string, desc ocispecs.Descriptor, tag string) error { - lockPath := indexJSONPath + IndexJSONLockFileSuffix - lock := flock.New(lockPath) +func (s StoreIndex) Put(tag string, desc ocispecs.Descriptor) error { + lock := flock.New(s.lockPath) locked, err := lock.TryLock() if err != nil { - return errors.Wrapf(err, "could not lock %s", lockPath) + return errors.Wrapf(err, "could not lock %s", s.lockPath) } if !locked { - return errors.Errorf("could not lock %s", lockPath) + return errors.Errorf("could not lock %s", s.lockPath) } defer func() { lock.Unlock() - os.RemoveAll(lockPath) + os.RemoveAll(s.lockPath) }() - f, err := os.OpenFile(indexJSONPath, os.O_RDWR|os.O_CREATE, 0644) + + f, err := os.OpenFile(s.indexPath, os.O_RDWR|os.O_CREATE, 0644) if err != nil { - return errors.Wrapf(err, "could not open %s", indexJSONPath) + return errors.Wrapf(err, "could not open %s", s.indexPath) } defer f.Close() + var idx ocispecs.Index b, err := io.ReadAll(f) if err != nil { - return errors.Wrapf(err, "could not read %s", indexJSONPath) + return errors.Wrapf(err, "could not read %s", s.indexPath) } if len(b) > 0 { if err := json.Unmarshal(b, &idx); err != nil { - return errors.Wrapf(err, "could not unmarshal %s (%q)", indexJSONPath, string(b)) + return errors.Wrapf(err, "could not unmarshal %s (%q)", s.indexPath, string(b)) } } - if err = PutDescToIndex(&idx, desc, tag); err != nil { + + if err = insertDesc(&idx, desc, tag); err != nil { return err } + b, err = json.Marshal(idx) if err != nil { return err @@ -87,27 +105,56 @@ func PutDescToIndexJSONFileLocked(indexJSONPath string, desc ocispecs.Descriptor return nil } -func ReadIndexJSONFileLocked(indexJSONPath string) (*ocispecs.Index, error) { - lockPath := indexJSONPath + IndexJSONLockFileSuffix - lock := flock.New(lockPath) - locked, err := lock.TryRLock() +func (s StoreIndex) Get(tag string) (*ocispecs.Descriptor, error) { + idx, err := s.Read() if err != nil { - return nil, errors.Wrapf(err, "could not lock %s", lockPath) + return nil, err } - if !locked { - return nil, errors.Errorf("could not lock %s", lockPath) + + for _, m := range idx.Manifests { + if t, ok := m.Annotations[ocispecs.AnnotationRefName]; ok && t == tag { + return &m, nil + } } - defer func() { - lock.Unlock() - os.RemoveAll(lockPath) - }() - b, err := os.ReadFile(indexJSONPath) + return nil, nil +} + +func (s StoreIndex) GetSingle() (*ocispecs.Descriptor, error) { + idx, err := s.Read() if err != nil { - return nil, errors.Wrapf(err, "could not read %s", indexJSONPath) + return nil, err } - var idx ocispecs.Index - if err := json.Unmarshal(b, &idx); err != nil { - return nil, errors.Wrapf(err, "could not unmarshal %s (%q)", indexJSONPath, string(b)) + + if len(idx.Manifests) == 1 { + return &idx.Manifests[0], nil } - return &idx, nil + return nil, nil +} + +// insertDesc puts desc to index with tag. +// Existing manifests with the same tag will be removed from the index. +func insertDesc(index *ocispecs.Index, desc ocispecs.Descriptor, tag string) error { + if index == nil { + return nil + } + + if index.SchemaVersion == 0 { + index.SchemaVersion = 2 + } + if tag != "" { + if desc.Annotations == nil { + desc.Annotations = make(map[string]string) + } + desc.Annotations[ocispecs.AnnotationRefName] = tag + // remove existing manifests with the same tag + var manifests []ocispecs.Descriptor + for _, m := range index.Manifests { + if m.Annotations[ocispecs.AnnotationRefName] != tag { + manifests = append(manifests, m) + } + } + index.Manifests = manifests + } + index.Manifests = append(index.Manifests, desc) + return nil } diff --git a/client/solve.go b/client/solve.go index 67129957b1df..653b00a66df2 100644 --- a/client/solve.go +++ b/client/solve.go @@ -129,7 +129,7 @@ func (c *Client) solve(ctx context.Context, def *llb.Definition, runGateway runG ex = opt.Exports[0] } - indicesToUpdate := []string{} + storesToUpdate := []string{} if !opt.SessionPreInitialized { if len(syncedDirs) > 0 { @@ -194,7 +194,7 @@ func (c *Client) solve(ctx context.Context, def *llb.Definition, runGateway runG return nil, err } contentStores["export"] = cs - indicesToUpdate = append(indicesToUpdate, filepath.Join(ex.OutputDir, "index.json")) + storesToUpdate = append(storesToUpdate, ex.OutputDir) default: s.Allow(filesync.NewFSSyncTargetDir(ex.OutputDir)) } @@ -327,8 +327,9 @@ func (c *Client) solve(ctx context.Context, def *llb.Definition, runGateway runG if err = json.Unmarshal([]byte(manifestDescJSON), &manifestDesc); err != nil { return nil, err } - for indexJSONPath, tag := range cacheOpt.indicesToUpdate { - if err = ociindex.PutDescToIndexJSONFileLocked(indexJSONPath, manifestDesc, tag); err != nil { + for storePath, tag := range cacheOpt.storesToUpdate { + idx := ociindex.NewStoreIndex(storePath) + if err := idx.Put(tag, manifestDesc); err != nil { return nil, err } } @@ -342,12 +343,13 @@ func (c *Client) solve(ctx context.Context, def *llb.Definition, runGateway runG if err = json.Unmarshal([]byte(manifestDescDt), &manifestDesc); err != nil { return nil, err } - for _, indexJSONPath := range indicesToUpdate { + for _, storePath := range storesToUpdate { tag := "latest" if t, ok := res.ExporterResponse["image.name"]; ok { tag = t } - if err = ociindex.PutDescToIndexJSONFileLocked(indexJSONPath, manifestDesc, tag); err != nil { + idx := ociindex.NewStoreIndex(storePath) + if err := idx.Put(tag, manifestDesc); err != nil { return nil, err } } @@ -406,10 +408,10 @@ func defaultSessionName() string { } type cacheOptions struct { - options controlapi.CacheOptions - contentStores map[string]content.Store // key: ID of content store ("local:" + csDir) - indicesToUpdate map[string]string // key: index.JSON file name, value: tag - frontendAttrs map[string]string + options controlapi.CacheOptions + contentStores map[string]content.Store // key: ID of content store ("local:" + csDir) + storesToUpdate map[string]string // key: path to content store, value: tag + frontendAttrs map[string]string } func parseCacheOptions(ctx context.Context, isGateway bool, opt SolveOpt) (*cacheOptions, error) { @@ -418,7 +420,7 @@ func parseCacheOptions(ctx context.Context, isGateway bool, opt SolveOpt) (*cach cacheImports []*controlapi.CacheOptionsEntry ) contentStores := make(map[string]content.Store) - indicesToUpdate := make(map[string]string) // key: index.JSON file name, value: tag + storesToUpdate := make(map[string]string) frontendAttrs := make(map[string]string) for _, ex := range opt.CacheExports { if ex.Type == "local" { @@ -440,8 +442,7 @@ func parseCacheOptions(ctx context.Context, isGateway bool, opt SolveOpt) (*cach tag = t } // TODO(AkihiroSuda): support custom index JSON path and tag - indexJSONPath := filepath.Join(csDir, "index.json") - indicesToUpdate[indexJSONPath] = tag + storesToUpdate[csDir] = tag } if ex.Type == "registry" { regRef := ex.Attrs["ref"] @@ -465,27 +466,26 @@ func parseCacheOptions(ctx context.Context, isGateway bool, opt SolveOpt) (*cach bklog.G(ctx).Warning("local cache import at " + csDir + " not found due to err: " + err.Error()) continue } - // if digest is not specified, load from "latest" tag + // if digest is not specified, attempt to load from tag if im.Attrs["digest"] == "" { - idx, err := ociindex.ReadIndexJSONFileLocked(filepath.Join(csDir, "index.json")) + tag := "latest" + if t, ok := im.Attrs["tag"]; ok { + tag = t + } + + idx := ociindex.NewStoreIndex(csDir) + desc, err := idx.Get(tag) if err != nil { bklog.G(ctx).Warning("local cache import at " + csDir + " not found due to err: " + err.Error()) continue } - for _, m := range idx.Manifests { - tag := "latest" - if t, ok := im.Attrs["tag"]; ok { - tag = t - } - if m.Annotations[ocispecs.AnnotationRefName] == tag { - im.Attrs["digest"] = string(m.Digest) - break - } - } - if im.Attrs["digest"] == "" { - return nil, errors.New("local cache importer requires either explicit digest, \"latest\" tag or custom tag on index.json") + if desc != nil { + im.Attrs["digest"] = desc.Digest.String() } } + if im.Attrs["digest"] == "" { + return nil, errors.New("local cache importer requires either explicit digest, \"latest\" tag or custom tag on index.json") + } contentStores["local:"+csDir] = cs } if im.Type == "registry" { @@ -513,9 +513,9 @@ func parseCacheOptions(ctx context.Context, isGateway bool, opt SolveOpt) (*cach Exports: cacheExports, Imports: cacheImports, }, - contentStores: contentStores, - indicesToUpdate: indicesToUpdate, - frontendAttrs: frontendAttrs, + contentStores: contentStores, + storesToUpdate: storesToUpdate, + frontendAttrs: frontendAttrs, } return &res, nil } diff --git a/cmd/buildctl/debug/monitor.go b/cmd/buildctl/debug/monitor.go index 34bb9c319ecc..2cf83e9326bf 100644 --- a/cmd/buildctl/debug/monitor.go +++ b/cmd/buildctl/debug/monitor.go @@ -48,6 +48,9 @@ func monitor(clicontext *cli.Context) error { return err } fmt.Printf("event: %s ref:%s\n", ev.Type.String(), ev.Record.Ref) + if ev.Record.NumTotalSteps != 0 { + fmt.Printf(" cache: %d/%d\n", ev.Record.NumCachedSteps, ev.Record.NumTotalSteps) + } if ev.Record.Logs != nil { fmt.Printf(" logs: %s\n", ev.Record.Logs) } diff --git a/control/control.go b/control/control.go index ef0f2cf59597..b8d204981007 100644 --- a/control/control.go +++ b/control/control.go @@ -3,6 +3,7 @@ package control import ( "context" "fmt" + "strconv" "sync" "sync/atomic" "time" @@ -368,6 +369,13 @@ func (c *Controller) Solve(ctx context.Context, req *controlapi.SolveRequest) (* } else { exp.CacheExportMode = exportMode } + if ignoreErrorStr, ok := e.Attrs["ignore-error"]; ok { + if ignoreError, supported := parseCacheExportIgnoreError(ignoreErrorStr); !supported { + bklog.G(ctx).Debugf("skipping invalid cache export ignore-error: %s", e.Attrs["ignore-error"]) + } else { + exp.IgnoreError = ignoreError + } + } cacheExporters = append(cacheExporters, exp) } @@ -550,6 +558,14 @@ func parseCacheExportMode(mode string) (solver.CacheExportMode, bool) { return solver.CacheExportModeMin, false } +func parseCacheExportIgnoreError(ignoreErrorStr string) (bool, bool) { + ignoreError, err := strconv.ParseBool(ignoreErrorStr) + if err != nil { + return false, false + } + return ignoreError, true +} + func toPBGCPolicy(in []client.PruneInfo) []*apitypes.GCPolicy { policy := make([]*apitypes.GCPolicy, 0, len(in)) for _, p := range in { diff --git a/control/control_test.go b/control/control_test.go index 1c5f499b613f..8707287e277a 100644 --- a/control/control_test.go +++ b/control/control_test.go @@ -84,3 +84,65 @@ func TestDuplicateCacheOptions(t *testing.T) { }) } } + +func TestParseCacheExportIgnoreError(t *testing.T) { + tests := map[string]struct { + expectedIgnoreError bool + expectedSupported bool + }{ + "": { + expectedIgnoreError: false, + expectedSupported: false, + }, + ".": { + expectedIgnoreError: false, + expectedSupported: false, + }, + "fake": { + expectedIgnoreError: false, + expectedSupported: false, + }, + "true": { + expectedIgnoreError: true, + expectedSupported: true, + }, + "True": { + expectedIgnoreError: true, + expectedSupported: true, + }, + "TRUE": { + expectedIgnoreError: true, + expectedSupported: true, + }, + "truee": { + expectedIgnoreError: false, + expectedSupported: false, + }, + "false": { + expectedIgnoreError: false, + expectedSupported: true, + }, + "False": { + expectedIgnoreError: false, + expectedSupported: true, + }, + "FALSE": { + expectedIgnoreError: false, + expectedSupported: true, + }, + "ffalse": { + expectedIgnoreError: false, + expectedSupported: false, + }, + } + + for ignoreErrStr, test := range tests { + t.Run(ignoreErrStr, func(t *testing.T) { + ignoreErr, supported := parseCacheExportIgnoreError(ignoreErrStr) + t.Log("checking expectedIgnoreError") + require.Equal(t, ignoreErr, test.expectedIgnoreError) + t.Log("checking expectedSupported") + require.Equal(t, supported, test.expectedSupported) + }) + } +} diff --git a/docs/attestations/README.md b/docs/attestations/README.md new file mode 100644 index 000000000000..b4e4c1e76449 --- /dev/null +++ b/docs/attestations/README.md @@ -0,0 +1,16 @@ +# Attestations + +BuildKit supports creating and attaching attestations to build artifacts. +Generated attestations use the [in-toto attestation format](https://github.com/in-toto/attestation). + +The currently supported attestation types are: + +- [SBOMs](./sbom.md) +- [SLSA Provenance](./slsa-provenance.md) + +Upon generation, attestations are attached differently to the export result: + +- For the `image`, `oci` and `docker` exporters, attestations are exported + using the attached [attestation storage](./attestation-storage.md). +- For the `local` and `tar` exporters, attestations are written to separate + files within the output directory. diff --git a/docs/attestation-storage.md b/docs/attestations/attestation-storage.md similarity index 100% rename from docs/attestation-storage.md rename to docs/attestations/attestation-storage.md diff --git a/docs/sbom-protocol.md b/docs/attestations/sbom-protocol.md similarity index 92% rename from docs/sbom-protocol.md rename to docs/attestations/sbom-protocol.md index 47b623ea95df..1505fbfffcca 100644 --- a/docs/sbom-protocol.md +++ b/docs/attestations/sbom-protocol.md @@ -48,9 +48,8 @@ by BuildKit: This variable specifies the main target, passing the path to the root filesystem of the final build result. - The scanner should scan this filesystem, and write its SBOM scans to - `$BUILDKIT_SCAN_DESTINATION/.spdx.json`. If the scan name is not - significant the scan can be named `$(basename $BUILDKIT_SCAN_SOURCE)`. + The scanner should scan this filesystem, and write its SBOM result to + `$BUILDKIT_SCAN_DESTINATION/$(basename $BUILDKIT_SCAN_SOURCE).spdx.json`. - `BUILDKIT_SCAN_SOURCE_EXTRAS` (optional) diff --git a/docs/sbom.md b/docs/attestations/sbom.md similarity index 100% rename from docs/sbom.md rename to docs/attestations/sbom.md diff --git a/docs/attestations/slsa-definitions.md b/docs/attestations/slsa-definitions.md new file mode 100644 index 000000000000..76f25d9847b9 --- /dev/null +++ b/docs/attestations/slsa-definitions.md @@ -0,0 +1,589 @@ +# SLSA definitions + +BuildKit supports the [creation of SLSA Provenance](./slsa-provenance.md) for builds that +it runs. + +The provenance format generated by BuildKit is defined by the +[SLSA Provenance format](https://slsa.dev/provenance/v0.2). + +This page describes how BuildKit populate each field, and whether the field gets +included when you generate attestations `mode=min` and `mode=max`. + +## `builder.id` [(SLSA)](https://slsa.dev/provenance/v0.2#builder.id) + +Included with `mode=min` and `mode=max`. + +The `builder.id` field is set to the URL of the build, if available. + +```json + "builder": { + "id": "https://github.com/docker/buildx/actions/runs/3709599520" + }, +``` + +This value can be set using the `builder-id` attestation parameter. + +## `buildType` [(SLSA)](https://slsa.dev/provenance/v0.2#buildType) + +Included with `mode=min` and `mode=max`. + +The `buildType` field is set to `https://mobyproject.org/buildkit@v1` can be +used to determine the structure of the provenance content. + +```json + "buildType": "https://mobyproject.org/buildkit@v1", +``` + +## `invocation.configSource` [(SLSA)](https://slsa.dev/provenance/v0.2#invocation.configSource) + +Included with `mode=min` and `mode=max`. + +Describes the config that initialized the build. + +```json + "invocation": { + "configSource": { + "uri": "https://github.com/moby/buildkit.git#refs/tags/v0.11.0-rc3", + "digest": { + "sha1": "4b220de5058abfd01ff619c9d2ff6b09a049bea0" + }, + "entryPoint": "Dockerfile" + }, + ... + }, +``` + +For builds initialized from a remote context, like a Git or HTTP URL, this +object defines the context URL and its immutable digest in the `uri` and `digest` fields. +For builds using a local frontend, such as a Dockerfile, the `entryPoint` field defines the path +for the frontend file that initialized the build (`filename` frontend option). + +## `invocation.parameters` [(SLSA)](https://slsa.dev/provenance/v0.2#invocation.parameters) + +Partially included with `mode=min`. + +Describes build inputs passed to the build. + +```json + "invocation": { + "parameters": { + "frontend": "gateway.v0", + "args": { + "build-arg:BUILDKIT_CONTEXT_KEEP_GIT_DIR": "1", + "label:FOO": "bar", + "source": "docker/dockerfile-upstream:master", + "target": "release" + }, + "secrets": [ + { + "id": "GIT_AUTH_HEADER", + "optional": true + }, + ... + ], + "ssh": [], + "locals": [] + }, + ... + }, +``` + +The following fields are included with both `mode=min` and `mode=max`: + +- `locals` lists any local sources used in the build, including the build + context and frontend file. +- `frontend` defines type of BuildKit frontend used for the build. Currently, + this can be `dockerfile.v0` or `gateway.v0`. +- `args` defines the build arguments passed to the BuildKit frontend. + + The keys inside the `args` object reflect the options as BuildKit receives + them. For example, `build-arg` and `label` prefixes are used for build + arguments and labels, and `target` key defines the target stage that was + built. The `source` key defines the source image for the Gateway frontend, if + used. + +The following fields are only included with `mode=max`: + +- `secrets` defines secrets used during the build. Note that actual secret + values are not included. +- `ssh` defines the ssh forwards used during the build. + +## `invocation.environment` [(SLSA)](https://slsa.dev/provenance/v0.2#invocation.environment) + +Included with `mode=min` and `mode=max`. + +```json + "invocation": { + "environment": { + "platform": "linux/amd64" + }, + ... + }, +``` + +The only value BuildKit currently sets is the `platform` of the current build +machine. Note that this is not necessarily the platform of the build result that +can be determined from the `in-toto` subject field. + +## `materials` [(SLSA)](https://slsa.dev/provenance/v0.2#materials) + +Included with `mode=min` and `mode=max`. + +Defines all the external artifacts that were part of the build. The value +depends on the type of artifact: + +- The URL of Git repositories containing source code for the image +- HTTP URLs if you are building from a remote tarball, or that was included + using an `ADD` command in Dockerfile +- Any Docker images used during the build + +The URLs to the Docker images will be in +[Package URL](https://github.com/package-url/purl-spec) format. + +All the build materials will include the immutable checksum of the artifact. +When building from a mutable tag, you can use the digest information to +determine if the artifact has been updated compared to when the build ran. + +```json + "materials": [ + { + "uri": "pkg:docker/alpine@3.17?platform=linux%2Famd64", + "digest": { + "sha256": "8914eb54f968791faf6a8638949e480fef81e697984fba772b3976835194c6d4" + } + }, + { + "uri": "https://github.com/moby/buildkit.git#refs/tags/v0.11.0", + "digest": { + "sha1": "4b220de5058abfd01ff619c9d2ff6b09a049bea0" + } + }, + ... + ], +``` + +## `buildConfig` [(SLSA)](https://slsa.dev/provenance/v0.2#buildConfig) + +Only included with `mode=max`. + +Defines the build steps performed during the build. + +BuildKit internally uses LLB definition to execute the build steps. The LLB +definition of the build steps is defined in `buildConfig.llbDefinition` field. + +Each LLB step is the JSON definition of the +[LLB ProtoBuf API](https://github.com/moby/buildkit/blob/v0.10.0/solver/pb/ops.proto). +The dependencies for a vertex in the LLB graph can be found in the `inputs` +field for every step. + +```json + "buildConfig": { + "llbDefinition": [ + { + "id": "step0", + "op": { + "Op": { + "exec": { + "meta": { + "args": [ + "/bin/sh", + "-c", + "go build ." + ], + "env": [ + "PATH=/go/bin:/usr/local/go/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", + "GOPATH=/go", + "GOFLAGS=-mod=vendor", + ], + "cwd": "/src", + }, + "mounts": [...] + } + }, + "platform": {...}, + }, + "inputs": [ + "step8:0", + "step2:0", + ] + }, + ... + ] + }, +``` + +## `metadata.buildInvocationId` [(SLSA)](https://slsa.dev/provenance/v0.2#metadata.buildIncocationId) + +Included with `mode=min` and `mode=max`. + +Unique identifier for the build invocation. When building a multi-platform image +with a single build request, this value will be the shared by all the platform +versions of the image. + +```json + "metadata": { + "buildInvocationID": "rpv7a389uzil5lqmrgwhijwjz", + ... + }, +``` + +## `metadata.buildStartedOn` [(SLSA)](https://slsa.dev/provenance/v0.2#metadata.buildStartedOn) + +Included with `mode=min` and `mode=max`. + +Timestamp when the build started. + +```json + "metadata": { + "buildStartedOn": "2021-11-17T15:00:00Z", + ... + }, +``` + +## `metadata.buildFinishedOn` [(SLSA)](https://slsa.dev/provenance/v0.2#metadata.buildFinishedOn) + +Included with `mode=min` and `mode=max`. + +Timestamp when the build finished. + +```json + "metadata": { + "buildFinishedOn": "2021-11-17T15:01:00Z", + ... + }, +``` + +## `metadata.completeness` [(SLSA)](https://slsa.dev/provenance/v0.2#metadata.completeness) + +Included with `mode=min` and `mode=max`. + +Defines if the provenance information is complete. + +`completeness.parameters` is true if all the build arguments are included in the +`invocation.parameters` field. When building with `min` mode, the build +arguments are not included in the provenance information and parameters are not +complete. Parameters are also not complete on direct LLB builds that did not use +a frontend. + +`completeness.environment` is always true for BuildKit builds. + +`completeness.materials` is true if `materials` field includes all the +dependencies of the build. When building from un-tracked source in a local +directory, the materials are not complete, while when building from a remote Git +repository all materials can be tracked by BuildKit and `completeness.materials` +is true. + +```json + "metadata": { + "completeness": { + "parameters": true, + "environment": true, + "materials": true + }, + ... + }, +``` + +## `metadata.reproducible` [(SLSA)](https://slsa.dev/provenance/v0.2#metadata.reproducible) + +Defines if the build result is supposed to be byte-by-byte reproducible. This +value can be set by the user with the `reproducible=true` attestation parameter. + +```json + "metadata": { + "reproducible": false, + ... + }, +``` + +## `metadata.https://mobyproject.org/buildkit@v1#hermetic` + +Included with `mode=min` and `mode=max`. + +This extension field is set to true if the build was hermetic and did not access +the network. In Dockerfiles, a build is hermetic if it does not use `RUN` +commands or disables network with `--network=none` flag. + +```json + "metadata": { + "https://mobyproject.org/buildkit@v1#hermetic": true, + ... + }, +``` + +## `metadata.https://mobyproject.org/buildkit@v1#metadata` + +Partially included with `mode=min`. + +This extension field defines BuildKit-specific additional metadata that is not +part of the SLSA provenance spec. + +```json + "metadata": { + "https://mobyproject.org/buildkit@v1#metadata": { + "source": {...}, + "layers": {...}, + "vcs": {...}, + }, + ... + }, +``` + +### `source` + +Only included with `mode=max`. + +Defines a source mapping of LLB build steps, defined in the +`buildConfig.llbDefinition` field, to their original source code (for example, +Dockerfile commands). The `source.locations` field contains the ranges of all +the Dockerfile commands ran in an LLB step. `source.infos` array contains the +source code itself. This mapping is present if the BuildKit frontend provided it +when creating the LLB definition. + +### `layers` + +Only included with `mode=max`. + +Defines the layer mapping of LLB build step mounts defined in +`buildConfig.llbDefinition` to the OCI descriptors of equivalent layers. This +mapping is present if the layer data was available, usually when attestation is +for an image or if the build step pulled in image data as part of the build. + +### `vcs` + +Included with `mode=min` and `mode=max`. + +Defines optional metadata for the version control system used for the build. If +a build uses a remote context from Git repository, BuildKit extracts the details +of the version control system automatically and displays it in the +`invocation.configSource` field. But if the build uses a source from a local +directory, the VCS information is lost even if the directory contained a Git +repository. In this case, the build client can send additional `vcs:source` and +`vcs:revision` build options and BuildKit will add them to the provenance +attestations as extra metadata. Note that, contrary to the +`invocation.configSource` field, BuildKit doesn't verify the `vcs` values, and +as such they can't be trusted and should only be used as a metadata hint. + +## Output + +To inspect the provenance that was generated and attached to a container image, +you can use the `docker buildx imagetools` command to inspect the image in a +registry. Inspecting the attestation displays the format described in the +[attestation storage specification](./attestation-storage.md). + +For example, inspecting a simple Docker image based on `alpine:latest` results +in a provenance attestation similar to the following, for a `mode=min` build: + +```json +{ + "_type": "https://in-toto.io/Statement/v0.1", + "predicateType": "https://slsa.dev/provenance/v0.2", + "subject": [ + { + "name": "pkg:docker//@?platform=", + "digest": { + "sha256": "e8275b2b76280af67e26f068e5d585eb905f8dfd2f1918b3229db98133cb4862" + } + } + ], + "predicate": { + "builder": { + "id": "" + }, + "buildType": "https://mobyproject.org/buildkit@v1", + "materials": [ + { + "uri": "pkg:docker/alpine@latest?platform=linux%2Famd64", + "digest": { + "sha256": "8914eb54f968791faf6a8638949e480fef81e697984fba772b3976835194c6d4" + } + } + ], + "invocation": { + "configSource": { + "entryPoint": "Dockerfile" + }, + "parameters": { + "frontend": "dockerfile.v0", + "args": {}, + "locals": [ + { + "name": "context" + }, + { + "name": "dockerfile" + } + ] + }, + "environment": { + "platform": "linux/amd64" + } + }, + "metadata": { + "buildInvocationID": "yirbp1aosi1vqjmi3z6bc75nb", + "buildStartedOn": "2022-12-08T11:48:59.466513707Z", + "buildFinishedOn": "2022-12-08T11:49:01.256820297Z", + "reproducible": false, + "completeness": { + "parameters": true, + "environment": true, + "materials": false + }, + "https://mobyproject.org/buildkit@v1#metadata": {} + } + } +} +``` + +For a similar build, but with `mode=max`: + +```json +{ + "_type": "https://in-toto.io/Statement/v0.1", + "predicateType": "https://slsa.dev/provenance/v0.2", + "subject": [ + { + "name": "pkg:docker//@?platform=", + "digest": { + "sha256": "e8275b2b76280af67e26f068e5d585eb905f8dfd2f1918b3229db98133cb4862" + } + } + ], + "predicate": { + "builder": { + "id": "" + }, + "buildType": "https://mobyproject.org/buildkit@v1", + "materials": [ + { + "uri": "pkg:docker/alpine@latest?platform=linux%2Famd64", + "digest": { + "sha256": "8914eb54f968791faf6a8638949e480fef81e697984fba772b3976835194c6d4" + } + } + ], + "invocation": { + "configSource": { + "entryPoint": "Dockerfile" + }, + "parameters": { + "frontend": "dockerfile.v0", + "args": {}, + "locals": [ + { + "name": "context" + }, + { + "name": "dockerfile" + } + ] + }, + "environment": { + "platform": "linux/amd64" + } + }, + "buildConfig": { + "llbDefinition": [ + { + "id": "step0", + "op": { + "Op": { + "source": { + "identifier": "docker-image://docker.io/library/alpine:latest@sha256:8914eb54f968791faf6a8638949e480fef81e697984fba772b3976835194c6d4" + } + }, + "platform": { + "Architecture": "amd64", + "OS": "linux" + }, + "constraints": {} + } + }, + { + "id": "step1", + "op": { + "Op": null + }, + "inputs": ["step0:0"] + } + ] + }, + "metadata": { + "buildInvocationID": "46ue2x93k3xj5l463dektwldw", + "buildStartedOn": "2022-12-08T11:50:54.953375437Z", + "buildFinishedOn": "2022-12-08T11:50:55.447841328Z", + "reproducible": false, + "completeness": { + "parameters": true, + "environment": true, + "materials": false + }, + "https://mobyproject.org/buildkit@v1#metadata": { + "source": { + "locations": { + "step0": { + "locations": [ + { + "ranges": [ + { + "start": { + "line": 1 + }, + "end": { + "line": 1 + } + } + ] + } + ] + } + }, + "infos": [ + { + "filename": "Dockerfile", + "data": "RlJPTSBhbHBpbmU6bGF0ZXN0Cg==", + "llbDefinition": [ + { + "id": "step0", + "op": { + "Op": { + "source": { + "identifier": "local://dockerfile", + "attrs": { + "local.differ": "none", + "local.followpaths": "[\"Dockerfile\",\"Dockerfile.dockerignore\",\"dockerfile\"]", + "local.session": "q2jnwdkas0i0iu4knchd92jaz", + "local.sharedkeyhint": "dockerfile" + } + } + }, + "constraints": {} + } + }, + { + "id": "step1", + "op": { + "Op": null + }, + "inputs": ["step0:0"] + } + ] + } + ] + }, + "layers": { + "step0:0": [ + [ + { + "mediaType": "application/vnd.oci.image.layer.v1.tar+gzip", + "digest": "sha256:c158987b05517b6f2c5913f3acef1f2182a32345a304fe357e3ace5fadcad715", + "size": 3370706 + } + ] + ] + } + } + } + } +} +``` diff --git a/docs/attestations/slsa-provenance.md b/docs/attestations/slsa-provenance.md new file mode 100644 index 000000000000..a1437e717027 --- /dev/null +++ b/docs/attestations/slsa-provenance.md @@ -0,0 +1,99 @@ +# SLSA provenance + +BuildKit supports automatic creation of provenance attestations for the build +process. Provenance attestations record information describing how a build was +created, and is important for tracking the security of your software artifacts. + +Provenance attestations created by BuildKit include details such as: + +- Build parameters and environment. +- Build timestamps. +- Version control metadata for your build sources. +- Build dependencies with their immutable checksums. For example, base images or external URLs used by the build. +- Descriptions of all build steps, with their source and layer mappings. + +Provenance generated by BuildKit is wrapped inside [in-toto attestations](https://github.com/in-toto/attestation) +in the [SLSA Provenance format](https://slsa.dev/provenance/v0.2). + +For more information about how the attestation fields get generated, see [SLSA definitions](./slsa-definitions.md). + +## Build with provenance attestations + +To build an image with provenance attestations using `buildctl`, use the `attest:provenance` option: + +```bash +buildctl build \ + --frontend=dockerfile.v0 \ + --local context=. \ + --local dockerfile=. \ + --opt attest:provenance= +``` + +You can also customize the attestations using parameters: + +```bash +buildctl build \ + --frontend=dockerfile.v0 \ + --local context=. \ + --local dockerfile=. \ + --opt attest:provenance=mode=min,inline-only=true +``` + +All BuildKit exporters support attaching attestations to build results. +When the final output format is a container image (`image` or `oci` exporter), provenance is attached +to the image using the format described in the [attestation storage specification](./attestation-storage.md). +When creating a multi-platform image, each platform version of the image gets its own provenance. + +If you use the `local` or `tar` exporter, the provenance will be written to a file named `provenance.json` +and exported with your build result, in the root directory. + +## Parameters + +| Parameter | Type | Default | Description | +| -------------- | -------------- | ---------------- | ----------------------------------------------------------------------------------------------------------- | +| `mode` | `min`,`max` | `max` | Configures the amount of provenance to be generated. See [mode](#mode) | +| `builder-id` | String | | Explicitly set SLSA [`builder.id`](https://slsa.dev/provenance/v0.2#builder.id) field | +| `filename` | String | `provenance.json` | Set filename for provenance attestation when exported with `local` or `tar` exporter | +| `reproducible` | `true`,`false` | `false` | Explicitly set SLSA [`metadata.reproducible`](https://slsa.dev/provenance/v0.2#metadata.reproducible) field | +| `inline-only` | `true`,`false` | `false` | Only embed provenance into exporters that support inline content. See [inline-only](#inline-only) | + +### `mode` + +Provenance can be generated in one of two modes: `min` or `max`. By default, +when provenance is enabled, the `mode` parameter will be set to `max`. + +In `min` mode, BuildKit generates only the bare minimum amount of provenance, +including: + +- Build timestamps +- The frontend used +- The build materials + +However, the values of build arguments, the identities of secrets, and rich +layer metadata will not be included. `mode=min` should be safe to set on all +builds, as it does not leak information from any part of the build environment. + +In `max` mode, BuildKit generates all of the above, as well as: + +- The source Dockerfile, and rich layer metadata with sourcemaps to connect the + source with the build result +- The values of passed build arguments +- Metadata about secrets and ssh mounts + +Wherever possible, you should prefer `mode=max` as it contains significantly +more detailed information for analysis. However, on some builds it may not be +appropriate, as it includes the values of various build arguments and metadata +about secrets - these builds should be refactored to prefer passing hidden +values through secrets wherever possible to prevent unnecessary information +leakage. + +### `inline-only` + +By default, provenance is by included in all exporters that support +attestations. The `inline-only` parameter allows configuring this behavior, to +only include the provenance results in exporters that support inline content, +specifically only exporters that produce container images. + +Since other exporters produce attestations into separate files, in their +filesystems, you may not want to include the provenance in these cases. + diff --git a/executor/containerdexecutor/executor.go b/executor/containerdexecutor/executor.go index 75d9e819bae3..ac195c431588 100644 --- a/executor/containerdexecutor/executor.go +++ b/executor/containerdexecutor/executor.go @@ -45,6 +45,19 @@ type containerdExecutor struct { rootless bool } +// OnCreateRuntimer provides an alternative to OCI hooks for applying network +// configuration to a container. If the [network.Provider] returns a +// [network.Namespace] which also implements this interface, the containerd +// executor will run the callback at the appropriate point in the container +// lifecycle. +type OnCreateRuntimer interface { + // OnCreateRuntime is analogous to the createRuntime OCI hook. The + // function is called after the container is created, before the user + // process has been executed. The argument is the container PID in the + // runtime namespace. + OnCreateRuntime(pid uint32) error +} + // New creates a new executor backed by connection to containerd API func New(client *containerd.Client, root, cgroup string, networkProviders map[pb.NetMode]network.Provider, dnsConfig *oci.DNSConfig, apparmorProfile string, selinux bool, traceSocket string, rootless bool) executor.Executor { // clean up old hosts/resolv.conf file. ignore errors @@ -210,6 +223,12 @@ func (w *containerdExecutor) Run(ctx context.Context, id string, root executor.M } }() + if nn, ok := namespace.(OnCreateRuntimer); ok { + if err := nn.OnCreateRuntime(task.Pid()); err != nil { + return err + } + } + trace.SpanFromContext(ctx).AddEvent("Container created") err = w.runProcess(ctx, task, process.Resize, process.Signal, func() { startedOnce.Do(func() { diff --git a/exporter/attestation/unbundle.go b/exporter/attestation/unbundle.go index a0dc21731b33..498b13624bad 100644 --- a/exporter/attestation/unbundle.go +++ b/exporter/attestation/unbundle.go @@ -5,6 +5,7 @@ import ( "encoding/json" "os" "path" + "strings" "github.com/containerd/continuity/fs" intoto "github.com/in-toto/in-toto-golang/in_toto" @@ -20,6 +21,10 @@ import ( // Unbundle iterates over all provided result attestations and un-bundles any // bundled attestations by loading them from the provided refs map. func Unbundle(ctx context.Context, s session.Group, bundled []exporter.Attestation) ([]exporter.Attestation, error) { + if err := Validate(bundled); err != nil { + return nil, err + } + eg, ctx := errgroup.WithContext(ctx) unbundled := make([][]exporter.Attestation, len(bundled)) @@ -28,6 +33,12 @@ func Unbundle(ctx context.Context, s session.Group, bundled []exporter.Attestati eg.Go(func() error { switch att.Kind { case gatewaypb.AttestationKindInToto: + if strings.HasPrefix(att.InToto.PredicateType, "https://slsa.dev/provenance/") { + if att.ContentFunc == nil { + // provenance may only be set buildkit-side using ContentFunc + return errors.New("frontend may not set provenance attestations") + } + } unbundled[i] = append(unbundled[i], att) case gatewaypb.AttestationKindBundle: if att.ContentFunc != nil { @@ -52,6 +63,11 @@ func Unbundle(ctx context.Context, s session.Group, bundled []exporter.Attestati if err != nil { return err } + for _, att := range atts { + if strings.HasPrefix(att.InToto.PredicateType, "https://slsa.dev/provenance/") { + return errors.New("frontend may not bundle provenance attestations") + } + } unbundled[i] = append(unbundled[i], atts...) } return nil @@ -65,10 +81,9 @@ func Unbundle(ctx context.Context, s session.Group, bundled []exporter.Attestati for _, atts := range unbundled { joined = append(joined, atts...) } - for _, att := range joined { - if err := validate(att); err != nil { - return nil, err - } + + if err := Validate(joined); err != nil { + return nil, err } return joined, nil } @@ -117,6 +132,7 @@ func unbundle(ctx context.Context, root string, bundle exporter.Attestation) ([] } unbundled = append(unbundled, exporter.Attestation{ Kind: gatewaypb.AttestationKindInToto, + Metadata: bundle.Metadata, Path: path.Join(bundle.Path, entry.Name()), ContentFunc: func() ([]byte, error) { return predicate, nil }, InToto: result.InTotoAttestation{ @@ -128,8 +144,17 @@ func unbundle(ctx context.Context, root string, bundle exporter.Attestation) ([] return unbundled, nil } +func Validate(atts []exporter.Attestation) error { + for _, att := range atts { + if err := validate(att); err != nil { + return err + } + } + return nil +} + func validate(att exporter.Attestation) error { - if att.Path == "" { + if att.Kind != gatewaypb.AttestationKindBundle && att.Path == "" { return errors.New("attestation does not have set path") } if att.Ref == nil && att.ContentFunc == nil { diff --git a/exporter/containerimage/attestations.go b/exporter/containerimage/attestations.go index 680be58596d9..782c18733035 100644 --- a/exporter/containerimage/attestations.go +++ b/exporter/containerimage/attestations.go @@ -14,6 +14,7 @@ import ( gatewaypb "github.com/moby/buildkit/frontend/gateway/pb" "github.com/moby/buildkit/session" "github.com/moby/buildkit/solver" + "github.com/moby/buildkit/solver/result" "github.com/moby/buildkit/version" ocispecs "github.com/opencontainers/image-spec/specs-go/v1" "github.com/pkg/errors" @@ -35,6 +36,13 @@ func supplementSBOM(ctx context.Context, s session.Group, target cache.Immutable if att.InToto.PredicateType != intoto.PredicateSPDX { return att, nil } + name, ok := att.Metadata[result.AttestationSBOMCore] + if !ok { + return att, nil + } + if n, _, _ := strings.Cut(att.Path, "."); n != string(name) { + return att, nil + } content, err := attestation.ReadAll(ctx, s, att) if err != nil { @@ -43,7 +51,8 @@ func supplementSBOM(ctx context.Context, s session.Group, target cache.Immutable doc, err := decodeSPDX(content) if err != nil { - return att, err + // ignore decoding error + return att, nil } layers, err := newFileLayerFinder(target, targetRemote) diff --git a/exporter/containerimage/export.go b/exporter/containerimage/export.go index 9c5262377d3f..55eaf3ff5803 100644 --- a/exporter/containerimage/export.go +++ b/exporter/containerimage/export.go @@ -78,7 +78,8 @@ func (e *imageExporter) Resolve(ctx context.Context, opt map[string]string) (exp RefCfg: cacheconfig.RefConfig{ Compression: compression.New(compression.Default), }, - BuildInfo: true, + BuildInfo: true, + ForceInlineAttestations: true, }, store: true, } diff --git a/exporter/containerimage/opts.go b/exporter/containerimage/opts.go index 057bd299e4f2..c12d86127e0f 100644 --- a/exporter/containerimage/opts.go +++ b/exporter/containerimage/opts.go @@ -12,13 +12,14 @@ import ( ) const ( - keyImageName = "name" - keyLayerCompression = "compression" - keyCompressionLevel = "compression-level" - keyForceCompression = "force-compression" - keyOCITypes = "oci-mediatypes" - keyBuildInfo = "buildinfo" - keyBuildInfoAttrs = "buildinfo-attrs" + keyImageName = "name" + keyLayerCompression = "compression" + keyCompressionLevel = "compression-level" + keyForceCompression = "force-compression" + keyOCITypes = "oci-mediatypes" + keyBuildInfo = "buildinfo" + keyBuildInfoAttrs = "buildinfo-attrs" + keyForceInlineAttestations = "attestation-inline" // preferNondistLayersKey is an exporter option which can be used to mark a layer as non-distributable if the layer reference was // already found to use a non-distributable media type. @@ -34,6 +35,8 @@ type ImageCommitOpts struct { BuildInfoAttrs bool Annotations AnnotationsGroup Epoch *time.Time + + ForceInlineAttestations bool // force inline attestations to be attached } func (c *ImageCommitOpts) Load(opt map[string]string) (map[string]string, error) { @@ -73,6 +76,8 @@ func (c *ImageCommitOpts) Load(opt map[string]string) (map[string]string, error) err = parseBoolWithDefault(&c.BuildInfo, k, v, true) case keyBuildInfoAttrs: err = parseBoolWithDefault(&c.BuildInfoAttrs, k, v, false) + case keyForceInlineAttestations: + err = parseBool(&c.ForceInlineAttestations, k, v) case keyPreferNondistLayers: err = parseBool(&c.RefCfg.PreferNonDistributable, k, v) default: diff --git a/exporter/containerimage/writer.go b/exporter/containerimage/writer.go index c9b5d48b804e..068d86958f8f 100644 --- a/exporter/containerimage/writer.go +++ b/exporter/containerimage/writer.go @@ -69,22 +69,24 @@ func (ic *ImageWriter) Commit(ctx context.Context, inp *exporter.Source, session return nil, err } - requiredAttestations := false - for _, p := range ps.Platforms { - if atts, ok := inp.Attestations[p.ID]; ok { - atts = attestation.Filter(atts, nil, map[string][]byte{ - result.AttestationInlineOnlyKey: []byte(strconv.FormatBool(true)), - }) - if len(atts) > 0 { - requiredAttestations = true - break + if !isMap { + // enable index if we need to include attestations + for _, p := range ps.Platforms { + if atts, ok := inp.Attestations[p.ID]; ok { + if !opts.ForceInlineAttestations { + // if we don't need force inline attestations (for oci + // exporter), filter them out + atts = attestation.Filter(atts, nil, map[string][]byte{ + result.AttestationInlineOnlyKey: []byte(strconv.FormatBool(true)), + }) + } + if len(atts) > 0 { + isMap = true + break + } } } } - if requiredAttestations { - isMap = true - } - if opts.Epoch == nil { if tm, ok, err := epoch.ParseSource(inp); err != nil { return nil, err @@ -108,9 +110,6 @@ func (ic *ImageWriter) Commit(ctx context.Context, inp *exporter.Source, session if len(ps.Platforms) > 1 { return nil, errors.Errorf("cannot export multiple platforms without multi-platform enabled") } - if requiredAttestations { - return nil, errors.Errorf("cannot export attestations without multi-platform enabled") - } var ref cache.ImmutableRef var p exptypes.Platform diff --git a/exporter/local/fs.go b/exporter/local/fs.go index bb90cfdd0d32..c5a524aae32f 100644 --- a/exporter/local/fs.go +++ b/exporter/local/fs.go @@ -132,7 +132,7 @@ func CreateFS(ctx context.Context, sessionID string, k string, ref cache.Immutab names := map[string]struct{}{} for i, stmt := range stmts { - dt, err := json.Marshal(stmt) + dt, err := json.MarshalIndent(stmt, "", " ") if err != nil { return nil, nil, errors.Wrap(err, "failed to marshal attestation") } diff --git a/frontend/attestations/sbom/sbom.go b/frontend/attestations/sbom/sbom.go index 54e8e316a2a2..b4446aed45f0 100644 --- a/frontend/attestations/sbom/sbom.go +++ b/frontend/attestations/sbom/sbom.go @@ -88,7 +88,8 @@ func CreateSBOMScanner(ctx context.Context, resolver llb.ImageMetaResolver, scan Kind: gatewaypb.AttestationKindBundle, Ref: stsbom, Metadata: map[string][]byte{ - result.AttestationReasonKey: result.AttestationReasonSBOM, + result.AttestationReasonKey: []byte(result.AttestationReasonSBOM), + result.AttestationSBOMCore: []byte(CoreSBOMName), }, InToto: result.InTotoAttestation{ PredicateType: intoto.PredicateSPDX, diff --git a/frontend/dockerfile/dockerfile_provenance_test.go b/frontend/dockerfile/dockerfile_provenance_test.go index f315ecb963a2..94f1374db0b1 100644 --- a/frontend/dockerfile/dockerfile_provenance_test.go +++ b/frontend/dockerfile/dockerfile_provenance_test.go @@ -198,7 +198,7 @@ RUN echo "ok" > /foo require.False(t, pred.Metadata.Completeness.Materials) require.False(t, pred.Metadata.Reproducible) - require.False(t, pred.Metadata.Completeness.Hermetic) + require.False(t, pred.Metadata.Hermetic) if mode == "max" || mode == "" { require.Equal(t, 2, len(pred.Metadata.BuildKitMetadata.Layers)) @@ -347,8 +347,8 @@ COPY myapp.Dockerfile / require.Equal(t, 0, len(pred.Invocation.Parameters.Locals)) require.True(t, pred.Metadata.Completeness.Materials) - require.True(t, pred.Metadata.Completeness.Hermetic) require.True(t, pred.Metadata.Completeness.Environment) + require.True(t, pred.Metadata.Hermetic) if isClient { require.False(t, pred.Metadata.Completeness.Parameters) @@ -862,3 +862,40 @@ RUN --mount=type=secret,id=mysecret --mount=type=secret,id=othersecret --mount=t require.Equal(t, "default", pred.Invocation.Parameters.SSH[0].ID) require.True(t, pred.Invocation.Parameters.SSH[0].Optional) } + +func testNilProvenance(t *testing.T, sb integration.Sandbox) { + integration.CheckFeatureCompat(t, sb, integration.FeatureProvenance) + ctx := sb.Context() + + c, err := client.New(ctx, sb.Address()) + require.NoError(t, err) + defer c.Close() + + f := getFrontend(t, sb) + + dockerfile := []byte(` +FROM scratch +ENV FOO=bar +`) + dir, err := integration.Tmpdir( + t, + fstest.CreateFile("Dockerfile", dockerfile, 0600), + ) + require.NoError(t, err) + + _, err = f.Solve(sb.Context(), c, client.SolveOpt{ + LocalDirs: map[string]string{ + builder.DefaultLocalNameDockerfile: dir, + builder.DefaultLocalNameContext: dir, + }, + FrontendAttrs: map[string]string{ + "attest:provenance": "mode=max", + }, + Exports: []client.ExportEntry{ + { + Type: client.ExporterImage, + }, + }, + }, nil) + require.NoError(t, err) +} diff --git a/frontend/dockerfile/dockerfile_test.go b/frontend/dockerfile/dockerfile_test.go index 53a0d0cf0d15..f99ff1181d54 100644 --- a/frontend/dockerfile/dockerfile_test.go +++ b/frontend/dockerfile/dockerfile_test.go @@ -154,6 +154,7 @@ var allTests = integration.TestFuncs( testClientFrontendProvenance, testClientLLBProvenance, testSecretSSHProvenance, + testNilProvenance, testSBOMScannerArgs, ) @@ -3598,7 +3599,7 @@ COPY --from=busybox /etc/passwd test dockerfile = []byte(` FROM busybox AS golang -RUN mkdir /usr/bin && echo -n foo > /usr/bin/go +RUN mkdir -p /usr/bin && echo -n foo > /usr/bin/go FROM scratch COPY --from=golang /usr/bin/go go diff --git a/frontend/gateway/forwarder/forward.go b/frontend/gateway/forwarder/forward.go index 9ff531795abc..e13894ba37ed 100644 --- a/frontend/gateway/forwarder/forward.go +++ b/frontend/gateway/forwarder/forward.go @@ -67,6 +67,13 @@ func (c *bridgeClient) Solve(ctx context.Context, req client.SolveRequest) (*cli if err != nil { return nil, c.wrapSolveError(err) } + for _, atts := range res.Attestations { + for _, att := range atts { + if att.ContentFunc != nil { + return nil, errors.Errorf("attestation callback cannot be sent through gateway") + } + } + } c.mu.Lock() cRes, err := result.ConvertResult(res, func(r solver.ResultProxy) (client.Reference, error) { @@ -178,6 +185,13 @@ func (c *bridgeClient) toFrontendResult(r *client.Result) (*frontend.Result, err if r == nil { return nil, nil } + for _, atts := range r.Attestations { + for _, att := range atts { + if att.ContentFunc != nil { + return nil, errors.Errorf("attestation callback cannot be sent through gateway") + } + } + } res, err := result.ConvertResult(r, func(r client.Reference) (solver.ResultProxy, error) { rr, ok := r.(*ref) @@ -186,13 +200,6 @@ func (c *bridgeClient) toFrontendResult(r *client.Result) (*frontend.Result, err } return rr.acquireResultProxy(), nil }) - for _, atts := range res.Attestations { - for _, att := range atts { - if att.ContentFunc != nil { - return nil, errors.Errorf("attestation callback cannot be sent through gateway") - } - } - } if err != nil { return nil, err } diff --git a/go.mod b/go.mod index 2a831069b6a8..4dc34195436b 100644 --- a/go.mod +++ b/go.mod @@ -59,9 +59,9 @@ require ( github.com/pkg/profile v1.5.0 github.com/serialx/hashring v0.0.0-20190422032157-8b2912629002 github.com/sirupsen/logrus v1.9.0 - github.com/spdx/tools-golang v0.3.1-0.20221108182156-8a01147e6342 + github.com/spdx/tools-golang v0.3.1-0.20230104082527-d6f58551be3f github.com/stretchr/testify v1.8.0 - github.com/tonistiigi/fsutil v0.0.0-20221114235510-0127568185cf + github.com/tonistiigi/fsutil v0.0.0-20230105215944-fb433841cbfa github.com/tonistiigi/go-actions-cache v0.0.0-20220404170428-0bdeb6e1eac7 github.com/tonistiigi/go-archvariant v1.0.0 github.com/tonistiigi/units v0.0.0-20180711220420-6950e57a87ea @@ -152,5 +152,3 @@ require ( golang.org/x/text v0.5.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) - -replace github.com/spdx/tools-golang => github.com/jedevc/spdx-tools-golang v0.0.0-20221205121515-6fe9d226281a diff --git a/go.sum b/go.sum index 5707a032f27b..7c859ea97572 100644 --- a/go.sum +++ b/go.sum @@ -912,8 +912,6 @@ github.com/j-keck/arping v0.0.0-20160618110441-2cf9dc699c56/go.mod h1:ymszkNOg6t github.com/j-keck/arping v1.0.2/go.mod h1:aJbELhR92bSk7tp79AWM/ftfc90EfEi2bQJrbBFOsPw= github.com/jaguilar/vt100 v0.0.0-20150826170717-2703a27b14ea/go.mod h1:QMdK4dGB3YhEW2BmA1wgGpPYI3HZy/5gD705PXKUVSg= github.com/jarcoal/httpmock v1.0.5/go.mod h1:ATjnClrvW/3tijVmpL/va5Z3aAyGvqU3gCT8nX0Txik= -github.com/jedevc/spdx-tools-golang v0.0.0-20221205121515-6fe9d226281a h1:oEb/YxUfXfGhRJVEg4yBfSnB6ZgS9aBu4RAM0fB9XsA= -github.com/jedevc/spdx-tools-golang v0.0.0-20221205121515-6fe9d226281a/go.mod h1:VHzvNsKAfAGqs4ZvwRL+7a0dNsL20s7lGui4K9C0xQM= github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU= github.com/jingyugao/rowserrcheck v0.0.0-20191204022205-72ab7603b68a/go.mod h1:xRskid8CManxVta/ALEhJha/pweKBaVG6fWgc0yH25s= github.com/jirfag/go-printf-func-name v0.0.0-20191110105641-45db9963cdd3/go.mod h1:HEWGJkRDzjJY2sqdDwxccsGicWEf9BQOZsq2tV+xzM0= @@ -1318,6 +1316,8 @@ github.com/sourcegraph/go-diff v0.5.1/go.mod h1:j2dHj3m8aZgQO8lMTcTnBcXkRRRqi34c github.com/sourcegraph/go-diff v0.5.3/go.mod h1:v9JDtjCE4HHHCZGId75rg8gkKKa98RVjBcBGsVmMmak= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spdx/gordf v0.0.0-20201111095634-7098f93598fb/go.mod h1:uKWaldnbMnjsSAXRurWqqrdyZen1R7kxl8TkmWk2OyM= +github.com/spdx/tools-golang v0.3.1-0.20230104082527-d6f58551be3f h1:9B623Cfs+mclYK6dsae7gLSwuIBHvlgmEup87qpqsAQ= +github.com/spdx/tools-golang v0.3.1-0.20230104082527-d6f58551be3f/go.mod h1:VHzvNsKAfAGqs4ZvwRL+7a0dNsL20s7lGui4K9C0xQM= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= @@ -1379,8 +1379,8 @@ github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802/go.mod h1 github.com/tommy-muehle/go-mnd v1.1.1/go.mod h1:dSUh0FtTP8VhvkL1S+gUR1OKd9ZnSaozuI6r3m6wOig= github.com/tommy-muehle/go-mnd v1.3.1-0.20200224220436-e6f9a994e8fa/go.mod h1:dSUh0FtTP8VhvkL1S+gUR1OKd9ZnSaozuI6r3m6wOig= github.com/tonistiigi/fsutil v0.0.0-20201103201449-0834f99b7b85/go.mod h1:a7cilN64dG941IOXfhJhlH0qB92hxJ9A1ewrdUmJ6xo= -github.com/tonistiigi/fsutil v0.0.0-20221114235510-0127568185cf h1:2n2v98sRhXEG0Kh7+EvctaNIyOim36Ekp4pGDzbuvO8= -github.com/tonistiigi/fsutil v0.0.0-20221114235510-0127568185cf/go.mod h1:AvLEd1LEIl64G2Jpgwo7aVV5lGH0ePcKl0ygGIHNYl8= +github.com/tonistiigi/fsutil v0.0.0-20230105215944-fb433841cbfa h1:XOFp/3aBXlqmOFAg3r6e0qQjPnK5I970LilqX+Is1W8= +github.com/tonistiigi/fsutil v0.0.0-20230105215944-fb433841cbfa/go.mod h1:AvLEd1LEIl64G2Jpgwo7aVV5lGH0ePcKl0ygGIHNYl8= github.com/tonistiigi/go-actions-cache v0.0.0-20220404170428-0bdeb6e1eac7 h1:8eY6m1mjgyB8XySUR7WvebTM8D/Vs86jLJzD/Tw7zkc= github.com/tonistiigi/go-actions-cache v0.0.0-20220404170428-0bdeb6e1eac7/go.mod h1:qqvyZqkfwkoJuPU/bw61bItaoO0SJ8YSW0vSVRRvsRg= github.com/tonistiigi/go-archvariant v1.0.0 h1:5LC1eDWiBNflnTF1prCiX09yfNHIxDC/aukdhCdTyb0= diff --git a/session/sshforward/copy.go b/session/sshforward/copy.go index 8d05e9484144..936e1826af92 100644 --- a/session/sshforward/copy.go +++ b/session/sshforward/copy.go @@ -14,16 +14,19 @@ type Stream interface { } func Copy(ctx context.Context, conn io.ReadWriteCloser, stream Stream, closeStream func() error) error { + defer conn.Close() g, ctx := errgroup.WithContext(ctx) g.Go(func() (retErr error) { p := &BytesMessage{} for { if err := stream.RecvMsg(p); err != nil { - conn.Close() if err == io.EOF { + // indicates client performed CloseSend, but they may still be + // reading data, so don't close conn yet return nil } + conn.Close() return errors.WithStack(err) } select { diff --git a/solver/llbsolver/history.go b/solver/llbsolver/history.go index 80bbb5ae7012..5c326f83fed3 100644 --- a/solver/llbsolver/history.go +++ b/solver/llbsolver/history.go @@ -44,6 +44,12 @@ type HistoryQueue struct { deleted map[string]struct{} } +type StatusImportResult struct { + Descriptor ocispecs.Descriptor + NumCachedSteps int + NumTotalSteps int +} + func NewHistoryQueue(opt HistoryQueueOpt) *HistoryQueue { if opt.CleanConfig == nil { opt.CleanConfig = &config.HistoryConfig{ @@ -433,7 +439,7 @@ func (w *Writer) Commit(ctx context.Context) (*ocispecs.Descriptor, func(), erro }, nil } -func (h *HistoryQueue) ImportStatus(ctx context.Context, ch chan *client.SolveStatus) (_ *ocispecs.Descriptor, _ func(), err error) { +func (h *HistoryQueue) ImportStatus(ctx context.Context, ch chan *client.SolveStatus) (_ *StatusImportResult, _ func(), err error) { defer func() { if ch == nil { return @@ -455,8 +461,19 @@ func (h *HistoryQueue) ImportStatus(ctx context.Context, ch chan *client.SolveSt } }() + vtxMap := make(map[digest.Digest]bool) + buf := make([]byte, 32*1024) for st := range ch { + for _, vtx := range st.Vertexes { + if _, ok := vtxMap[vtx.Digest]; !ok { + vtxMap[vtx.Digest] = false + } + if vtx.Cached { + vtxMap[vtx.Digest] = true + } + } + hdr := make([]byte, 4) for _, pst := range st.Marshal() { sz := pst.Size() @@ -479,7 +496,23 @@ func (h *HistoryQueue) ImportStatus(ctx context.Context, ch chan *client.SolveSt if err := bufW.Flush(); err != nil { return nil, nil, err } - return w.Commit(ctx) + desc, release, err := w.Commit(ctx) + if err != nil { + return nil, nil, err + } + + numCached := 0 + for _, cached := range vtxMap { + if cached { + numCached++ + } + } + + return &StatusImportResult{ + Descriptor: *desc, + NumCachedSteps: numCached, + NumTotalSteps: len(vtxMap), + }, release, nil } func (h *HistoryQueue) Listen(ctx context.Context, req *controlapi.BuildHistoryRequest, f func(*controlapi.BuildHistoryEvent) error) error { diff --git a/solver/llbsolver/proc/provenance.go b/solver/llbsolver/proc/provenance.go index 4d8506b2987f..1af3af196028 100644 --- a/solver/llbsolver/proc/provenance.go +++ b/solver/llbsolver/proc/provenance.go @@ -32,6 +32,10 @@ func ProvenanceProcessor(attrs map[string]string) llbsolver.Processor { return nil, errors.Errorf("no build info found for provenance %s", p.ID) } + if cp == nil { + continue + } + ref, ok := res.FindRef(p.ID) if !ok { return nil, errors.Errorf("could not find ref %s", p.ID) @@ -42,16 +46,21 @@ func ProvenanceProcessor(attrs map[string]string) llbsolver.Processor { return nil, err } + filename := "provenance.json" + if v, ok := attrs["filename"]; ok { + filename = v + } + res.AddAttestation(p.ID, llbsolver.Attestation{ Kind: gatewaypb.AttestationKindInToto, Metadata: map[string][]byte{ - result.AttestationReasonKey: result.AttestationReasonProvenance, + result.AttestationReasonKey: []byte(result.AttestationReasonProvenance), result.AttestationInlineOnlyKey: []byte(strconv.FormatBool(inlineOnly)), }, InToto: result.InTotoAttestation{ PredicateType: slsa02.PredicateSLSAProvenance, }, - Path: "provenance.json", + Path: filename, ContentFunc: func() ([]byte, error) { pr, err := pc.Predicate() if err != nil { diff --git a/solver/llbsolver/provenance.go b/solver/llbsolver/provenance.go index cf8b9b6b45bb..b30581c852d9 100644 --- a/solver/llbsolver/provenance.go +++ b/solver/llbsolver/provenance.go @@ -188,7 +188,7 @@ type resultRequests struct { platforms []exptypes.Platform } -// filterImagePlatforms filter out images that not for the current platform if an image existist for every platform in a result +// filterImagePlatforms filter out images that not for the current platform if an image exists for every platform in a result func (reqs *resultRequests) filterImagePlatforms(k string, imgs []provenance.ImageSource) []provenance.ImageSource { if len(reqs.platforms) == 0 { return imgs diff --git a/solver/llbsolver/provenance/buildconfig.go b/solver/llbsolver/provenance/buildconfig.go index 3234d4b1d7bb..4d9bf85ec1ba 100644 --- a/solver/llbsolver/provenance/buildconfig.go +++ b/solver/llbsolver/provenance/buildconfig.go @@ -141,11 +141,6 @@ func toBuildSteps(def *pb.Definition) ([]BuildStep, map[digest.Digest]int, error if err != nil { return nil, nil, err } - for i := 0; i < len(dgsts)/2; i++ { - j := len(dgsts) - 1 - i - dgsts[i], dgsts[j] = dgsts[j], dgsts[i] - } - indexes := map[digest.Digest]int{} for i, dgst := range dgsts { indexes[dgst] = i @@ -179,7 +174,6 @@ func walkDigests(dgsts []digest.Digest, ops map[digest.Digest]*pb.Op, dgst diges if op == nil { return nil, errors.Errorf("invalid nil input %v", dgst) } - dgsts = append(dgsts, dgst) visited[dgst] = struct{}{} for _, inp := range op.Inputs { var err error @@ -188,5 +182,6 @@ func walkDigests(dgsts []digest.Digest, ops map[digest.Digest]*pb.Op, dgst diges return nil, err } } + dgsts = append(dgsts, dgst) return dgsts, nil } diff --git a/solver/llbsolver/provenance/predicate.go b/solver/llbsolver/provenance/predicate.go index ec4f68cd598a..7608f5cfae9c 100644 --- a/solver/llbsolver/provenance/predicate.go +++ b/solver/llbsolver/provenance/predicate.go @@ -44,13 +44,8 @@ type Environment struct { type ProvenanceMetadata struct { slsa02.ProvenanceMetadata - Completeness ProvenanceComplete `json:"completeness"` - BuildKitMetadata BuildKitMetadata `json:"https://mobyproject.org/buildkit@v1#metadata,omitempty"` -} - -type ProvenanceComplete struct { - slsa02.ProvenanceComplete - Hermetic bool `json:"https://mobyproject.org/buildkit@v1#hermetic,omitempty"` + BuildKitMetadata BuildKitMetadata `json:"https://mobyproject.org/buildkit@v1#metadata,omitempty"` + Hermetic bool `json:"https://mobyproject.org/buildkit@v1#hermetic,omitempty"` } type BuildKitMetadata struct { @@ -211,14 +206,14 @@ func NewPredicate(c *Capture) (*ProvenancePredicate, error) { Materials: materials, }, Metadata: &ProvenanceMetadata{ - Completeness: ProvenanceComplete{ - ProvenanceComplete: slsa02.ProvenanceComplete{ + ProvenanceMetadata: slsa02.ProvenanceMetadata{ + Completeness: slsa02.ProvenanceComplete{ Parameters: c.Frontend != "", Environment: true, Materials: !incompleteMaterials, }, - Hermetic: !incompleteMaterials && !c.NetworkAccess, }, + Hermetic: !incompleteMaterials && !c.NetworkAccess, }, } diff --git a/solver/llbsolver/solver.go b/solver/llbsolver/solver.go index 453e656c762b..daeafba5f9ca 100644 --- a/solver/llbsolver/solver.go +++ b/solver/llbsolver/solver.go @@ -58,6 +58,7 @@ type ExporterRequest struct { type RemoteCacheExporter struct { remotecache.Exporter solver.CacheExportMode + IgnoreError bool } // ResolveWorkerFunc returns default worker for the temporary default non-distributed use cases @@ -269,17 +270,19 @@ func (s *Solver) recordBuildHistory(ctx context.Context, id string, req frontend } eg.Go(func() error { - desc, releaseStatus, err := s.history.ImportStatus(ctx2, ch) + st, releaseStatus, err := s.history.ImportStatus(ctx2, ch) if err != nil { return err } mu.Lock() releasers = append(releasers, releaseStatus) rec.Logs = &controlapi.Descriptor{ - Digest: desc.Digest, - Size_: desc.Size, - MediaType: desc.MediaType, + Digest: st.Descriptor.Digest, + Size_: st.Descriptor.Size, + MediaType: st.Descriptor.MediaType, } + rec.NumCachedSteps = int32(st.NumCachedSteps) + rec.NumTotalSteps = int32(st.NumTotalSteps) mu.Unlock() return nil }) @@ -353,6 +356,7 @@ func (s *Solver) recordBuildHistory(ctx context.Context, id string, req frontend return err } enc := json.NewEncoder(w) + enc.SetIndent("", " ") for _, sp := range spans { if err := enc.Encode(sp); err != nil { return err @@ -569,7 +573,7 @@ func runCacheExporters(ctx context.Context, exporters []RemoteCacheExporter, j * func(exp RemoteCacheExporter, i int) { eg.Go(func() (err error) { id := fmt.Sprint(j.SessionID, "-cache-", i) - return inBuilderContext(ctx, j, exp.Exporter.Name(), id, func(ctx context.Context, _ session.Group) error { + err = inBuilderContext(ctx, j, exp.Exporter.Name(), id, func(ctx context.Context, _ session.Group) error { prepareDone := progress.OneOff(ctx, "preparing build cache for export") if err := result.EachRef(cached, inp, func(res solver.CachedResult, ref cache.ImmutableRef) error { ctx = withDescHandlerCacheOpts(ctx, ref) @@ -588,13 +592,13 @@ func runCacheExporters(ctx context.Context, exporters []RemoteCacheExporter, j * }); err != nil { return prepareDone(err) } - prepareDone(nil) resps[i], err = exp.Finalize(ctx) - if err != nil { - return err - } - return nil + return prepareDone(err) }) + if exp.IgnoreError { + err = nil + } + return err }) }(exp, i) } diff --git a/solver/result/attestation.go b/solver/result/attestation.go index 6fa8403db67c..77af74da1906 100644 --- a/solver/result/attestation.go +++ b/solver/result/attestation.go @@ -9,12 +9,13 @@ import ( const ( AttestationReasonKey = "reason" + AttestationSBOMCore = "sbom-core" AttestationInlineOnlyKey = "inline-only" ) -var ( - AttestationReasonSBOM = []byte("sbom") - AttestationReasonProvenance = []byte("provenance") +const ( + AttestationReasonSBOM = "sbom" + AttestationReasonProvenance = "provenance" ) type Attestation[T any] struct { diff --git a/util/pull/pullprogress/progress.go b/util/pull/pullprogress/progress.go index b743706ed955..93c50106f79d 100644 --- a/util/pull/pullprogress/progress.go +++ b/util/pull/pullprogress/progress.go @@ -106,6 +106,8 @@ func trackProgress(ctx context.Context, desc ocispecs.Descriptor, manager PullMa select { case <-ctx.Done(): onFinalStatus = true + // we need a context for the manager.Status() calls to pass once. after that this function will exit + ctx = context.TODO() case <-ticker.C: } diff --git a/util/testutil/integration/pins.go b/util/testutil/integration/pins.go new file mode 100644 index 000000000000..32145f71714a --- /dev/null +++ b/util/testutil/integration/pins.go @@ -0,0 +1,15 @@ +package integration + +var pins = map[string]map[string]string{ + // busybox is pinned to 1.35. Newer produces has "illegal instruction" panic on some of Github infra on sha256sum + "busybox:latest": { + "amd64": "sha256:0d5a701f0ca53f38723108687add000e1922f812d4187dea7feaee85d2f5a6c5", + "arm64v8": "sha256:ffe38d75e44d8ffac4cd6d09777ffc31e94ea0ded6a0164e825a325dc17a3b68", + "library": "sha256:f4ed5f2163110c26d42741fdc92bd1710e118aed4edb19212548e8ca4e5fca22", + }, + "alpine:latest": { + "amd64": "sha256:c0d488a800e4127c334ad20d61d7bc21b4097540327217dfab52262adc02380c", + "arm64v8": "sha256:af06af3514c44a964d3b905b498cf6493db8f1cde7c10e078213a89c87308ba0", + "library": "sha256:8914eb54f968791faf6a8638949e480fef81e697984fba772b3976835194c6d4", + }, +} diff --git a/util/testutil/integration/run.go b/util/testutil/integration/run.go index 5168e087a766..18f6f0b748cd 100644 --- a/util/testutil/integration/run.go +++ b/util/testutil/integration/run.go @@ -264,12 +264,18 @@ func OfficialImages(names ...string) map[string]string { ns := runtime.GOARCH if ns == "arm64" { ns = "arm64v8" - } else if ns != "amd64" && ns != "armhf" { + } else if ns != "amd64" { ns = "library" } m := map[string]string{} for _, name := range names { - m["library/"+name] = "docker.io/" + ns + "/" + name + ref := "docker.io/" + ns + "/" + name + if pns, ok := pins[name]; ok { + if dgst, ok := pns[ns]; ok { + ref += "@" + dgst + } + } + m["library/"+name] = ref } return m } diff --git a/util/tracing/detect/detect.go b/util/tracing/detect/detect.go index e005b14d0dfe..13e54bdefc01 100644 --- a/util/tracing/detect/detect.go +++ b/util/tracing/detect/detect.go @@ -73,21 +73,31 @@ func detectExporter() (sdktrace.SpanExporter, error) { return nil, nil } -func detect() error { - tp = trace.NewNoopTracerProvider() - +func getExporter() (sdktrace.SpanExporter, error) { exp, err := detectExporter() if err != nil { - return err + return nil, err + } + + if exp != nil { + exp = &threadSafeExporterWrapper{ + exporter: exp, + } } if Recorder != nil { Recorder.SpanExporter = exp exp = Recorder } + return exp, nil +} + +func detect() error { + tp = trace.NewNoopTracerProvider() - if exp == nil { - return nil + exp, err := getExporter() + if err != nil || exp == nil { + return err } // enable log with traceID when valid exporter diff --git a/util/tracing/detect/threadsafe.go b/util/tracing/detect/threadsafe.go new file mode 100644 index 000000000000..51d14448dfed --- /dev/null +++ b/util/tracing/detect/threadsafe.go @@ -0,0 +1,26 @@ +package detect + +import ( + "context" + "sync" + + sdktrace "go.opentelemetry.io/otel/sdk/trace" +) + +// threadSafeExporterWrapper wraps an OpenTelemetry SpanExporter and makes it thread-safe. +type threadSafeExporterWrapper struct { + mu sync.Mutex + exporter sdktrace.SpanExporter +} + +func (tse *threadSafeExporterWrapper) ExportSpans(ctx context.Context, spans []sdktrace.ReadOnlySpan) error { + tse.mu.Lock() + defer tse.mu.Unlock() + return tse.exporter.ExportSpans(ctx, spans) +} + +func (tse *threadSafeExporterWrapper) Shutdown(ctx context.Context) error { + tse.mu.Lock() + defer tse.mu.Unlock() + return tse.exporter.Shutdown(ctx) +} diff --git a/vendor/github.com/spdx/tools-golang/spdx/v2_2/package.go b/vendor/github.com/spdx/tools-golang/spdx/v2_2/package.go index 77d9c08f5851..2d99e0456b0b 100644 --- a/vendor/github.com/spdx/tools-golang/spdx/v2_2/package.go +++ b/vendor/github.com/spdx/tools-golang/spdx/v2_2/package.go @@ -50,7 +50,7 @@ type Package struct { // 7.9: Package Verification Code PackageVerificationCode common.PackageVerificationCode `json:"packageVerificationCode"` - // 7.10: Package Checksum: may have keys for SHA1, SHA256 and/or MD5 + // 7.10: Package Checksum: may have keys for SHA1, SHA256, SHA512 and/or MD5 // Cardinality: optional, one or many PackageChecksums []common.Checksum `json:"checksums,omitempty"` diff --git a/vendor/github.com/spdx/tools-golang/spdx/v2_3/package.go b/vendor/github.com/spdx/tools-golang/spdx/v2_3/package.go index 86ab08f14642..b9d5b9515b48 100644 --- a/vendor/github.com/spdx/tools-golang/spdx/v2_3/package.go +++ b/vendor/github.com/spdx/tools-golang/spdx/v2_3/package.go @@ -51,7 +51,7 @@ type Package struct { // Cardinality: if FilesAnalyzed == true must be present, if FilesAnalyzed == false must be omitted PackageVerificationCode *common.PackageVerificationCode `json:"packageVerificationCode,omitempty"` - // 7.10: Package Checksum: may have keys for SHA1, SHA256, MD5, SHA3-256, SHA3-384, SHA3-512, BLAKE2b-256, BLAKE2b-384, BLAKE2b-512, BLAKE3, ADLER32 + // 7.10: Package Checksum: may have keys for SHA1, SHA256, SHA512, MD5, SHA3-256, SHA3-384, SHA3-512, BLAKE2b-256, BLAKE2b-384, BLAKE2b-512, BLAKE3, ADLER32 // Cardinality: optional, one or many PackageChecksums []common.Checksum `json:"checksums,omitempty"` diff --git a/vendor/github.com/tonistiigi/fsutil/send.go b/vendor/github.com/tonistiigi/fsutil/send.go index 2c1a3801d58a..f1c51b83652d 100644 --- a/vendor/github.com/tonistiigi/fsutil/send.go +++ b/vendor/github.com/tonistiigi/fsutil/send.go @@ -135,7 +135,7 @@ func (s *sender) sendFile(h *sendHandle) error { defer f.Close() buf := bufPool.Get().(*[]byte) defer bufPool.Put(buf) - if _, err := io.CopyBuffer(&fileSender{sender: s, id: h.id}, f, *buf); err != nil { + if _, err := io.CopyBuffer(&fileSender{sender: s, id: h.id}, struct{ io.Reader }{f}, *buf); err != nil { return err } } diff --git a/vendor/modules.txt b/vendor/modules.txt index 004bf96ad171..79338b4e03a5 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -666,7 +666,7 @@ github.com/shibumi/go-pathspec # github.com/sirupsen/logrus v1.9.0 ## explicit; go 1.13 github.com/sirupsen/logrus -# github.com/spdx/tools-golang v0.3.1-0.20221108182156-8a01147e6342 => github.com/jedevc/spdx-tools-golang v0.0.0-20221205121515-6fe9d226281a +# github.com/spdx/tools-golang v0.3.1-0.20230104082527-d6f58551be3f ## explicit; go 1.13 github.com/spdx/tools-golang/json github.com/spdx/tools-golang/spdx/common @@ -676,7 +676,7 @@ github.com/spdx/tools-golang/spdx/v2_3 ## explicit; go 1.13 github.com/stretchr/testify/assert github.com/stretchr/testify/require -# github.com/tonistiigi/fsutil v0.0.0-20221114235510-0127568185cf +# github.com/tonistiigi/fsutil v0.0.0-20230105215944-fb433841cbfa ## explicit; go 1.18 github.com/tonistiigi/fsutil github.com/tonistiigi/fsutil/copy @@ -928,4 +928,3 @@ google.golang.org/protobuf/types/pluginpb # gopkg.in/yaml.v3 v3.0.1 ## explicit gopkg.in/yaml.v3 -# github.com/spdx/tools-golang => github.com/jedevc/spdx-tools-golang v0.0.0-20221205121515-6fe9d226281a