// Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: BUSL-1.1 package execclient // import ( // "bytes" // "context" // "fmt" // "io" // "os" // "os/signal" // "github.com/containerd/console" // "google.golang.org/protobuf/proto" // "github.com/hashicorp/go-hclog" // grpc_net_conn "github.com/mitchellh/go-grpc-net-conn" // sshterm "golang.org/x/crypto/ssh/terminal" // "github.com/hashicorp/vagrant-plugin-sdk/terminal" // "github.com/hashicorp/vagrant/internal/server/proto/vagrant_server" // ) // type Client struct { // Logger hclog.Logger // UI terminal.UI // Context context.Context // Client vagrant_server.VagrantClient // DeploymentId string // DeploymentSeq uint64 // Args []string // Stdin io.Reader // Stdout io.Writer // Stderr io.Writer // } // func (c *Client) Run() (int, error) { // // Determine if we should allocate a pty. If we should, we need to send // // along a TERM value to the remote end that matches our own. // var ptyReq *vagrant_server.ExecStreamRequest_PTY // var ptyF *os.File // var status terminal.Status // if f, ok := c.Stdout.(*os.File); ok && sshterm.IsTerminal(int(f.Fd())) { // status = c.UI.Status() // defer status.Close() // status.Update(fmt.Sprintf("Connecting to deployment v%d...", c.DeploymentSeq)) // ptyF = f // c, err := console.ConsoleFromFile(ptyF) // if err != nil { // return 0, err // } // sz, err := c.Size() // c = nil // if err != nil { // return 0, err // } // ptyReq = &vagrant_server.ExecStreamRequest_PTY{ // Enable: true, // Term: os.Getenv("TERM"), // WindowSize: &vagrant_server.ExecStreamRequest_WindowSize{ // Rows: int32(sz.Height), // Cols: int32(sz.Width), // Height: int32(sz.Height), // Width: int32(sz.Width), // }, // } // } // // Start our exec stream // client, err := c.Client.StartExecStream(c.Context) // if err != nil { // return 0, err // } // defer client.CloseSend() // if status != nil { // status.Update("Initializing session...") // } // // Send the start event // if err := client.Send(&vagrant_server.ExecStreamRequest{ // Event: &vagrant_server.ExecStreamRequest_Start_{ // Start: &vagrant_server.ExecStreamRequest_Start{ // DeploymentId: c.DeploymentId, // Args: c.Args, // Pty: ptyReq, // }, // }, // }); err != nil { // return 0, err // } // if status != nil { // status.Update("Waiting for instance assignment...") // } // // Receive our open message. If this fails then we weren't assigned. // resp, err := client.Recv() // if err != nil { // return 1, err // } // if _, ok := resp.Event.(*vagrant_server.ExecStreamResponse_Open_); !ok { // return 1, fmt.Errorf("internal protocol error: unexpected opening message") // } // if ptyF != nil { // status.Close() // c.UI.Output("Connected to deployment v%d", c.DeploymentSeq, terminal.WithSuccessStyle()) // } // // Close our UI if we can // if closer, ok := c.UI.(io.Closer); ok { // closer.Close() // } // if ptyF != nil { // // We need to go into raw mode with stdin // if f, ok := c.Stdin.(*os.File); ok { // oldState, err := sshterm.MakeRaw(int(f.Fd())) // if err != nil { // return 0, err // } // defer sshterm.Restore(int(f.Fd()), oldState) // } // fmt.Fprintf(c.Stdout, "\r") // } // // Create the context that we'll listen to that lets us cancel our // // extra goroutines here. // ctx, cancel := context.WithCancel(c.Context) // defer cancel() // input := &EscapeWatcher{Cancel: cancel, Input: c.Stdin} // // Build our connection. We only build the stdin sending side because // // we can receive other message types from our recv. // go io.Copy(&grpc_net_conn.Conn{ // Stream: client, // Request: &vagrant_server.ExecStreamRequest{}, // Encode: grpc_net_conn.SimpleEncoder(func(msg proto.Message) *[]byte { // req := msg.(*vagrant_server.ExecStreamRequest) // if req.Event == nil { // req.Event = &vagrant_server.ExecStreamRequest_Input_{ // Input: &vagrant_server.ExecStreamRequest_Input{}, // } // } // return &req.Event.(*vagrant_server.ExecStreamRequest_Input_).Input.Data // }), // }, input) // // Add our recv blocker that sends data // recvCh := make(chan *vagrant_server.ExecStreamResponse) // go func() { // defer cancel() // for { // resp, err := client.Recv() // if err != nil { // c.Logger.Error("receive error", "err", err) // return // } // recvCh <- resp // } // }() // // Listen for window change events // winchCh := make(chan os.Signal, 1) // registerSigwinch(winchCh) // defer signal.Stop(winchCh) // // Loop for data // for { // select { // case resp := <-recvCh: // switch event := resp.Event.(type) { // case *vagrant_server.ExecStreamResponse_Output_: // // TODO: stderr // out := c.Stdout // io.Copy(out, bytes.NewReader(event.Output.Data)) // case *vagrant_server.ExecStreamResponse_Exit_: // return int(event.Exit.Code), nil // default: // c.Logger.Warn("unknown event type", // "type", fmt.Sprintf("%T", resp.Event)) // } // case <-winchCh: // // Window change, send new size // c, err := console.ConsoleFromFile(ptyF) // if err != nil { // continue // } // sz, err := c.Size() // if err != nil { // continue // } // // Send the new window size // if err := client.Send(&vagrant_server.ExecStreamRequest{ // Event: &vagrant_server.ExecStreamRequest_Winch{ // Winch: &vagrant_server.ExecStreamRequest_WindowSize{ // Rows: int32(sz.Height), // Cols: int32(sz.Width), // Height: int32(sz.Height), // Width: int32(sz.Width), // }, // }, // }); err != nil { // // Ignore this error // continue // } // case <-ctx.Done(): // return 1, nil // } // } // }