...

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

Documentation: edge-infra.dev/pkg/f8n/warehouse/pallet

     1  // Package pallet defines the requirements for a Pallet OCI artifact and provides
     2  // utilities for constructing them.
     3  package pallet
     4  
     5  import (
     6  	"fmt"
     7  	"strings"
     8  
     9  	v1 "github.com/google/go-containerregistry/pkg/v1"
    10  
    11  	wh "edge-infra.dev/pkg/f8n/warehouse"
    12  	"edge-infra.dev/pkg/f8n/warehouse/capability"
    13  	"edge-infra.dev/pkg/f8n/warehouse/cluster"
    14  	"edge-infra.dev/pkg/f8n/warehouse/oci"
    15  	"edge-infra.dev/pkg/f8n/warehouse/oci/layer"
    16  	"edge-infra.dev/pkg/f8n/warehouse/oci/match"
    17  )
    18  
    19  // WarehouseKind is the value for the kind annotation on Warehouse OCI artifacts.
    20  const WarehouseKind = wh.PalletKind
    21  
    22  // Pallet contains all of the state for a pallet package.
    23  type Pallet interface {
    24  	// Artifact is the OCI artifact representing the pallet.
    25  	oci.Artifact
    26  	// Pallets can be unwrapped into their underlying OCI artifact type.
    27  	oci.Unwrapper
    28  
    29  	// Name returns the globally unique name for the package. Hoisted from
    30  	// Metadata for convenience
    31  	Name() string
    32  
    33  	// Metadata returns the complete package metadata.
    34  	Metadata() Metadata
    35  
    36  	// Providers returns the list of cluster providers that this package can
    37  	// be scheduled to.
    38  	Providers() cluster.Providers
    39  
    40  	// Supports returns true if the pallet is compatible with a specific provider
    41  	// TODO: Make this static function that can leverage lazily computed internal state
    42  	// to reduce Pallet interface surface area. only used in two places today so not
    43  	// a big impact.
    44  	// TODO: can maybe remove this entirely
    45  	Supports(provider cluster.Provider) bool
    46  
    47  	// Capabilities returns the list of cluster capabilities that this package, or
    48  	// its dependencies, integrate with.
    49  	Capabilities() capability.Capabilities
    50  
    51  	// Renderable returns whether or this package will have rendering parameters
    52  	// replaced on unpack.
    53  	Renderable() bool
    54  
    55  	// Image returns the v1.Image for the input cluster provider.
    56  	//
    57  	// If the artifact doesn't support the input provider, ErrUnsupportedProvider
    58  	// is returned. If the Pallet is itself a v1.Image, the cluster provider can be
    59  	// omitted and the v1.Image will be returned directly. If a cluster provider is
    60  	// passed in, it is validated against the Pallet's supported providers.
    61  	Image(provider cluster.Provider) (v1.Image, error)
    62  
    63  	// Layers returns all Warehouse layers for the input cluster provider. This is
    64  	// a convenience method that allows getting artifact contents without the
    65  	// extra call to Image(p).
    66  	//
    67  	// If the artifact doesn't support the input provider, ErrUnsupportedProvider
    68  	// is returned. If the Pallet is itself a v1.Image, the cluster provider can
    69  	// be omitted. If a cluster provider is passed in, it is valided against the
    70  	// Pallet's supported providers.
    71  	Layers(provider cluster.Provider) ([]layer.Layer, error)
    72  
    73  	// Dependencies returns the set of direct dependencies for this Pallet.
    74  	Dependencies() ([]Pallet, error)
    75  
    76  	// Parameters returns the set of rendering parameters used in this Pallet.
    77  	Parameters() []string
    78  }
    79  
    80  type pallet struct {
    81  	oci.Artifact
    82  	meta         Metadata
    83  	providers    cluster.Providers
    84  	capabilities capability.Capabilities
    85  	renderable   bool
    86  	parameters   []string
    87  }
    88  
    89  func New(a oci.Artifact) (Pallet, error) {
    90  	switch a := a.(type) {
    91  	case nil:
    92  		return nil, fmt.Errorf("%w: pallet.New: non-nil artifact is required",
    93  			oci.ErrInvalidArtifact)
    94  		// if this is our private concrete implementation of Pallet, we are done
    95  	case *pallet:
    96  		return a, nil
    97  	// if this is one of our implementations, unwrap it into the underlying ggcr
    98  	// type. because Pallet implements oci.Artifact, this prevents us from
    99  	// inadvertently creating deeply nested instances
   100  	case oci.Unwrapper:
   101  		return New(a.Unwrap())
   102  	}
   103  
   104  	// otherwise we have to pull out all of the relevant data the hard way
   105  	p := &pallet{Artifact: a}
   106  
   107  	annos, err := oci.Annotations(a)
   108  	if err != nil {
   109  		return nil, fmt.Errorf("failed to read annotations from oci artifact: %w", err)
   110  	}
   111  
   112  	if annos[wh.AnnotationRender] != "false" {
   113  		p.renderable = true
   114  	}
   115  
   116  	if annos[wh.AnnotationParameters] != "" {
   117  		p.parameters = strings.Split(annos[wh.AnnotationParameters], ",")
   118  	}
   119  
   120  	p.meta, err = metadataFromAnnotations(annos)
   121  	if err != nil {
   122  		return nil, fmt.Errorf("failed to parse metadata from oci artifact: %w", err)
   123  	}
   124  
   125  	p.providers, err = cluster.ProvidersFromAnnotations(annos)
   126  	if err != nil {
   127  		return nil, fmt.Errorf("failed to parse cluster provider information from oci artifact: %w", err)
   128  	}
   129  
   130  	p.capabilities = capability.FromAnnotations(annos)
   131  
   132  	return p, nil
   133  }
   134  
   135  // Assert that the Pallet interface is implemented
   136  var _ Pallet = (*pallet)(nil)
   137  
   138  func (p *pallet) Name() string {
   139  	return p.meta.Name
   140  }
   141  
   142  func (p *pallet) Metadata() Metadata {
   143  	return p.meta
   144  }
   145  
   146  func (p *pallet) Providers() cluster.Providers {
   147  	return p.providers
   148  }
   149  
   150  func (p *pallet) Supports(t cluster.Provider) bool {
   151  	for _, provider := range p.providers {
   152  		if provider == t {
   153  			return true
   154  		}
   155  	}
   156  	return false
   157  }
   158  
   159  func (p *pallet) Capabilities() capability.Capabilities {
   160  	return p.capabilities
   161  }
   162  
   163  // Unwrap implements Unwrapper.
   164  func (p *pallet) Unwrap() oci.Artifact {
   165  	return p.Artifact
   166  }
   167  
   168  func (p *pallet) Renderable() bool {
   169  	return p.renderable
   170  }
   171  
   172  func (p *pallet) Image(provider cluster.Provider) (v1.Image, error) { // TODO: test
   173  	if !p.Supports(provider) && provider != "" {
   174  		return nil, fmt.Errorf("pallet.Image: %w: %s",
   175  			cluster.ErrUnsupportedProvider, provider)
   176  	}
   177  
   178  	switch a := p.Artifact.(type) {
   179  	case v1.Image:
   180  		return a, nil
   181  	case v1.ImageIndex:
   182  		if provider == "" {
   183  			return nil, fmt.Errorf("pallet.Image: %w: a cluster provider is required "+
   184  				"if the Pallet is a v1.ImageIndex", cluster.ErrUnsupportedProvider)
   185  		}
   186  		return match.FindImage(a, match.Multiple(
   187  			match.Provider(provider),
   188  			match.RefName(p.Name()),
   189  		))
   190  	default:
   191  		return nil, fmt.Errorf("pallet.Image: %w", oci.ErrInvalidArtifact)
   192  	}
   193  }
   194  
   195  func (p *pallet) Layers(provider cluster.Provider) ([]layer.Layer, error) {
   196  	img, err := p.Image(provider)
   197  	if err != nil {
   198  		return nil, err
   199  	}
   200  
   201  	return layer.FromImage(img)
   202  }
   203  
   204  func (p *pallet) Dependencies() ([]Pallet, error) {
   205  	switch a := p.Artifact.(type) {
   206  	case v1.Image:
   207  		return nil, nil
   208  	case v1.ImageIndex:
   209  		manifests, err := match.FindManifests(a, match.Dependencies(p.Name()))
   210  		if err != nil {
   211  			return nil, err
   212  		}
   213  
   214  		deps := make([]Pallet, 0, len(manifests))
   215  		for _, m := range manifests {
   216  			dep, err := oci.ArtifactFromIdx(a, m)
   217  			if err != nil {
   218  				return nil, err
   219  			}
   220  			pallet, err := New(dep)
   221  			if err != nil {
   222  				return nil, fmt.Errorf("failed to create pallet for dependency: %w", err)
   223  			}
   224  			deps = append(deps, pallet)
   225  		}
   226  
   227  		return deps, nil
   228  	default:
   229  		return nil, fmt.Errorf("%w", oci.ErrInvalidArtifact)
   230  	}
   231  }
   232  
   233  func (p *pallet) Parameters() []string {
   234  	return p.parameters
   235  }
   236  

View as plain text