Skip to content

Commit

Permalink
Merge pull request #196 from depot/feat/decode-manifest-config-sbom
Browse files Browse the repository at this point in the history
  • Loading branch information
goller authored Sep 23, 2023
2 parents b13745d + b6687bc commit 5f95eca
Show file tree
Hide file tree
Showing 3 changed files with 81 additions and 22 deletions.
23 changes: 23 additions & 0 deletions pkg/buildx/commands/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -934,6 +934,29 @@ func decodeExporterResponse(exporterResponse map[string]string) map[string]inter
out[k] = v
continue
}
if k == load.ImagesExported {
_, manifests, imageConfigs, err := load.DecodeExportImages(v)
if err != nil {
out[k] = v
} else {
out["manifests"] = manifests
out["imageConfigs"] = imageConfigs
}

continue
}

if k == sbom.SBOMsLabel {
sboms, err := sbom.DecodeSBOMs(v)
if err != nil {
out[k] = v
} else {
out["sboms"] = sboms
}

continue
}

var raw map[string]interface{}
if err = json.Unmarshal(dt, &raw); err != nil || len(raw) == 0 {
out[k] = v
Expand Down
67 changes: 45 additions & 22 deletions pkg/load/load.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ func chooseNodeResponse(nodeResponses []depotbuild.DepotNodeResponse) depotbuild
const ImagesExported = "depot/images.exported"

func decodeNodeResponse(architecture string, nodeRes depotbuild.DepotNodeResponse) (rawManifest, rawConfig []byte, err error) {
if _, ok := nodeRes.SolveResponse.ExporterResponse[ImagesExported]; ok {
if _, err := EncodedExportedImages(nodeRes.SolveResponse.ExporterResponse); err == nil {
return decodeNodeResponseV2(architecture, nodeRes)
}

Expand All @@ -104,29 +104,57 @@ func decodeNodeResponse(architecture string, nodeRes depotbuild.DepotNodeRespons
}

func decodeNodeResponseV2(architecture string, nodeRes depotbuild.DepotNodeResponse) (rawManifest, rawConfig []byte, err error) {
type ExportedImage struct {
// JSON-encoded ocispecs.Manifest.
// This is double encoded as buildkit has extra fields when used as a docker schema.
// This matters as the digest is calculated including all those extra fields.
Manifest []byte `json:"manifest"`
// JSON-encoded ocispecs.Image.
// Double encoded for the same reason.
Config []byte `json:"config"`
encodedExportedImages, err := EncodedExportedImages(nodeRes.SolveResponse.ExporterResponse)
if err != nil {
return nil, nil, err
}

exportedImages, _, imageConfigs, err := DecodeExportImages(encodedExportedImages)
if err != nil {
return nil, nil, err
}

encodedExportedImages, ok := nodeRes.SolveResponse.ExporterResponse[ImagesExported]
idx, err := chooseBestImageManifestV2(architecture, imageConfigs)
if err != nil {
return nil, nil, err
}

return exportedImages[idx].Manifest, exportedImages[idx].Config, nil
}

// EncodedExportedImages returns the encoded exported images from the solve response.
// This uses the `depot.export.image.version=2` format.
func EncodedExportedImages(exporterResponse map[string]string) (string, error) {
encodedExportedImages, ok := exporterResponse[ImagesExported]
if !ok {
return nil, nil, errors.New("missing image export response")
return "", errors.New("missing image export response")
}
return encodedExportedImages, nil
}

// RawExportedImage is the JSON-encoded image manifest and config used loading the image.
type RawExportedImage struct {
// JSON-encoded ocispecs.Manifest.
// This is double encoded as buildkit has extra fields when used as a docker schema.
// This matters as the digest is calculated including all those extra fields.
Manifest []byte `json:"manifest"`
// JSON-encoded ocispecs.Image.
// Double encoded for the same reason.
Config []byte `json:"config"`
}

// DecodeExportImages decodes the exported images from the solve response.
// The solve response is encoded with a bunch of JSON/b64 encoding to attempt
// to pass a variety of data structures to the CLI.
func DecodeExportImages(encodedExportedImages string) ([]RawExportedImage, []ocispecs.Manifest, []ocispecs.Image, error) {
jsonExportedImages, err := base64.StdEncoding.DecodeString(encodedExportedImages)
if err != nil {
return nil, nil, fmt.Errorf("invalid exported images encoding: %w", err)
return nil, nil, nil, fmt.Errorf("invalid exported images encoding: %w", err)
}

var exportedImages []ExportedImage
var exportedImages []RawExportedImage
if err := json.Unmarshal(jsonExportedImages, &exportedImages); err != nil {
return nil, nil, fmt.Errorf("invalid exported images json: %w", err)
return nil, nil, nil, fmt.Errorf("invalid exported images json: %w", err)
}

// Potentially multiple platforms were built, so we need to find the
Expand All @@ -136,23 +164,18 @@ func decodeNodeResponseV2(architecture string, nodeRes depotbuild.DepotNodeRespo
for i := range exportedImages {
var manifest ocispecs.Manifest
if err := json.Unmarshal(exportedImages[i].Manifest, &manifest); err != nil {
return nil, nil, fmt.Errorf("invalid image manifest json: %w", err)
return nil, nil, nil, fmt.Errorf("invalid image manifest json: %w", err)
}
manifests[i] = manifest

var image ocispecs.Image
if err := json.Unmarshal(exportedImages[i].Config, &image); err != nil {
return nil, nil, fmt.Errorf("invalid image config json: %w", err)
return nil, nil, nil, fmt.Errorf("invalid image config json: %w", err)
}
imageConfigs[i] = image
}

idx, err := chooseBestImageManifestV2(architecture, imageConfigs)
if err != nil {
return nil, nil, err
}

return exportedImages[idx].Manifest, exportedImages[idx].Config, nil
return exportedImages, manifests, imageConfigs, nil
}

// We encode the image manifest and image config within the buildkitd Solve response
Expand Down
13 changes: 13 additions & 0 deletions pkg/sbom/sbom.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,19 @@ type SBOM struct {
Image *ImageSBOM `json:"image"`
}

// This is a custom marshaler to prevent conversion of JSON statement into base64.
func (s *SBOM) MarshalJSON() ([]byte, error) {
return json.Marshal(&struct {
Statement json.RawMessage `json:"statement"`
Platform string `json:"platform"`
Image *ImageSBOM `json:"image,omitempty"`
}{
Statement: json.RawMessage(s.Statement),
Platform: s.Platform,
Image: s.Image,
})
}

// ImageSBOM describes an image that is described by an SBOM.
type ImageSBOM struct {
// Name is the image name and tag.
Expand Down

0 comments on commit 5f95eca

Please sign in to comment.