github.com/quay/claircore@v1.5.28/apk/scanner.go (about)

     1  package apk
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"errors"
     7  	"fmt"
     8  	"io/fs"
     9  	"runtime/trace"
    10  
    11  	"github.com/quay/zlog"
    12  
    13  	"github.com/quay/claircore"
    14  	"github.com/quay/claircore/indexer"
    15  )
    16  
    17  const (
    18  	name    = "apk"
    19  	version = "v0.0.1"
    20  	kind    = "package"
    21  )
    22  
    23  var (
    24  	_ indexer.VersionedScanner = (*Scanner)(nil)
    25  	_ indexer.PackageScanner   = (*Scanner)(nil)
    26  )
    27  
    28  // Scanner scans for packages in an apk database.
    29  //
    30  // The zero value is ready to use.
    31  type Scanner struct{}
    32  
    33  // Name implements indexer.VersionedScanner.
    34  func (*Scanner) Name() string { return name }
    35  
    36  // Version implements indexer.VersionedScanner.
    37  func (*Scanner) Version() string { return version }
    38  
    39  // Kind implements indexer.VersionedScanner.
    40  func (*Scanner) Kind() string { return kind }
    41  
    42  const installedFile = "lib/apk/db/installed"
    43  
    44  // Scan examines a layer for an apk installation database, and extracts
    45  // the packages listed there.
    46  //
    47  // A return of (nil, nil) is expected if there's no apk database.
    48  func (*Scanner) Scan(ctx context.Context, layer *claircore.Layer) ([]*claircore.Package, error) {
    49  	if err := ctx.Err(); err != nil {
    50  		return nil, err
    51  	}
    52  	defer trace.StartRegion(ctx, "Scanner.Scan").End()
    53  	trace.Log(ctx, "layer", layer.Hash.String())
    54  	ctx = zlog.ContextWithValues(ctx,
    55  		"component", "apk/Scanner.Scan",
    56  		"version", version,
    57  		"layer", layer.Hash.String())
    58  
    59  	zlog.Debug(ctx).Msg("start")
    60  	defer zlog.Debug(ctx).Msg("done")
    61  
    62  	sys, err := layer.FS()
    63  	if err != nil {
    64  		return nil, fmt.Errorf("apk: unable to open layer: %w", err)
    65  	}
    66  	b, err := fs.ReadFile(sys, installedFile)
    67  	switch {
    68  	case err == nil:
    69  	case errors.Is(err, fs.ErrNotExist):
    70  		return nil, nil
    71  	default:
    72  		return nil, err
    73  	}
    74  	zlog.Debug(ctx).Msg("found database")
    75  
    76  	pkgs := []*claircore.Package{}
    77  	srcs := make(map[string]*claircore.Package)
    78  
    79  	// It'd be great if we could just use the textproto package here, but we
    80  	// can't because the database "keys" are case sensitive, unlike MIME
    81  	// headers. So, roll our own entry and header splitting.
    82  	delim := []byte("\n\n")
    83  	entries := bytes.Split(b, delim)
    84  	for _, entry := range entries {
    85  		if len(entry) == 0 {
    86  			continue
    87  		}
    88  		p := claircore.Package{
    89  			Kind:      claircore.BINARY,
    90  			PackageDB: installedFile,
    91  		}
    92  		r := bytes.NewBuffer(entry)
    93  		for line, err := r.ReadBytes('\n'); err == nil; line, err = r.ReadBytes('\n') {
    94  			l := string(bytes.TrimSpace(line[2:]))
    95  			switch line[0] {
    96  			case 'P':
    97  				p.Name = l
    98  			case 'V':
    99  				p.Version = l
   100  			case 'c':
   101  				p.RepositoryHint = l
   102  			case 'A':
   103  				p.Arch = l
   104  			case 'o':
   105  				if src, ok := srcs[l]; ok {
   106  					p.Source = src
   107  				} else {
   108  					p.Source = &claircore.Package{
   109  						Name: l,
   110  						Kind: claircore.SOURCE,
   111  					}
   112  					if p.Version != "" {
   113  						p.Source.Version = p.Version
   114  					}
   115  					srcs[l] = p.Source
   116  				}
   117  			}
   118  		}
   119  		pkgs = append(pkgs, &p)
   120  	}
   121  	zlog.Debug(ctx).Int("count", len(pkgs)).Msg("found packages")
   122  
   123  	return pkgs, nil
   124  }