Skip to content

[jb] remote debugging of backend plugin #12442

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions components/ide/jetbrains/backend-plugin/debug/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module github.com/gitpod-io/gitpod/jetbrains/backend/plugin/debug

go 1.18
31 changes: 31 additions & 0 deletions components/ide/jetbrains/backend-plugin/debug/main.go
Original file line number Diff line number Diff line change
@@ -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)
}
5 changes: 0 additions & 5 deletions components/ide/jetbrains/image/startup.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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 "$@"
116 changes: 92 additions & 24 deletions components/ide/jetbrains/image/status/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ package main

import (
"bytes"
"compress/zlib"
"context"
"encoding/json"
"errors"
Expand Down Expand Up @@ -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")
Expand Down Expand Up @@ -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 {
Expand All @@ -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")
Expand All @@ -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")
Expand All @@ -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)

Expand Down Expand Up @@ -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()

Expand Down