mirror of
https://github.com/gitpod-io/gitpod.git
synced 2025-12-08 17:36:30 +00:00
193 lines
4.2 KiB
Go
193 lines
4.2 KiB
Go
// Copyright (c) 2020 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 (
|
|
"bytes"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"os"
|
|
"os/exec"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/prometheus/procfs"
|
|
"golang.org/x/sys/unix"
|
|
"golang.org/x/xerrors"
|
|
|
|
"github.com/gitpod-io/gitpod/test/pkg/agent/workspace/api"
|
|
"github.com/gitpod-io/gitpod/test/pkg/integration"
|
|
)
|
|
|
|
func main() {
|
|
done := make(chan struct{})
|
|
go func() {
|
|
mux := http.NewServeMux()
|
|
mux.Handle("/shutdown", shugtdownHandler(done))
|
|
_ = http.ListenAndServe(":8080", mux)
|
|
}()
|
|
|
|
err := enterSupervisorNamespaces()
|
|
if err != nil {
|
|
panic(fmt.Sprintf("enterSupervisorNamespaces: %v", err))
|
|
}
|
|
|
|
integration.ServeAgent(done, new(WorkspaceAgent))
|
|
}
|
|
|
|
func shugtdownHandler(done chan struct{}) http.HandlerFunc {
|
|
return func(w http.ResponseWriter, _ *http.Request) {
|
|
close(done)
|
|
w.Write([]byte("shutdown"))
|
|
w.WriteHeader(http.StatusOK)
|
|
}
|
|
}
|
|
|
|
func enterSupervisorNamespaces() error {
|
|
if os.Getenv("AGENT_IN_RING2") != "" {
|
|
return nil
|
|
}
|
|
|
|
nsenter, err := exec.LookPath("nsenter")
|
|
if err != nil {
|
|
return xerrors.Errorf("cannot find nsenter")
|
|
}
|
|
|
|
// This agent expectes to be called using the workspacekit lift (i.e. in ring1).
|
|
// We then enter the PID and mount namespace of supervisor.
|
|
// First, we need to find the supervisor process
|
|
proc, err := procfs.NewFS("/proc")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
procs, err := proc.AllProcs()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
var supervisorPID int
|
|
for _, p := range procs {
|
|
cmd, _ := p.CmdLine()
|
|
for _, c := range cmd {
|
|
if strings.HasSuffix(c, "supervisor") {
|
|
supervisorPID = p.PID
|
|
break
|
|
}
|
|
}
|
|
if supervisorPID != 0 {
|
|
break
|
|
}
|
|
}
|
|
if supervisorPID == 0 {
|
|
return xerrors.Errorf("no supervisor process found")
|
|
}
|
|
|
|
// Then we copy ourselves to a location that we can access from the supervisor NS
|
|
self, err := os.Executable()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
fn := fmt.Sprintf("/proc/%d/root/agent", supervisorPID)
|
|
dst, err := os.OpenFile(fn, os.O_CREATE|os.O_WRONLY, 0755)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
selfFD, err := os.Open(self)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer selfFD.Close()
|
|
_, err = io.Copy(dst, selfFD)
|
|
if err != nil {
|
|
return xerrors.Errorf("error copying agent: %w", err)
|
|
}
|
|
|
|
return unix.Exec(nsenter, append([]string{nsenter, "-t", strconv.Itoa(supervisorPID), "-m", "-p", "/agent"}, os.Args[1:]...), append(os.Environ(), "AGENT_IN_RING2=true"))
|
|
}
|
|
|
|
// WorkspaceAgent provides ingteration test services from within a workspace
|
|
type WorkspaceAgent struct {
|
|
}
|
|
|
|
// ListDir lists a directory's content
|
|
func (*WorkspaceAgent) ListDir(req *api.ListDirRequest, resp *api.ListDirResponse) error {
|
|
dc, err := os.ReadDir(req.Dir)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
*resp = api.ListDirResponse{}
|
|
for _, c := range dc {
|
|
resp.Files = append(resp.Files, c.Name())
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// WriteFile writes a file in the workspace
|
|
func (*WorkspaceAgent) WriteFile(req *api.WriteFileRequest, resp *api.WriteFileResponse) (err error) {
|
|
err = os.WriteFile(req.Path, req.Content, req.Mode)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
*resp = api.WriteFileResponse{}
|
|
return
|
|
}
|
|
|
|
// Exec executes a command in the workspace
|
|
func (*WorkspaceAgent) Exec(req *api.ExecRequest, resp *api.ExecResponse) (err error) {
|
|
cmd := exec.Command(req.Command, req.Args...)
|
|
cmd.Env = os.Environ()
|
|
cmd.Env = append(cmd.Env, req.Env...)
|
|
if req.Dir != "" {
|
|
cmd.Dir = req.Dir
|
|
}
|
|
var (
|
|
stdout bytes.Buffer
|
|
stderr bytes.Buffer
|
|
)
|
|
cmd.Stdout = &stdout
|
|
cmd.Stderr = &stderr
|
|
err = cmd.Run()
|
|
|
|
var rc int
|
|
if err != nil {
|
|
exitError, ok := err.(*exec.ExitError)
|
|
if !ok {
|
|
fullCommand := strings.Join(append([]string{req.Command}, req.Args...), " ")
|
|
return xerrors.Errorf("%s: %w", fullCommand, err)
|
|
}
|
|
rc = exitError.ExitCode()
|
|
err = nil
|
|
}
|
|
|
|
*resp = api.ExecResponse{
|
|
ExitCode: rc,
|
|
Stdout: stdout.String(),
|
|
Stderr: stderr.String(),
|
|
}
|
|
return
|
|
}
|
|
|
|
func (*WorkspaceAgent) BurnCpu(req *api.BurnCpuRequest, resp *api.BurnCpuResponse) error {
|
|
done := make(chan int)
|
|
for i := 0; i < int(req.Procs); i++ {
|
|
go func() {
|
|
for {
|
|
select {
|
|
case <-done:
|
|
return
|
|
default:
|
|
}
|
|
}
|
|
}()
|
|
}
|
|
|
|
time.Sleep(req.Timeout)
|
|
close(done)
|
|
return nil
|
|
}
|