github.com/mcuadros/ascode@v1.3.1/starlark/runtime/runtime.go (about)

     1  package runtime
     2  
     3  import (
     4  	"fmt"
     5  	osfilepath "path/filepath"
     6  
     7  	"github.com/mcuadros/ascode/starlark/module/docker"
     8  	"github.com/mcuadros/ascode/starlark/module/filepath"
     9  	"github.com/mcuadros/ascode/starlark/module/os"
    10  	"github.com/mcuadros/ascode/starlark/module/url"
    11  	"github.com/mcuadros/ascode/starlark/types"
    12  	"github.com/mcuadros/ascode/terraform"
    13  	"github.com/qri-io/starlib/encoding/base64"
    14  	"github.com/qri-io/starlib/encoding/csv"
    15  	"github.com/qri-io/starlib/encoding/json"
    16  	"github.com/qri-io/starlib/encoding/yaml"
    17  	"github.com/qri-io/starlib/http"
    18  	"github.com/qri-io/starlib/math"
    19  	"github.com/qri-io/starlib/re"
    20  	"github.com/qri-io/starlib/time"
    21  	"go.starlark.net/repl"
    22  	"go.starlark.net/resolve"
    23  	"go.starlark.net/starlark"
    24  	"go.starlark.net/starlarkstruct"
    25  )
    26  
    27  func init() {
    28  	resolve.AllowRecursion = true
    29  	resolve.AllowFloat = true
    30  	resolve.AllowGlobalReassign = true
    31  	resolve.AllowLambda = true
    32  	resolve.AllowNestedDef = true
    33  	resolve.AllowSet = true
    34  }
    35  
    36  // LoadModuleFunc is a concurrency-safe and idempotent function that returns
    37  // the module when is called from the `load` funcion.
    38  type LoadModuleFunc func() (starlark.StringDict, error)
    39  
    40  // Runtime represents the AsCode runtime, it defines the available modules,
    41  // the predeclared globals and handles how the `load` function behaves.
    42  type Runtime struct {
    43  	Terraform   *types.Terraform
    44  	pm          *terraform.PluginManager
    45  	predeclared starlark.StringDict
    46  	modules     map[string]LoadModuleFunc
    47  	moduleCache map[string]*moduleCache
    48  
    49  	path string
    50  }
    51  
    52  // NewRuntime returns a new Runtime for the given terraform.PluginManager.
    53  func NewRuntime(pm *terraform.PluginManager) *Runtime {
    54  	tf := types.NewTerraform(pm)
    55  	predeclared := starlark.StringDict{}
    56  	predeclared["tf"] = tf
    57  	predeclared["provisioner"] = types.BuiltinProvisioner()
    58  	predeclared["backend"] = types.BuiltinBackend()
    59  	predeclared["validate"] = types.BuiltinValidate()
    60  	predeclared["hcl"] = types.BuiltinHCL()
    61  	predeclared["fn"] = types.BuiltinFunctionAttribute()
    62  	predeclared["ref"] = types.BuiltinRef()
    63  	predeclared["evaluate"] = types.BuiltinEvaluate(predeclared)
    64  	predeclared["struct"] = starlark.NewBuiltin("struct", starlarkstruct.Make)
    65  	predeclared["module"] = starlark.NewBuiltin("module", starlarkstruct.MakeModule)
    66  
    67  	return &Runtime{
    68  		Terraform:   tf,
    69  		pm:          pm,
    70  		moduleCache: make(map[string]*moduleCache),
    71  		modules: map[string]LoadModuleFunc{
    72  			filepath.ModuleName: filepath.LoadModule,
    73  			os.ModuleName:       os.LoadModule,
    74  			docker.ModuleName:   docker.LoadModule,
    75  
    76  			"encoding/json":   json.LoadModule,
    77  			"encoding/base64": base64.LoadModule,
    78  			"encoding/csv":    csv.LoadModule,
    79  			"encoding/yaml":   yaml.LoadModule,
    80  			"math":            math.LoadModule,
    81  			"re":              re.LoadModule,
    82  			"time":            time.LoadModule,
    83  			"http":            http.LoadModule,
    84  			"url":             url.LoadModule,
    85  		},
    86  		predeclared: predeclared,
    87  	}
    88  }
    89  
    90  // ExecFile parses, resolves, and executes a Starlark file.
    91  func (r *Runtime) ExecFile(filename string) (starlark.StringDict, error) {
    92  	fullpath, _ := osfilepath.Abs(filename)
    93  	r.path, _ = osfilepath.Split(fullpath)
    94  
    95  	thread := &starlark.Thread{Name: "thread", Load: r.load}
    96  	r.setLocals(thread)
    97  
    98  	return starlark.ExecFile(thread, filename, nil, r.predeclared)
    99  }
   100  
   101  // REPL executes a read, eval, print loop.
   102  func (r *Runtime) REPL() {
   103  	thread := &starlark.Thread{Name: "thread", Load: r.load}
   104  	r.setLocals(thread)
   105  
   106  	repl.REPL(thread, r.predeclared)
   107  }
   108  
   109  func (r *Runtime) setLocals(t *starlark.Thread) {
   110  	t.SetLocal("base_path", r.path)
   111  	t.SetLocal(types.PluginManagerLocal, r.pm)
   112  }
   113  
   114  func (r *Runtime) load(t *starlark.Thread, module string) (starlark.StringDict, error) {
   115  	if m, ok := r.modules[module]; ok {
   116  		return m()
   117  	}
   118  
   119  	filename := osfilepath.Join(r.path, module)
   120  	return r.loadFile(t, filename)
   121  }
   122  
   123  type moduleCache struct {
   124  	globals starlark.StringDict
   125  	err     error
   126  }
   127  
   128  func (r *Runtime) loadFile(thread *starlark.Thread, module string) (starlark.StringDict, error) {
   129  	e, ok := r.moduleCache[module]
   130  	if e == nil {
   131  		if ok {
   132  			// request for package whose loading is in progress
   133  			return nil, fmt.Errorf("cycle in load graph")
   134  		}
   135  
   136  		// Add a placeholder to indicate "load in progress".
   137  		r.moduleCache[module] = nil
   138  
   139  		thread := &starlark.Thread{Name: "exec " + module, Load: thread.Load}
   140  		globals, err := starlark.ExecFile(thread, module, nil, r.predeclared)
   141  
   142  		e = &moduleCache{globals, err}
   143  		r.moduleCache[module] = e
   144  	}
   145  
   146  	return e.globals, e.err
   147  }