pkg/oci.go (96 lines of code) (raw):
package obom
import (
"bytes"
"context"
"encoding/json"
"fmt"
v1 "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/spdx/tools-golang/spdx/v2/v2_3"
"oras.land/oras-go/v2"
"oras.land/oras-go/v2/content/memory"
"oras.land/oras-go/v2/registry"
"oras.land/oras-go/v2/registry/remote/auth"
)
const (
APPLICATION_USERAGENT = "obom"
)
type CredentialsResolver = func(context.Context, string) (auth.Credential, error)
// PushSBOM pushes the SPDX SBOM bytes to the registry as an OCI artifact.
// It takes in a pointer to an SPDX document, a pointer to a descriptor, a byte slice of the SBOM, a reference string, a map of SPDX annotations, and a credentials resolver function.
// It returns an error if there was an issue pushing the SBOM to the registry.
func PushSBOM(sbomDoc *v2_3.Document, sbomDescriptor *v1.Descriptor, sbomBytes []byte, reference string, spdx_annotations map[string]string, pushSummary bool, attachArtifacts map[string][]string, dest oras.Target) (*v1.Descriptor, error) {
mem := memory.New()
ctx := context.Background()
// Create a Reader for the bytes
sbomReader := bytes.NewReader(sbomBytes)
// Add descriptor to a memory store
err := mem.Push(ctx, *sbomDescriptor, sbomReader)
if err != nil {
return nil, fmt.Errorf("error pushing SBOM into memory store: %w", err)
}
layers := []v1.Descriptor{*sbomDescriptor}
// Add annotations to the manifest
annotations := make(map[string]string)
for k, v := range spdx_annotations {
annotations[k] = v
}
// add the summary blob as a layer if pushSummary is set
if pushSummary {
sbomSummary, err := GetSBOMSummary(sbomDoc)
if err != nil {
return nil, fmt.Errorf("error getting SBOM summary: %w", err)
}
// Marshal the summary into a string
summaryBytes, err := json.Marshal(sbomSummary)
if err != nil {
return nil, fmt.Errorf("error marshaling summary into bytes: %w", err)
}
summaryDescriptor, err := oras.PushBytes(ctx, mem, "application/json", summaryBytes)
if err != nil {
return nil, fmt.Errorf("error pushing summary into memory store: %w", err)
}
layers = append(layers, summaryDescriptor)
}
// Pack the files and tag the packed manifest
artifactType := MEDIATYPE_SPDX
manifestDescriptor, err := oras.PackManifest(ctx, mem, oras.PackManifestVersion1_1, artifactType, oras.PackManifestOptions{
Layers: layers,
ManifestAnnotations: annotations,
})
if err != nil {
return nil, fmt.Errorf("error packing manifest: %w", err)
}
// Use the latest tag if no tag is specified
tag := "latest"
ref, err := registry.ParseReference(reference)
if err != nil {
return nil, fmt.Errorf("error parsing reference: %w", err)
}
if ref.Reference != "" {
tag = ref.Reference
}
if err = mem.Tag(ctx, manifestDescriptor, tag); err != nil {
return nil, err
}
if len(attachArtifacts) > 0 {
for artifactType, paths := range attachArtifacts {
for _, path := range paths {
// load the artifact from the path
artifactDesc, artifactBytes, err := LoadArtifactFromFile(path, artifactType)
if err != nil {
return nil, fmt.Errorf("error loading artifact: %v", err)
}
err = AttachArtifact(ctx, &manifestDescriptor, artifactDesc, artifactType, artifactBytes, mem)
if err != nil {
return nil, fmt.Errorf("error attaching artifact: %v", err)
}
}
}
}
// Copy from the memory store to the remote repository
manifest, err := oras.ExtendedCopy(ctx, mem, tag, dest, tag, oras.DefaultExtendedCopyOptions)
return &manifest, err
}
// AttachArtifact attaches an artifact to the subject descriptor
func AttachArtifact(ctx context.Context, subject *v1.Descriptor, artifactDescriptor *v1.Descriptor, artifactType string, artifactBytes []byte, mem *memory.Store) error {
// Create a Reader for the bytes
artifactReader := bytes.NewReader(artifactBytes)
// Add descriptor to a memory store
err := mem.Push(ctx, *artifactDescriptor, artifactReader)
if err != nil {
return fmt.Errorf("error pushing artifact into memory store: %w", err)
}
// Pack the artifact manifest with the subject descriptor
_, err = oras.PackManifest(ctx, mem, oras.PackManifestVersion1_1, artifactType, oras.PackManifestOptions{
Subject: subject,
Layers: []v1.Descriptor{*artifactDescriptor},
})
if err != nil {
return fmt.Errorf("error packing artifact manifest: %w", err)
}
return nil
}