package unpack import ( "fmt" v1 "github.com/google/go-containerregistry/pkg/v1" "sigs.k8s.io/kustomize/api/filters/namespace" "edge-infra.dev/pkg/f8n/warehouse/cluster" "edge-infra.dev/pkg/f8n/warehouse/lift" "edge-infra.dev/pkg/f8n/warehouse/lift/render" "edge-infra.dev/pkg/f8n/warehouse/oci" "edge-infra.dev/pkg/f8n/warehouse/oci/layer" "edge-infra.dev/pkg/f8n/warehouse/pallet" ) // Layers unpacks a single input artifact a into the desired processed layers // based on options. By default, all layers are returned. func Layers(a oci.Artifact, opts ...Option) ([]layer.Layer, error) { options, err := makeOptions(opts...) if err != nil { return nil, err } // resolve input to a Pallet switch a := a.(type) { // use it directly if a pallet was passed to us, after confirming options // are valid case pallet.Pallet: // if the pallet isn't backed by an image, options.provider must be present if _, ok := a.Unwrap().(v1.Image); !ok && options.provider == "" { return nil, fmt.Errorf( "%s: %s pallet requires one of %s", cluster.ErrNoProviders, a.Name(), a.Providers(), ) } return unpackPallet(a, *options) case v1.ImageIndex: if options.provider == "" { return nil, fmt.Errorf( "%s: v1.ImageIndex requires a provider", cluster.ErrNoProviders, ) } } // construct pallet if we weren't given one p, err := pallet.New(a) if err != nil { return nil, err } return unpackPallet(p, *options) } // unpackPallet processes layers from a single pallet based on the options func unpackPallet(p pallet.Pallet, opts options) ([]layer.Layer, error) { // get raw layers layers, err := p.Layers(opts.provider) if err != nil { return nil, err } switch p.Renderable() { // add global rendering parameters via unpacking options and package info case true: params := map[string]string{lift.PalletNameRenderingParameter: p.Name()} // TODO(aw185176): parameters declared by package should be parsed and error // returned if it needs cluster provider but we don't have it if opts.provider != "" { params[lift.ClusterProviderRenderingParameter] = string(opts.provider) } opts.parameters = append(opts.parameters, params) case false: opts.render = false } // unless we are specifically requested for specific layers, return all of them if len(opts.keys) == 0 && len(opts.types) == 0 { for i, l := range layers { layers[i], err = processLayer(l, p.Parameters(), opts) if err != nil { return nil, err } } return layers, nil } // otherwise we filter and process requested layers from artifact return filterLayers(layers, p.Parameters(), opts) } // filterLayers filters out undesirable layers in-place based and performs the // appropriate mutations (eg, rendering) on the unpacking options func filterLayers(layers []layer.Layer, palletParams []string, opts options) ([]layer.Layer, error) { var ( err error j = 0 ) // if we find a desired layer, process it based on options for _, l := range layers { // NOTE: we avoid duplicated processing of layers by validating that both // opts.keys and opts.types are not provided for _, desired := range opts.keys { if l.Key() == desired { l, err = processLayer(l, palletParams, opts) if err != nil { return nil, err } layers[j] = l j++ } } for _, t := range opts.types { if l.Type() == t { l, err = processLayer(l, palletParams, opts) if err != nil { return nil, err } layers[j] = l j++ } } } layers = layers[:j] return layers, nil } func processLayer(l layer.Layer, palletParams []string, opts options) (layer.Layer, error) { var err error if opts.render { l, err = render.Layer(l, palletParams, opts.parameters...) if err != nil { return nil, err } } if opts.infraNamespace != "" && l.Type() == layer.Infra { l, err = layer.Filter(l, namespace.Filter{Namespace: opts.infraNamespace}) if err != nil { return nil, err } } return l, nil }