github.com/quay/claircore@v1.5.28/whiteout/resolver.go (about)

     1  package whiteout
     2  
     3  import (
     4  	"context"
     5  	"path/filepath"
     6  	"strings"
     7  
     8  	"github.com/quay/zlog"
     9  
    10  	"github.com/quay/claircore"
    11  	"github.com/quay/claircore/indexer"
    12  )
    13  
    14  var (
    15  	_ indexer.Resolver = (*Resolver)(nil)
    16  )
    17  
    18  type Resolver struct{}
    19  
    20  func (r *Resolver) Resolve(ctx context.Context, ir *claircore.IndexReport, layers []*claircore.Layer) *claircore.IndexReport {
    21  	// Here we need to check if any of the packages
    22  	// found are moot due to whiteouts.
    23  	ls := newLayerSorter(layers)
    24  	finalPackages := map[string]*claircore.Package{}
    25  	finalEnvironments := map[string][]*claircore.Environment{}
    26  	for pkgID, pkg := range ir.Packages {
    27  		packageDeleted := false
    28  		// Check all layers where the package appeared for the newest one
    29  		packageLayer := ir.Environments[pkgID][0].IntroducedIn.String()
    30  		for i := 1; i < len(ir.Environments[pkgID]); i++ {
    31  			if ls.isChildOf(ir.Environments[pkgID][i].IntroducedIn.String(), packageLayer) {
    32  				packageLayer = ir.Environments[pkgID][i].IntroducedIn.String()
    33  			}
    34  		}
    35  		for fileLayer, f := range ir.Files {
    36  			// Check if it's a whiteout file, if it applies to the package's
    37  			// filepath and if the layer the whiteout file came from came after.
    38  			// The spec states: "Whiteout files MUST only apply to resources in
    39  			// lower/parent layers" hence why we don't check if they're in the same
    40  			// layer.
    41  			if f.Kind == claircore.FileKindWhiteout && ls.isChildOf(fileLayer, packageLayer) && fileIsDeleted(pkg.Filepath, f.Path) {
    42  				packageDeleted = true
    43  				zlog.Debug(ctx).
    44  					Str("package name", pkg.Name).
    45  					Str("package file", pkg.Filepath).
    46  					Str("whiteout file", f.Path).
    47  					Msg("package determined to be deleted")
    48  			}
    49  		}
    50  		if !packageDeleted {
    51  			finalPackages[pkgID] = pkg
    52  			finalEnvironments[pkgID] = ir.Environments[pkgID]
    53  
    54  		}
    55  	}
    56  	ir.Packages = finalPackages
    57  	ir.Environments = finalEnvironments
    58  	return ir
    59  }
    60  
    61  // FileIsDeleted returns whether or not the filepath(fp) has been deleted
    62  // by the corresponding whiteoutPath. It follows the OCI spec for whiteouts:
    63  // https://github.com/opencontainers/image-spec/blob/main/layer.md#whiteouts
    64  func fileIsDeleted(fp, whiteoutPath string) bool {
    65  	var checkFile string
    66  	fpParts := strings.Split(fp, "/")
    67  	switch {
    68  	case filepath.Base(whiteoutPath) == ".wh..wh..opq":
    69  		// Special opaque case, "indicating that all siblings are hidden in the lower layer"
    70  		checkFile = filepath.Dir(whiteoutPath)
    71  		if checkFile == fp {
    72  			// Account for the parent dir of the whiteout file
    73  			return false
    74  		}
    75  	case strings.HasPrefix(filepath.Base(whiteoutPath), ".wh."):
    76  		origFileName := filepath.Base(whiteoutPath)[4:]
    77  		checkFile = filepath.Join(filepath.Dir(whiteoutPath), origFileName)
    78  	default:
    79  		return false
    80  	}
    81  	checkFileParts := strings.Split(checkFile, "/")
    82  	if len(checkFileParts) > len(fpParts) {
    83  		return false
    84  	}
    85  	for i, p := range checkFileParts {
    86  		if p != fpParts[i] {
    87  			return false
    88  		}
    89  	}
    90  	return true
    91  }
    92  
    93  type layerSorter map[string]int
    94  
    95  func newLayerSorter(layers []*claircore.Layer) layerSorter {
    96  	ls := make(map[string]int, len(layers))
    97  	for i, l := range layers {
    98  		ls[l.Hash.String()] = i
    99  	}
   100  	return ls
   101  }
   102  
   103  // IsChildOf decides if whiteoutLayer comes after packageLayer in the layer
   104  // hierarchy, i.e. is whiteoutLayer a child of packageLayer?
   105  func (ls layerSorter) isChildOf(whiteoutLayer, packageLayer string) bool {
   106  	return ls[whiteoutLayer] > ls[packageLayer]
   107  }