Removed internal os module
Module "os" is no longer needed since Risor comes with an "os" and "exec" module out of the box now.
This commit is contained in:
parent
ceb064a346
commit
12909c89ee
|
@ -61,13 +61,6 @@ func (sc *ScriptController) Init() {
|
||||||
} else {
|
} else {
|
||||||
log.Printf("warn: script lookup paths are invalid: %v", err)
|
log.Printf("warn: script lookup paths are invalid: %v", err)
|
||||||
}
|
}
|
||||||
sc.scriptManager.SetDefaultOptions(scriptmanager.Options{
|
|
||||||
OSExecShell: "/bin/bash",
|
|
||||||
Permissions: scriptmanager.Permissions{
|
|
||||||
AllowShellCommands: true,
|
|
||||||
AllowEnv: true,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sc *ScriptController) SetMessageSender(sendMsg func(msg tea.Msg)) {
|
func (sc *ScriptController) SetMessageSender(sendMsg func(msg tea.Msg)) {
|
||||||
|
|
|
@ -52,7 +52,6 @@ func (m *extModule) command(ctx context.Context, args ...object.Object) object.O
|
||||||
}
|
}
|
||||||
|
|
||||||
newEnv := thisEnv
|
newEnv := thisEnv
|
||||||
newEnv.options = m.scriptPlugin.scriptService.options
|
|
||||||
ctx = ctxWithScriptEnv(ctx, newEnv)
|
ctx = ctxWithScriptEnv(ctx, newEnv)
|
||||||
|
|
||||||
res, err := callFn(ctx, fnRes, objArgs)
|
res, err := callFn(ctx, fnRes, objArgs)
|
||||||
|
@ -114,7 +113,6 @@ func (m *extModule) keyBinding(ctx context.Context, args ...object.Object) objec
|
||||||
}
|
}
|
||||||
|
|
||||||
newEnv := thisEnv
|
newEnv := thisEnv
|
||||||
newEnv.options = m.scriptPlugin.scriptService.options
|
|
||||||
ctx = ctxWithScriptEnv(ctx, newEnv)
|
ctx = ctxWithScriptEnv(ctx, newEnv)
|
||||||
|
|
||||||
res, err := callFn(ctx, fnRes, objArgs)
|
res, err := callFn(ctx, fnRes, objArgs)
|
||||||
|
|
|
@ -1,64 +0,0 @@
|
||||||
package scriptmanager
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"github.com/risor-io/risor/object"
|
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
)
|
|
||||||
|
|
||||||
type osModule struct {
|
|
||||||
}
|
|
||||||
|
|
||||||
func (om *osModule) exec(ctx context.Context, args ...object.Object) object.Object {
|
|
||||||
if err := require("os.exec", 1, args); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
cmdExec, objErr := object.AsString(args[0])
|
|
||||||
if objErr != nil {
|
|
||||||
return objErr
|
|
||||||
}
|
|
||||||
|
|
||||||
opts := scriptEnvFromCtx(ctx).options
|
|
||||||
if !opts.Permissions.AllowShellCommands {
|
|
||||||
return object.Errorf("permission error: no permission to shell out")
|
|
||||||
}
|
|
||||||
|
|
||||||
cmd := exec.Command(opts.OSExecShell, "-c", cmdExec)
|
|
||||||
out, err := cmd.Output()
|
|
||||||
if err != nil {
|
|
||||||
return object.NewError(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return object.NewString(string(out))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (om *osModule) env(ctx context.Context, args ...object.Object) object.Object {
|
|
||||||
if err := require("os.env", 1, args); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
cmdEnvName, objErr := object.AsString(args[0])
|
|
||||||
if objErr != nil {
|
|
||||||
return objErr
|
|
||||||
}
|
|
||||||
|
|
||||||
opts := scriptEnvFromCtx(ctx).options
|
|
||||||
if !opts.Permissions.AllowEnv {
|
|
||||||
return object.Nil
|
|
||||||
}
|
|
||||||
|
|
||||||
envVal, hasVal := os.LookupEnv(cmdEnvName)
|
|
||||||
if !hasVal {
|
|
||||||
return object.Nil
|
|
||||||
}
|
|
||||||
return object.NewString(envVal)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (om *osModule) register() *object.Module {
|
|
||||||
return object.NewBuiltinsModule("os", map[string]object.Object{
|
|
||||||
"exec": object.NewBuiltin("exec", om.exec),
|
|
||||||
"env": object.NewBuiltin("env", om.env),
|
|
||||||
})
|
|
||||||
}
|
|
|
@ -15,49 +15,16 @@ func TestOSModule_Env(t *testing.T) {
|
||||||
t.Setenv("EMPTY_VALUE", "")
|
t.Setenv("EMPTY_VALUE", "")
|
||||||
|
|
||||||
testFS := testScriptFile(t, "test.tm", `
|
testFS := testScriptFile(t, "test.tm", `
|
||||||
assert(os.env("FULL_VALUE") == "this is a value")
|
assert(os.getenv("FULL_VALUE") == "this is a value")
|
||||||
assert(os.env("EMPTY_VALUE") == "")
|
assert(os.getenv("EMPTY_VALUE") == "")
|
||||||
assert(os.env("MISSING_VALUE") == nil)
|
assert(os.getenv("MISSING_VALUE") == "")
|
||||||
|
|
||||||
assert(bool(os.env("FULL_VALUE")) == true)
|
assert(bool(os.getenv("FULL_VALUE")) == true)
|
||||||
assert(bool(os.env("EMPTY_VALUE")) == false)
|
assert(bool(os.getenv("EMPTY_VALUE")) == false)
|
||||||
assert(bool(os.env("MISSING_VALUE")) == false)
|
assert(bool(os.getenv("MISSING_VALUE")) == false)
|
||||||
`)
|
`)
|
||||||
|
|
||||||
srv := scriptmanager.New(scriptmanager.WithFS(testFS))
|
srv := scriptmanager.New(scriptmanager.WithFS(testFS))
|
||||||
srv.SetDefaultOptions(scriptmanager.Options{
|
|
||||||
OSExecShell: "/bin/bash",
|
|
||||||
Permissions: scriptmanager.Permissions{
|
|
||||||
AllowEnv: true,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
ctx := context.Background()
|
|
||||||
err := <-srv.RunAdHocScript(ctx, "test.tm")
|
|
||||||
assert.NoError(t, err)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("should return nil when no access to environment variables", func(t *testing.T) {
|
|
||||||
t.Setenv("FULL_VALUE", "this is a value")
|
|
||||||
t.Setenv("EMPTY_VALUE", "")
|
|
||||||
|
|
||||||
testFS := testScriptFile(t, "test.tm", `
|
|
||||||
assert(os.env("FULL_VALUE") == nil)
|
|
||||||
assert(os.env("EMPTY_VALUE") == nil)
|
|
||||||
assert(os.env("MISSING_VALUE") == nil)
|
|
||||||
|
|
||||||
assert(bool(os.env("FULL_VALUE")) == false)
|
|
||||||
assert(bool(os.env("EMPTY_VALUE")) == false)
|
|
||||||
assert(bool(os.env("MISSING_VALUE")) == false)
|
|
||||||
`)
|
|
||||||
|
|
||||||
srv := scriptmanager.New(scriptmanager.WithFS(testFS))
|
|
||||||
srv.SetDefaultOptions(scriptmanager.Options{
|
|
||||||
OSExecShell: "/bin/bash",
|
|
||||||
Permissions: scriptmanager.Permissions{
|
|
||||||
AllowEnv: false,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
err := <-srv.RunAdHocScript(ctx, "test.tm")
|
err := <-srv.RunAdHocScript(ctx, "test.tm")
|
||||||
|
@ -71,17 +38,11 @@ func TestOSModule_Exec(t *testing.T) {
|
||||||
mockedUIService.EXPECT().PrintMessage(mock.Anything, "hello world\n")
|
mockedUIService.EXPECT().PrintMessage(mock.Anything, "hello world\n")
|
||||||
|
|
||||||
testFS := testScriptFile(t, "test.tm", `
|
testFS := testScriptFile(t, "test.tm", `
|
||||||
res := os.exec('echo "hello world"')
|
res := exec('echo', ["hello world"]).stdout
|
||||||
ui.print(res)
|
ui.print(res)
|
||||||
`)
|
`)
|
||||||
|
|
||||||
srv := scriptmanager.New(scriptmanager.WithFS(testFS))
|
srv := scriptmanager.New(scriptmanager.WithFS(testFS))
|
||||||
srv.SetDefaultOptions(scriptmanager.Options{
|
|
||||||
OSExecShell: "/bin/bash",
|
|
||||||
Permissions: scriptmanager.Permissions{
|
|
||||||
AllowShellCommands: true,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
srv.SetIFaces(scriptmanager.Ifaces{
|
srv.SetIFaces(scriptmanager.Ifaces{
|
||||||
UI: mockedUIService,
|
UI: mockedUIService,
|
||||||
})
|
})
|
||||||
|
@ -92,72 +53,4 @@ func TestOSModule_Exec(t *testing.T) {
|
||||||
|
|
||||||
mockedUIService.AssertExpectations(t)
|
mockedUIService.AssertExpectations(t)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("should refuse to execute command if do not have permissions", func(t *testing.T) {
|
|
||||||
mockedUIService := mocks.NewUIService(t)
|
|
||||||
mockedUIService.EXPECT().PrintMessage(mock.Anything, "failed")
|
|
||||||
|
|
||||||
testFS := testScriptFile(t, "test.tm", `
|
|
||||||
res := try(func() { return os.exec('echo "hello world"') }, "failed")
|
|
||||||
ui.print(res)
|
|
||||||
`)
|
|
||||||
|
|
||||||
srv := scriptmanager.New(scriptmanager.WithFS(testFS))
|
|
||||||
srv.SetDefaultOptions(scriptmanager.Options{
|
|
||||||
OSExecShell: "/bin/bash",
|
|
||||||
Permissions: scriptmanager.Permissions{
|
|
||||||
AllowShellCommands: false,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
srv.SetIFaces(scriptmanager.Ifaces{
|
|
||||||
UI: mockedUIService,
|
|
||||||
})
|
|
||||||
|
|
||||||
ctx := context.Background()
|
|
||||||
err := <-srv.RunAdHocScript(ctx, "test.tm")
|
|
||||||
assert.NoError(t, err)
|
|
||||||
|
|
||||||
mockedUIService.AssertExpectations(t)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("should be able to change permissions which will affect plugins", func(t *testing.T) {
|
|
||||||
mockedUIService := mocks.NewUIService(t)
|
|
||||||
mockedUIService.EXPECT().PrintMessage(mock.Anything, "Loaded the plugin\n")
|
|
||||||
|
|
||||||
testFS := testScriptFile(t, "test.tm", `
|
|
||||||
ext.command("mycommand", func() {
|
|
||||||
ui.print(os.exec('echo "this cannot run"'))
|
|
||||||
})
|
|
||||||
|
|
||||||
ui.print(os.exec('echo "Loaded the plugin"'))
|
|
||||||
`)
|
|
||||||
|
|
||||||
srv := scriptmanager.New(scriptmanager.WithFS(testFS))
|
|
||||||
srv.SetDefaultOptions(scriptmanager.Options{
|
|
||||||
OSExecShell: "/bin/bash",
|
|
||||||
Permissions: scriptmanager.Permissions{
|
|
||||||
AllowShellCommands: true,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
srv.SetIFaces(scriptmanager.Ifaces{
|
|
||||||
UI: mockedUIService,
|
|
||||||
})
|
|
||||||
|
|
||||||
ctx := context.Background()
|
|
||||||
_, err := srv.LoadScript(ctx, "test.tm")
|
|
||||||
assert.NoError(t, err)
|
|
||||||
|
|
||||||
srv.SetDefaultOptions(scriptmanager.Options{
|
|
||||||
OSExecShell: "/bin/bash",
|
|
||||||
Permissions: scriptmanager.Permissions{
|
|
||||||
AllowShellCommands: false,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
errChan := make(chan error)
|
|
||||||
assert.NoError(t, srv.LookupCommand("mycommand").Invoke(ctx, []string{}, errChan))
|
|
||||||
assert.Error(t, waitForErr(t, errChan))
|
|
||||||
|
|
||||||
mockedUIService.AssertExpectations(t)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,44 +2,12 @@ package scriptmanager
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/risor-io/risor/limits"
|
"github.com/risor-io/risor/limits"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Options struct {
|
|
||||||
// OSExecShell is the shell to use for calls to 'os.exec'. If not defined,
|
|
||||||
// it will use the value of the SHELL environment variable, otherwise it will
|
|
||||||
// default to '/bin/bash'
|
|
||||||
OSExecShell string
|
|
||||||
|
|
||||||
// Permissions are the permissions the script can execute in
|
|
||||||
Permissions Permissions
|
|
||||||
}
|
|
||||||
|
|
||||||
func (opts Options) configuredShell() string {
|
|
||||||
if opts.OSExecShell != "" {
|
|
||||||
return opts.OSExecShell
|
|
||||||
}
|
|
||||||
if shell, hasShell := os.LookupEnv("SHELL"); hasShell {
|
|
||||||
return shell
|
|
||||||
}
|
|
||||||
return "/bin/bash"
|
|
||||||
}
|
|
||||||
|
|
||||||
// Permissions control the set of permissions of a script
|
|
||||||
type Permissions struct {
|
|
||||||
// AllowShellCommands determines whether or not a script can execute shell commands.
|
|
||||||
AllowShellCommands bool
|
|
||||||
|
|
||||||
// AllowEnv determines whether or not a script can access environment variables
|
|
||||||
AllowEnv bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// scriptEnv is the runtime environment for a particular script execution
|
// scriptEnv is the runtime environment for a particular script execution
|
||||||
type scriptEnv struct {
|
type scriptEnv struct {
|
||||||
filename string
|
filename string
|
||||||
options Options
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type scriptEnvKeyType struct{}
|
type scriptEnvKeyType struct{}
|
||||||
|
|
|
@ -17,7 +17,6 @@ import (
|
||||||
type Service struct {
|
type Service struct {
|
||||||
lookupPaths []fs.FS
|
lookupPaths []fs.FS
|
||||||
ifaces Ifaces
|
ifaces Ifaces
|
||||||
options Options
|
|
||||||
sched *scriptScheduler
|
sched *scriptScheduler
|
||||||
plugins []*ScriptPlugin
|
plugins []*ScriptPlugin
|
||||||
}
|
}
|
||||||
|
@ -37,10 +36,6 @@ func (s *Service) SetLookupPaths(fs []fs.FS) {
|
||||||
s.lookupPaths = fs
|
s.lookupPaths = fs
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) SetDefaultOptions(options Options) {
|
|
||||||
s.options = options
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Service) SetIFaces(ifaces Ifaces) {
|
func (s *Service) SetIFaces(ifaces Ifaces) {
|
||||||
s.ifaces = ifaces
|
s.ifaces = ifaces
|
||||||
}
|
}
|
||||||
|
@ -94,13 +89,10 @@ func (s *Service) startAdHocScript(ctx context.Context, filename string, errChan
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx = ctxWithScriptEnv(ctx, scriptEnv{filename: filepath.Base(filename), options: s.options})
|
ctx = ctxWithScriptEnv(ctx, scriptEnv{filename: filepath.Base(filename)})
|
||||||
|
|
||||||
if _, err := risor.Eval(ctx, code,
|
if _, err := risor.Eval(ctx, code,
|
||||||
risor.WithGlobals(s.builtins()),
|
risor.WithGlobals(s.builtins()),
|
||||||
// risor.WithDefaultBuiltins(),
|
|
||||||
// risor.WithDefaultModules(),
|
|
||||||
// risor.WithBuiltins(s.builtins()),
|
|
||||||
); err != nil {
|
); err != nil {
|
||||||
errChan <- errors.Wrapf(err, "script %v", filename)
|
errChan <- errors.Wrapf(err, "script %v", filename)
|
||||||
return
|
return
|
||||||
|
@ -126,12 +118,9 @@ func (s *Service) loadScript(ctx context.Context, filename string, resChan chan
|
||||||
scriptService: s,
|
scriptService: s,
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx = ctxWithScriptEnv(ctx, scriptEnv{filename: filepath.Base(filename), options: s.options})
|
ctx = ctxWithScriptEnv(ctx, scriptEnv{filename: filepath.Base(filename)})
|
||||||
|
|
||||||
if _, err := risor.Eval(ctx, code,
|
if _, err := risor.Eval(ctx, code,
|
||||||
// risor.WithDefaultBuiltins(),
|
|
||||||
// risor.WithDefaultModules(),
|
|
||||||
// risor.WithBuiltins(s.builtins()),
|
|
||||||
risor.WithGlobals(s.builtins()),
|
risor.WithGlobals(s.builtins()),
|
||||||
risor.WithGlobals(map[string]any{
|
risor.WithGlobals(map[string]any{
|
||||||
"ext": (&extModule{scriptPlugin: newPlugin}).register(),
|
"ext": (&extModule{scriptPlugin: newPlugin}).register(),
|
||||||
|
@ -251,7 +240,6 @@ func (s *Service) builtins() map[string]any {
|
||||||
return map[string]any{
|
return map[string]any{
|
||||||
"ui": (&uiModule{uiService: s.ifaces.UI}).register(),
|
"ui": (&uiModule{uiService: s.ifaces.UI}).register(),
|
||||||
"session": (&sessionModule{sessionService: s.ifaces.Session}).register(),
|
"session": (&sessionModule{sessionService: s.ifaces.Session}).register(),
|
||||||
"os": (&osModule{}).register(),
|
|
||||||
"print": object.NewBuiltin("print", printBuiltin),
|
"print": object.NewBuiltin("print", printBuiltin),
|
||||||
"printf": object.NewBuiltin("printf", printfBuiltin),
|
"printf": object.NewBuiltin("printf", printfBuiltin),
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue