...

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

Documentation: edge-infra.dev/pkg/f8n/warehouse/lift/unpack

     1  // Package unpack allows for unpacking OCI artifacts, rendering the contents using
     2  // supported variables, and applying the artifacts to a K8s cluster.
     3  package unpack
     4  
     5  import (
     6  	"fmt"
     7  
     8  	v1 "github.com/google/go-containerregistry/pkg/v1"
     9  
    10  	"edge-infra.dev/pkg/f8n/warehouse/cluster"
    11  	"edge-infra.dev/pkg/f8n/warehouse/oci"
    12  	"edge-infra.dev/pkg/f8n/warehouse/oci/layer"
    13  	"edge-infra.dev/pkg/f8n/warehouse/oci/walk"
    14  	"edge-infra.dev/pkg/f8n/warehouse/pallet"
    15  )
    16  
    17  // Fn is called for each visited package in Walk() with a Pallet instantiated
    18  // from the root package node and the unpacked layers for that Pallet.
    19  type Fn func(p pallet.Pallet, layers []layer.Layer) error
    20  
    21  // Walk unpacks the input Artifact a and calls fn for each valid node in the
    22  // traversal, based on the input options. The caller is passed the result of
    23  // unpacking and processing each visited node via the unpacking function fn.
    24  func Walk(a oci.Artifact, fn Fn, opts ...Option) error {
    25  	options, err := makeOptions(opts...)
    26  	if err != nil {
    27  		return err
    28  	}
    29  
    30  	// short circuit if input is standalone image or backed by one
    31  	switch a := a.(type) {
    32  	// if artifact is not backed by v1.Image, options.provider must be set to
    33  	// non-empty value
    34  	case oci.Unwrapper:
    35  		if img, ok := a.Unwrap().(v1.Image); ok {
    36  			return unpackImg(img, fn, options)
    37  		}
    38  	// if its a v1.Image straight up, short-circuit directly
    39  	case v1.Image:
    40  		return unpackImg(a, fn, options)
    41  	}
    42  
    43  	// we now know we are dealing with something like v1.ImageIndex
    44  	// so options.provider is required
    45  	if options.provider == "" {
    46  		return cluster.ErrNoProviders
    47  	}
    48  
    49  	// validate passed provider against artifact providers
    50  	p, err := pallet.New(a)
    51  	if err != nil {
    52  		return err
    53  	}
    54  	if err := p.Providers().IsValid(options.provider); err != nil {
    55  		return err
    56  	}
    57  
    58  	// create walker for indexes
    59  	visited := map[string]v1.Hash{} // so we only call fn once per package
    60  	walker := &walk.Fns{
    61  		Image: func(img v1.Image, parent v1.ImageIndex) error {
    62  			var (
    63  				p   pallet.Pallet
    64  				err error
    65  			)
    66  
    67  			isPkg, err := oci.IsPackage(img, parent)
    68  			if !isPkg || err != nil {
    69  				p, err = pallet.New(parent)
    70  				if err != nil {
    71  					return err
    72  				}
    73  			} else {
    74  				p, err = pallet.New(img)
    75  				if err != nil {
    76  					return err
    77  				}
    78  			}
    79  
    80  			// filter variants other than the one we are specifically looking for.
    81  			// we know options.provider is present because its required for all
    82  			// non-Image inputs
    83  			if !p.Supports(options.provider) {
    84  				return nil
    85  			}
    86  
    87  			// calculate digest because we need it for either comparison against
    88  			// visited nodes or to mark a visited node
    89  			digest, err := p.Digest()
    90  			if err != nil {
    91  				return err
    92  			}
    93  
    94  			if visited[p.Name()] != (v1.Hash{}) {
    95  				if visited[p.Name()] != digest {
    96  					return fmt.Errorf("unpack.Walk: %w", oci.NewConflictErr(
    97  						p.Name(), visited[p.Name()], digest,
    98  					))
    99  				}
   100  
   101  				// return early if we have already visited this package and there is not
   102  				// a conflict
   103  				return nil
   104  			}
   105  
   106  			layers, err := unpackPallet(p, *options)
   107  			if err != nil {
   108  				return err
   109  			}
   110  
   111  			// mark node as visited before we invoke the unpacking
   112  			// function
   113  			visited[p.Name()] = digest
   114  
   115  			return fn(p, layers)
   116  		},
   117  	}
   118  
   119  	return walk.Walk(a, walker)
   120  }
   121  
   122  // unpackImg is a helper when the artifact is a single image
   123  func unpackImg(img v1.Image, fn Fn, opts *options) error {
   124  	p, err := pallet.New(img)
   125  	if err != nil {
   126  		return err
   127  	}
   128  
   129  	layers, err := unpackPallet(p, *opts)
   130  	if err != nil {
   131  		return err
   132  	}
   133  
   134  	return fn(p, layers)
   135  }
   136  

View as plain text