Path: blob/main/components/gitpod-cli/pkg/supervisor/terminal-attach.go
2500 views
// Copyright (c) 2022 Gitpod GmbH. All rights reserved.1// Licensed under the GNU Affero General Public License (AGPL).2// See License.AGPL.txt in the project root for license information.34package supervisor56import (7"context"8"io"9"os"10"os/signal"11"syscall"1213"github.com/creack/pty"14"github.com/gitpod-io/gitpod/supervisor/api"15log "github.com/sirupsen/logrus"16"golang.org/x/term"17"golang.org/x/xerrors"18)1920type AttachToTerminalOpts struct {21Interactive bool22ForceResize bool23Token string24}2526func (client *SupervisorClient) AttachToTerminal(ctx context.Context, alias string, opts AttachToTerminalOpts) (int, error) {27// Copy to stdout/stderr28listen, err := client.Terminal.Listen(ctx, &api.ListenTerminalRequest{29Alias: alias,30})31if err != nil {32return 0, xerrors.Errorf("cannot attach to terminal: %w", err)33}34var exitCode int35errchan := make(chan error, 5)36go func() {37for {38resp, err := listen.Recv()39if err != nil {40errchan <- err41}42os.Stdout.Write(resp.GetData())43terminalExitCode := resp.GetExitCode()44if terminalExitCode > 0 {45exitCode = int(terminalExitCode)46}47}48}()4950// Set stdin in raw mode.51oldState, err := term.MakeRaw(int(os.Stdin.Fd()))52if err != nil {53return 0, xerrors.Errorf("cannot attach to terminal: %w", err)54}55defer func() { _ = term.Restore(int(os.Stdin.Fd()), oldState) }() // Best effort.5657if opts.Interactive {58// Handle pty size.59ch := make(chan os.Signal, 1)60signal.Notify(ch, syscall.SIGWINCH)61go func() {62for range ch {63size, err := pty.GetsizeFull(os.Stdin)64if err != nil {65log.WithError(err).Error("cannot determine stdin's terminal size")66continue67}6869req := &api.SetTerminalSizeRequest{70Alias: alias,71Size: &api.TerminalSize{72Cols: uint32(size.Cols),73Rows: uint32(size.Rows),74WidthPx: uint32(size.X),75HeightPx: uint32(size.Y),76},77}7879var expectResize bool80if opts.ForceResize {81req.Priority = &api.SetTerminalSizeRequest_Force{Force: true}82expectResize = true83} else if opts.Token != "" {84req.Priority = &api.SetTerminalSizeRequest_Token{Token: opts.Token}85expectResize = true86}8788_, err = client.Terminal.SetSize(ctx, req)89if err != nil && expectResize {90log.WithError(err).Error("cannot set terminal size")91continue92}93}94}()95ch <- syscall.SIGWINCH // Initial resize.9697// Copy stdin to the pty and the pty to stdout.98go func() {99buf := make([]byte, 32*1024)100for {101n, err := os.Stdin.Read(buf)102if n > 0 {103_, serr := client.Terminal.Write(ctx, &api.WriteTerminalRequest{Alias: alias, Stdin: buf[:n]})104if serr != nil {105errchan <- err106return107}108}109if err != nil {110errchan <- err111return112}113}114}()115}116117// wait indefinitely118select {119case err := <-errchan:120if err != io.EOF {121return 0, err122} else {123return exitCode, nil124}125case <-ctx.Done():126return 0, nil127}128}129130131