package validate import ( "fmt" v1 "github.com/google/go-containerregistry/pkg/v1" wh "edge-infra.dev/pkg/f8n/warehouse" "edge-infra.dev/pkg/f8n/warehouse/oci" "edge-infra.dev/pkg/f8n/warehouse/oci/layer" ) // Validate performs a depth-first traversal of the input artifact's graph, visiting // each node in reverse topological order from the input artifact's root node. // Functions on are called after validation of children, in a bottom up fashion. // // This operates similarly to mutate.Map, but does not mutate the graph. func Validate(a oci.Artifact, fns *Fns) error { rootAnnotations, err := oci.Annotations(a) if err != nil { return err } switch t := a.(type) { case oci.Unwrapper: return Validate(t.Unwrap(), fns) case v1.ImageIndex: return fns.validateIndex(t, rootAnnotations) case v1.Image: return fns.validateImage(t, rootAnnotations) default: return oci.ErrInvalidArtifact } } // Fns defines functions for each OCI entity to be called during graph traversal // in Validate operations. Functions can validate the input OCI entity by returning // an error. Returning nil indicates the artifact is valid. // // The input OCI entity (idx, img, l) is passed in _after_ validating all of // it's child nodes. // // rootAnnotations are used to ensure values set in child annotations are present // in the root node, e.g. cluster providers, parameters etc. type Fns struct { Index func(idx oci.Artifact, rootAnnotations map[string]string) error Image func(img oci.Artifact, rootAnnotations map[string]string) error Layer func(l layer.Layer, rootAnnotations map[string]string) error } func (f *Fns) validateIndex(idx oci.Artifact, rootAnnotations map[string]string) error { i := idx.(v1.ImageIndex) m, err := i.IndexManifest() if err != nil { return err } for n, desc := range m.Manifests { switch { case desc.MediaType.IsIndex(): var ii v1.ImageIndex ii, err = i.ImageIndex(desc.Digest) if err == nil { err = f.validateIndex(ii, rootAnnotations) } case desc.MediaType.IsImage(): var im v1.Image im, err = i.Image(desc.Digest) if err == nil { err = f.validateImage(im, rootAnnotations) } default: err = oci.ErrInvalidArtifact } if err != nil { return fmt.Errorf("descriptor %d is invalid: %v", n, err) } } return f.indexValidator(idx, rootAnnotations) } func (f *Fns) validateImage(img oci.Artifact, rootAnnotations map[string]string) error { i := img.(v1.Image) layers, err := layer.FromImage(i) if err != nil { return err } for n, layer := range layers { if err := f.layerValidator(layer, rootAnnotations); err != nil { return fmt.Errorf("layer %d is invalid: %v", n, err) } } return f.imageValidator(img, rootAnnotations) } // indexValidator wraps Fns.Image with nil check and error handling to simplify traversal logic. func (f *Fns) indexValidator(idx oci.Artifact, rootAnnotations map[string]string) error { if f.Index != nil { if err := f.Index(idx, rootAnnotations); err != nil { name := artifactName(idx) if name != "" { return fmt.Errorf("'%s' is invalid v1.ImageIndex: %v", name, err) } return fmt.Errorf("invalid v1.ImageIndex: %v", err) } } return nil } // imageValidator wraps Fns.Image with nil check and error handling to simplify traversal logic. func (f *Fns) imageValidator(img oci.Artifact, rootAnnotations map[string]string) error { if f.Image != nil { if err := f.Image(img, rootAnnotations); err != nil { name := artifactName(img) if name != "" { return fmt.Errorf("'%s' is invalid v1.Image: %v", name, err) } return fmt.Errorf("invalid v1.Image: %v", err) } } return nil } // layerValidator wraps Fns.Image with nil check and error handling to simplify traversal logic. func (f *Fns) layerValidator(l layer.Layer, rootAnnotations map[string]string) error { if f.Layer != nil { if err := f.Layer(l, rootAnnotations); err != nil { return fmt.Errorf("invalid v1.Layer: %v", err) } } return nil } // Gets artifact name from "com.ncr.warehouse.name" annotation, returns empty if it doesn't exist. func artifactName(a oci.Artifact) string { annos, err := oci.Annotations(a) if err != nil { return "" } name, ok := annos[wh.AnnotationName] if !ok { return "" } return name }