Skip to content

Commit 70719cd

Browse files
authored
Add experimental support for jupyter desktop (#1345)
* Implement jlab desktop * Install jupyter server on remote side when using jupyter desktop
1 parent 98c0476 commit 70719cd

File tree

5 files changed

+102
-3
lines changed

5 files changed

+102
-3
lines changed

cmd/agent/container/setup.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -434,6 +434,8 @@ func (cmd *SetupContainerCmd) installIDE(setupInfo *config.Result, ide *provider
434434
return fleet.NewFleetServer(config.GetRemoteUser(setupInfo), ide.Options, log).Install(setupInfo.SubstitutionContext.ContainerWorkspaceFolder)
435435
case string(config2.IDEJupyterNotebook):
436436
return jupyter.NewJupyterNotebookServer(setupInfo.SubstitutionContext.ContainerWorkspaceFolder, config.GetRemoteUser(setupInfo), ide.Options, log).Install()
437+
case string(config2.IDEJupyterDesktop):
438+
return jupyter.NewJupyterNotebookServer(setupInfo.SubstitutionContext.ContainerWorkspaceFolder, config.GetRemoteUser(setupInfo), ide.Options, log).Install()
437439
}
438440

439441
return nil

cmd/up.go

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -354,6 +354,18 @@ func (cmd *UpCmd) Run(
354354
cmd.GitToken,
355355
log,
356356
)
357+
case string(config.IDEJupyterDesktop):
358+
return startJupyterDesktop(
359+
cmd.GPGAgentForwarding,
360+
ctx,
361+
devPodConfig,
362+
client,
363+
user,
364+
ideConfig.Options,
365+
cmd.GitUsername,
366+
cmd.GitToken,
367+
log,
368+
)
357369
}
358370
}
359371

@@ -630,6 +642,61 @@ func startJupyterNotebookInBrowser(
630642
)
631643
}
632644

645+
func startJupyterDesktop(
646+
forwardGpg bool,
647+
ctx context.Context,
648+
devPodConfig *config.Config,
649+
client client2.BaseWorkspaceClient,
650+
user string,
651+
ideOptions map[string]config.OptionValue,
652+
gitUsername, gitToken string,
653+
logger log.Logger,
654+
) error {
655+
if forwardGpg {
656+
err := performGpgForwarding(client, logger)
657+
if err != nil {
658+
return err
659+
}
660+
}
661+
662+
// determine port
663+
jupyterAddress, jupyterPort, err := parseAddressAndPort(
664+
jupyter.Options.GetValue(ideOptions, jupyter.BindAddressOption),
665+
jupyter.DefaultServerPort,
666+
)
667+
if err != nil {
668+
return err
669+
}
670+
671+
// wait until reachable then open browser
672+
targetURL := fmt.Sprintf("http://localhost:%d/lab", jupyterPort)
673+
if jupyter.Options.GetValue(ideOptions, jupyter.OpenOption) == "true" {
674+
go func() {
675+
err = open2.JLabDesktop(ctx, targetURL, logger)
676+
if err != nil {
677+
logger.Errorf("error opening jupyter desktop: %v", err)
678+
}
679+
logger.Infof("Successfully started jupyter desktop")
680+
}()
681+
}
682+
683+
// start in browser
684+
logger.Infof("Starting jupyter desktop using server %s", targetURL)
685+
extraPorts := []string{fmt.Sprintf("%s:%d", jupyterAddress, jupyter.DefaultServerPort)}
686+
return startBrowserTunnel(
687+
ctx,
688+
devPodConfig,
689+
client,
690+
user,
691+
targetURL,
692+
false,
693+
extraPorts,
694+
gitUsername,
695+
gitToken,
696+
logger,
697+
)
698+
}
699+
633700
func startFleet(ctx context.Context, client client2.BaseWorkspaceClient, logger log.Logger) error {
634701
// create ssh command
635702
stdout := &bytes.Buffer{}

pkg/config/ide.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ const (
1818
IDEWebStorm IDE = "webstorm"
1919
IDEFleet IDE = "fleet"
2020
IDEJupyterNotebook IDE = "jupyternotebook"
21+
IDEJupyterDesktop IDE = "jupyterdesktop"
2122
IDECursor IDE = "cursor"
2223
IDEPositron IDE = "positron"
2324
)

pkg/ide/ideparse/parse.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,14 @@ var AllowedIDEs = []AllowedIDE{
122122
IconDark: "https://devpod.sh/assets/jupyter_dark.svg",
123123
Experimental: true,
124124
},
125+
{
126+
Name: config.IDEJupyterDesktop,
127+
DisplayName: "Jupyter Desktop",
128+
Options: jupyter.Options,
129+
Icon: "https://devpod.sh/assets/jupyter.svg",
130+
IconDark: "https://devpod.sh/assets/jupyter_dark.svg",
131+
Experimental: true,
132+
},
125133
{
126134
Name: config.IDEVSCodeInsiders,
127135
DisplayName: "VSCode Insiders",

pkg/open/open.go

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,28 +4,49 @@ import (
44
"context"
55
"fmt"
66
"net/http"
7+
"os/exec"
78
"time"
89

910
devpodhttp "github.com/loft-sh/devpod/pkg/http"
1011
"github.com/loft-sh/log"
1112
"github.com/skratchdot/open-golang/open"
1213
)
1314

15+
// Open opens the given url in the default application, retrying every second until the context is done
1416
func Open(ctx context.Context, url string, log log.Logger) error {
1517
for {
1618
select {
1719
case <-ctx.Done():
1820
return nil
1921
case <-time.After(time.Second):
20-
err := tryOpen(ctx, url, log)
22+
err := tryOpen(ctx, url, open.Start, log)
2123
if err == nil {
2224
return nil
2325
}
2426
}
2527
}
2628
}
2729

28-
func tryOpen(ctx context.Context, url string, log log.Logger) error {
30+
// JLabDesktop opens the given url in the JLab desktop application, retrying every second until the context is done
31+
func JLabDesktop(ctx context.Context, url string, log log.Logger) error {
32+
for {
33+
select {
34+
case <-ctx.Done():
35+
return nil
36+
case <-time.After(time.Second):
37+
err := tryOpen(ctx, url, jlabOpen, log)
38+
if err == nil {
39+
return nil
40+
}
41+
}
42+
}
43+
}
44+
45+
func jlabOpen(url string) error {
46+
return exec.Command("jlab", url).Run()
47+
}
48+
49+
func tryOpen(ctx context.Context, url string, fn func(string) error, log log.Logger) error {
2950
timeoutCtx, cancel := context.WithTimeout(ctx, time.Second)
3051
defer cancel()
3152

@@ -48,7 +69,7 @@ func tryOpen(ctx context.Context, url string, log log.Logger) error {
4869
return nil
4970
case <-time.After(time.Second):
5071
}
51-
_ = open.Start(url)
72+
_ = fn(url)
5273
log.Donef("Successfully opened %s", url)
5374
return nil
5475
}

0 commit comments

Comments
 (0)