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 }