go.mondoo.com/cnquery@v0.0.0-20231005093811-59568235f6ea/internal/datalakes/inmemory/query_conductor.go (about) 1 // Copyright (c) Mondoo, Inc. 2 // SPDX-License-Identifier: BUSL-1.1 3 4 package inmemory 5 6 import ( 7 "context" 8 "errors" 9 "fmt" 10 11 "github.com/rs/zerolog/log" 12 "go.mondoo.com/cnquery/explorer" 13 "go.mondoo.com/cnquery/llx" 14 "go.mondoo.com/cnquery/types" 15 "go.mondoo.com/cnquery/utils/multierr" 16 ) 17 18 type wrapResolved struct { 19 *explorer.ResolvedPack 20 filtersChecksum string 21 assetMRN string 22 } 23 24 func (db *Db) SetResolvedPack(mrn string, filtersChecksum string, resolved *explorer.ResolvedPack) error { 25 v := wrapResolved{resolved, filtersChecksum, mrn} 26 ok := db.cache.Set(dbIDresolvedPack+mrn, v, 1) 27 if !ok { 28 return errors.New("failed to save query '" + mrn + "' to cache") 29 } 30 return nil 31 } 32 33 func (db *Db) SetAssetResolvedPack(ctx context.Context, assetMrn string, resolved *explorer.ResolvedPack, version explorer.ResolvedVersion) error { 34 x, ok := db.cache.Get(dbIDAsset + assetMrn) 35 if !ok { 36 return errors.New("cannot find asset '" + assetMrn + "'") 37 } 38 assetw := x.(wrapAsset) 39 40 if assetw.ResolvedPack != nil && assetw.ResolvedPack.GraphExecutionChecksum == resolved.GraphExecutionChecksum && string(assetw.ResolvedVersion) == string(version) { 41 log.Debug(). 42 Str("asset", assetMrn). 43 Msg("resolverj.db> asset resolved query pack is already cached (and unchanged)") 44 return nil 45 } 46 47 assetw.ResolvedPack = resolved 48 assetw.ResolvedVersion = version 49 50 var err error 51 job := resolved.ExecutionJob 52 for checksum, info := range job.Datapoints { 53 err = db.initDataValue(ctx, assetMrn, checksum, types.Type(info.Type)) 54 if err != nil { 55 log.Error(). 56 Err(err). 57 Str("asset", assetMrn). 58 Str("query checksum", checksum). 59 Msg("resolver.db> failed to set asset resolved pack, failed to initialize data value") 60 return errors.New("failed to create asset scoring job (failed to init data)") 61 } 62 } 63 64 ok = db.cache.Set(dbIDAsset+assetMrn, assetw, 1) 65 if !ok { 66 return errors.New("failed to save resolved pack for asset '" + assetMrn + "'") 67 } 68 69 return nil 70 } 71 72 func (db *Db) GetResolvedPack(mrn string) (*explorer.ResolvedPack, error) { 73 q, ok := db.cache.Get(dbIDresolvedPack + mrn) 74 if !ok { 75 return nil, errors.New("query '" + mrn + "' not found") 76 } 77 return (q.(wrapResolved)).ResolvedPack, nil 78 } 79 80 var errTypesDontMatch = errors.New("types don't match") 81 82 // UpdateData sets the list of data value for a given asset and returns a list of updated IDs 83 func (db *Db) UpdateData(ctx context.Context, assetMrn string, data map[string]*llx.Result) (map[string]types.Type, error) { 84 resolved, err := db.GetResolvedPack(assetMrn) 85 if err != nil { 86 return nil, errors.New("cannot find collectorJob to store data: " + err.Error()) 87 } 88 executionJob := resolved.ExecutionJob 89 90 res := make(map[string]types.Type, len(data)) 91 var errs multierr.Errors 92 for dpChecksum, val := range data { 93 info, ok := executionJob.Datapoints[dpChecksum] 94 if !ok { 95 return nil, errors.New("cannot find this datapoint to store values: " + dpChecksum) 96 } 97 98 if val.Data != nil && !val.Data.IsNil() && val.Data.Type != "" && 99 val.Data.Type != info.Type && types.Type(info.Type) != types.Unset { 100 log.Warn(). 101 Str("checksum", dpChecksum). 102 Str("asset", assetMrn). 103 Interface("data", val.Data). 104 Str("expected", types.Type(info.Type).Label()). 105 Str("received", types.Type(val.Data.Type).Label()). 106 Msg("resolver.db> failed to store data, types don't match") 107 108 errs.Add(fmt.Errorf("failed to store data for %q, %w: expected %s, got %s", 109 dpChecksum, errTypesDontMatch, types.Type(info.Type).Label(), types.Type(val.Data.Type).Label())) 110 111 continue 112 } 113 114 err := db.setDatum(ctx, assetMrn, dpChecksum, val) 115 if err != nil { 116 errs.Add(err) 117 continue 118 } 119 120 // TODO: we don't know which data was updated and which wasn't yet, so 121 // we currently always notify... 122 res[dpChecksum] = types.Type(info.Type) 123 } 124 125 if !errs.IsEmpty() { 126 return nil, errs.Deduplicate() 127 } 128 return res, nil 129 } 130 131 func (db *Db) setDatum(ctx context.Context, assetMrn string, checksum string, value *llx.Result) error { 132 id := dbIDData + assetMrn + "\x00" + checksum 133 ok := db.cache.Set(id, value, 1) 134 if !ok { 135 return errors.New("failed to save asset data for asset '" + assetMrn + "' and checksum '" + checksum + "'") 136 } 137 return nil 138 } 139 140 // GetReport retrieves all scores and data for a given asset 141 func (db *Db) GetReport(ctx context.Context, assetMrn string, packMrn string) (*explorer.Report, error) { 142 x, ok := db.cache.Get(dbIDAsset + assetMrn) 143 if !ok { 144 return nil, errors.New("cannot find asset '" + assetMrn + "'") 145 } 146 assetw := x.(wrapAsset) 147 resolvedPack := assetw.ResolvedPack 148 149 data := map[string]*llx.Result{} 150 for id := range resolvedPack.ExecutionJob.Datapoints { 151 datum, ok := db.cache.Get(dbIDData + assetMrn + "\x00" + id) 152 if !ok { 153 continue 154 } 155 if datum == nil { 156 data[id] = &llx.Result{ 157 Data: llx.NilPrimitive, 158 CodeId: id, 159 } 160 } 161 data[id] = datum.(*llx.Result) 162 } 163 164 return &explorer.Report{ 165 PackMrn: packMrn, 166 EntityMrn: assetMrn, 167 Data: data, 168 }, nil 169 } 170 171 func (db *Db) initDataValue(ctx context.Context, assetMrn string, checksum string, typ types.Type) error { 172 id := dbIDData + assetMrn + "\x00" + checksum 173 _, ok := db.cache.Get(id) 174 if ok { 175 return nil 176 } 177 178 ok = db.cache.Set(id, nil, 1) 179 if !ok { 180 return errors.New("failed to initialize data value for asset '" + assetMrn + "' with checksum '" + checksum + "'") 181 } 182 return nil 183 } 184 185 // SetProps will override properties for a given entity (asset, space, org) 186 func (db *Db) SetProps(ctx context.Context, req *explorer.PropsReq) error { 187 x, ok := db.cache.Get(dbIDAsset + req.EntityMrn) 188 if !ok { 189 return errors.New("failed to find entity " + req.EntityMrn) 190 } 191 asset := x.(wrapAsset) 192 193 if asset.Bundle == nil { 194 return errors.New("found an asset without a bundle configured in the DB") 195 } 196 197 allProps := make(map[string]*explorer.Property, len(asset.Bundle.Props)) 198 for i := range asset.Bundle.Props { 199 cur := asset.Bundle.Props[i] 200 if cur.Mrn != "" { 201 allProps[cur.Mrn] = cur 202 } 203 if cur.Uid != "" { 204 allProps[cur.Uid] = cur 205 } 206 } 207 208 for i := range req.Props { 209 cur := req.Props[i] 210 id := cur.Mrn 211 if id == "" { 212 id = cur.Uid 213 } 214 if id == "" { 215 return errors.New("cannot set property without MRN: " + cur.Mql) 216 } 217 218 if cur.Mql == "" { 219 delete(allProps, id) 220 } 221 allProps[id] = cur 222 } 223 224 asset.Bundle.Props = []*explorer.Property{} 225 for k, v := range allProps { 226 // since props can be in the list with both UIDs and MRNs, in the case 227 // where a property sets both we want to ignore one entry to avoid duplicates 228 if v.Mrn != "" && v.Uid != "" && k == v.Uid { 229 continue 230 } 231 asset.Bundle.Props = append(asset.Bundle.Props, v) 232 } 233 234 db.cache.Set(dbIDAsset+req.EntityMrn, asset, 1) 235 236 return nil 237 }