diff options
Diffstat (limited to 'pkg')
-rw-r--r-- | pkg/api/handlers/compat/containers_attach.go | 36 | ||||
-rw-r--r-- | pkg/api/handlers/compat/resize.go | 68 | ||||
-rw-r--r-- | pkg/api/handlers/utils/errors.go | 8 | ||||
-rw-r--r-- | pkg/api/server/register_containers.go | 6 | ||||
-rw-r--r-- | pkg/api/server/register_exec.go | 4 | ||||
-rw-r--r-- | pkg/autoupdate/autoupdate.go | 9 | ||||
-rw-r--r-- | pkg/bindings/containers/containers.go | 101 | ||||
-rw-r--r-- | pkg/varlinkapi/transfers.go | 2 |
8 files changed, 187 insertions, 47 deletions
diff --git a/pkg/api/handlers/compat/containers_attach.go b/pkg/api/handlers/compat/containers_attach.go index 3c9a6fd69..5fc3117b9 100644 --- a/pkg/api/handlers/compat/containers_attach.go +++ b/pkg/api/handlers/compat/containers_attach.go @@ -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, "") -} diff --git a/pkg/api/handlers/compat/resize.go b/pkg/api/handlers/compat/resize.go new file mode 100644 index 000000000..3ead733bc --- /dev/null +++ b/pkg/api/handlers/compat/resize.go @@ -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, "") +} diff --git a/pkg/api/handlers/utils/errors.go b/pkg/api/handlers/utils/errors.go index 3253a9be3..c17720694 100644 --- a/pkg/api/handlers/utils/errors.go +++ b/pkg/api/handlers/utils/errors.go @@ -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) diff --git a/pkg/api/server/register_containers.go b/pkg/api/server/register_containers.go index 378d1e06c..0d78e4cdb 100644 --- a/pkg/api/server/register_containers.go +++ b/pkg/api/server/register_containers.go @@ -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: diff --git a/pkg/api/server/register_exec.go b/pkg/api/server/register_exec.go index 19b7e2fcd..1533edba9 100644 --- a/pkg/api/server/register_exec.go +++ b/pkg/api/server/register_exec.go @@ -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: diff --git a/pkg/autoupdate/autoupdate.go b/pkg/autoupdate/autoupdate.go index 1b0419892..eca5c342c 100644 --- a/pkg/autoupdate/autoupdate.go +++ b/pkg/autoupdate/autoupdate.go @@ -23,6 +23,10 @@ import ( // container labels. const Label = "io.containers.autoupdate" +// Label denotes the container label key to specify authfile in +// container labels. +const AuthfileLabel = "io.containers.autoupdate.authfile" + // Policy represents an auto-update policy. type Policy string @@ -144,6 +148,11 @@ func AutoUpdate(runtime *libpod.Runtime, options Options) ([]string, []error) { if rawImageName == "" { errs = append(errs, errors.Errorf("error auto-updating container %q: raw-image name is empty", ctr.ID())) } + labels := ctr.Labels() + authFilePath, exists := labels[AuthfileLabel] + if exists { + options.Authfile = authFilePath + } needsUpdate, err := newerImageAvailable(runtime, image, rawImageName, options) if err != nil { errs = append(errs, errors.Wrapf(err, "error auto-updating container %q: image check for %q failed", ctr.ID(), rawImageName)) diff --git a/pkg/bindings/containers/containers.go b/pkg/bindings/containers/containers.go index b77ef208d..f0984b8e3 100644 --- a/pkg/bindings/containers/containers.go +++ b/pkg/bindings/containers/containers.go @@ -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 +} diff --git a/pkg/varlinkapi/transfers.go b/pkg/varlinkapi/transfers.go index 9df8ffcdc..aed6e054d 100644 --- a/pkg/varlinkapi/transfers.go +++ b/pkg/varlinkapi/transfers.go @@ -39,7 +39,7 @@ func (i *VarlinkAPI) SendFile(call iopodman.VarlinkCall, ftype string, length in logrus.Debugf("successfully received %s", outputFile.Name()) // Send an ACK to the client - call.Call.Writer.WriteString(outputFile.Name()) + call.Call.Writer.WriteString(outputFile.Name() + ":") call.Call.Writer.Flush() return nil |