github.com/quay/claircore@v1.5.28/ubuntu/distributionscanner.go (about) 1 package ubuntu 2 3 import ( 4 "bytes" 5 "context" 6 "errors" 7 "fmt" 8 "io" 9 "io/fs" 10 "runtime/trace" 11 "strings" 12 13 "github.com/quay/zlog" 14 15 "github.com/quay/claircore" 16 "github.com/quay/claircore/indexer" 17 ) 18 19 const ( 20 scannerName = "ubuntu" 21 scannerVersion = "3" 22 scannerKind = "distribution" 23 24 osReleasePath = `etc/os-release` 25 lsbReleasePath = `etc/lsb-release` 26 ) 27 28 var ( 29 _ indexer.DistributionScanner = (*DistributionScanner)(nil) 30 _ indexer.VersionedScanner = (*DistributionScanner)(nil) 31 ) 32 33 // DistributionScanner implements [indexer.DistributionScanner] looking for Ubuntu distributions. 34 type DistributionScanner struct{} 35 36 // Name implements [scanner.VersionedScanner]. 37 func (*DistributionScanner) Name() string { return scannerName } 38 39 // Version implements [scanner.VersionedScanner]. 40 func (*DistributionScanner) Version() string { return scannerVersion } 41 42 // Kind implements [scanner.VersionedScanner]. 43 func (*DistributionScanner) Kind() string { return scannerKind } 44 45 // Scan implements [indexer.DistributionScanner]. 46 func (ds *DistributionScanner) Scan(ctx context.Context, l *claircore.Layer) ([]*claircore.Distribution, error) { 47 defer trace.StartRegion(ctx, "Scanner.Scan").End() 48 ctx = zlog.ContextWithValues(ctx, 49 "component", "ubuntu/DistributionScanner.Scan", 50 "version", ds.Version(), 51 "layer", l.Hash.String()) 52 zlog.Debug(ctx).Msg("start") 53 defer zlog.Debug(ctx).Msg("done") 54 sys, err := l.FS() 55 if err != nil { 56 return nil, fmt.Errorf("ubuntu: unable to open layer: %w", err) 57 } 58 d, err := findDist(sys) 59 if err != nil { 60 return nil, fmt.Errorf("ubuntu: %w", err) 61 } 62 if d == nil { 63 return nil, nil 64 } 65 return []*claircore.Distribution{d}, nil 66 } 67 68 func findDist(sys fs.FS) (*claircore.Distribution, error) { 69 var err error 70 var b []byte 71 var idKey, verKey, nameKey string 72 73 b, err = fs.ReadFile(sys, lsbReleasePath) 74 if errors.Is(err, nil) { 75 idKey = `DISTRIB_ID` 76 verKey = `DISTRIB_RELEASE` 77 nameKey = `DISTRIB_CODENAME` 78 goto Found 79 } 80 b, err = fs.ReadFile(sys, osReleasePath) 81 if errors.Is(err, nil) { 82 idKey = `ID` 83 verKey = `VERSION_ID` 84 nameKey = `VERSION_CODENAME` 85 goto Found 86 } 87 return nil, nil 88 89 Found: 90 var hasID bool 91 var ver, name string 92 buf := bytes.NewBuffer(b) 93 for l, err := buf.ReadString('\n'); len(l) != 0; l, err = buf.ReadString('\n') { 94 switch { 95 case errors.Is(err, nil): 96 case errors.Is(err, io.EOF): 97 default: 98 return nil, fmt.Errorf("unexpected error looking for %q: %w", verKey, err) 99 } 100 k, v, ok := strings.Cut(l, "=") 101 if !ok { 102 continue 103 } 104 v = strings.Trim(v, "\"\r\n") 105 switch k { 106 case idKey: 107 if !strings.EqualFold(v, "ubuntu") { 108 // This is not Ubuntu, so skip it. 109 return nil, nil 110 } 111 hasID = true 112 case nameKey: 113 name = v 114 case verKey: 115 ver = v 116 } 117 } 118 if !hasID { 119 // If ID or DISTRIB_ID is missing, just say this is not Ubuntu. 120 return nil, nil 121 } 122 if name != "" && ver != "" { 123 return mkDist(ver, name), nil 124 } 125 return nil, nil 126 }