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