github.com/quay/claircore@v1.5.28/debian/distributionscanner.go (about)

     1  package debian
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"fmt"
     7  	"io/fs"
     8  	"regexp"
     9  	"runtime/trace"
    10  	"strconv"
    11  	"strings"
    12  	"unicode"
    13  
    14  	"github.com/quay/zlog"
    15  
    16  	"github.com/quay/claircore"
    17  	"github.com/quay/claircore/indexer"
    18  	"github.com/quay/claircore/osrelease"
    19  )
    20  
    21  var (
    22  	_ indexer.DistributionScanner = (*DistributionScanner)(nil)
    23  	_ indexer.VersionedScanner    = (*DistributionScanner)(nil)
    24  )
    25  
    26  // DistributionScanner attempts to discover if a layer
    27  // displays characteristics of a Debian distribution.
    28  type DistributionScanner struct{}
    29  
    30  // Name implements [indexer.VersionedScanner].
    31  func (*DistributionScanner) Name() string { return "debian" }
    32  
    33  // Version implements [indexer.VersionedScanner].
    34  func (*DistributionScanner) Version() string { return "3" }
    35  
    36  // Kind implements [indexer.VersionedScanner].
    37  func (*DistributionScanner) Kind() string { return "distribution" }
    38  
    39  // Scan implements [indexer.DistributionScanner].
    40  func (ds *DistributionScanner) Scan(ctx context.Context, l *claircore.Layer) ([]*claircore.Distribution, error) {
    41  	defer trace.StartRegion(ctx, "Scanner.Scan").End()
    42  	ctx = zlog.ContextWithValues(ctx,
    43  		"component", "debian/DistributionScanner.Scan",
    44  		"version", ds.Version(),
    45  		"layer", l.Hash.String())
    46  	zlog.Debug(ctx).Msg("start")
    47  	defer zlog.Debug(ctx).Msg("done")
    48  
    49  	sys, err := l.FS()
    50  	if err != nil {
    51  		return nil, fmt.Errorf("debian: unable to open layer: %w", err)
    52  	}
    53  	d, err := findDist(ctx, sys)
    54  	if err != nil {
    55  		return nil, err
    56  	}
    57  	if d == nil {
    58  		return nil, nil
    59  	}
    60  	return []*claircore.Distribution{d}, nil
    61  }
    62  
    63  func findDist(ctx context.Context, sys fs.FS) (*claircore.Distribution, error) {
    64  	f, err := sys.Open(osrelease.Path)
    65  	switch {
    66  	case errors.Is(err, nil):
    67  	case errors.Is(err, fs.ErrNotExist):
    68  		zlog.Debug(ctx).Msg("no os-release file")
    69  		return nil, nil
    70  	default:
    71  		return nil, fmt.Errorf("debian: unexpected error: %w", err)
    72  	}
    73  	kv, err := osrelease.Parse(ctx, f)
    74  	if err != nil {
    75  		zlog.Info(ctx).
    76  			Err(err).Msg("malformed os-release file")
    77  		return nil, nil
    78  	}
    79  	if kv[`ID`] != `debian` {
    80  		return nil, nil
    81  	}
    82  
    83  	// Regex pattern matches item within string that appear as so: (bookworm), (buster), (bullseye)
    84  	ver := regexp.MustCompile(`\(\w+\)$`)
    85  
    86  	name, nameok := kv[`VERSION_CODENAME`]
    87  	idstr := kv[`VERSION_ID`]
    88  	if !nameok {
    89  		name = strings.TrimFunc(ver.FindString(kv[`VERSION`]), func(r rune) bool { return !unicode.IsLetter(r) })
    90  	}
    91  	if name == "" || idstr == "" {
    92  		zlog.Info(ctx).
    93  			Err(err).Msg("malformed os-release file")
    94  		return nil, nil
    95  	}
    96  	id, err := strconv.ParseInt(idstr, 10, 32)
    97  	if err != nil {
    98  		zlog.Info(ctx).
    99  			Err(err).Msg("malformed os-release file")
   100  		return nil, nil
   101  	}
   102  	return mkDist(name, int(id)), nil
   103  }