// Package mutate implements Warehouse-specific mutation logic and helpers for // mutating Warehouse OCI artifacts. package mutate import ( "fmt" v1 "github.com/google/go-containerregistry/pkg/v1" "github.com/google/go-containerregistry/pkg/v1/mutate" wh "edge-infra.dev/pkg/f8n/warehouse" "edge-infra.dev/pkg/f8n/warehouse/oci" ) // TODO: make AppendManifests do the work of parsing the descriptors and preserving media type etc? // TODO: AppendManifests probably has to change since it does some pallet-specific annotation carry over // AppendManifests appends the OCI artifacts to the input ImageIndex by creating // a new ImageIndex with the appropriate descriptors for each artifact. Some // annotations defined by the Warehouse spec on input artifacts will be // persisted or mutated onto the result descriptor that is appended to the // ImageIndex. // // NOTE: Although AppendManifests implements Warehouse-specific behavior by // adding annotations to the descriptors, no verification that the input // ImageIndex is itself a valid Pallet is done. func AppendManifests(idx v1.ImageIndex, artifacts ...oci.Artifact) (v1.ImageIndex, error) { if len(artifacts) == 0 { return nil, fmt.Errorf("mutate.AppendManifests: at least one artifact is required") } adds, err := artifactsToAddendums(artifacts) if err != nil { return nil, fmt.Errorf("mutate.AppendManifests: %w", err) } return mutate.AppendManifests(idx, adds...), nil } // artifactsToAddendums converts the input artifacts into something that can be // appended to ImageIndex func artifactsToAddendums(aa []oci.Artifact) ([]mutate.IndexAddendum, error) { var adds []mutate.IndexAddendum for _, a := range aa { add, err := artifactToAddendum(a) if err != nil { return nil, err } adds = append(adds, add) } return adds, nil } // artifactToAddendum converts the input artifact into something that can be // appended to an ImageIndex, preserving or mapping annotations with semantic // value in the Warehouse spec to the embedded descriptors func artifactToAddendum(a oci.Artifact) (mutate.IndexAddendum, error) { desc := v1.Descriptor{} switch a := a.(type) { // if this is one of our implementations, call the function again with the // unwrapped ggcr type case oci.Unwrapper: return artifactToAddendum(a.Unwrap()) case v1.Image: manifest, err := a.Manifest() if err != nil { return mutate.IndexAddendum{}, fmt.Errorf("failed to parse image manifest: %w", err) } desc.Annotations = descAnnotations(manifest.Annotations) case v1.ImageIndex: manifest, err := a.IndexManifest() if err != nil { return mutate.IndexAddendum{}, fmt.Errorf("failed to parse image index manifest: %w", err) } desc.Annotations = descAnnotations(manifest.Annotations) default: // TODO: try to lazily evaluate annotations? see mutate code in ggcr for example // TODO: contextual error, dont have pallet name available anymore return mutate.IndexAddendum{}, fmt.Errorf("%w", oci.ErrInvalidArtifact) } return mutate.IndexAddendum{Add: a, Descriptor: desc}, nil } // descAnnotations adds the required annotations for a descriptor based on the // manifests of the target it is referencing func descAnnotations(annos map[string]string) map[string]string { m := map[string]string{} for k, v := range annos { switch k { case wh.AnnotationName: m[wh.AnnotationRefName] = v case wh.AnnotationClusterProviders: m[wh.AnnotationClusterProviders] = v case wh.AnnotationKind: m[wh.AnnotationKind] = v } } return m }