...

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

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

     1  package layer
     2  
     3  import (
     4  	"fmt"
     5  	"sort"
     6  
     7  	"github.com/fluxcd/pkg/ssa"
     8  	v1 "github.com/google/go-containerregistry/pkg/v1"
     9  	"github.com/google/go-containerregistry/pkg/v1/partial"
    10  	"github.com/google/go-containerregistry/pkg/v1/static"
    11  
    12  	wh "edge-infra.dev/pkg/f8n/warehouse"
    13  	"edge-infra.dev/pkg/k8s/unstructured"
    14  )
    15  
    16  // ErrInvalidLayer can occur if the v1.Layer is not a valid Warehouse layer,
    17  // per the Warehouse spec.
    18  var ErrInvalidLayer = fmt.Errorf("OCI layer is not a valid Warehouse layer")
    19  
    20  // Layer is a v1.Layer with additional accessors and Warehouse-specific
    21  // functionality. It wraps a v1.Layer in a way that it can preserve annotations
    22  // when partial.Descriptor() is called with it. This is required because we use
    23  // static.Layer, which does not implement Descriptor()
    24  type Layer interface {
    25  	v1.Layer
    26  
    27  	// Descriptor implements partial.withDescriptor so that partial.Descriptor(layer)
    28  	// returns the expected annotations.
    29  	Descriptor() (*v1.Descriptor, error)
    30  
    31  	// Key returns a stable identifier for this layer based on its type.
    32  	//
    33  	// If a layer is a runtime capability, the runtime capability name is returned.
    34  	// Otherwise, the layer's key is it's type
    35  	Key() string
    36  
    37  	// Type returns the Warehouse layer type.
    38  	Type() Type
    39  
    40  	// Unstructured returns the layer's contents as untyped K8s objects that can be
    41  	// applied to the K8s API server
    42  	Unstructured() ([]*unstructured.Unstructured, error)
    43  
    44  	// Annotations returns the annotations present for this layer.
    45  	Annotations() map[string]string
    46  }
    47  
    48  // layer provides a basic concrete implementation of Layer. It can be used as
    49  // is or as the basis for another implementation (e.g., mutate.Layer)
    50  type layer struct {
    51  	v1.Layer
    52  	t     Type
    53  	annos map[string]string
    54  }
    55  
    56  // layer implements Layer
    57  var _ Layer = &layer{}
    58  
    59  // New creates a Warehouse layer for the given type and contents. Contents are
    60  // uncompressed.
    61  func New(t Type, contents []byte, opts ...Option) (Layer, error) {
    62  	if err := t.IsValid(); err != nil {
    63  		return nil, err
    64  	}
    65  	o := makeOptions(opts...)
    66  	if o.cap != "" && t != Runtime {
    67  		return nil, fmt.Errorf(
    68  			"layer.New: ForCapability cannot be used with non-Runtime layer types",
    69  		)
    70  	}
    71  
    72  	l := &layer{
    73  		static.NewLayer(contents, YAML),
    74  		t,
    75  		map[string]string{wh.AnnotationLayerType: string(t)},
    76  	}
    77  
    78  	for k, v := range o.annos {
    79  		l.annos[k] = v
    80  	}
    81  
    82  	return l, nil
    83  }
    84  
    85  // Descriptor implements partial.withDescriptor so that partial.Descriptor(layer)
    86  // returns the expected annotations.
    87  func (l *layer) Descriptor() (*v1.Descriptor, error) {
    88  	d, err := l.Digest()
    89  	if err != nil {
    90  		return nil, err
    91  	}
    92  
    93  	s, err := l.Size()
    94  	if err != nil {
    95  		return nil, err
    96  	}
    97  
    98  	mt, err := l.MediaType()
    99  	if err != nil {
   100  		return nil, err
   101  	}
   102  
   103  	if l.annos == nil {
   104  		l.annos = make(map[string]string)
   105  	}
   106  
   107  	return &v1.Descriptor{
   108  		Digest:      d,
   109  		Size:        s,
   110  		MediaType:   mt,
   111  		Annotations: l.annos,
   112  	}, nil
   113  }
   114  
   115  func (l *layer) Key() string {
   116  	if c, ok := l.annos[wh.AnnotationLayerRuntimeCapability]; ok {
   117  		return c
   118  	}
   119  	return string(l.t)
   120  }
   121  
   122  func (l *layer) Type() Type {
   123  	return l.t
   124  }
   125  
   126  func (l *layer) Unstructured() ([]*unstructured.Unstructured, error) {
   127  	r, err := l.Uncompressed()
   128  	if err != nil {
   129  		return nil, err
   130  	}
   131  	return ssa.ReadObjects(r)
   132  }
   133  
   134  func (l *layer) Annotations() map[string]string {
   135  	return l.annos
   136  }
   137  
   138  // Layer sorting
   139  // TODO(aw185176): test layer sorting
   140  type layers []Layer
   141  
   142  var _ sort.Interface = layers{}
   143  
   144  func (l layers) Len() int      { return len(l) }
   145  func (l layers) Swap(i, j int) { l[i], l[j] = l[j], l[i] }
   146  func (l layers) Less(i, j int) bool {
   147  	switch {
   148  	// everything is less than base runtime
   149  	case l[j].Key() == Runtime.String():
   150  		return false
   151  	case l[i].Key() == Runtime.String():
   152  		return true
   153  	// everything other than base runtime layer is less than infra
   154  	case l[i].Key() == Infra.String():
   155  		return true
   156  	default:
   157  		return false
   158  	}
   159  }
   160  
   161  // Sort sorts the input layers by their key in place.
   162  func Sort(ll []Layer) {
   163  	sort.Stable(layers(ll))
   164  }
   165  
   166  // FromImage parses Warehouse layers from a standard OCI image. Any layers that
   167  // aren't valid Warehouse layers are dropped.
   168  func FromImage(img v1.Image) ([]Layer, error) {
   169  	imgLayers, err := img.Layers()
   170  	if err != nil {
   171  		return nil, err
   172  	}
   173  
   174  	ll := make([]Layer, 0, len(imgLayers))
   175  	for _, il := range imgLayers {
   176  		l, err := FromOCILayer(il)
   177  		if err == nil {
   178  			ll = append(ll, l)
   179  		}
   180  	}
   181  	return ll, nil
   182  }
   183  
   184  // FromOCILayer creates a Warehouse Layer from a standard v1.Layer. If it isn't
   185  // a valid OCI v1.Layer, ErrInvalidLayer is returned.
   186  func FromOCILayer(l v1.Layer) (Layer, error) {
   187  	d, err := partial.Descriptor(l)
   188  	if err != nil {
   189  		return nil, err
   190  	}
   191  
   192  	if d.Annotations[wh.AnnotationLayerType] == "" {
   193  		return nil, fmt.Errorf("%w: missing %s annotation",
   194  			ErrInvalidLayer, wh.AnnotationLayerType)
   195  	}
   196  
   197  	return &layer{
   198  		Layer: l,
   199  		t:     Type(d.Annotations[wh.AnnotationLayerType]),
   200  		annos: d.Annotations,
   201  	}, nil
   202  }
   203  

View as plain text