diff options
Diffstat (limited to 'pkg')
28 files changed, 1043 insertions, 738 deletions
diff --git a/pkg/adapter/containers.go b/pkg/adapter/containers.go index 932d209cd..1bca99cec 100644 --- a/pkg/adapter/containers.go +++ b/pkg/adapter/containers.go @@ -5,12 +5,17 @@ package adapter import ( "context" "fmt" + "io/ioutil" + "os" + "path/filepath" "strconv" + "strings" "sync" "syscall" "time" "github.com/containers/libpod/cmd/podman/cliconfig" + "github.com/containers/libpod/cmd/podman/shared" "github.com/containers/libpod/libpod" "github.com/containers/libpod/pkg/adapter/shortcuts" "github.com/pkg/errors" @@ -154,3 +159,148 @@ func (r *LocalRuntime) Log(c *cliconfig.LogsValues, options *libpod.LogOptions) } return nil } + +// CreateContainer creates a libpod container +func (r *LocalRuntime) CreateContainer(ctx context.Context, c *cliconfig.CreateValues) (string, error) { + results := shared.NewIntermediateLayer(&c.PodmanCommand) + ctr, _, err := shared.CreateContainer(ctx, &results, r.Runtime) + return ctr.ID(), err +} + +// Run a libpod container +func (r *LocalRuntime) Run(ctx context.Context, c *cliconfig.RunValues, exitCode int) (int, error) { + results := shared.NewIntermediateLayer(&c.PodmanCommand) + + ctr, createConfig, err := shared.CreateContainer(ctx, &results, r.Runtime) + if err != nil { + return exitCode, err + } + + if logrus.GetLevel() == logrus.DebugLevel { + cgroupPath, err := ctr.CGroupPath() + if err == nil { + logrus.Debugf("container %q has CgroupParent %q", ctr.ID(), cgroupPath) + } + } + + // Handle detached start + if createConfig.Detach { + // if the container was created as part of a pod, also start its dependencies, if any. + if err := ctr.Start(ctx, c.IsSet("pod")); err != nil { + // This means the command did not exist + exitCode = 127 + if strings.Index(err.Error(), "permission denied") > -1 { + exitCode = 126 + } + return exitCode, err + } + + fmt.Printf("%s\n", ctr.ID()) + exitCode = 0 + return exitCode, nil + } + + outputStream := os.Stdout + errorStream := os.Stderr + inputStream := os.Stdin + + // If -i is not set, clear stdin + if !c.Bool("interactive") { + inputStream = nil + } + + // If attach is set, clear stdin/stdout/stderr and only attach requested + if c.IsSet("attach") || c.IsSet("a") { + outputStream = nil + errorStream = nil + if !c.Bool("interactive") { + inputStream = nil + } + + attachTo := c.StringSlice("attach") + for _, stream := range attachTo { + switch strings.ToLower(stream) { + case "stdout": + outputStream = os.Stdout + case "stderr": + errorStream = os.Stderr + case "stdin": + inputStream = os.Stdin + default: + return exitCode, errors.Wrapf(libpod.ErrInvalidArg, "invalid stream %q for --attach - must be one of stdin, stdout, or stderr", stream) + } + } + } + // if the container was created as part of a pod, also start its dependencies, if any. + if err := StartAttachCtr(ctx, ctr, outputStream, errorStream, inputStream, c.String("detach-keys"), c.Bool("sig-proxy"), true, c.IsSet("pod")); err != nil { + // We've manually detached from the container + // Do not perform cleanup, or wait for container exit code + // Just exit immediately + if errors.Cause(err) == libpod.ErrDetach { + exitCode = 0 + return exitCode, nil + } + // This means the command did not exist + exitCode = 127 + if strings.Index(err.Error(), "permission denied") > -1 { + exitCode = 126 + } + if c.IsSet("rm") { + if deleteError := r.Runtime.RemoveContainer(ctx, ctr, true, false); deleteError != nil { + logrus.Errorf("unable to remove container %s after failing to start and attach to it", ctr.ID()) + } + } + return exitCode, err + } + + if ecode, err := ctr.Wait(); err != nil { + if errors.Cause(err) == libpod.ErrNoSuchCtr { + // The container may have been removed + // Go looking for an exit file + config, err := r.Runtime.GetConfig() + if err != nil { + return exitCode, err + } + ctrExitCode, err := ReadExitFile(config.TmpDir, ctr.ID()) + if err != nil { + logrus.Errorf("Cannot get exit code: %v", err) + exitCode = 127 + } else { + exitCode = ctrExitCode + } + } + } else { + exitCode = int(ecode) + } + + if c.IsSet("rm") { + r.Runtime.RemoveContainer(ctx, ctr, false, true) + } + + return exitCode, nil +} + +// ReadExitFile reads a container's exit file +func ReadExitFile(runtimeTmp, ctrID string) (int, error) { + exitFile := filepath.Join(runtimeTmp, "exits", fmt.Sprintf("%s-old", ctrID)) + + logrus.Debugf("Attempting to read container %s exit code from file %s", ctrID, exitFile) + + // Check if it exists + if _, err := os.Stat(exitFile); err != nil { + return 0, errors.Wrapf(err, "error getting exit file for container %s", ctrID) + } + + // File exists, read it in and convert to int + statusStr, err := ioutil.ReadFile(exitFile) + if err != nil { + return 0, errors.Wrapf(err, "error reading exit file for container %s", ctrID) + } + + exitCode, err := strconv.Atoi(string(statusStr)) + if err != nil { + return 0, errors.Wrapf(err, "error parsing exit code for container %s", ctrID) + } + + return exitCode, nil +} diff --git a/pkg/adapter/containers_remote.go b/pkg/adapter/containers_remote.go index 2982d6cbb..3730827c7 100644 --- a/pkg/adapter/containers_remote.go +++ b/pkg/adapter/containers_remote.go @@ -262,3 +262,33 @@ func (r *LocalRuntime) Log(c *cliconfig.LogsValues, options *libpod.LogOptions) } return nil } + +// CreateContainer creates a container from the cli over varlink +func (r *LocalRuntime) CreateContainer(ctx context.Context, c *cliconfig.CreateValues) (string, error) { + if !c.Bool("detach") { + // TODO need to add attach when that function becomes available + return "", errors.New("the remote client only supports detached containers") + } + results := shared.NewIntermediateLayer(&c.PodmanCommand) + return iopodman.CreateContainer().Call(r.Conn, results.MakeVarlink()) +} + +// Run creates a container overvarlink and then starts it +func (r *LocalRuntime) Run(ctx context.Context, c *cliconfig.RunValues, exitCode int) (int, error) { + // 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 +} + +func ReadExitFile(runtimeTmp, ctrID string) (int, error) { + return 0, libpod.ErrNotImplemented +} diff --git a/pkg/adapter/images.go b/pkg/adapter/images.go new file mode 100644 index 000000000..c8ea1cdea --- /dev/null +++ b/pkg/adapter/images.go @@ -0,0 +1,34 @@ +// +build !remoteclient + +package adapter + +import ( + "github.com/containers/libpod/cmd/podman/cliconfig" + "github.com/containers/libpod/libpod/image" + "github.com/pkg/errors" +) + +// Tree ... +func (r *LocalRuntime) Tree(c *cliconfig.TreeValues) (*image.InfoImage, map[string]*image.LayerInfo, *ContainerImage, error) { + img, err := r.NewImageFromLocal(c.InputArgs[0]) + if err != nil { + return nil, nil, nil, err + } + + // Fetch map of image-layers, which is used for printing output. + layerInfoMap, err := image.GetLayersMapWithImageInfo(r.Runtime.ImageRuntime()) + if err != nil { + return nil, nil, nil, errors.Wrapf(err, "error while retrieving layers of image %q", img.InputName) + } + + // Create an imageInfo and fill the image and layer info + imageInfo := &image.InfoImage{ + ID: img.ID(), + Tags: img.Names(), + } + + if err := image.BuildImageHierarchyMap(imageInfo, layerInfoMap, img.TopLayer()); err != nil { + return nil, nil, nil, err + } + return imageInfo, layerInfoMap, img, nil +} diff --git a/pkg/adapter/images_remote.go b/pkg/adapter/images_remote.go index e7b38dccc..722058d4a 100644 --- a/pkg/adapter/images_remote.go +++ b/pkg/adapter/images_remote.go @@ -6,8 +6,11 @@ import ( "context" "encoding/json" + "github.com/containers/libpod/cmd/podman/cliconfig" iopodman "github.com/containers/libpod/cmd/podman/varlink" + "github.com/containers/libpod/libpod/image" "github.com/containers/libpod/pkg/inspect" + "github.com/pkg/errors" ) // Inspect returns returns an ImageData struct from over a varlink connection @@ -22,3 +25,32 @@ func (i *ContainerImage) Inspect(ctx context.Context) (*inspect.ImageData, error } return &data, nil } + +// Tree ... +func (r *LocalRuntime) Tree(c *cliconfig.TreeValues) (*image.InfoImage, map[string]*image.LayerInfo, *ContainerImage, error) { + layerInfoMap := make(map[string]*image.LayerInfo) + imageInfo := &image.InfoImage{} + + img, err := r.NewImageFromLocal(c.InputArgs[0]) + if err != nil { + return nil, nil, nil, err + } + + reply, err := iopodman.GetLayersMapWithImageInfo().Call(r.Conn) + if err != nil { + return nil, nil, nil, errors.Wrap(err, "failed to obtain image layers") + } + if err := json.Unmarshal([]byte(reply), &layerInfoMap); err != nil { + return nil, nil, nil, errors.Wrap(err, "failed to unmarshal image layers") + } + + reply, err = iopodman.BuildImageHierarchyMap().Call(r.Conn, c.InputArgs[0]) + if err != nil { + return nil, nil, nil, errors.Wrap(err, "failed to get build image map") + } + if err := json.Unmarshal([]byte(reply), imageInfo); err != nil { + return nil, nil, nil, errors.Wrap(err, "failed to unmarshal build image map") + } + + return imageInfo, layerInfoMap, img, nil +} diff --git a/pkg/adapter/runtime.go b/pkg/adapter/runtime.go index 6a68a3aea..182a04044 100644 --- a/pkg/adapter/runtime.go +++ b/pkg/adapter/runtime.go @@ -8,7 +8,6 @@ import ( "io" "io/ioutil" "os" - "strconv" "text/template" "github.com/containers/buildah" @@ -23,6 +22,7 @@ import ( "github.com/containers/libpod/libpod/events" "github.com/containers/libpod/libpod/image" "github.com/containers/libpod/pkg/rootless" + "github.com/containers/storage/pkg/archive" "github.com/pkg/errors" ) @@ -123,38 +123,6 @@ func (r *LocalRuntime) Export(name string, path string) error { if err != nil { return errors.Wrapf(err, "error looking up container %q", name) } - if os.Geteuid() != 0 { - state, err := ctr.State() - if err != nil { - return errors.Wrapf(err, "cannot read container state %q", ctr.ID()) - } - if state == libpod.ContainerStateRunning || state == libpod.ContainerStatePaused { - data, err := ioutil.ReadFile(ctr.Config().ConmonPidFile) - if err != nil { - return errors.Wrapf(err, "cannot read conmon PID file %q", ctr.Config().ConmonPidFile) - } - conmonPid, err := strconv.Atoi(string(data)) - if err != nil { - return errors.Wrapf(err, "cannot parse PID %q", data) - } - became, ret, err := rootless.JoinDirectUserAndMountNS(uint(conmonPid)) - if err != nil { - return err - } - if became { - os.Exit(ret) - } - } else { - became, ret, err := rootless.BecomeRootInUserNS() - if err != nil { - return err - } - if became { - os.Exit(ret) - } - } - } - return ctr.Export(path) } @@ -342,46 +310,6 @@ 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 ( @@ -430,3 +358,8 @@ func (r *LocalRuntime) Events(c *cliconfig.EventValues) error { } return nil } + +// Diff shows the difference in two objects +func (r *LocalRuntime) Diff(c *cliconfig.DiffValues, to string) ([]archive.Change, error) { + return r.Runtime.GetDiff("", to) +} diff --git a/pkg/adapter/runtime_remote.go b/pkg/adapter/runtime_remote.go index dcc2d5aa6..807a9ad8f 100644 --- a/pkg/adapter/runtime_remote.go +++ b/pkg/adapter/runtime_remote.go @@ -82,6 +82,7 @@ type remoteImage struct { Digest digest.Digest isParent bool Runtime *LocalRuntime + TopLayer string } // Container ... @@ -147,6 +148,7 @@ func imageInListToContainerImage(i iopodman.Image, name string, runtime *LocalRu Names: i.RepoTags, isParent: i.IsParent, Runtime: runtime, + TopLayer: i.TopLayer, } return &ContainerImage{ri}, nil } @@ -280,6 +282,11 @@ func (ci *ContainerImage) Dangling() bool { return len(ci.Names()) == 0 } +// TopLayer returns an images top layer as a string +func (ci *ContainerImage) TopLayer() string { + return ci.remoteImage.TopLayer +} + // TagImage ... func (ci *ContainerImage) TagImage(tag string) error { _, err := iopodman.TagImage().Call(ci.Runtime.Conn, ci.ID(), tag) @@ -755,13 +762,6 @@ func (r *LocalRuntime) HealthCheck(c *cliconfig.HealthCheckValues) (libpod.Healt return -1, libpod.ErrNotImplemented } -// 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) { - // Nothing to do in the remote case - return true, 0, nil -} - // Events monitors libpod/podman events over a varlink connection func (r *LocalRuntime) Events(c *cliconfig.EventValues) error { var more uint64 @@ -831,3 +831,30 @@ func (r *LocalRuntime) Events(c *cliconfig.EventValues) error { } return nil } + +// Diff ... +func (r *LocalRuntime) Diff(c *cliconfig.DiffValues, to string) ([]archive.Change, error) { + var changes []archive.Change + reply, err := iopodman.Diff().Call(r.Conn, to) + if err != nil { + return nil, err + } + for _, change := range reply { + changes = append(changes, archive.Change{Path: change.Path, Kind: stringToChangeType(change.ChangeType)}) + } + return changes, nil +} + +func stringToChangeType(change string) archive.ChangeType { + switch change { + case "A": + return archive.ChangeAdd + case "D": + return archive.ChangeDelete + default: + logrus.Errorf("'%s' is unknown archive type", change) + fallthrough + case "C": + return archive.ChangeModify + } +} diff --git a/pkg/adapter/sigproxy.go b/pkg/adapter/sigproxy.go new file mode 100644 index 000000000..af968cb89 --- /dev/null +++ b/pkg/adapter/sigproxy.go @@ -0,0 +1,36 @@ +package adapter + +import ( + "os" + "syscall" + + "github.com/containers/libpod/libpod" + "github.com/docker/docker/pkg/signal" + "github.com/sirupsen/logrus" +) + +// ProxySignals ... +func ProxySignals(ctr *libpod.Container) { + sigBuffer := make(chan os.Signal, 128) + signal.CatchAll(sigBuffer) + + logrus.Debugf("Enabling signal proxying") + + go func() { + for s := range sigBuffer { + // Ignore SIGCHLD and SIGPIPE - these are mostly likely + // intended for the podman command itself. + if s == signal.SIGCHLD || s == signal.SIGPIPE { + continue + } + + if err := ctr.Kill(uint(s.(syscall.Signal))); err != nil { + logrus.Errorf("Error forwarding signal %d to container %s: %v", s, ctr.ID(), err) + signal.StopCatch(sigBuffer) + syscall.Kill(syscall.Getpid(), s.(syscall.Signal)) + } + } + }() + + return +} diff --git a/pkg/adapter/terminal.go b/pkg/adapter/terminal.go new file mode 100644 index 000000000..0b608decf --- /dev/null +++ b/pkg/adapter/terminal.go @@ -0,0 +1,159 @@ +package adapter + +import ( + "context" + "fmt" + "os" + gosignal "os/signal" + + "github.com/containers/libpod/libpod" + "github.com/docker/docker/pkg/signal" + "github.com/docker/docker/pkg/term" + "github.com/pkg/errors" + "github.com/sirupsen/logrus" + "golang.org/x/crypto/ssh/terminal" + "k8s.io/client-go/tools/remotecommand" +) + +// RawTtyFormatter ... +type RawTtyFormatter struct { +} + +// StartAttachCtr starts and (if required) attaches to a container +func StartAttachCtr(ctx context.Context, ctr *libpod.Container, stdout, stderr, stdin *os.File, detachKeys string, sigProxy bool, startContainer bool, recursive bool) error { + 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 && ctr.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 errors.Wrapf(err, "unable to save terminal state") + } + + logrus.SetFormatter(&RawTtyFormatter{}) + term.SetRawTerminal(os.Stdin.Fd()) + + defer restoreTerminal(oldTermState) + } + + streams := new(libpod.AttachStreams) + streams.OutputStream = stdout + streams.ErrorStream = stderr + streams.InputStream = stdin + streams.AttachOutput = true + streams.AttachError = true + streams.AttachInput = true + + if stdout == nil { + logrus.Debugf("Not attaching to stdout") + streams.AttachOutput = false + } + if stderr == nil { + logrus.Debugf("Not attaching to stderr") + streams.AttachError = false + } + if stdin == nil { + logrus.Debugf("Not attaching to stdin") + streams.AttachInput = false + } + + if !startContainer { + if sigProxy { + ProxySignals(ctr) + } + + return ctr.Attach(streams, detachKeys, resize) + } + + attachChan, err := ctr.StartAndAttach(ctx, streams, detachKeys, resize, recursive) + if err != nil { + return err + } + + if sigProxy { + ProxySignals(ctr) + } + + if stdout == nil && stderr == nil { + fmt.Printf("%s\n", ctr.ID()) + } + + err = <-attachChan + if err != nil { + return errors.Wrapf(err, "error attaching to container %s", ctr.ID()) + } + + return nil +} + +// getResize returns a TerminalSize command matching stdin's current +// size on success, and nil on errors. +func getResize() *remotecommand.TerminalSize { + winsize, err := term.GetWinsize(os.Stdin.Fd()) + if err != nil { + logrus.Warnf("Could not get terminal size %v", err) + return nil + } + return &remotecommand.TerminalSize{ + Width: winsize.Width, + Height: winsize.Height, + } +} + +// Helper for prepareAttach - set up a goroutine to generate terminal resize events +func resizeTty(ctx context.Context, resize chan remotecommand.TerminalSize) { + sigchan := make(chan os.Signal, 1) + gosignal.Notify(sigchan, signal.SIGWINCH) + go func() { + defer close(resize) + // Update the terminal size immediately without waiting + // for a SIGWINCH to get the correct initial size. + resizeEvent := getResize() + for { + if resizeEvent == nil { + select { + case <-ctx.Done(): + return + case <-sigchan: + resizeEvent = getResize() + } + } else { + select { + case <-ctx.Done(): + return + case <-sigchan: + resizeEvent = getResize() + case resize <- *resizeEvent: + resizeEvent = nil + } + } + } + }() +} + +func restoreTerminal(state *term.State) error { + logrus.SetFormatter(&logrus.TextFormatter{}) + return term.RestoreTerminal(os.Stdin.Fd(), state) +} + +// Format ... +func (f *RawTtyFormatter) Format(entry *logrus.Entry) ([]byte, error) { + textFormatter := logrus.TextFormatter{} + bytes, err := textFormatter.Format(entry) + + if err == nil { + bytes = append(bytes, '\r') + } + + return bytes, err +} diff --git a/pkg/annotations/annotations.go b/pkg/annotations/annotations.go index 008cca7ee..fe2591a0c 100644 --- a/pkg/annotations/annotations.go +++ b/pkg/annotations/annotations.go @@ -19,9 +19,18 @@ const ( // HostName is the container host name annotation HostName = "io.kubernetes.cri-o.HostName" + // CgroupParent is the sandbox cgroup parent + CgroupParent = "io.kubernetes.cri-o.CgroupParent" + // IP is the container ipv4 or ipv6 address IP = "io.kubernetes.cri-o.IP" + // NamespaceOptions store the options for namespaces + NamespaceOptions = "io.kubernetes.cri-o.NamespaceOptions" + + // SeccompProfilePath is the node seccomp profile path + SeccompProfilePath = "io.kubernetes.cri-o.SeccompProfilePath" + // Image is the container image ID annotation Image = "io.kubernetes.cri-o.Image" @@ -34,6 +43,9 @@ const ( // KubeName is the kubernetes name annotation KubeName = "io.kubernetes.cri-o.KubeName" + // PortMappings holds the port mappings for the sandbox + PortMappings = "io.kubernetes.cri-o.PortMappings" + // Labels are the kubernetes labels annotation Labels = "io.kubernetes.cri-o.Labels" @@ -46,6 +58,9 @@ const ( // Name is the pod name annotation Name = "io.kubernetes.cri-o.Name" + // Namespace is the pod namespace annotation + Namespace = "io.kubernetes.cri-o.Namespace" + // PrivilegedRuntime is the annotation for the privileged runtime path PrivilegedRuntime = "io.kubernetes.cri-o.PrivilegedRuntime" @@ -67,8 +82,8 @@ const ( // MountPoint is the mount point of the container rootfs MountPoint = "io.kubernetes.cri-o.MountPoint" - // TrustedSandbox is the annotation for trusted sandboxes - TrustedSandbox = "io.kubernetes.cri-o.TrustedSandbox" + // RuntimeHandler is the annotation for runtime handler + RuntimeHandler = "io.kubernetes.cri-o.RuntimeHandler" // TTY is the terminal path annotation TTY = "io.kubernetes.cri-o.TTY" @@ -79,8 +94,14 @@ const ( // StdinOnce is the stdin_once annotation StdinOnce = "io.kubernetes.cri-o.StdinOnce" - // Volumes is the volumes annotation + // Volumes is the volumes annotatoin Volumes = "io.kubernetes.cri-o.Volumes" + + // HostNetwork indicates whether the host network namespace is used or not + HostNetwork = "io.kubernetes.cri-o.HostNetwork" + + // CNIResult is the JSON string representation of the Result from CNI + CNIResult = "io.kubernetes.cri-o.CNIResult" ) // ContainerType values diff --git a/pkg/registrar/registrar_test.go b/pkg/registrar/registrar_test.go index 0c1ef312a..50af95915 100644 --- a/pkg/registrar/registrar_test.go +++ b/pkg/registrar/registrar_test.go @@ -1,119 +1,213 @@ -package registrar +package registrar_test import ( - "reflect" "testing" -) - -func TestReserve(t *testing.T) { - r := NewRegistrar() - - obj := "test1" - if err := r.Reserve("test", obj); err != nil { - t.Fatal(err) - } - - if err := r.Reserve("test", obj); err != nil { - t.Fatal(err) - } - - obj2 := "test2" - err := r.Reserve("test", obj2) - if err == nil { - t.Fatalf("expected error when reserving an already reserved name to another object") - } - if err != ErrNameReserved { - t.Fatal("expected `ErrNameReserved` error when attempting to reserve an already reserved name") - } -} - -func TestRelease(t *testing.T) { - r := NewRegistrar() - obj := "testing" - - if err := r.Reserve("test", obj); err != nil { - t.Fatal(err) - } - r.Release("test") - r.Release("test") // Ensure there is no panic here - if err := r.Reserve("test", obj); err != nil { - t.Fatal(err) - } -} - -func TestGetNames(t *testing.T) { - r := NewRegistrar() - obj := "testing" - names := []string{"test1", "test2"} - - for _, name := range names { - if err := r.Reserve(name, obj); err != nil { - t.Fatal(err) - } - } - r.Reserve("test3", "other") - - names2, err := r.GetNames(obj) - if err != nil { - t.Fatal(err) - } - - if !reflect.DeepEqual(names, names2) { - t.Fatalf("Exepected: %v, Got: %v", names, names2) - } -} + "github.com/containers/libpod/pkg/registrar" + . "github.com/containers/libpod/test/framework" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) -func TestDelete(t *testing.T) { - r := NewRegistrar() - obj := "testing" - names := []string{"test1", "test2"} - for _, name := range names { - if err := r.Reserve(name, obj); err != nil { - t.Fatal(err) - } - } - - r.Reserve("test3", "other") - r.Delete(obj) - - _, err := r.GetNames(obj) - if err == nil { - t.Fatal("expected error getting names for deleted key") - } - - if err != ErrNoSuchKey { - t.Fatal("expected `ErrNoSuchKey`") - } +// TestRegistrar runs the created specs +func TestRegistrar(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "Registrar") } -func TestGet(t *testing.T) { - r := NewRegistrar() - obj := "testing" - name := "test" - - _, err := r.Get(name) - if err == nil { - t.Fatal("expected error when key does not exist") - } - if err != ErrNameNotReserved { - t.Fatal(err) - } - - if err := r.Reserve(name, obj); err != nil { - t.Fatal(err) - } - - if _, err = r.Get(name); err != nil { - t.Fatal(err) - } - - r.Delete(obj) - _, err = r.Get(name) - if err == nil { - t.Fatal("expected error when key does not exist") - } - if err != ErrNameNotReserved { - t.Fatal(err) - } -} +// nolint: gochecknoglobals +var t *TestFramework + +var _ = BeforeSuite(func() { + t = NewTestFramework(NilFunc, NilFunc) + t.Setup() +}) + +var _ = AfterSuite(func() { + t.Teardown() +}) + +// The actual test suite +var _ = t.Describe("Registrar", func() { + // Constant test data needed by some tests + const ( + testKey = "testKey" + testName = "testName" + anotherKey = "anotherKey" + ) + + // The system under test + var sut *registrar.Registrar + + // Prepare the system under test and register a test name and key before + // each test + BeforeEach(func() { + sut = registrar.NewRegistrar() + Expect(sut.Reserve(testName, testKey)).To(BeNil()) + }) + + t.Describe("Reserve", func() { + It("should succeed to reserve a new registrar", func() { + // Given + // When + err := sut.Reserve("name", "key") + + // Then + Expect(err).To(BeNil()) + }) + + It("should succeed to reserve a registrar twice", func() { + // Given + // When + err := sut.Reserve(testName, testKey) + + // Then + Expect(err).To(BeNil()) + }) + + It("should fail to reserve an already reserved registrar", func() { + // Given + // When + err := sut.Reserve(testName, anotherKey) + + // Then + Expect(err).NotTo(BeNil()) + Expect(err).To(Equal(registrar.ErrNameReserved)) + }) + }) + + t.Describe("Release", func() { + It("should succeed to release a registered registrar multiple times", func() { + // Given + // When + // Then + sut.Release(testName) + sut.Release(testName) + }) + + It("should succeed to release a unknown registrar multiple times", func() { + // Given + // When + // Then + sut.Release(anotherKey) + sut.Release(anotherKey) + }) + + It("should succeed to release and re-register a registrar", func() { + // Given + // When + sut.Release(testName) + err := sut.Reserve(testName, testKey) + + // Then + Expect(err).To(BeNil()) + }) + }) + + t.Describe("GetNames", func() { + It("should succeed to retrieve a single name for a registrar", func() { + // Given + // When + names, err := sut.GetNames(testKey) + + // Then + Expect(err).To(BeNil()) + Expect(len(names)).To(Equal(1)) + Expect(names[0]).To(Equal(testName)) + }) + + It("should succeed to retrieve all names for a registrar", func() { + // Given + testNames := []string{"test1", "test2"} + for _, name := range testNames { + Expect(sut.Reserve(name, anotherKey)).To(BeNil()) + } + + // When + names, err := sut.GetNames(anotherKey) + + // Then + Expect(err).To(BeNil()) + Expect(len(names)).To(Equal(2)) + Expect(names).To(Equal(testNames)) + }) + }) + + t.Describe("GetNames", func() { + It("should succeed to retrieve a single name for a registrar", func() { + // Given + // When + names, err := sut.GetNames(testKey) + + // Then + Expect(err).To(BeNil()) + Expect(len(names)).To(Equal(1)) + Expect(names[0]).To(Equal(testName)) + }) + + It("should succeed to retrieve all names for a registrar", func() { + // Given + anotherKey := "anotherKey" + testNames := []string{"test1", "test2"} + for _, name := range testNames { + Expect(sut.Reserve(name, anotherKey)).To(BeNil()) + } + + // When + names, err := sut.GetNames(anotherKey) + + // Then + Expect(err).To(BeNil()) + Expect(len(names)).To(Equal(2)) + Expect(names).To(Equal(testNames)) + }) + }) + + t.Describe("Delete", func() { + It("should succeed to delete a registrar", func() { + // Given + // When + sut.Delete(testKey) + + // Then + names, err := sut.GetNames(testKey) + Expect(len(names)).To(BeZero()) + Expect(err).To(Equal(registrar.ErrNoSuchKey)) + }) + }) + + t.Describe("Get", func() { + It("should succeed to get a key for a registrar", func() { + // Given + // When + key, err := sut.Get(testName) + + // Then + Expect(err).To(BeNil()) + Expect(key).To(Equal(testKey)) + }) + + It("should fail to get a key for a not existing registrar", func() { + // Given + // When + key, err := sut.Get("notExistingName") + + // Then + Expect(key).To(BeEmpty()) + Expect(err).To(Equal(registrar.ErrNameNotReserved)) + }) + }) + + t.Describe("GetAll", func() { + It("should succeed to get all names", func() { + // Given + // When + names := sut.GetAll() + + // Then + Expect(len(names)).To(Equal(1)) + Expect(len(names[testKey])).To(Equal(1)) + Expect(names[testKey][0]).To(Equal(testName)) + }) + }) +}) diff --git a/pkg/rootless/rootless.go b/pkg/rootless/rootless.go deleted file mode 100644 index a531e43ce..000000000 --- a/pkg/rootless/rootless.go +++ /dev/null @@ -1,9 +0,0 @@ -package rootless - -// Opts allows to customize how re-execing to a rootless process is done -type Opts struct { - // Argument overrides the arguments on the command line - // for the re-execed process. The process in the namespace - // must use rootless.Argument() to read its value. - Argument string -} diff --git a/pkg/rootless/rootless_linux.c b/pkg/rootless/rootless_linux.c index 2e2c3acac..9cb79ed4d 100644 --- a/pkg/rootless/rootless_linux.c +++ b/pkg/rootless/rootless_linux.c @@ -13,10 +13,36 @@ #include <sys/wait.h> #include <string.h> #include <stdbool.h> +#include <sys/types.h> +#include <sys/prctl.h> +#include <dirent.h> static const char *_max_user_namespaces = "/proc/sys/user/max_user_namespaces"; static const char *_unprivileged_user_namespaces = "/proc/sys/kernel/unprivileged_userns_clone"; +static int n_files; + +static void __attribute__((constructor)) init() +{ + DIR *d; + + /* Store how many FDs were open before the Go runtime kicked in. */ + d = opendir ("/proc/self/fd"); + if (d) + { + struct dirent *ent; + + for (ent = readdir (d); ent; ent = readdir (d)) + { + int fd = atoi (ent->d_name); + if (fd > n_files && fd != dirfd (d)) + n_files = fd; + } + closedir (d); + } +} + + static int syscall_setresuid (uid_t ruid, uid_t euid, uid_t suid) { @@ -133,12 +159,25 @@ reexec_userns_join (int userns, int mountns) pid = fork (); if (pid < 0) fprintf (stderr, "cannot fork: %s\n", strerror (errno)); + if (pid) - return pid; + { + /* We passed down these fds, close them. */ + int f; + for (f = 3; f < n_files; f++) + close (f); + return pid; + } setenv ("_CONTAINERS_USERNS_CONFIGURED", "init", 1); setenv ("_CONTAINERS_ROOTLESS_UID", uid, 1); + if (prctl (PR_SET_PDEATHSIG, SIGTERM, 0, 0, 0) < 0) + { + fprintf (stderr, "cannot prctl(PR_SET_PDEATHSIG): %s\n", strerror (errno)); + _exit (EXIT_FAILURE); + } + if (setns (userns, 0) < 0) { fprintf (stderr, "cannot setns: %s\n", strerror (errno)); diff --git a/pkg/rootless/rootless_linux.go b/pkg/rootless/rootless_linux.go index 0be0e08bf..1d1b1713d 100644 --- a/pkg/rootless/rootless_linux.go +++ b/pkg/rootless/rootless_linux.go @@ -46,25 +46,6 @@ func IsRootless() bool { return isRootless } -var ( - skipStorageSetup = false -) - -// SetSkipStorageSetup tells the runtime to not setup containers/storage -func SetSkipStorageSetup(v bool) { - skipStorageSetup = v -} - -// SkipStorageSetup tells if we should skip the containers/storage setup -func SkipStorageSetup() bool { - return skipStorageSetup -} - -// Argument returns the argument that was set for the rootless session. -func Argument() string { - return os.Getenv("_CONTAINERS_ROOTLESS_ARG") -} - // GetRootlessUID returns the UID of the user in the parent userNS func GetRootlessUID() int { uidEnv := os.Getenv("_CONTAINERS_ROOTLESS_UID") @@ -104,51 +85,86 @@ func tryMappingTool(tool string, pid int, hostID int, mappings []idtools.IDMap) return nil } -// JoinNS re-exec podman in a new userNS and join the user namespace of the specified -// PID. -func JoinNS(pid uint, preserveFDs int) (bool, int, error) { - if os.Geteuid() == 0 || os.Getenv("_CONTAINERS_USERNS_CONFIGURED") != "" { - return false, -1, nil +func readUserNs(path string) (string, error) { + b := make([]byte, 256) + _, err := syscall.Readlink(path, b) + if err != nil { + return "", err } + return string(b), nil +} - userNS, err := getUserNSForPid(pid) +func readUserNsFd(fd uintptr) (string, error) { + return readUserNs(fmt.Sprintf("/proc/self/fd/%d", fd)) +} + +func getParentUserNs(fd uintptr) (uintptr, error) { + const nsGetParent = 0xb702 + ret, _, errno := syscall.Syscall(syscall.SYS_IOCTL, fd, uintptr(nsGetParent), 0) + if errno != 0 { + return 0, errno + } + return (uintptr)(unsafe.Pointer(ret)), nil +} + +// getUserNSFirstChild returns an open FD for the first direct child user namespace that created the process +// Each container creates a new user namespace where the runtime runs. The current process in the container +// might have created new user namespaces that are child of the initial namespace we created. +// This function finds the initial namespace created for the container that is a child of the current namespace. +// +// current ns +// / \ +// TARGET -> a [other containers] +// / +// b +// / +// NS READ USING THE PID -> c +func getUserNSFirstChild(fd uintptr) (*os.File, error) { + currentNS, err := readUserNs("/proc/self/ns/user") if err != nil { - return false, -1, err + return nil, err } - defer userNS.Close() - pidC := C.reexec_userns_join(C.int(userNS.Fd()), -1) - if int(pidC) < 0 { - return false, -1, errors.Errorf("cannot re-exec process") + ns, err := readUserNsFd(fd) + if err != nil { + return nil, errors.Wrapf(err, "cannot read user namespace") } - if preserveFDs > 0 { - for fd := 3; fd < 3+preserveFDs; fd++ { - // These fds were passed down to the runtime. Close them - // and not interfere - os.NewFile(uintptr(fd), fmt.Sprintf("fd-%d", fd)).Close() - } + if ns == currentNS { + return nil, errors.New("process running in the same user namespace") } - ret := C.reexec_in_user_namespace_wait(pidC) - if ret < 0 { - return false, -1, errors.New("error waiting for the re-exec process") - } + for { + nextFd, err := getParentUserNs(fd) + if err != nil { + return nil, errors.Wrapf(err, "cannot get parent user namespace") + } - return true, int(ret), nil -} + ns, err = readUserNsFd(nextFd) + if err != nil { + return nil, errors.Wrapf(err, "cannot read user namespace") + } -// JoinDirectUserAndMountNS re-exec podman in a new userNS and join the user and mount -// namespace of the specified PID without looking up its parent. Useful to join directly -// the conmon process. It is a convenience function for JoinDirectUserAndMountNSWithOpts -// with a default configuration. -func JoinDirectUserAndMountNS(pid uint) (bool, int, error) { - return JoinDirectUserAndMountNSWithOpts(pid, nil) + if ns == currentNS { + syscall.Close(int(nextFd)) + + // Drop O_CLOEXEC for the fd. + _, _, errno := syscall.Syscall(syscall.SYS_FCNTL, fd, syscall.F_SETFD, 0) + if errno != 0 { + syscall.Close(int(fd)) + return nil, errno + } + + return os.NewFile(fd, "userns child"), nil + } + syscall.Close(int(fd)) + fd = nextFd + } } -// JoinDirectUserAndMountNSWithOpts re-exec podman in a new userNS and join the user and -// mount namespace of the specified PID without looking up its parent. Useful to join -// directly the conmon process. -func JoinDirectUserAndMountNSWithOpts(pid uint, opts *Opts) (bool, int, error) { +// JoinUserAndMountNS re-exec podman in a new userNS and join the user and mount +// namespace of the specified PID without looking up its parent. Useful to join directly +// the conmon process. +func JoinUserAndMountNS(pid uint) (bool, int, error) { if os.Geteuid() == 0 || os.Getenv("_CONTAINERS_USERNS_CONFIGURED") != "" { return false, -1, nil } @@ -165,39 +181,11 @@ func JoinDirectUserAndMountNSWithOpts(pid uint, opts *Opts) (bool, int, error) { } defer userNS.Close() - if opts != nil && opts.Argument != "" { - if err := os.Setenv("_CONTAINERS_ROOTLESS_ARG", opts.Argument); err != nil { - return false, -1, err - } - } - - pidC := C.reexec_userns_join(C.int(userNS.Fd()), C.int(mountNS.Fd())) - if int(pidC) < 0 { - return false, -1, errors.Errorf("cannot re-exec process") - } - - ret := C.reexec_in_user_namespace_wait(pidC) - if ret < 0 { - return false, -1, errors.New("error waiting for the re-exec process") - } - - return true, int(ret), nil -} - -// JoinNSPath re-exec podman in a new userNS and join the owner user namespace of the -// specified path. -func JoinNSPath(path string) (bool, int, error) { - if os.Geteuid() == 0 || os.Getenv("_CONTAINERS_USERNS_CONFIGURED") != "" { - return false, -1, nil - } - - userNS, err := getUserNSForPath(path) + fd, err := getUserNSFirstChild(userNS.Fd()) if err != nil { return false, -1, err } - defer userNS.Close() - - pidC := C.reexec_userns_join(C.int(userNS.Fd()), -1) + pidC := C.reexec_userns_join(C.int(fd.Fd()), C.int(mountNS.Fd())) if int(pidC) < 0 { return false, -1, errors.Errorf("cannot re-exec process") } @@ -213,16 +201,8 @@ func JoinNSPath(path string) (bool, int, error) { // BecomeRootInUserNS re-exec podman in a new userNS. It returns whether podman was re-executed // into a new user namespace and the return code from the re-executed podman process. // If podman was re-executed the caller needs to propagate the error code returned by the child -// process. It is a convenience function for BecomeRootInUserNSWithOpts with a default configuration. -func BecomeRootInUserNS() (bool, int, error) { - return BecomeRootInUserNSWithOpts(nil) -} - -// BecomeRootInUserNSWithOpts re-exec podman in a new userNS. It returns whether podman was -// re-execute into a new user namespace and the return code from the re-executed podman process. -// If podman was re-executed the caller needs to propagate the error code returned by the child // process. -func BecomeRootInUserNSWithOpts(opts *Opts) (bool, int, error) { +func BecomeRootInUserNS() (bool, int, error) { if os.Geteuid() == 0 || os.Getenv("_CONTAINERS_USERNS_CONFIGURED") != "" { if os.Getenv("_CONTAINERS_USERNS_CONFIGURED") == "init" { return false, 0, runInUser() @@ -241,12 +221,6 @@ func BecomeRootInUserNSWithOpts(opts *Opts) (bool, int, error) { defer w.Close() defer w.Write([]byte("0")) - if opts != nil && opts.Argument != "" { - if err := os.Setenv("_CONTAINERS_ROOTLESS_ARG", opts.Argument); err != nil { - return false, -1, err - } - } - pidC := C.reexec_in_user_namespace(C.int(r.Fd())) pid := int(pidC) if pid < 0 { @@ -328,112 +302,3 @@ func BecomeRootInUserNSWithOpts(opts *Opts) (bool, int, error) { return true, int(ret), nil } - -func readUserNs(path string) (string, error) { - b := make([]byte, 256) - _, err := syscall.Readlink(path, b) - if err != nil { - return "", err - } - return string(b), nil -} - -func readUserNsFd(fd uintptr) (string, error) { - return readUserNs(fmt.Sprintf("/proc/self/fd/%d", fd)) -} - -func getOwner(fd uintptr) (uintptr, error) { - const nsGetUserns = 0xb701 - ret, _, errno := syscall.Syscall(syscall.SYS_IOCTL, fd, uintptr(nsGetUserns), 0) - if errno != 0 { - return 0, errno - } - return (uintptr)(unsafe.Pointer(ret)), nil -} - -func getParentUserNs(fd uintptr) (uintptr, error) { - const nsGetParent = 0xb702 - ret, _, errno := syscall.Syscall(syscall.SYS_IOCTL, fd, uintptr(nsGetParent), 0) - if errno != 0 { - return 0, errno - } - return (uintptr)(unsafe.Pointer(ret)), nil -} - -func getUserNSForPath(path string) (*os.File, error) { - u, err := os.Open(path) - if err != nil { - return nil, errors.Wrapf(err, "cannot open %s", path) - } - defer u.Close() - fd, err := getOwner(u.Fd()) - if err != nil { - return nil, err - } - - return getUserNSFirstChild(fd) -} - -func getUserNSForPid(pid uint) (*os.File, error) { - path := fmt.Sprintf("/proc/%d/ns/user", pid) - u, err := os.Open(path) - if err != nil { - return nil, errors.Wrapf(err, "cannot open %s", path) - } - - return getUserNSFirstChild(u.Fd()) -} - -// getUserNSFirstChild returns an open FD for the first direct child user namespace that created the process -// Each container creates a new user namespace where the runtime runs. The current process in the container -// might have created new user namespaces that are child of the initial namespace we created. -// This function finds the initial namespace created for the container that is a child of the current namespace. -// -// current ns -// / \ -// TARGET -> a [other containers] -// / -// b -// / -// NS READ USING THE PID -> c -func getUserNSFirstChild(fd uintptr) (*os.File, error) { - currentNS, err := readUserNs("/proc/self/ns/user") - if err != nil { - return nil, err - } - - ns, err := readUserNsFd(fd) - if err != nil { - return nil, errors.Wrapf(err, "cannot read user namespace") - } - if ns == currentNS { - return nil, errors.New("process running in the same user namespace") - } - - for { - nextFd, err := getParentUserNs(fd) - if err != nil { - return nil, errors.Wrapf(err, "cannot get parent user namespace") - } - - ns, err = readUserNsFd(nextFd) - if err != nil { - return nil, errors.Wrapf(err, "cannot read user namespace") - } - - if ns == currentNS { - syscall.Close(int(nextFd)) - - // Drop O_CLOEXEC for the fd. - _, _, errno := syscall.Syscall(syscall.SYS_FCNTL, fd, syscall.F_SETFD, 0) - if errno != 0 { - syscall.Close(int(fd)) - return nil, errno - } - - return os.NewFile(fd, "userns child"), nil - } - syscall.Close(int(fd)) - fd = nextFd - } -} diff --git a/pkg/rootless/rootless_unsupported.go b/pkg/rootless/rootless_unsupported.go index e01d7855c..47b5dd7cc 100644 --- a/pkg/rootless/rootless_unsupported.go +++ b/pkg/rootless/rootless_unsupported.go @@ -19,54 +19,15 @@ func BecomeRootInUserNS() (bool, int, error) { return false, -1, errors.New("this function is not supported on this os") } -// BecomeRootInUserNS is a stub function that always returns false and an -// error on unsupported OS's -func BecomeRootInUserNSWithOpts(opts *Opts) (bool, int, error) { - return false, -1, errors.New("this function is not supported on this os") -} - // GetRootlessUID returns the UID of the user in the parent userNS func GetRootlessUID() int { return -1 } -// SetSkipStorageSetup tells the runtime to not setup containers/storage -func SetSkipStorageSetup(bool) { -} - -// SkipStorageSetup tells if we should skip the containers/storage setup -func SkipStorageSetup() bool { - return false -} - -// JoinNS re-exec podman in a new userNS and join the user namespace of the specified -// PID. -func JoinNS(pid uint, preserveFDs int) (bool, int, error) { - return false, -1, errors.New("this function is not supported on this os") -} - -// JoinNSPath re-exec podman in a new userNS and join the owner user namespace of the -// specified path. -func JoinNSPath(path string) (bool, int, error) { - return false, -1, errors.New("this function is not supported on this os") -} - -// JoinDirectUserAndMountNSWithOpts re-exec podman in a new userNS and join the user and -// mount namespace of the specified PID without looking up its parent. Useful to join -// directly the conmon process. -func JoinDirectUserAndMountNSWithOpts(pid uint, opts *Opts) (bool, int, error) { - return false, -1, errors.New("this function is not supported on this os") -} - -// JoinDirectUserAndMountNS re-exec podman in a new userNS and join the user and mount +// JoinUserAndMountNS re-exec podman in a new userNS and join the user and mount // namespace of the specified PID without looking up its parent. Useful to join directly -// the conmon process. It is a convenience function for JoinDirectUserAndMountNSWithOpts +// the conmon process. It is a convenience function for JoinUserAndMountNSWithOpts // with a default configuration. -func JoinDirectUserAndMountNS(pid uint) (bool, int, error) { +func JoinUserAndMountNS(pid uint) (bool, int, error) { return false, -1, errors.New("this function is not supported on this os") } - -// Argument returns the argument that was set for the rootless session. -func Argument() string { - return "" -} diff --git a/pkg/spec/createconfig.go b/pkg/spec/createconfig.go index 0a12e3dca..e71d9d3db 100644 --- a/pkg/spec/createconfig.go +++ b/pkg/spec/createconfig.go @@ -1,7 +1,6 @@ package createconfig import ( - "encoding/json" "fmt" "net" "os" @@ -12,7 +11,6 @@ import ( "github.com/containers/image/manifest" "github.com/containers/libpod/libpod" "github.com/containers/libpod/pkg/namespaces" - "github.com/containers/libpod/pkg/rootless" "github.com/containers/storage" "github.com/containers/storage/pkg/stringid" "github.com/cri-o/ocicni/pkg/ocicni" @@ -24,18 +22,16 @@ import ( "golang.org/x/sys/unix" ) -type mountType string - // Type constants const ( bps = iota iops // TypeBind is the type for mounting host dir - TypeBind mountType = "bind" + TypeBind = "bind" // TypeVolume is the type for remote storage volumes - // TypeVolume mountType = "volume" // re-enable upon use + // TypeVolume = "volume" // re-enable upon use // TypeTmpfs is the type for mounting tmpfs - TypeTmpfs mountType = "tmpfs" + TypeTmpfs = "tmpfs" ) // CreateResourceConfig represents resource elements in CreateConfig @@ -131,15 +127,15 @@ type CreateConfig struct { Mounts []spec.Mount //mounts Volumes []string //volume VolumesFrom []string - WorkDir string //workdir - LabelOpts []string //SecurityOpts - NoNewPrivs bool //SecurityOpts - ApparmorProfile string //SecurityOpts - SeccompProfilePath string //SecurityOpts + NamedVolumes []*libpod.ContainerNamedVolume // Filled in by CreateConfigToOCISpec + WorkDir string //workdir + LabelOpts []string //SecurityOpts + NoNewPrivs bool //SecurityOpts + ApparmorProfile string //SecurityOpts + SeccompProfilePath string //SecurityOpts SecurityOpts []string Rootfs string - LocalVolumes []spec.Mount //Keeps track of the built-in volumes of container used in the --volumes-from flag - Syslog bool // Whether to enable syslog on exit commands + Syslog bool // Whether to enable syslog on exit commands } func u32Ptr(i int64) *uint32 { u := uint32(i); return &u } @@ -173,9 +169,9 @@ func (c *CreateConfig) AddContainerInitBinary(path string) error { c.Command = append([]string{"/dev/init", "--"}, c.Command...) c.Mounts = append(c.Mounts, spec.Mount{ Destination: "/dev/init", - Type: "bind", + Type: TypeBind, Source: path, - Options: []string{"bind", "ro"}, + Options: []string{TypeBind, "ro"}, }) return nil } @@ -218,9 +214,9 @@ func (c *CreateConfig) initFSMounts() []spec.Mount { return mounts } -//GetVolumeMounts takes user provided input for bind mounts and creates Mount structs +// GetVolumeMounts takes user provided input for bind mounts and creates Mount structs func (c *CreateConfig) GetVolumeMounts(specMounts []spec.Mount) ([]spec.Mount, error) { - m := c.LocalVolumes + m := []spec.Mount{} for _, i := range c.Volumes { var options []string spliti := strings.Split(i, ":") @@ -256,9 +252,11 @@ func (c *CreateConfig) GetVolumeMounts(specMounts []spec.Mount) ([]spec.Mount, e mount.Source = "tmpfs" mount.Options = append(mount.Options, "tmpcopyup") } else { + // TODO: Move support for this and tmpfs into libpod + // Should tmpfs also be handled as named volumes? Wouldn't be hard // This will cause a new local Volume to be created on your system mount.Source = stringid.GenerateNonCryptoID() - mount.Options = append(mount.Options, "bind") + mount.Options = append(mount.Options, TypeBind) } m = append(m, mount) } @@ -269,13 +267,12 @@ func (c *CreateConfig) GetVolumeMounts(specMounts []spec.Mount) ([]spec.Mount, e // GetVolumesFrom reads the create-config artifact of the container to get volumes from // and adds it to c.Volumes of the current container. func (c *CreateConfig) GetVolumesFrom() error { - var options string - - if rootless.SkipStorageSetup() { + if os.Geteuid() != 0 { return nil } for _, vol := range c.VolumesFrom { + options := "" splitVol := strings.SplitN(vol, ":", 2) if len(splitVol) == 2 { options = splitVol[1] @@ -284,41 +281,60 @@ func (c *CreateConfig) GetVolumesFrom() error { if err != nil { return errors.Wrapf(err, "error looking up container %q", splitVol[0]) } - inspect, err := ctr.Inspect(false) - if err != nil { - return errors.Wrapf(err, "error inspecting %q", splitVol[0]) - } - var createArtifact CreateConfig - artifact, err := ctr.GetArtifact("create-config") - if err != nil { - return errors.Wrapf(err, "error getting create-config artifact for %q", splitVol[0]) + + logrus.Debugf("Adding volumes from container %s", ctr.ID()) + + // Look up the container's user volumes. This gets us the + // destinations of all mounts the user added to the container. + userVolumesArr := ctr.UserVolumes() + + // We're going to need to access them a lot, so convert to a map + // to reduce looping. + // We'll also use the map to indicate if we missed any volumes along the way. + userVolumes := make(map[string]bool) + for _, dest := range userVolumesArr { + userVolumes[dest] = false } - if err := json.Unmarshal(artifact, &createArtifact); err != nil { - return err + + // Now we get the container's spec and loop through its volumes + // and append them in if we can find them. + spec := ctr.Spec() + if spec == nil { + return errors.Errorf("error retrieving container %s spec", ctr.ID()) } - for key := range createArtifact.BuiltinImgVolumes { - for _, m := range inspect.Mounts { - if m.Destination == key { - c.LocalVolumes = append(c.LocalVolumes, m) - break + for _, mnt := range spec.Mounts { + if mnt.Type != TypeBind { + continue + } + if _, exists := userVolumes[mnt.Destination]; exists { + userVolumes[mnt.Destination] = true + localOptions := options + if localOptions == "" { + localOptions = strings.Join(mnt.Options, ",") } + c.Volumes = append(c.Volumes, fmt.Sprintf("%s:%s:%s", mnt.Source, mnt.Destination, localOptions)) } } - for _, i := range createArtifact.Volumes { - // Volumes format is host-dir:ctr-dir[:options], so get the host and ctr dir - // and add on the options given by the user to the flag. - spliti := strings.SplitN(i, ":", 3) - // Throw error if mounting volume from container with Z option (private label) - // Override this by adding 'z' to options. - if len(spliti) > 2 && strings.Contains(spliti[2], "Z") && !strings.Contains(options, "z") { - return errors.Errorf("volume mounted with private option 'Z' in %q. Use option 'z' to mount in current container", ctr.ID()) + // We're done with the spec mounts. Add named volumes. + // Add these unconditionally - none of them are automatically + // part of the container, as some spec mounts are. + namedVolumes := ctr.NamedVolumes() + for _, namedVol := range namedVolumes { + if _, exists := userVolumes[namedVol.Dest]; exists { + userVolumes[namedVol.Dest] = true } - if options == "" { - // Mount the volumes with the default options - c.Volumes = append(c.Volumes, createArtifact.Volumes...) - } else { - c.Volumes = append(c.Volumes, spliti[0]+":"+spliti[1]+":"+options) + localOptions := options + if localOptions == "" { + localOptions = strings.Join(namedVol.Options, ",") + } + c.Volumes = append(c.Volumes, fmt.Sprintf("%s:%s:%s", namedVol.Name, namedVol.Dest, localOptions)) + } + + // Check if we missed any volumes + for volDest, found := range userVolumes { + if !found { + logrus.Warnf("Unable to match volume %s from container %s for volumes-from", volDest, ctr.ID()) } } } @@ -418,14 +434,20 @@ func (c *CreateConfig) GetContainerCreateOptions(runtime *libpod.Runtime, pod *l // others, if they are included volumes := make([]string, 0, len(c.Volumes)) for _, vol := range c.Volumes { - volumes = append(volumes, strings.SplitN(vol, ":", 2)[0]) + // We always want the volume destination + splitVol := strings.SplitN(vol, ":", 3) + if len(splitVol) > 1 { + volumes = append(volumes, splitVol[1]) + } else { + volumes = append(volumes, splitVol[0]) + } } options = append(options, libpod.WithUserVolumes(volumes)) } - if len(c.LocalVolumes) != 0 { - options = append(options, libpod.WithLocalVolumes(c.LocalVolumes)) + if len(c.NamedVolumes) != 0 { + options = append(options, libpod.WithNamedVolumes(c.NamedVolumes)) } if len(c.Command) != 0 { @@ -539,7 +561,7 @@ func (c *CreateConfig) GetContainerCreateOptions(runtime *libpod.Runtime, pod *l options = append(options, libpod.WithPrivileged(c.Privileged)) - useImageVolumes := c.ImageVolumeType == "bind" + useImageVolumes := c.ImageVolumeType == TypeBind // Gather up the options for NewContainer which consist of With... funcs options = append(options, libpod.WithRootFSFromImage(c.ImageID, c.Image, useImageVolumes)) options = append(options, libpod.WithSecLabels(c.LabelOpts)) diff --git a/pkg/spec/spec.go b/pkg/spec/spec.go index a61741f73..9b6bd089e 100644 --- a/pkg/spec/spec.go +++ b/pkg/spec/spec.go @@ -6,6 +6,7 @@ import ( "path/filepath" "strings" + "github.com/containers/libpod/libpod" "github.com/containers/libpod/pkg/rootless" "github.com/containers/storage/pkg/mount" pmount "github.com/containers/storage/pkg/mount" @@ -48,6 +49,33 @@ func supercedeUserMounts(mounts []spec.Mount, configMount []spec.Mount) []spec.M return configMount } +// Split named volumes from normal volumes +func splitNamedVolumes(mounts []spec.Mount) ([]spec.Mount, []*libpod.ContainerNamedVolume) { + newMounts := make([]spec.Mount, 0) + namedVolumes := make([]*libpod.ContainerNamedVolume, 0) + for _, mount := range mounts { + // If it's not a named volume, append unconditionally + if mount.Type != TypeBind { + newMounts = append(newMounts, mount) + continue + } + // Volumes that are not named volumes must be an absolute or + // relative path. + // Volume names may not begin with a non-alphanumeric character + // so the HasPrefix() check is safe here. + if strings.HasPrefix(mount.Source, "/") || strings.HasPrefix(mount.Source, ".") { + newMounts = append(newMounts, mount) + } else { + namedVolume := new(libpod.ContainerNamedVolume) + namedVolume.Name = mount.Source + namedVolume.Dest = mount.Destination + namedVolume.Options = mount.Options + namedVolumes = append(namedVolumes, namedVolume) + } + } + return newMounts, namedVolumes +} + func getAvailableGids() (int64, error) { idMap, err := user.ParseIDMapFile("/proc/self/gid_map") if err != nil { @@ -99,7 +127,7 @@ func CreateConfigToOCISpec(config *CreateConfig) (*spec.Spec, error) { //nolint } sysMnt := spec.Mount{ Destination: "/sys", - Type: "bind", + Type: TypeBind, Source: "/sys", Options: []string{"rprivate", "nosuid", "noexec", "nodev", r, "rbind"}, } @@ -126,7 +154,7 @@ func CreateConfigToOCISpec(config *CreateConfig) (*spec.Spec, error) { //nolint g.RemoveMount("/dev/mqueue") devMqueue := spec.Mount{ Destination: "/dev/mqueue", - Type: "bind", + Type: TypeBind, Source: "/dev/mqueue", Options: []string{"bind", "nosuid", "noexec", "nodev"}, } @@ -136,7 +164,7 @@ func CreateConfigToOCISpec(config *CreateConfig) (*spec.Spec, error) { //nolint g.RemoveMount("/proc") procMount := spec.Mount{ Destination: "/proc", - Type: "bind", + Type: TypeBind, Source: "/proc", Options: []string{"rbind", "nosuid", "noexec", "nodev"}, } @@ -377,6 +405,12 @@ func CreateConfigToOCISpec(config *CreateConfig) (*spec.Spec, error) { //nolint configSpec.Mounts = supercedeUserMounts(volumeMounts, configSpec.Mounts) //--mount configSpec.Mounts = supercedeUserMounts(config.initFSMounts(), configSpec.Mounts) + + // Split normal mounts and named volumes + newMounts, namedVolumes := splitNamedVolumes(configSpec.Mounts) + configSpec.Mounts = newMounts + config.NamedVolumes = namedVolumes + // BLOCK IO blkio, err := config.CreateBlockIO() if err != nil { diff --git a/pkg/varlinkapi/config.go b/pkg/varlinkapi/config.go index f557d04e5..e75170547 100644 --- a/pkg/varlinkapi/config.go +++ b/pkg/varlinkapi/config.go @@ -1,3 +1,5 @@ +// +build varlink + package varlinkapi import ( diff --git a/pkg/varlinkapi/containers.go b/pkg/varlinkapi/containers.go index 7a6ae3507..ac1352dac 100644 --- a/pkg/varlinkapi/containers.go +++ b/pkg/varlinkapi/containers.go @@ -1,3 +1,5 @@ +// +build varlink + package varlinkapi import ( @@ -583,27 +585,6 @@ func (i *LibpodAPI) GetContainerStatsWithHistory(call iopodman.VarlinkCall, prev return call.ReplyGetContainerStatsWithHistory(cStats) } -// ContainerStatsToLibpodContainerStats converts the varlink containerstats to a libpod -// container stats -func ContainerStatsToLibpodContainerStats(stats iopodman.ContainerStats) libpod.ContainerStats { - cstats := libpod.ContainerStats{ - ContainerID: stats.Id, - Name: stats.Name, - CPU: stats.Cpu, - CPUNano: uint64(stats.Cpu_nano), - SystemNano: uint64(stats.System_nano), - MemUsage: uint64(stats.Mem_usage), - MemLimit: uint64(stats.Mem_limit), - MemPerc: stats.Mem_perc, - NetInput: uint64(stats.Net_input), - NetOutput: uint64(stats.Net_output), - BlockInput: uint64(stats.Block_input), - BlockOutput: uint64(stats.Block_output), - PIDs: uint64(stats.Pids), - } - return cstats -} - // 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/containers_create.go b/pkg/varlinkapi/containers_create.go index 8990ac001..6b23dce5e 100644 --- a/pkg/varlinkapi/containers_create.go +++ b/pkg/varlinkapi/containers_create.go @@ -1,220 +1,18 @@ +// +build varlink + package varlinkapi import ( - "context" - "encoding/json" - "fmt" - "os" - "strings" - "syscall" - + "github.com/containers/libpod/cmd/podman/shared" "github.com/containers/libpod/cmd/podman/varlink" - "github.com/containers/libpod/libpod" - "github.com/containers/libpod/libpod/image" - "github.com/containers/libpod/pkg/inspect" - "github.com/containers/libpod/pkg/namespaces" - "github.com/containers/libpod/pkg/rootless" - cc "github.com/containers/libpod/pkg/spec" - "github.com/containers/libpod/pkg/util" - "github.com/docker/docker/pkg/signal" - "github.com/sirupsen/logrus" ) // CreateContainer ... func (i *LibpodAPI) CreateContainer(call iopodman.VarlinkCall, config iopodman.Create) error { - rtc, err := i.Runtime.GetConfig() - if err != nil { - return call.ReplyErrorOccurred(err.Error()) - } - ctx := getContext() - - newImage, err := i.Runtime.ImageRuntime().New(ctx, config.Image, rtc.SignaturePolicyPath, "", os.Stderr, nil, image.SigningOptions{}, false, nil) - if err != nil { - return call.ReplyErrorOccurred(err.Error()) - } - data, err := newImage.Inspect(ctx) - - createConfig, err := varlinkCreateToCreateConfig(ctx, config, i.Runtime, config.Image, data) - if err != nil { - return call.ReplyErrorOccurred(err.Error()) - } - - runtimeSpec, err := cc.CreateConfigToOCISpec(createConfig) + generic := shared.VarlinkCreateToGeneric(config) + ctr, _, err := shared.CreateContainer(getContext(), &generic, i.Runtime) if err != nil { return call.ReplyErrorOccurred(err.Error()) } - - // TODO fix when doing remote client and dealing with the ability to create a container - // within a non-existing pod (i.e. --pod new:foobar) - options, err := createConfig.GetContainerCreateOptions(i.Runtime, nil) - if err != nil { - return call.ReplyErrorOccurred(err.Error()) - } - - ctr, err := i.Runtime.NewContainer(ctx, runtimeSpec, options...) - if err != nil { - return call.ReplyErrorOccurred(err.Error()) - } - createConfigJSON, err := json.Marshal(createConfig) - if err != nil { - return call.ReplyErrorOccurred(err.Error()) - } - if err := ctr.AddArtifact("create-config", createConfigJSON); err != nil { - return call.ReplyErrorOccurred(err.Error()) - } - - logrus.Debug("new container created ", ctr.ID()) - return call.ReplyCreateContainer(ctr.ID()) } - -// varlinkCreateToCreateConfig takes the varlink input struct and maps it to a pointer -// of a CreateConfig, which eventually can be used to create the OCI spec. -func varlinkCreateToCreateConfig(ctx context.Context, create iopodman.Create, runtime *libpod.Runtime, imageName string, data *inspect.ImageData) (*cc.CreateConfig, error) { - idmappings, err := util.ParseIDMapping(create.Uidmap, create.Gidmap, create.Subuidname, create.Subgidname) - if err != nil { - return nil, err - } - inputCommand := create.Command - entrypoint := create.Entrypoint - - // ENTRYPOINT - // User input entrypoint takes priority over image entrypoint - if len(entrypoint) == 0 { - entrypoint = data.Config.Entrypoint - } - // if entrypoint=, we need to clear the entrypoint - if len(entrypoint) == 1 && strings.Join(create.Entrypoint, "") == "" { - entrypoint = []string{} - } - // Build the command - // If we have an entry point, it goes first - command := entrypoint - if len(inputCommand) > 0 { - // User command overrides data CMD - command = append(command, inputCommand...) - } else if len(data.Config.Cmd) > 0 && len(command) == 0 { - // If not user command, add CMD - command = append(command, data.Config.Cmd...) - } - - stopSignal := syscall.SIGTERM - if create.Stop_signal > 0 { - stopSignal, err = signal.ParseSignal(fmt.Sprintf("%d", create.Stop_signal)) - if err != nil { - return nil, err - } - } - - user := create.User - if user == "" { - user = data.Config.User - } - - // EXPOSED PORTS - portBindings, err := cc.ExposedPorts(create.Exposed_ports, create.Publish, create.Publish_all, data.Config.ExposedPorts) - if err != nil { - return nil, err - } - - // NETWORK MODE - networkMode := create.Net_mode - if networkMode == "" { - if rootless.IsRootless() { - networkMode = "slirp4netns" - } else { - networkMode = "bridge" - } - } - - // WORKING DIR - workDir := create.Work_dir - if workDir == "" { - workDir = "/" - } - - imageID := data.ID - var ImageVolumes map[string]struct{} - if data != nil && create.Image_volume_type != "ignore" { - ImageVolumes = data.Config.Volumes - } - - config := &cc.CreateConfig{ - Runtime: runtime, - BuiltinImgVolumes: ImageVolumes, - ConmonPidFile: create.Conmon_pidfile, - ImageVolumeType: create.Image_volume_type, - CapAdd: create.Cap_add, - CapDrop: create.Cap_drop, - CgroupParent: create.Cgroup_parent, - Command: command, - Detach: create.Detach, - Devices: create.Devices, - DNSOpt: create.Dns_opt, - DNSSearch: create.Dns_search, - DNSServers: create.Dns_servers, - Entrypoint: create.Entrypoint, - Env: create.Env, - GroupAdd: create.Group_add, - Hostname: create.Hostname, - HostAdd: create.Host_add, - IDMappings: idmappings, - Image: imageName, - ImageID: imageID, - Interactive: create.Interactive, - Labels: create.Labels, - LogDriver: create.Log_driver, - LogDriverOpt: create.Log_driver_opt, - Name: create.Name, - Network: networkMode, - IpcMode: namespaces.IpcMode(create.Ipc_mode), - NetMode: namespaces.NetworkMode(networkMode), - UtsMode: namespaces.UTSMode(create.Uts_mode), - PidMode: namespaces.PidMode(create.Pid_mode), - Pod: create.Pod, - Privileged: create.Privileged, - Publish: create.Publish, - PublishAll: create.Publish_all, - PortBindings: portBindings, - Quiet: create.Quiet, - ReadOnlyRootfs: create.Readonly_rootfs, - Resources: cc.CreateResourceConfig{ - BlkioWeight: uint16(create.Resources.Blkio_weight), - BlkioWeightDevice: create.Resources.Blkio_weight_device, - CPUShares: uint64(create.Resources.Cpu_shares), - CPUPeriod: uint64(create.Resources.Cpu_period), - CPUsetCPUs: create.Resources.Cpuset_cpus, - CPUsetMems: create.Resources.Cpuset_mems, - CPUQuota: create.Resources.Cpu_quota, - CPURtPeriod: uint64(create.Resources.Cpu_rt_period), - CPURtRuntime: create.Resources.Cpu_rt_runtime, - CPUs: create.Resources.Cpus, - DeviceReadBps: create.Resources.Device_read_bps, - DeviceReadIOps: create.Resources.Device_write_bps, - DeviceWriteBps: create.Resources.Device_read_iops, - DeviceWriteIOps: create.Resources.Device_write_iops, - DisableOomKiller: create.Resources.Disable_oomkiller, - ShmSize: create.Resources.Shm_size, - Memory: create.Resources.Memory, - MemoryReservation: create.Resources.Memory_reservation, - MemorySwap: create.Resources.Memory_swap, - MemorySwappiness: int(create.Resources.Memory_swappiness), - KernelMemory: create.Resources.Kernel_memory, - OomScoreAdj: int(create.Resources.Oom_score_adj), - PidsLimit: create.Resources.Pids_limit, - Ulimit: create.Resources.Ulimit, - }, - Rm: create.Rm, - StopSignal: stopSignal, - StopTimeout: uint(create.Stop_timeout), - Sysctl: create.Sys_ctl, - Tmpfs: create.Tmpfs, - Tty: create.Tty, - User: user, - UsernsMode: namespaces.UsernsMode(create.Userns_mode), - Volumes: create.Volumes, - WorkDir: workDir, - } - - return config, nil -} diff --git a/pkg/varlinkapi/events.go b/pkg/varlinkapi/events.go index 47c628ead..1e5696fbe 100644 --- a/pkg/varlinkapi/events.go +++ b/pkg/varlinkapi/events.go @@ -1,3 +1,5 @@ +// +build varlink + package varlinkapi import ( diff --git a/pkg/varlinkapi/images.go b/pkg/varlinkapi/images.go index 0ca867410..95aa1b335 100644 --- a/pkg/varlinkapi/images.go +++ b/pkg/varlinkapi/images.go @@ -1,3 +1,5 @@ +// +build varlink + package varlinkapi import ( @@ -103,6 +105,7 @@ func (i *LibpodAPI) GetImage(call iopodman.VarlinkCall, id string) error { VirtualSize: newImage.VirtualSize, Containers: int64(len(containers)), Labels: labels, + TopLayer: newImage.TopLayer(), } return call.ReplyGetImage(il) } @@ -910,3 +913,53 @@ func (i *LibpodAPI) LoadImage(call iopodman.VarlinkCall, name, inputFile string, } return call.ReplyLoadImage(br) } + +// Diff ... +func (i *LibpodAPI) Diff(call iopodman.VarlinkCall, name string) error { + var response []iopodman.DiffInfo + changes, err := i.Runtime.GetDiff("", name) + if err != nil { + return call.ReplyErrorOccurred(err.Error()) + } + for _, change := range changes { + response = append(response, iopodman.DiffInfo{Path: change.Path, ChangeType: change.Kind.String()}) + } + return call.ReplyDiff(response) +} + +// GetLayersMapWithImageInfo is a development only endpoint to obtain layer information for an image. +func (i *LibpodAPI) GetLayersMapWithImageInfo(call iopodman.VarlinkCall) error { + layerInfo, err := image.GetLayersMapWithImageInfo(i.Runtime.ImageRuntime()) + if err != nil { + return call.ReplyErrorOccurred(err.Error()) + } + b, err := json.Marshal(layerInfo) + if err != nil { + return call.ReplyErrorOccurred(err.Error()) + } + return call.ReplyGetLayersMapWithImageInfo(string(b)) +} + +// BuildImageHierarchyMap ... +func (i *LibpodAPI) BuildImageHierarchyMap(call iopodman.VarlinkCall, name string) error { + img, err := i.Runtime.ImageRuntime().NewFromLocal(name) + if err != nil { + return call.ReplyErrorOccurred(err.Error()) + } + imageInfo := &image.InfoImage{ + ID: img.ID(), + Tags: img.Names(), + } + layerInfo, err := image.GetLayersMapWithImageInfo(i.Runtime.ImageRuntime()) + if err != nil { + return call.ReplyErrorOccurred(err.Error()) + } + if err := image.BuildImageHierarchyMap(imageInfo, layerInfo, img.TopLayer()); err != nil { + return call.ReplyErrorOccurred(err.Error()) + } + b, err := json.Marshal(imageInfo) + if err != nil { + return call.ReplyErrorOccurred(err.Error()) + } + return call.ReplyBuildImageHierarchyMap(string(b)) +} diff --git a/pkg/varlinkapi/mount.go b/pkg/varlinkapi/mount.go index 3b4fe87e3..63ce44291 100644 --- a/pkg/varlinkapi/mount.go +++ b/pkg/varlinkapi/mount.go @@ -1,3 +1,5 @@ +// +build varlink + package varlinkapi import ( diff --git a/pkg/varlinkapi/pods.go b/pkg/varlinkapi/pods.go index c79cee4c2..ac8e24747 100644 --- a/pkg/varlinkapi/pods.go +++ b/pkg/varlinkapi/pods.go @@ -1,3 +1,5 @@ +// +build varlink + package varlinkapi import ( diff --git a/pkg/varlinkapi/remote_client.go b/pkg/varlinkapi/remote_client.go new file mode 100644 index 000000000..dd0613494 --- /dev/null +++ b/pkg/varlinkapi/remote_client.go @@ -0,0 +1,29 @@ +// +build varlink remoteclient + +package varlinkapi + +import ( + "github.com/containers/libpod/cmd/podman/varlink" + "github.com/containers/libpod/libpod" +) + +// ContainerStatsToLibpodContainerStats converts the varlink containerstats to a libpod +// container stats +func ContainerStatsToLibpodContainerStats(stats iopodman.ContainerStats) libpod.ContainerStats { + cstats := libpod.ContainerStats{ + ContainerID: stats.Id, + Name: stats.Name, + CPU: stats.Cpu, + CPUNano: uint64(stats.Cpu_nano), + SystemNano: uint64(stats.System_nano), + MemUsage: uint64(stats.Mem_usage), + MemLimit: uint64(stats.Mem_limit), + MemPerc: stats.Mem_perc, + NetInput: uint64(stats.Net_input), + NetOutput: uint64(stats.Net_output), + BlockInput: uint64(stats.Block_input), + BlockOutput: uint64(stats.Block_output), + PIDs: uint64(stats.Pids), + } + return cstats +} diff --git a/pkg/varlinkapi/system.go b/pkg/varlinkapi/system.go index 816143e9f..7f436a954 100644 --- a/pkg/varlinkapi/system.go +++ b/pkg/varlinkapi/system.go @@ -1,3 +1,5 @@ +// +build varlink + package varlinkapi import ( diff --git a/pkg/varlinkapi/transfers.go b/pkg/varlinkapi/transfers.go index 9a97bc810..96f76bcdc 100644 --- a/pkg/varlinkapi/transfers.go +++ b/pkg/varlinkapi/transfers.go @@ -1,3 +1,5 @@ +// +build varlink + package varlinkapi import ( diff --git a/pkg/varlinkapi/util.go b/pkg/varlinkapi/util.go index 7e487c03a..3c4b9b79a 100644 --- a/pkg/varlinkapi/util.go +++ b/pkg/varlinkapi/util.go @@ -1,3 +1,5 @@ +// +build varlink + package varlinkapi import ( diff --git a/pkg/varlinkapi/volumes.go b/pkg/varlinkapi/volumes.go index 02874d2b1..19ba38e7c 100644 --- a/pkg/varlinkapi/volumes.go +++ b/pkg/varlinkapi/volumes.go @@ -1,3 +1,5 @@ +// +build varlink + package varlinkapi import ( |