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  }