go.mondoo.com/cnquery@v0.0.0-20231005093811-59568235f6ea/providers/os/resources/packages/apk_packages.go (about)

     1  // Copyright (c) Mondoo, Inc.
     2  // SPDX-License-Identifier: BUSL-1.1
     3  
     4  package packages
     5  
     6  import (
     7  	"bufio"
     8  	"fmt"
     9  	"io"
    10  	"regexp"
    11  
    12  	"github.com/rs/zerolog/log"
    13  	"go.mondoo.com/cnquery/providers/os/connection/shared"
    14  )
    15  
    16  const (
    17  	AlpinePkgFormat = "apk"
    18  )
    19  
    20  var APK_REGEX = regexp.MustCompile(`^([A-Za-z]):(.*)$`)
    21  
    22  // ParseApkDbPackages parses the database of the apk package manager located in
    23  // `/lib/apk/db/installed`
    24  // Apk spec: https://wiki.alpinelinux.org/wiki/Apk_spec
    25  func ParseApkDbPackages(input io.Reader) []Package {
    26  	pkgs := []Package{}
    27  
    28  	var pkgVersion string
    29  	var pkgEpoch string
    30  
    31  	add := func(pkg Package) {
    32  		// merge version and epoch
    33  		if pkgEpoch == "0" || pkgEpoch == "" {
    34  			pkg.Version = pkgVersion
    35  		} else {
    36  			pkg.Version = pkgEpoch + ":" + pkgVersion
    37  		}
    38  
    39  		pkg.Format = AlpinePkgFormat
    40  
    41  		// do sanitization checks to ensure we have minimal information
    42  		if pkg.Name != "" && pkg.Version != "" {
    43  			pkgs = append(pkgs, pkg)
    44  		} else {
    45  			log.Debug().Msg("ignored apk package since information is missing")
    46  		}
    47  	}
    48  
    49  	scanner := bufio.NewScanner(input)
    50  	pkg := Package{}
    51  	var key string
    52  	for scanner.Scan() {
    53  		line := scanner.Text()
    54  
    55  		// reset package definition once we reach a newline
    56  		if len(line) == 0 {
    57  			add(pkg)
    58  			// reset values
    59  			pkgEpoch = ""
    60  			pkgVersion = ""
    61  			pkg = Package{}
    62  		}
    63  
    64  		m := APK_REGEX.FindStringSubmatch(line)
    65  		key = ""
    66  		if m != nil {
    67  			key = m[1]
    68  		}
    69  
    70  		// if we short line, we ignore it since this is not a valid line
    71  		if len(line) < 2 {
    72  			continue
    73  		}
    74  
    75  		// Parse the package name or version.
    76  		switch key {
    77  		case "P":
    78  			pkg.Name = m[2] // package name
    79  		case "V":
    80  			pkgVersion = m[2] // package version
    81  		case "A":
    82  			pkg.Arch = m[2] // architecture
    83  		case "t":
    84  			pkgEpoch = m[2] // epoch
    85  		case "o":
    86  			pkg.Origin = m[2] // origin
    87  		case "T":
    88  			pkg.Description = m[2] // description
    89  		}
    90  	}
    91  
    92  	// if the last line is not an empty line we have things in flight, lets check it
    93  	add(pkg)
    94  	return pkgs
    95  }
    96  
    97  var APK_UPDATE_REGEX = regexp.MustCompile(`^([a-zA-Z0-9._]+)-([a-zA-Z0-9.\-\+]+)\s+<\s([a-zA-Z0-9.\-\+]+)\s*$`)
    98  
    99  func ParseApkUpdates(input io.Reader) (map[string]PackageUpdate, error) {
   100  	pkgs := map[string]PackageUpdate{}
   101  	scanner := bufio.NewScanner(input)
   102  	for scanner.Scan() {
   103  		line := scanner.Text()
   104  		m := APK_UPDATE_REGEX.FindStringSubmatch(line)
   105  		if m != nil {
   106  			pkgs[m[1]] = PackageUpdate{
   107  				Name:      m[1],
   108  				Version:   m[2],
   109  				Available: m[3],
   110  			}
   111  		}
   112  	}
   113  	return pkgs, nil
   114  }
   115  
   116  // Arch, Manjaro
   117  type AlpinePkgManager struct {
   118  	conn shared.Connection
   119  }
   120  
   121  func (apm *AlpinePkgManager) Name() string {
   122  	return "Alpine Package Manager"
   123  }
   124  
   125  func (apm *AlpinePkgManager) Format() string {
   126  	return AlpinePkgFormat
   127  }
   128  
   129  func (apm *AlpinePkgManager) List() ([]Package, error) {
   130  	fr, err := apm.conn.FileSystem().Open("/lib/apk/db/installed")
   131  	if err != nil {
   132  		return nil, fmt.Errorf("could not read package list")
   133  	}
   134  	defer fr.Close()
   135  
   136  	return ParseApkDbPackages(fr), nil
   137  }
   138  
   139  func (apm *AlpinePkgManager) Available() (map[string]PackageUpdate, error) {
   140  	// it only works if apk is updated
   141  	apm.conn.RunCommand("apk update")
   142  
   143  	// determine package updates
   144  	cmd, err := apm.conn.RunCommand("apk version -v -l '<'")
   145  	if err != nil {
   146  		log.Debug().Err(err).Msg("mql[packages]> could not read package updates")
   147  		return nil, fmt.Errorf("could not read package update list")
   148  	}
   149  	return ParseApkUpdates(cmd.Stdout)
   150  }