// Package pallet defines the requirements for a Pallet OCI artifact and provides // utilities for constructing them. package pallet import ( "fmt" "strings" v1 "github.com/google/go-containerregistry/pkg/v1" wh "edge-infra.dev/pkg/f8n/warehouse" "edge-infra.dev/pkg/f8n/warehouse/capability" "edge-infra.dev/pkg/f8n/warehouse/cluster" "edge-infra.dev/pkg/f8n/warehouse/oci" "edge-infra.dev/pkg/f8n/warehouse/oci/layer" "edge-infra.dev/pkg/f8n/warehouse/oci/match" ) // WarehouseKind is the value for the kind annotation on Warehouse OCI artifacts. const WarehouseKind = wh.PalletKind // Pallet contains all of the state for a pallet package. type Pallet interface { // Artifact is the OCI artifact representing the pallet. oci.Artifact // Pallets can be unwrapped into their underlying OCI artifact type. oci.Unwrapper // Name returns the globally unique name for the package. Hoisted from // Metadata for convenience Name() string // Metadata returns the complete package metadata. Metadata() Metadata // Providers returns the list of cluster providers that this package can // be scheduled to. Providers() cluster.Providers // Supports returns true if the pallet is compatible with a specific provider // TODO: Make this static function that can leverage lazily computed internal state // to reduce Pallet interface surface area. only used in two places today so not // a big impact. // TODO: can maybe remove this entirely Supports(provider cluster.Provider) bool // Capabilities returns the list of cluster capabilities that this package, or // its dependencies, integrate with. Capabilities() capability.Capabilities // Renderable returns whether or this package will have rendering parameters // replaced on unpack. Renderable() bool // Image returns the v1.Image for the input cluster provider. // // If the artifact doesn't support the input provider, ErrUnsupportedProvider // is returned. If the Pallet is itself a v1.Image, the cluster provider can be // omitted and the v1.Image will be returned directly. If a cluster provider is // passed in, it is validated against the Pallet's supported providers. Image(provider cluster.Provider) (v1.Image, error) // Layers returns all Warehouse layers for the input cluster provider. This is // a convenience method that allows getting artifact contents without the // extra call to Image(p). // // If the artifact doesn't support the input provider, ErrUnsupportedProvider // is returned. If the Pallet is itself a v1.Image, the cluster provider can // be omitted. If a cluster provider is passed in, it is valided against the // Pallet's supported providers. Layers(provider cluster.Provider) ([]layer.Layer, error) // Dependencies returns the set of direct dependencies for this Pallet. Dependencies() ([]Pallet, error) // Parameters returns the set of rendering parameters used in this Pallet. Parameters() []string } type pallet struct { oci.Artifact meta Metadata providers cluster.Providers capabilities capability.Capabilities renderable bool parameters []string } func New(a oci.Artifact) (Pallet, error) { switch a := a.(type) { case nil: return nil, fmt.Errorf("%w: pallet.New: non-nil artifact is required", oci.ErrInvalidArtifact) // if this is our private concrete implementation of Pallet, we are done case *pallet: return a, nil // if this is one of our implementations, unwrap it into the underlying ggcr // type. because Pallet implements oci.Artifact, this prevents us from // inadvertently creating deeply nested instances case oci.Unwrapper: return New(a.Unwrap()) } // otherwise we have to pull out all of the relevant data the hard way p := &pallet{Artifact: a} annos, err := oci.Annotations(a) if err != nil { return nil, fmt.Errorf("failed to read annotations from oci artifact: %w", err) } if annos[wh.AnnotationRender] != "false" { p.renderable = true } if annos[wh.AnnotationParameters] != "" { p.parameters = strings.Split(annos[wh.AnnotationParameters], ",") } p.meta, err = metadataFromAnnotations(annos) if err != nil { return nil, fmt.Errorf("failed to parse metadata from oci artifact: %w", err) } p.providers, err = cluster.ProvidersFromAnnotations(annos) if err != nil { return nil, fmt.Errorf("failed to parse cluster provider information from oci artifact: %w", err) } p.capabilities = capability.FromAnnotations(annos) return p, nil } // Assert that the Pallet interface is implemented var _ Pallet = (*pallet)(nil) func (p *pallet) Name() string { return p.meta.Name } func (p *pallet) Metadata() Metadata { return p.meta } func (p *pallet) Providers() cluster.Providers { return p.providers } func (p *pallet) Supports(t cluster.Provider) bool { for _, provider := range p.providers { if provider == t { return true } } return false } func (p *pallet) Capabilities() capability.Capabilities { return p.capabilities } // Unwrap implements Unwrapper. func (p *pallet) Unwrap() oci.Artifact { return p.Artifact } func (p *pallet) Renderable() bool { return p.renderable } func (p *pallet) Image(provider cluster.Provider) (v1.Image, error) { // TODO: test if !p.Supports(provider) && provider != "" { return nil, fmt.Errorf("pallet.Image: %w: %s", cluster.ErrUnsupportedProvider, provider) } switch a := p.Artifact.(type) { case v1.Image: return a, nil case v1.ImageIndex: if provider == "" { return nil, fmt.Errorf("pallet.Image: %w: a cluster provider is required "+ "if the Pallet is a v1.ImageIndex", cluster.ErrUnsupportedProvider) } return match.FindImage(a, match.Multiple( match.Provider(provider), match.RefName(p.Name()), )) default: return nil, fmt.Errorf("pallet.Image: %w", oci.ErrInvalidArtifact) } } func (p *pallet) Layers(provider cluster.Provider) ([]layer.Layer, error) { img, err := p.Image(provider) if err != nil { return nil, err } return layer.FromImage(img) } func (p *pallet) Dependencies() ([]Pallet, error) { switch a := p.Artifact.(type) { case v1.Image: return nil, nil case v1.ImageIndex: manifests, err := match.FindManifests(a, match.Dependencies(p.Name())) if err != nil { return nil, err } deps := make([]Pallet, 0, len(manifests)) for _, m := range manifests { dep, err := oci.ArtifactFromIdx(a, m) if err != nil { return nil, err } pallet, err := New(dep) if err != nil { return nil, fmt.Errorf("failed to create pallet for dependency: %w", err) } deps = append(deps, pallet) } return deps, nil default: return nil, fmt.Errorf("%w", oci.ErrInvalidArtifact) } } func (p *pallet) Parameters() []string { return p.parameters }