diff options
-rw-r--r-- | docs/source/markdown/podman-auto-update.1.md | 6 | ||||
-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 | ||||
-rw-r--r-- | test/system/030-run.bats | 4 | ||||
-rw-r--r-- | test/system/160-volumes.bats | 13 | ||||
-rw-r--r-- | test/system/250-systemd.bats | 7 |
12 files changed, 212 insertions, 52 deletions
diff --git a/docs/source/markdown/podman-auto-update.1.md b/docs/source/markdown/podman-auto-update.1.md index f98b71420..435a767c1 100644 --- a/docs/source/markdown/podman-auto-update.1.md +++ b/docs/source/markdown/podman-auto-update.1.md @@ -13,6 +13,8 @@ If the label is present and set to "image", Podman reaches out to the correspond An image is considered updated if the digest in the local storage is different than the one of the remote image. If an image must be updated, Podman pulls it down and restarts the systemd unit executing the container. +If "io.containers.autoupdate.authfile" label is present, Podman reaches out to corresponding authfile when pulling images. + At container-creation time, Podman looks up the "PODMAN_SYSTEMD_UNIT" environment variables and stores it verbatim in the container's label. This variable is now set by all systemd units generated by `podman-generate-systemd` and is set to `%n` (i.e., the name of systemd unit starting the container). This data is then being used in the auto-update sequence to instruct systemd (via DBUS) to restart the unit and hence to restart the container. @@ -35,7 +37,9 @@ environment variable. `export REGISTRY_AUTH_FILE=path` ``` # Start a container -$ podman run --label "io.containers.autoupdate=image" -d busybox:latest top +$ podman run --label "io.containers.autoupdate=image" \ + --label "io.containers.autoupdate.autfile=/some/authfile.json" \ + -d busybox:latest top bc219740a210455fa27deacc96d50a9e20516492f1417507c13ce1533dbdcd9d # Generate a systemd unit for this container 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 diff --git a/test/system/030-run.bats b/test/system/030-run.bats index ae2e39d6b..1bcf3896f 100644 --- a/test/system/030-run.bats +++ b/test/system/030-run.bats @@ -8,8 +8,8 @@ load helpers # 2019-09 Fedora 31 and rawhide (32) are switching from runc to crun # because of cgroups v2; crun emits different error messages. # Default to runc: - err_no_such_cmd="Error: .*: starting container process caused .*exec:.*stat /no/such/command: no such file or directory" - err_no_exec_dir="Error: .*: starting container process caused .*exec:.* permission denied" + err_no_such_cmd="Error: .*: starting container process caused.*exec:.*stat /no/such/command: no such file or directory" + err_no_exec_dir="Error: .*: starting container process caused.*exec:.* permission denied" # ...but check the configured runtime engine, and switch to crun as needed run_podman info --format '{{ .Host.OCIRuntime.Path }}' diff --git a/test/system/160-volumes.bats b/test/system/160-volumes.bats index cd9f3c8ad..5d65a950f 100644 --- a/test/system/160-volumes.bats +++ b/test/system/160-volumes.bats @@ -116,8 +116,17 @@ EOF chmod 755 $mountpoint/myscript # By default, volumes are mounted noexec. This should fail. - run_podman 126 run --rm --volume $myvolume:/vol:z $IMAGE /vol/myscript - is "$output" ".* OCI runtime permission denied.*" "run on volume, noexec" + # ARGH. Unfortunately, runc (used for cgroups v1) produces a different error + local expect_rc=126 + local expect_msg='.* OCI runtime permission denied.*' + run_podman info --format '{{ .Host.OCIRuntime.Path }}' + if expr "$output" : ".*/runc"; then + expect_rc=1 + expect_msg='.* exec user process caused.*permission denied' + fi + + run_podman ${expect_rc} run --rm --volume $myvolume:/vol:z $IMAGE /vol/myscript + is "$output" "$expect_msg" "run on volume, noexec" # With exec, it should pass run_podman run --rm -v $myvolume:/vol:z,exec $IMAGE /vol/myscript diff --git a/test/system/250-systemd.bats b/test/system/250-systemd.bats index cdac43c1c..4bee13414 100644 --- a/test/system/250-systemd.bats +++ b/test/system/250-systemd.bats @@ -33,6 +33,13 @@ function teardown() { # This test can fail in dev. environment because of SELinux. # quick fix: chcon -t container_runtime_exec_t ./bin/podman @test "podman generate - systemd - basic" { + # podman initializes this if unset, but systemctl doesn't + if is_rootless; then + if [ -z "$XDG_RUNTIME_DIR" ]; then + export XDG_RUNTIME_DIR=/run/user/$(id -u) + fi + fi + cname=$(random_string) run_podman create --name $cname --detach $IMAGE top |