...

Source file src/edge-infra.dev/pkg/f8n/warehouse/oci/validate/validate.go

Documentation: edge-infra.dev/pkg/f8n/warehouse/oci/validate

     1  package validate
     2  
     3  import (
     4  	"fmt"
     5  
     6  	v1 "github.com/google/go-containerregistry/pkg/v1"
     7  
     8  	wh "edge-infra.dev/pkg/f8n/warehouse"
     9  	"edge-infra.dev/pkg/f8n/warehouse/oci"
    10  	"edge-infra.dev/pkg/f8n/warehouse/oci/layer"
    11  )
    12  
    13  // Validate performs a depth-first traversal of the input artifact's graph, visiting
    14  // each node in reverse topological order from the input artifact's root node.
    15  // Functions on are called after validation of children, in a bottom up fashion.
    16  //
    17  // This operates similarly to mutate.Map, but does not mutate the graph.
    18  func Validate(a oci.Artifact, fns *Fns) error {
    19  	rootAnnotations, err := oci.Annotations(a)
    20  	if err != nil {
    21  		return err
    22  	}
    23  
    24  	switch t := a.(type) {
    25  	case oci.Unwrapper:
    26  		return Validate(t.Unwrap(), fns)
    27  	case v1.ImageIndex:
    28  		return fns.validateIndex(t, rootAnnotations)
    29  	case v1.Image:
    30  		return fns.validateImage(t, rootAnnotations)
    31  	default:
    32  		return oci.ErrInvalidArtifact
    33  	}
    34  }
    35  
    36  // Fns defines functions for each OCI entity to be called during graph traversal
    37  // in Validate operations. Functions can validate the input OCI entity by returning
    38  // an error. Returning nil indicates the artifact is valid.
    39  //
    40  // The input OCI entity (idx, img, l) is passed in _after_ validating all of
    41  // it's child nodes.
    42  //
    43  // rootAnnotations are used to ensure values set in child annotations are present
    44  // in the root node, e.g. cluster providers, parameters etc.
    45  type Fns struct {
    46  	Index func(idx oci.Artifact, rootAnnotations map[string]string) error
    47  	Image func(img oci.Artifact, rootAnnotations map[string]string) error
    48  	Layer func(l layer.Layer, rootAnnotations map[string]string) error
    49  }
    50  
    51  func (f *Fns) validateIndex(idx oci.Artifact, rootAnnotations map[string]string) error {
    52  	i := idx.(v1.ImageIndex)
    53  
    54  	m, err := i.IndexManifest()
    55  	if err != nil {
    56  		return err
    57  	}
    58  
    59  	for n, desc := range m.Manifests {
    60  		switch {
    61  		case desc.MediaType.IsIndex():
    62  			var ii v1.ImageIndex
    63  			ii, err = i.ImageIndex(desc.Digest)
    64  			if err == nil {
    65  				err = f.validateIndex(ii, rootAnnotations)
    66  			}
    67  		case desc.MediaType.IsImage():
    68  			var im v1.Image
    69  			im, err = i.Image(desc.Digest)
    70  			if err == nil {
    71  				err = f.validateImage(im, rootAnnotations)
    72  			}
    73  		default:
    74  			err = oci.ErrInvalidArtifact
    75  		}
    76  
    77  		if err != nil {
    78  			return fmt.Errorf("descriptor %d is invalid: %v", n, err)
    79  		}
    80  	}
    81  
    82  	return f.indexValidator(idx, rootAnnotations)
    83  }
    84  
    85  func (f *Fns) validateImage(img oci.Artifact, rootAnnotations map[string]string) error {
    86  	i := img.(v1.Image)
    87  
    88  	layers, err := layer.FromImage(i)
    89  	if err != nil {
    90  		return err
    91  	}
    92  
    93  	for n, layer := range layers {
    94  		if err := f.layerValidator(layer, rootAnnotations); err != nil {
    95  			return fmt.Errorf("layer %d is invalid: %v", n, err)
    96  		}
    97  	}
    98  
    99  	return f.imageValidator(img, rootAnnotations)
   100  }
   101  
   102  // indexValidator wraps Fns.Image with nil check and error handling to simplify traversal logic.
   103  func (f *Fns) indexValidator(idx oci.Artifact, rootAnnotations map[string]string) error {
   104  	if f.Index != nil {
   105  		if err := f.Index(idx, rootAnnotations); err != nil {
   106  			name := artifactName(idx)
   107  			if name != "" {
   108  				return fmt.Errorf("'%s' is invalid v1.ImageIndex: %v", name, err)
   109  			}
   110  			return fmt.Errorf("invalid v1.ImageIndex: %v", err)
   111  		}
   112  	}
   113  	return nil
   114  }
   115  
   116  // imageValidator wraps Fns.Image with nil check and error handling to simplify traversal logic.
   117  func (f *Fns) imageValidator(img oci.Artifact, rootAnnotations map[string]string) error {
   118  	if f.Image != nil {
   119  		if err := f.Image(img, rootAnnotations); err != nil {
   120  			name := artifactName(img)
   121  			if name != "" {
   122  				return fmt.Errorf("'%s' is invalid v1.Image: %v", name, err)
   123  			}
   124  			return fmt.Errorf("invalid v1.Image: %v", err)
   125  		}
   126  	}
   127  	return nil
   128  }
   129  
   130  // layerValidator wraps Fns.Image with nil check and error handling to simplify traversal logic.
   131  func (f *Fns) layerValidator(l layer.Layer, rootAnnotations map[string]string) error {
   132  	if f.Layer != nil {
   133  		if err := f.Layer(l, rootAnnotations); err != nil {
   134  			return fmt.Errorf("invalid v1.Layer: %v", err)
   135  		}
   136  	}
   137  	return nil
   138  }
   139  
   140  // Gets artifact name from "com.ncr.warehouse.name" annotation, returns empty if it doesn't exist.
   141  func artifactName(a oci.Artifact) string {
   142  	annos, err := oci.Annotations(a)
   143  	if err != nil {
   144  		return ""
   145  	}
   146  	name, ok := annos[wh.AnnotationName]
   147  	if !ok {
   148  		return ""
   149  	}
   150  	return name
   151  }
   152  

View as plain text