diff options
Diffstat (limited to 'pkg')
-rw-r--r-- | pkg/adapter/containers.go | 173 | ||||
-rw-r--r-- | pkg/adapter/containers_remote.go | 273 | ||||
-rw-r--r-- | pkg/adapter/runtime.go | 65 | ||||
-rw-r--r-- | pkg/adapter/shortcuts/shortcuts.go | 39 | ||||
-rw-r--r-- | pkg/spec/spec.go | 3 | ||||
-rw-r--r-- | pkg/varlinkapi/attach.go | 75 | ||||
-rw-r--r-- | pkg/varlinkapi/containers.go | 65 | ||||
-rw-r--r-- | pkg/varlinkapi/util.go | 33 | ||||
-rw-r--r-- | pkg/varlinkapi/virtwriter/virtwriter.go | 155 |
9 files changed, 830 insertions, 51 deletions
diff --git a/pkg/adapter/containers.go b/pkg/adapter/containers.go index 1bca99cec..a9b3232e7 100644 --- a/pkg/adapter/containers.go +++ b/pkg/adapter/containers.go @@ -18,6 +18,7 @@ import ( "github.com/containers/libpod/cmd/podman/shared" "github.com/containers/libpod/libpod" "github.com/containers/libpod/pkg/adapter/shortcuts" + "github.com/containers/storage" "github.com/pkg/errors" "github.com/sirupsen/logrus" ) @@ -62,52 +63,144 @@ func (r *LocalRuntime) StopContainers(ctx context.Context, cli *cliconfig.StopVa timeout = &t } - var ( - ok = []string{} - failures = map[string]error{} - ) + maxWorkers := shared.DefaultPoolSize("stop") + if cli.GlobalIsSet("max-workers") { + maxWorkers = cli.GlobalFlags.MaxWorks + } + logrus.Debugf("Setting maximum stop workers to %d", maxWorkers) ctrs, err := shortcuts.GetContainersByContext(cli.All, cli.Latest, cli.InputArgs, r.Runtime) if err != nil { - return ok, failures, err + return nil, nil, err } + pool := shared.NewPool("stop", maxWorkers, len(ctrs)) for _, c := range ctrs { + c := c + if timeout == nil { t := c.StopTimeout() timeout = &t logrus.Debugf("Set timeout to container %s default (%d)", c.ID(), *timeout) } - if err := c.StopWithTimeout(*timeout); err == nil { - ok = append(ok, c.ID()) - } else if errors.Cause(err) == libpod.ErrCtrStopped { - ok = append(ok, c.ID()) - logrus.Debugf("Container %s is already stopped", c.ID()) - } else { - failures[c.ID()] = err - } + + pool.Add(shared.Job{ + c.ID(), + func() error { + err := c.StopWithTimeout(*timeout) + if err != nil { + if errors.Cause(err) == libpod.ErrCtrStopped { + logrus.Debugf("Container %s is already stopped", c.ID()) + return nil + } + logrus.Debugf("Failed to stop container %s: %s", c.ID(), err.Error()) + } + return err + }, + }) } - return ok, failures, nil + return pool.Run() } // KillContainers sends signal to container(s) based on CLI inputs. // Returns list of successful id(s), map of failed id(s) + error, or error not from container func (r *LocalRuntime) KillContainers(ctx context.Context, cli *cliconfig.KillValues, signal syscall.Signal) ([]string, map[string]error, error) { + maxWorkers := shared.DefaultPoolSize("kill") + if cli.GlobalIsSet("max-workers") { + maxWorkers = cli.GlobalFlags.MaxWorks + } + logrus.Debugf("Setting maximum kill workers to %d", maxWorkers) + + ctrs, err := shortcuts.GetContainersByContext(cli.All, cli.Latest, cli.InputArgs, r.Runtime) + if err != nil { + return nil, nil, err + } + + pool := shared.NewPool("kill", maxWorkers, len(ctrs)) + for _, c := range ctrs { + c := c + + pool.Add(shared.Job{ + c.ID(), + func() error { + return c.Kill(uint(signal)) + }, + }) + } + return pool.Run() +} + +// RemoveContainers removes container(s) based on CLI inputs. +func (r *LocalRuntime) RemoveContainers(ctx context.Context, cli *cliconfig.RmValues) ([]string, map[string]error, error) { var ( ok = []string{} failures = map[string]error{} ) + maxWorkers := shared.DefaultPoolSize("rm") + if cli.GlobalIsSet("max-workers") { + maxWorkers = cli.GlobalFlags.MaxWorks + } + logrus.Debugf("Setting maximum rm workers to %d", maxWorkers) + ctrs, err := shortcuts.GetContainersByContext(cli.All, cli.Latest, cli.InputArgs, r.Runtime) if err != nil { + // Force may be used to remove containers no longer found in the database + if cli.Force && len(cli.InputArgs) > 0 && errors.Cause(err) == libpod.ErrNoSuchCtr { + r.RemoveContainersFromStorage(cli.InputArgs) + } return ok, failures, err } + pool := shared.NewPool("rm", maxWorkers, len(ctrs)) for _, c := range ctrs { - if err := c.Kill(uint(signal)); err == nil { - ok = append(ok, c.ID()) + c := c + + pool.Add(shared.Job{ + c.ID(), + func() error { + err := r.RemoveContainer(ctx, c, cli.Force, cli.Volumes) + if err != nil { + logrus.Debugf("Failed to remove container %s: %s", c.ID(), err.Error()) + } + return err + }, + }) + } + return pool.Run() +} + +// UmountRootFilesystems removes container(s) based on CLI inputs. +func (r *LocalRuntime) UmountRootFilesystems(ctx context.Context, cli *cliconfig.UmountValues) ([]string, map[string]error, error) { + var ( + ok = []string{} + failures = map[string]error{} + ) + + ctrs, err := shortcuts.GetContainersByContext(cli.All, cli.Latest, cli.InputArgs, r.Runtime) + if err != nil { + return ok, failures, err + } + + for _, ctr := range ctrs { + state, err := ctr.State() + if err != nil { + logrus.Debugf("Error umounting container %s state: %s", ctr.ID(), err.Error()) + continue + } + if state == libpod.ContainerStateRunning { + logrus.Debugf("Error umounting container %s, is running", ctr.ID()) + continue + } + + if err := ctr.Unmount(cli.Force); err != nil { + if cli.All && errors.Cause(err) == storage.ErrLayerNotMounted { + logrus.Debugf("Error umounting container %s, storage.ErrLayerNotMounted", ctr.ID()) + continue + } + failures[ctr.ID()] = errors.Wrapf(err, "error unmounting continaner %s", ctr.ID()) } else { - failures[c.ID()] = err + ok = append(ok, ctr.ID()) } } return ok, failures, nil @@ -304,3 +397,49 @@ func ReadExitFile(runtimeTmp, ctrID string) (int, error) { return exitCode, nil } + +// Ps ... +func (r *LocalRuntime) Ps(c *cliconfig.PsValues, opts shared.PsOptions) ([]shared.PsContainerOutput, error) { + maxWorkers := shared.Parallelize("ps") + if c.GlobalIsSet("max-workers") { + maxWorkers = c.GlobalFlags.MaxWorks + } + logrus.Debugf("Setting maximum workers to %d", maxWorkers) + return shared.GetPsContainerOutput(r.Runtime, opts, c.Filter, maxWorkers) +} + +// Attach ... +func (r *LocalRuntime) Attach(ctx context.Context, c *cliconfig.AttachValues) error { + var ( + ctr *libpod.Container + err error + ) + + if c.Latest { + ctr, err = r.Runtime.GetLatestContainer() + } else { + ctr, err = r.Runtime.LookupContainer(c.InputArgs[0]) + } + + if err != nil { + return errors.Wrapf(err, "unable to exec into %s", c.InputArgs[0]) + } + + conState, err := ctr.State() + if err != nil { + return errors.Wrapf(err, "unable to determine state of %s", ctr.ID()) + } + if conState != libpod.ContainerStateRunning { + return errors.Errorf("you can only attach to running containers") + } + + inputStream := os.Stdin + if c.NoStdin { + inputStream = nil + } + // If the container is in a pod, also set to recursively start dependencies + if err := StartAttachCtr(ctx, ctr, os.Stdout, os.Stderr, inputStream, c.DetachKeys, c.SigProxy, false, ctr.PodID() != ""); err != nil && errors.Cause(err) != libpod.ErrDetach { + return errors.Wrapf(err, "error attaching to container %s", ctr.ID()) + } + return nil +} diff --git a/pkg/adapter/containers_remote.go b/pkg/adapter/containers_remote.go index 3730827c7..1ae39749f 100644 --- a/pkg/adapter/containers_remote.go +++ b/pkg/adapter/containers_remote.go @@ -6,18 +6,25 @@ import ( "context" "encoding/json" "fmt" + "io" + "os" "strconv" "syscall" "time" "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/cmd/podman/shared" - "github.com/containers/libpod/cmd/podman/varlink" + iopodman "github.com/containers/libpod/cmd/podman/varlink" "github.com/containers/libpod/libpod" "github.com/containers/libpod/pkg/inspect" + "github.com/containers/libpod/pkg/varlinkapi/virtwriter" + "github.com/docker/docker/pkg/term" + "github.com/opencontainers/runtime-spec/specs-go" "github.com/pkg/errors" "github.com/sirupsen/logrus" "github.com/varlink/go/varlink" + "golang.org/x/crypto/ssh/terminal" + "k8s.io/client-go/tools/remotecommand" ) // Inspect returns an inspect struct from varlink @@ -70,6 +77,19 @@ func (r *LocalRuntime) ContainerState(name string) (*libpod.ContainerState, erro } +// Spec obtains the container spec. +func (r *LocalRuntime) Spec(name string) (*specs.Spec, error) { + reply, err := iopodman.Spec().Call(r.Conn, name) + if err != nil { + return nil, err + } + data := specs.Spec{} + if err := json.Unmarshal([]byte(reply), &data); err != nil { + return nil, err + } + return &data, nil +} + // LookupContainer gets basic information about container over a varlink // connection and then translates it to a *Container func (r *LocalRuntime) LookupContainer(idOrName string) (*Container, error) { @@ -78,10 +98,6 @@ func (r *LocalRuntime) LookupContainer(idOrName string) (*Container, error) { return nil, err } config := r.Config(idOrName) - if err != nil { - return nil, err - } - return &Container{ remoteContainer{ r, @@ -128,7 +144,7 @@ func (c *Container) Name() string { return c.config.Name } -// StopContainers stops requested containers using CLI inputs. +// StopContainers stops requested containers using varlink. // Returns the list of stopped container ids, map of failed to stop container ids + errors, or any non-container error func (r *LocalRuntime) StopContainers(ctx context.Context, cli *cliconfig.StopValues) ([]string, map[string]error, error) { var ( @@ -152,7 +168,7 @@ func (r *LocalRuntime) StopContainers(ctx context.Context, cli *cliconfig.StopVa return ok, failures, nil } -// KillContainers sends signal to container(s) based on CLI inputs. +// KillContainers sends signal to container(s) based on varlink. // Returns list of successful id(s), map of failed id(s) + error, or error not from container func (r *LocalRuntime) KillContainers(ctx context.Context, cli *cliconfig.KillValues, signal syscall.Signal) ([]string, map[string]error, error) { var ( @@ -176,6 +192,52 @@ func (r *LocalRuntime) KillContainers(ctx context.Context, cli *cliconfig.KillVa return ok, failures, nil } +// RemoveContainer removes container(s) based on varlink inputs. +func (r *LocalRuntime) RemoveContainers(ctx context.Context, cli *cliconfig.RmValues) ([]string, map[string]error, error) { + ids, err := iopodman.GetContainersByContext().Call(r.Conn, cli.All, cli.Latest, cli.InputArgs) + if err != nil { + return nil, nil, err + } + + var ( + ok = []string{} + failures = map[string]error{} + ) + + for _, id := range ids { + _, err := iopodman.RemoveContainer().Call(r.Conn, id, cli.Force, cli.Volumes) + if err != nil { + failures[id] = err + } else { + ok = append(ok, id) + } + } + return ok, failures, nil +} + +// UmountRootFilesystems umounts container(s) root filesystems based on varlink inputs +func (r *LocalRuntime) UmountRootFilesystems(ctx context.Context, cli *cliconfig.UmountValues) ([]string, map[string]error, error) { + ids, err := iopodman.GetContainersByContext().Call(r.Conn, cli.All, cli.Latest, cli.InputArgs) + if err != nil { + return nil, nil, err + } + + var ( + ok = []string{} + failures = map[string]error{} + ) + + for _, id := range ids { + err := iopodman.UnmountContainer().Call(r.Conn, id, cli.Force) + if err != nil { + failures[id] = err + } else { + ok = append(ok, id) + } + } + return ok, failures, nil +} + // WaitOnContainers waits for all given container(s) to stop. // interval is currently ignored. func (r *LocalRuntime) WaitOnContainers(ctx context.Context, cli *cliconfig.WaitValues, interval time.Duration) ([]string, map[string]error, error) { @@ -227,7 +289,7 @@ func BatchContainerOp(ctr *Container, opts shared.PsOptions) (shared.BatchContai // Logs one or more containers over a varlink connection func (r *LocalRuntime) Log(c *cliconfig.LogsValues, options *libpod.LogOptions) error { - //GetContainersLogs + // GetContainersLogs reply, err := iopodman.GetContainersLogs().Send(r.Conn, uint64(varlink.More), c.InputArgs, c.Follow, c.Latest, options.Since.Format(time.RFC3339Nano), int64(c.Tail), c.Timestamps) if err != nil { return errors.Wrapf(err, "failed to get container logs") @@ -275,20 +337,205 @@ func (r *LocalRuntime) CreateContainer(ctx context.Context, c *cliconfig.CreateV // Run creates a container overvarlink and then starts it func (r *LocalRuntime) Run(ctx context.Context, c *cliconfig.RunValues, exitCode int) (int, error) { + // FIXME + // podman-remote run -it alpine ls DOES NOT WORK YET + // podman-remote run -it alpine /bin/sh does, i suspect there is some sort of + // timing issue between the socket availability and terminal setup and the command + // being run. + // TODO the exit codes for run need to be figured out for remote connections - if !c.Bool("detach") { - return 0, errors.New("the remote client only supports detached containers") - } results := shared.NewIntermediateLayer(&c.PodmanCommand) cid, err := iopodman.CreateContainer().Call(r.Conn, results.MakeVarlink()) if err != nil { return 0, err } - fmt.Println(cid) _, err = iopodman.StartContainer().Call(r.Conn, cid) - return 0, err + if err != nil { + return 0, err + } + errChan, err := r.attach(ctx, os.Stdin, os.Stdout, cid) + if err != nil { + return 0, err + } + if c.Bool("detach") { + fmt.Println(cid) + return 0, err + } + finalError := <-errChan + return 0, finalError } func ReadExitFile(runtimeTmp, ctrID string) (int, error) { return 0, libpod.ErrNotImplemented } + +// Ps ... +func (r *LocalRuntime) Ps(c *cliconfig.PsValues, opts shared.PsOptions) ([]shared.PsContainerOutput, error) { + var psContainers []shared.PsContainerOutput + last := int64(c.Last) + PsOpts := iopodman.PsOpts{ + All: c.All, + Filters: &c.Filter, + Last: &last, + Latest: &c.Latest, + NoTrunc: &c.NoTrunct, + Pod: &c.Pod, + Quiet: &c.Quiet, + Sort: &c.Sort, + Sync: &c.Sync, + } + containers, err := iopodman.Ps().Call(r.Conn, PsOpts) + if err != nil { + return nil, err + } + for _, ctr := range containers { + createdAt, err := time.Parse(time.RFC3339Nano, ctr.CreatedAt) + if err != nil { + return nil, err + } + exitedAt, err := time.Parse(time.RFC3339Nano, ctr.ExitedAt) + if err != nil { + return nil, err + } + startedAt, err := time.Parse(time.RFC3339Nano, ctr.StartedAt) + if err != nil { + return nil, err + } + containerSize := shared.ContainerSize{ + RootFsSize: ctr.RootFsSize, + RwSize: ctr.RwSize, + } + state, err := libpod.StringToContainerStatus(ctr.State) + if err != nil { + return nil, err + } + psc := shared.PsContainerOutput{ + ID: ctr.Id, + Image: ctr.Image, + Command: ctr.Command, + Created: ctr.Created, + Ports: ctr.Ports, + Names: ctr.Names, + IsInfra: ctr.IsInfra, + Status: ctr.Status, + State: state, + Pid: int(ctr.PidNum), + Size: &containerSize, + Pod: ctr.Pod, + CreatedAt: createdAt, + ExitedAt: exitedAt, + StartedAt: startedAt, + Labels: ctr.Labels, + PID: ctr.NsPid, + Cgroup: ctr.Cgroup, + IPC: ctr.Ipc, + MNT: ctr.Mnt, + NET: ctr.Net, + PIDNS: ctr.PidNs, + User: ctr.User, + UTS: ctr.Uts, + Mounts: ctr.Mounts, + } + psContainers = append(psContainers, psc) + } + return psContainers, nil +} + +func (r *LocalRuntime) attach(ctx context.Context, stdin, stdout *os.File, cid string) (chan error, error) { + var ( + oldTermState *term.State + ) + errChan := make(chan error) + spec, err := r.Spec(cid) + if err != nil { + return nil, err + } + resize := make(chan remotecommand.TerminalSize) + haveTerminal := terminal.IsTerminal(int(os.Stdin.Fd())) + + // Check if we are attached to a terminal. If we are, generate resize + // events, and set the terminal to raw mode + if haveTerminal && spec.Process.Terminal { + logrus.Debugf("Handling terminal attach") + + subCtx, cancel := context.WithCancel(ctx) + defer cancel() + + resizeTty(subCtx, resize) + oldTermState, err = term.SaveState(os.Stdin.Fd()) + if err != nil { + return nil, errors.Wrapf(err, "unable to save terminal state") + } + + logrus.SetFormatter(&RawTtyFormatter{}) + term.SetRawTerminal(os.Stdin.Fd()) + + } + + _, err = iopodman.Attach().Send(r.Conn, varlink.Upgrade, cid) + if err != nil { + restoreTerminal(oldTermState) + return nil, err + } + + // These are the varlink sockets + reader := r.Conn.Reader + writer := r.Conn.Writer + + // These are the special writers that encode input from the client. + varlinkStdinWriter := virtwriter.NewVirtWriteCloser(writer, virtwriter.ToStdin) + varlinkResizeWriter := virtwriter.NewVirtWriteCloser(writer, virtwriter.TerminalResize) + + go func() { + // Read from the wire and direct to stdout or stderr + err := virtwriter.Reader(reader, stdout, os.Stderr, nil, nil) + defer restoreTerminal(oldTermState) + errChan <- err + }() + + go func() { + for termResize := range resize { + b, err := json.Marshal(termResize) + if err != nil { + defer restoreTerminal(oldTermState) + errChan <- err + } + _, err = varlinkResizeWriter.Write(b) + if err != nil { + defer restoreTerminal(oldTermState) + errChan <- err + } + } + }() + + // Takes stdinput and sends it over the wire after being encoded + go func() { + if _, err := io.Copy(varlinkStdinWriter, stdin); err != nil { + defer restoreTerminal(oldTermState) + errChan <- err + } + + }() + return errChan, nil + +} + +// Attach to a remote terminal +func (r *LocalRuntime) Attach(ctx context.Context, c *cliconfig.AttachValues) error { + ctr, err := r.LookupContainer(c.InputArgs[0]) + if err != nil { + return nil + } + if ctr.state.State != libpod.ContainerStateRunning { + return errors.New("you can only attach to running containers") + } + inputStream := os.Stdin + if c.NoStdin { + inputStream = nil + } + errChan, err := r.attach(ctx, inputStream, os.Stdout, c.InputArgs[0]) + if err != nil { + return err + } + return <-errChan +} diff --git a/pkg/adapter/runtime.go b/pkg/adapter/runtime.go index 182a04044..d45bdb56d 100644 --- a/pkg/adapter/runtime.go +++ b/pkg/adapter/runtime.go @@ -310,6 +310,46 @@ func (r *LocalRuntime) HealthCheck(c *cliconfig.HealthCheckValues) (libpod.Healt return r.Runtime.HealthCheck(c.InputArgs[0]) } +// JoinOrCreateRootlessPod joins the specified pod if it is running or it creates a new user namespace +// if the pod is stopped +// func (r *LocalRuntime) JoinOrCreateRootlessPod(pod *Pod) (bool, int, error) { +// if os.Geteuid() == 0 { +// return false, 0, nil +// } +// opts := rootless.Opts{ +// Argument: pod.ID(), +// } +// +// inspect, err := pod.Inspect() +// if err != nil { +// return false, 0, err +// } +// for _, ctr := range inspect.Containers { +// prevCtr, err := r.LookupContainer(ctr.ID) +// if err != nil { +// return false, -1, err +// } +// s, err := prevCtr.State() +// if err != nil { +// return false, -1, err +// } +// if s != libpod.ContainerStateRunning && s != libpod.ContainerStatePaused { +// continue +// } +// data, err := ioutil.ReadFile(prevCtr.Config().ConmonPidFile) +// if err != nil { +// return false, -1, errors.Wrapf(err, "cannot read conmon PID file %q", prevCtr.Config().ConmonPidFile) +// } +// conmonPid, err := strconv.Atoi(string(data)) +// if err != nil { +// return false, -1, errors.Wrapf(err, "cannot parse PID %q", data) +// } +// return rootless.JoinDirectUserAndMountNSWithOpts(uint(conmonPid), &opts) +// } +// +// return rootless.BecomeRootInUserNSWithOpts(&opts) +// } + // Events is a wrapper to libpod to obtain libpod/podman events func (r *LocalRuntime) Events(c *cliconfig.EventValues) error { var ( @@ -363,3 +403,28 @@ func (r *LocalRuntime) Events(c *cliconfig.EventValues) error { func (r *LocalRuntime) Diff(c *cliconfig.DiffValues, to string) ([]archive.Change, error) { return r.Runtime.GetDiff("", to) } + +// func (r *LocalRuntime) joinContainerOrCreateRootlessUserNS(ctr *libpod.Container) (bool, int, error) { +// if os.Geteuid() == 0 { +// return false, 0, nil +// } +// s, err := ctr.State() +// if err != nil { +// return false, -1, err +// } +// opts := rootless.Opts{ +// Argument: ctr.ID(), +// } +// if s == libpod.ContainerStateRunning || s == libpod.ContainerStatePaused { +// data, err := ioutil.ReadFile(ctr.Config().ConmonPidFile) +// if err != nil { +// return false, -1, errors.Wrapf(err, "Container %s cannot read conmon PID file %q", ctr.ID(), ctr.Config().ConmonPidFile) +// } +// conmonPid, err := strconv.Atoi(string(data)) +// if err != nil { +// return false, -1, errors.Wrapf(err, "Container %s cannot parse PID %q", ctr.ID(), data) +// } +// return rootless.JoinDirectUserAndMountNSWithOpts(uint(conmonPid), &opts) +// } +// return rootless.BecomeRootInUserNSWithOpts(&opts) +// } diff --git a/pkg/adapter/shortcuts/shortcuts.go b/pkg/adapter/shortcuts/shortcuts.go index 677d88457..3e4eff555 100644 --- a/pkg/adapter/shortcuts/shortcuts.go +++ b/pkg/adapter/shortcuts/shortcuts.go @@ -1,6 +1,8 @@ package shortcuts -import "github.com/containers/libpod/libpod" +import ( + "github.com/containers/libpod/libpod" +) // GetPodsByContext gets pods whether all, latest, or a slice of names/ids func GetPodsByContext(all, latest bool, pods []string, runtime *libpod.Runtime) ([]*libpod.Pod, error) { @@ -27,28 +29,23 @@ func GetPodsByContext(all, latest bool, pods []string, runtime *libpod.Runtime) } // GetContainersByContext gets pods whether all, latest, or a slice of names/ids -func GetContainersByContext(all, latest bool, names []string, runtime *libpod.Runtime) ([]*libpod.Container, error) { - var ctrs = []*libpod.Container{} +func GetContainersByContext(all, latest bool, names []string, runtime *libpod.Runtime) (ctrs []*libpod.Container, err error) { + var ctr *libpod.Container + ctrs = []*libpod.Container{} if all { - return runtime.GetAllContainers() - } - - if latest { - c, err := runtime.GetLatestContainer() - if err != nil { - return nil, err - } - ctrs = append(ctrs, c) - return ctrs, nil - } - - for _, c := range names { - ctr, err := runtime.LookupContainer(c) - if err != nil { - return nil, err - } + ctrs, err = runtime.GetAllContainers() + } else if latest { + ctr, err = runtime.GetLatestContainer() ctrs = append(ctrs, ctr) + } else { + for _, n := range names { + ctr, e := runtime.LookupContainer(n) + if e != nil && err == nil { + err = e + } + ctrs = append(ctrs, ctr) + } } - return ctrs, nil + return } diff --git a/pkg/spec/spec.go b/pkg/spec/spec.go index 9b6bd089e..0371b6d4d 100644 --- a/pkg/spec/spec.go +++ b/pkg/spec/spec.go @@ -132,6 +132,9 @@ func CreateConfigToOCISpec(config *CreateConfig) (*spec.Spec, error) { //nolint Options: []string{"rprivate", "nosuid", "noexec", "nodev", r, "rbind"}, } g.AddMount(sysMnt) + if !config.Privileged && isRootless { + g.AddLinuxMaskedPaths("/sys/kernel") + } } if isRootless { nGids, err := getAvailableGids() diff --git a/pkg/varlinkapi/attach.go b/pkg/varlinkapi/attach.go new file mode 100644 index 000000000..53c4d1ff6 --- /dev/null +++ b/pkg/varlinkapi/attach.go @@ -0,0 +1,75 @@ +// +build varlink + +package varlinkapi + +import ( + "io" + + "github.com/containers/libpod/cmd/podman/varlink" + "github.com/containers/libpod/libpod" + "github.com/containers/libpod/pkg/varlinkapi/virtwriter" + "k8s.io/client-go/tools/remotecommand" +) + +// Close is method to close the writer + +// Attach ... +func (i *LibpodAPI) Attach(call iopodman.VarlinkCall, name string) error { + var finalErr error + resize := make(chan remotecommand.TerminalSize) + errChan := make(chan error) + + if !call.WantsUpgrade() { + return call.ReplyErrorOccurred("client must use upgraded connection to attach") + } + ctr, err := i.Runtime.LookupContainer(name) + if err != nil { + return call.ReplyErrorOccurred(err.Error()) + } + + // These are the varlink sockets + reader := call.Call.Reader + writer := call.Call.Writer + + // This pipe is used to pass stdin from the client to the input stream + // once the msg has been "decoded" + pr, pw := io.Pipe() + + stdoutWriter := virtwriter.NewVirtWriteCloser(writer, virtwriter.ToStdout) + // TODO if runc ever starts passing stderr, we can too + //stderrWriter := NewVirtWriteCloser(writer, ToStderr) + + streams := libpod.AttachStreams{ + OutputStream: stdoutWriter, + InputStream: pr, + // Runc eats the error stream + ErrorStream: stdoutWriter, + AttachInput: true, + AttachOutput: true, + // Runc eats the error stream + AttachError: true, + } + + go func() { + if err := virtwriter.Reader(reader, nil, nil, pw, resize); err != nil { + errChan <- err + } + }() + + go func() { + // TODO allow for customizable detach keys + if err := ctr.Attach(&streams, "", resize); err != nil { + errChan <- err + } + }() + + select { + // Blocking on an error + case finalErr = <-errChan: + // Need to close up shop + _ = finalErr + } + quitWriter := virtwriter.NewVirtWriteCloser(writer, virtwriter.Quit) + _, err = quitWriter.Write([]byte("HANG-UP")) + return call.Writer.Flush() +} diff --git a/pkg/varlinkapi/containers.go b/pkg/varlinkapi/containers.go index ac1352dac..17792ccfe 100644 --- a/pkg/varlinkapi/containers.go +++ b/pkg/varlinkapi/containers.go @@ -47,6 +47,55 @@ func (i *LibpodAPI) ListContainers(call iopodman.VarlinkCall) error { return call.ReplyListContainers(listContainers) } +func (i *LibpodAPI) Ps(call iopodman.VarlinkCall, opts iopodman.PsOpts) error { + var ( + containers []iopodman.PsContainer + ) + maxWorkers := shared.Parallelize("ps") + psOpts := makePsOpts(opts) + filters := []string{} + if opts.Filters != nil { + filters = *opts.Filters + } + psContainerOutputs, err := shared.GetPsContainerOutput(i.Runtime, psOpts, filters, maxWorkers) + if err != nil { + return call.ReplyErrorOccurred(err.Error()) + } + + for _, ctr := range psContainerOutputs { + container := iopodman.PsContainer{ + Id: ctr.ID, + Image: ctr.Image, + Command: ctr.Command, + Created: ctr.Created, + Ports: ctr.Ports, + Names: ctr.Names, + IsInfra: ctr.IsInfra, + Status: ctr.Status, + State: ctr.State.String(), + PidNum: int64(ctr.Pid), + RootFsSize: ctr.Size.RootFsSize, + RwSize: ctr.Size.RwSize, + Pod: ctr.Pod, + CreatedAt: ctr.CreatedAt.Format(time.RFC3339Nano), + ExitedAt: ctr.ExitedAt.Format(time.RFC3339Nano), + StartedAt: ctr.StartedAt.Format(time.RFC3339Nano), + Labels: ctr.Labels, + NsPid: ctr.PID, + Cgroup: ctr.Cgroup, + Ipc: ctr.Cgroup, + Mnt: ctr.MNT, + Net: ctr.NET, + PidNs: ctr.PIDNS, + User: ctr.User, + Uts: ctr.UTS, + Mounts: ctr.Mounts, + } + containers = append(containers, container) + } + return call.ReplyPs(containers) +} + // GetContainer ... func (i *LibpodAPI) GetContainer(call iopodman.VarlinkCall, id string) error { ctr, err := i.Runtime.LookupContainer(id) @@ -585,6 +634,22 @@ func (i *LibpodAPI) GetContainerStatsWithHistory(call iopodman.VarlinkCall, prev return call.ReplyGetContainerStatsWithHistory(cStats) } +// Spec ... +func (i *LibpodAPI) Spec(call iopodman.VarlinkCall, name string) error { + ctr, err := i.Runtime.LookupContainer(name) + if err != nil { + return call.ReplyErrorOccurred(err.Error()) + } + + spec := ctr.Spec() + b, err := json.Marshal(spec) + if err != nil { + return call.ReplyErrorOccurred(err.Error()) + } + + return call.ReplySpec(string(b)) +} + // GetContainersLogs is the varlink endpoint to obtain one or more container logs func (i *LibpodAPI) GetContainersLogs(call iopodman.VarlinkCall, names []string, follow, latest bool, since string, tail int64, timestamps bool) error { var wg sync.WaitGroup diff --git a/pkg/varlinkapi/util.go b/pkg/varlinkapi/util.go index 3c4b9b79a..8716c963a 100644 --- a/pkg/varlinkapi/util.go +++ b/pkg/varlinkapi/util.go @@ -162,3 +162,36 @@ func stringPullPolicyToType(s string) buildah.PullPolicy { } return buildah.PullIfMissing } + +func derefBool(inBool *bool) bool { + if inBool == nil { + return false + } + return *inBool +} + +func derefString(in *string) string { + if in == nil { + return "" + } + return *in +} + +func makePsOpts(inOpts iopodman.PsOpts) shared.PsOptions { + last := 0 + if inOpts.Last != nil { + lastT := *inOpts.Last + last = int(lastT) + } + return shared.PsOptions{ + All: inOpts.All, + Last: last, + Latest: derefBool(inOpts.Latest), + NoTrunc: derefBool(inOpts.NoTrunc), + Pod: derefBool(inOpts.Pod), + Size: true, + Sort: derefString(inOpts.Sort), + Namespace: true, + Sync: derefBool(inOpts.Sync), + } +} diff --git a/pkg/varlinkapi/virtwriter/virtwriter.go b/pkg/varlinkapi/virtwriter/virtwriter.go new file mode 100644 index 000000000..3adaf6e17 --- /dev/null +++ b/pkg/varlinkapi/virtwriter/virtwriter.go @@ -0,0 +1,155 @@ +package virtwriter + +import ( + "bufio" + "encoding/binary" + "encoding/json" + "errors" + "io" + "os" + + "k8s.io/client-go/tools/remotecommand" +) + +// SocketDest is the "key" to where IO should go on the varlink +// multiplexed socket +type SocketDest int + +const ( + // ToStdout indicates traffic should go stdout + ToStdout SocketDest = iota + // ToStdin indicates traffic came from stdin + ToStdin SocketDest = iota + // ToStderr indicates traffuc should go to stderr + ToStderr SocketDest = iota + // TerminalResize indicates a terminal resize event has occurred + // and data should be passed to resizer + TerminalResize SocketDest = iota + // Quit and detach + Quit SocketDest = iota +) + +// IntToSocketDest returns a socketdest based on integer input +func IntToSocketDest(i int) SocketDest { + switch i { + case ToStdout.Int(): + return ToStdout + case ToStderr.Int(): + return ToStderr + case ToStdin.Int(): + return ToStdin + case TerminalResize.Int(): + return TerminalResize + case Quit.Int(): + return Quit + default: + return ToStderr + } +} + +// Int returns the integer representation of the socket dest +func (sd SocketDest) Int() int { + return int(sd) +} + +// VirtWriteCloser are writers for attach which include the dest +// of the data +type VirtWriteCloser struct { + writer *bufio.Writer + dest SocketDest +} + +// NewVirtWriteCloser is a constructor +func NewVirtWriteCloser(w *bufio.Writer, dest SocketDest) VirtWriteCloser { + return VirtWriteCloser{w, dest} +} + +// Close is a required method for a writecloser +func (v VirtWriteCloser) Close() error { + return nil +} + +// Write prepends a header to the input message. The header is +// 8bytes. Position one contains the destination. Positions +// 5,6,7,8 are a big-endian encoded uint32 for len of the message. +func (v VirtWriteCloser) Write(input []byte) (int, error) { + header := []byte{byte(v.dest), 0, 0, 0} + // Go makes us define the byte for big endian + mlen := make([]byte, 4) + binary.BigEndian.PutUint32(mlen, uint32(len(input))) + // append the message len to the header + msg := append(header, mlen...) + // append the message to the header + msg = append(msg, input...) + _, err := v.writer.Write(msg) + if err != nil { + return 0, err + } + err = v.writer.Flush() + return len(input), err +} + +// Reader decodes the content that comes over the wire and directs it to the proper destination. +func Reader(r *bufio.Reader, output, errput *os.File, input *io.PipeWriter, resize chan remotecommand.TerminalSize) error { + var saveb []byte + var eom int + for { + readb := make([]byte, 32*1024) + n, err := r.Read(readb) + // TODO, later may be worth checking in len of the read is 0 + if err != nil { + return err + } + b := append(saveb, readb[0:n]...) + // no sense in reading less than the header len + for len(b) > 7 { + eom = int(binary.BigEndian.Uint32(b[4:8])) + 8 + // The message and header are togther + if len(b) >= eom { + out := append([]byte{}, b[8:eom]...) + + switch IntToSocketDest(int(b[0])) { + case ToStdout: + n, err := output.Write(out) + if err != nil { + return err + } + if n < len(out) { + return errors.New("short write error occurred on stdout") + } + case ToStderr: + n, err := errput.Write(out) + if err != nil { + return err + } + if n < len(out) { + return errors.New("short write error occurred on stderr") + } + case ToStdin: + n, err := input.Write(out) + if err != nil { + return err + } + if n < len(out) { + return errors.New("short write error occurred on stdin") + } + case TerminalResize: + // Resize events come over in bytes, need to be reserialized + resizeEvent := remotecommand.TerminalSize{} + if err := json.Unmarshal(out, &resizeEvent); err != nil { + return err + } + resize <- resizeEvent + case Quit: + return nil + } + b = b[eom:] + } else { + // We do not have the header and full message, need to slurp again + saveb = b + break + } + } + } + return nil +} |