go.mondoo.com/cnquery@v0.0.0-20231005093811-59568235f6ea/providers/os/resources/packages.go (about) 1 // Copyright (c) Mondoo, Inc. 2 // SPDX-License-Identifier: BUSL-1.1 3 4 package resources 5 6 import ( 7 "errors" 8 "regexp" 9 "sync" 10 11 "github.com/rs/zerolog/log" 12 "go.mondoo.com/cnquery/llx" 13 "go.mondoo.com/cnquery/providers-sdk/v1/plugin" 14 "go.mondoo.com/cnquery/providers/os/connection/shared" 15 "go.mondoo.com/cnquery/providers/os/resources/packages" 16 "go.mondoo.com/cnquery/utils/multierr" 17 ) 18 19 var PKG_IDENTIFIER = regexp.MustCompile(`^(.*):\/\/(.*)\/(.*)\/(.*)$`) 20 21 // A system package cannot be installed twice but there are edge cases: 22 // - the same package name could be installed for multiple archs 23 // - linux-kernel package get extra treatment and can co-exist in multiple versions 24 // We use identifiers similar to grafeas artifact identifier for packages 25 // - deb://name/version/arch 26 // - rpm://name/version/arch 27 func (x *mqlPackage) id() (string, error) { 28 return x.Format.Data + "://" + x.Name.Data + "/" + x.Version.Data + "/" + x.Arch.Data, nil 29 } 30 31 func initPackage(runtime *plugin.Runtime, args map[string]*llx.RawData) (map[string]*llx.RawData, plugin.Resource, error) { 32 // we only look up the package, if we have been supplied by its name and nothing else 33 raw, ok := args["name"] 34 if !ok || len(args) != 1 { 35 return args, nil, nil 36 } 37 name := raw.Value.(string) 38 39 pkgs, err := CreateResource(runtime, "packages", nil) 40 if err != nil { 41 return nil, nil, multierr.Wrap(err, "cannot get list of packages") 42 } 43 packages := pkgs.(*mqlPackages) 44 45 if err = packages.refreshCache(nil); err != nil { 46 return nil, nil, err 47 } 48 49 if res, ok := packages.packagesByName[name]; ok { 50 return nil, res, nil 51 } 52 53 res := &mqlPackage{} 54 res.Name = plugin.TValue[string]{Data: name, State: plugin.StateIsSet} 55 res.Installed = plugin.TValue[bool]{Data: false, State: plugin.StateIsSet} 56 res.Outdated = plugin.TValue[bool]{Data: false, State: plugin.StateIsSet} 57 res.Version.State = plugin.StateIsSet | plugin.StateIsNull 58 res.Epoch.State = plugin.StateIsSet | plugin.StateIsNull 59 res.Available.State = plugin.StateIsSet | plugin.StateIsNull 60 res.Description.State = plugin.StateIsSet | plugin.StateIsNull 61 res.Arch.State = plugin.StateIsSet | plugin.StateIsNull 62 res.Format.State = plugin.StateIsSet | plugin.StateIsNull 63 res.Origin.State = plugin.StateIsSet | plugin.StateIsNull 64 res.Status.State = plugin.StateIsSet | plugin.StateIsNull 65 return nil, res, nil 66 } 67 68 func (p *mqlPackage) status() (string, error) { 69 return "", nil 70 } 71 72 func (p *mqlPackage) outdated() (bool, error) { 73 if len(p.Available.Data) > 0 { 74 return true, nil 75 } 76 return false, nil 77 } 78 79 func (p *mqlPackage) origin() (string, error) { 80 return "", nil 81 } 82 83 type mqlPackagesInternal struct { 84 lock sync.Mutex 85 packagesByName map[string]*mqlPackage 86 } 87 88 func (x *mqlPackages) list() ([]interface{}, error) { 89 x.lock.Lock() 90 defer x.lock.Unlock() 91 92 conn := x.MqlRuntime.Connection.(shared.Connection) 93 pm, err := packages.ResolveSystemPkgManager(conn) 94 if pm == nil || err != nil { 95 return nil, errors.New("could not detect suitable package manager for platform") 96 } 97 98 // retrieve all system packages 99 osPkgs, err := pm.List() 100 if err != nil { 101 return nil, multierr.Wrap(err, "could not retrieve package list for platform") 102 } 103 104 // TODO: do we really need to make this a blocking call, we could update available updates async 105 // we try to retrieve the available updates 106 osAvailablePkgs, err := pm.Available() 107 if err != nil { 108 log.Debug().Err(err).Msg("mql[packages]> could not retrieve available updates") 109 osAvailablePkgs = map[string]packages.PackageUpdate{} 110 } 111 112 // make available updates easily findable 113 // we use packagename-arch as identifier 114 availableMap := make(map[string]packages.PackageUpdate) 115 for _, a := range osAvailablePkgs { 116 availableMap[a.Name+"/"+a.Arch] = a 117 } 118 119 // create MQL package os for each package 120 pkgs := make([]interface{}, len(osPkgs)) 121 for i, osPkg := range osPkgs { 122 // check if we found a newer version 123 available := "" 124 update, ok := availableMap[osPkg.Name+"/"+osPkg.Arch] 125 if ok { 126 available = update.Available 127 log.Debug().Str("package", osPkg.Name).Str("available", update.Available).Msg("mql[packages]> found newer version") 128 } 129 130 pkg, err := CreateResource(x.MqlRuntime, "package", map[string]*llx.RawData{ 131 "name": llx.StringData(osPkg.Name), 132 "version": llx.StringData(osPkg.Version), 133 "available": llx.StringData(available), 134 "arch": llx.StringData(osPkg.Arch), 135 "status": llx.StringData(osPkg.Status), 136 "description": llx.StringData(osPkg.Description), 137 "format": llx.StringData(osPkg.Format), 138 "installed": llx.BoolData(true), 139 "origin": llx.StringData(osPkg.Origin), 140 "epoch": llx.NilData, // TODO: support Epoch 141 }) 142 if err != nil { 143 return nil, err 144 } 145 146 pkgs[i] = pkg 147 } 148 149 return pkgs, x.refreshCache(pkgs) 150 } 151 152 func (x *mqlPackages) refreshCache(all []interface{}) error { 153 if all == nil { 154 raw := x.GetList() 155 if raw.Error != nil { 156 return raw.Error 157 } 158 all = raw.Data 159 } 160 161 x.packagesByName = map[string]*mqlPackage{} 162 163 for i := range all { 164 u := all[i].(*mqlPackage) 165 x.packagesByName[u.Name.Data] = u 166 } 167 168 return nil 169 }