diff --git a/components/ide/jetbrains/backend-plugin/debug/go.mod b/components/ide/jetbrains/backend-plugin/debug/go.mod new file mode 100644 index 00000000000000..3965805427c227 --- /dev/null +++ b/components/ide/jetbrains/backend-plugin/debug/go.mod @@ -0,0 +1,3 @@ +module github.com/gitpod-io/gitpod/jetbrains/backend/plugin/debug + +go 1.18 diff --git a/components/ide/jetbrains/backend-plugin/debug/main.go b/components/ide/jetbrains/backend-plugin/debug/main.go new file mode 100644 index 00000000000000..dd8aaa17ae9f11 --- /dev/null +++ b/components/ide/jetbrains/backend-plugin/debug/main.go @@ -0,0 +1,31 @@ +// Copyright (c) 2022 Gitpod GmbH. All rights reserved. +// Licensed under the GNU Affero General Public License (AGPL). +// See License-AGPL.txt in the project root for license information. + +package main + +import ( + "log" + "net/http" + "os" +) + +func main() { + body, err := os.Open("/workspace/gitpod/components/ide/jetbrains/backend-plugin/build/distributions/gitpod-remote-0.0.1.zip") + if err != nil { + log.Fatal(err) + } + defer body.Close() + + req, err := http.NewRequest("POST", "24000-workspaceUrl/debug/upload", body) + if err != nil { + log.Fatal(err) + } + req.Header.Set("x-gitpod-owner-token", "lalala") + + resp, err := http.DefaultClient.Do(req) + if err != nil { + log.Fatal(err) + } + log.Print(resp.StatusCode, resp.Status) +} diff --git a/components/ide/jetbrains/image/startup.sh b/components/ide/jetbrains/image/startup.sh index 7323bdfe85d9f1..c006085e5a301e 100755 --- a/components/ide/jetbrains/image/startup.sh +++ b/components/ide/jetbrains/image/startup.sh @@ -13,9 +13,4 @@ trap "jobs -p | xargs -r kill" SIGINT SIGTERM EXIT # by default remote dev already set -Xmx2048m, see /ide-desktop/backend/plugins/remote-dev-server/bin/launcher.sh unset JAVA_TOOL_OPTIONS -# enable remote debuggign if debug mode is enabled -if [ "${SUPERVISOR_DEBUG_ENABLE+}" = "true" ]; then - export JAVA_TOOL_OPTIONS "-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:0" -fi - exec /ide-desktop/status "$@" diff --git a/components/ide/jetbrains/image/status/main.go b/components/ide/jetbrains/image/status/main.go index c3e6eeac532c7b..68e69acc49dc03 100644 --- a/components/ide/jetbrains/image/status/main.go +++ b/components/ide/jetbrains/image/status/main.go @@ -6,6 +6,7 @@ package main import ( "bytes" + "compress/zlib" "context" "encoding/json" "errors" @@ -75,27 +76,71 @@ func main() { return } - repoRoot := wsInfo.GetCheckoutLocation() - gitpodConfig, err := parseGitpodConfig(repoRoot) - if err != nil { - log.WithError(err).Error("failed to parse .gitpod.yml") + // JB_BACKEND_DEBUG provides endpoint to upload plugin and restart + debug := os.Getenv("JB_BACKEND_DEBUG") != "" || os.Getenv("JB_BACKEND_DEBUG_SUSPEND") != "" + // JB_BACKEND_DEBUG_SUSPEND blocks start till a plugin is uploaded + debugSuspend := os.Getenv("JB_BACKEND_DEBUG_SUSPEND") != "" + debugPort := os.Getenv("JB_BACKEND_DEBUG_PORT") + if debugPort == "" { + debugPort = "0" } - version_2022_1, _ := version.NewVersion("2022.1") - if version_2022_1.LessThanOrEqual(backendVersion) { - err = installPlugins(repoRoot, gitpodConfig, alias) - installPluginsCost := time.Now().Local().Sub(startTime).Milliseconds() - if err != nil { - log.WithError(err).WithField("cost", installPluginsCost).Error("installing repo plugins: done") - } else { - log.WithField("cost", installPluginsCost).Info("installing repo plugins: done") - } + debugSuspendCh := make(chan struct{}) + + env := os.Environ() + if debugSuspend { + env = append(env, "JAVA_TOOL_OPTIONS=-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=*:"+debugPort) + } else if debug { + env = append(env, "JAVA_TOOL_OPTIONS=-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:"+debugPort) } - err = configureVMOptions(gitpodConfig, alias) - if err != nil { - log.WithError(err).Error("failed to configure vmoptions") + go run(wsInfo, alias, backendVersion, startTime, debugSuspendCh, env) + + if !debugSuspend { + close(debugSuspendCh) + } + + if debug { + http.HandleFunc("/debug/upload", func(w http.ResponseWriter, r *http.Request) { + const pluginDirPath = BackendPath + "/plugin/gitpod-remote" + err := os.RemoveAll(pluginDirPath) + if err != nil { + log.WithError(err).Error("failed to upload plugin") + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + pluginDir, err := os.OpenFile(pluginDirPath, os.O_WRONLY|os.O_CREATE, 0700) + if err != nil { + log.WithError(err).Error("failed to upload plugin") + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + defer pluginDir.Close() + + pluginArchive, err := zlib.NewReader(r.Body) + if err != nil { + log.WithError(err).Error("failed to upload plugin") + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + defer pluginArchive.Close() + + _, err = io.Copy(pluginDir, pluginArchive) + if err != nil { + log.WithError(err).Error("failed to upload plugin") + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + if debugSuspend { + debugSuspend = false + close(debugSuspendCh) + } else { + // restart + os.Exit(0) + } + }) } - go run(wsInfo, alias) http.HandleFunc("/joinLink", func(w http.ResponseWriter, r *http.Request) { backendPort := r.URL.Query().Get("backendPort") @@ -243,11 +288,34 @@ func resolveWorkspaceInfo(ctx context.Context) (*supervisor.WorkspaceInfoRespons return nil, errors.New("failed with attempt 10 times") } -func run(wsInfo *supervisor.WorkspaceInfoResponse, alias string) { +func run(wsInfo *supervisor.WorkspaceInfoResponse, alias string, backendVersion *version.Version, startTime time.Time, debugSuspend <-chan struct{}, runEnv []string) { + <-debugSuspend + + repoRoot := wsInfo.GetCheckoutLocation() + gitpodConfig, err := parseGitpodConfig(repoRoot) + if err != nil { + log.WithError(err).Error("failed to parse .gitpod.yml") + } + version_2022_1, _ := version.NewVersion("2022.1") + if version_2022_1.LessThanOrEqual(backendVersion) { + err = installPlugins(repoRoot, gitpodConfig, alias) + installPluginsCost := time.Now().Local().Sub(startTime).Milliseconds() + if err != nil { + log.WithError(err).WithField("cost", installPluginsCost).Error("installing repo plugins: done") + } else { + log.WithField("cost", installPluginsCost).Info("installing repo plugins: done") + } + } + + err = configureVMOptions(gitpodConfig, alias) + if err != nil { + log.WithError(err).Error("failed to configure vmoptions") + } + var args []string args = append(args, "run") args = append(args, wsInfo.GetCheckoutLocation()) - cmd := remoteDevServerCmd(args) + cmd := remoteDevServerCmd(args, runEnv) cmd.Env = append(cmd.Env, "JETBRAINS_GITPOD_BACKEND_KIND="+alias) workspaceUrl, err := url.Parse(wsInfo.WorkspaceUrl) if err == nil { @@ -261,7 +329,7 @@ func run(wsInfo *supervisor.WorkspaceInfoResponse, alias string) { } // Nicely handle SIGTERM sinal - go handleSignal(wsInfo.GetCheckoutLocation()) + go handleSignal() if err := cmd.Wait(); err != nil { log.WithError(err).Error("failed to wait") @@ -270,9 +338,9 @@ func run(wsInfo *supervisor.WorkspaceInfoResponse, alias string) { os.Exit(cmd.ProcessState.ExitCode()) } -func remoteDevServerCmd(args []string) *exec.Cmd { +func remoteDevServerCmd(args []string, env []string) *exec.Cmd { cmd := exec.Command(BackendPath+"/bin/remote-dev-server.sh", args...) - cmd.Env = os.Environ() + cmd.Env = env // Set default config and system directories under /workspace to preserve between restarts qualifier := os.Getenv("JETBRAINS_BACKEND_QUALIFIER") @@ -291,7 +359,7 @@ func remoteDevServerCmd(args []string) *exec.Cmd { return cmd } -func handleSignal(projectPath string) { +func handleSignal() { sigChan := make(chan os.Signal, 1) signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM) @@ -445,7 +513,7 @@ func installPlugins(repoRoot string, config *gitpod.GitpodConfig, alias string) args = append(args, "installPlugins") args = append(args, repoRoot) args = append(args, plugins...) - cmd := remoteDevServerCmd(args) + cmd := remoteDevServerCmd(args, os.Environ()) cmd.Stdout = io.MultiWriter(w, os.Stdout) installErr := cmd.Run()