github.com/yanndegat/hiera@v0.6.8/session/invocation.go (about) 1 package session 2 3 import ( 4 "fmt" 5 "strings" 6 "sync" 7 8 "github.com/yanndegat/hiera/merge" 9 10 "github.com/lyraproj/dgo/vf" 11 12 "github.com/lyraproj/dgo/typ" 13 14 "github.com/lyraproj/dgo/dgo" 15 "github.com/lyraproj/dgo/util" 16 "github.com/lyraproj/hierasdk/hiera" 17 "github.com/yanndegat/hiera/api" 18 "github.com/yanndegat/hiera/config" 19 ) 20 21 const hieraConfigsPrefix = `HieraConfig:` 22 const hieraLockPrefix = `HieraLock:` 23 24 type invocationMode byte 25 26 const ( 27 topLevelMode = invocationMode(iota) 28 lookupOptionsMode 29 dataMode 30 ) 31 32 type ivContext struct { 33 api.Session 34 nameStack []string 35 scope dgo.Keyed 36 luOpts dgo.Map 37 strategy api.MergeStrategy 38 configs map[string]api.ResolvedConfig 39 explainer api.Explainer 40 mode invocationMode 41 redacted bool 42 } 43 44 type nestedScope struct { 45 parentScope dgo.Keyed 46 scope dgo.Keyed 47 } 48 49 func newInvocation(s api.Session, scope dgo.Keyed, explainer api.Explainer) api.Invocation { 50 return &ivContext{ 51 Session: s, 52 nameStack: []string{}, 53 scope: scope, 54 configs: map[string]api.ResolvedConfig{}, 55 explainer: explainer, 56 mode: topLevelMode} 57 } 58 59 func (ns *nestedScope) Get(key interface{}) dgo.Value { 60 if v := ns.scope.Get(key); v != nil { 61 return v 62 } 63 return ns.parentScope.Get(key) 64 } 65 66 func (ic *ivContext) Config(configPath string, moduleName string) api.ResolvedConfig { 67 sc := ic.SharedCache() 68 if configPath == `` { 69 configPath = ic.SessionOptions().Get(api.HieraConfig).String() 70 } 71 72 if rc, ok := ic.configs[configPath]; ok { 73 return rc 74 } 75 76 cp := hieraConfigsPrefix + configPath 77 if val, ok := sc.Load(cp); ok { 78 rc := Resolve(ic, val.(api.Config), moduleName) 79 ic.configs[configPath] = rc 80 return rc 81 } 82 83 lc := hieraLockPrefix + configPath 84 85 myLock := sync.RWMutex{} 86 myLock.Lock() 87 88 var conf api.Config 89 if lv, loaded := sc.LoadOrStore(lc, &myLock); loaded { 90 // myLock was not stored so unlock it 91 myLock.Unlock() 92 93 if lock, ok := lv.(*sync.RWMutex); ok { 94 // The loaded value is a lock. Wait for new config to be stored in place of 95 // this lock 96 lock.RLock() 97 val, _ := sc.Load(cp) 98 conf = val.(api.Config) 99 lock.RUnlock() 100 } else { 101 conf = lv.(api.Config) 102 } 103 } else { 104 conf = config.New(configPath) 105 sc.Store(cp, conf) 106 myLock.Unlock() 107 } 108 rc := Resolve(ic, conf, moduleName) 109 ic.configs[configPath] = rc 110 return rc 111 } 112 113 func (ic *ivContext) ExplainMode() bool { 114 return ic.explainer != nil 115 } 116 117 func (ic *ivContext) LookupOptionsMode() bool { 118 return ic.mode == lookupOptionsMode 119 } 120 121 func (ic *ivContext) DataMode() bool { 122 return ic.mode == dataMode 123 } 124 125 func (ic *ivContext) extractConversion() (convertToType dgo.Type, convertToArgs dgo.Array) { 126 lo := ic.luOpts 127 if lo == nil { 128 return 129 } 130 ct := lo.Get(`convert_to`) 131 if ct == nil { 132 return 133 } 134 var ts dgo.Value 135 if cm, ok := ct.(dgo.Array); ok { 136 // First arg must be a type. The rest is arguments 137 switch cm.Len() { 138 case 0: 139 // Obviously bogus 140 case 1: 141 ts = cm.Get(0) 142 default: 143 ts = cm.Get(0) 144 convertToArgs = cm.Slice(1, cm.Len()) 145 } 146 } else { 147 ts = ct 148 } 149 if ts != nil { 150 ic.AliasMap().Collect(func(aa dgo.AliasAdder) { 151 convertToType = ic.Dialect().ParseType(aa, ts.(dgo.String)) 152 }) 153 } 154 return 155 } 156 157 func (ic *ivContext) SetMergeStrategy(cliMergeOption dgo.Value, lookupOptions dgo.Map) { 158 var opts dgo.Value 159 if cliMergeOption != nil { 160 ic.ReportMergeSource(`CLI option`) 161 opts = cliMergeOption 162 } else if lookupOptions != nil { 163 if opts = lookupOptions.Get(`merge`); opts != nil { 164 ic.ReportMergeSource(`"lookup_options" hash`) 165 } 166 } 167 168 var mergeName string 169 var mergeOpts dgo.Map 170 switch opts := opts.(type) { 171 case dgo.String: 172 mergeName = opts.String() 173 case dgo.Map: 174 if mn, ok := opts.Get(`strategy`).(dgo.String); ok { 175 mergeName = mn.String() 176 mergeOpts = opts.Without(`strategy`) 177 } 178 default: 179 mergeName = `first` 180 } 181 ic.luOpts = lookupOptions 182 ic.strategy = merge.GetStrategy(mergeName, mergeOpts) 183 } 184 185 func (ic *ivContext) LookupAndConvertData(fn func() dgo.Value) dgo.Value { 186 convertToType, convertToArgs := ic.extractConversion() 187 188 var v dgo.Value 189 if typ.Sensitive.Equals(convertToType) { 190 ic.DoRedacted(func() { v = fn() }) 191 } else { 192 v = fn() 193 } 194 195 if v != nil && convertToType != nil { 196 if convertToArgs != nil { 197 v = vf.Arguments(vf.Values(v).WithAll(convertToArgs)) 198 } 199 v = vf.New(convertToType, v) 200 } 201 return v 202 } 203 204 func (ic *ivContext) MergeHierarchy(key api.Key, pvs []api.DataProvider, merge api.MergeStrategy) dgo.Value { 205 return merge.MergeLookup(pvs, ic, func(pv interface{}) dgo.Value { 206 pr := pv.(api.DataProvider) 207 return ic.MergeLocations(key, pr, merge) 208 }) 209 } 210 211 func (ic *ivContext) MergeLocations(key api.Key, dh api.DataProvider, merge api.MergeStrategy) dgo.Value { 212 return ic.WithDataProvider(dh, func() dgo.Value { 213 locations := dh.Hierarchy().Locations() 214 switch len(locations) { 215 case 0: 216 if locations == nil { 217 return ic.invokeWithLocation(dh, nil, key) 218 } 219 return nil // glob or mapped_paths resulted in zero entries 220 case 1: 221 return ic.invokeWithLocation(dh, locations[0], key) 222 default: 223 return merge.MergeLookup(locations, ic, func(location interface{}) dgo.Value { 224 return ic.invokeWithLocation(dh, location.(api.Location), key) 225 }) 226 } 227 }) 228 } 229 230 func (ic *ivContext) invokeWithLocation(dh api.DataProvider, location api.Location, key api.Key) dgo.Value { 231 if location == nil { 232 return dh.LookupKey(key, ic, nil) 233 } 234 return ic.WithLocation(location, func() dgo.Value { 235 if location.Exists() { 236 return dh.LookupKey(key, ic, location) 237 } 238 ic.ReportLocationNotFound() 239 return nil 240 }) 241 } 242 243 func (ic *ivContext) Lookup(key api.Key, options dgo.Map) dgo.Value { 244 rootKey := key.Root() 245 if rootKey == `lookup_options` { 246 return ic.WithInvalidKey(key, func() dgo.Value { 247 ic.ReportNotFound(key) 248 return nil 249 }) 250 } 251 252 v := ic.TopProvider()(ic.ServerContext(options), rootKey) 253 if v != nil { 254 dc := ic.ForData() 255 v = key.Dig(dc, v) 256 } 257 return v 258 } 259 260 func (ic *ivContext) WithKey(key api.Key, actor dgo.Producer) dgo.Value { 261 if util.ContainsString(ic.nameStack, key.Source()) { 262 panic(fmt.Errorf(`recursive lookup detected in [%s]`, strings.Join(ic.nameStack, `, `))) 263 } 264 ic.nameStack = append(ic.nameStack, key.Source()) 265 defer func() { 266 ic.nameStack = ic.nameStack[:len(ic.nameStack)-1] 267 }() 268 return actor() 269 } 270 271 func (ic *ivContext) DoRedacted(doer dgo.Doer) { 272 if ic.redacted { 273 doer() 274 } else { 275 defer func() { 276 ic.redacted = false 277 }() 278 ic.redacted = true 279 doer() 280 } 281 } 282 283 func (ic *ivContext) DoWithScope(scope dgo.Keyed, doer dgo.Doer) { 284 sc := ic.scope 285 ic.scope = scope 286 doer() 287 ic.scope = sc 288 } 289 290 func (ic *ivContext) Scope() dgo.Keyed { 291 return ic.scope 292 } 293 294 // ServerContext creates and returns a new server context 295 func (ic *ivContext) ServerContext(options dgo.Map) api.ServerContext { 296 return &serverCtx{ProviderContext: hiera.ProviderContextFromMap(options), invocation: ic} 297 } 298 299 func (ic *ivContext) WithDataProvider(p api.DataProvider, producer dgo.Producer) dgo.Value { 300 if ic.explainer == nil { 301 return producer() 302 } 303 defer ic.explainer.Pop() 304 ic.explainer.PushDataProvider(p) 305 return producer() 306 } 307 308 func (ic *ivContext) WithInterpolation(expr string, producer dgo.Producer) dgo.Value { 309 if ic.explainer == nil { 310 return producer() 311 } 312 defer ic.explainer.Pop() 313 ic.explainer.PushInterpolation(expr) 314 return producer() 315 } 316 317 func (ic *ivContext) WithInvalidKey(key interface{}, producer dgo.Producer) dgo.Value { 318 if ic.explainer == nil { 319 return producer() 320 } 321 defer ic.explainer.Pop() 322 ic.explainer.PushInvalidKey(key) 323 return producer() 324 } 325 326 func (ic *ivContext) WithLocation(loc api.Location, producer dgo.Producer) dgo.Value { 327 if ic.explainer == nil { 328 return producer() 329 } 330 defer ic.explainer.Pop() 331 ic.explainer.PushLocation(loc) 332 return producer() 333 } 334 335 func (ic *ivContext) WithLookup(key api.Key, producer dgo.Producer) dgo.Value { 336 if ic.explainer == nil { 337 return producer() 338 } 339 defer ic.explainer.Pop() 340 ic.explainer.PushLookup(key) 341 return producer() 342 } 343 344 func (ic *ivContext) WithMerge(ms api.MergeStrategy, producer dgo.Producer) dgo.Value { 345 if ic.explainer == nil { 346 return producer() 347 } 348 defer ic.explainer.Pop() 349 ic.explainer.PushMerge(ms) 350 return producer() 351 } 352 353 func (ic *ivContext) WithModule(moduleName string, producer dgo.Producer) dgo.Value { 354 if ic.explainer == nil { 355 return producer() 356 } 357 defer ic.explainer.Pop() 358 ic.explainer.PushModule(moduleName) 359 return producer() 360 } 361 362 func (ic *ivContext) WithSegment(seg interface{}, producer dgo.Producer) dgo.Value { 363 if ic.explainer == nil { 364 return producer() 365 } 366 defer ic.explainer.Pop() 367 ic.explainer.PushSegment(seg) 368 return producer() 369 } 370 371 func (ic *ivContext) WithSubLookup(key api.Key, producer dgo.Producer) dgo.Value { 372 if ic.explainer == nil { 373 return producer() 374 } 375 defer ic.explainer.Pop() 376 ic.explainer.PushSubLookup(key) 377 return producer() 378 } 379 380 func (ic *ivContext) ForConfig() api.Invocation { 381 if ic.explainer == nil { 382 return ic 383 } 384 lic := *ic 385 lic.explainer = nil 386 return &lic 387 } 388 389 func (ic *ivContext) ForData() api.Invocation { 390 if ic.DataMode() { 391 return ic 392 } 393 lic := *ic 394 if !(lic.explainer == nil || !lic.explainer.OnlyOptions()) { 395 lic.explainer = nil 396 } 397 lic.mode = dataMode 398 return &lic 399 } 400 401 func (ic *ivContext) LookupOptions() dgo.Map { 402 return ic.luOpts 403 } 404 405 func (ic *ivContext) MergeStrategy() api.MergeStrategy { 406 return ic.strategy 407 } 408 409 func (ic *ivContext) ForLookupOptions() api.Invocation { 410 if ic.LookupOptionsMode() { 411 return ic 412 } 413 lic := *ic 414 if !(ic.explainer == nil || ic.explainer.Options() || ic.explainer.OnlyOptions()) { 415 lic.explainer = nil 416 } 417 lic.mode = lookupOptionsMode 418 return &lic 419 } 420 421 func (ic *ivContext) ReportLocationNotFound() { 422 if ic.explainer != nil { 423 ic.explainer.AcceptLocationNotFound() 424 } 425 } 426 427 func (ic *ivContext) ReportFound(key interface{}, value dgo.Value) { 428 if ic.explainer != nil { 429 ic.explainer.AcceptFound(key, value) 430 } 431 } 432 433 func (ic *ivContext) ReportMergeResult(value dgo.Value) { 434 if ic.explainer != nil { 435 ic.explainer.AcceptMergeResult(value) 436 } 437 } 438 439 func (ic *ivContext) ReportMergeSource(source string) { 440 if ic.explainer != nil { 441 ic.explainer.AcceptMergeSource(source) 442 } 443 } 444 445 func (ic *ivContext) ReportModuleNotFound() { 446 if ic.explainer != nil { 447 ic.explainer.AcceptModuleNotFound() 448 } 449 } 450 451 func (ic *ivContext) ReportNotFound(key interface{}) { 452 if ic.explainer != nil { 453 ic.explainer.AcceptNotFound(key) 454 } 455 } 456 457 func (ic *ivContext) ReportText(messageProducer func() string) { 458 if ic.explainer != nil { 459 ic.explainer.AcceptText(messageProducer()) 460 } 461 }