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  }