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 }