go.mondoo.com/cnquery@v0.0.0-20231005093811-59568235f6ea/internal/datalakes/inmemory/query_hub.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 "strings" 10 11 "go.mondoo.com/cnquery/explorer" 12 "go.mondoo.com/cnquery/mrn" 13 ) 14 15 type wrapQuery struct { 16 *explorer.Mquery 17 } 18 19 type wrapQueryPack struct { 20 *explorer.QueryPack 21 filters []*explorer.Mquery 22 } 23 24 // QueryExists checks if the given MRN exists 25 func (db *Db) QueryExists(ctx context.Context, mrn string) (bool, error) { 26 _, ok := db.cache.Get(dbIDQuery + mrn) 27 return ok, nil 28 } 29 30 // GetQuery retrieves a given query 31 func (db *Db) GetQuery(ctx context.Context, mrn string) (*explorer.Mquery, error) { 32 q, ok := db.cache.Get(dbIDQuery + mrn) 33 if !ok { 34 return nil, errors.New("query '" + mrn + "' not found") 35 } 36 return (q.(wrapQuery)).Mquery, nil 37 } 38 39 // SetQuery stores a given query 40 // Note: the query must be defined, it cannot be nil 41 func (db *Db) SetQuery(ctx context.Context, mrn string, mquery *explorer.Mquery) error { 42 v := wrapQuery{mquery} 43 ok := db.cache.Set(dbIDQuery+mrn, v, 1) 44 if !ok { 45 return errors.New("failed to save query '" + mrn + "' to cache") 46 } 47 return nil 48 } 49 50 // SetQueryPack stores a given pack in the datalake 51 func (db *Db) SetQueryPack(ctx context.Context, obj *explorer.QueryPack, filters []*explorer.Mquery) error { 52 _, err := db.setQueryPack(ctx, obj, filters) 53 return err 54 } 55 56 // GetQueryPack retrieves the pack 57 func (db *Db) GetQueryPack(ctx context.Context, mrn string) (*explorer.QueryPack, error) { 58 q, ok := db.cache.Get(dbIDQueryPack + mrn) 59 if !ok { 60 return nil, errors.New("query pack '" + mrn + "' not found") 61 } 62 return (q.(wrapQueryPack)).QueryPack, nil 63 } 64 65 // GetQueryPackFilters retrieves the query pack filters 66 func (db *Db) GetQueryPackFilters(ctx context.Context, in string) ([]*explorer.Mquery, error) { 67 // if it's an asset 68 if _, err := mrn.GetResource(in, explorer.MRN_RESOURCE_ASSET); err != nil { 69 return nil, errors.New("can only retrieve query pack filters for assets") 70 } 71 72 x, ok := db.cache.Get(dbIDAsset + in) 73 if !ok { 74 return nil, errors.New("failed to find asset " + in) 75 } 76 asset := x.(wrapAsset) 77 78 return asset.Bundle.Filters(), nil 79 } 80 81 func (db *Db) setQueryPack(ctx context.Context, in *explorer.QueryPack, filters []*explorer.Mquery) (wrapQueryPack, error) { 82 var err error 83 84 for i := range filters { 85 filter := filters[i] 86 if err = db.SetQuery(ctx, filter.Mrn, filter); err != nil { 87 return wrapQueryPack{}, err 88 } 89 } 90 91 obj := wrapQueryPack{ 92 QueryPack: in, 93 filters: filters, 94 } 95 96 ok := db.cache.Set(dbIDQueryPack+obj.Mrn, obj, 2) 97 if !ok { 98 return wrapQueryPack{}, errors.New("failed to save query pack '" + in.Mrn + "' to cache") 99 } 100 101 list, err := db.listQueryPacks() 102 if err != nil { 103 return wrapQueryPack{}, err 104 } 105 106 list[in.Mrn] = struct{}{} 107 ok = db.cache.Set(dbIDListQueryPacks, list, 0) 108 if !ok { 109 return wrapQueryPack{}, errors.New("failed to update query pack list cache") 110 } 111 112 return obj, nil 113 } 114 115 // DeleteQueryPack removes a given mrn 116 // Note: the MRN has to be valid 117 func (db *Db) DeleteQueryPack(ctx context.Context, mrn string) error { 118 _, ok := db.cache.Get(dbIDQueryPack + mrn) 119 if !ok { 120 return nil 121 } 122 123 errors := strings.Builder{} 124 125 // list update 126 list, err := db.listQueryPacks() 127 if err != nil { 128 return err 129 } 130 131 delete(list, mrn) 132 ok = db.cache.Set(dbIDListQueryPacks, list, 0) 133 if !ok { 134 errors.WriteString("failed to update query packs list cache") 135 } 136 137 db.cache.Del(dbIDQueryPack + mrn) 138 139 return nil 140 } 141 142 // ListQueryPacks for a given owner 143 // Note: Owner MRN is required 144 func (db *Db) ListQueryPacks(ctx context.Context, ownerMrn string, name string) ([]*explorer.QueryPack, error) { 145 mrns, err := db.listQueryPacks() 146 if err != nil { 147 return nil, err 148 } 149 150 res := []*explorer.QueryPack{} 151 for k := range mrns { 152 obj, err := db.GetQueryPack(ctx, k) 153 if err != nil { 154 return nil, err 155 } 156 157 if obj.OwnerMrn != ownerMrn { 158 continue 159 } 160 161 res = append(res, obj) 162 } 163 164 return res, nil 165 } 166 167 func (db *Db) listQueryPacks() (map[string]struct{}, error) { 168 x, ok := db.cache.Get(dbIDListQueryPacks) 169 if ok { 170 return x.(map[string]struct{}), nil 171 } 172 173 nu := map[string]struct{}{} 174 ok = db.cache.Set(dbIDListQueryPacks, nu, 0) 175 if !ok { 176 return nil, errors.New("failed to initialize query packs list cache") 177 } 178 return nu, nil 179 } 180 181 // GetBundle retrieves and if necessary updates the pack. Used for assets, 182 // which have multiple query packs associated with them. 183 func (db *Db) GetBundle(ctx context.Context, mrn string) (*explorer.Bundle, error) { 184 x, ok := db.cache.Get(dbIDAsset + mrn) 185 if !ok { 186 return nil, errors.New("failed to find asset " + mrn) 187 } 188 189 res := x.(wrapAsset).Bundle 190 191 // FIXME: we have to compensate for missing query variants in this function 192 // right now, because they don't get uploaded as full bundles right now. 193 // Overhaul this by uploading proper bundles, that include query variants. 194 // vv 195 skipMrn := map[string]struct{}{} 196 for i := range res.Packs { 197 pack := res.Packs[i] 198 for j := range pack.Groups { 199 group := pack.Groups[j] 200 for k := range group.Queries { 201 query := group.Queries[k] 202 if len(query.Variants) == 0 { 203 continue 204 } 205 206 // 🍝 darn spaghetti ... see above fixme 207 for l := range query.Variants { 208 mrn := query.Variants[l].Mrn 209 if _, ok := skipMrn[mrn]; ok { 210 continue 211 } 212 213 skipMrn[mrn] = struct{}{} 214 q, _ := db.GetQuery(ctx, query.Variants[l].Mrn) 215 if q != nil { 216 res.Queries = append(res.Queries, q) 217 } 218 } 219 } 220 } 221 } 222 // ^^ 223 224 return res, nil 225 } 226 227 // MutateBundle runs the given mutation on a bundle, typically an asset. 228 // If it cannot find the owner, it will create it. 229 func (db *Db) MutateBundle(ctx context.Context, mutation *explorer.BundleMutationDelta, createIfMissing bool) (*explorer.Bundle, error) { 230 x, ok := db.cache.Get(dbIDAsset + mutation.OwnerMrn) 231 if !ok { 232 if !createIfMissing { 233 return nil, errors.New("failed to find asset " + mutation.OwnerMrn) 234 } 235 236 var err error 237 x, _, err = db.ensureAssetObject(ctx, mutation.OwnerMrn) 238 if err != nil { 239 return nil, err 240 } 241 } 242 asset := x.(wrapAsset) 243 244 if asset.Bundle == nil { 245 return nil, errors.New("found an asset without a bundle configured in the DB") 246 } 247 248 existing := map[string]*explorer.QueryPack{} 249 for i := range asset.Bundle.Packs { 250 cur := asset.Bundle.Packs[i] 251 existing[cur.Mrn] = cur 252 } 253 254 for _, delta := range mutation.Deltas { 255 switch delta.Action { 256 case explorer.AssignmentDelta_ADD: 257 pack, err := db.GetQueryPack(ctx, delta.Mrn) 258 if err != nil { 259 return nil, errors.New("failed to find query pack for assignment: " + delta.Mrn) 260 } 261 262 existing[delta.Mrn] = pack 263 264 case explorer.AssignmentDelta_DELETE: 265 delete(existing, delta.Mrn) 266 267 default: 268 return nil, errors.New("cannot mutate bundle, the action is unknown") 269 } 270 } 271 272 res := make([]*explorer.QueryPack, len(existing)) 273 i := 0 274 for _, qp := range existing { 275 res[i] = qp 276 i++ 277 } 278 279 asset.Bundle.Packs = res 280 db.cache.Set(dbIDAsset+mutation.OwnerMrn, asset, 1) 281 282 return asset.Bundle, nil 283 }