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:
Leon Mika 2024-03-03 08:54:57 +11:00
parent ceb064a346
commit 12909c89ee
6 changed files with 9 additions and 233 deletions

View file

@ -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)) {

View file

@ -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)

View file

@ -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),
})
}

View file

@ -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)
})
} }

View file

@ -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{}

View file

@ -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),
} }