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  }