go.mondoo.com/cnquery@v0.0.0-20231005093811-59568235f6ea/explorer/property.go (about) 1 // Copyright (c) Mondoo, Inc. 2 // SPDX-License-Identifier: BUSL-1.1 3 4 package explorer 5 6 import ( 7 "context" 8 "errors" 9 10 "github.com/rs/zerolog/log" 11 "go.mondoo.com/cnquery" 12 "go.mondoo.com/cnquery/checksums" 13 llx "go.mondoo.com/cnquery/llx" 14 "go.mondoo.com/cnquery/mqlc" 15 "go.mondoo.com/cnquery/mrn" 16 "go.mondoo.com/cnquery/types" 17 "go.mondoo.com/cnquery/utils/multierr" 18 "google.golang.org/protobuf/proto" 19 ) 20 21 // RefreshMRN computes a MRN from the UID or validates the existing MRN. 22 // Both of these need to fit the ownerMRN. It also removes the UID. 23 func (p *Property) RefreshMRN(ownerMRN string) error { 24 nu, err := RefreshMRN(ownerMRN, p.Mrn, MRN_RESOURCE_QUERY, p.Uid) 25 if err != nil { 26 log.Error().Err(err).Str("owner", ownerMRN).Str("uid", p.Uid).Msg("failed to refresh mrn") 27 return multierr.Wrap(err, "failed to refresh mrn for query "+p.Title) 28 } 29 30 p.Mrn = nu 31 p.Uid = "" 32 return nil 33 } 34 35 // Compile a given property and return the bundle. 36 func (p *Property) Compile(props map[string]*llx.Primitive, schema llx.Schema) (*llx.CodeBundle, error) { 37 return mqlc.Compile(p.Mql, props, mqlc.NewConfig(schema, cnquery.DefaultFeatures)) 38 } 39 40 // RefreshChecksumAndType by compiling the query and updating the Checksum field 41 func (p *Property) RefreshChecksumAndType(schema llx.Schema) (*llx.CodeBundle, error) { 42 return p.refreshChecksumAndType(schema) 43 } 44 45 func (p *Property) refreshChecksumAndType(schema llx.Schema) (*llx.CodeBundle, error) { 46 bundle, err := p.Compile(nil, schema) 47 if err != nil { 48 return bundle, multierr.Wrap(err, "failed to compile property '"+p.Mql+"'") 49 } 50 51 if bundle.GetCodeV2().GetId() == "" { 52 return bundle, errors.New("failed to compile query: received empty result values") 53 } 54 55 // We think its ok to always use the new code id 56 p.CodeId = bundle.CodeV2.Id 57 58 // the compile step also dedents the code 59 p.Mql = bundle.Source 60 61 // TODO: record multiple entrypoints and types 62 // TODO(jaym): is it possible that the 2 could produce different types 63 if entrypoints := bundle.CodeV2.Entrypoints(); len(entrypoints) == 1 { 64 ep := entrypoints[0] 65 chunk := bundle.CodeV2.Chunk(ep) 66 typ := chunk.Type() 67 p.Type = string(typ) 68 } else { 69 p.Type = string(types.Any) 70 } 71 72 c := checksums.New. 73 Add(p.Mql). 74 Add(p.CodeId). 75 Add(p.Mrn). 76 Add(p.Type). 77 Add(p.Context). 78 Add(p.Title).Add("v2"). 79 Add(p.Desc) 80 81 for i := range p.For { 82 f := p.For[i] 83 c = c.Add(f.Mrn) 84 } 85 86 p.Checksum = c.String() 87 88 return bundle, nil 89 } 90 91 func (p *Property) Merge(base *Property) { 92 if p.Mql == "" { 93 p.Mql = base.Mql 94 } 95 if p.Type == "" { 96 p.Type = base.Type 97 } 98 if p.Context == "" { 99 p.Context = base.Context 100 } 101 if p.Title == "" { 102 p.Title = base.Title 103 } 104 if p.Desc == "" { 105 p.Desc = base.Desc 106 } 107 if len(p.For) == 0 { 108 p.For = base.For 109 } 110 } 111 112 type PropsCache struct { 113 cache map[string]*Property 114 uidOnlyProps map[string]*Property 115 } 116 117 func NewPropsCache() PropsCache { 118 return PropsCache{ 119 cache: map[string]*Property{}, 120 uidOnlyProps: map[string]*Property{}, 121 } 122 } 123 124 // Add properties, NOT overwriting existing ones (instead we add them as base) 125 func (c PropsCache) Add(props ...*Property) { 126 for i := range props { 127 base := props[i] 128 if base.Uid != "" && base.Mrn == "" { 129 // keep track of properties that were specified by uid only. 130 // we will merge them in later if we find a matching mrn 131 c.uidOnlyProps[base.Uid] = base 132 continue 133 } 134 // All properties at this point should have a mrn 135 if base.Mrn != "" { 136 if existingProp, ok := c.cache[base.Mrn]; ok { 137 existingProp.Merge(base) 138 } else { 139 c.cache[base.Mrn] = base 140 } 141 } 142 } 143 } 144 145 // try to Get the mrn, will also return uid-based 146 // properties if they exist first 147 func (c PropsCache) Get(ctx context.Context, propMrn string) (*Property, string, error) { 148 if res, ok := c.cache[propMrn]; ok { 149 name, err := mrn.GetResource(propMrn, MRN_RESOURCE_QUERY) 150 if err != nil { 151 return nil, "", errors.New("failed to get property name") 152 } 153 if uidProp, ok := c.uidOnlyProps[name]; ok { 154 // We have a property that was specified by uid only. We need to merge it in 155 // to get the full property. 156 p := proto.Clone(uidProp).(*Property) 157 p.Merge(res) 158 return p, name, nil 159 } else { 160 // Everything was specified by mrn 161 return res, name, nil 162 } 163 } 164 165 // We currently don't grab properties from upstream. This requires further investigation. 166 return nil, "", errors.New("property " + propMrn + " not found") 167 }