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 }