Skip to content

Commit

Permalink
feat: toolbox exec session management
Browse files Browse the repository at this point in the history
Signed-off-by: Toma Puljak <[email protected]>
  • Loading branch information
Tpuljak committed Jan 20, 2025
1 parent fedeaf4 commit 5d9b747
Show file tree
Hide file tree
Showing 34 changed files with 3,863 additions and 100 deletions.
14 changes: 10 additions & 4 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,16 @@
"program": "${workspaceFolder}/cmd/daytona",
"console": "integratedTerminal",
"env": {
"DAYTONA_WS_ID": "WS_ID",
"DAYTONA_WS_PROJECT_NAME": "PROJECT_NAME",
"DAYTONA_SERVER_URL": "http://localhost:3986",
"DAYTONA_SERVER_API_KEY": "1234567890",
"DAYTONA_SERVER_API_KEY": "MWJjOGNiMDEtYTFhYi00ZTUzLWIxMTMtOWM5MmVjYzI5NWI0",
"DAYTONA_SERVER_URL": "https://31997cd4-a879-4955-b8b5-49d7e33b50a8.try-eu.daytona.app",
"DAYTONA_SERVER_API_URL": "https://api-31997cd4-a879-4955-b8b5-49d7e33b50a8.try-eu.daytona.app",
"DAYTONA_AGENT_LOG_FILE_PATH": "(HOME)/.daytona-agent.log",
"DAYTONA_WS_ID": "e412ba7c6ec4",
"DAYTONA_TELEMETRY_ENABLED": "true",
"DAYTONA_WS_PROJECT_NAME": "kubectl",
"DAYTONA_CLIENT_ID": "3c79d5f9-f561-4f92-a99b-5ad5de374579",
"DAYTONA_WS_PROJECT_REPOSITORY_URL": "https://github.com/kubernetes/kubectl.git",
"DAYTONA_SERVER_VERSION": "v0.0.0-dev",
},
"args": [
"agent"
Expand Down
40 changes: 40 additions & 0 deletions cmd/daytona/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,32 @@
package main

import (
"context"
"fmt"
"net/http"
"os"
"time"

golog "log"

"github.com/daytonaio/daytona/cmd/daytona/config"
"github.com/daytonaio/daytona/internal"
"github.com/daytonaio/daytona/internal/util"
apiclient_util "github.com/daytonaio/daytona/internal/util/apiclient"
"github.com/daytonaio/daytona/pkg/cmd"
"github.com/daytonaio/daytona/pkg/cmd/workspacemode"
"github.com/gorilla/websocket"
"github.com/rs/zerolog"
zlog "github.com/rs/zerolog/log"
log "github.com/sirupsen/logrus"
)

func main() {
if len(os.Args) > 1 && os.Args[1] == "logs" {
readCmdLogs(os.Args[2], os.Args[3])
return
}

if internal.WorkspaceMode() {
err := workspacemode.Execute()
if err != nil {
Expand Down Expand Up @@ -62,3 +73,32 @@ func init() {

golog.SetOutput(&util.DebugLogWriter{})
}

func readCmdLogs(sessionId, cmdId string) {
ws, res, err := apiclient_util.GetWebsocketConn(context.Background(), fmt.Sprintf("/workspace/kubectl/kubectl/toolbox/process/session/%s/%s/logs", sessionId, cmdId), &config.Profile{
Api: config.ServerApi{
Url: "http://localhost:3986",
Key: "OGY3ZDEyMDMtNzFmZi00ZDIxLWIzZDgtNWQ3OTk0ZjA2MWJk",
},
}, util.Pointer("follow=true"))
if res.StatusCode == http.StatusNotFound {
log.Fatal(err)
}
if err != nil {
log.Fatal(err)
}

for {
_, msg, err := ws.ReadMessage()
if err != nil {
if websocket.IsUnexpectedCloseError(err, websocket.CloseNormalClosure, websocket.CloseAbnormalClosure) {
log.Fatal(err)
} else {
log.Info(err)
return
}
}

fmt.Print(string(msg))
}
}
35 changes: 2 additions & 33 deletions pkg/agent/ssh/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@ import (
"io"
"os"
"os/exec"
"strings"
"syscall"
"unsafe"

"github.com/creack/pty"
"github.com/daytonaio/daytona/pkg/agent/ssh/config"
"github.com/daytonaio/daytona/pkg/common"
"github.com/gliderlabs/ssh"
"github.com/pkg/sftp"
"golang.org/x/sys/unix"
Expand Down Expand Up @@ -81,7 +81,7 @@ func (s *Server) Start() error {
}

func (s *Server) handlePty(session ssh.Session, ptyReq ssh.Pty, winCh <-chan ssh.Window) {
shell := s.getShell()
shell := common.GetShell()
cmd := exec.Command(shell)

cmd.Dir = s.ProjectDir
Expand Down Expand Up @@ -233,37 +233,6 @@ func (s *Server) osSignalFrom(sig ssh.Signal) os.Signal {
}
}

func (s *Server) getShell() string {
out, err := exec.Command("sh", "-c", "grep '^[^#]' /etc/shells").Output()
if err != nil {
return "sh"
}

if strings.Contains(string(out), "/usr/bin/zsh") {
return "/usr/bin/zsh"
}

if strings.Contains(string(out), "/bin/zsh") {
return "/bin/zsh"
}

if strings.Contains(string(out), "/usr/bin/bash") {
return "/usr/bin/bash"
}

if strings.Contains(string(out), "/bin/bash") {
return "/bin/bash"
}

shellEnv, shellSet := os.LookupEnv("SHELL")

if shellSet {
return shellEnv
}

return "sh"
}

func (s *Server) sftpHandler(session ssh.Session) {
debugStream := io.Discard
serverOptions := []sftp.ServerOption{
Expand Down
87 changes: 45 additions & 42 deletions pkg/agent/toolbox/process/execute.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,63 +14,66 @@ import (
"github.com/gin-gonic/gin"
)

func ExecuteCommand(c *gin.Context) {
var request ExecuteRequest
if err := c.ShouldBindJSON(&request); err != nil {
c.AbortWithError(400, errors.New("command is required"))
return
}
func ExecuteCommand(projectDir string) func(c *gin.Context) {
return func(c *gin.Context) {
var request ExecuteRequest
if err := c.ShouldBindJSON(&request); err != nil {
c.AbortWithError(400, errors.New("command is required"))
return
}

cmdParts := parseCommand(request.Command)
if len(cmdParts) == 0 {
c.AbortWithError(400, errors.New("empty command"))
return
}
cmdParts := parseCommand(request.Command)
if len(cmdParts) == 0 {
c.AbortWithError(400, errors.New("empty command"))
return
}

cmd := exec.Command(cmdParts[0], cmdParts[1:]...)
cmd := exec.Command(cmdParts[0], cmdParts[1:]...)
cmd.Dir = projectDir

// set maximum execution time
timeout := 10 * time.Second
if request.Timeout != nil && *request.Timeout > 0 {
timeout = time.Duration(*request.Timeout) * time.Second
}
// set maximum execution time
timeout := 10 * time.Second
if request.Timeout != nil && *request.Timeout > 0 {
timeout = time.Duration(*request.Timeout) * time.Second
}

timeoutReached := false
timer := time.AfterFunc(timeout, func() {
timeoutReached = true
if cmd.Process != nil {
// kill the process group
err := cmd.Process.Kill()
if err != nil {
log.Error(err)
return
}
}
})
defer timer.Stop()

timeoutReached := false
timer := time.AfterFunc(timeout, func() {
timeoutReached = true
if cmd.Process != nil {
// kill the process group
err := cmd.Process.Kill()
if err != nil {
log.Error(err)
output, err := cmd.CombinedOutput()
if err != nil {
if timeoutReached {
c.AbortWithError(408, errors.New("command execution timeout"))
return
}
c.AbortWithError(400, err)
return
}
})
defer timer.Stop()

output, err := cmd.CombinedOutput()
if err != nil {
if timeoutReached {
c.AbortWithError(408, errors.New("command execution timeout"))
if cmd.ProcessState == nil {
c.JSON(200, ExecuteResponse{
Code: -1,
Result: string(output),
})
return
}
c.AbortWithError(400, err)
return
}

if cmd.ProcessState == nil {
c.JSON(200, ExecuteResponse{
Code: -1,
Code: cmd.ProcessState.ExitCode(),
Result: string(output),
})
return
}

c.JSON(200, ExecuteResponse{
Code: cmd.ProcessState.ExitCode(),
Result: string(output),
})
}

// parseCommand splits a command string properly handling quotes
Expand Down
Loading

0 comments on commit 5d9b747

Please sign in to comment.