github.com/yanndegat/hiera@v0.6.8/session/session.go (about)

     1  package session
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"fmt"
     7  	"os"
     8  	"path/filepath"
     9  	"runtime"
    10  	"strings"
    11  	"sync"
    12  	"unicode"
    13  
    14  	"github.com/lyraproj/dgo/streamer/pcore"
    15  
    16  	"github.com/yanndegat/hiera/config"
    17  
    18  	"github.com/lyraproj/dgo/dgo"
    19  	"github.com/lyraproj/dgo/loader"
    20  	"github.com/lyraproj/dgo/streamer"
    21  	"github.com/lyraproj/dgo/tf"
    22  	"github.com/lyraproj/dgo/vf"
    23  	"github.com/lyraproj/hierasdk/hiera"
    24  	"github.com/yanndegat/hiera/api"
    25  	"github.com/yanndegat/hiera/provider"
    26  )
    27  
    28  type session struct {
    29  	context.Context
    30  	aliasMap dgo.AliasMap
    31  	dialect  streamer.Dialect
    32  	vars     map[string]interface{}
    33  	scope    dgo.Keyed
    34  	loader   dgo.Loader
    35  }
    36  
    37  const hieraCacheKey = `Hiera::Cache`
    38  const hieraTopProviderKey = `Hiera::TopProvider`
    39  const hieraSessionOptionsKey = `Hiera::SessionOptions`
    40  const hieraTopProviderCacheKey = `Hiera::TopProvider::Cache`
    41  const hieraPluginRegistry = `Hiera::Plugins`
    42  
    43  // New creates a new Hiera Session which, among other things, holds on to a synchronized
    44  // cache where all loaded things end up.
    45  //
    46  // parent: typically obtained using context.Background() but can be any context.
    47  //
    48  // topProvider: the topmost provider that defines the hierarchy
    49  //
    50  // options: a map[string]any of configuration options
    51  func New(parent context.Context, topProvider hiera.LookupKey, oif interface{}, ldr dgo.Loader) api.Session {
    52  	if topProvider == nil {
    53  		topProvider = provider.ConfigLookupKey
    54  	}
    55  
    56  	options := vf.MutableMap()
    57  	if oif != nil {
    58  		options.PutAll(api.ToMap(`session options`, oif))
    59  	}
    60  
    61  	if options.Get(api.HieraConfig) == nil {
    62  		addHieraConfig(options)
    63  	}
    64  
    65  	var dialect streamer.Dialect
    66  	if ds, ok := options.Get(api.HieraDialect).(dgo.String); ok {
    67  		switch ds.String() {
    68  		case "dgo":
    69  			dialect = streamer.DgoDialect()
    70  		case "pcore":
    71  			dialect = pcore.Dialect()
    72  		default:
    73  			panic(fmt.Errorf(`unknown dialect '%s'`, ds))
    74  		}
    75  	}
    76  	if dialect == nil {
    77  		dialect = streamer.DgoDialect()
    78  	}
    79  
    80  	var scope dgo.Keyed
    81  	if sv, ok := options.Get(api.HieraScope).(dgo.Keyed); ok {
    82  		// Freeze scope if possible
    83  		if f, ok := sv.(dgo.Freezable); ok {
    84  			sv = f.FrozenCopy().(dgo.Keyed)
    85  		}
    86  		scope = sv
    87  	} else {
    88  		scope = vf.Map()
    89  	}
    90  	options.Freeze()
    91  
    92  	vars := map[string]interface{}{
    93  		hieraCacheKey:            &sync.Map{},
    94  		hieraTopProviderKey:      topProvider,
    95  		hieraTopProviderCacheKey: &sync.Map{},
    96  		hieraSessionOptionsKey:   options,
    97  		hieraPluginRegistry:      &pluginRegistry{}}
    98  
    99  	s := &session{Context: parent, aliasMap: tf.DefaultAliases(), vars: vars, dialect: dialect, scope: scope}
   100  	s.loader = s.newHieraLoader(ldr)
   101  	return s
   102  }
   103  
   104  func addHieraConfig(options dgo.Map) {
   105  	var hieraRoot string
   106  	if r := options.Get(api.HieraRoot); r != nil {
   107  		hieraRoot = r.String()
   108  	} else {
   109  		var err error
   110  		if hieraRoot, err = os.Getwd(); err != nil {
   111  			panic(err)
   112  		}
   113  	}
   114  
   115  	var fileName string
   116  	if r := options.Get(api.HieraConfigFileName); r != nil {
   117  		fileName = r.String()
   118  	} else if configFile, ok := os.LookupEnv("HIERA_CONFIGFILE"); ok {
   119  		fileName = configFile
   120  	} else {
   121  		fileName = config.FileName
   122  	}
   123  	options.Put(api.HieraConfig, filepath.Join(hieraRoot, fileName))
   124  }
   125  
   126  func (s *session) AliasMap() dgo.AliasMap {
   127  	return s.aliasMap
   128  }
   129  
   130  func (s *session) Dialect() streamer.Dialect {
   131  	return s.dialect
   132  }
   133  
   134  func (s *session) Invocation(si interface{}, explainer api.Explainer) api.Invocation {
   135  	var scope dgo.Keyed
   136  	if si == nil {
   137  		scope = s.Scope()
   138  	} else {
   139  		scope = &nestedScope{s.Scope(), api.ToMap(`invocation scope`, si)}
   140  	}
   141  	return newInvocation(s, scope, explainer)
   142  }
   143  
   144  // KillPlugins will ensure that all plugins started by this executable are gracefully terminated if possible or
   145  // otherwise forcefully killed.
   146  func (s *session) KillPlugins() {
   147  	if pr := s.Get(hieraPluginRegistry); pr != nil {
   148  		pr.(*pluginRegistry).stopAll()
   149  	}
   150  }
   151  
   152  func (s *session) Loader() dgo.Loader {
   153  	return s.loader
   154  }
   155  
   156  func (s *session) LoadFunction(he api.Entry) (fn dgo.Function, ok bool) {
   157  	n := he.Function().Name()
   158  	l := s.Loader()
   159  	fn, ok = l.Namespace(`function`).Get(n).(dgo.Function)
   160  	if ok {
   161  		return
   162  	}
   163  
   164  	file := he.PluginFile()
   165  	if file == `` {
   166  		file = n
   167  		if runtime.GOOS == `windows` {
   168  			file += `.exe`
   169  		}
   170  	}
   171  
   172  	var path string
   173  	if filepath.IsAbs(file) {
   174  		path = filepath.Clean(file)
   175  	} else {
   176  		path = filepath.Clean(filepath.Join(he.PluginDir(), file))
   177  		abs, err := filepath.Abs(path)
   178  		if err != nil {
   179  			panic(err)
   180  		}
   181  		path = abs
   182  	}
   183  
   184  	l = l.Namespace(`plugin`)
   185  	for _, pn := range strings.Split(path, string(os.PathSeparator)) {
   186  		l = l.Namespace(pn)
   187  		if l == nil {
   188  			return nil, false
   189  		}
   190  	}
   191  
   192  	fn, ok = l.Get(n).(dgo.Function)
   193  	return fn, ok
   194  }
   195  
   196  func (s *session) Scope() dgo.Keyed {
   197  	return s.scope
   198  }
   199  
   200  func (s *session) Get(key string) interface{} {
   201  	return s.vars[key]
   202  }
   203  
   204  func (s *session) TopProvider() hiera.LookupKey {
   205  	if v, ok := s.Get(hieraTopProviderKey).(hiera.LookupKey); ok {
   206  		return v
   207  	}
   208  	panic(notInitialized())
   209  }
   210  
   211  func (s *session) TopProviderCache() *sync.Map {
   212  	if v, ok := s.Get(hieraTopProviderCacheKey).(*sync.Map); ok {
   213  		return v
   214  	}
   215  	panic(notInitialized())
   216  }
   217  
   218  func (s *session) SessionOptions() dgo.Map {
   219  	if v := s.Get(hieraSessionOptionsKey); v != nil {
   220  		if g, ok := v.(dgo.Map); ok {
   221  			return g
   222  		}
   223  	}
   224  	panic(notInitialized())
   225  }
   226  
   227  func notInitialized() error {
   228  	return errors.New(`session is not initialized`)
   229  }
   230  
   231  func (s *session) SharedCache() *sync.Map {
   232  	if v, ok := s.Get(hieraCacheKey).(*sync.Map); ok {
   233  		return v
   234  	}
   235  	panic(notInitialized())
   236  }
   237  
   238  func (s *session) newHieraLoader(p dgo.Loader) dgo.Loader {
   239  	nsCreator := func(l dgo.Loader, name string) dgo.Loader {
   240  		switch name {
   241  		case `plugin`:
   242  			return s.createPluginLoader(l)
   243  		case `function`:
   244  			return s.createFunctionLoader(l)
   245  		default:
   246  			return nil
   247  		}
   248  	}
   249  	var l dgo.Loader
   250  	if p == nil {
   251  		l = loader.New(nil, ``, nil, nil, nsCreator)
   252  	} else {
   253  		l = p.NewChild(nil, nsCreator)
   254  	}
   255  	return l
   256  }
   257  
   258  func (s *session) createFunctionLoader(l dgo.Loader) dgo.Loader {
   259  	m, ok := s.SessionOptions().Get(api.HieraFunctions).(dgo.Map)
   260  	if !ok {
   261  		m = vf.Map()
   262  	}
   263  	return loader.New(l, `function`, m, nil, nil)
   264  }
   265  
   266  func (s *session) createPluginLoader(p dgo.Loader) dgo.Loader {
   267  	var pluginFinder = func(l dgo.Loader, _ string) interface{} {
   268  		an := l.AbsoluteName()
   269  
   270  		// Strip everything up to '/plugin/'
   271  		ix := strings.Index(an, `/plugin/`)
   272  		if ix < 0 {
   273  			return nil
   274  		}
   275  		path := an[ix+7:]
   276  		// In Windows, the path might start with a slash followed by a drive letter. If it does, the slash
   277  		// must be removed.
   278  		if runtime.GOOS == "windows" && len(path) > 3 && path[0] == '/' && unicode.IsLetter(rune(path[1])) && path[2] == ':' && path[3] == '/' {
   279  			path = path[1:]
   280  		}
   281  
   282  		// Get the plugin registry for this session
   283  		var allPlugins *pluginRegistry
   284  		if pr := s.Get(hieraPluginRegistry); pr != nil {
   285  			allPlugins = pr.(*pluginRegistry)
   286  		} else {
   287  			return nil
   288  		}
   289  		return allPlugins.startPlugin(s.SessionOptions(), path)
   290  	}
   291  
   292  	var pluginNamespace func(l dgo.Loader, name string) dgo.Loader
   293  	pluginNamespace = func(l dgo.Loader, name string) dgo.Loader {
   294  		return loader.New(l, name, nil, pluginFinder, pluginNamespace)
   295  	}
   296  
   297  	return loader.New(p, `plugin`, nil, pluginFinder, pluginNamespace)
   298  }