Skip to content

Commit 31dd410

Browse files
authored
Implement experimental support for marimo (#1343)
* :wqImplement experimental support for marimo * Fix log messages * Fix icon logo * Replace token with setting * Replace token with setting
1 parent 70719cd commit 31dd410

File tree

5 files changed

+191
-2
lines changed

5 files changed

+191
-2
lines changed

cmd/agent/container/setup.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import (
3030
"github.com/loft-sh/devpod/pkg/ide/fleet"
3131
"github.com/loft-sh/devpod/pkg/ide/jetbrains"
3232
"github.com/loft-sh/devpod/pkg/ide/jupyter"
33+
"github.com/loft-sh/devpod/pkg/ide/marimo"
3334
"github.com/loft-sh/devpod/pkg/ide/openvscode"
3435
"github.com/loft-sh/devpod/pkg/ide/vscode"
3536
provider2 "github.com/loft-sh/devpod/pkg/provider"
@@ -436,6 +437,8 @@ func (cmd *SetupContainerCmd) installIDE(setupInfo *config.Result, ide *provider
436437
return jupyter.NewJupyterNotebookServer(setupInfo.SubstitutionContext.ContainerWorkspaceFolder, config.GetRemoteUser(setupInfo), ide.Options, log).Install()
437438
case string(config2.IDEJupyterDesktop):
438439
return jupyter.NewJupyterNotebookServer(setupInfo.SubstitutionContext.ContainerWorkspaceFolder, config.GetRemoteUser(setupInfo), ide.Options, log).Install()
440+
case string(config2.IDEMarimo):
441+
return marimo.NewServer(setupInfo.SubstitutionContext.ContainerWorkspaceFolder, config.GetRemoteUser(setupInfo), ide.Options, log).Install()
439442
}
440443

441444
return nil

cmd/up.go

Lines changed: 71 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import (
2727
"github.com/loft-sh/devpod/pkg/ide/fleet"
2828
"github.com/loft-sh/devpod/pkg/ide/jetbrains"
2929
"github.com/loft-sh/devpod/pkg/ide/jupyter"
30+
"github.com/loft-sh/devpod/pkg/ide/marimo"
3031
"github.com/loft-sh/devpod/pkg/ide/openvscode"
3132
"github.com/loft-sh/devpod/pkg/ide/vscode"
3233
"github.com/loft-sh/devpod/pkg/loft"
@@ -364,8 +365,18 @@ func (cmd *UpCmd) Run(
364365
ideConfig.Options,
365366
cmd.GitUsername,
366367
cmd.GitToken,
367-
log,
368-
)
368+
log)
369+
case string(config.IDEMarimo):
370+
return startMarimoInBrowser(
371+
cmd.GPGAgentForwarding,
372+
ctx,
373+
devPodConfig,
374+
client,
375+
user,
376+
ideConfig.Options,
377+
cmd.GitUsername,
378+
cmd.GitToken,
379+
log)
369380
}
370381
}
371382

@@ -584,6 +595,64 @@ func (cmd *UpCmd) devPodUpMachine(
584595
)
585596
}
586597

598+
func startMarimoInBrowser(
599+
forwardGpg bool,
600+
ctx context.Context,
601+
devPodConfig *config.Config,
602+
client client2.BaseWorkspaceClient,
603+
user string,
604+
ideOptions map[string]config.OptionValue,
605+
gitUsername, gitToken string,
606+
logger log.Logger,
607+
) error {
608+
if forwardGpg {
609+
err := performGpgForwarding(client, logger)
610+
if err != nil {
611+
return err
612+
}
613+
}
614+
615+
// determine port
616+
address, port, err := parseAddressAndPort(
617+
marimo.Options.GetValue(ideOptions, marimo.BindAddressOption),
618+
marimo.DefaultServerPort,
619+
)
620+
if err != nil {
621+
return err
622+
}
623+
624+
// wait until reachable then open browser
625+
targetURL := fmt.Sprintf("http://localhost:%d?access_token=%s", port, marimo.Options.GetValue(ideOptions, marimo.AccessToken))
626+
if marimo.Options.GetValue(ideOptions, marimo.OpenOption) == "true" {
627+
go func() {
628+
err = open2.Open(ctx, targetURL, logger)
629+
if err != nil {
630+
logger.Errorf("error opening marimo: %v", err)
631+
}
632+
633+
logger.Infof(
634+
"Successfully started marimo in browser mode. Please keep this terminal open as long as you use Marimo",
635+
)
636+
}()
637+
}
638+
639+
// start in browser
640+
logger.Infof("Starting marimo in browser mode at %s", targetURL)
641+
extraPorts := []string{fmt.Sprintf("%s:%d", address, marimo.DefaultServerPort)}
642+
return startBrowserTunnel(
643+
ctx,
644+
devPodConfig,
645+
client,
646+
user,
647+
targetURL,
648+
false,
649+
extraPorts,
650+
gitUsername,
651+
gitToken,
652+
logger,
653+
)
654+
}
655+
587656
func startJupyterNotebookInBrowser(
588657
forwardGpg bool,
589658
ctx context.Context,

pkg/config/ide.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,4 +21,5 @@ const (
2121
IDEJupyterDesktop IDE = "jupyterdesktop"
2222
IDECursor IDE = "cursor"
2323
IDEPositron IDE = "positron"
24+
IDEMarimo IDE = "marimo"
2425
)

pkg/ide/ideparse/parse.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,13 @@ var AllowedIDEs = []AllowedIDE{
151151
Icon: "https://devpod.sh/assets/positron.svg",
152152
Experimental: true,
153153
},
154+
{
155+
Name: config.IDEMarimo,
156+
DisplayName: "Marimo",
157+
Options: vscode.Options,
158+
Icon: "https://devpod.sh/assets/marimo.svg",
159+
Experimental: true,
160+
},
154161
}
155162

156163
func RefreshIDEOptions(devPodConfig *config.Config, workspace *provider.Workspace, ide string, options []string) (*provider.Workspace, error) {

pkg/ide/marimo/marimo.go

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
package marimo
2+
3+
import (
4+
"fmt"
5+
"os/exec"
6+
"strconv"
7+
8+
"github.com/loft-sh/log"
9+
10+
"github.com/loft-sh/devpod/pkg/command"
11+
"github.com/loft-sh/devpod/pkg/config"
12+
"github.com/loft-sh/devpod/pkg/ide"
13+
"github.com/loft-sh/devpod/pkg/single"
14+
)
15+
16+
const DefaultServerPort = 10710
17+
const (
18+
OpenOption = "OPEN"
19+
AccessToken = "ACCESS_TOKEN"
20+
BindAddressOption = "BIND_ADDRESS"
21+
)
22+
23+
var Options = ide.Options{
24+
BindAddressOption: {
25+
Name: BindAddressOption,
26+
Description: "The address to bind the server to locally. E.g. 0.0.0.0:12345",
27+
Default: "",
28+
},
29+
AccessToken: {
30+
Name: AccessToken,
31+
Description: "Access token to authenticate with the server",
32+
Default: "NhLpVl4re5PFd3QRFxvQ",
33+
},
34+
OpenOption: {
35+
Name: OpenOption,
36+
Description: "If DevPod should automatically open the browser",
37+
Default: "true",
38+
Enum: []string{
39+
"true",
40+
"false",
41+
},
42+
},
43+
}
44+
45+
type Server struct {
46+
opts map[string]config.OptionValue
47+
userName string
48+
workspaceFolder string
49+
log log.Logger
50+
}
51+
52+
func NewServer(workspaceFolder, userName string, opts map[string]config.OptionValue, log log.Logger) *Server {
53+
return &Server{
54+
opts: opts,
55+
workspaceFolder: workspaceFolder,
56+
userName: userName,
57+
log: log,
58+
}
59+
}
60+
61+
func (s *Server) Install() error {
62+
if command.ExistsForUser("marimo", s.userName) {
63+
return nil
64+
}
65+
66+
// check if pip3 exists
67+
baseCommand := ""
68+
if command.ExistsForUser("pip3", s.userName) {
69+
baseCommand = "pip3"
70+
} else if command.ExistsForUser("pip", s.userName) {
71+
baseCommand = "pip"
72+
} else {
73+
return fmt.Errorf("seems like neither pip3 nor pip exists, please make sure to install python correctly")
74+
}
75+
76+
// install notebook command
77+
runCommand := fmt.Sprintf("%s install marimo", baseCommand)
78+
args := []string{}
79+
if s.userName != "" {
80+
args = append(args, "su", s.userName, "-c", runCommand)
81+
} else {
82+
args = append(args, "sh", "-c", runCommand)
83+
}
84+
85+
// install
86+
s.log.Infof("Installing marimo...")
87+
out, err := exec.Command(args[0], args[1:]...).CombinedOutput()
88+
if err != nil {
89+
return fmt.Errorf("error installing marimo: %w", command.WrapCommandError(out, err))
90+
}
91+
return s.start()
92+
}
93+
94+
func (s *Server) start() error {
95+
return single.Single("marimo.pid", func() (*exec.Cmd, error) {
96+
s.log.Infof("Starting marimo in background...")
97+
token := Options.GetValue(s.opts, AccessToken)
98+
runCommand := fmt.Sprintf("marimo edit --headless --host 0.0.0.0 --port %s --token-password %s", strconv.Itoa(DefaultServerPort), token)
99+
args := []string{}
100+
if s.userName != "" {
101+
args = append(args, "su", s.userName, "-l", "-c", runCommand)
102+
} else {
103+
args = append(args, "sh", "-l", "-c", runCommand)
104+
}
105+
cmd := exec.Command(args[0], args[1:]...)
106+
cmd.Dir = s.workspaceFolder
107+
return cmd, nil
108+
})
109+
}

0 commit comments

Comments
 (0)