mirror of https://github.com/containers/podman.git
Merge pull request #6249 from jwhonce/wip/resize
V2 Implement terminal handling in bindings attach
This commit is contained in:
commit
9fe49335e0
|
|
@ -10,7 +10,6 @@ import (
|
|||
"github.com/gorilla/schema"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
"k8s.io/client-go/tools/remotecommand"
|
||||
)
|
||||
|
||||
// AttachHeader is the literal header sent for upgraded/hijacked connections for
|
||||
|
|
@ -127,38 +126,3 @@ func AttachContainer(w http.ResponseWriter, r *http.Request) {
|
|||
|
||||
logrus.Debugf("Attach for container %s completed successfully", ctr.ID())
|
||||
}
|
||||
|
||||
func ResizeContainer(w http.ResponseWriter, r *http.Request) {
|
||||
runtime := r.Context().Value("runtime").(*libpod.Runtime)
|
||||
decoder := r.Context().Value("decoder").(*schema.Decoder)
|
||||
|
||||
query := struct {
|
||||
Height uint16 `schema:"h"`
|
||||
Width uint16 `schema:"w"`
|
||||
}{}
|
||||
if err := decoder.Decode(&query, r.URL.Query()); err != nil {
|
||||
// This is not a 400, despite the fact that is should be, for
|
||||
// compatibility reasons.
|
||||
utils.InternalServerError(w, errors.Wrapf(err, "error parsing query options"))
|
||||
return
|
||||
}
|
||||
|
||||
name := utils.GetName(r)
|
||||
ctr, err := runtime.LookupContainer(name)
|
||||
if err != nil {
|
||||
utils.ContainerNotFound(w, name, err)
|
||||
return
|
||||
}
|
||||
|
||||
newSize := remotecommand.TerminalSize{
|
||||
Width: query.Width,
|
||||
Height: query.Height,
|
||||
}
|
||||
if err := ctr.AttachResize(newSize); err != nil {
|
||||
utils.InternalServerError(w, err)
|
||||
return
|
||||
}
|
||||
// This is not a 204, even though we write nothing, for compatibility
|
||||
// reasons.
|
||||
utils.WriteResponse(w, http.StatusOK, "")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,68 @@
|
|||
package compat
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/containers/libpod/libpod"
|
||||
"github.com/containers/libpod/pkg/api/handlers/utils"
|
||||
"github.com/gorilla/schema"
|
||||
"github.com/pkg/errors"
|
||||
"k8s.io/client-go/tools/remotecommand"
|
||||
)
|
||||
|
||||
func ResizeTTY(w http.ResponseWriter, r *http.Request) {
|
||||
runtime := r.Context().Value("runtime").(*libpod.Runtime)
|
||||
decoder := r.Context().Value("decoder").(*schema.Decoder)
|
||||
|
||||
// /containers/{id}/resize
|
||||
query := struct {
|
||||
height uint16 `schema:"h"`
|
||||
width uint16 `schema:"w"`
|
||||
}{
|
||||
// override any golang type defaults
|
||||
}
|
||||
|
||||
if err := decoder.Decode(&query, r.URL.Query()); err != nil {
|
||||
utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest,
|
||||
errors.Wrapf(err, "Failed to parse parameters for %s", r.URL.String()))
|
||||
return
|
||||
}
|
||||
|
||||
sz := remotecommand.TerminalSize{
|
||||
Width: query.width,
|
||||
Height: query.height,
|
||||
}
|
||||
|
||||
var status int
|
||||
name := utils.GetName(r)
|
||||
switch {
|
||||
case strings.Contains(r.URL.Path, "/containers/"):
|
||||
ctnr, err := runtime.LookupContainer(name)
|
||||
if err != nil {
|
||||
utils.ContainerNotFound(w, name, err)
|
||||
return
|
||||
}
|
||||
if err := ctnr.AttachResize(sz); err != nil {
|
||||
utils.InternalServerError(w, errors.Wrapf(err, "cannot resize container"))
|
||||
return
|
||||
}
|
||||
// This is not a 204, even though we write nothing, for compatibility
|
||||
// reasons.
|
||||
status = http.StatusOK
|
||||
case strings.Contains(r.URL.Path, "/exec/"):
|
||||
ctnr, err := runtime.GetExecSessionContainer(name)
|
||||
if err != nil {
|
||||
utils.SessionNotFound(w, name, err)
|
||||
return
|
||||
}
|
||||
if err := ctnr.ExecResize(name, sz); err != nil {
|
||||
utils.InternalServerError(w, errors.Wrapf(err, "cannot resize session"))
|
||||
return
|
||||
}
|
||||
// This is not a 204, even though we write nothing, for compatibility
|
||||
// reasons.
|
||||
status = http.StatusCreated
|
||||
}
|
||||
utils.WriteResponse(w, status, "")
|
||||
}
|
||||
|
|
@ -63,6 +63,14 @@ func PodNotFound(w http.ResponseWriter, name string, err error) {
|
|||
Error(w, msg, http.StatusNotFound, err)
|
||||
}
|
||||
|
||||
func SessionNotFound(w http.ResponseWriter, name string, err error) {
|
||||
if errors.Cause(err) != define.ErrNoSuchExecSession {
|
||||
InternalServerError(w, err)
|
||||
}
|
||||
msg := fmt.Sprintf("No such exec session: %s", name)
|
||||
Error(w, msg, http.StatusNotFound, err)
|
||||
}
|
||||
|
||||
func ContainerNotRunning(w http.ResponseWriter, containerID string, err error) {
|
||||
msg := fmt.Sprintf("Container %s is not running", containerID)
|
||||
Error(w, msg, http.StatusConflict, err)
|
||||
|
|
|
|||
|
|
@ -584,9 +584,9 @@ func (s *APIServer) registerContainersHandlers(r *mux.Router) error {
|
|||
// $ref: "#/responses/NoSuchContainer"
|
||||
// 500:
|
||||
// $ref: "#/responses/InternalError"
|
||||
r.HandleFunc(VersionedPath("/containers/{name}/resize"), s.APIHandler(compat.ResizeContainer)).Methods(http.MethodPost)
|
||||
r.HandleFunc(VersionedPath("/containers/{name}/resize"), s.APIHandler(compat.ResizeTTY)).Methods(http.MethodPost)
|
||||
// Added non version path to URI to support docker non versioned paths
|
||||
r.HandleFunc("/containers/{name}/resize", s.APIHandler(compat.ResizeContainer)).Methods(http.MethodPost)
|
||||
r.HandleFunc("/containers/{name}/resize", s.APIHandler(compat.ResizeTTY)).Methods(http.MethodPost)
|
||||
// swagger:operation GET /containers/{name}/export compat exportContainer
|
||||
// ---
|
||||
// tags:
|
||||
|
|
@ -1259,7 +1259,7 @@ func (s *APIServer) registerContainersHandlers(r *mux.Router) error {
|
|||
// $ref: "#/responses/NoSuchContainer"
|
||||
// 500:
|
||||
// $ref: "#/responses/InternalError"
|
||||
r.HandleFunc(VersionedPath("/libpod/containers/{name}/resize"), s.APIHandler(compat.ResizeContainer)).Methods(http.MethodPost)
|
||||
r.HandleFunc(VersionedPath("/libpod/containers/{name}/resize"), s.APIHandler(compat.ResizeTTY)).Methods(http.MethodPost)
|
||||
// swagger:operation GET /libpod/containers/{name}/export libpod libpodExportContainer
|
||||
// ---
|
||||
// tags:
|
||||
|
|
|
|||
|
|
@ -145,9 +145,9 @@ func (s *APIServer) registerExecHandlers(r *mux.Router) error {
|
|||
// $ref: "#/responses/NoSuchExecInstance"
|
||||
// 500:
|
||||
// $ref: "#/responses/InternalError"
|
||||
r.Handle(VersionedPath("/exec/{id}/resize"), s.APIHandler(compat.UnsupportedHandler)).Methods(http.MethodPost)
|
||||
r.Handle(VersionedPath("/exec/{id}/resize"), s.APIHandler(compat.ResizeTTY)).Methods(http.MethodPost)
|
||||
// Added non version path to URI to support docker non versioned paths
|
||||
r.Handle("/exec/{id}/resize", s.APIHandler(compat.UnsupportedHandler)).Methods(http.MethodPost)
|
||||
r.Handle("/exec/{id}/resize", s.APIHandler(compat.ResizeTTY)).Methods(http.MethodPost)
|
||||
// swagger:operation GET /exec/{id}/json compat inspectExec
|
||||
// ---
|
||||
// tags:
|
||||
|
|
|
|||
|
|
@ -7,8 +7,11 @@ import (
|
|||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"os/signal"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"github.com/containers/libpod/libpod/define"
|
||||
"github.com/containers/libpod/pkg/api/handlers"
|
||||
|
|
@ -16,6 +19,7 @@ import (
|
|||
"github.com/containers/libpod/pkg/domain/entities"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
"golang.org/x/crypto/ssh/terminal"
|
||||
)
|
||||
|
||||
var (
|
||||
|
|
@ -374,11 +378,58 @@ func Attach(ctx context.Context, nameOrId string, detachKeys *string, logs, stre
|
|||
params.Add("stderr", "true")
|
||||
}
|
||||
|
||||
// Unless all requirements are met, don't use "stdin" is a terminal
|
||||
file, ok := stdin.(*os.File)
|
||||
needTTY := ok && terminal.IsTerminal(int(file.Fd())) && ctnr.Config.Tty
|
||||
if needTTY {
|
||||
state, err := terminal.MakeRaw(int(file.Fd()))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
logrus.SetFormatter(&rawFormatter{})
|
||||
|
||||
defer func() {
|
||||
if err := terminal.Restore(int(file.Fd()), state); err != nil {
|
||||
logrus.Errorf("unable to restore terminal: %q", err)
|
||||
}
|
||||
logrus.SetFormatter(&logrus.TextFormatter{})
|
||||
}()
|
||||
|
||||
winChange := make(chan os.Signal, 1)
|
||||
signal.Notify(winChange, syscall.SIGWINCH)
|
||||
winCtx, winCancel := context.WithCancel(ctx)
|
||||
defer winCancel()
|
||||
|
||||
go func() {
|
||||
// Prime the pump, we need one reset to ensure everything is ready
|
||||
winChange <- syscall.SIGWINCH
|
||||
for {
|
||||
select {
|
||||
case <-winCtx.Done():
|
||||
return
|
||||
case <-winChange:
|
||||
h, w, err := terminal.GetSize(int(file.Fd()))
|
||||
if err != nil {
|
||||
logrus.Warnf("failed to obtain TTY size: " + err.Error())
|
||||
}
|
||||
|
||||
if err := ResizeContainerTTY(ctx, nameOrId, &h, &w); err != nil {
|
||||
logrus.Warnf("failed to resize TTY: " + err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
response, err := conn.DoRequest(nil, http.MethodPost, "/containers/%s/attach", params, nameOrId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer response.Body.Close()
|
||||
if !(response.IsSuccess() || response.IsInformational()) {
|
||||
return response.Process(nil)
|
||||
}
|
||||
|
||||
if stdin != nil {
|
||||
go func() {
|
||||
|
|
@ -401,11 +452,8 @@ func Attach(ctx context.Context, nameOrId string, detachKeys *string, logs, stre
|
|||
// Read multiplexed channels and write to appropriate stream
|
||||
fd, l, err := DemuxHeader(response.Body, buffer)
|
||||
if err != nil {
|
||||
switch {
|
||||
case errors.Is(err, io.EOF):
|
||||
if errors.Is(err, io.EOF) {
|
||||
return nil
|
||||
case errors.Is(err, io.ErrUnexpectedEOF):
|
||||
continue
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
|
@ -437,7 +485,7 @@ func Attach(ctx context.Context, nameOrId string, detachKeys *string, logs, stre
|
|||
}
|
||||
}
|
||||
}
|
||||
return err
|
||||
return nil
|
||||
}
|
||||
|
||||
// DemuxHeader reads header for stream from server multiplexed stdin/stdout/stderr/2nd error channel
|
||||
|
|
@ -477,3 +525,46 @@ func DemuxFrame(r io.Reader, buffer []byte, length int) (frame []byte, err error
|
|||
|
||||
return buffer[0:length], nil
|
||||
}
|
||||
|
||||
// ResizeContainerTTY sets container's TTY height and width in characters
|
||||
func ResizeContainerTTY(ctx context.Context, nameOrId string, height *int, width *int) error {
|
||||
return resizeTTY(ctx, bindings.JoinURL("containers", nameOrId, "resize"), height, width)
|
||||
}
|
||||
|
||||
// ResizeExecTTY sets session's TTY height and width in characters
|
||||
func ResizeExecTTY(ctx context.Context, nameOrId string, height *int, width *int) error {
|
||||
return resizeTTY(ctx, bindings.JoinURL("exec", nameOrId, "resize"), height, width)
|
||||
}
|
||||
|
||||
// resizeTTY set size of TTY of container
|
||||
func resizeTTY(ctx context.Context, endpoint string, height *int, width *int) error {
|
||||
conn, err := bindings.GetClient(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
params := url.Values{}
|
||||
if height != nil {
|
||||
params.Set("h", strconv.Itoa(*height))
|
||||
}
|
||||
if width != nil {
|
||||
params.Set("w", strconv.Itoa(*width))
|
||||
}
|
||||
rsp, err := conn.DoRequest(nil, http.MethodPost, endpoint, params)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return rsp.Process(nil)
|
||||
}
|
||||
|
||||
type rawFormatter struct {
|
||||
logrus.TextFormatter
|
||||
}
|
||||
|
||||
func (f *rawFormatter) Format(entry *logrus.Entry) ([]byte, error) {
|
||||
buffer, err := f.TextFormatter.Format(entry)
|
||||
if err != nil {
|
||||
return buffer, err
|
||||
}
|
||||
return append(buffer, '\r'), nil
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue