diff options
74 files changed, 4826 insertions, 2268 deletions
@@ -4,6 +4,7 @@ set -xeuo pipefail DIST=$(cat /etc/redhat-release | awk '{print $1}') IMAGE=registry.fedoraproject.org/fedora:27 PACKAGER=dnf +NOTEST=${NOTEST:-0} if [[ ${DIST} != "Fedora" ]]; then PACKAGER=yum IMAGE=registry.centos.org/centos/centos:7 @@ -44,9 +45,13 @@ if test -z "${INSIDE_CONTAINER:-}"; then --workdir /go/src/github.com/projectatomic/libpod \ -e INSIDE_CONTAINER=1 \ -e PYTHON=$PYTHON \ + -e NOTEST=$NOTEST \ ${IMAGE} /go/src/github.com/projectatomic/libpod/.papr.sh + if [[ "${NOTEST}" -eq 1 ]]; then + exit + fi systemd-detect-virt - ./test/test_runner.sh + script -qefc ./test/test_runner.sh exit 0 fi @@ -82,13 +87,16 @@ ${PACKAGER} install -y \ export GITVALIDATE_TIP=$(cd $GOSRC; git log -2 --pretty='%H' | tail -n 1) export TAGS="seccomp $($GOSRC/hack/btrfs_tag.sh) $($GOSRC/hack/libdm_tag.sh) $($GOSRC/hack/btrfs_installed_tag.sh) $($GOSRC/hack/ostree_tag.sh) $($GOSRC/hack/selinux_tag.sh)" -make gofmt TAGS="${TAGS}" -make testunit TAGS="${TAGS}" +if [[ "${NOTEST}" -eq 0 ]]; then + make gofmt TAGS="${TAGS}" + make testunit TAGS="${TAGS}" +fi + make install.tools TAGS="${TAGS}" # Only check lint and gitvalidation on more recent # distros with updated git and tooling -if [[ ${PACKAGER} != "yum" ]]; then +if [[ ${PACKAGER} != "yum" ]] && [[ "${NOTEST}" -eq 0 ]]; then HEAD=$GITVALIDATE_TIP make -C $GOSRC .gitvalidation TAGS="${TAGS}" make lint fi @@ -96,3 +104,8 @@ fi make TAGS="${TAGS}" make TAGS="${TAGS}" install PREFIX=/host/usr ETCDIR=/host/etc make TAGS="${TAGS}" test-binaries + +# Add the new bats core instead of the deprecated bats +git clone https://github.com/bats-core/bats-core /bats-core +cd /bats-core +install -D -m 755 libexec/* /host/usr/bin/ @@ -15,8 +15,12 @@ tests: - CRIO_ROOT=/var/tmp/checkout PODMAN_BINARY=/usr/bin/podman CONMON_BINARY=/usr/libexec/crio/conmon PAPR=1 sh .papr.sh packages: - - bats - containernetworking-cni + +extra-repos: + - name: updatestesting + baseurl: http://download.fedoraproject.org/pub/fedora/linux/updates/testing/27/x86_64/ + --- inherit: true diff --git a/.tool/lint b/.tool/lint index 72109a809..05495abe6 100755 --- a/.tool/lint +++ b/.tool/lint @@ -29,7 +29,6 @@ ${LINTER} \ --enable=gofmt\ --enable=golint\ --enable=ineffassign\ - --enable=interfacer\ --enable=megacheck\ --enable=misspell\ --enable=structcheck\ diff --git a/Dockerfile b/Dockerfile index 6764bdf1a..b562ddd1f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -68,7 +68,7 @@ RUN set -x \ && rm -rf "$GOPATH" # Install CNI plugins -ENV CNI_COMMIT dcf7368eeab15e2affc6256f0bb1e84dd46a34de +ENV CNI_COMMIT 7480240de9749f9a0a5c8614b17f1f03e0c06ab9 RUN set -x \ && export GOPATH="$(mktemp -d)" \ && git clone https://github.com/containernetworking/plugins.git "$GOPATH/src/github.com/containernetworking/plugins" \ @@ -1,5 +1,5 @@ GO ?= go -EPOCH_TEST_COMMIT ?= dd0d35d +EPOCH_TEST_COMMIT ?= 858116f HEAD ?= HEAD PROJECT := github.com/projectatomic/libpod GIT_BRANCH := $(shell git rev-parse --abbrev-ref HEAD 2>/dev/null) @@ -10,7 +10,7 @@ PREFIX ?= ${DESTDIR}/usr/local BINDIR ?= ${PREFIX}/bin LIBEXECDIR ?= ${PREFIX}/libexec MANDIR ?= ${PREFIX}/share/man -ETCDIR ?= /etc +ETCDIR ?= ${DESTDIR}/etc ETCDIR_LIBPOD ?= ${ETCDIR}/crio BUILDTAGS ?= seccomp $(shell hack/btrfs_tag.sh) $(shell hack/libdm_tag.sh) $(shell hack/btrfs_installed_tag.sh) $(shell hack/ostree_tag.sh) $(shell hack/selinux_tag.sh) @@ -135,13 +135,16 @@ docs/%.1: docs/%.1.md .gopathok docs: $(MANPAGES) +docker-docs: docs + (cd docs; ./dckrman.sh *.1) + install: .gopathok install.bin install.man install.cni install.bin: install ${SELINUXOPT} -D -m 755 bin/podman $(BINDIR)/podman install ${SELINUXOPT} -D -m 755 bin/conmon $(LIBEXECDIR)/crio/conmon -install.man: +install.man: docs install ${SELINUXOPT} -d -m 755 $(MANDIR)/man1 install ${SELINUXOPT} -m 644 $(filter %.1,$(MANPAGES)) -t $(MANDIR)/man1 @@ -154,7 +157,12 @@ install.completions: install ${SELINUXOPT} -m 644 -D completions/bash/podman ${BASHINSTALLDIR} install.cni: - install ${SELINUXOPT} -D -m 644 cni/97-podman-bridge.conf ${ETCDIR}/cni/net.d/97-podman-bridge.conf + install ${SELINUXOPT} -D -m 644 cni/87-podman-bridge.conflist ${ETCDIR}/cni/net.d/87-podman-bridge.conflist + +install.docker: docker-docs + install ${SELINUXOPT} -D -m 755 docker $(BINDIR)/docker + install ${SELINUXOPT} -d -m 755 $(MANDIR)/man1 + install ${SELINUXOPT} -m 644 docs/docker*.1 -t $(MANDIR)/man1 uninstall: rm -f $(LIBEXECDIR)/crio/conmon @@ -1,7 +1,7 @@ ![PODMAN logo](https://cdn.rawgit.com/kubernetes-incubator/cri-o/master/logo/crio-logo.svg) # libpod - library for running OCI-based containers in Pods -### Status: Development +### Status: Active Development ## What is the scope of this project? @@ -10,83 +10,51 @@ libpod also contains a tool podman, which allows you to manage Pods, Containers, At a high level, we expect the scope of libpod/podman to the following functionalities: -* Support multiple image formats including the existing Docker/OCI image formats -* Support for multiple means to download images including trust & image verification -* Container image management (managing image layers, overlay filesystems, etc) -* Container and POD process lifecycle management +* Support multiple image formats including the existing Docker/OCI image formats. +* Support for multiple means to download images including trust & image verification. +* Container image management (managing image layers, overlay filesystems, etc). +* Container and POD process lifecycle management. * Resource isolation of containers and PODS. ## What is not in scope for this project? -* Building container images. See Buildah +* Building container images. See Buildah. * Signing and pushing images to various image storages. See Skopeo. * Container Runtimes daemons for working with Kubernetes CRIs See CRI-O. +## OCI Projects Plans + The plan is to use OCI projects and best of breed libraries for different aspects: - Runtime: [runc](https://github.com/opencontainers/runc) (or any OCI runtime-spec implementation) and [oci runtime tools](https://github.com/opencontainers/runtime-tools) - Images: Image management using [containers/image](https://github.com/containers/image) - Storage: Storage and management of image layers using [containers/storage](https://github.com/containers/storage) - Networking: Networking support through use of [CNI](https://github.com/containernetworking/cni) -libpod is currently in active development. - -## Commands -| Command | Description | Demo| -| :------------------------------------------------------- | :------------------------------------------------------------------------ | :----| -| [podman(1)](/docs/podman.1.md) | Simple management tool for pods and images || -| [podman-attach(1)](/docs/podman-attach.1.md) | Attach to a running container |[![...](/docs/play.png)](https://asciinema.org/a/XDlocUrHVETFECg4zlO9nBbLf)| -| [podman-build(1)](/docs/podman-build.1.md) | Build an image using instructions from Dockerfiles || -| [podman-commit(1)](/docs/podman-commit.1.md) | Create new image based on the changed container || -| [podman-cp(1)](/docs/podman-cp.1.md) | Instead of providing a `podman cp` command, the man page `podman-cp` describes how to use the `podman mount` command to have even more flexibility and functionality|| -| [podman-create(1)](/docs/podman-create.1.md) | Create a new container || -| [podman-diff(1)](/docs/podman-diff.1.md) | Inspect changes on a container or image's filesystem |[![...](/docs/play.png)](https://asciinema.org/a/FXfWB9CKYFwYM4EfqW3NSZy1G)| -| [podman-exec(1)](/docs/podman-exec.1.md) | Execute a command in a running container -| [podman-export(1)](/docs/podman-export.1.md) | Export container's filesystem contents as a tar archive |[![...](/docs/play.png)](https://asciinema.org/a/913lBIRAg5hK8asyIhhkQVLtV)| -| [podman-history(1)](/docs/podman-history.1.md) | Shows the history of an image |[![...](/docs/play.png)](https://asciinema.org/a/bCvUQJ6DkxInMELZdc5DinNSx)| -| [podman-images(1)](/docs/podman-images.1.md) | List images in local storage |[![...](/docs/play.png)](https://asciinema.org/a/133649)| -| [podman-import(1)](/docs/podman-import.1.md) | Import a tarball and save it as a filesystem image || -| [podman-info(1)](/docs/podman-info.1.md) | Display system information |[![...](/docs/play.png)](https://asciinema.org/a/yKbi5fQ89y5TJ8e1RfJd4ivTD)| -| [podman-inspect(1)](/docs/podman-inspect.1.md) | Display the configuration of a container or image |[![...](/docs/play.png)](https://asciinema.org/a/133418)| -| [podman-kill(1)](/docs/podman-kill.1.md) | Kill the main process in one or more running containers |[![...](/docs/play.png)](https://asciinema.org/a/3jNos0A5yzO4hChu7ddKkUPw7)| -| [podman-load(1)](/docs/podman-load.1.md) | Load an image from docker archive or oci |[![...](/docs/play.png)](https://asciinema.org/a/kp8kOaexEhEa20P1KLZ3L5X4g)| -| [podman-login(1)](/docs/podman-login.1.md) | Login to a container registry |[![...](/docs/play.png)](https://asciinema.org/a/oNiPgmfo1FjV2YdesiLpvihtV)| -| [podman-logout(1)](/docs/podman-logout.1.md) | Logout of a container registry |[![...](/docs/play.png)](https://asciinema.org/a/oNiPgmfo1FjV2YdesiLpvihtV)| -| [podman-logs(1)](/docs/podman-logs.1.md) | Display the logs of a container |[![...](/docs/play.png)](https://asciinema.org/a/MZPTWD5CVs3dMREkBxQBY9C5z)| -| [podman-mount(1)](/docs/podman-mount.1.md) | Mount a working container's root filesystem |[![...](/docs/play.png)](https://asciinema.org/a/YSP6hNvZo0RGeMHDA97PhPAf3)| -| [podman-pause(1)](/docs/podman-pause.1.md) | Pause one or more running containers |[![...](/docs/play.png)](https://asciinema.org/a/141292)| -| [podman-ps(1)](/docs/podman-ps.1.md) | Prints out information about containers |[![...](/docs/play.png)](https://asciinema.org/a/bbT41kac6CwZ5giESmZLIaTLR)| -| [podman-pull(1)](/docs/podman-pull.1.md) | Pull an image from a registry |[![...](/docs/play.png)](https://asciinema.org/a/lr4zfoynHJOUNu1KaXa1dwG2X)| -| [podman-push(1)](/docs/podman-push.1.md) | Push an image to a specified destination |[![...](/docs/play.png)](https://asciinema.org/a/133276)| -| [podman-rm(1)](/docs/podman-rm.1.md) | Removes one or more containers |[![...](/docs/play.png)](https://asciinema.org/a/7EMk22WrfGtKWmgHJX9Nze1Qp)| -| [podman-rmi(1)](/docs/podman-rmi.1.md) | Removes one or more images |[![...](/docs/play.png)](https://asciinema.org/a/133799)| -| [podman-save(1)](/docs/podman-save.1.md) | Saves an image to an archive |[![...](/docs/play.png)](https://asciinema.org/a/kp8kOaexEhEa20P1KLZ3L5X4g)| -| [podman-start(1)](/docs/podman-start.1.md) | Starts one or more containers -| [podman-stats(1)](/docs/podman-stats.1.md) | Display a live stream of one or more containers' resource usage statistics|[![...](/docs/play.png)](https://asciinema.org/a/vfUPbAA5tsNWhsfB9p25T6xdr)| -| [podman-stop(1)](/docs/podman-stop.1.md) | Stops one or more running containers |[![...](/docs/play.png)](https://asciinema.org/a/KNRF9xVXeaeNTNjBQVogvZBcp)| -| [podman-tag(1)](/docs/podman-tag.1.md) | Add an additional name to a local image |[![...](/docs/play.png)](https://asciinema.org/a/133803)| -| [podman-top(1)](/docs/podman-top.1.md) | Display the running processes of a container |[![...](/docs/play.png)](https://asciinema.org/a/5WCCi1LXwSuRbvaO9cBUYf3fk)| -| [podman-umount(1)](/docs/podman-umount.1.md) | Unmount a working container's root filesystem |[![...](/docs/play.png)](https://asciinema.org/a/MZPTWD5CVs3dMREkBxQBY9C5z)| -| [podman-unpause(1)](/docs/podman-unpause.1.md) | Unpause one or more running containers |[![...](/docs/play.png)](https://asciinema.org/a/141292)| -| [podman-version(1)](/docs/podman-version.1.md) | Display the version information |[![...](/docs/play.png)](https://asciinema.org/a/mfrn61pjZT9Fc8L4NbfdSqfgu)| -| [podman-wait(1)](/docs/podman-wait.1.md) | Wait on one or more containers to stop and print their exit codes |[![...](/docs/play.png)](https://asciinema.org/a/QNPGKdjWuPgI96GcfkycQtah0)| - -## OCI Hooks Support - -[PODMAN configures OCI Hooks to run when launching a container](./hooks.md) - -## PODMAN Usage Transfer - -[Useful information for ops and dev transfer as it relates to infrastructure that utilizes PODMAN](/transfer.md) - -## Communication +## Podman Information for Developers + +**[Installation notes](/install.md)** +Information on how to install Podman in your environment. + +**[OCI Hooks Support](/hooks.md)** +Information on how Podman configures OCI Hooks to run when launching a container. + +**[Podman Commands](/commands.md)** +A list of the Podman commands with links to their man pages and in many cases videos +showing the commands in use. + +**[Podman Usage Transfer](/transfer.md)** +Useful information for ops and dev transfer as it relates to infrastructure that utilizes Podman. This page +includes tables showing Docker commands and their Podman equivalent commands. + +**[Tutorials](docs/tutorials/tutorials.md)** +Tutorials on the Podman utility. + +## Communication with Fellow Developers For async communication and long running discussions please use issues and pull requests on the github repo. This will be the best place to discuss design and implementation. For sync communication we have an IRC channel #PODMAN, on chat.freenode.net, that everyone is welcome to join and chat about development. -## [Podman tutorial](podman_tutorial.md) -For more information on how to build, install, and use podman, check out the [podman tutorial](podman_tutorial.md). - ### Current Roadmap 1. Basic pod/container lifecycle, basic image pull (done) diff --git a/cmd/podman/common.go b/cmd/podman/common.go index e0ef43782..ae4bbd65d 100644 --- a/cmd/podman/common.go +++ b/cmd/podman/common.go @@ -173,7 +173,7 @@ var createFlags = []cli.Flag{ Name: "cpu-shares", Usage: "CPU shares (relative weight)", }, - cli.StringFlag{ + cli.Float64Flag{ Name: "cpus", Usage: "Number of CPUs. The default is 0.000 which means no limit", }, @@ -316,7 +316,8 @@ var createFlags = []cli.Flag{ }, cli.StringFlag{ Name: "net", - Usage: "Setup the network namespace", + Usage: "Connect a container to a network (alias for --network)", + Value: "bridge", }, cli.StringFlag{ Name: "network", diff --git a/cmd/podman/create.go b/cmd/podman/create.go index ead2f6735..80cb7f432 100644 --- a/cmd/podman/create.go +++ b/cmd/podman/create.go @@ -4,6 +4,7 @@ import ( "encoding/json" "fmt" "io" + "net" "os" "strconv" "strings" @@ -47,7 +48,7 @@ type createResourceConfig struct { CPURtPeriod uint64 // cpu-rt-period CPURtRuntime int64 // cpu-rt-runtime CPUShares uint64 // cpu-shares - CPUs string // cpus + CPUs float64 // cpus CPUsetCPUs string CPUsetMems string // cpuset-mems DeviceReadBps []string // device-read-bps @@ -83,6 +84,7 @@ type createConfig struct { Env map[string]string //env ExposedPorts map[nat.Port]struct{} GroupAdd []uint32 // group-add + HostAdd []string //add-host Hostname string //hostname Image string ImageID string @@ -114,7 +116,6 @@ type createConfig struct { SigProxy bool //sig-proxy StopSignal syscall.Signal // stop-signal StopTimeout uint // stop-timeout - StorageOpts []string //storage-opt Sysctl map[string]string //sysctl Tmpfs []string // tmpfs Tty bool //tty @@ -171,6 +172,9 @@ func createCmd(c *cli.Context) error { defer runtime.Shutdown(false) imageName, _, data, err := imageData(c, runtime, c.Args()[0]) + if err != nil { + return err + } createConfig, err := parseCreateOpts(c, runtime, imageName, data) if err != nil { return err @@ -216,8 +220,6 @@ func createCmd(c *cli.Context) error { return nil } -const seccompDefaultPath = "/etc/crio/seccomp.json" - func parseSecurityOpt(config *createConfig, securityOpts []string) error { var ( labelOpts []string @@ -267,28 +269,56 @@ func parseSecurityOpt(config *createConfig, securityOpts []string) error { } if config.SeccompProfilePath == "" { - if _, err := os.Stat(seccompDefaultPath); err != nil { + if _, err := os.Stat(libpod.SeccompOverridePath); err == nil { + config.SeccompProfilePath = libpod.SeccompOverridePath + } else { if !os.IsNotExist(err) { - return errors.Wrapf(err, "can't check if %q exists", seccompDefaultPath) + return errors.Wrapf(err, "can't check if %q exists", libpod.SeccompOverridePath) + } + if _, err := os.Stat(libpod.SeccompDefaultPath); err != nil { + if !os.IsNotExist(err) { + return errors.Wrapf(err, "can't check if %q exists", libpod.SeccompDefaultPath) + } + } else { + config.SeccompProfilePath = libpod.SeccompDefaultPath } - } else { - config.SeccompProfilePath = seccompDefaultPath } } config.ProcessLabel, config.MountLabel, err = label.InitLabels(labelOpts) return err } -func exposedPorts(c *cli.Context, imageExposedPorts map[string]struct{}) (map[nat.Port]struct{}, map[nat.Port][]nat.PortBinding, error) { - // TODO Handle exposed ports from image - // Currently ignoring imageExposedPorts +// isPortInPortBindings determines if an exposed host port is in user +// provided ports +func isPortInPortBindings(pb map[nat.Port][]nat.PortBinding, port nat.Port) bool { + var hostPorts []string + for _, i := range pb { + hostPorts = append(hostPorts, i[0].HostPort) + } + return libpod.StringInSlice(port.Port(), hostPorts) +} - ports, portBindings, err := nat.ParsePortSpecs(c.StringSlice("publish")) +func exposedPorts(c *cli.Context, imageExposedPorts map[string]struct{}) (map[nat.Port]struct{}, map[nat.Port][]nat.PortBinding, error) { + var exposedPorts []string + var ports map[nat.Port]struct{} + ports = make(map[nat.Port]struct{}) + _, portBindings, err := nat.ParsePortSpecs(c.StringSlice("publish")) if err != nil { return nil, nil, err } - for _, e := range c.StringSlice("expose") { + // Parse the ports from the image itself + for i := range imageExposedPorts { + fields := strings.Split(i, "/") + if len(fields) > 2 { + return nil, nil, errors.Errorf("invalid exposed port format in image") + } + exposedPorts = append(exposedPorts, fields[0]) + } + + // Add the ports from the image to the ports from the user + exposedPorts = append(exposedPorts, c.StringSlice("expose")...) + for _, e := range exposedPorts { // Merge in exposed ports to the map of published ports if strings.Contains(e, ":") { return nil, nil, fmt.Errorf("invalid port format for --expose: %s", e) @@ -306,6 +336,28 @@ func exposedPorts(c *cli.Context, imageExposedPorts map[string]struct{}) (map[na if err != nil { return nil, nil, err } + // check if the port in question is already being used + if isPortInPortBindings(portBindings, p) { + return nil, nil, errors.Errorf("host port %s already used in --publish option", p.Port()) + } + + if c.Bool("publish-all") { + l, err := net.Listen("tcp", ":0") + if err != nil { + return nil, nil, errors.Wrapf(err, "unable to get free port") + } + _, randomPort, err := net.SplitHostPort(l.Addr().String()) + if err != nil { + return nil, nil, errors.Wrapf(err, "unable to determine free port") + } + rp, err := strconv.Atoi(randomPort) + if err != nil { + return nil, nil, errors.Wrapf(err, "unable to convert random port to int") + } + logrus.Debug(fmt.Sprintf("Using random host port %s with container port %d", randomPort, p.Int())) + portBindings[p] = CreatePortBinding(rp, "") + continue + } if _, exists := ports[p]; !exists { ports[p] = struct{}{} } @@ -371,7 +423,6 @@ func imageData(c *cli.Context, runtime *libpod.Runtime, image string) (string, s // Parses CLI options related to container creation into a config which can be // parsed into an OCI runtime spec func parseCreateOpts(c *cli.Context, runtime *libpod.Runtime, imageName string, data *libpod.ImageData) (*createConfig, error) { - //imageName, imageID, data, err := imageData(c, runtime, image) var command []string var memoryLimit, memoryReservation, memorySwap, memoryKernel int64 var blkioWeight uint16 @@ -382,9 +433,9 @@ func parseCreateOpts(c *cli.Context, runtime *libpod.Runtime, imageName string, command = c.Args()[1:] } - sysctl, err := convertStringSliceToMap(c.StringSlice("sysctl"), "=") + sysctl, err := validateSysctl(c.StringSlice("sysctl")) if err != nil { - return nil, errors.Wrapf(err, "sysctl values must be in the form of KEY=VALUE") + return nil, errors.Wrapf(err, "invalid value for sysctl") } groupAdd, err := stringSlicetoUint32Slice(c.StringSlice("group-add")) @@ -444,6 +495,12 @@ func parseCreateOpts(c *cli.Context, runtime *libpod.Runtime, imageName string, if c.Bool("detach") && c.Bool("rm") { return nil, errors.Errorf("--rm and --detach can not be specified together") } + if c.Int64("cpu-period") != 0 && c.Float64("cpus") > 0 { + return nil, errors.Errorf("--cpu-period and --cpus cannot be set together") + } + if c.Int64("cpu-quota") != 0 && c.Float64("cpus") > 0 { + return nil, errors.Errorf("--cpu-quota and --cpus cannot be set together") + } utsMode := container.UTSMode(c.String("uts")) if !utsMode.Valid() { @@ -536,6 +593,29 @@ func parseCreateOpts(c *cli.Context, runtime *libpod.Runtime, imageName string, if err != nil { return nil, errors.Wrapf(err, "unable to translate --shm-size") } + // Network + // Both --network and --net have default values of 'bridge' + // --net only overrides --network when --network is not explicitly + // set and --net is. + if c.IsSet("network") && c.IsSet("net") { + return nil, errors.Errorf("cannot use --network and --net together. use only --network instead") + } + networkMode := c.String("network") + if !c.IsSet("network") && c.IsSet("net") { + networkMode = c.String("net") + } + + // Verify the additional hosts are in correct format + for _, host := range c.StringSlice("add-host") { + if _, err := validateExtraHost(host); err != nil { + return nil, err + } + } + + // Check for . and dns-search domains + if libpod.StringInSlice(".", c.StringSlice("dns-search")) && len(c.StringSlice("dns-search")) > 1 { + return nil, errors.Errorf("cannot pass additional search domains when also specifying '.'") + } config := &createConfig{ Runtime: runtime, @@ -553,6 +633,7 @@ func parseCreateOpts(c *cli.Context, runtime *libpod.Runtime, imageName string, ExposedPorts: ports, GroupAdd: groupAdd, Hostname: c.String("hostname"), + HostAdd: c.StringSlice("add-host"), Image: imageName, ImageID: imageID, Interactive: c.Bool("interactive"), @@ -564,10 +645,10 @@ func parseCreateOpts(c *cli.Context, runtime *libpod.Runtime, imageName string, LogDriverOpt: c.StringSlice("log-opt"), MacAddress: c.String("mac-address"), Name: c.String("name"), - Network: c.String("network"), + Network: networkMode, NetworkAlias: c.StringSlice("network-alias"), IpcMode: ipcMode, - NetMode: container.NetworkMode(c.String("network")), + NetMode: container.NetworkMode(networkMode), UtsMode: utsMode, PidMode: pidMode, Pod: c.String("pod"), @@ -582,12 +663,12 @@ func parseCreateOpts(c *cli.Context, runtime *libpod.Runtime, imageName string, BlkioWeightDevice: c.StringSlice("blkio-weight-device"), CPUShares: c.Uint64("cpu-shares"), CPUPeriod: c.Uint64("cpu-period"), - CPUsetCPUs: c.String("cpu-period"), + CPUsetCPUs: c.String("cpuset-cpus"), CPUsetMems: c.String("cpuset-mems"), CPUQuota: c.Int64("cpu-quota"), CPURtPeriod: c.Uint64("cpu-rt-period"), CPURtRuntime: c.Int64("cpu-rt-runtime"), - CPUs: c.String("cpus"), + CPUs: c.Float64("cpus"), DeviceReadBps: c.StringSlice("device-read-bps"), DeviceReadIOps: c.StringSlice("device-read-iops"), DeviceWriteBps: c.StringSlice("device-write-bps"), @@ -609,7 +690,6 @@ func parseCreateOpts(c *cli.Context, runtime *libpod.Runtime, imageName string, SigProxy: c.Bool("sig-proxy"), StopSignal: stopSignal, StopTimeout: c.Uint("stop-timeout"), - StorageOpts: c.StringSlice("storage-opt"), Sysctl: sysctl, Tmpfs: c.StringSlice("tmpfs"), Tty: tty, @@ -633,3 +713,12 @@ func parseCreateOpts(c *cli.Context, runtime *libpod.Runtime, imageName string, } return config, nil } + +//CreatePortBinding takes port (int) and IP (string) and creates an array of portbinding structs +func CreatePortBinding(hostPort int, hostIP string) []nat.PortBinding { + pb := nat.PortBinding{ + HostPort: strconv.Itoa(hostPort), + } + pb.HostIP = hostIP + return []nat.PortBinding{pb} +} diff --git a/cmd/podman/create_cli.go b/cmd/podman/create_cli.go index 0cc265e92..24856feb8 100644 --- a/cmd/podman/create_cli.go +++ b/cmd/podman/create_cli.go @@ -24,14 +24,45 @@ func getAllLabels(labelFile, inputLabels []string) (map[string]string, error) { return labels, nil } -func convertStringSliceToMap(strSlice []string, delimiter string) (map[string]string, error) { +// validateSysctl validates a sysctl and returns it. +func validateSysctl(strSlice []string) (map[string]string, error) { sysctl := make(map[string]string) - for _, inputSysctl := range strSlice { - values := strings.Split(inputSysctl, delimiter) - if len(values) < 2 { - return sysctl, errors.Errorf("%s in an invalid sysctl value", inputSysctl) + validSysctlMap := map[string]bool{ + "kernel.msgmax": true, + "kernel.msgmnb": true, + "kernel.msgmni": true, + "kernel.sem": true, + "kernel.shmall": true, + "kernel.shmmax": true, + "kernel.shmmni": true, + "kernel.shm_rmid_forced": true, + } + validSysctlPrefixes := []string{ + "net.", + "fs.mqueue.", + } + + for _, val := range strSlice { + foundMatch := false + arr := strings.Split(val, "=") + if len(arr) < 2 { + return nil, errors.Errorf("%s is invalid, sysctl values must be in the form of KEY=VALUE", val) + } + if validSysctlMap[arr[0]] { + sysctl[arr[0]] = arr[1] + continue + } + + for _, prefix := range validSysctlPrefixes { + if strings.HasPrefix(arr[0], prefix) { + sysctl[arr[0]] = arr[1] + foundMatch = true + break + } + } + if !foundMatch { + return nil, errors.Errorf("sysctl '%s' is not whitelisted", arr[0]) } - sysctl[values[0]] = values[1] } return sysctl, nil } diff --git a/cmd/podman/create_cli_test.go b/cmd/podman/create_cli_test.go index 63a1e5dd3..fa128c8e6 100644 --- a/cmd/podman/create_cli_test.go +++ b/cmd/podman/create_cli_test.go @@ -28,15 +28,15 @@ func createTmpFile(content []byte) (string, error) { return tmpfile.Name(), nil } -func TestConvertStringSliceToMap(t *testing.T) { - strSlice := []string{"BLAU=BLUE", "GELB=YELLOW"} - result, _ := convertStringSliceToMap(strSlice, "=") - assert.Equal(t, result["BLAU"], "BLUE") +func TestValidateSysctl(t *testing.T) { + strSlice := []string{"net.core.test1=4", "kernel.msgmax=2"} + result, _ := validateSysctl(strSlice) + assert.Equal(t, result["net.core.test1"], "4") } -func TestConvertStringSliceToMapBadData(t *testing.T) { +func TestValidateSysctlBadSysctl(t *testing.T) { strSlice := []string{"BLAU=BLUE", "GELB^YELLOW"} - _, err := convertStringSliceToMap(strSlice, "=") + _, err := validateSysctl(strSlice) assert.Error(t, err) } diff --git a/cmd/podman/parse.go b/cmd/podman/parse.go index bb45d08c4..33988a3b6 100644 --- a/cmd/podman/parse.go +++ b/cmd/podman/parse.go @@ -696,21 +696,6 @@ func parseSecurityOpts(securityOpts []string) ([]string, error) { //nolint return securityOpts, nil } -// parses storage options per container into a map -// for storage-opt flag -func parseStorageOpts(storageOpts []string) (map[string]string, error) { //nolint - m := make(map[string]string) - for _, option := range storageOpts { - if strings.Contains(option, "=") { - opt := strings.SplitN(option, "=", 2) - m[opt[0]] = opt[1] - } else { - return nil, errors.Errorf("invalid storage option %q", option) - } - } - return m, nil -} - // convertKVStringsToMap converts ["key=value"] to {"key":"value"} func convertKVStringsToMap(values []string) map[string]string { result := make(map[string]string, len(values)) diff --git a/cmd/podman/ps.go b/cmd/podman/ps.go index c674c9d1e..ef5d40c43 100644 --- a/cmd/podman/ps.go +++ b/cmd/podman/ps.go @@ -1,6 +1,7 @@ package main import ( + "encoding/json" "fmt" "os" "path/filepath" @@ -10,15 +11,19 @@ import ( "strings" "time" + "github.com/cri-o/ocicni/pkg/ocicni" "github.com/docker/go-units" specs "github.com/opencontainers/runtime-spec/specs-go" "github.com/pkg/errors" "github.com/projectatomic/libpod/cmd/podman/formats" "github.com/projectatomic/libpod/libpod" + "github.com/sirupsen/logrus" "github.com/urfave/cli" "k8s.io/apimachinery/pkg/fields" ) +const mountTruncLength = 12 + type psOptions struct { all bool filter string @@ -62,12 +67,13 @@ type psJSONParams struct { ID string `json:"id"` Image string `json:"image"` ImageID string `json:"image_id"` - Command string `json:"command"` + Command []string `json:"command"` CreatedAt time.Time `json:"createdAt"` RunningFor time.Duration `json:"runningFor"` Status string `json:"status"` Ports map[string]struct{} `json:"ports"` - Size uint `json:"size"` + RootFsSize int64 `json:"rootFsSize"` + RWSize int64 `json:"rwSize"` Names string `json:"names"` Labels fields.Set `json:"labels"` Mounts []specs.Mount `json:"mounts"` @@ -241,7 +247,7 @@ func generateContainerFilterFuncs(filter, filterValue string, runtime *libpod.Ru switch filter { case "id": return func(c *libpod.Container) bool { - return c.ID() == filterValue + return strings.Contains(c.ID(), filterValue) }, nil case "label": return func(c *libpod.Container) bool { @@ -254,7 +260,7 @@ func generateContainerFilterFuncs(filter, filterValue string, runtime *libpod.Ru }, nil case "name": return func(c *libpod.Container) bool { - return c.Name() == filterValue + return strings.Contains(c.Name(), filterValue) }, nil case "exited": exitCode, err := strconv.ParseInt(filterValue, 10, 32) @@ -277,14 +283,18 @@ func generateContainerFilterFuncs(filter, filterValue string, runtime *libpod.Ru if err != nil { return false } - return status.String() == filterValue + state := status.String() + if status == libpod.ContainerStateConfigured { + state = "created" + } + return state == filterValue }, nil case "ancestor": // This needs to refine to match docker // - ancestor=(<image-name>[:tag]|<image-id>| ⟨image@digest⟩) - containers created from an image or a descendant. return func(c *libpod.Container) bool { containerConfig := c.Config() - if containerConfig.RootfsImageID == filterValue || containerConfig.RootfsImageName == filterValue { + if strings.Contains(containerConfig.RootfsImageID, filterValue) || strings.Contains(containerConfig.RootfsImageName, filterValue) { return true } return false @@ -315,8 +325,21 @@ func generateContainerFilterFuncs(filter, filterValue string, runtime *libpod.Ru //- volume=(<volume-name>|<mount-point-destination>) return func(c *libpod.Container) bool { containerConfig := c.Config() - //TODO We need to still lookup against volumes too - return containerConfig.MountLabel == filterValue + var dest string + arr := strings.Split(filterValue, ":") + source := arr[0] + if len(arr) == 2 { + dest = arr[1] + } + for _, mount := range containerConfig.Spec.Mounts { + if dest != "" && (mount.Source == source && mount.Destination == dest) { + return true + } + if dest == "" && mount.Source == source { + return true + } + } + return false }, nil } return nil, errors.Errorf("%s is an invalid filter", filter) @@ -393,15 +416,32 @@ func getTemplateOutput(containers []*libpod.Container, opts psOptions) ([]psTemp runningFor := units.HumanDuration(time.Since(conConfig.CreatedTime)) createdAt := runningFor + " ago" imageName := conConfig.RootfsImageName + rootFsSize, err := ctr.RootFsSize() + if err != nil { + logrus.Errorf("error getting root fs size for %q: %v", ctr.ID(), err) + } + rwSize, err := ctr.RWSize() + if err != nil { + logrus.Errorf("error getting rw size for %q: %v", ctr.ID(), err) + } + + var createArtifact createConfig + artifact, err := ctr.GetArtifact("create-config") + if err == nil { + if err := json.Unmarshal(artifact, &createArtifact); err != nil { + return nil, err + } + } else { + logrus.Errorf("couldn't get some ps information, error getting artifact %q: %v", ctr.ID(), err) + } // TODO We currently dont have the ability to get many of // these data items. Uncomment as progress is made - //command := getStrFromSquareBrackets(ctr.ImageCreatedBy) command := strings.Join(ctr.Spec().Process.Args, " ") - //mounts := getMounts(ctr.Mounts, opts.noTrunc) - //ports := getPorts(ctr.Config.ExposedPorts) - //size := units.HumanSize(float64(ctr.SizeRootFs)) + ports := getPorts(ctr.Config().PortMappings) + mounts := getMounts(createArtifact.Volumes, opts.noTrunc) + size := units.HumanSizeWithPrecision(float64(rwSize), 3) + " (virtual " + units.HumanSizeWithPrecision(float64(rootFsSize), 3) + ")" labels := formatLabels(ctr.Labels()) ns := getNamespaces(pid) @@ -412,7 +452,7 @@ func getTemplateOutput(containers []*libpod.Container, opts psOptions) ([]psTemp status = "Up " + runningFor + " ago" case libpod.ContainerStatePaused: status = "Paused" - case libpod.ContainerStateCreated: + case libpod.ContainerStateCreated, libpod.ContainerStateConfigured: status = "Created" default: status = "Dead" @@ -433,19 +473,19 @@ func getTemplateOutput(containers []*libpod.Container, opts psOptions) ([]psTemp CreatedAt: createdAt, RunningFor: runningFor, Status: status, - //Ports: ports, - //Size: size, - Names: ctr.Name(), - Labels: labels, - //Mounts: mounts, - PID: pid, - Cgroup: ns.Cgroup, - IPC: ns.IPC, - MNT: ns.MNT, - NET: ns.NET, - PIDNS: ns.PID, - User: ns.User, - UTS: ns.UTS, + Ports: ports, + Size: size, + Names: ctr.Name(), + Labels: labels, + Mounts: mounts, + PID: pid, + Cgroup: ns.Cgroup, + IPC: ns.IPC, + MNT: ns.MNT, + NET: ns.NET, + PIDNS: ns.PID, + User: ns.User, + UTS: ns.UTS, } psOutput = append(psOutput, params) } @@ -499,19 +539,28 @@ func getJSONOutput(containers []*libpod.Container, nSpace bool) ([]psJSONParams, if err != nil { return psOutput, errors.Wrapf(err, "unable to obtain container state for JSON output") } + rootFsSize, err := ctr.RootFsSize() + if err != nil { + logrus.Errorf("error getting root fs size for %q: %v", ctr.ID(), err) + } + rwSize, err := ctr.RWSize() + if err != nil { + logrus.Errorf("error getting rw size for %q: %v", ctr.ID(), err) + } + params := psJSONParams{ // TODO When we have ability to obtain the commented out data, we need // TODO to add it - ID: ctr.ID(), - Image: cc.RootfsImageName, - ImageID: cc.RootfsImageID, - //Command: getStrFromSquareBrackets(ctr.ImageCreatedBy), - Command: strings.Join(ctr.Spec().Process.Args, " "), + ID: ctr.ID(), + Image: cc.RootfsImageName, + ImageID: cc.RootfsImageID, + Command: ctr.Spec().Process.Args, CreatedAt: cc.CreatedTime, RunningFor: time.Since(cc.CreatedTime), Status: conState.String(), //Ports: cc.Spec.Linux.Resources.Network. - //Size: ctr.SizeRootFs, + RootFsSize: rootFsSize, + RWSize: rwSize, Names: cc.Name, Labels: cc.Labels, Mounts: cc.Spec.Mounts, @@ -570,37 +619,36 @@ func formatLabels(labels map[string]string) string { return "" } -/* // getMounts converts the volumes mounted to a string of the form "mount1, mount2" // it truncates it if noTrunc is false -func getMounts(mounts []specs.Mount, noTrunc bool) string { +func getMounts(mounts []string, noTrunc bool) string { var arr []string if len(mounts) == 0 { return "" } for _, mount := range mounts { - if noTrunc { - arr = append(arr, mount.Source) + splitArr := strings.Split(mount, ":") + if len(splitArr[0]) > mountTruncLength && !noTrunc { + arr = append(arr, splitArr[0][:mountTruncLength]+"...") continue } - tempArr := strings.SplitAfter(mount.Source, "/") - if len(tempArr) >= 3 { - arr = append(arr, strings.Join(tempArr[:3], "")) - } else { - arr = append(arr, mount.Source) - } + arr = append(arr, splitArr[0]) } return strings.Join(arr, ",") } + // getPorts converts the ports used to a string of the from "port1, port2" -func getPorts(ports map[string]struct{}) string { - var arr []string +func getPorts(ports []ocicni.PortMapping) string { + var portDisplay []string if len(ports) == 0 { return "" } - for key := range ports { - arr = append(arr, key) + for _, v := range ports { + hostIP := v.HostIP + if hostIP == "" { + hostIP = "0.0.0.0" + } + portDisplay = append(portDisplay, fmt.Sprintf("%s:%d->%d/%s", hostIP, v.HostPort, v.ContainerPort, v.Protocol)) } - return strings.Join(arr, ",") + return strings.Join(portDisplay, ", ") } -*/ diff --git a/cmd/podman/rm.go b/cmd/podman/rm.go index 8dd3475c0..182089e8e 100644 --- a/cmd/podman/rm.go +++ b/cmd/podman/rm.go @@ -80,13 +80,6 @@ func rmCmd(c *cli.Context) error { } } for _, container := range delContainers { - if err != nil { - if lastError != nil { - fmt.Fprintln(os.Stderr, lastError) - } - lastError = errors.Wrapf(err, "failed to find container %s", container.ID()) - continue - } err = runtime.RemoveContainer(container, c.Bool("force")) if err != nil { if lastError != nil { diff --git a/cmd/podman/run.go b/cmd/podman/run.go index eecfe87b3..97f60cdbf 100644 --- a/cmd/podman/run.go +++ b/cmd/podman/run.go @@ -46,6 +46,10 @@ func runCmd(c *cli.Context) error { } imageName, _, data, err := imageData(c, runtime, c.Args()[0]) + if err != nil { + return err + } + createConfig, err := parseCreateOpts(c, runtime, imageName, data) if err != nil { return err diff --git a/cmd/podman/run_test.go b/cmd/podman/run_test.go index 622e75d3e..b82df86db 100644 --- a/cmd/podman/run_test.go +++ b/cmd/podman/run_test.go @@ -3,6 +3,7 @@ package main import ( "testing" + units "github.com/docker/go-units" ociv1 "github.com/opencontainers/image-spec/specs-go/v1" spec "github.com/opencontainers/runtime-spec/specs-go" "github.com/projectatomic/libpod/libpod" @@ -65,11 +66,24 @@ func createCLI() cli.App { return a } -func getRuntimeSpec(c *cli.Context) *spec.Spec { - runtime, _ := getRuntime(c) - createConfig, _ := parseCreateOpts(c, runtime, "alpine", generateAlpineImageData()) - runtimeSpec, _ := createConfigToOCISpec(createConfig) - return runtimeSpec +func getRuntimeSpec(c *cli.Context) (*spec.Spec, error) { + /* + TODO: This test has never worked. Need to install content + runtime, err := getRuntime(c) + if err != nil { + return nil, err + } + createConfig, err := parseCreateOpts(c, runtime, "alpine", generateAlpineImageData()) + */ + createConfig, err := parseCreateOpts(c, nil, "alpine", generateAlpineImageData()) + if err != nil { + return nil, err + } + runtimeSpec, err := createConfigToOCISpec(createConfig) + if err != nil { + return nil, err + } + return runtimeSpec, nil } // TestPIDsLimit verifies the inputed pid-limit is correctly defined in the spec @@ -77,6 +91,34 @@ func TestPIDsLimit(t *testing.T) { a := createCLI() args := []string{"--pids-limit", "22"} a.Run(append(cmd, args...)) - runtimeSpec := getRuntimeSpec(CLI) + runtimeSpec, err := getRuntimeSpec(CLI) + if err != nil { + t.Fatalf(err.Error()) + } assert.Equal(t, runtimeSpec.Linux.Resources.Pids.Limit, int64(22)) } + +// TestBLKIOWeightDevice verifies the inputed blkio weigh device is correctly defined in the spec +func TestBLKIOWeightDevice(t *testing.T) { + a := createCLI() + args := []string{"--blkio-weight-device", "/dev/sda:100"} + a.Run(append(cmd, args...)) + runtimeSpec, err := getRuntimeSpec(CLI) + if err != nil { + t.Fatalf(err.Error()) + } + assert.Equal(t, *runtimeSpec.Linux.Resources.BlockIO.WeightDevice[0].Weight, uint16(100)) +} + +// TestMemorySwap verifies that the inputed memory swap is correctly defined in the spec +func TestMemorySwap(t *testing.T) { + a := createCLI() + args := []string{"--memory-swap", "45m", "--memory", "40m"} + a.Run(append(cmd, args...)) + runtimeSpec, err := getRuntimeSpec(CLI) + if err != nil { + t.Fatalf(err.Error()) + } + mem, _ := units.RAMInBytes("45m") + assert.Equal(t, *runtimeSpec.Linux.Resources.Memory.Swap, mem) +} diff --git a/cmd/podman/spec.go b/cmd/podman/spec.go index d630b2f50..cb9efdcb2 100644 --- a/cmd/podman/spec.go +++ b/cmd/podman/spec.go @@ -1,14 +1,14 @@ package main import ( - "encoding/json" - "fmt" "io/ioutil" + "strconv" "strings" "github.com/cri-o/ocicni/pkg/ocicni" "github.com/docker/docker/daemon/caps" "github.com/docker/docker/pkg/mount" + "github.com/docker/docker/profiles/seccomp" "github.com/docker/go-units" "github.com/opencontainers/runc/libcontainer/devices" spec "github.com/opencontainers/runtime-spec/specs-go" @@ -21,6 +21,8 @@ import ( "golang.org/x/sys/unix" ) +const cpuPeriod = 100000 + func blockAccessToKernelFilesystems(config *createConfig, g *generate.Generator) { if !config.Privileged { for _, mp := range []string{ @@ -51,21 +53,10 @@ func blockAccessToKernelFilesystems(config *createConfig, g *generate.Generator) func addPidNS(config *createConfig, g *generate.Generator) error { pidMode := config.PidMode if pidMode.IsHost() { - return g.RemoveLinuxNamespace(libpod.PIDNamespace) + return g.RemoveLinuxNamespace(string(spec.PIDNamespace)) } if pidMode.IsContainer() { - ctr, err := config.Runtime.LookupContainer(pidMode.Container()) - if err != nil { - return errors.Wrapf(err, "container %q not found", pidMode.Container()) - } - pid, err := ctr.PID() - if err != nil { - return errors.Wrapf(err, "Failed to get pid of container %q", pidMode.Container()) - } - pidNsPath := fmt.Sprintf("/proc/%d/ns/pid", pid) - if err := g.AddOrReplaceLinuxNamespace(libpod.PIDNamespace, pidNsPath); err != nil { - return err - } + logrus.Debug("using container pidmode") } return nil } @@ -74,7 +65,7 @@ func addNetNS(config *createConfig, g *generate.Generator) error { netMode := config.NetMode if netMode.IsHost() { logrus.Debug("Using host netmode") - return g.RemoveLinuxNamespace(libpod.NetNamespace) + return g.RemoveLinuxNamespace(spec.NetworkNamespace) } else if netMode.IsNone() { logrus.Debug("Using none netmode") return nil @@ -83,18 +74,6 @@ func addNetNS(config *createConfig, g *generate.Generator) error { return nil } else if netMode.IsContainer() { logrus.Debug("Using container netmode") - ctr, err := config.Runtime.LookupContainer(netMode.ConnectedContainer()) - if err != nil { - return errors.Wrapf(err, "container %q not found", netMode.ConnectedContainer()) - } - pid, err := ctr.PID() - if err != nil { - return errors.Wrapf(err, "Failed to get pid of container %q", netMode.ConnectedContainer()) - } - nsPath := fmt.Sprintf("/proc/%d/ns/net", pid) - if err := g.AddOrReplaceLinuxNamespace(libpod.NetNamespace, nsPath); err != nil { - return err - } } else { return errors.Errorf("unknown network mode") } @@ -104,7 +83,7 @@ func addNetNS(config *createConfig, g *generate.Generator) error { func addUTSNS(config *createConfig, g *generate.Generator) error { utsMode := config.UtsMode if utsMode.IsHost() { - return g.RemoveLinuxNamespace(libpod.UTSNamespace) + return g.RemoveLinuxNamespace(spec.UTSNamespace) } return nil } @@ -112,21 +91,10 @@ func addUTSNS(config *createConfig, g *generate.Generator) error { func addIpcNS(config *createConfig, g *generate.Generator) error { ipcMode := config.IpcMode if ipcMode.IsHost() { - return g.RemoveLinuxNamespace(libpod.IPCNamespace) + return g.RemoveLinuxNamespace(spec.IPCNamespace) } if ipcMode.IsContainer() { - ctr, err := config.Runtime.LookupContainer(ipcMode.Container()) - if err != nil { - return errors.Wrapf(err, "container %q not found", ipcMode.Container()) - } - pid, err := ctr.PID() - if err != nil { - return errors.Wrapf(err, "Failed to get pid of container %q", ipcMode.Container()) - } - nsPath := fmt.Sprintf("/proc/%d/ns/ipc", pid) - if err := g.AddOrReplaceLinuxNamespace(libpod.IPCNamespace, nsPath); err != nil { - return err - } + logrus.Debug("Using container ipcmode") } return nil @@ -143,7 +111,7 @@ func addRlimits(config *createConfig, g *generate.Generator) error { return errors.Wrapf(err, "ulimit option %q requires name=SOFT:HARD, failed to be parsed", u) } - g.AddProcessRlimits("RLIMIT_"+strings.ToUpper(ul.Name), uint64(ul.Soft), uint64(ul.Hard)) + g.AddProcessRlimits("RLIMIT_"+strings.ToUpper(ul.Name), uint64(ul.Hard), uint64(ul.Soft)) } return nil } @@ -210,10 +178,8 @@ func createConfigToOCISpec(config *createConfig) (*spec.Spec, error) { if config.Hostname != "" { g.AddProcessEnv("HOSTNAME", config.Hostname) } - - for _, sysctl := range config.Sysctl { - s := strings.SplitN(sysctl, "=", 2) - g.AddLinuxSysctl(s[0], s[1]) + for sysctlKey, sysctlVal := range config.Sysctl { + g.AddLinuxSysctl(sysctlKey, sysctlVal) } // RESOURCES - MEMORY @@ -236,7 +202,6 @@ func createConfigToOCISpec(config *createConfig) (*spec.Spec, error) { g.SetProcessOOMScoreAdj(config.Resources.OomScoreAdj) // RESOURCES - CPU - if config.Resources.CPUShares != 0 { g.SetLinuxResourcesCPUShares(config.Resources.CPUShares) } @@ -246,14 +211,18 @@ func createConfigToOCISpec(config *createConfig) (*spec.Spec, error) { if config.Resources.CPUPeriod != 0 { g.SetLinuxResourcesCPUPeriod(config.Resources.CPUPeriod) } + if config.Resources.CPUs != 0 { + g.SetLinuxResourcesCPUPeriod(cpuPeriod) + g.SetLinuxResourcesCPUQuota(int64(config.Resources.CPUs * cpuPeriod)) + } if config.Resources.CPURtRuntime != 0 { g.SetLinuxResourcesCPURealtimeRuntime(config.Resources.CPURtRuntime) } if config.Resources.CPURtPeriod != 0 { g.SetLinuxResourcesCPURealtimePeriod(config.Resources.CPURtPeriod) } - if config.Resources.CPUs != "" { - g.SetLinuxResourcesCPUCpus(config.Resources.CPUs) + if config.Resources.CPUsetCPUs != "" { + g.SetLinuxResourcesCPUCpus(config.Resources.CPUsetCPUs) } if config.Resources.CPUsetMems != "" { g.SetLinuxResourcesCPUMems(config.Resources.CPUsetMems) @@ -322,16 +291,31 @@ func createConfigToOCISpec(config *createConfig) (*spec.Spec, error) { } configSpec := g.Spec() - if config.SeccompProfilePath != "" && config.SeccompProfilePath != "unconfined" { - seccompProfile, err := ioutil.ReadFile(config.SeccompProfilePath) - if err != nil { - return nil, errors.Wrapf(err, "opening seccomp profile (%s) failed", config.SeccompProfilePath) - } - var seccompConfig spec.LinuxSeccomp - if err := json.Unmarshal(seccompProfile, &seccompConfig); err != nil { - return nil, errors.Wrapf(err, "decoding seccomp profile (%s) failed", config.SeccompProfilePath) + // HANDLE CAPABILITIES + // NOTE: Must happen before SECCOMP + if err := setupCapabilities(config, configSpec); err != nil { + return nil, err + } + + // HANDLE SECCOMP + if config.SeccompProfilePath != "unconfined" { + if config.SeccompProfilePath != "" { + seccompProfile, err := ioutil.ReadFile(config.SeccompProfilePath) + if err != nil { + return nil, errors.Wrapf(err, "opening seccomp profile (%s) failed", config.SeccompProfilePath) + } + seccompConfig, err := seccomp.LoadProfile(string(seccompProfile), configSpec) + if err != nil { + return nil, errors.Wrapf(err, "loading seccomp profile (%s) failed", config.SeccompProfilePath) + } + configSpec.Linux.Seccomp = seccompConfig + } else { + seccompConfig, err := seccomp.GetDefaultProfile(configSpec) + if err != nil { + return nil, errors.Wrapf(err, "loading seccomp profile (%s) failed", config.SeccompProfilePath) + } + configSpec.Linux.Seccomp = seccompConfig } - configSpec.Linux.Seccomp = &seccompConfig } // BIND MOUNTS @@ -351,9 +335,13 @@ func createConfigToOCISpec(config *createConfig) (*spec.Spec, error) { } } - // HANDLE CAPABILITIES - if err := setupCapabilities(config, configSpec); err != nil { - return nil, err + // BLOCK IO + blkio, err := config.CreateBlockIO() + if err != nil { + return nil, errors.Wrapf(err, "error creating block io") + } + if blkio != nil { + configSpec.Linux.Resources.BlockIO = blkio } /* @@ -383,8 +371,8 @@ func createConfigToOCISpec(config *createConfig) (*spec.Spec, error) { return configSpec, nil } -func (c *createConfig) CreateBlockIO() (spec.LinuxBlockIO, error) { - bio := spec.LinuxBlockIO{} +func (c *createConfig) CreateBlockIO() (*spec.LinuxBlockIO, error) { + bio := &spec.LinuxBlockIO{} bio.Weight = &c.Resources.BlkioWeight if len(c.Resources.BlkioWeightDevice) > 0 { var lwds []spec.LinuxWeightDevice @@ -401,6 +389,7 @@ func (c *createConfig) CreateBlockIO() (spec.LinuxBlockIO, error) { lwd.Minor = int64(unix.Minor(wdStat.Rdev)) lwds = append(lwds, lwd) } + bio.WeightDevice = lwds } if len(c.Resources.DeviceReadBps) > 0 { readBps, err := makeThrottleArray(c.Resources.DeviceReadBps) @@ -430,7 +419,6 @@ func (c *createConfig) CreateBlockIO() (spec.LinuxBlockIO, error) { } bio.ThrottleWriteIOPSDevice = writeIOps } - return bio, nil } @@ -556,6 +544,8 @@ func (c *createConfig) GetTmpfsMounts() []spec.Mount { func (c *createConfig) GetContainerCreateOptions() ([]libpod.CtrCreateOption, error) { var options []libpod.CtrCreateOption + var portBindings []ocicni.PortMapping + var err error // Uncomment after talking to mheon about unimplemented funcs // options = append(options, libpod.WithLabels(c.labels)) @@ -567,12 +557,57 @@ func (c *createConfig) GetContainerCreateOptions() ([]libpod.CtrCreateOption, er logrus.Debugf("appending name %s", c.Name) options = append(options, libpod.WithName(c.Name)) } - // TODO parse ports into libpod format and include - if !c.NetMode.IsHost() { - options = append(options, libpod.WithNetNS([]ocicni.PortMapping{})) + + // TODO deal with ports defined in image metadata + if len(c.PortBindings) > 0 || len(c.ExposedPorts) > 0 { + portBindings, err = c.CreatePortBindings() + if err != nil { + return nil, errors.Wrapf(err, "unable to create port bindings") + } + } + + if c.NetMode.IsContainer() { + connectedCtr, err := c.Runtime.LookupContainer(c.NetMode.ConnectedContainer()) + if err != nil { + return nil, errors.Wrapf(err, "container %q not found", c.NetMode.ConnectedContainer()) + } + options = append(options, libpod.WithNetNSFrom(connectedCtr)) + } else if !c.NetMode.IsHost() { + options = append(options, libpod.WithNetNS(portBindings)) + } + + if c.PidMode.IsContainer() { + connectedCtr, err := c.Runtime.LookupContainer(c.PidMode.Container()) + if err != nil { + return nil, errors.Wrapf(err, "container %q not found", c.PidMode.Container()) + } + + options = append(options, libpod.WithPIDNSFrom(connectedCtr)) + } + if c.IpcMode.IsContainer() { + connectedCtr, err := c.Runtime.LookupContainer(c.IpcMode.Container()) + if err != nil { + return nil, errors.Wrapf(err, "container %q not found", c.IpcMode.Container()) + } + + options = append(options, libpod.WithIPCNSFrom(connectedCtr)) } + options = append(options, libpod.WithStopSignal(c.StopSignal)) options = append(options, libpod.WithStopTimeout(c.StopTimeout)) + if len(c.DNSSearch) > 0 { + options = append(options, libpod.WithDNSSearch(c.DNSSearch)) + } + if len(c.DNSServers) > 0 { + options = append(options, libpod.WithDNS(c.DNSServers)) + } + if len(c.DNSOpt) > 0 { + options = append(options, libpod.WithDNSOption(c.DNSOpt)) + } + if len(c.HostAdd) > 0 { + options = append(options, libpod.WithHosts(c.HostAdd)) + } + return options, nil } @@ -598,3 +633,43 @@ func makeThrottleArray(throttleInput []string) ([]spec.LinuxThrottleDevice, erro } return ltds, nil } + +// CreatePortBindings iterates ports mappings and exposed ports into a format CNI understands +func (c *createConfig) CreatePortBindings() ([]ocicni.PortMapping, error) { + var portBindings []ocicni.PortMapping + for containerPb, hostPb := range c.PortBindings { + var pm ocicni.PortMapping + pm.ContainerPort = int32(containerPb.Int()) + for _, i := range hostPb { + var hostPort int + var err error + pm.HostIP = i.HostIP + if i.HostPort == "" { + hostPort = containerPb.Int() + } else { + hostPort, err = strconv.Atoi(i.HostPort) + if err != nil { + return nil, errors.Wrapf(err, "unable to convert host port to integer") + } + } + + pm.HostPort = int32(hostPort) + // CNI requires us to make both udp and tcp structs + pm.Protocol = "udp" + portBindings = append(portBindings, pm) + pm.Protocol = "tcp" + portBindings = append(portBindings, pm) + } + } + for j := range c.ExposedPorts { + var expose ocicni.PortMapping + expose.HostPort = int32(j.Int()) + expose.ContainerPort = int32(j.Int()) + // CNI requires us to make both udp and tcp structs + expose.Protocol = "udp" + portBindings = append(portBindings, expose) + expose.Protocol = "tcp" + portBindings = append(portBindings, expose) + } + return portBindings, nil +} diff --git a/cmd/podman/stats.go b/cmd/podman/stats.go index d2ffd8a75..871eb9e2f 100644 --- a/cmd/podman/stats.go +++ b/cmd/podman/stats.go @@ -22,7 +22,7 @@ type statsOutputParams struct { MemPerc string `json:"mem_percent"` NetIO string `json:"netio"` BlockIO string `json:"blocki"` - PIDS uint64 `json:"pids"` + PIDS string `json:"pids"` } var ( @@ -62,14 +62,22 @@ func statsCmd(c *cli.Context) error { } all := c.Bool("all") - - if c.Bool("latest") && all { - return errors.Errorf("--all and --latest cannot be used together") + latest := c.Bool("latest") + ctr := 0 + if all { + ctr += 1 + } + if latest { + ctr += 1 + } + if len(c.Args()) > 0 { + ctr += 1 } - if c.Bool("latest") && len(c.Args()) > 0 { - return errors.Errorf("no container names are allowed with --latest") + if ctr > 1 { + return errors.Errorf("--all, --latest and containers cannot be used together") } + runtime, err := getRuntime(c) if err != nil { return errors.Wrapf(err, "could not get runtime") @@ -91,19 +99,19 @@ func statsCmd(c *cli.Context) error { format = genStatsFormat() } + containerFunc = runtime.GetRunningContainers if len(c.Args()) > 0 { containerFunc = func() ([]*libpod.Container, error) { return runtime.GetContainersByList(c.Args()) } - } else if c.Bool("latest") { + } else if latest { containerFunc = func() ([]*libpod.Container, error) { - var ctrs []*libpod.Container lastCtr, err := runtime.GetLatestContainer() - ctrs = append(ctrs, lastCtr) - return ctrs, err + if err != nil { + return nil, err + } + return []*libpod.Container{lastCtr}, nil } } else if all { containerFunc = runtime.GetAllContainers - } else { - containerFunc = runtime.GetRunningContainers } ctrs, err = containerFunc() @@ -215,18 +223,29 @@ func (i *statsOutputParams) headerMap() map[string]string { } func combineHumanValues(a, b uint64) string { + if a == 0 && b == 0 { + return "-- / --" + } return fmt.Sprintf("%s / %s", units.HumanSize(float64(a)), units.HumanSize(float64(b))) } func floatToPercentString(f float64) string { strippedFloat, err := libpod.RemoveScientificNotationFromFloat(f) - if err != nil { + if err != nil || strippedFloat == 0 { // If things go bazinga, return a safe value - return "0.00 %" + return "--" } return fmt.Sprintf("%.2f", strippedFloat) + "%" } +func pidsToString(pid uint64) string { + if pid == 0 { + // If things go bazinga, return a safe value + return "--" + } + return fmt.Sprintf("%d", pid) +} + func getStatsOutputParams(stats *libpod.ContainerStats) statsOutputParams { return statsOutputParams{ Container: stats.ContainerID[:12], @@ -236,6 +255,6 @@ func getStatsOutputParams(stats *libpod.ContainerStats) statsOutputParams { MemPerc: floatToPercentString(stats.MemPerc), NetIO: combineHumanValues(stats.NetInput, stats.NetOutput), BlockIO: combineHumanValues(stats.BlockInput, stats.BlockOutput), - PIDS: stats.PIDs, + PIDS: pidsToString(stats.PIDs), } } diff --git a/cni/87-podman-bridge.conflist b/cni/87-podman-bridge.conflist new file mode 100644 index 000000000..a5e241c80 --- /dev/null +++ b/cni/87-podman-bridge.conflist @@ -0,0 +1,25 @@ +{ + "cniVersion": "0.3.0", + "name": "podman", + "plugins": [ + { + "type": "bridge", + "bridge": "cni0", + "isGateway": true, + "ipMasq": true, + "ipam": { + "type": "host-local", + "subnet": "10.88.0.0/16", + "routes": [ + { "dst": "0.0.0.0/0" } + ] + } + }, + { + "type": "portmap", + "capabilities": { + "portMappings": true + } + } + ] +} diff --git a/cni/97-podman-bridge.conf b/cni/97-podman-bridge.conf deleted file mode 100644 index 27fc096c4..000000000 --- a/cni/97-podman-bridge.conf +++ /dev/null @@ -1,15 +0,0 @@ -{ - "cniVersion": "0.3.0", - "name": "podman", - "type": "bridge", - "bridge": "cni0", - "isGateway": true, - "ipMasq": true, - "ipam": { - "type": "host-local", - "subnet": "10.88.0.0/16", - "routes": [ - { "dst": "0.0.0.0/0" } - ] - } -} diff --git a/commands.md b/commands.md new file mode 100644 index 000000000..7ce60d309 --- /dev/null +++ b/commands.md @@ -0,0 +1,42 @@ +![PODMAN logo](https://cdn.rawgit.com/kubernetes-incubator/cri-o/master/logo/crio-logo.svg) +# libpod - library for running OCI-based containers in Pods + +## Podman Commands +| Command | Description | Demo| +| :------------------------------------------------------- | :------------------------------------------------------------------------ | :----| +| [podman(1)](/docs/podman.1.md) | Simple management tool for pods and images || +| [podman-attach(1)](/docs/podman-attach.1.md) | Attach to a running container |[![...](/docs/play.png)](https://asciinema.org/a/XDlocUrHVETFECg4zlO9nBbLf)| +| [podman-build(1)](/docs/podman-build.1.md) | Build an image using instructions from Dockerfiles || +| [podman-commit(1)](/docs/podman-commit.1.md) | Create new image based on the changed container || +| [podman-cp(1)](/docs/podman-cp.1.md) | Instead of providing a `podman cp` command, the man page `podman-cp` describes how to use the `podman mount` command to have even more flexibility and functionality|| +| [podman-create(1)](/docs/podman-create.1.md) | Create a new container || +| [podman-diff(1)](/docs/podman-diff.1.md) | Inspect changes on a container or image's filesystem |[![...](/docs/play.png)](https://asciinema.org/a/FXfWB9CKYFwYM4EfqW3NSZy1G)| +| [podman-exec(1)](/docs/podman-exec.1.md) | Execute a command in a running container +| [podman-export(1)](/docs/podman-export.1.md) | Export container's filesystem contents as a tar archive |[![...](/docs/play.png)](https://asciinema.org/a/913lBIRAg5hK8asyIhhkQVLtV)| +| [podman-history(1)](/docs/podman-history.1.md) | Shows the history of an image |[![...](/docs/play.png)](https://asciinema.org/a/bCvUQJ6DkxInMELZdc5DinNSx)| +| [podman-images(1)](/docs/podman-images.1.md) | List images in local storage |[![...](/docs/play.png)](https://asciinema.org/a/133649)| +| [podman-import(1)](/docs/podman-import.1.md) | Import a tarball and save it as a filesystem image || +| [podman-info(1)](/docs/podman-info.1.md) | Display system information |[![...](/docs/play.png)](https://asciinema.org/a/yKbi5fQ89y5TJ8e1RfJd4ivTD)| +| [podman-inspect(1)](/docs/podman-inspect.1.md) | Display the configuration of a container or image |[![...](/docs/play.png)](https://asciinema.org/a/133418)| +| [podman-kill(1)](/docs/podman-kill.1.md) | Kill the main process in one or more running containers |[![...](/docs/play.png)](https://asciinema.org/a/3jNos0A5yzO4hChu7ddKkUPw7)| +| [podman-load(1)](/docs/podman-load.1.md) | Load an image from docker archive or oci |[![...](/docs/play.png)](https://asciinema.org/a/kp8kOaexEhEa20P1KLZ3L5X4g)| +| [podman-login(1)](/docs/podman-login.1.md) | Login to a container registry |[![...](/docs/play.png)](https://asciinema.org/a/oNiPgmfo1FjV2YdesiLpvihtV)| +| [podman-logout(1)](/docs/podman-logout.1.md) | Logout of a container registry |[![...](/docs/play.png)](https://asciinema.org/a/oNiPgmfo1FjV2YdesiLpvihtV)| +| [podman-logs(1)](/docs/podman-logs.1.md) | Display the logs of a container |[![...](/docs/play.png)](https://asciinema.org/a/MZPTWD5CVs3dMREkBxQBY9C5z)| +| [podman-mount(1)](/docs/podman-mount.1.md) | Mount a working container's root filesystem |[![...](/docs/play.png)](https://asciinema.org/a/YSP6hNvZo0RGeMHDA97PhPAf3)| +| [podman-pause(1)](/docs/podman-pause.1.md) | Pause one or more running containers |[![...](/docs/play.png)](https://asciinema.org/a/141292)| +| [podman-ps(1)](/docs/podman-ps.1.md) | Prints out information about containers |[![...](/docs/play.png)](https://asciinema.org/a/bbT41kac6CwZ5giESmZLIaTLR)| +| [podman-pull(1)](/docs/podman-pull.1.md) | Pull an image from a registry |[![...](/docs/play.png)](https://asciinema.org/a/lr4zfoynHJOUNu1KaXa1dwG2X)| +| [podman-push(1)](/docs/podman-push.1.md) | Push an image to a specified destination |[![...](/docs/play.png)](https://asciinema.org/a/133276)| +| [podman-rm(1)](/docs/podman-rm.1.md) | Removes one or more containers |[![...](/docs/play.png)](https://asciinema.org/a/7EMk22WrfGtKWmgHJX9Nze1Qp)| +| [podman-rmi(1)](/docs/podman-rmi.1.md) | Removes one or more images |[![...](/docs/play.png)](https://asciinema.org/a/133799)| +| [podman-save(1)](/docs/podman-save.1.md) | Saves an image to an archive |[![...](/docs/play.png)](https://asciinema.org/a/kp8kOaexEhEa20P1KLZ3L5X4g)| +| [podman-start(1)](/docs/podman-start.1.md) | Starts one or more containers +| [podman-stats(1)](/docs/podman-stats.1.md) | Display a live stream of one or more containers' resource usage statistics|[![...](/docs/play.png)](https://asciinema.org/a/vfUPbAA5tsNWhsfB9p25T6xdr)| +| [podman-stop(1)](/docs/podman-stop.1.md) | Stops one or more running containers |[![...](/docs/play.png)](https://asciinema.org/a/KNRF9xVXeaeNTNjBQVogvZBcp)| +| [podman-tag(1)](/docs/podman-tag.1.md) | Add an additional name to a local image |[![...](/docs/play.png)](https://asciinema.org/a/133803)| +| [podman-top(1)](/docs/podman-top.1.md) | Display the running processes of a container |[![...](/docs/play.png)](https://asciinema.org/a/5WCCi1LXwSuRbvaO9cBUYf3fk)| +| [podman-umount(1)](/docs/podman-umount.1.md) | Unmount a working container's root filesystem |[![...](/docs/play.png)](https://asciinema.org/a/MZPTWD5CVs3dMREkBxQBY9C5z)| +| [podman-unpause(1)](/docs/podman-unpause.1.md) | Unpause one or more running containers |[![...](/docs/play.png)](https://asciinema.org/a/141292)| +| [podman-version(1)](/docs/podman-version.1.md) | Display the version information |[![...](/docs/play.png)](https://asciinema.org/a/mfrn61pjZT9Fc8L4NbfdSqfgu)| +| [podman-wait(1)](/docs/podman-wait.1.md) | Wait on one or more containers to stop and print their exit codes |[![...](/docs/play.png)](https://asciinema.org/a/QNPGKdjWuPgI96GcfkycQtah0)| diff --git a/completions/bash/podman b/completions/bash/podman index 8447ab436..b1a19f255 100644 --- a/completions/bash/podman +++ b/completions/bash/podman @@ -1079,7 +1079,6 @@ _podman_container_run() { --shm-size --stop-signal --stop-timeout - --storage-opt --tmpfs --sysctl --ulimit @@ -0,0 +1,3 @@ +#!/bin/sh +echo "Using podman package to emulate the Docker CLI" +exec /usr/bin/podman $@ diff --git a/docs/dckrman.sh b/docs/dckrman.sh new file mode 100755 index 000000000..8ae7fd40d --- /dev/null +++ b/docs/dckrman.sh @@ -0,0 +1,5 @@ +#!/bin/sh +for i in $@; do + filename=$(echo $i | sed 's/podman/docker/g') + echo .so man1/$i > $filename +done diff --git a/docs/podman-build.1.md b/docs/podman-build.1.md index 8b991e2cf..6ca22678a 100644 --- a/docs/podman-build.1.md +++ b/docs/podman-build.1.md @@ -52,7 +52,7 @@ Recognized formats include *oci* (OCI image-spec v1.0, the default) and Pull the image even if a version of the image is already present. -**--quiet** +**-q, --quiet** Suppress output messages which indicate which instruction is being processed, and of progress when pulling images from a registry, and when writing the diff --git a/docs/podman-create.1.md b/docs/podman-create.1.md index bf2f8b4c2..10a790f4a 100644 --- a/docs/podman-create.1.md +++ b/docs/podman-create.1.md @@ -422,17 +422,6 @@ incompatible with any restart policy other than `none`. **--stop-timeout**=*10* Timeout (in seconds) to stop a container. Default is 10. -**--storage-opt**=[] - Storage driver options per container - - $ podman create -it --storage-opt size=120G fedora /bin/bash - - This (size) will allow to set the container rootfs size to 120G at creation time. - This option is only available for the `devicemapper`, `btrfs`, `overlay2` and `zfs` graph drivers. - For the `devicemapper`, `btrfs` and `zfs` storage drivers, user cannot pass a size less than the Default BaseFS Size. - For the `overlay2` storage driver, the size option is only available if the backing fs is `xfs` and mounted with the `pquota` mount option. - Under these conditions, user can pass any size less then the backing fs size. - **--sysctl**=SYSCTL Configure namespaced kernel parameters at runtime diff --git a/docs/podman-run.1.md b/docs/podman-run.1.md index 0431478e4..8a36bf425 100644 --- a/docs/podman-run.1.md +++ b/docs/podman-run.1.md @@ -56,13 +56,6 @@ each of stdin, stdout, and stderr. **--cidfile**="" Write the container ID to the file -**--cpu-count**=*0* - Limit the number of CPUs available for execution by the container. - - On Windows Server containers, this is approximated as a percentage of total CPU usage. - - On Windows Server containers, the processor resource controls are mutually exclusive, the order of precedence is CPUCount first, then CPUShares, and CPUPercent last. - **--cpu-period**=*0* Limit the CPU CFS (Completely Fair Scheduler) period @@ -427,17 +420,6 @@ incompatible with any restart policy other than `none`. **--stop-timeout**=*10* Timeout (in seconds) to stop a container. Default is 10. -**--storage-opt**=[] - Storage driver options per container - - $ podman run -it --storage-opt size=120G fedora /bin/bash - - This (size) will allow to set the container rootfs size to 120G at creation time. - This option is only available for the `devicemapper`, `btrfs`, `overlay2` and `zfs` graph drivers. - For the `devicemapper`, `btrfs` and `zfs` storage drivers, user cannot pass a size less than the Default BaseFS Size. - For the `overlay2` storage driver, the size option is only available if the backing fs is `xfs` and mounted with the `pquota` mount option. - Under these conditions, user can pass any size less then the backing fs size. - **--sysctl**=SYSCTL Configure namespaced kernel parameters at runtime diff --git a/podman_tutorial.md b/docs/tutorials/podman_tutorial.md index 619e83c35..619e83c35 100644 --- a/podman_tutorial.md +++ b/docs/tutorials/podman_tutorial.md diff --git a/docs/tutorials/tutorials.md b/docs/tutorials/tutorials.md new file mode 100644 index 000000000..6ada366c0 --- /dev/null +++ b/docs/tutorials/tutorials.md @@ -0,0 +1,9 @@ +![PODMAN logo](https://cdn.rawgit.com/kubernetes-incubator/cri-o/master/logo/crio-logo.svg) + +# Podman Tutorials + +## Links to a number of useful tutorials for the Podman utility. + +**[Introduction Tutorial](https://github.com/projectatomic/libpod/tree/master/docs/tutorials/podman_tutorial.md)** + +Learn how to setup Podman and perform some basic commands with the utility. diff --git a/libpod/container.go b/libpod/container.go index 2c769b00b..27042de39 100644 --- a/libpod/container.go +++ b/libpod/container.go @@ -1,76 +1,99 @@ package libpod import ( - "encoding/json" "fmt" - "io" - "io/ioutil" "net" - "os" "path/filepath" - "syscall" "time" "github.com/containerd/cgroups" "github.com/containernetworking/plugins/pkg/ns" "github.com/containers/storage" - "github.com/containers/storage/pkg/archive" "github.com/cri-o/ocicni/pkg/ocicni" - "github.com/docker/docker/daemon/caps" - "github.com/docker/docker/pkg/mount" - "github.com/docker/docker/pkg/namesgenerator" - "github.com/docker/docker/pkg/stringid" - "github.com/docker/docker/pkg/term" - "github.com/mrunalp/fileutils" spec "github.com/opencontainers/runtime-spec/specs-go" - "github.com/opencontainers/runtime-tools/generate" - "github.com/opencontainers/selinux/go-selinux/label" "github.com/pkg/errors" - "github.com/projectatomic/libpod/libpod/driver" - crioAnnotations "github.com/projectatomic/libpod/pkg/annotations" - "github.com/projectatomic/libpod/pkg/chrootuser" - "github.com/sirupsen/logrus" "github.com/ulule/deepcopier" - "golang.org/x/sys/unix" - "k8s.io/apimachinery/pkg/util/wait" - "k8s.io/client-go/tools/remotecommand" ) -// ContainerState represents the current state of a container -type ContainerState int +// ContainerStatus represents the current state of a container +type ContainerStatus int const ( // ContainerStateUnknown indicates that the container is in an error // state where information about it cannot be retrieved - ContainerStateUnknown ContainerState = iota + ContainerStateUnknown ContainerStatus = iota // ContainerStateConfigured indicates that the container has had its // storage configured but it has not been created in the OCI runtime - ContainerStateConfigured ContainerState = iota + ContainerStateConfigured ContainerStatus = iota // ContainerStateCreated indicates the container has been created in // the OCI runtime but not started - ContainerStateCreated ContainerState = iota + ContainerStateCreated ContainerStatus = iota // ContainerStateRunning indicates the container is currently executing - ContainerStateRunning ContainerState = iota + ContainerStateRunning ContainerStatus = iota // ContainerStateStopped indicates that the container was running but has // exited - ContainerStateStopped ContainerState = iota + ContainerStateStopped ContainerStatus = iota // ContainerStatePaused indicates that the container has been paused - ContainerStatePaused ContainerState = iota - // name of the directory holding the artifacts - artifactsDir = "artifacts" + ContainerStatePaused ContainerStatus = iota ) // CgroupParent is the default prefix to a cgroup path in libpod var CgroupParent = "/libpod_parent" +// LinuxNS represents a Linux namespace +type LinuxNS int + +const ( + // InvalidNS is an invalid namespace + InvalidNS LinuxNS = iota + // IPCNS is the IPC namespace + IPCNS LinuxNS = iota + // MountNS is the mount namespace + MountNS LinuxNS = iota + // NetNS is the network namespace + NetNS LinuxNS = iota + // PIDNS is the PID namespace + PIDNS LinuxNS = iota + // UserNS is the user namespace + UserNS LinuxNS = iota + // UTSNS is the UTS namespace + UTSNS LinuxNS = iota + // CgroupNS is the CGroup namespace + CgroupNS LinuxNS = iota +) + +// String returns a string representation of a Linux namespace +// It is guaranteed to be the name of the namespace in /proc for valid ns types +func (ns LinuxNS) String() string { + switch ns { + case InvalidNS: + return "invalid" + case IPCNS: + return "ipc" + case MountNS: + return "mnt" + case NetNS: + return "net" + case PIDNS: + return "pid" + case UserNS: + return "user" + case UTSNS: + return "uts" + case CgroupNS: + return "cgroup" + default: + return "unknown" + } +} + // Container is a single OCI container type Container struct { config *ContainerConfig - pod *Pod runningSpec *spec.Spec - state *containerRuntimeInfo + state *containerState // Locked indicates that a container has been locked as part of a // Batch() operation @@ -85,14 +108,12 @@ type Container struct { // TODO fetch IP and Subnet Mask from networks once we have updated OCICNI // TODO enable pod support // TODO Add readonly support -// TODO add SHM size support -// TODO add shared namespace support -// containerRuntimeInfo contains the current state of the container +// containerState contains the current state of the container // It is stored on disk in a tmpfs and recreated on reboot -type containerRuntimeInfo struct { +type containerState struct { // The current state of the running container - State ContainerState `json:"state"` + State ContainerStatus `json:"state"` // The path to the JSON OCI runtime spec for this container ConfigPath string `json:"configPath,omitempty"` // RunDir is a per-boot directory for container content @@ -156,23 +177,28 @@ type ContainerConfig struct { Mounts []string `json:"mounts,omitempty"` // Security Config + // Whether the container is privileged + Privileged bool `json:"privileged"` + // Whether to set the No New Privileges flag + NoNewPrivs bool `json:"noNewPrivs"` // SELinux process label for container ProcessLabel string `json:"ProcessLabel,omitempty"` // SELinux mount label for root filesystem MountLabel string `json:"MountLabel,omitempty"` // User and group to use in the container // Can be specified by name or UID/GID - User string `json:"user"` + User string `json:"user,omitempty"` // Namespace Config // IDs of container to share namespaces with // NetNsCtr conflicts with the CreateNetNS bool - IPCNsCtr string `json:"ipcNsCtr"` - MountNsCtr string `json:"mountNsCtr"` - NetNsCtr string `json:"netNsCtr"` - PIDNsCtr string `json:"pidNsCtr"` - UserNsCtr string `json:"userNsCtr"` - UTSNsCtr string `json:"utsNsCtr"` + IPCNsCtr string `json:"ipcNsCtr,omitempty"` + MountNsCtr string `json:"mountNsCtr,omitempty"` + NetNsCtr string `json:"netNsCtr,omitempty"` + PIDNsCtr string `json:"pidNsCtr,omitempty"` + UserNsCtr string `json:"userNsCtr,omitempty"` + UTSNsCtr string `json:"utsNsCtr,omitempty"` + CgroupNsCtr string `json:"cgroupNsCtr,omitempty"` // Network Config // CreateNetNS indicates that libpod should create and configure a new @@ -183,6 +209,18 @@ type ContainerConfig struct { // namespace // These are not used unless CreateNetNS is true PortMappings []ocicni.PortMapping `json:"portMappings,omitempty"` + // DNS servers to use in container resolv.conf + // Will override servers in host resolv if set + DNSServer []net.IP `json:"dnsServer,omitempty"` + // DNS Search domains to use in container resolv.conf + // Will override search domains in host resolv if set + DNSSearch []string `json:"dnsSearch,omitempty"` + // DNS options to be set in container resolv.conf + // With override options in host resolv if set + DNSOption []string `json:"dnsOption,omitempty"` + // Hosts to add in container + // Will be appended to host's host file + HostAdd []string `json:"hostsAdd,omitempty"` // Misc Options // Whether to keep container STDIN open @@ -202,9 +240,9 @@ type ContainerConfig struct { // TODO log options - logpath for plaintext, others for log drivers } -// ContainerStater returns a string representation for users +// ContainerStatus returns a string representation for users // of a container state -func (t ContainerState) String() string { +func (t ContainerStatus) String() string { switch t { case ContainerStateUnknown: return "unknown" @@ -248,6 +286,44 @@ func (c *Container) ProcessLabel() string { return c.config.ProcessLabel } +// Dependencies gets the containers this container depends upon +func (c *Container) Dependencies() []string { + // Collect in a map first to remove dupes + dependsCtrs := map[string]bool{} + if c.config.IPCNsCtr != "" { + dependsCtrs[c.config.IPCNsCtr] = true + } + if c.config.MountNsCtr != "" { + dependsCtrs[c.config.MountNsCtr] = true + } + if c.config.NetNsCtr != "" { + dependsCtrs[c.config.NetNsCtr] = true + } + if c.config.PIDNsCtr != "" { + dependsCtrs[c.config.NetNsCtr] = true + } + if c.config.UserNsCtr != "" { + dependsCtrs[c.config.UserNsCtr] = true + } + if c.config.UTSNsCtr != "" { + dependsCtrs[c.config.UTSNsCtr] = true + } + if c.config.CgroupNsCtr != "" { + dependsCtrs[c.config.CgroupNsCtr] = true + } + + if len(dependsCtrs) == 0 { + return []string{} + } + + depends := make([]string, 0, len(dependsCtrs)) + for ctr := range dependsCtrs { + depends = append(depends, ctr) + } + + return depends +} + // Spec returns the container's OCI runtime spec // The spec returned is the one used to create the container. The running // spec may differ slightly as mounts are added based on the image @@ -280,63 +356,6 @@ func (c *Container) RuntimeName() string { return c.runtime.ociRuntime.name } -// rootFsSize gets the size of the container's root filesystem -// A container FS is split into two parts. The first is the top layer, a -// mutable layer, and the rest is the RootFS: the set of immutable layers -// that make up the image on which the container is based. -func (c *Container) rootFsSize() (int64, error) { - container, err := c.runtime.store.Container(c.ID()) - if err != nil { - return 0, err - } - - // Ignore the size of the top layer. The top layer is a mutable RW layer - // and is not considered a part of the rootfs - rwLayer, err := c.runtime.store.Layer(container.LayerID) - if err != nil { - return 0, err - } - layer, err := c.runtime.store.Layer(rwLayer.Parent) - if err != nil { - return 0, err - } - - size := int64(0) - for layer.Parent != "" { - layerSize, err := c.runtime.store.DiffSize(layer.Parent, layer.ID) - if err != nil { - return size, errors.Wrapf(err, "getting diffsize of layer %q and its parent %q", layer.ID, layer.Parent) - } - size += layerSize - layer, err = c.runtime.store.Layer(layer.Parent) - if err != nil { - return 0, err - } - } - // Get the size of the last layer. Has to be outside of the loop - // because the parent of the last layer is "", andlstore.Get("") - // will return an error. - layerSize, err := c.runtime.store.DiffSize(layer.Parent, layer.ID) - return size + layerSize, err -} - -// rwSize Gets the size of the mutable top layer of the container. -func (c *Container) rwSize() (int64, error) { - container, err := c.runtime.store.Container(c.ID()) - if err != nil { - return 0, err - } - - // Get the size of the top layer by calculating the size of the diff - // between the layer and its parent. The top layer of a container is - // the only RW layer, all others are immutable - layer, err := c.runtime.store.Layer(container.LayerID) - if err != nil { - return 0, err - } - return c.runtime.store.DiffSize(layer.Parent, layer.ID) -} - // LogPath returns the path to the container's log file // This file will only be present after Init() is called to create the container // in runc @@ -428,7 +447,7 @@ func (c *Container) FinishedTime() (time.Time, error) { } // State returns the current state of the container -func (c *Container) State() (ContainerState, error) { +func (c *Container) State() (ContainerStatus, error) { if !c.locked { c.lock.Lock() defer c.lock.Unlock() @@ -467,944 +486,56 @@ func (c *Container) MountPoint() (string, error) { return c.state.Mountpoint, nil } -// The path to the container's root filesystem - where the OCI spec will be -// placed, amongst other things -func (c *Container) bundlePath() string { - return c.config.StaticDir -} - -// The path to the container's logs file -func (c *Container) logPath() string { - return filepath.Join(c.config.StaticDir, "ctr.log") -} - -// Retrieves the path of the container's attach socket -func (c *Container) attachSocketPath() string { - return filepath.Join(c.runtime.ociRuntime.socketsDir, c.ID(), "attach") -} - -// Sync this container with on-disk state and runc status -// Should only be called with container lock held -// This function should suffice to ensure a container's state is accurate and -// it is valid for use. -func (c *Container) syncContainer() error { - if err := c.runtime.state.UpdateContainer(c); err != nil { - return err - } - // If runc knows about the container, update its status in runc - // And then save back to disk - if (c.state.State != ContainerStateUnknown) && - (c.state.State != ContainerStateConfigured) { - oldState := c.state.State - // TODO: optionally replace this with a stat for the exit file - if err := c.runtime.ociRuntime.updateContainerStatus(c); err != nil { - return err - } - // Only save back to DB if state changed - if c.state.State != oldState { - if err := c.save(); err != nil { - return err - } - } - } - - if !c.valid { - return errors.Wrapf(ErrCtrRemoved, "container %s is not valid", c.ID()) - } - - return nil -} - -// Make a new container -func newContainer(rspec *spec.Spec, lockDir string) (*Container, error) { - if rspec == nil { - return nil, errors.Wrapf(ErrInvalidArg, "must provide a valid runtime spec to create container") - } - - ctr := new(Container) - ctr.config = new(ContainerConfig) - ctr.state = new(containerRuntimeInfo) - - ctr.config.ID = stringid.GenerateNonCryptoID() - ctr.config.Name = namesgenerator.GetRandomName(0) - - ctr.config.Spec = new(spec.Spec) - deepcopier.Copy(rspec).To(ctr.config.Spec) - ctr.config.CreatedTime = time.Now() - - ctr.config.ShmSize = DefaultShmSize - ctr.config.CgroupParent = CgroupParent - - // Path our lock file will reside at - lockPath := filepath.Join(lockDir, ctr.config.ID) - // Grab a lockfile at the given path - lock, err := storage.GetLockfile(lockPath) - if err != nil { - return nil, errors.Wrapf(err, "error creating lockfile for new container") - } - ctr.lock = lock - - return ctr, nil -} - -// Create container root filesystem for use -func (c *Container) setupStorage() error { - if !c.valid { - return errors.Wrapf(ErrCtrRemoved, "container %s is not valid", c.ID()) - } - - if c.state.State != ContainerStateConfigured { - return errors.Wrapf(ErrCtrStateInvalid, "container %s must be in Configured state to have storage set up", c.ID()) - } - - // Need both an image ID and image name, plus a bool telling us whether to use the image configuration - if c.config.RootfsImageID == "" || c.config.RootfsImageName == "" { - return errors.Wrapf(ErrInvalidArg, "must provide image ID and image name to use an image") - } - - containerInfo, err := c.runtime.storageService.CreateContainerStorage(c.runtime.imageContext, c.config.RootfsImageName, c.config.RootfsImageID, c.config.Name, c.config.ID, c.config.MountLabel) - if err != nil { - return errors.Wrapf(err, "error creating container storage") - } - - c.config.StaticDir = containerInfo.Dir - c.state.RunDir = containerInfo.RunDir - - artifacts := filepath.Join(c.config.StaticDir, artifactsDir) - if err := os.MkdirAll(artifacts, 0755); err != nil { - return errors.Wrapf(err, "error creating artifacts directory %q", artifacts) - } - - return nil -} - -// Tear down a container's storage prior to removal -func (c *Container) teardownStorage() error { - if !c.valid { - return errors.Wrapf(ErrCtrRemoved, "container %s is not valid", c.ID()) - } - - if c.state.State == ContainerStateRunning || c.state.State == ContainerStatePaused { - return errors.Wrapf(ErrCtrStateInvalid, "cannot remove storage for container %s as it is running or paused", c.ID()) - } - - artifacts := filepath.Join(c.config.StaticDir, artifactsDir) - if err := os.RemoveAll(artifacts); err != nil { - return errors.Wrapf(err, "error removing artifacts %q", artifacts) - } - - if err := c.cleanupStorage(); err != nil { - return errors.Wrapf(err, "failed to cleanup container %s storage", c.ID()) - } - - if err := c.runtime.storageService.DeleteContainer(c.ID()); err != nil { - return errors.Wrapf(err, "error removing container %s root filesystem", c.ID()) - } - - return nil -} - -// Refresh refreshes the container's state after a restart -func (c *Container) refresh() error { +// NamespacePath returns the path of one of the container's namespaces +// If the container is not running, an error will be returned +func (c *Container) NamespacePath(ns LinuxNS) (string, error) { c.lock.Lock() defer c.lock.Unlock() - - if !c.valid { - return errors.Wrapf(ErrCtrRemoved, "container %s is not valid - may have been removed", c.ID()) - } - - // We need to get the container's temporary directory from c/storage - // It was lost in the reboot and must be recreated - dir, err := c.runtime.storageService.GetRunDir(c.ID()) - if err != nil { - return errors.Wrapf(err, "error retrieving temporary directory for container %s", c.ID()) - } - c.state.RunDir = dir - - if err := c.runtime.state.SaveContainer(c); err != nil { - return errors.Wrapf(err, "error refreshing state for container %s", c.ID()) - } - - return nil -} - -// Init creates a container in the OCI runtime -func (c *Container) Init() (err error) { - if !c.locked { - c.lock.Lock() - defer c.lock.Unlock() - - if err := c.syncContainer(); err != nil { - return err - } - } - - if c.state.State != ContainerStateConfigured { - return errors.Wrapf(ErrCtrExists, "container %s has already been created in runtime", c.ID()) - } - - if err := c.mountStorage(); err != nil { - return err - } - defer func() { - if err != nil { - if err2 := c.cleanupStorage(); err2 != nil { - logrus.Errorf("Error cleaning up storage for container %s: %v", c.ID(), err2) - } - } - }() - - // Make a network namespace for the container - if c.config.CreateNetNS && c.state.NetNS == nil { - if err := c.runtime.createNetNS(c); err != nil { - return err - } - } - defer func() { - if err != nil { - if err2 := c.runtime.teardownNetNS(c); err2 != nil { - logrus.Errorf("Error tearing down network namespace for container %s: %v", c.ID(), err2) - } - } - }() - - // If the OCI spec already exists, we need to replace it - // Cannot guarantee some things, e.g. network namespaces, have the same - // paths - jsonPath := filepath.Join(c.bundlePath(), "config.json") - if _, err := os.Stat(jsonPath); err != nil { - if !os.IsNotExist(err) { - return errors.Wrapf(err, "error doing stat on container %s spec", c.ID()) - } - // The spec does not exist, we're fine - } else { - // The spec exists, need to remove it - if err := os.Remove(jsonPath); err != nil { - return errors.Wrapf(err, "error replacing runtime spec for container %s", c.ID()) - } - } - - // Copy /etc/resolv.conf to the container's rundir - resolvPath := "/etc/resolv.conf" - - // Check if the host system is using system resolve and if so - // copy its resolv.conf - _, err = os.Stat("/run/systemd/resolve/resolv.conf") - if err == nil { - resolvPath = "/run/systemd/resolve/resolv.conf" - } - runDirResolv, err := c.copyHostFileToRundir(resolvPath) - if err != nil { - return errors.Wrapf(err, "unable to copy resolv.conf to ", runDirResolv) - } - // Copy /etc/hosts to the container's rundir - runDirHosts, err := c.copyHostFileToRundir("/etc/hosts") - if err != nil { - return errors.Wrapf(err, "unable to copy /etc/hosts to ", runDirHosts) - } - - // Save OCI spec to disk - g := generate.NewFromSpec(c.config.Spec) - // If network namespace was requested, add it now - if c.config.CreateNetNS { - g.AddOrReplaceLinuxNamespace(spec.NetworkNamespace, c.state.NetNS.Path()) - } - // Remove default /etc/shm mount - g.RemoveMount("/dev/shm") - // Mount ShmDir from host into container - shmMnt := spec.Mount{ - Type: "bind", - Source: c.config.ShmDir, - Destination: "/dev/shm", - Options: []string{"rw", "bind"}, - } - g.AddMount(shmMnt) - // Bind mount resolv.conf - resolvMnt := spec.Mount{ - Type: "bind", - Source: runDirResolv, - Destination: "/etc/resolv.conf", - Options: []string{"rw", "bind"}, - } - g.AddMount(resolvMnt) - // Bind mount hosts - hostsMnt := spec.Mount{ - Type: "bind", - Source: runDirHosts, - Destination: "/etc/hosts", - Options: []string{"rw", "bind"}, - } - g.AddMount(hostsMnt) - - if c.config.User != "" { - if !c.state.Mounted { - return errors.Wrapf(ErrCtrStateInvalid, "container %s must be mounted in order to translate User field", c.ID()) - } - uid, gid, err := chrootuser.GetUser(c.state.Mountpoint, c.config.User) - if err != nil { - return err - } - // User and Group must go together - g.SetProcessUID(uid) - g.SetProcessGID(gid) - } - - c.runningSpec = g.Spec() - c.runningSpec.Root.Path = c.state.Mountpoint - c.runningSpec.Annotations[crioAnnotations.Created] = c.config.CreatedTime.Format(time.RFC3339Nano) - c.runningSpec.Annotations["org.opencontainers.image.stopSignal"] = fmt.Sprintf("%d", c.config.StopSignal) - - fileJSON, err := json.Marshal(c.runningSpec) - if err != nil { - return errors.Wrapf(err, "error exporting runtime spec for container %s to JSON", c.ID()) - } - if err := ioutil.WriteFile(jsonPath, fileJSON, 0644); err != nil { - return errors.Wrapf(err, "error writing runtime spec JSON to file for container %s", c.ID()) - } - - logrus.Debugf("Created OCI spec for container %s at %s", c.ID(), jsonPath) - - c.state.ConfigPath = jsonPath - - // With the spec complete, do an OCI create - // TODO set cgroup parent in a sane fashion - if err := c.runtime.ociRuntime.createContainer(c, CgroupParent); err != nil { - return err - } - - logrus.Debugf("Created container %s in runc", c.ID()) - - c.state.State = ContainerStateCreated - - return c.save() -} - -// Start starts a container -func (c *Container) Start() error { - if !c.locked { - c.lock.Lock() - defer c.lock.Unlock() - - if err := c.syncContainer(); err != nil { - return err - } - } - - // Container must be created or stopped to be started - if !(c.state.State == ContainerStateCreated || c.state.State == ContainerStateStopped) { - return errors.Wrapf(ErrCtrStateInvalid, "container %s must be in Created or Stopped state to be started", c.ID()) - } - - // Mount storage for the container - if err := c.mountStorage(); err != nil { - return err - } - - if err := c.runtime.ociRuntime.startContainer(c); err != nil { - return err - } - - logrus.Debugf("Started container %s", c.ID()) - - // Update container's state as it should be ContainerStateRunning now - if err := c.runtime.ociRuntime.updateContainerStatus(c); err != nil { - return err - } - - return c.save() -} - -// Stop uses the container's stop signal (or SIGTERM if no signal was specified) -// to stop the container, and if it has not stopped after the given timeout (in -// seconds), uses SIGKILL to attempt to forcibly stop the container. -// If timeout is 0, SIGKILL will be used immediately -func (c *Container) Stop(timeout uint) error { - if !c.locked { - c.lock.Lock() - defer c.lock.Unlock() - - if err := c.syncContainer(); err != nil { - return err - } - } - - logrus.Debugf("Stopping ctr %s with timeout %d", c.ID(), timeout) - - if c.state.State == ContainerStateConfigured || - c.state.State == ContainerStateUnknown || - c.state.State == ContainerStatePaused { - return errors.Wrapf(ErrCtrStateInvalid, "can only stop created, running, or stopped containers") - } - - if err := c.runtime.ociRuntime.stopContainer(c, timeout); err != nil { - return err - } - - // Sync the container's state to pick up return code - if err := c.runtime.ociRuntime.updateContainerStatus(c); err != nil { - return err - } - - return c.cleanupStorage() -} - -// Kill sends a signal to a container -func (c *Container) Kill(signal uint) error { - if !c.locked { - c.lock.Lock() - defer c.lock.Unlock() - - if err := c.syncContainer(); err != nil { - return err - } + if err := c.syncContainer(); err != nil { + return "", errors.Wrapf(err, "error updating container %s state", c.ID()) } if c.state.State != ContainerStateRunning { - return errors.Wrapf(ErrCtrStateInvalid, "can only kill running containers") - } - - return c.runtime.ociRuntime.killContainer(c, signal) -} - -// Exec starts a new process inside the container -func (c *Container) Exec(tty, privileged bool, env, cmd []string, user string) error { - var capList []string - - if !c.locked { - c.lock.Lock() - defer c.lock.Unlock() - - if err := c.syncContainer(); err != nil { - return err - } - } - - conState := c.state.State - - if conState != ContainerStateRunning { - return errors.Errorf("cannot attach to container that is not running") - } - if privileged { - capList = caps.GetAllCapabilities() - } - globalOpts := runcGlobalOptions{ - log: c.LogPath(), - } - execOpts := runcExecOptions{ - capAdd: capList, - pidFile: filepath.Join(c.state.RunDir, fmt.Sprintf("%s-execpid", stringid.GenerateNonCryptoID()[:12])), - env: env, - user: user, - cwd: c.config.Spec.Process.Cwd, - tty: tty, - } - - return c.runtime.ociRuntime.execContainer(c, cmd, globalOpts, execOpts) -} - -// Attach attaches to a container -// Returns fully qualified URL of streaming server for the container -func (c *Container) Attach(noStdin bool, keys string, attached chan<- bool) error { - if !c.locked { - c.lock.Lock() - if err := c.syncContainer(); err != nil { - c.lock.Unlock() - return err - } - c.lock.Unlock() - } - - if c.state.State != ContainerStateCreated && - c.state.State != ContainerStateRunning { - return errors.Wrapf(ErrCtrStateInvalid, "can only attach to created or running containers") - } - - // Check the validity of the provided keys first - var err error - detachKeys := []byte{} - if len(keys) > 0 { - detachKeys, err = term.ToBytes(keys) - if err != nil { - return errors.Wrapf(err, "invalid detach keys") - } - } - - resize := make(chan remotecommand.TerminalSize) - defer close(resize) - - err = c.attachContainerSocket(resize, noStdin, detachKeys, attached) - return err -} - -// Mount mounts a container's filesystem on the host -// The path where the container has been mounted is returned -func (c *Container) Mount(label string) (string, error) { - if !c.locked { - c.lock.Lock() - defer c.lock.Unlock() - - if err := c.syncContainer(); err != nil { - return "", err - } - } - - // return mountpoint if container already mounted - if c.state.Mounted { - return c.state.Mountpoint, nil - } - - mountLabel := label - if label == "" { - mountLabel = c.config.MountLabel + return "", errors.Wrapf(ErrCtrStopped, "cannot get namespace path unless container %s is running", c.ID()) } - mountPoint, err := c.runtime.store.Mount(c.ID(), mountLabel) - if err != nil { - return "", err - } - c.state.Mountpoint = mountPoint - c.state.Mounted = true - c.config.MountLabel = mountLabel - - if err := c.save(); err != nil { - return "", err - } - - return mountPoint, nil -} - -// Unmount unmounts a container's filesystem on the host -func (c *Container) Unmount() error { - if !c.locked { - c.lock.Lock() - defer c.lock.Unlock() - - if err := c.syncContainer(); err != nil { - return err - } - } - - if c.state.State == ContainerStateRunning || c.state.State == ContainerStatePaused { - return errors.Wrapf(ErrCtrStateInvalid, "cannot remove storage for container %s as it is running or paused", c.ID()) - } - - return c.cleanupStorage() -} - -// Pause pauses a container -func (c *Container) Pause() error { - if !c.locked { - c.lock.Lock() - defer c.lock.Unlock() - - if err := c.syncContainer(); err != nil { - return err - } - } - - if c.state.State == ContainerStatePaused { - return errors.Wrapf(ErrCtrStateInvalid, "%q is already paused", c.ID()) - } - if c.state.State != ContainerStateRunning && c.state.State != ContainerStateCreated { - return errors.Wrapf(ErrCtrStateInvalid, "%q is not running/created, can't pause", c.state.State) - } - if err := c.runtime.ociRuntime.pauseContainer(c); err != nil { - return err - } - - logrus.Debugf("Paused container %s", c.ID()) - - // Update container's state as it should be ContainerStatePaused now - if err := c.runtime.ociRuntime.updateContainerStatus(c); err != nil { - return err - } - - return c.save() -} - -// Unpause unpauses a container -func (c *Container) Unpause() error { - if !c.locked { - c.lock.Lock() - defer c.lock.Unlock() - - if err := c.syncContainer(); err != nil { - return err - } - } - - if c.state.State != ContainerStatePaused { - return errors.Wrapf(ErrCtrStateInvalid, "%q is not paused, can't unpause", c.ID()) - } - if err := c.runtime.ociRuntime.unpauseContainer(c); err != nil { - return err - } - - logrus.Debugf("Unpaused container %s", c.ID()) - - // Update container's state as it should be ContainerStateRunning now - if err := c.runtime.ociRuntime.updateContainerStatus(c); err != nil { - return err - } - - return c.save() -} - -// Export exports a container's root filesystem as a tar archive -// The archive will be saved as a file at the given path -func (c *Container) Export(path string) error { - if !c.locked { - c.lock.Lock() - defer c.lock.Unlock() - - if err := c.syncContainer(); err != nil { - return err - } - } - - return c.export(path) -} - -func (c *Container) export(path string) error { - mountPoint := c.state.Mountpoint - if !c.state.Mounted { - mount, err := c.runtime.store.Mount(c.ID(), c.config.MountLabel) - if err != nil { - return errors.Wrapf(err, "error mounting container %q", c.ID()) - } - mountPoint = mount - defer func() { - if err := c.runtime.store.Unmount(c.ID()); err != nil { - logrus.Errorf("error unmounting container %q: %v", c.ID(), err) - } - }() - } - - input, err := archive.Tar(mountPoint, archive.Uncompressed) - if err != nil { - return errors.Wrapf(err, "error reading container directory %q", c.ID()) - } - - outFile, err := os.Create(path) - if err != nil { - return errors.Wrapf(err, "error creating file %q", path) - } - defer outFile.Close() - - _, err = io.Copy(outFile, input) - return err -} - -// AddArtifact creates and writes to an artifact file for the container -func (c *Container) AddArtifact(name string, data []byte) error { - if !c.valid { - return ErrCtrRemoved - } - - return ioutil.WriteFile(c.getArtifactPath(name), data, 0740) -} - -// GetArtifact reads the specified artifact file from the container -func (c *Container) GetArtifact(name string) ([]byte, error) { - if !c.valid { - return nil, ErrCtrRemoved - } - - return ioutil.ReadFile(c.getArtifactPath(name)) -} -// RemoveArtifact deletes the specified artifacts file -func (c *Container) RemoveArtifact(name string) error { - if !c.valid { - return ErrCtrRemoved + if ns == InvalidNS { + return "", errors.Wrapf(ErrInvalidArg, "invalid namespace requested from container %s", c.ID()) } - return os.Remove(c.getArtifactPath(name)) + return fmt.Sprintf("/proc/%d/ns/%s", c.state.PID, ns.String()), nil } -func (c *Container) getArtifactPath(name string) string { - return filepath.Join(c.config.StaticDir, artifactsDir, name) +// CGroupPath returns a cgroups "path" for a given container. +func (c *Container) CGroupPath() cgroups.Path { + return cgroups.StaticPath(filepath.Join(c.config.CgroupParent, fmt.Sprintf("libpod-conmon-%s", c.ID()))) } -// Inspect a container for low-level information -func (c *Container) Inspect(size bool) (*ContainerInspectData, error) { - if !c.locked { - c.lock.Lock() - defer c.lock.Unlock() - - if err := c.syncContainer(); err != nil { - return nil, err - } - } - - storeCtr, err := c.runtime.store.Container(c.ID()) - if err != nil { - return nil, errors.Wrapf(err, "error getting container from store %q", c.ID()) - } - layer, err := c.runtime.store.Layer(storeCtr.LayerID) - if err != nil { - return nil, errors.Wrapf(err, "error reading information about layer %q", storeCtr.LayerID) - } - driverData, err := driver.GetDriverData(c.runtime.store, layer.ID) - if err != nil { - return nil, errors.Wrapf(err, "error getting graph driver info %q", c.ID()) - } - - return c.getContainerInspectData(size, driverData) +// StopTimeout returns a stop timeout field for this container +func (c *Container) StopTimeout() uint { + return c.config.StopTimeout } -// Commit commits the changes between a container and its image, creating a new -// image -func (c *Container) Commit(pause bool, options CopyOptions) error { +// RootFsSize returns the root FS size of the container +func (c *Container) RootFsSize() (int64, error) { if !c.locked { c.lock.Lock() defer c.lock.Unlock() - if err := c.syncContainer(); err != nil { - return err + return -1, errors.Wrapf(err, "error updating container %s state", c.ID()) } } - - if c.state.State == ContainerStateRunning && pause { - if err := c.runtime.ociRuntime.pauseContainer(c); err != nil { - return errors.Wrapf(err, "error pausing container %q", c.ID()) - } - defer func() { - if err := c.runtime.ociRuntime.unpauseContainer(c); err != nil { - logrus.Errorf("error unpausing container %q: %v", c.ID(), err) - } - }() - } - - tempFile, err := ioutil.TempFile(c.runtime.config.TmpDir, "podman-commit") - if err != nil { - return errors.Wrapf(err, "error creating temp file") - } - defer os.Remove(tempFile.Name()) - defer tempFile.Close() - - if err := c.export(tempFile.Name()); err != nil { - return err - } - return c.runtime.ImportImage(tempFile.Name(), options) -} - -// Wait blocks on a container to exit and returns its exit code -func (c *Container) Wait() (int32, error) { - if !c.valid { - return -1, ErrCtrRemoved - } - - err := wait.PollImmediateInfinite(1, - func() (bool, error) { - stopped, err := c.isStopped() - if err != nil { - return false, err - } - if !stopped { - return false, nil - } else { // nolint - return true, nil // nolint - } // nolint - }, - ) - if err != nil { - return 0, err - } - exitCode := c.state.ExitCode - return exitCode, nil -} - -func (c *Container) isStopped() (bool, error) { - if !c.locked { - c.lock.Lock() - defer c.lock.Unlock() - } - err := c.syncContainer() - if err != nil { - return true, err - } - return c.state.State == ContainerStateStopped, nil -} - -// save container state to the database -func (c *Container) save() error { - if err := c.runtime.state.SaveContainer(c); err != nil { - return errors.Wrapf(err, "error saving container %s state", c.ID()) - } - return nil -} - -// mountStorage sets up the container's root filesystem -// It mounts the image and any other requested mounts -// TODO: Add ability to override mount label so we can use this for Mount() too -// TODO: Can we use this for export? Copying SHM into the export might not be -// good -func (c *Container) mountStorage() (err error) { - // Container already mounted, nothing to do - if c.state.Mounted { - return nil - } - - // TODO: generalize this mount code so it will mount every mount in ctr.config.Mounts - - mounted, err := mount.Mounted(c.config.ShmDir) - if err != nil { - return errors.Wrapf(err, "unable to determine if %q is mounted", c.config.ShmDir) - } - - if !mounted { - shmOptions := fmt.Sprintf("mode=1777,size=%d", c.config.ShmSize) - if err := unix.Mount("shm", c.config.ShmDir, "tmpfs", unix.MS_NOEXEC|unix.MS_NOSUID|unix.MS_NODEV, - label.FormatMountLabel(shmOptions, c.config.MountLabel)); err != nil { - return errors.Wrapf(err, "failed to mount shm tmpfs %q", c.config.ShmDir) - } - } - - mountPoint, err := c.runtime.storageService.MountContainerImage(c.ID()) - if err != nil { - return errors.Wrapf(err, "error mounting storage for container %s", c.ID()) - } - c.state.Mounted = true - c.state.Mountpoint = mountPoint - - logrus.Debugf("Created root filesystem for container %s at %s", c.ID(), c.state.Mountpoint) - - defer func() { - if err != nil { - if err2 := c.cleanupStorage(); err2 != nil { - logrus.Errorf("Error unmounting storage for container %s: %v", c.ID(), err) - } - } - }() - - return c.save() + return c.rootFsSize() } -// CleanupStorage unmounts all mount points in container and cleans up container storage -func (c *Container) CleanupStorage() error { +// RWSize returns the rw size of the container +func (c *Container) RWSize() (int64, error) { if !c.locked { c.lock.Lock() defer c.lock.Unlock() if err := c.syncContainer(); err != nil { - return err + return -1, errors.Wrapf(err, "error updating container %s state", c.ID()) } } - return c.cleanupStorage() -} - -// cleanupStorage unmounts and cleans up the container's root filesystem -func (c *Container) cleanupStorage() error { - if !c.state.Mounted { - // Already unmounted, do nothing - return nil - } - - for _, mount := range c.config.Mounts { - if err := unix.Unmount(mount, unix.MNT_DETACH); err != nil { - if err != syscall.EINVAL { - logrus.Warnf("container %s failed to unmount %s : %v", c.ID(), mount, err) - } - } - } - - // Also unmount storage - if err := c.runtime.storageService.UnmountContainerImage(c.ID()); err != nil { - return errors.Wrapf(err, "error unmounting container %s root filesystem", c.ID()) - } - - c.state.Mountpoint = "" - c.state.Mounted = false - - return c.save() -} - -// CGroupPath returns a cgroups "path" for a given container. -func (c *Container) CGroupPath() cgroups.Path { - return cgroups.StaticPath(filepath.Join(c.config.CgroupParent, fmt.Sprintf("libpod-conmon-%s", c.ID()))) -} - -// copyHostFileToRundir copies the provided file to the runtimedir -func (c *Container) copyHostFileToRundir(sourcePath string) (string, error) { - destFileName := filepath.Join(c.state.RunDir, filepath.Base(sourcePath)) - if err := fileutils.CopyFile(sourcePath, destFileName); err != nil { - return "", err - } - // Relabel runDirResolv for the container - if err := label.Relabel(destFileName, c.config.MountLabel, false); err != nil { - return "", err - } - return destFileName, nil -} - -// StopTimeout returns a stop timeout field for this container -func (c *Container) StopTimeout() uint { - return c.config.StopTimeout -} - -// Batch starts a batch operation on the given container -// All commands in the passed function will execute under the same lock and -// without syncronyzing state after each operation -// This will result in substantial performance benefits when running numerous -// commands on the same container -// Note that the container passed into the Batch function cannot be removed -// during batched operations. runtime.RemoveContainer can only be called outside -// of Batch -// Any error returned by the given batch function will be returned unmodified by -// Batch -// As Batch normally disables updating the current state of the container, the -// Sync() function is provided to enable container state to be updated and -// checked within Batch. -func (c *Container) Batch(batchFunc func(*Container) error) error { - c.lock.Lock() - defer c.lock.Unlock() - - if err := c.syncContainer(); err != nil { - return err - } - - newCtr := new(Container) - newCtr.config = c.config - newCtr.state = c.state - newCtr.runtime = c.runtime - newCtr.valid = true - - newCtr.locked = true - - if err := batchFunc(newCtr); err != nil { - return err - } - - newCtr.locked = false - - return c.save() -} - -// Sync updates the current state of the container, checking whether its state -// has changed -// Sync can only be used inside Batch() - otherwise, it will be done -// automatically. -// When called outside Batch(), Sync() is a no-op -func (c *Container) Sync() error { - if !c.locked { - return nil - } - - // If runc knows about the container, update its status in runc - // And then save back to disk - if (c.state.State != ContainerStateUnknown) && - (c.state.State != ContainerStateConfigured) { - oldState := c.state.State - // TODO: optionally replace this with a stat for the exit file - if err := c.runtime.ociRuntime.updateContainerStatus(c); err != nil { - return err - } - // Only save back to DB if state changed - if c.state.State != oldState { - if err := c.save(); err != nil { - return err - } - } - } - - return nil + return c.rwSize() } diff --git a/libpod/container_api.go b/libpod/container_api.go new file mode 100644 index 000000000..2b3c83eb2 --- /dev/null +++ b/libpod/container_api.go @@ -0,0 +1,767 @@ +package libpod + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "os" + "path/filepath" + "time" + + "github.com/docker/docker/daemon/caps" + "github.com/docker/docker/pkg/stringid" + "github.com/docker/docker/pkg/term" + spec "github.com/opencontainers/runtime-spec/specs-go" + "github.com/opencontainers/runtime-tools/generate" + "github.com/pkg/errors" + "github.com/projectatomic/libpod/libpod/driver" + crioAnnotations "github.com/projectatomic/libpod/pkg/annotations" + "github.com/projectatomic/libpod/pkg/chrootuser" + "github.com/sirupsen/logrus" + "k8s.io/apimachinery/pkg/util/wait" + "k8s.io/client-go/tools/remotecommand" +) + +// Init creates a container in the OCI runtime +func (c *Container) Init() (err error) { + if !c.locked { + c.lock.Lock() + defer c.lock.Unlock() + + if err := c.syncContainer(); err != nil { + return err + } + } + + if c.state.State != ContainerStateConfigured { + return errors.Wrapf(ErrCtrExists, "container %s has already been created in runtime", c.ID()) + } + + if err := c.mountStorage(); err != nil { + return err + } + defer func() { + if err != nil { + if err2 := c.cleanupStorage(); err2 != nil { + logrus.Errorf("Error cleaning up storage for container %s: %v", c.ID(), err2) + } + } + }() + + // Make a network namespace for the container + if c.config.CreateNetNS && c.state.NetNS == nil { + if err := c.runtime.createNetNS(c); err != nil { + return err + } + } + defer func() { + if err != nil { + if err2 := c.runtime.teardownNetNS(c); err2 != nil { + logrus.Errorf("Error tearing down network namespace for container %s: %v", c.ID(), err2) + } + } + }() + + // If the OCI spec already exists, we need to replace it + // Cannot guarantee some things, e.g. network namespaces, have the same + // paths + jsonPath := filepath.Join(c.bundlePath(), "config.json") + if _, err := os.Stat(jsonPath); err != nil { + if !os.IsNotExist(err) { + return errors.Wrapf(err, "error doing stat on container %s spec", c.ID()) + } + // The spec does not exist, we're fine + } else { + // The spec exists, need to remove it + if err := os.Remove(jsonPath); err != nil { + return errors.Wrapf(err, "error replacing runtime spec for container %s", c.ID()) + } + } + + // Copy /etc/resolv.conf to the container's rundir + runDirResolv, err := c.generateResolvConf() + if err != nil { + return err + } + + // Copy /etc/hosts to the container's rundir + runDirHosts, err := c.generateHosts() + if err != nil { + return errors.Wrapf(err, "unable to copy /etc/hosts to container space") + } + + if c.Spec().Hostname == "" { + id := c.ID() + if len(c.ID()) > 11 { + id = c.ID()[:12] + } + c.config.Spec.Hostname = id + } + runDirHostname, err := c.generateEtcHostname(c.config.Spec.Hostname) + if err != nil { + return errors.Wrapf(err, "unable to generate hostname file for container") + } + + // Save OCI spec to disk + g := generate.NewFromSpec(c.config.Spec) + // If network namespace was requested, add it now + if c.config.CreateNetNS { + g.AddOrReplaceLinuxNamespace(spec.NetworkNamespace, c.state.NetNS.Path()) + } + // Remove default /etc/shm mount + g.RemoveMount("/dev/shm") + // Mount ShmDir from host into container + shmMnt := spec.Mount{ + Type: "bind", + Source: c.config.ShmDir, + Destination: "/dev/shm", + Options: []string{"rw", "bind"}, + } + g.AddMount(shmMnt) + // Bind mount resolv.conf + resolvMnt := spec.Mount{ + Type: "bind", + Source: runDirResolv, + Destination: "/etc/resolv.conf", + Options: []string{"rw", "bind"}, + } + g.AddMount(resolvMnt) + // Bind mount hosts + hostsMnt := spec.Mount{ + Type: "bind", + Source: runDirHosts, + Destination: "/etc/hosts", + Options: []string{"rw", "bind"}, + } + g.AddMount(hostsMnt) + // Bind hostname + hostnameMnt := spec.Mount{ + Type: "bind", + Source: runDirHostname, + Destination: "/etc/hostname", + Options: []string{"rw", "bind"}, + } + g.AddMount(hostnameMnt) + + if c.config.User != "" { + if !c.state.Mounted { + return errors.Wrapf(ErrCtrStateInvalid, "container %s must be mounted in order to translate User field", c.ID()) + } + uid, gid, err := chrootuser.GetUser(c.state.Mountpoint, c.config.User) + if err != nil { + return err + } + // User and Group must go together + g.SetProcessUID(uid) + g.SetProcessGID(gid) + } + + // Add shared namespaces from other containers + if c.config.IPCNsCtr != "" { + ipcCtr, err := c.runtime.state.Container(c.config.IPCNsCtr) + if err != nil { + return err + } + + nsPath, err := ipcCtr.NamespacePath(IPCNS) + if err != nil { + return err + } + + if err := g.AddOrReplaceLinuxNamespace(spec.IPCNamespace, nsPath); err != nil { + return err + } + } + if c.config.MountNsCtr != "" { + mountCtr, err := c.runtime.state.Container(c.config.MountNsCtr) + if err != nil { + return err + } + + nsPath, err := mountCtr.NamespacePath(MountNS) + if err != nil { + return err + } + + if err := g.AddOrReplaceLinuxNamespace(spec.MountNamespace, nsPath); err != nil { + return err + } + } + if c.config.NetNsCtr != "" { + netCtr, err := c.runtime.state.Container(c.config.NetNsCtr) + if err != nil { + return err + } + + nsPath, err := netCtr.NamespacePath(NetNS) + if err != nil { + return err + } + + if err := g.AddOrReplaceLinuxNamespace(spec.NetworkNamespace, nsPath); err != nil { + return err + } + } + if c.config.PIDNsCtr != "" { + pidCtr, err := c.runtime.state.Container(c.config.PIDNsCtr) + if err != nil { + return err + } + + nsPath, err := pidCtr.NamespacePath(PIDNS) + if err != nil { + return err + } + + if err := g.AddOrReplaceLinuxNamespace(string(spec.PIDNamespace), nsPath); err != nil { + return err + } + } + if c.config.UserNsCtr != "" { + userCtr, err := c.runtime.state.Container(c.config.UserNsCtr) + if err != nil { + return err + } + + nsPath, err := userCtr.NamespacePath(UserNS) + if err != nil { + return err + } + + if err := g.AddOrReplaceLinuxNamespace(spec.UserNamespace, nsPath); err != nil { + return err + } + } + if c.config.UTSNsCtr != "" { + utsCtr, err := c.runtime.state.Container(c.config.UTSNsCtr) + if err != nil { + return err + } + + nsPath, err := utsCtr.NamespacePath(UTSNS) + if err != nil { + return err + } + + if err := g.AddOrReplaceLinuxNamespace(spec.UTSNamespace, nsPath); err != nil { + return err + } + } + if c.config.CgroupNsCtr != "" { + cgroupCtr, err := c.runtime.state.Container(c.config.CgroupNsCtr) + if err != nil { + return err + } + + nsPath, err := cgroupCtr.NamespacePath(CgroupNS) + if err != nil { + return err + } + + if err := g.AddOrReplaceLinuxNamespace(spec.CgroupNamespace, nsPath); err != nil { + return err + } + } + + c.runningSpec = g.Spec() + c.runningSpec.Root.Path = c.state.Mountpoint + c.runningSpec.Annotations[crioAnnotations.Created] = c.config.CreatedTime.Format(time.RFC3339Nano) + c.runningSpec.Annotations["org.opencontainers.image.stopSignal"] = fmt.Sprintf("%d", c.config.StopSignal) + + fileJSON, err := json.Marshal(c.runningSpec) + if err != nil { + return errors.Wrapf(err, "error exporting runtime spec for container %s to JSON", c.ID()) + } + if err := ioutil.WriteFile(jsonPath, fileJSON, 0644); err != nil { + return errors.Wrapf(err, "error writing runtime spec JSON to file for container %s", c.ID()) + } + + logrus.Debugf("Created OCI spec for container %s at %s", c.ID(), jsonPath) + + c.state.ConfigPath = jsonPath + + // With the spec complete, do an OCI create + // TODO set cgroup parent in a sane fashion + if err := c.runtime.ociRuntime.createContainer(c, CgroupParent); err != nil { + return err + } + + logrus.Debugf("Created container %s in runc", c.ID()) + + c.state.State = ContainerStateCreated + + return c.save() +} + +// Start starts a container +func (c *Container) Start() error { + if !c.locked { + c.lock.Lock() + defer c.lock.Unlock() + + if err := c.syncContainer(); err != nil { + return err + } + } + + // Container must be created or stopped to be started + if !(c.state.State == ContainerStateCreated || c.state.State == ContainerStateStopped) { + return errors.Wrapf(ErrCtrStateInvalid, "container %s must be in Created or Stopped state to be started", c.ID()) + } + + // Mount storage for the container + if err := c.mountStorage(); err != nil { + return err + } + + if err := c.runtime.ociRuntime.startContainer(c); err != nil { + return err + } + + logrus.Debugf("Started container %s", c.ID()) + + c.state.State = ContainerStateRunning + + return c.save() +} + +// Stop uses the container's stop signal (or SIGTERM if no signal was specified) +// to stop the container, and if it has not stopped after the given timeout (in +// seconds), uses SIGKILL to attempt to forcibly stop the container. +// If timeout is 0, SIGKILL will be used immediately +func (c *Container) Stop(timeout uint) error { + if !c.locked { + c.lock.Lock() + defer c.lock.Unlock() + + if err := c.syncContainer(); err != nil { + return err + } + } + + logrus.Debugf("Stopping ctr %s with timeout %d", c.ID(), timeout) + + if c.state.State == ContainerStateConfigured || + c.state.State == ContainerStateUnknown || + c.state.State == ContainerStatePaused { + return errors.Wrapf(ErrCtrStateInvalid, "can only stop created, running, or stopped containers") + } + + if err := c.runtime.ociRuntime.stopContainer(c, timeout); err != nil { + return err + } + + // Sync the container's state to pick up return code + if err := c.runtime.ociRuntime.updateContainerStatus(c); err != nil { + return err + } + + return c.cleanupStorage() +} + +// Kill sends a signal to a container +func (c *Container) Kill(signal uint) error { + if !c.locked { + c.lock.Lock() + defer c.lock.Unlock() + + if err := c.syncContainer(); err != nil { + return err + } + } + + if c.state.State != ContainerStateRunning { + return errors.Wrapf(ErrCtrStateInvalid, "can only kill running containers") + } + + return c.runtime.ociRuntime.killContainer(c, signal) +} + +// Exec starts a new process inside the container +func (c *Container) Exec(tty, privileged bool, env, cmd []string, user string) error { + var capList []string + + if !c.locked { + c.lock.Lock() + defer c.lock.Unlock() + + if err := c.syncContainer(); err != nil { + return err + } + } + + conState := c.state.State + + if conState != ContainerStateRunning { + return errors.Errorf("cannot attach to container that is not running") + } + if privileged { + capList = caps.GetAllCapabilities() + } + globalOpts := runcGlobalOptions{ + log: c.LogPath(), + } + execOpts := runcExecOptions{ + capAdd: capList, + pidFile: filepath.Join(c.state.RunDir, fmt.Sprintf("%s-execpid", stringid.GenerateNonCryptoID()[:12])), + env: env, + user: user, + cwd: c.config.Spec.Process.Cwd, + tty: tty, + } + + return c.runtime.ociRuntime.execContainer(c, cmd, globalOpts, execOpts) +} + +// Attach attaches to a container +// Returns fully qualified URL of streaming server for the container +func (c *Container) Attach(noStdin bool, keys string, attached chan<- bool) error { + if !c.locked { + c.lock.Lock() + if err := c.syncContainer(); err != nil { + c.lock.Unlock() + return err + } + c.lock.Unlock() + } + + if c.state.State != ContainerStateCreated && + c.state.State != ContainerStateRunning { + return errors.Wrapf(ErrCtrStateInvalid, "can only attach to created or running containers") + } + + // Check the validity of the provided keys first + var err error + detachKeys := []byte{} + if len(keys) > 0 { + detachKeys, err = term.ToBytes(keys) + if err != nil { + return errors.Wrapf(err, "invalid detach keys") + } + } + + resize := make(chan remotecommand.TerminalSize) + defer close(resize) + + err = c.attachContainerSocket(resize, noStdin, detachKeys, attached) + return err +} + +// Mount mounts a container's filesystem on the host +// The path where the container has been mounted is returned +func (c *Container) Mount(label string) (string, error) { + if !c.locked { + c.lock.Lock() + defer c.lock.Unlock() + + if err := c.syncContainer(); err != nil { + return "", err + } + } + + // return mountpoint if container already mounted + if c.state.Mounted { + return c.state.Mountpoint, nil + } + + mountLabel := label + if label == "" { + mountLabel = c.config.MountLabel + } + mountPoint, err := c.runtime.store.Mount(c.ID(), mountLabel) + if err != nil { + return "", err + } + c.state.Mountpoint = mountPoint + c.state.Mounted = true + c.config.MountLabel = mountLabel + + if err := c.save(); err != nil { + return "", err + } + + return mountPoint, nil +} + +// Unmount unmounts a container's filesystem on the host +func (c *Container) Unmount() error { + if !c.locked { + c.lock.Lock() + defer c.lock.Unlock() + + if err := c.syncContainer(); err != nil { + return err + } + } + + if c.state.State == ContainerStateRunning || c.state.State == ContainerStatePaused { + return errors.Wrapf(ErrCtrStateInvalid, "cannot remove storage for container %s as it is running or paused", c.ID()) + } + + return c.cleanupStorage() +} + +// Pause pauses a container +func (c *Container) Pause() error { + if !c.locked { + c.lock.Lock() + defer c.lock.Unlock() + + if err := c.syncContainer(); err != nil { + return err + } + } + + if c.state.State == ContainerStatePaused { + return errors.Wrapf(ErrCtrStateInvalid, "%q is already paused", c.ID()) + } + if c.state.State != ContainerStateRunning && c.state.State != ContainerStateCreated { + return errors.Wrapf(ErrCtrStateInvalid, "%q is not running/created, can't pause", c.state.State) + } + if err := c.runtime.ociRuntime.pauseContainer(c); err != nil { + return err + } + + logrus.Debugf("Paused container %s", c.ID()) + + c.state.State = ContainerStatePaused + + return c.save() +} + +// Unpause unpauses a container +func (c *Container) Unpause() error { + if !c.locked { + c.lock.Lock() + defer c.lock.Unlock() + + if err := c.syncContainer(); err != nil { + return err + } + } + + if c.state.State != ContainerStatePaused { + return errors.Wrapf(ErrCtrStateInvalid, "%q is not paused, can't unpause", c.ID()) + } + if err := c.runtime.ociRuntime.unpauseContainer(c); err != nil { + return err + } + + logrus.Debugf("Unpaused container %s", c.ID()) + + c.state.State = ContainerStateRunning + + return c.save() +} + +// Export exports a container's root filesystem as a tar archive +// The archive will be saved as a file at the given path +func (c *Container) Export(path string) error { + if !c.locked { + c.lock.Lock() + defer c.lock.Unlock() + + if err := c.syncContainer(); err != nil { + return err + } + } + + return c.export(path) +} + +// AddArtifact creates and writes to an artifact file for the container +func (c *Container) AddArtifact(name string, data []byte) error { + if !c.valid { + return ErrCtrRemoved + } + + return ioutil.WriteFile(c.getArtifactPath(name), data, 0740) +} + +// GetArtifact reads the specified artifact file from the container +func (c *Container) GetArtifact(name string) ([]byte, error) { + if !c.valid { + return nil, ErrCtrRemoved + } + + return ioutil.ReadFile(c.getArtifactPath(name)) +} + +// RemoveArtifact deletes the specified artifacts file +func (c *Container) RemoveArtifact(name string) error { + if !c.valid { + return ErrCtrRemoved + } + + return os.Remove(c.getArtifactPath(name)) +} + +// Inspect a container for low-level information +func (c *Container) Inspect(size bool) (*ContainerInspectData, error) { + if !c.locked { + c.lock.Lock() + defer c.lock.Unlock() + + if err := c.syncContainer(); err != nil { + return nil, err + } + } + + storeCtr, err := c.runtime.store.Container(c.ID()) + if err != nil { + return nil, errors.Wrapf(err, "error getting container from store %q", c.ID()) + } + layer, err := c.runtime.store.Layer(storeCtr.LayerID) + if err != nil { + return nil, errors.Wrapf(err, "error reading information about layer %q", storeCtr.LayerID) + } + driverData, err := driver.GetDriverData(c.runtime.store, layer.ID) + if err != nil { + return nil, errors.Wrapf(err, "error getting graph driver info %q", c.ID()) + } + + return c.getContainerInspectData(size, driverData) +} + +// Commit commits the changes between a container and its image, creating a new +// image +func (c *Container) Commit(pause bool, options CopyOptions) error { + if !c.locked { + c.lock.Lock() + defer c.lock.Unlock() + + if err := c.syncContainer(); err != nil { + return err + } + } + + if c.state.State == ContainerStateRunning && pause { + if err := c.runtime.ociRuntime.pauseContainer(c); err != nil { + return errors.Wrapf(err, "error pausing container %q", c.ID()) + } + defer func() { + if err := c.runtime.ociRuntime.unpauseContainer(c); err != nil { + logrus.Errorf("error unpausing container %q: %v", c.ID(), err) + } + }() + } + + tempFile, err := ioutil.TempFile(c.runtime.config.TmpDir, "podman-commit") + if err != nil { + return errors.Wrapf(err, "error creating temp file") + } + defer os.Remove(tempFile.Name()) + defer tempFile.Close() + + if err := c.export(tempFile.Name()); err != nil { + return err + } + return c.runtime.ImportImage(tempFile.Name(), options) +} + +// Wait blocks on a container to exit and returns its exit code +func (c *Container) Wait() (int32, error) { + if !c.valid { + return -1, ErrCtrRemoved + } + + err := wait.PollImmediateInfinite(1, + func() (bool, error) { + stopped, err := c.isStopped() + if err != nil { + return false, err + } + if !stopped { + return false, nil + } else { // nolint + return true, nil // nolint + } // nolint + }, + ) + if err != nil { + return 0, err + } + exitCode := c.state.ExitCode + return exitCode, nil +} + +// CleanupStorage unmounts all mount points in container and cleans up container storage +func (c *Container) CleanupStorage() error { + if !c.locked { + c.lock.Lock() + defer c.lock.Unlock() + if err := c.syncContainer(); err != nil { + return err + } + } + return c.cleanupStorage() +} + +// Batch starts a batch operation on the given container +// All commands in the passed function will execute under the same lock and +// without syncronyzing state after each operation +// This will result in substantial performance benefits when running numerous +// commands on the same container +// Note that the container passed into the Batch function cannot be removed +// during batched operations. runtime.RemoveContainer can only be called outside +// of Batch +// Any error returned by the given batch function will be returned unmodified by +// Batch +// As Batch normally disables updating the current state of the container, the +// Sync() function is provided to enable container state to be updated and +// checked within Batch. +func (c *Container) Batch(batchFunc func(*Container) error) error { + c.lock.Lock() + defer c.lock.Unlock() + + if err := c.syncContainer(); err != nil { + return err + } + + newCtr := new(Container) + newCtr.config = c.config + newCtr.state = c.state + newCtr.runtime = c.runtime + newCtr.lock = c.lock + newCtr.valid = true + + newCtr.locked = true + + if err := batchFunc(newCtr); err != nil { + return err + } + + newCtr.locked = false + + return c.save() +} + +// Sync updates the current state of the container, checking whether its state +// has changed +// Sync can only be used inside Batch() - otherwise, it will be done +// automatically. +// When called outside Batch(), Sync() is a no-op +func (c *Container) Sync() error { + if !c.locked { + return nil + } + + // If runc knows about the container, update its status in runc + // And then save back to disk + if (c.state.State != ContainerStateUnknown) && + (c.state.State != ContainerStateConfigured) { + oldState := c.state.State + // TODO: optionally replace this with a stat for the exit file + if err := c.runtime.ociRuntime.updateContainerStatus(c); err != nil { + return err + } + // Only save back to DB if state changed + if c.state.State != oldState { + if err := c.save(); err != nil { + return err + } + } + } + + return nil +} diff --git a/libpod/container_inspect.go b/libpod/container_inspect.go index 0bb45cedd..78dd00c16 100644 --- a/libpod/container_inspect.go +++ b/libpod/container_inspect.go @@ -4,7 +4,6 @@ import ( "github.com/cri-o/ocicni/pkg/ocicni" "github.com/projectatomic/libpod/libpod/driver" "github.com/sirupsen/logrus" - "github.com/ulule/deepcopier" ) func (c *Container) getContainerInspectData(size bool, driverData *driver.Data) (*ContainerInspectData, error) { @@ -77,7 +76,7 @@ func (c *Container) getContainerInspectData(size bool, driverData *driver.Data) // Copy port mappings into network settings if config.PortMappings != nil { - deepcopier.Copy(config.PortMappings).To(data.NetworkSettings.Ports) + data.NetworkSettings.Ports = config.PortMappings } // Get information on the container's network namespace (if present) diff --git a/libpod/container_internal.go b/libpod/container_internal.go new file mode 100644 index 000000000..9b785bfa5 --- /dev/null +++ b/libpod/container_internal.go @@ -0,0 +1,515 @@ +package libpod + +import ( + "fmt" + "io" + "io/ioutil" + "os" + "path/filepath" + "strings" + "syscall" + "time" + + "github.com/containers/storage" + "github.com/containers/storage/pkg/archive" + "github.com/docker/docker/pkg/mount" + "github.com/docker/docker/pkg/namesgenerator" + "github.com/docker/docker/pkg/stringid" + spec "github.com/opencontainers/runtime-spec/specs-go" + "github.com/opencontainers/selinux/go-selinux/label" + "github.com/pkg/errors" + "github.com/sirupsen/logrus" + "github.com/ulule/deepcopier" + "golang.org/x/sys/unix" +) + +const ( + // name of the directory holding the artifacts + artifactsDir = "artifacts" +) + +// rootFsSize gets the size of the container's root filesystem +// A container FS is split into two parts. The first is the top layer, a +// mutable layer, and the rest is the RootFS: the set of immutable layers +// that make up the image on which the container is based. +func (c *Container) rootFsSize() (int64, error) { + container, err := c.runtime.store.Container(c.ID()) + if err != nil { + return 0, err + } + + // Ignore the size of the top layer. The top layer is a mutable RW layer + // and is not considered a part of the rootfs + rwLayer, err := c.runtime.store.Layer(container.LayerID) + if err != nil { + return 0, err + } + layer, err := c.runtime.store.Layer(rwLayer.Parent) + if err != nil { + return 0, err + } + + size := int64(0) + for layer.Parent != "" { + layerSize, err := c.runtime.store.DiffSize(layer.Parent, layer.ID) + if err != nil { + return size, errors.Wrapf(err, "getting diffsize of layer %q and its parent %q", layer.ID, layer.Parent) + } + size += layerSize + layer, err = c.runtime.store.Layer(layer.Parent) + if err != nil { + return 0, err + } + } + // Get the size of the last layer. Has to be outside of the loop + // because the parent of the last layer is "", andlstore.Get("") + // will return an error. + layerSize, err := c.runtime.store.DiffSize(layer.Parent, layer.ID) + return size + layerSize, err +} + +// rwSize Gets the size of the mutable top layer of the container. +func (c *Container) rwSize() (int64, error) { + container, err := c.runtime.store.Container(c.ID()) + if err != nil { + return 0, err + } + + // Get the size of the top layer by calculating the size of the diff + // between the layer and its parent. The top layer of a container is + // the only RW layer, all others are immutable + layer, err := c.runtime.store.Layer(container.LayerID) + if err != nil { + return 0, err + } + return c.runtime.store.DiffSize(layer.Parent, layer.ID) +} + +// The path to the container's root filesystem - where the OCI spec will be +// placed, amongst other things +func (c *Container) bundlePath() string { + return c.config.StaticDir +} + +// The path to the container's logs file +func (c *Container) logPath() string { + return filepath.Join(c.config.StaticDir, "ctr.log") +} + +// Retrieves the path of the container's attach socket +func (c *Container) attachSocketPath() string { + return filepath.Join(c.runtime.ociRuntime.socketsDir, c.ID(), "attach") +} + +// Sync this container with on-disk state and runc status +// Should only be called with container lock held +// This function should suffice to ensure a container's state is accurate and +// it is valid for use. +func (c *Container) syncContainer() error { + if err := c.runtime.state.UpdateContainer(c); err != nil { + return err + } + // If runc knows about the container, update its status in runc + // And then save back to disk + if (c.state.State != ContainerStateUnknown) && + (c.state.State != ContainerStateConfigured) { + oldState := c.state.State + // TODO: optionally replace this with a stat for the exit file + if err := c.runtime.ociRuntime.updateContainerStatus(c); err != nil { + return err + } + // Only save back to DB if state changed + if c.state.State != oldState { + if err := c.save(); err != nil { + return err + } + } + } + + if !c.valid { + return errors.Wrapf(ErrCtrRemoved, "container %s is not valid", c.ID()) + } + + return nil +} + +// Make a new container +func newContainer(rspec *spec.Spec, lockDir string) (*Container, error) { + if rspec == nil { + return nil, errors.Wrapf(ErrInvalidArg, "must provide a valid runtime spec to create container") + } + + ctr := new(Container) + ctr.config = new(ContainerConfig) + ctr.state = new(containerState) + + ctr.config.ID = stringid.GenerateNonCryptoID() + ctr.config.Name = namesgenerator.GetRandomName(0) + + ctr.config.Spec = new(spec.Spec) + deepcopier.Copy(rspec).To(ctr.config.Spec) + ctr.config.CreatedTime = time.Now() + + ctr.config.ShmSize = DefaultShmSize + ctr.config.CgroupParent = CgroupParent + + // Path our lock file will reside at + lockPath := filepath.Join(lockDir, ctr.config.ID) + // Grab a lockfile at the given path + lock, err := storage.GetLockfile(lockPath) + if err != nil { + return nil, errors.Wrapf(err, "error creating lockfile for new container") + } + ctr.lock = lock + + return ctr, nil +} + +// Create container root filesystem for use +func (c *Container) setupStorage() error { + if !c.valid { + return errors.Wrapf(ErrCtrRemoved, "container %s is not valid", c.ID()) + } + + if c.state.State != ContainerStateConfigured { + return errors.Wrapf(ErrCtrStateInvalid, "container %s must be in Configured state to have storage set up", c.ID()) + } + + // Need both an image ID and image name, plus a bool telling us whether to use the image configuration + if c.config.RootfsImageID == "" || c.config.RootfsImageName == "" { + return errors.Wrapf(ErrInvalidArg, "must provide image ID and image name to use an image") + } + + containerInfo, err := c.runtime.storageService.CreateContainerStorage(c.runtime.imageContext, c.config.RootfsImageName, c.config.RootfsImageID, c.config.Name, c.config.ID, c.config.MountLabel) + if err != nil { + return errors.Wrapf(err, "error creating container storage") + } + + c.config.StaticDir = containerInfo.Dir + c.state.RunDir = containerInfo.RunDir + + artifacts := filepath.Join(c.config.StaticDir, artifactsDir) + if err := os.MkdirAll(artifacts, 0755); err != nil { + return errors.Wrapf(err, "error creating artifacts directory %q", artifacts) + } + + return nil +} + +// Tear down a container's storage prior to removal +func (c *Container) teardownStorage() error { + if !c.valid { + return errors.Wrapf(ErrCtrRemoved, "container %s is not valid", c.ID()) + } + + if c.state.State == ContainerStateRunning || c.state.State == ContainerStatePaused { + return errors.Wrapf(ErrCtrStateInvalid, "cannot remove storage for container %s as it is running or paused", c.ID()) + } + + artifacts := filepath.Join(c.config.StaticDir, artifactsDir) + if err := os.RemoveAll(artifacts); err != nil { + return errors.Wrapf(err, "error removing artifacts %q", artifacts) + } + + if err := c.cleanupStorage(); err != nil { + return errors.Wrapf(err, "failed to cleanup container %s storage", c.ID()) + } + + if err := c.runtime.storageService.DeleteContainer(c.ID()); err != nil { + return errors.Wrapf(err, "error removing container %s root filesystem", c.ID()) + } + + return nil +} + +// Refresh refreshes the container's state after a restart +func (c *Container) refresh() error { + c.lock.Lock() + defer c.lock.Unlock() + + if !c.valid { + return errors.Wrapf(ErrCtrRemoved, "container %s is not valid - may have been removed", c.ID()) + } + + // We need to get the container's temporary directory from c/storage + // It was lost in the reboot and must be recreated + dir, err := c.runtime.storageService.GetRunDir(c.ID()) + if err != nil { + return errors.Wrapf(err, "error retrieving temporary directory for container %s", c.ID()) + } + c.state.RunDir = dir + + if err := c.runtime.state.SaveContainer(c); err != nil { + return errors.Wrapf(err, "error refreshing state for container %s", c.ID()) + } + + return nil +} + +func (c *Container) export(path string) error { + mountPoint := c.state.Mountpoint + if !c.state.Mounted { + mount, err := c.runtime.store.Mount(c.ID(), c.config.MountLabel) + if err != nil { + return errors.Wrapf(err, "error mounting container %q", c.ID()) + } + mountPoint = mount + defer func() { + if err := c.runtime.store.Unmount(c.ID()); err != nil { + logrus.Errorf("error unmounting container %q: %v", c.ID(), err) + } + }() + } + + input, err := archive.Tar(mountPoint, archive.Uncompressed) + if err != nil { + return errors.Wrapf(err, "error reading container directory %q", c.ID()) + } + + outFile, err := os.Create(path) + if err != nil { + return errors.Wrapf(err, "error creating file %q", path) + } + defer outFile.Close() + + _, err = io.Copy(outFile, input) + return err +} + +// Get path of artifact with a given name for this container +func (c *Container) getArtifactPath(name string) string { + return filepath.Join(c.config.StaticDir, artifactsDir, name) +} + +// Used with Wait() to determine if a container has exited +func (c *Container) isStopped() (bool, error) { + if !c.locked { + c.lock.Lock() + defer c.lock.Unlock() + } + err := c.syncContainer() + if err != nil { + return true, err + } + return c.state.State == ContainerStateStopped, nil +} + +// save container state to the database +func (c *Container) save() error { + if err := c.runtime.state.SaveContainer(c); err != nil { + return errors.Wrapf(err, "error saving container %s state", c.ID()) + } + return nil +} + +// mountStorage sets up the container's root filesystem +// It mounts the image and any other requested mounts +// TODO: Add ability to override mount label so we can use this for Mount() too +// TODO: Can we use this for export? Copying SHM into the export might not be +// good +func (c *Container) mountStorage() (err error) { + // Container already mounted, nothing to do + if c.state.Mounted { + return nil + } + + // TODO: generalize this mount code so it will mount every mount in ctr.config.Mounts + + mounted, err := mount.Mounted(c.config.ShmDir) + if err != nil { + return errors.Wrapf(err, "unable to determine if %q is mounted", c.config.ShmDir) + } + + if !mounted { + shmOptions := fmt.Sprintf("mode=1777,size=%d", c.config.ShmSize) + if err := unix.Mount("shm", c.config.ShmDir, "tmpfs", unix.MS_NOEXEC|unix.MS_NOSUID|unix.MS_NODEV, + label.FormatMountLabel(shmOptions, c.config.MountLabel)); err != nil { + return errors.Wrapf(err, "failed to mount shm tmpfs %q", c.config.ShmDir) + } + } + + mountPoint, err := c.runtime.storageService.MountContainerImage(c.ID()) + if err != nil { + return errors.Wrapf(err, "error mounting storage for container %s", c.ID()) + } + c.state.Mounted = true + c.state.Mountpoint = mountPoint + + logrus.Debugf("Created root filesystem for container %s at %s", c.ID(), c.state.Mountpoint) + + defer func() { + if err != nil { + if err2 := c.cleanupStorage(); err2 != nil { + logrus.Errorf("Error unmounting storage for container %s: %v", c.ID(), err) + } + } + }() + + return c.save() +} + +// cleanupStorage unmounts and cleans up the container's root filesystem +func (c *Container) cleanupStorage() error { + if !c.state.Mounted { + // Already unmounted, do nothing + return nil + } + + for _, mount := range c.config.Mounts { + if err := unix.Unmount(mount, unix.MNT_DETACH); err != nil { + if err != syscall.EINVAL { + logrus.Warnf("container %s failed to unmount %s : %v", c.ID(), mount, err) + } + } + } + + // Also unmount storage + if err := c.runtime.storageService.UnmountContainerImage(c.ID()); err != nil { + return errors.Wrapf(err, "error unmounting container %s root filesystem", c.ID()) + } + + c.state.Mountpoint = "" + c.state.Mounted = false + + return c.save() +} + +// WriteStringToRundir copies the provided file to the runtimedir +func (c *Container) WriteStringToRundir(destFile, output string) (string, error) { + destFileName := filepath.Join(c.state.RunDir, destFile) + f, err := os.Create(destFileName) + if err != nil { + return "", errors.Wrapf(err, "unable to create %s", destFileName) + } + + defer f.Close() + _, err = f.WriteString(output) + if err != nil { + return "", errors.Wrapf(err, "unable to write %s", destFileName) + } + // Relabel runDirResolv for the container + if err := label.Relabel(destFileName, c.config.MountLabel, false); err != nil { + return "", err + } + return destFileName, nil +} + +type resolv struct { + nameServers []string + searchDomains []string + options []string +} + +// generateResolvConf generates a containers resolv.conf +func (c *Container) generateResolvConf() (string, error) { + // Copy /etc/resolv.conf to the container's rundir + resolvPath := "/etc/resolv.conf" + + // Check if the host system is using system resolve and if so + // copy its resolv.conf + if _, err := os.Stat("/run/systemd/resolve/resolv.conf"); err == nil { + resolvPath = "/run/systemd/resolve/resolv.conf" + } + orig, err := ioutil.ReadFile(resolvPath) + if err != nil { + return "", errors.Wrapf(err, "unable to read %s", resolvPath) + } + if len(c.config.DNSServer) == 0 && len(c.config.DNSSearch) == 0 && len(c.config.DNSOption) == 0 { + return c.WriteStringToRundir("resolv.conf", fmt.Sprintf("%s", orig)) + } + + // Read and organize the hosts /etc/resolv.conf + resolv := createResolv(string(orig[:])) + + // Populate the resolv struct with user's dns search domains + if len(c.config.DNSSearch) > 0 { + resolv.searchDomains = nil + // The . character means the user doesnt want any search domains in the container + if !StringInSlice(".", c.config.DNSSearch) { + resolv.searchDomains = append(resolv.searchDomains, c.Config().DNSSearch...) + } + } + + // Populate the resolv struct with user's dns servers + if len(c.config.DNSServer) > 0 { + resolv.nameServers = nil + for _, i := range c.config.DNSServer { + resolv.nameServers = append(resolv.nameServers, i.String()) + } + } + + // Populate the resolve struct with the users dns options + if len(c.config.DNSOption) > 0 { + resolv.options = nil + resolv.options = append(resolv.options, c.Config().DNSOption...) + } + return c.WriteStringToRundir("resolv.conf", resolv.ToString()) +} + +// createResolv creates a resolv struct from an input string +func createResolv(input string) resolv { + var resolv resolv + for _, line := range strings.Split(input, "\n") { + if strings.HasPrefix(line, "search") { + fields := strings.Fields(line) + if len(fields) < 2 { + logrus.Debugf("invalid resolv.conf line %s", line) + continue + } + resolv.searchDomains = append(resolv.searchDomains, fields[1:]...) + } else if strings.HasPrefix(line, "nameserver") { + fields := strings.Fields(line) + if len(fields) < 2 { + logrus.Debugf("invalid resolv.conf line %s", line) + continue + } + resolv.nameServers = append(resolv.nameServers, fields[1]) + } else if strings.HasPrefix(line, "options") { + fields := strings.Fields(line) + if len(fields) < 2 { + logrus.Debugf("invalid resolv.conf line %s", line) + continue + } + resolv.options = append(resolv.options, fields[1:]...) + } + } + return resolv +} + +//ToString returns a resolv struct in the form of a resolv.conf +func (r resolv) ToString() string { + var result string + // Populate the output string with search domains + result += fmt.Sprintf("search %s\n", strings.Join(r.searchDomains, " ")) + // Populate the output string with name servers + for _, i := range r.nameServers { + result += fmt.Sprintf("nameserver %s\n", i) + } + // Populate the output string with dns options + for _, i := range r.options { + result += fmt.Sprintf("options %s\n", i) + } + return result +} + +// generateHosts creates a containers hosts file +func (c *Container) generateHosts() (string, error) { + orig, err := ioutil.ReadFile("/etc/hosts") + if err != nil { + return "", errors.Wrapf(err, "unable to read /etc/hosts") + } + hosts := string(orig) + if len(c.config.HostAdd) > 0 { + for _, host := range c.config.HostAdd { + // the host format has already been verified at this point + fields := strings.Split(host, ":") + hosts += fmt.Sprintf("%s %s\n", fields[0], fields[1]) + } + } + return c.WriteStringToRundir("hosts", hosts) +} + +// generateEtcHostname creates a containers /etc/hostname +func (c *Container) generateEtcHostname(hostname string) (string, error) { + return c.WriteStringToRundir("hostname", hostname) +} diff --git a/libpod/in_memory_state.go b/libpod/in_memory_state.go index 4e4cbb664..55162e6c8 100644 --- a/libpod/in_memory_state.go +++ b/libpod/in_memory_state.go @@ -1,6 +1,8 @@ package libpod import ( + "strings" + "github.com/docker/docker/pkg/truncindex" "github.com/pkg/errors" "github.com/projectatomic/libpod/pkg/registrar" @@ -10,6 +12,7 @@ import ( type InMemoryState struct { pods map[string]*Pod containers map[string]*Container + ctrDepends map[string][]string podNameIndex *registrar.Registrar podIDIndex *truncindex.TruncIndex ctrNameIndex *registrar.Registrar @@ -23,6 +26,8 @@ func NewInMemoryState() (State, error) { state.pods = make(map[string]*Pod) state.containers = make(map[string]*Container) + state.ctrDepends = make(map[string][]string) + state.podNameIndex = registrar.NewRegistrar() state.ctrNameIndex = registrar.NewRegistrar() @@ -101,8 +106,7 @@ func (s *InMemoryState) HasContainer(id string) (bool, error) { } // AddContainer adds a container to the state -// If the container belongs to a pod, the pod must already be present when the -// container is added, and the container must be present in the pod +// Containers in a pod cannot be added to the state func (s *InMemoryState) AddContainer(ctr *Container) error { if !ctr.valid { return errors.Wrapf(ErrCtrRemoved, "container with ID %s is not valid", ctr.ID()) @@ -113,17 +117,8 @@ func (s *InMemoryState) AddContainer(ctr *Container) error { return errors.Wrapf(ErrCtrExists, "container with ID %s already exists in state", ctr.ID()) } - if ctr.pod != nil { - if _, ok := s.pods[ctr.pod.ID()]; !ok { - return errors.Wrapf(ErrNoSuchPod, "pod %s does not exist, cannot add container %s", ctr.pod.ID(), ctr.ID()) - } - - hasCtr, err := ctr.pod.HasContainer(ctr.ID()) - if err != nil { - return errors.Wrapf(err, "error checking if container %s is present in pod %s", ctr.ID(), ctr.pod.ID()) - } else if !hasCtr { - return errors.Wrapf(ErrNoSuchCtr, "container %s is not present in pod %s", ctr.ID(), ctr.pod.ID()) - } + if ctr.config.Pod != "" { + return errors.Wrapf(ErrInvalidArg, "cannot add a container that is in a pod with AddContainer, use AddContainerToPod") } if err := s.ctrNameIndex.Reserve(ctr.Name(), ctr.ID()); err != nil { @@ -137,6 +132,12 @@ func (s *InMemoryState) AddContainer(ctr *Container) error { s.containers[ctr.ID()] = ctr + // Add containers this container depends on + depCtrs := ctr.Dependencies() + for _, depCtr := range depCtrs { + s.addCtrToDependsMap(ctr.ID(), depCtr) + } + return nil } @@ -146,6 +147,13 @@ func (s *InMemoryState) RemoveContainer(ctr *Container) error { // Almost no validity checks are performed, to ensure we can kick // misbehaving containers out of the state + // Ensure we don't remove a container which other containers depend on + deps, ok := s.ctrDepends[ctr.ID()] + if ok && len(deps) != 0 { + depsStr := strings.Join(deps, ", ") + return errors.Wrapf(ErrCtrExists, "the following containers depend on container %s: %s", ctr.ID(), depsStr) + } + if _, ok := s.containers[ctr.ID()]; !ok { return errors.Wrapf(ErrNoSuchCtr, "no container exists in state with ID %s", ctr.ID()) } @@ -156,6 +164,14 @@ func (s *InMemoryState) RemoveContainer(ctr *Container) error { delete(s.containers, ctr.ID()) s.ctrNameIndex.Release(ctr.Name()) + delete(s.ctrDepends, ctr.ID()) + + // Remove us from container dependencies + depCtrs := ctr.Dependencies() + for _, depCtr := range depCtrs { + s.removeCtrFromDependsMap(ctr.ID(), depCtr) + } + return nil } @@ -163,6 +179,17 @@ func (s *InMemoryState) RemoveContainer(ctr *Container) error { // As all state is in-memory, no update will be required // As such this is a no-op func (s *InMemoryState) UpdateContainer(ctr *Container) error { + // If the container is invalid, return error + if !ctr.valid { + return errors.Wrapf(ErrCtrRemoved, "container with ID %s is not valid", ctr.ID()) + } + + // If the container does not exist, return error + if _, ok := s.containers[ctr.ID()]; !ok { + ctr.valid = false + return errors.Wrapf(ErrNoSuchCtr, "container with ID %s not found in state", ctr.ID()) + } + return nil } @@ -171,9 +198,34 @@ func (s *InMemoryState) UpdateContainer(ctr *Container) error { // are made // As such this is a no-op func (s *InMemoryState) SaveContainer(ctr *Container) error { + // If the container is invalid, return error + if !ctr.valid { + return errors.Wrapf(ErrCtrRemoved, "container with ID %s is not valid", ctr.ID()) + } + + // If the container does not exist, return error + if _, ok := s.containers[ctr.ID()]; !ok { + ctr.valid = false + return errors.Wrapf(ErrNoSuchCtr, "container with ID %s not found in state", ctr.ID()) + } + return nil } +// ContainerInUse checks if the given container is being used by other containers +func (s *InMemoryState) ContainerInUse(ctr *Container) ([]string, error) { + if !ctr.valid { + return nil, ErrCtrRemoved + } + + arr, ok := s.ctrDepends[ctr.ID()] + if !ok { + return []string{}, nil + } + + return arr, nil +} + // AllContainers retrieves all containers from the state func (s *InMemoryState) AllContainers() ([]*Container, error) { ctrs := make([]*Container, 0, len(s.containers)) @@ -241,6 +293,20 @@ func (s *InMemoryState) HasPod(id string) (bool, error) { return ok, nil } +// PodContainers retrieves the containers from a pod given the pod's full ID +func (s *InMemoryState) PodContainers(id string) ([]*Container, error) { + if id == "" { + return nil, ErrEmptyID + } + + pod, ok := s.pods[id] + if !ok { + return nil, errors.Wrapf(ErrNoSuchPod, "no pod with ID %s found", id) + } + + return pod.GetContainers() +} + // AddPod adds a given pod to the state // Only empty pods can be added to the state func (s *InMemoryState) AddPod(pod *Pod) error { @@ -289,6 +355,89 @@ func (s *InMemoryState) RemovePod(pod *Pod) error { return nil } +// UpdatePod updates a pod's state from the backing database +// As in-memory states have no database this is a no-op +func (s *InMemoryState) UpdatePod(pod *Pod) error { + return nil +} + +// AddContainerToPod adds a container to the given pod, also adding it to the +// state +func (s *InMemoryState) AddContainerToPod(pod *Pod, ctr *Container) error { + if !pod.valid { + return errors.Wrapf(ErrPodRemoved, "pod %s is not valid and cannot be added to", pod.ID()) + } + if !ctr.valid { + return errors.Wrapf(ErrCtrRemoved, "container %s is not valid and cannot be added to the pod", ctr.ID()) + } + + if ctr.config.Pod != pod.ID() { + return errors.Wrapf(ErrInvalidArg, "container %s is not in pod %s", ctr.ID(), pod.ID()) + } + + // Add container to pod + if err := pod.addContainer(ctr); err != nil { + return err + } + + // Add container to state + _, ok := s.containers[ctr.ID()] + if ok { + return errors.Wrapf(ErrCtrExists, "container with ID %s already exists in state", ctr.ID()) + } + + if err := s.ctrNameIndex.Reserve(ctr.Name(), ctr.ID()); err != nil { + return errors.Wrapf(err, "error reserving container name %s", ctr.Name()) + } + + if err := s.ctrIDIndex.Add(ctr.ID()); err != nil { + s.ctrNameIndex.Release(ctr.Name()) + return errors.Wrapf(err, "error releasing container ID %s", ctr.ID()) + } + + s.containers[ctr.ID()] = ctr + + return nil +} + +// RemoveContainerFromPod removes the given container from the given pod +// The container is also removed from the state +func (s *InMemoryState) RemoveContainerFromPod(pod *Pod, ctr *Container) error { + if !pod.valid { + return errors.Wrapf(ErrPodRemoved, "pod %s is not valid and containers cannot be removed", pod.ID()) + } + if !ctr.valid { + return errors.Wrapf(ErrCtrRemoved, "container %s is not valid and cannot be removed from the pod", ctr.ID()) + } + + // Is the container in the pod? + exists, err := pod.HasContainer(ctr.ID()) + if err != nil { + return errors.Wrapf(err, "error checking for container %s in pod %s", ctr.ID(), pod.ID()) + } + if !exists { + return errors.Wrapf(ErrNoSuchCtr, "no container %s in pod %s", ctr.ID(), pod.ID()) + } + + // Remove container from pod + if err := pod.removeContainer(ctr); err != nil { + return err + } + + // Remove container from state + if _, ok := s.containers[ctr.ID()]; !ok { + return errors.Wrapf(ErrNoSuchCtr, "no container exists in state with ID %s", ctr.ID()) + } + + if err := s.ctrIDIndex.Delete(ctr.ID()); err != nil { + return errors.Wrapf(err, "error removing container ID from index") + } + delete(s.containers, ctr.ID()) + s.ctrNameIndex.Release(ctr.Name()) + + return nil +} + // AllPods retrieves all pods currently in the state func (s *InMemoryState) AllPods() ([]*Pod, error) { pods := make([]*Pod, 0, len(s.pods)) @@ -298,3 +447,43 @@ func (s *InMemoryState) AllPods() ([]*Pod, error) { return pods, nil } + +// Internal Functions + +// Add a container to the dependency mappings +func (s *InMemoryState) addCtrToDependsMap(ctrID, dependsID string) { + if dependsID != "" { + arr, ok := s.ctrDepends[dependsID] + if !ok { + // Do not have a mapping for that container yet + s.ctrDepends[dependsID] = []string{ctrID} + } else { + // Have a mapping for the container + arr = append(arr, ctrID) + s.ctrDepends[dependsID] = arr + } + } +} + +// Remove a container from dependency mappings +func (s *InMemoryState) removeCtrFromDependsMap(ctrID, dependsID string) { + if dependsID != "" { + arr, ok := s.ctrDepends[dependsID] + if !ok { + // Internal state seems inconsistent + // But the dependency is definitely gone + // So just return + return + } + + newArr := make([]string, 0, len(arr)) + + for _, id := range arr { + if id != ctrID { + newArr = append(newArr, id) + } + } + + s.ctrDepends[dependsID] = newArr + } +} diff --git a/libpod/networking.go b/libpod/networking.go index 41bd65d25..40756cf88 100644 --- a/libpod/networking.go +++ b/libpod/networking.go @@ -35,7 +35,6 @@ func (r *Runtime) createNetNS(ctr *Container) (err error) { }() logrus.Debugf("Made network namespace at %s for container %s", ctrNS.Path(), ctr.ID()) - podNetwork := getPodNetwork(ctr.ID(), ctr.Name(), ctrNS.Path(), ctr.config.PortMappings) _, err = r.netPlugin.SetUpPod(podNetwork) diff --git a/libpod/oci.go b/libpod/oci.go index c122071e3..155b23640 100644 --- a/libpod/oci.go +++ b/libpod/oci.go @@ -232,11 +232,8 @@ func (r *OCIRuntime) createContainer(ctr *Container, cgroupParent string) (err e if err != nil { logrus.Warnf("Failed to add conmon to cgroupfs sandbox cgroup: %v", err) } else { - // XXX: this defer does nothing as the cgroup can't be deleted cause - // it contains the conmon pid in tasks // we need to remove this defer and delete the cgroup once conmon exits // maybe need a conmon monitor? - defer control.Delete() if err := control.Add(cgroups.Process{Pid: cmd.Process.Pid}); err != nil { logrus.Warnf("Failed to add conmon to cgroupfs sandbox cgroup: %v", err) } diff --git a/libpod/options.go b/libpod/options.go index 8a9cf94b6..f82cb20c4 100644 --- a/libpod/options.go +++ b/libpod/options.go @@ -1,7 +1,7 @@ package libpod import ( - "fmt" + "net" "path/filepath" "regexp" "syscall" @@ -13,27 +13,9 @@ import ( ) var ( - ctrNotImplemented = func(c *Container) error { - return fmt.Errorf("NOT IMPLEMENTED") - } nameRegex = regexp.MustCompile("[a-zA-Z0-9_-]+") ) -const ( - // IPCNamespace represents the IPC namespace - IPCNamespace = "ipc" - // MountNamespace represents the mount namespace - MountNamespace = "mount" - // NetNamespace represents the network namespace - NetNamespace = "network" - // PIDNamespace represents the PID namespace - PIDNamespace = "pid" - // UserNamespace represents the user namespace - UserNamespace = "user" - // UTSNamespace represents the UTS namespace - UTSNamespace = "uts" -) - // Runtime Creation Options // WithStorageConfig uses the given configuration to set up container storage @@ -100,15 +82,21 @@ func WithSignaturePolicy(path string) RuntimeOption { } } -// WithInMemoryState specifies that the runtime will be backed by an in-memory -// state only, and state will not persist after the runtime is shut down -func WithInMemoryState() RuntimeOption { +// WithStateType sets the backing state implementation for libpod +// Please note that information is not portable between backing states +// As such, if this differs between two libpods running on the same system, +// they will not share containers, and unspecified behavior may occur +func WithStateType(storeType RuntimeStateStore) RuntimeOption { return func(rt *Runtime) error { if rt.valid { return ErrRuntimeFinalized } - rt.config.InMemoryState = true + if storeType == InvalidStateStore { + return errors.Wrapf(ErrInvalidArg, "must provide a valid state store type") + } + + rt.config.StateType = storeType return nil } @@ -341,15 +329,6 @@ func WithStdin() CtrCreateOption { } } -// WithSharedNamespaces sets a container to share namespaces with another -// container. If the from container belongs to a pod, the new container will -// be added to the pod. -// By default no namespaces are shared. To share a namespace, add the Namespace -// string constant to the map as a key -func WithSharedNamespaces(from *Container, namespaces map[string]string) CtrCreateOption { - return ctrNotImplemented -} - // WithPod adds the container to a pod func (r *Runtime) WithPod(pod *Pod) CtrCreateOption { return func(ctr *Container) error { @@ -362,7 +341,6 @@ func (r *Runtime) WithPod(pod *Pod) CtrCreateOption { } ctr.config.Pod = pod.ID() - ctr.pod = pod return nil } @@ -434,6 +412,164 @@ func WithStopTimeout(timeout uint) CtrCreateOption { } } +// WithIPCNSFrom indicates the the container should join the IPC namespace of +// the given container +func WithIPCNSFrom(nsCtr *Container) CtrCreateOption { + return func(ctr *Container) error { + if ctr.valid { + return ErrCtrFinalized + } + + if !nsCtr.valid { + return ErrCtrRemoved + } + + if nsCtr.ID() == ctr.ID() { + return errors.Wrapf(ErrInvalidArg, "must specify another container") + } + + ctr.config.IPCNsCtr = nsCtr.ID() + + return nil + } +} + +// WithMountNSFrom indicates the the container should join the mount namespace +// of the given container +func WithMountNSFrom(nsCtr *Container) CtrCreateOption { + return func(ctr *Container) error { + if ctr.valid { + return ErrCtrFinalized + } + + if !nsCtr.valid { + return ErrCtrRemoved + } + + if nsCtr.ID() == ctr.ID() { + return errors.Wrapf(ErrInvalidArg, "must specify another container") + } + + ctr.config.MountNsCtr = nsCtr.ID() + + return nil + } +} + +// WithNetNSFrom indicates the the container should join the network namespace +// of the given container +func WithNetNSFrom(nsCtr *Container) CtrCreateOption { + return func(ctr *Container) error { + if ctr.valid { + return ErrCtrFinalized + } + + if !nsCtr.valid { + return ErrCtrRemoved + } + + if nsCtr.ID() == ctr.ID() { + return errors.Wrapf(ErrInvalidArg, "must specify another container") + } + + if ctr.config.CreateNetNS { + return errors.Wrapf(ErrInvalidArg, "cannot join another container's net ns as we are making a new net ns") + } + + ctr.config.NetNsCtr = nsCtr.ID() + + return nil + } +} + +// WithPIDNSFrom indicates the the container should join the PID namespace of +// the given container +func WithPIDNSFrom(nsCtr *Container) CtrCreateOption { + return func(ctr *Container) error { + if ctr.valid { + return ErrCtrFinalized + } + + if !nsCtr.valid { + return ErrCtrRemoved + } + + if nsCtr.ID() == ctr.ID() { + return errors.Wrapf(ErrInvalidArg, "must specify another container") + } + + ctr.config.PIDNsCtr = nsCtr.ID() + + return nil + } +} + +// WithUserNSFrom indicates the the container should join the user namespace of +// the given container +func WithUserNSFrom(nsCtr *Container) CtrCreateOption { + return func(ctr *Container) error { + if ctr.valid { + return ErrCtrFinalized + } + + if !nsCtr.valid { + return ErrCtrRemoved + } + + if nsCtr.ID() == ctr.ID() { + return errors.Wrapf(ErrInvalidArg, "must specify another container") + } + + ctr.config.UserNsCtr = nsCtr.ID() + + return nil + } +} + +// WithUTSNSFrom indicates the the container should join the UTS namespace of +// the given container +func WithUTSNSFrom(nsCtr *Container) CtrCreateOption { + return func(ctr *Container) error { + if ctr.valid { + return ErrCtrFinalized + } + + if !nsCtr.valid { + return ErrCtrRemoved + } + + if nsCtr.ID() == ctr.ID() { + return errors.Wrapf(ErrInvalidArg, "must specify another container") + } + + ctr.config.UTSNsCtr = nsCtr.ID() + + return nil + } +} + +// WithCgroupNSFrom indicates the the container should join the CGroup namespace +// of the given container +func WithCgroupNSFrom(nsCtr *Container) CtrCreateOption { + return func(ctr *Container) error { + if ctr.valid { + return ErrCtrFinalized + } + + if !nsCtr.valid { + return ErrCtrRemoved + } + + if nsCtr.ID() == ctr.ID() { + return errors.Wrapf(ErrInvalidArg, "must specify another container") + } + + ctr.config.CgroupNsCtr = nsCtr.ID() + + return nil + } +} + // WithNetNS indicates that the container should be given a new network // namespace with a minimal configuration // An optional array of port mappings can be provided @@ -443,8 +579,12 @@ func WithNetNS(portMappings []ocicni.PortMapping) CtrCreateOption { return ErrCtrFinalized } + if ctr.config.NetNsCtr != "" { + return errors.Wrapf(ErrInvalidArg, "container is already set to join another container's net ns, cannot create a new net ns") + } + ctr.config.CreateNetNS = true - copy(ctr.config.PortMappings, portMappings) + ctr.config.PortMappings = portMappings return nil } @@ -502,3 +642,55 @@ func WithPodLabels(labels map[string]string) PodCreateOption { return nil } } + +// WithDNSSearch sets the additional search domains of a container +func WithDNSSearch(searchDomains []string) CtrCreateOption { + return func(ctr *Container) error { + if ctr.valid { + return ErrCtrFinalized + } + ctr.config.DNSSearch = searchDomains + return nil + } +} + +// WithDNS sets additional name servers for the container +func WithDNS(dnsServers []string) CtrCreateOption { + return func(ctr *Container) error { + if ctr.valid { + return ErrCtrFinalized + } + var dns []net.IP + for _, i := range dnsServers { + result := net.ParseIP(i) + if result == nil { + return errors.Wrapf(ErrInvalidArg, "invalid IP address %s", i) + } + dns = append(dns, result) + } + ctr.config.DNSServer = dns + return nil + } +} + +// WithDNSOption sets addition dns options for the container +func WithDNSOption(dnsOptions []string) CtrCreateOption { + return func(ctr *Container) error { + if ctr.valid { + return ErrCtrFinalized + } + ctr.config.DNSOption = dnsOptions + return nil + } +} + +// WithHosts sets additional host:IP for the hosts file +func WithHosts(hosts []string) CtrCreateOption { + return func(ctr *Container) error { + if ctr.valid { + return ErrCtrFinalized + } + ctr.config.HostAdd = hosts + return nil + } +} diff --git a/libpod/runtime.go b/libpod/runtime.go index 50aa97528..804f69c9e 100644 --- a/libpod/runtime.go +++ b/libpod/runtime.go @@ -14,6 +14,25 @@ import ( "github.com/ulule/deepcopier" ) +// RuntimeStateStore is a constant indicating which state store implementation +// should be used by libpod +type RuntimeStateStore int + +const ( + // InvalidStateStore is an invalid state store + InvalidStateStore RuntimeStateStore = iota + // InMemoryStateStore is an in-memory state that will not persist data + // on containers and pods between libpod instances or after system + // reboot + InMemoryStateStore RuntimeStateStore = iota + // SQLiteStateStore is a state backed by a SQLite database + SQLiteStateStore RuntimeStateStore = iota + // SeccompDefaultPath defines the default seccomp path + SeccompDefaultPath = "/usr/share/containers/seccomp.json" + // SeccompOverridePath if this exists it overrides the default seccomp path + SeccompOverridePath = "/etc/crio/seccomp.json" +) + // A RuntimeOption is a functional option which alters the Runtime created by // NewRuntime type RuntimeOption func(*Runtime) error @@ -39,7 +58,7 @@ type RuntimeConfig struct { InsecureRegistries []string Registries []string SignaturePolicyPath string - InMemoryState bool + StateType RuntimeStateStore RuntimePath string ConmonPath string ConmonEnvVars []string @@ -57,7 +76,7 @@ var ( // Leave this empty so containers/storage will use its defaults StorageConfig: storage.StoreOptions{}, ImageDefaultTransport: DefaultTransport, - InMemoryState: false, + StateType: SQLiteStateStore, RuntimePath: "/usr/bin/runc", ConmonPath: findConmonPath(), ConmonEnvVars: []string{ @@ -176,14 +195,15 @@ func NewRuntime(options ...RuntimeOption) (runtime *Runtime, err error) { runtime.netPlugin = netPlugin // Set up the state - if runtime.config.InMemoryState { + switch runtime.config.StateType { + case InMemoryStateStore: state, err := NewInMemoryState() if err != nil { return nil, err } runtime.state = state - } else { - dbPath := filepath.Join(runtime.config.StaticDir, "state.sql") + case SQLiteStateStore: + dbPath := filepath.Join(runtime.config.StaticDir, "sql_state.db") specsDir := filepath.Join(runtime.config.StaticDir, "ocispec") // Make a directory to hold JSON versions of container OCI specs @@ -200,6 +220,8 @@ func NewRuntime(options ...RuntimeOption) (runtime *Runtime, err error) { return nil, err } runtime.state = state + default: + return nil, errors.Wrapf(ErrInvalidArg, "unrecognized state type passed") } // We now need to see if the system has restarted diff --git a/libpod/runtime_ctr.go b/libpod/runtime_ctr.go index 66dcb2f95..42f3dd892 100644 --- a/libpod/runtime_ctr.go +++ b/libpod/runtime_ctr.go @@ -3,6 +3,7 @@ package libpod import ( "os" "path/filepath" + "strings" "time" spec "github.com/opencontainers/runtime-spec/specs-go" @@ -71,25 +72,22 @@ func (r *Runtime) NewContainer(rSpec *spec.Spec, options ...CtrCreateOption) (c ctr.config.Mounts = append(ctr.config.Mounts, ctr.config.ShmDir) } - // If the container is in a pod, add it to the pod - if ctr.pod != nil { - if err := ctr.pod.addContainer(ctr); err != nil { - return nil, errors.Wrapf(err, "error adding new container to pod %s", ctr.pod.ID()) - } - } - defer func() { - if err != nil && ctr.pod != nil { - if err2 := ctr.pod.removeContainer(ctr); err2 != nil { - logrus.Errorf("Error removing partially-created container from pod %s: %s", ctr.pod.ID(), err2) - } + // Add the container to the state + // TODO: May be worth looking into recovering from name/ID collisions here + if ctr.config.Pod != "" { + // Get the pod from state + pod, err := r.state.Pod(ctr.config.Pod) + if err != nil { + return nil, errors.Wrapf(err, "cannot add container %s to pod %s", ctr.ID(), ctr.config.Pod) } - }() - if err := r.state.AddContainer(ctr); err != nil { - // TODO: Might be worth making an effort to detect duplicate IDs and names - // We can recover from that by generating a new ID for the - // container - return nil, errors.Wrapf(err, "error adding new container to state") + if err := r.state.AddContainerToPod(pod, ctr); err != nil { + return nil, err + } + } else { + if err := r.state.AddContainer(ctr); err != nil { + return nil, err + } } return ctr, nil @@ -124,6 +122,16 @@ func (r *Runtime) removeContainer(c *Container, force bool) error { return errors.Wrapf(ErrCtrStateInvalid, "container %s is paused, cannot remove until unpaused", c.ID()) } + // Check that no other containers depend on the container + deps, err := r.state.ContainerInUse(c) + if err != nil { + return err + } + if len(deps) != 0 { + depsStr := strings.Join(deps, ", ") + return errors.Wrapf(ErrCtrExists, "container %s has dependent containers which must be removed before it: %s", c.ID(), depsStr) + } + // Check that the container's in a good state to be removed if c.state.State == ContainerStateRunning && force { if err := r.ociRuntime.stopContainer(c, c.StopTimeout()); err != nil { @@ -150,25 +158,34 @@ func (r *Runtime) removeContainer(c *Container, force bool) error { return err } - if err := r.state.RemoveContainer(c); err != nil { - return errors.Wrapf(err, "error removing container from state") + // Remove the container from the state + if c.config.Pod != "" { + pod, err := r.state.Pod(c.config.Pod) + if err != nil { + return errors.Wrapf(err, "container %s is in pod %s, but pod cannot be retrieved", c.ID(), pod.ID()) + } + + if err := r.state.RemoveContainerFromPod(pod, c); err != nil { + return err + } + } else { + if err := r.state.RemoveContainer(c); err != nil { + return err + } } // Delete the container - if err := r.ociRuntime.deleteContainer(c); err != nil { - return errors.Wrapf(err, "error removing container %s from runc", c.ID()) + // Only do this if we're not ContainerStateConfigured - if we are, + // we haven't been created in the runtime yet + if c.state.State == ContainerStateConfigured { + if err := r.ociRuntime.deleteContainer(c); err != nil { + return errors.Wrapf(err, "error removing container %s from runc", c.ID()) + } } // Set container as invalid so it can no longer be used c.valid = false - // Remove container from pod, if it joined one - if c.pod != nil { - if err := c.pod.removeContainer(c); err != nil { - return errors.Wrapf(err, "error removing container from pod %s", c.pod.ID()) - } - } - return nil } diff --git a/libpod/sql_state.go b/libpod/sql_state.go index fe3232e62..42f5fe11e 100644 --- a/libpod/sql_state.go +++ b/libpod/sql_state.go @@ -15,7 +15,7 @@ import ( // DBSchema is the current DB schema version // Increments every time a change is made to the database's tables -const DBSchema = 7 +const DBSchema = 8 // SQLState is a state implementation backed by a persistent SQLite3 database type SQLState struct { @@ -284,7 +284,8 @@ func (s *SQLState) AddContainer(ctr *Container) (err error) { ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, - ?, ?, ? + ?, ?, ?, ?, ?, + ?, ?, ?, ? );` addCtrState = `INSERT INTO containerState VALUES ( ?, ?, ?, ?, ?, @@ -306,9 +307,24 @@ func (s *SQLState) AddContainer(ctr *Container) (err error) { return errors.Wrapf(err, "error marshaling container %s mounts to JSON", ctr.ID()) } - portsJSON, err := json.Marshal(ctr.config.PortMappings) + dnsServerJSON, err := json.Marshal(ctr.config.DNSServer) + if err != nil { + return errors.Wrapf(err, "error marshaling container %s DNS servers to JSON", ctr.ID()) + } + + dnsSearchJSON, err := json.Marshal(ctr.config.DNSSearch) if err != nil { - return errors.Wrapf(err, "error marshaling container %s port mappings to JSON", ctr.ID()) + return errors.Wrapf(err, "error marshaling container %s DNS search domains to JSON", ctr.ID()) + } + + dnsOptionJSON, err := json.Marshal(ctr.config.DNSOption) + if err != nil { + return errors.Wrapf(err, "error marshaling container %s DNS options to JSON", ctr.ID()) + } + + hostAddJSON, err := json.Marshal(ctr.config.HostAdd) + if err != nil { + return errors.Wrapf(err, "error marshaling container %s hosts to JSON", ctr.ID()) } labelsJSON, err := json.Marshal(ctr.config.Labels) @@ -321,6 +337,19 @@ func (s *SQLState) AddContainer(ctr *Container) (err error) { netNSPath = ctr.state.NetNS.Path() } + specJSON, err := json.Marshal(ctr.config.Spec) + if err != nil { + return errors.Wrapf(err, "error marshalling container %s spec to JSON", ctr.ID()) + } + + portsJSON := []byte{} + if len(ctr.config.PortMappings) > 0 { + portsJSON, err = json.Marshal(&ctr.config.PortMappings) + if err != nil { + return errors.Wrapf(err, "error marshalling container %s port mappings to JSON", ctr.ID()) + } + } + tx, err := s.db.Begin() if err != nil { return errors.Wrapf(err, "error beginning database transaction") @@ -348,6 +377,8 @@ func (s *SQLState) AddContainer(ctr *Container) (err error) { ctr.config.StaticDir, string(mounts), + boolToSQL(ctr.config.Privileged), + boolToSQL(ctr.config.NoNewPrivs), ctr.config.ProcessLabel, ctr.config.MountLabel, ctr.config.User, @@ -358,9 +389,13 @@ func (s *SQLState) AddContainer(ctr *Container) (err error) { stringToNullString(ctr.config.PIDNsCtr), stringToNullString(ctr.config.UserNsCtr), stringToNullString(ctr.config.UTSNsCtr), + stringToNullString(ctr.config.CgroupNsCtr), boolToSQL(ctr.config.CreateNetNS), - string(portsJSON), + string(dnsServerJSON), + string(dnsSearchJSON), + string(dnsOptionJSON), + string(hostAddJSON), boolToSQL(ctr.config.Stdin), string(labelsJSON), @@ -392,10 +427,6 @@ func (s *SQLState) AddContainer(ctr *Container) (err error) { } // Save the container's runtime spec to disk - specJSON, err := json.Marshal(ctr.config.Spec) - if err != nil { - return errors.Wrapf(err, "error marshalling container %s spec to JSON", ctr.ID()) - } specPath := getSpecPath(s.specsDir, ctr.ID()) if err := ioutil.WriteFile(specPath, specJSON, 0750); err != nil { return errors.Wrapf(err, "error saving container %s spec JSON to disk", ctr.ID()) @@ -408,6 +439,21 @@ func (s *SQLState) AddContainer(ctr *Container) (err error) { } }() + // If the container has port mappings, save them to disk + if len(ctr.config.PortMappings) > 0 { + portPath := getPortsPath(s.specsDir, ctr.ID()) + if err := ioutil.WriteFile(portPath, portsJSON, 0750); err != nil { + return errors.Wrapf(err, "error saving container %s port JSON to disk", ctr.ID()) + } + defer func() { + if err != nil { + if err2 := os.Remove(portPath); err2 != nil { + logrus.Errorf("Error removing container %s JSON ports from state: %v", ctr.ID(), err2) + } + } + }() + } + if err := tx.Commit(); err != nil { return errors.Wrapf(err, "error committing transaction to add container %s", ctr.ID()) } @@ -481,8 +527,8 @@ func (s *SQLState) UpdateContainer(ctr *Container) error { return errors.Wrapf(err, "error parsing database state for container %s", ctr.ID()) } - newState := new(containerRuntimeInfo) - newState.State = ContainerState(state) + newState := new(containerState) + newState.State = ContainerStatus(state) newState.ConfigPath = configPath newState.RunDir = runDir newState.Mountpoint = mountpoint @@ -605,6 +651,8 @@ func (s *SQLState) SaveContainer(ctr *Container) error { return errors.Wrapf(err, "error retrieving number of rows modified by update of container %s", ctr.ID()) } if rows == 0 { + // Container was probably removed elsewhere + ctr.valid = false return ErrNoSuchCtr } @@ -615,6 +663,51 @@ func (s *SQLState) SaveContainer(ctr *Container) error { return nil } +// ContainerInUse checks if other containers depend on the given container +// It returns the IDs of containers which depend on the given container +func (s *SQLState) ContainerInUse(ctr *Container) ([]string, error) { + const inUseQuery = `SELECT Id FROM containers WHERE + IPCNsCtr=? OR + MountNsCtr=? OR + NetNsCtr=? OR + PIDNsCtr=? OR + UserNsCtr=? OR + UTSNsCtr=? OR + CgroupNsCtr=?;` + + if !s.valid { + return nil, ErrDBClosed + } + + if !ctr.valid { + return nil, ErrCtrRemoved + } + + id := ctr.ID() + + rows, err := s.db.Query(inUseQuery, id, id, id, id, id, id, id) + if err != nil { + return nil, errors.Wrapf(err, "error querying database for containers that depend on container %s", id) + } + defer rows.Close() + + ids := []string{} + + for rows.Next() { + var ctrID string + if err := rows.Scan(&ctrID); err != nil { + return nil, errors.Wrapf(err, "error scanning container IDs from db rows for container %s", id) + } + + ids = append(ids, ctrID) + } + if err := rows.Err(); err != nil { + return nil, errors.Wrapf(err, "error retrieving rows for container %s", id) + } + + return ids, nil +} + // RemoveContainer removes the container from the state func (s *SQLState) RemoveContainer(ctr *Container) error { const ( @@ -668,6 +761,15 @@ func (s *SQLState) RemoveContainer(ctr *Container) error { return errors.Wrapf(err, "error removing JSON spec from state for container %s", ctr.ID()) } + // Remove containers ports JSON from disk + // May not exist, so ignore os.IsNotExist + portsPath := getPortsPath(s.specsDir, ctr.ID()) + if err := os.Remove(portsPath); err != nil { + if !os.IsNotExist(err) { + return errors.Wrapf(err, "error removing JSON ports from state for container %s", ctr.ID()) + } + } + ctr.valid = false return nil @@ -736,6 +838,11 @@ func (s *SQLState) HasPod(id string) (bool, error) { return false, ErrNotImplemented } +// PodContainers returns all the containers in a pod given the pod's full ID +func (s *SQLState) PodContainers(id string) ([]*Container, error) { + return nil, ErrNotImplemented +} + // AddPod adds a pod to the state // Only empty pods can be added to the state func (s *SQLState) AddPod(pod *Pod) error { @@ -748,6 +855,21 @@ func (s *SQLState) RemovePod(pod *Pod) error { return ErrNotImplemented } +// UpdatePod updates a pod from the database +func (s *SQLState) UpdatePod(pod *Pod) error { + return ErrNotImplemented +} + +// AddContainerToPod adds a container to the given pod +func (s *SQLState) AddContainerToPod(pod *Pod, ctr *Container) error { + return ErrNotImplemented +} + +// RemoveContainerFromPod removes a container from the given pod +func (s *SQLState) RemoveContainerFromPod(pod *Pod, ctr *Container) error { + return ErrNotImplemented +} + // AllPods retrieves all pods presently in the state func (s *SQLState) AllPods() ([]*Pod, error) { return nil, ErrNotImplemented diff --git a/libpod/sql_state_internal.go b/libpod/sql_state_internal.go index ef3b6bd4e..24d5d8bd4 100644 --- a/libpod/sql_state_internal.go +++ b/libpod/sql_state_internal.go @@ -4,6 +4,7 @@ import ( "database/sql" "encoding/json" "io/ioutil" + "os" "path/filepath" "time" @@ -178,6 +179,8 @@ func prepareDB(db *sql.DB) (err error) { StaticDir TEXT NOT NULL, Mounts TEXT NOT NULL, + Privileged INTEGER NOT NULL, + NoNewPrivs INTEGER NOT NULL, ProcessLabel TEXT NOT NULL, MountLabel TEXT NOT NULL, User TEXT NOT NULL, @@ -188,9 +191,13 @@ func prepareDB(db *sql.DB) (err error) { PIDNsCtr TEXT, UserNsCtr TEXT, UTSNsCtr TEXT, + CgroupNsCtr TEXT, CreateNetNS INTEGER NOT NULL, - PortMappings TEXT NOT NULL, + DNSServer TEXT NOT NULL, + DNSSearch TEXT NOT NULL, + DNSOption TEXT NOT NULL, + HostAdd TEXT NOT NULL, Stdin INTEGER NOT NULL, LabelsJSON TEXT NOT NULL, @@ -202,16 +209,20 @@ func prepareDB(db *sql.DB) (err error) { CHECK (ImageVolumes IN (0, 1)), CHECK (ReadOnly IN (0, 1)), CHECK (SHMSize>=0), + CHECK (Privileged IN (0, 1)), + CHECK (NoNewPrivs IN (0, 1)), CHECK (CreateNetNS IN (0, 1)), CHECK (Stdin IN (0, 1)), CHECK (StopSignal>=0), - FOREIGN KEY (Pod) REFERENCES pod(Id) DEFERRABLE INITIALLY DEFERRED, - FOREIGN KEY (IPCNsCtr) REFERENCES containers(Id) DEFERRABLE INITIALLY DEFERRED, - FOREIGN KEY (MountNsCtr) REFERENCES containers(Id) DEFERRABLE INITIALLY DEFERRED, - FOREIGN KEY (NetNsCtr) REFERENCES containers(Id) DEFERRABLE INITIALLY DEFERRED, - FOREIGN KEY (PIDNsCtr) REFERENCES containers(Id) DEFERRABLE INITIALLY DEFERRED, - FOREIGN KEY (UserNsCtr) REFERENCES containers(Id) DEFERRABLE INITIALLY DEFERRED, - FOREIGN KEY (UTSNsCtr) REFERENCES containers(Id) DEFERRABLE INITIALLY DEFERRED + FOREIGN KEY (Id) REFERENCES containerState(Id) DEFERRABLE INITIALLY DEFERRED + FOREIGN KEY (Pod) REFERENCES pod(Id) DEFERRABLE INITIALLY DEFERRED, + FOREIGN KEY (IPCNsCtr) REFERENCES containers(Id) DEFERRABLE INITIALLY DEFERRED, + FOREIGN KEY (MountNsCtr) REFERENCES containers(Id) DEFERRABLE INITIALLY DEFERRED, + FOREIGN KEY (NetNsCtr) REFERENCES containers(Id) DEFERRABLE INITIALLY DEFERRED, + FOREIGN KEY (PIDNsCtr) REFERENCES containers(Id) DEFERRABLE INITIALLY DEFERRED, + FOREIGN KEY (UserNsCtr) REFERENCES containers(Id) DEFERRABLE INITIALLY DEFERRED, + FOREIGN KEY (UTSNsCtr) REFERENCES containers(Id) DEFERRABLE INITIALLY DEFERRED, + FOREIGN KEY (CgroupNsCtr) REFERENCES containers(Id) DEFERRABLE INITIALLY DEFERRED ); ` @@ -283,6 +294,11 @@ func getSpecPath(specsDir, id string) string { return filepath.Join(specsDir, id) } +// Get filename for container port mappings on disk +func getPortsPath(specsDir, id string) string { + return filepath.Join(specsDir, id+"_ports") +} + // Convert a bool into SQL-readable format func boolToSQL(b bool) int { if b { @@ -347,19 +363,25 @@ func (s *SQLState) ctrFromScannable(row scannable) (*Container, error) { staticDir string mounts string + privileged int + noNewPrivs int processLabel string mountLabel string user string - ipcNsCtrNullStr sql.NullString - mountNsCtrNullStr sql.NullString - netNsCtrNullStr sql.NullString - pidNsCtrNullStr sql.NullString - userNsCtrNullStr sql.NullString - utsNsCtrNullStr sql.NullString + ipcNsCtrNullStr sql.NullString + mountNsCtrNullStr sql.NullString + netNsCtrNullStr sql.NullString + pidNsCtrNullStr sql.NullString + userNsCtrNullStr sql.NullString + utsNsCtrNullStr sql.NullString + cgroupNsCtrNullStr sql.NullString - createNetNS int - portMappingsJSON string + createNetNS int + dnsServerJSON string + dnsSearchJSON string + dnsOptionJSON string + hostAddJSON string stdin int labelsJSON string @@ -396,6 +418,8 @@ func (s *SQLState) ctrFromScannable(row scannable) (*Container, error) { &staticDir, &mounts, + &privileged, + &noNewPrivs, &processLabel, &mountLabel, &user, @@ -406,9 +430,13 @@ func (s *SQLState) ctrFromScannable(row scannable) (*Container, error) { &pidNsCtrNullStr, &userNsCtrNullStr, &utsNsCtrNullStr, + &cgroupNsCtrNullStr, &createNetNS, - &portMappingsJSON, + &dnsServerJSON, + &dnsSearchJSON, + &dnsOptionJSON, + &hostAddJSON, &stdin, &labelsJSON, @@ -439,7 +467,7 @@ func (s *SQLState) ctrFromScannable(row scannable) (*Container, error) { ctr := new(Container) ctr.config = new(ContainerConfig) - ctr.state = new(containerRuntimeInfo) + ctr.state = new(containerState) ctr.config.ID = id ctr.config.Name = name @@ -453,6 +481,8 @@ func (s *SQLState) ctrFromScannable(row scannable) (*Container, error) { ctr.config.ShmSize = shmSize ctr.config.StaticDir = staticDir + ctr.config.Privileged = boolFromSQL(privileged) + ctr.config.NoNewPrivs = boolFromSQL(noNewPrivs) ctr.config.ProcessLabel = processLabel ctr.config.MountLabel = mountLabel ctr.config.User = user @@ -463,6 +493,7 @@ func (s *SQLState) ctrFromScannable(row scannable) (*Container, error) { ctr.config.PIDNsCtr = stringFromNullString(pidNsCtrNullStr) ctr.config.UserNsCtr = stringFromNullString(userNsCtrNullStr) ctr.config.UTSNsCtr = stringFromNullString(utsNsCtrNullStr) + ctr.config.CgroupNsCtr = stringFromNullString(cgroupNsCtrNullStr) ctr.config.CreateNetNS = boolFromSQL(createNetNS) @@ -471,7 +502,7 @@ func (s *SQLState) ctrFromScannable(row scannable) (*Container, error) { ctr.config.StopTimeout = stopTimeout ctr.config.CgroupParent = cgroupParent - ctr.state.State = ContainerState(state) + ctr.state.State = ContainerStatus(state) ctr.state.ConfigPath = configPath ctr.state.RunDir = runDir ctr.state.Mountpoint = mountpoint @@ -490,8 +521,20 @@ func (s *SQLState) ctrFromScannable(row scannable) (*Container, error) { return nil, errors.Wrapf(err, "error parsing container %s mounts JSON", id) } - if err := json.Unmarshal([]byte(portMappingsJSON), &ctr.config.PortMappings); err != nil { - return nil, errors.Wrapf(err, "error parsing container %s port mappings JSON", id) + if err := json.Unmarshal([]byte(dnsServerJSON), &ctr.config.DNSServer); err != nil { + return nil, errors.Wrapf(err, "error parsing container %s DNS server JSON", id) + } + + if err := json.Unmarshal([]byte(dnsSearchJSON), &ctr.config.DNSSearch); err != nil { + return nil, errors.Wrapf(err, "error parsing container %s DNS search JSON", id) + } + + if err := json.Unmarshal([]byte(dnsOptionJSON), &ctr.config.DNSOption); err != nil { + return nil, errors.Wrapf(err, "error parsing container %s DNS option JSON", id) + } + + if err := json.Unmarshal([]byte(hostAddJSON), &ctr.config.HostAdd); err != nil { + return nil, errors.Wrapf(err, "error parsing container %s DNS server JSON", id) } labels := make(map[string]string) @@ -550,5 +593,25 @@ func (s *SQLState) ctrFromScannable(row scannable) (*Container, error) { } ctr.config.Spec = ociSpec + // Retrieve the ports from disk + // They may not exist - if they don't, this container just doesn't have ports + portPath := getPortsPath(s.specsDir, id) + _, err = os.Stat(portPath) + if err != nil { + if !os.IsNotExist(err) { + return nil, errors.Wrapf(err, "error stating container %s JSON ports", id) + } + } + if err == nil { + // The file exists, read it + fileContents, err := ioutil.ReadFile(portPath) + if err != nil { + return nil, errors.Wrapf(err, "error reading container %s JSON ports", id) + } + if err := json.Unmarshal(fileContents, &ctr.config.PortMappings); err != nil { + return nil, errors.Wrapf(err, "error parsing container %s JSON ports", id) + } + } + return ctr, nil } diff --git a/libpod/sql_state_test.go b/libpod/sql_state_test.go deleted file mode 100644 index 020e2ce40..000000000 --- a/libpod/sql_state_test.go +++ /dev/null @@ -1,569 +0,0 @@ -package libpod - -import ( - "encoding/json" - "io/ioutil" - "os" - "path/filepath" - "reflect" - "testing" - "time" - - "github.com/containers/storage" - "github.com/opencontainers/runtime-tools/generate" - "github.com/stretchr/testify/assert" -) - -func getTestContainer(id, name, locksDir string) (*Container, error) { - ctr := &Container{ - config: &ContainerConfig{ - ID: id, - Name: name, - RootfsImageID: id, - RootfsImageName: "testimg", - ImageVolumes: true, - ReadOnly: true, - StaticDir: "/does/not/exist/", - Stdin: true, - Labels: make(map[string]string), - StopSignal: 0, - StopTimeout: 0, - CreatedTime: time.Now(), - }, - state: &containerRuntimeInfo{ - State: ContainerStateRunning, - ConfigPath: "/does/not/exist/specs/" + id, - RunDir: "/does/not/exist/tmp/", - Mounted: true, - Mountpoint: "/does/not/exist/tmp/" + id, - PID: 1234, - }, - valid: true, - } - - g := generate.New() - ctr.config.Spec = g.Spec() - - ctr.config.Labels["test"] = "testing" - - // Must make lockfile or container will error on being retrieved from DB - lockPath := filepath.Join(locksDir, id) - lock, err := storage.GetLockfile(lockPath) - if err != nil { - return nil, err - } - ctr.lock = lock - - return ctr, nil -} - -// This horrible hack tests if containers are equal in a way that should handle -// empty arrays being dropped to nil pointers in the spec JSON -func testContainersEqual(a, b *Container) bool { - if a == nil && b == nil { - return true - } else if a == nil || b == nil { - return false - } - - if a.valid != b.valid { - return false - } - - aConfigJSON, err := json.Marshal(a.config) - if err != nil { - return false - } - - bConfigJSON, err := json.Marshal(b.config) - if err != nil { - return false - } - - if !reflect.DeepEqual(aConfigJSON, bConfigJSON) { - return false - } - - aStateJSON, err := json.Marshal(a.state) - if err != nil { - return false - } - - bStateJSON, err := json.Marshal(b.state) - if err != nil { - return false - } - - return reflect.DeepEqual(aStateJSON, bStateJSON) -} - -// Get an empty state for use in tests -// An empty Runtime is provided -func getEmptyState() (s State, p string, p2 string, err error) { - tmpDir, err := ioutil.TempDir("", "libpod_state_test_") - if err != nil { - return nil, "", "", err - } - defer func() { - if err != nil { - os.RemoveAll(tmpDir) - } - }() - - dbPath := filepath.Join(tmpDir, "db.sql") - specsDir := filepath.Join(tmpDir, "specs") - lockDir := filepath.Join(tmpDir, "locks") - - runtime := new(Runtime) - runtime.config = new(RuntimeConfig) - runtime.config.StorageConfig = storage.StoreOptions{} - - state, err := NewSQLState(dbPath, specsDir, lockDir, runtime) - if err != nil { - return nil, "", "", err - } - - return state, tmpDir, lockDir, nil -} - -func TestAddAndGetContainer(t *testing.T) { - state, path, lockPath, err := getEmptyState() - assert.NoError(t, err) - defer os.RemoveAll(path) - defer state.Close() - - testCtr, err := getTestContainer("0123456789ABCDEF0123456789ABCDEF", "test", lockPath) - assert.NoError(t, err) - - err = state.AddContainer(testCtr) - assert.NoError(t, err) - - retrievedCtr, err := state.Container(testCtr.ID()) - assert.NoError(t, err) - - // Use assert.EqualValues if the test fails to pretty print diff - // between actual and expected - if !testContainersEqual(testCtr, retrievedCtr) { - assert.EqualValues(t, testCtr, retrievedCtr) - } -} - -func TestAddAndGetContainerFromMultiple(t *testing.T) { - state, path, lockPath, err := getEmptyState() - assert.NoError(t, err) - defer os.RemoveAll(path) - defer state.Close() - - testCtr1, err := getTestContainer("11111111111111111111111111111111", "test1", lockPath) - assert.NoError(t, err) - testCtr2, err := getTestContainer("22222222222222222222222222222222", "test2", lockPath) - assert.NoError(t, err) - - err = state.AddContainer(testCtr1) - assert.NoError(t, err) - - err = state.AddContainer(testCtr2) - assert.NoError(t, err) - - retrievedCtr, err := state.Container(testCtr1.ID()) - assert.NoError(t, err) - - // Use assert.EqualValues if the test fails to pretty print diff - // between actual and expected - if !testContainersEqual(testCtr1, retrievedCtr) { - assert.EqualValues(t, testCtr1, retrievedCtr) - } -} - -func TestAddInvalidContainerFails(t *testing.T) { - state, path, _, err := getEmptyState() - assert.NoError(t, err) - defer os.RemoveAll(path) - defer state.Close() - - err = state.AddContainer(&Container{}) - assert.Error(t, err) -} - -func TestAddDuplicateIDFails(t *testing.T) { - state, path, lockPath, err := getEmptyState() - assert.NoError(t, err) - defer os.RemoveAll(path) - defer state.Close() - - testCtr1, err := getTestContainer("11111111111111111111111111111111", "test1", lockPath) - assert.NoError(t, err) - testCtr2, err := getTestContainer(testCtr1.ID(), "test2", lockPath) - assert.NoError(t, err) - - err = state.AddContainer(testCtr1) - assert.NoError(t, err) - - err = state.AddContainer(testCtr2) - assert.Error(t, err) -} - -func TestAddDuplicateNameFails(t *testing.T) { - state, path, lockPath, err := getEmptyState() - assert.NoError(t, err) - defer os.RemoveAll(path) - defer state.Close() - - testCtr1, err := getTestContainer("11111111111111111111111111111111", "test1", lockPath) - assert.NoError(t, err) - testCtr2, err := getTestContainer("22222222222222222222222222222222", testCtr1.Name(), lockPath) - assert.NoError(t, err) - - err = state.AddContainer(testCtr1) - assert.NoError(t, err) - - err = state.AddContainer(testCtr2) - assert.Error(t, err) -} - -func TestGetNonexistantContainerFails(t *testing.T) { - state, path, _, err := getEmptyState() - assert.NoError(t, err) - defer os.RemoveAll(path) - defer state.Close() - - _, err = state.Container("does not exist") - assert.Error(t, err) -} - -func TestGetContainerWithEmptyIDFails(t *testing.T) { - state, path, _, err := getEmptyState() - assert.NoError(t, err) - defer os.RemoveAll(path) - defer state.Close() - - _, err = state.Container("") - assert.Error(t, err) -} - -func TestLookupContainerWithEmptyIDFails(t *testing.T) { - state, path, _, err := getEmptyState() - assert.NoError(t, err) - defer os.RemoveAll(path) - defer state.Close() - - _, err = state.LookupContainer("") - assert.Error(t, err) -} - -func TestLookupNonexistantContainerFails(t *testing.T) { - state, path, _, err := getEmptyState() - assert.NoError(t, err) - defer os.RemoveAll(path) - - _, err = state.LookupContainer("does not exist") - assert.Error(t, err) -} - -func TestLookupContainerByFullID(t *testing.T) { - state, path, lockPath, err := getEmptyState() - assert.NoError(t, err) - defer os.RemoveAll(path) - defer state.Close() - - testCtr, err := getTestContainer("0123456789ABCDEF0123456789ABCDEF", "test", lockPath) - assert.NoError(t, err) - - err = state.AddContainer(testCtr) - assert.NoError(t, err) - - retrievedCtr, err := state.LookupContainer(testCtr.ID()) - assert.NoError(t, err) - - // Use assert.EqualValues if the test fails to pretty print diff - // between actual and expected - if !testContainersEqual(testCtr, retrievedCtr) { - assert.EqualValues(t, testCtr, retrievedCtr) - } -} - -func TestLookupContainerByUniquePartialID(t *testing.T) { - state, path, lockPath, err := getEmptyState() - assert.NoError(t, err) - defer os.RemoveAll(path) - defer state.Close() - - testCtr, err := getTestContainer("0123456789ABCDEF0123456789ABCDEF", "test", lockPath) - assert.NoError(t, err) - - err = state.AddContainer(testCtr) - assert.NoError(t, err) - - retrievedCtr, err := state.LookupContainer(testCtr.ID()[0:8]) - assert.NoError(t, err) - - // Use assert.EqualValues if the test fails to pretty print diff - // between actual and expected - if !testContainersEqual(testCtr, retrievedCtr) { - assert.EqualValues(t, testCtr, retrievedCtr) - } -} - -func TestLookupContainerByNonUniquePartialIDFails(t *testing.T) { - state, path, lockPath, err := getEmptyState() - assert.NoError(t, err) - defer os.RemoveAll(path) - defer state.Close() - - testCtr1, err := getTestContainer("00000000000000000000000000000000", "test1", lockPath) - assert.NoError(t, err) - testCtr2, err := getTestContainer("00000000000000000000000000000001", "test2", lockPath) - assert.NoError(t, err) - - err = state.AddContainer(testCtr1) - assert.NoError(t, err) - - err = state.AddContainer(testCtr2) - assert.NoError(t, err) - - _, err = state.LookupContainer(testCtr1.ID()[0:8]) - assert.Error(t, err) -} - -func TestLookupContainerByName(t *testing.T) { - state, path, lockPath, err := getEmptyState() - assert.NoError(t, err) - defer os.RemoveAll(path) - defer state.Close() - - testCtr, err := getTestContainer("0123456789ABCDEF0123456789ABCDEF", "test", lockPath) - assert.NoError(t, err) - - err = state.AddContainer(testCtr) - assert.NoError(t, err) - - retrievedCtr, err := state.LookupContainer(testCtr.Name()) - assert.NoError(t, err) - - // Use assert.EqualValues if the test fails to pretty print diff - // between actual and expected - if !testContainersEqual(testCtr, retrievedCtr) { - assert.EqualValues(t, testCtr, retrievedCtr) - } -} - -func TestHasContainerEmptyIDFails(t *testing.T) { - state, path, _, err := getEmptyState() - assert.NoError(t, err) - defer os.RemoveAll(path) - defer state.Close() - - _, err = state.HasContainer("") - assert.Error(t, err) -} - -func TestHasContainerNoSuchContainerReturnsFalse(t *testing.T) { - state, path, _, err := getEmptyState() - assert.NoError(t, err) - defer os.RemoveAll(path) - defer state.Close() - - exists, err := state.HasContainer("does not exist") - assert.NoError(t, err) - assert.False(t, exists) -} - -func TestHasContainerFindsContainer(t *testing.T) { - state, path, lockPath, err := getEmptyState() - assert.NoError(t, err) - defer os.RemoveAll(path) - defer state.Close() - - testCtr, err := getTestContainer("0123456789ABCDEF0123456789ABCDEF", "test", lockPath) - assert.NoError(t, err) - - err = state.AddContainer(testCtr) - assert.NoError(t, err) - - exists, err := state.HasContainer(testCtr.ID()) - assert.NoError(t, err) - assert.True(t, exists) -} - -func TestSaveAndUpdateContainer(t *testing.T) { - state, path, lockPath, err := getEmptyState() - assert.NoError(t, err) - defer os.RemoveAll(path) - defer state.Close() - - testCtr, err := getTestContainer("0123456789ABCDEF0123456789ABCDEF", "test", lockPath) - assert.NoError(t, err) - - err = state.AddContainer(testCtr) - assert.NoError(t, err) - - retrievedCtr, err := state.Container(testCtr.ID()) - assert.NoError(t, err) - - retrievedCtr.state.State = ContainerStateStopped - retrievedCtr.state.ExitCode = 127 - retrievedCtr.state.FinishedTime = time.Now() - - err = state.SaveContainer(retrievedCtr) - assert.NoError(t, err) - - err = state.UpdateContainer(testCtr) - assert.NoError(t, err) - - // Use assert.EqualValues if the test fails to pretty print diff - // between actual and expected - if !testContainersEqual(testCtr, retrievedCtr) { - assert.EqualValues(t, testCtr, retrievedCtr) - } -} - -func TestUpdateContainerNotInDatabaseReturnsError(t *testing.T) { - state, path, lockPath, err := getEmptyState() - assert.NoError(t, err) - defer os.RemoveAll(path) - defer state.Close() - - testCtr, err := getTestContainer("0123456789ABCDEF0123456789ABCDEF", "test", lockPath) - assert.NoError(t, err) - - err = state.UpdateContainer(testCtr) - assert.Error(t, err) - assert.False(t, testCtr.valid) -} - -func TestUpdateInvalidContainerReturnsError(t *testing.T) { - state, path, _, err := getEmptyState() - assert.NoError(t, err) - defer os.RemoveAll(path) - defer state.Close() - - err = state.UpdateContainer(&Container{}) - assert.Error(t, err) -} - -func TestSaveInvalidContainerReturnsError(t *testing.T) { - state, path, _, err := getEmptyState() - assert.NoError(t, err) - defer os.RemoveAll(path) - defer state.Close() - - err = state.SaveContainer(&Container{}) - assert.Error(t, err) -} - -func TestSaveContainerNotInStateReturnsError(t *testing.T) { - state, path, lockPath, err := getEmptyState() - assert.NoError(t, err) - defer os.RemoveAll(path) - defer state.Close() - - testCtr, err := getTestContainer("0123456789ABCDEF0123456789ABCDEF", "test", lockPath) - assert.NoError(t, err) - - err = state.SaveContainer(testCtr) - assert.Error(t, err) -} - -func TestRemoveContainer(t *testing.T) { - state, path, lockPath, err := getEmptyState() - assert.NoError(t, err) - defer os.RemoveAll(path) - defer state.Close() - - testCtr, err := getTestContainer("0123456789ABCDEF0123456789ABCDEF", "test", lockPath) - assert.NoError(t, err) - - err = state.AddContainer(testCtr) - assert.NoError(t, err) - - ctrs, err := state.AllContainers() - assert.NoError(t, err) - assert.Equal(t, 1, len(ctrs)) - - err = state.RemoveContainer(testCtr) - assert.NoError(t, err) - - ctrs2, err := state.AllContainers() - assert.NoError(t, err) - assert.Equal(t, 0, len(ctrs2)) -} - -func TestRemoveNonexistantContainerFails(t *testing.T) { - state, path, lockPath, err := getEmptyState() - assert.NoError(t, err) - defer os.RemoveAll(path) - defer state.Close() - - testCtr, err := getTestContainer("0123456789ABCDEF0123456789ABCDEF", "test", lockPath) - assert.NoError(t, err) - - err = state.RemoveContainer(testCtr) - assert.Error(t, err) -} - -func TestGetAllContainersOnNewStateIsEmpty(t *testing.T) { - state, path, _, err := getEmptyState() - assert.NoError(t, err) - defer os.RemoveAll(path) - defer state.Close() - - ctrs, err := state.AllContainers() - assert.NoError(t, err) - assert.Equal(t, 0, len(ctrs)) -} - -func TestGetAllContainersWithOneContainer(t *testing.T) { - state, path, lockPath, err := getEmptyState() - assert.NoError(t, err) - defer os.RemoveAll(path) - defer state.Close() - - testCtr, err := getTestContainer("0123456789ABCDEF0123456789ABCDEF", "test", lockPath) - assert.NoError(t, err) - - err = state.AddContainer(testCtr) - assert.NoError(t, err) - - ctrs, err := state.AllContainers() - assert.NoError(t, err) - assert.Equal(t, 1, len(ctrs)) - - // Use assert.EqualValues if the test fails to pretty print diff - // between actual and expected - if !testContainersEqual(testCtr, ctrs[0]) { - assert.EqualValues(t, testCtr, ctrs[0]) - } -} - -func TestGetAllContainersTwoContainers(t *testing.T) { - state, path, lockPath, err := getEmptyState() - assert.NoError(t, err) - defer os.RemoveAll(path) - defer state.Close() - - testCtr1, err := getTestContainer("11111111111111111111111111111111", "test1", lockPath) - assert.NoError(t, err) - testCtr2, err := getTestContainer("22222222222222222222222222222222", "test2", lockPath) - assert.NoError(t, err) - - err = state.AddContainer(testCtr1) - assert.NoError(t, err) - - err = state.AddContainer(testCtr2) - assert.NoError(t, err) - - ctrs, err := state.AllContainers() - assert.NoError(t, err) - assert.Equal(t, 2, len(ctrs)) - - // Containers should be ordered by creation time - - // Use assert.EqualValues if the test fails to pretty print diff - // between actual and expected - if !testContainersEqual(testCtr2, ctrs[0]) { - assert.EqualValues(t, testCtr2, ctrs[0]) - } - if !testContainersEqual(testCtr1, ctrs[1]) { - assert.EqualValues(t, testCtr1, ctrs[1]) - } -} diff --git a/libpod/state.go b/libpod/state.go index 4a79b8d2d..01ae58bd1 100644 --- a/libpod/state.go +++ b/libpod/state.go @@ -16,9 +16,7 @@ type State interface { // Checks if a container with the given ID is present in the state HasContainer(id string) (bool, error) // Adds container to state - // If the container belongs to a pod, that pod must already be present - // in the state when the container is added, and the container must be - // present in the pod + // The container cannot be part of a pod AddContainer(ctr *Container) error // Removes container from state // The container will only be removed from the state, not from the pod @@ -28,6 +26,13 @@ type State interface { UpdateContainer(ctr *Container) error // SaveContainer saves a container's current state to the backing store SaveContainer(ctr *Container) error + // ContainerInUse checks if other containers depend upon a given + // container + // It returns a slice of the IDs of containers which depend on the given + // container. If the slice is empty, no container depend on the given + // container. + // A container cannot be removed if other containers depend on it + ContainerInUse(ctr *Container) ([]string, error) // Retrieves all containers presently in state AllContainers() ([]*Container, error) @@ -37,6 +42,8 @@ type State interface { LookupPod(idOrName string) (*Pod, error) // Checks if a pod with the given ID is present in the state HasPod(id string) (bool, error) + // Get all the containers in a pod. Accepts full ID of pod. + PodContainers(id string) ([]*Container, error) // Adds pod to state // Only empty pods can be added to the state AddPod(pod *Pod) error @@ -44,6 +51,14 @@ type State interface { // Containers within a pod will not be removed from the state, and will // not be changed to remove them from the now-removed pod RemovePod(pod *Pod) error + // UpdatePod updates a pod's state from the backing store + UpdatePod(pod *Pod) error + // AddContainerToPod adds a container to an existing pod + // The container given will be added to the state and the pod + AddContainerToPod(pod *Pod, ctr *Container) error + // RemoveContainerFromPod removes a container from an existing pod + // The container will also be removed from the state + RemoveContainerFromPod(pod *Pod, ctr *Container) error // Retrieves all pods presently in state AllPods() ([]*Pod, error) } diff --git a/libpod/state_test.go b/libpod/state_test.go new file mode 100644 index 000000000..0a18c988d --- /dev/null +++ b/libpod/state_test.go @@ -0,0 +1,622 @@ +package libpod + +import ( + "io/ioutil" + "os" + "path/filepath" + "testing" + "time" + + "github.com/containers/storage" + "github.com/stretchr/testify/assert" +) + +// Returns state, tmp directory containing all state files, locks directory +// (subdirectory of tmp dir), and error +// Closing the state and removing the given tmp directory should be sufficient +// to clean up +type emptyStateFunc func() (State, string, string, error) + +const ( + tmpDirPrefix = "libpod_state_test_" +) + +var ( + testedStates = map[string]emptyStateFunc{ + "sql": getEmptySQLState, + "in-memory": getEmptyInMemoryState, + } +) + +// Get an empty in-memory state for use in tests +func getEmptyInMemoryState() (s State, p string, p2 string, err error) { + tmpDir, err := ioutil.TempDir("", tmpDirPrefix) + if err != nil { + return nil, "", "", err + } + defer func() { + if err != nil { + os.RemoveAll(tmpDir) + } + }() + + state, err := NewInMemoryState() + if err != nil { + return nil, "", "", err + } + + // Don't need a separate locks dir as InMemoryState stores nothing on + // disk + return state, tmpDir, tmpDir, nil +} + +// Get an empty SQL state for use in tests +// An empty Runtime is provided +func getEmptySQLState() (s State, p string, p2 string, err error) { + tmpDir, err := ioutil.TempDir("", tmpDirPrefix) + if err != nil { + return nil, "", "", err + } + defer func() { + if err != nil { + os.RemoveAll(tmpDir) + } + }() + + dbPath := filepath.Join(tmpDir, "db.sql") + specsDir := filepath.Join(tmpDir, "specs") + lockDir := filepath.Join(tmpDir, "locks") + + runtime := new(Runtime) + runtime.config = new(RuntimeConfig) + runtime.config.StorageConfig = storage.StoreOptions{} + + state, err := NewSQLState(dbPath, specsDir, lockDir, runtime) + if err != nil { + return nil, "", "", err + } + + return state, tmpDir, lockDir, nil +} + +func runForAllStates(t *testing.T, testName string, testFunc func(*testing.T, State, string)) { + for stateName, stateFunc := range testedStates { + state, path, lockPath, err := stateFunc() + if err != nil { + t.Fatalf("Error initializing state %s", stateName) + } + defer os.RemoveAll(path) + defer state.Close() + + testName = testName + "-" + stateName + + success := t.Run(testName, func(t *testing.T) { + testFunc(t, state, lockPath) + }) + if !success { + t.Fail() + t.Logf("%s failed for state %s", testName, stateName) + } + } +} + +func TestAddAndGetContainer(t *testing.T) { + runForAllStates(t, "TestAddAndGetContainer", addAndGetContainer) +} + +func addAndGetContainer(t *testing.T, state State, lockPath string) { + testCtr, err := getTestContainer("0123456789ABCDEF0123456789ABCDEF", "test", lockPath) + assert.NoError(t, err) + + err = state.AddContainer(testCtr) + assert.NoError(t, err) + + retrievedCtr, err := state.Container(testCtr.ID()) + assert.NoError(t, err) + + // Use assert.EqualValues if the test fails to pretty print diff + // between actual and expected + if !testContainersEqual(testCtr, retrievedCtr) { + assert.EqualValues(t, testCtr, retrievedCtr) + } +} + +func TestAddAndGetContainerFromMultiple(t *testing.T) { + runForAllStates(t, "TestAddAndGetContainerFromMultiple", addAndGetContainerFromMultiple) +} + +func addAndGetContainerFromMultiple(t *testing.T, state State, lockPath string) { + testCtr1, err := getTestContainer("11111111111111111111111111111111", "test1", lockPath) + assert.NoError(t, err) + testCtr2, err := getTestContainer("22222222222222222222222222222222", "test2", lockPath) + assert.NoError(t, err) + + err = state.AddContainer(testCtr1) + assert.NoError(t, err) + + err = state.AddContainer(testCtr2) + assert.NoError(t, err) + + retrievedCtr, err := state.Container(testCtr1.ID()) + assert.NoError(t, err) + + // Use assert.EqualValues if the test fails to pretty print diff + // between actual and expected + if !testContainersEqual(testCtr1, retrievedCtr) { + assert.EqualValues(t, testCtr1, retrievedCtr) + } +} + +func TestAddInvalidContainerFails(t *testing.T) { + runForAllStates(t, "TestAddInvalidContainerFails", addInvalidContainerFails) +} + +func addInvalidContainerFails(t *testing.T, state State, lockPath string) { + err := state.AddContainer(&Container{config: &ContainerConfig{ID: "1234"}}) + assert.Error(t, err) +} + +func TestAddDuplicateCtrIDFails(t *testing.T) { + runForAllStates(t, "TestAddDuplicateCtrIDFails", addDuplicateCtrIDFails) +} + +func addDuplicateCtrIDFails(t *testing.T, state State, lockPath string) { + testCtr1, err := getTestContainer("11111111111111111111111111111111", "test1", lockPath) + assert.NoError(t, err) + testCtr2, err := getTestContainer(testCtr1.ID(), "test2", lockPath) + assert.NoError(t, err) + + err = state.AddContainer(testCtr1) + assert.NoError(t, err) + + err = state.AddContainer(testCtr2) + assert.Error(t, err) +} + +func TestAddDuplicateCtrNameFails(t *testing.T) { + runForAllStates(t, "TestAddDuplicateCtrNameFails", addDuplicateCtrNameFails) +} + +func addDuplicateCtrNameFails(t *testing.T, state State, lockPath string) { + testCtr1, err := getTestContainer("11111111111111111111111111111111", "test1", lockPath) + assert.NoError(t, err) + testCtr2, err := getTestContainer("22222222222222222222222222222222", testCtr1.Name(), lockPath) + assert.NoError(t, err) + + err = state.AddContainer(testCtr1) + assert.NoError(t, err) + + err = state.AddContainer(testCtr2) + assert.Error(t, err) +} + +func TestGetNonexistentContainerFails(t *testing.T) { + runForAllStates(t, "TestGetNonexistentContainerFails", getNonexistentContainerFails) +} + +func getNonexistentContainerFails(t *testing.T, state State, lockPath string) { + _, err := state.Container("does not exist") + assert.Error(t, err) +} + +func TestGetContainerWithEmptyIDFails(t *testing.T) { + runForAllStates(t, "TestGetContainerWithEmptyIDFails", getContainerWithEmptyIDFails) +} + +func getContainerWithEmptyIDFails(t *testing.T, state State, lockPath string) { + _, err := state.Container("") + assert.Error(t, err) +} + +func TestLookupContainerWithEmptyIDFails(t *testing.T) { + runForAllStates(t, "TestLookupContainerWithEmptyIDFails", lookupContainerWithEmptyIDFails) +} + +func lookupContainerWithEmptyIDFails(t *testing.T, state State, lockPath string) { + _, err := state.LookupContainer("") + assert.Error(t, err) +} + +func TestLookupNonexistentContainerFails(t *testing.T) { + runForAllStates(t, "TestLookupNonexistantContainerFails", lookupNonexistentContainerFails) +} + +func lookupNonexistentContainerFails(t *testing.T, state State, lockPath string) { + _, err := state.LookupContainer("does not exist") + assert.Error(t, err) +} + +func TestLookupContainerByFullID(t *testing.T) { + runForAllStates(t, "TestLookupContainerByFullID", lookupContainerByFullID) +} + +func lookupContainerByFullID(t *testing.T, state State, lockPath string) { + testCtr, err := getTestContainer("0123456789ABCDEF0123456789ABCDEF", "test", lockPath) + assert.NoError(t, err) + + err = state.AddContainer(testCtr) + assert.NoError(t, err) + + retrievedCtr, err := state.LookupContainer(testCtr.ID()) + assert.NoError(t, err) + + // Use assert.EqualValues if the test fails to pretty print diff + // between actual and expected + if !testContainersEqual(testCtr, retrievedCtr) { + assert.EqualValues(t, testCtr, retrievedCtr) + } +} + +func TestLookupContainerByUniquePartialID(t *testing.T) { + runForAllStates(t, "TestLookupContainerByUniquePartialID", lookupContainerByUniquePartialID) +} + +func lookupContainerByUniquePartialID(t *testing.T, state State, lockPath string) { + testCtr, err := getTestContainer("0123456789ABCDEF0123456789ABCDEF", "test", lockPath) + assert.NoError(t, err) + + err = state.AddContainer(testCtr) + assert.NoError(t, err) + + retrievedCtr, err := state.LookupContainer(testCtr.ID()[0:8]) + assert.NoError(t, err) + + // Use assert.EqualValues if the test fails to pretty print diff + // between actual and expected + if !testContainersEqual(testCtr, retrievedCtr) { + assert.EqualValues(t, testCtr, retrievedCtr) + } +} + +func TestLookupContainerByNonUniquePartialIDFails(t *testing.T) { + runForAllStates(t, "TestLookupContainerByNonUniquePartialIDFails", lookupContainerByNonUniquePartialIDFails) +} + +func lookupContainerByNonUniquePartialIDFails(t *testing.T, state State, lockPath string) { + testCtr1, err := getTestContainer("00000000000000000000000000000000", "test1", lockPath) + assert.NoError(t, err) + testCtr2, err := getTestContainer("00000000000000000000000000000001", "test2", lockPath) + assert.NoError(t, err) + + err = state.AddContainer(testCtr1) + assert.NoError(t, err) + + err = state.AddContainer(testCtr2) + assert.NoError(t, err) + + _, err = state.LookupContainer(testCtr1.ID()[0:8]) + assert.Error(t, err) +} + +func TestLookupContainerByName(t *testing.T) { + runForAllStates(t, "TestLookupContainerByName", lookupContainerByName) +} + +func lookupContainerByName(t *testing.T, state State, lockPath string) { + state, path, lockPath, err := getEmptySQLState() + assert.NoError(t, err) + defer os.RemoveAll(path) + defer state.Close() + + testCtr, err := getTestContainer("0123456789ABCDEF0123456789ABCDEF", "test", lockPath) + assert.NoError(t, err) + + err = state.AddContainer(testCtr) + assert.NoError(t, err) + + retrievedCtr, err := state.LookupContainer(testCtr.Name()) + assert.NoError(t, err) + + // Use assert.EqualValues if the test fails to pretty print diff + // between actual and expected + if !testContainersEqual(testCtr, retrievedCtr) { + assert.EqualValues(t, testCtr, retrievedCtr) + } +} + +func TestHasContainerEmptyIDFails(t *testing.T) { + runForAllStates(t, "TestHasContainerEmptyIDFails", hasContainerEmptyIDFails) +} + +func hasContainerEmptyIDFails(t *testing.T, state State, lockPath string) { + _, err := state.HasContainer("") + assert.Error(t, err) +} + +func TestHasContainerNoSuchContainerReturnsFalse(t *testing.T) { + runForAllStates(t, "TestHasContainerNoSuchContainerReturnsFalse", hasContainerNoSuchContainerReturnsFalse) +} + +func hasContainerNoSuchContainerReturnsFalse(t *testing.T, state State, lockPath string) { + exists, err := state.HasContainer("does not exist") + assert.NoError(t, err) + assert.False(t, exists) +} + +func TestHasContainerFindsContainer(t *testing.T) { + runForAllStates(t, "TestHasContainerFindsContainer", hasContainerFindsContainer) +} + +func hasContainerFindsContainer(t *testing.T, state State, lockPath string) { + testCtr, err := getTestContainer("0123456789ABCDEF0123456789ABCDEF", "test", lockPath) + assert.NoError(t, err) + + err = state.AddContainer(testCtr) + assert.NoError(t, err) + + exists, err := state.HasContainer(testCtr.ID()) + assert.NoError(t, err) + assert.True(t, exists) +} + +func TestSaveAndUpdateContainer(t *testing.T) { + runForAllStates(t, "TestSaveAndUpdateContainer", saveAndUpdateContainer) +} + +func saveAndUpdateContainer(t *testing.T, state State, lockPath string) { + testCtr, err := getTestContainer("0123456789ABCDEF0123456789ABCDEF", "test", lockPath) + assert.NoError(t, err) + + err = state.AddContainer(testCtr) + assert.NoError(t, err) + + retrievedCtr, err := state.Container(testCtr.ID()) + assert.NoError(t, err) + + retrievedCtr.state.State = ContainerStateStopped + retrievedCtr.state.ExitCode = 127 + retrievedCtr.state.FinishedTime = time.Now() + + err = state.SaveContainer(retrievedCtr) + assert.NoError(t, err) + + err = state.UpdateContainer(testCtr) + assert.NoError(t, err) + + // Use assert.EqualValues if the test fails to pretty print diff + // between actual and expected + if !testContainersEqual(testCtr, retrievedCtr) { + assert.EqualValues(t, testCtr, retrievedCtr) + } +} + +func TestUpdateContainerNotInDatabaseReturnsError(t *testing.T) { + runForAllStates(t, "TestUpdateContainerNotInDatabaseReturnsError", updateContainerNotInDatabaseReturnsError) +} + +func updateContainerNotInDatabaseReturnsError(t *testing.T, state State, lockPath string) { + testCtr, err := getTestContainer("0123456789ABCDEF0123456789ABCDEF", "test", lockPath) + assert.NoError(t, err) + + err = state.UpdateContainer(testCtr) + assert.Error(t, err) + assert.False(t, testCtr.valid) +} + +func TestUpdateInvalidContainerReturnsError(t *testing.T) { + runForAllStates(t, "TestUpdateInvalidContainerReturnsError", updateInvalidContainerReturnsError) +} + +func updateInvalidContainerReturnsError(t *testing.T, state State, lockPath string) { + err := state.UpdateContainer(&Container{config: &ContainerConfig{ID: "1234"}}) + assert.Error(t, err) +} + +func TestSaveInvalidContainerReturnsError(t *testing.T) { + runForAllStates(t, "TestSaveInvalidContainerReturnsError", saveInvalidContainerReturnsError) +} + +func saveInvalidContainerReturnsError(t *testing.T, state State, lockPath string) { + err := state.SaveContainer(&Container{config: &ContainerConfig{ID: "1234"}}) + assert.Error(t, err) +} + +func TestSaveContainerNotInStateReturnsError(t *testing.T) { + runForAllStates(t, "TestSaveContainerNotInStateReturnsError", saveContainerNotInStateReturnsError) +} + +func saveContainerNotInStateReturnsError(t *testing.T, state State, lockPath string) { + testCtr, err := getTestContainer("0123456789ABCDEF0123456789ABCDEF", "test", lockPath) + assert.NoError(t, err) + + err = state.SaveContainer(testCtr) + assert.Error(t, err) + assert.False(t, testCtr.valid) +} + +func TestRemoveContainer(t *testing.T) { + runForAllStates(t, "TestRemoveContainer", removeContainer) +} + +func removeContainer(t *testing.T, state State, lockPath string) { + testCtr, err := getTestContainer("0123456789ABCDEF0123456789ABCDEF", "test", lockPath) + assert.NoError(t, err) + + err = state.AddContainer(testCtr) + assert.NoError(t, err) + + ctrs, err := state.AllContainers() + assert.NoError(t, err) + assert.Equal(t, 1, len(ctrs)) + + err = state.RemoveContainer(testCtr) + assert.NoError(t, err) + + ctrs2, err := state.AllContainers() + assert.NoError(t, err) + assert.Equal(t, 0, len(ctrs2)) +} + +func TestRemoveNonexistantContainerFails(t *testing.T) { + runForAllStates(t, "TestRemoveNonexistantContainerFails", removeNonexistantContainerFails) +} + +func removeNonexistantContainerFails(t *testing.T, state State, lockPath string) { + testCtr, err := getTestContainer("0123456789ABCDEF0123456789ABCDEF", "test", lockPath) + assert.NoError(t, err) + + err = state.RemoveContainer(testCtr) + assert.Error(t, err) +} + +func TestGetAllContainersOnNewStateIsEmpty(t *testing.T) { + runForAllStates(t, "TestGetAllContainersOnNewStateIsEmpty", getAllContainersOnNewStateIsEmpty) +} + +func getAllContainersOnNewStateIsEmpty(t *testing.T, state State, lockPath string) { + ctrs, err := state.AllContainers() + assert.NoError(t, err) + assert.Equal(t, 0, len(ctrs)) +} + +func TestGetAllContainersWithOneContainer(t *testing.T) { + runForAllStates(t, "TestGetAllContainersWithOneContainer", getAllContainersWithOneContainer) +} + +func getAllContainersWithOneContainer(t *testing.T, state State, lockPath string) { + testCtr, err := getTestContainer("0123456789ABCDEF0123456789ABCDEF", "test", lockPath) + assert.NoError(t, err) + + err = state.AddContainer(testCtr) + assert.NoError(t, err) + + ctrs, err := state.AllContainers() + assert.NoError(t, err) + assert.Equal(t, 1, len(ctrs)) + + // Use assert.EqualValues if the test fails to pretty print diff + // between actual and expected + if !testContainersEqual(testCtr, ctrs[0]) { + assert.EqualValues(t, testCtr, ctrs[0]) + } +} + +func TestGetAllContainersTwoContainers(t *testing.T) { + runForAllStates(t, "TestGetAllContainersTwoContainers", getAllContainersTwoContainers) +} + +func getAllContainersTwoContainers(t *testing.T, state State, lockPath string) { + testCtr1, err := getTestContainer("11111111111111111111111111111111", "test1", lockPath) + assert.NoError(t, err) + testCtr2, err := getTestContainer("22222222222222222222222222222222", "test2", lockPath) + assert.NoError(t, err) + + err = state.AddContainer(testCtr1) + assert.NoError(t, err) + + err = state.AddContainer(testCtr2) + assert.NoError(t, err) + + ctrs, err := state.AllContainers() + assert.NoError(t, err) + assert.Equal(t, 2, len(ctrs)) +} + +func TestContainerInUseInvalidContainer(t *testing.T) { + runForAllStates(t, "TestContainerInUseInvalidContainer", containerInUseInvalidContainer) +} + +func containerInUseInvalidContainer(t *testing.T, state State, lockPath string) { + _, err := state.ContainerInUse(&Container{}) + assert.Error(t, err) +} + +func TestContainerInUseOneContainer(t *testing.T) { + runForAllStates(t, "TestContainerInUseOneContainer", containerInUseOneContainer) +} + +func containerInUseOneContainer(t *testing.T, state State, lockPath string) { + testCtr1, err := getTestContainer("11111111111111111111111111111111", "test1", lockPath) + assert.NoError(t, err) + testCtr2, err := getTestContainer("22222222222222222222222222222222", "test2", lockPath) + assert.NoError(t, err) + + testCtr2.config.UserNsCtr = testCtr1.config.ID + + err = state.AddContainer(testCtr1) + assert.NoError(t, err) + + err = state.AddContainer(testCtr2) + assert.NoError(t, err) + + ids, err := state.ContainerInUse(testCtr1) + assert.NoError(t, err) + assert.Equal(t, 1, len(ids)) + assert.Equal(t, testCtr2.config.ID, ids[0]) +} + +func TestContainerInUseTwoContainers(t *testing.T) { + runForAllStates(t, "TestContainerInUseTwoContainers", containerInUseTwoContainers) +} + +func containerInUseTwoContainers(t *testing.T, state State, lockPath string) { + testCtr1, err := getTestContainer("11111111111111111111111111111111", "test1", lockPath) + assert.NoError(t, err) + testCtr2, err := getTestContainer("22222222222222222222222222222222", "test2", lockPath) + assert.NoError(t, err) + testCtr3, err := getTestContainer("33333333333333333333333333333333", "test3", lockPath) + assert.NoError(t, err) + + testCtr2.config.UserNsCtr = testCtr1.config.ID + testCtr3.config.IPCNsCtr = testCtr1.config.ID + + err = state.AddContainer(testCtr1) + assert.NoError(t, err) + + err = state.AddContainer(testCtr2) + assert.NoError(t, err) + + err = state.AddContainer(testCtr3) + assert.NoError(t, err) + + ids, err := state.ContainerInUse(testCtr1) + assert.NoError(t, err) + assert.Equal(t, 2, len(ids)) +} + +func TestCannotRemoveContainerWithDependency(t *testing.T) { + runForAllStates(t, "TestCannotRemoveContainerWithDependency", cannotRemoveContainerWithDependency) +} + +func cannotRemoveContainerWithDependency(t *testing.T, state State, lockPath string) { + testCtr1, err := getTestContainer("11111111111111111111111111111111", "test1", lockPath) + assert.NoError(t, err) + testCtr2, err := getTestContainer("22222222222222222222222222222222", "test2", lockPath) + assert.NoError(t, err) + + testCtr2.config.UserNsCtr = testCtr1.config.ID + + err = state.AddContainer(testCtr1) + assert.NoError(t, err) + + err = state.AddContainer(testCtr2) + assert.NoError(t, err) + + err = state.RemoveContainer(testCtr1) + assert.Error(t, err) +} + +func TestCanRemoveContainerAfterDependencyRemoved(t *testing.T) { + runForAllStates(t, "TestCanRemoveContainerAfterDependencyRemoved", canRemoveContainerAfterDependencyRemoved) +} + +func canRemoveContainerAfterDependencyRemoved(t *testing.T, state State, lockPath string) { + testCtr1, err := getTestContainer("11111111111111111111111111111111", "test1", lockPath) + assert.NoError(t, err) + testCtr2, err := getTestContainer("22222222222222222222222222222222", "test2", lockPath) + assert.NoError(t, err) + + testCtr2.config.UserNsCtr = testCtr1.config.ID + + err = state.AddContainer(testCtr1) + assert.NoError(t, err) + + err = state.AddContainer(testCtr2) + assert.NoError(t, err) + + err = state.RemoveContainer(testCtr2) + assert.NoError(t, err) + + err = state.RemoveContainer(testCtr1) + assert.NoError(t, err) +} diff --git a/libpod/stats.go b/libpod/stats.go index 86da0679e..e87654277 100644 --- a/libpod/stats.go +++ b/libpod/stats.go @@ -29,11 +29,16 @@ type ContainerStats struct { // GetContainerStats gets the running stats for a given container func (c *Container) GetContainerStats(previousStats *ContainerStats) (*ContainerStats, error) { stats := new(ContainerStats) + stats.ContainerID = c.ID() c.lock.Lock() defer c.lock.Unlock() if err := c.syncContainer(); err != nil { return stats, errors.Wrapf(err, "error updating container %s state", c.ID()) } + if c.state.State != ContainerStateRunning { + return stats, nil + } + cgroup, err := cgroups.Load(cgroups.V1, c.CGroupPath()) if err != nil { return stats, errors.Wrapf(err, "unable to load cgroup at %+v", c.CGroupPath()) @@ -50,7 +55,6 @@ func (c *Container) GetContainerStats(previousStats *ContainerStats) (*Container previousCPU := previousStats.CPUNano previousSystem := previousStats.SystemNano - stats.ContainerID = c.ID() stats.CPU = calculateCPUPercent(cgroupStats, previousCPU, previousSystem) stats.MemUsage = cgroupStats.Memory.Usage.Usage stats.MemLimit = getMemLimit(cgroupStats.Memory.Usage.Limit) diff --git a/libpod/test_common.go b/libpod/test_common.go new file mode 100644 index 000000000..131a44d0f --- /dev/null +++ b/libpod/test_common.go @@ -0,0 +1,116 @@ +package libpod + +import ( + "encoding/json" + "net" + "path/filepath" + "reflect" + "time" + + "github.com/containers/storage" + "github.com/cri-o/ocicni/pkg/ocicni" + "github.com/opencontainers/runtime-tools/generate" +) + +// nolint +func getTestContainer(id, name, locksDir string) (*Container, error) { + ctr := &Container{ + config: &ContainerConfig{ + ID: id, + Name: name, + RootfsImageID: id, + RootfsImageName: "testimg", + ImageVolumes: true, + ReadOnly: true, + StaticDir: "/does/not/exist/", + Stdin: true, + Labels: make(map[string]string), + StopSignal: 0, + StopTimeout: 0, + CreatedTime: time.Now(), + Privileged: true, + Mounts: []string{"/does/not/exist"}, + DNSServer: []net.IP{net.ParseIP("192.168.1.1"), net.ParseIP("192.168.2.2")}, + DNSSearch: []string{"example.com", "example.example.com"}, + PortMappings: []ocicni.PortMapping{ + { + HostPort: 80, + ContainerPort: 90, + Protocol: "tcp", + HostIP: "192.168.3.3", + }, + { + HostPort: 100, + ContainerPort: 110, + Protocol: "udp", + HostIP: "192.168.4.4", + }, + }, + }, + state: &containerState{ + State: ContainerStateRunning, + ConfigPath: "/does/not/exist/specs/" + id, + RunDir: "/does/not/exist/tmp/", + Mounted: true, + Mountpoint: "/does/not/exist/tmp/" + id, + PID: 1234, + }, + valid: true, + } + + g := generate.New() + ctr.config.Spec = g.Spec() + + ctr.config.Labels["test"] = "testing" + + // Must make lockfile or container will error on being retrieved from DB + lockPath := filepath.Join(locksDir, id) + lock, err := storage.GetLockfile(lockPath) + if err != nil { + return nil, err + } + ctr.lock = lock + + return ctr, nil +} + +// This horrible hack tests if containers are equal in a way that should handle +// empty arrays being dropped to nil pointers in the spec JSON +// nolint +func testContainersEqual(a, b *Container) bool { + if a == nil && b == nil { + return true + } else if a == nil || b == nil { + return false + } + + if a.valid != b.valid { + return false + } + + aConfigJSON, err := json.Marshal(a.config) + if err != nil { + return false + } + + bConfigJSON, err := json.Marshal(b.config) + if err != nil { + return false + } + + if !reflect.DeepEqual(aConfigJSON, bConfigJSON) { + return false + } + + aStateJSON, err := json.Marshal(a.state) + if err != nil { + return false + } + + bStateJSON, err := json.Marshal(b.state) + if err != nil { + return false + } + + return reflect.DeepEqual(aStateJSON, bStateJSON) +} diff --git a/test/podman_attach.bats b/test/podman_attach.bats index 8676b2e43..605a44789 100644 --- a/test/podman_attach.bats +++ b/test/podman_attach.bats @@ -11,14 +11,14 @@ function setup() { } @test "attach to a bogus container" { - run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} attach foobar" + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} attach foobar echo "$output" [ "$status" -eq 125 ] } @test "attach to non-running container" { ${PODMAN_BINARY} ${PODMAN_OPTIONS} create --name foobar -d -i ${ALPINE} ls - run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} attach foobar" + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} attach foobar echo "$output" [ "$status" -eq 125 ] } @@ -26,7 +26,7 @@ function setup() { @test "attach to multiple containers" { ${PODMAN_BINARY} ${PODMAN_OPTIONS} run --name foobar1 -d -i ${ALPINE} /bin/sh ${PODMAN_BINARY} ${PODMAN_OPTIONS} run --name foobar2 -d -i ${ALPINE} /bin/sh - run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} attach foobar1 foobar2" + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} attach foobar1 foobar2 echo "$output" [ "$status" -eq 125 ] } diff --git a/test/podman_commit.bats b/test/podman_commit.bats index 9257743e9..45c2b010e 100644 --- a/test/podman_commit.bats +++ b/test/podman_commit.bats @@ -13,109 +13,85 @@ function setup() { } @test "podman commit default" { - run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} run -d --name my_ctr ${FEDORA_MINIMAL} sleep 6000" - echo "$output" - [ "$status" -eq 0 ] - run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} commit my_ctr image-committed" + ${PODMAN_BINARY} ${PODMAN_OPTIONS} run -d --name my_ctr ${FEDORA_MINIMAL} sleep 6000 + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} commit my_ctr image-committed echo "$output" [ "$status" -eq 0 ] run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} images | grep image-committed" echo "$output" [ "$status" -eq 0 ] - run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} rmi image-committed" - echo "$output" - [ "$status" -eq 0 ] - run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} stop my_ctr" + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} rmi image-committed echo "$output" [ "$status" -eq 0 ] + ${PODMAN_BINARY} ${PODMAN_OPTIONS} stop my_ctr } @test "podman commit with message flag" { - run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} run -d --name my_ctr ${FEDORA_MINIMAL} sleep 6000" - echo "$output" - [ "$status" -eq 0 ] - run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} commit --message testing-commit my_ctr image-committed" + ${PODMAN_BINARY} ${PODMAN_OPTIONS} run -d --name my_ctr ${FEDORA_MINIMAL} sleep 6000 + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} commit --message testing-commit my_ctr image-committed echo "$output" [ "$status" -eq 0 ] run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} inspect image-committed | grep testing-commit" echo "$output" [ "$status" -eq 0 ] - run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} rmi image-committed" - echo "$output" - [ "$status" -eq 0 ] - run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} stop my_ctr" + ${PODMAN_BINARY} ${PODMAN_OPTIONS} rmi image-committed echo "$output" [ "$status" -eq 0 ] + ${PODMAN_BINARY} ${PODMAN_OPTIONS} stop my_ctr } @test "podman commit with author flag" { - run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} run -d --name my_ctr ${FEDORA_MINIMAL} sleep 6000" - echo "$output" - [ "$status" -eq 0 ] - run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} commit --author author-name my_ctr image-committed" + ${PODMAN_BINARY} ${PODMAN_OPTIONS} run -d --name my_ctr ${FEDORA_MINIMAL} sleep 6000 + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} commit --author author-name my_ctr image-committed echo "$output" [ "$status" -eq 0 ] run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} inspect image-committed | grep author-name" echo "$output" [ "$status" -eq 0 ] - run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} rmi image-committed" - echo "$output" - [ "$status" -eq 0 ] - run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} stop my_ctr" + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} rmi image-committed echo "$output" [ "$status" -eq 0 ] + ${PODMAN_BINARY} ${PODMAN_OPTIONS} stop my_ctr } @test "podman commit with change flag" { - run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} run -d --name my_ctr ${FEDORA_MINIMAL} sleep 6000" - echo "$output" - [ "$status" -eq 0 ] - run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} commit --change LABEL=image=blue my_ctr image-committed" + ${PODMAN_BINARY} ${PODMAN_OPTIONS} run -d --name my_ctr ${FEDORA_MINIMAL} sleep 6000 + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} commit --change LABEL=image=blue my_ctr image-committed echo "$output" [ "$status" -eq 0 ] run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} inspect image-committed | grep blue" echo "$output" [ "$status" -eq 0 ] - run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} rmi image-committed" - echo "$output" - [ "$status" -eq 0 ] - run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} stop my_ctr" + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} rmi image-committed echo "$output" [ "$status" -eq 0 ] + ${PODMAN_BINARY} ${PODMAN_OPTIONS} stop my_ctr } @test "podman commit with pause flag" { - run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} run -d --name my_ctr ${FEDORA_MINIMAL} sleep 6000" - echo "$output" - [ "$status" -eq 0 ] - run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} commit --pause=false my_ctr image-committed" + ${PODMAN_BINARY} ${PODMAN_OPTIONS} run -d --name my_ctr ${FEDORA_MINIMAL} sleep 6000 + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} commit --pause=false my_ctr image-committed echo "$output" [ "$status" -eq 0 ] run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} images | grep image-committed" echo "$output" [ "$status" -eq 0 ] - run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} rmi image-committed" - echo "$output" - [ "$status" -eq 0 ] - run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} stop my_ctr" + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} rmi image-committed echo "$output" [ "$status" -eq 0 ] + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} stop my_ctr } @test "podman commit non-running container" { - run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} create --name my_ctr ${FEDORA_MINIMAL} ls" - echo "$output" - [ "$status" -eq 0 ] - run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} commit my_ctr image-committed" + ${PODMAN_BINARY} ${PODMAN_OPTIONS} create --name my_ctr ${FEDORA_MINIMAL} ls + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} commit my_ctr image-committed echo "$output" [ "$status" -eq 0 ] run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} images | grep image-committed" echo "$output" [ "$status" -eq 0 ] - run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} rmi image-committed" - echo "$output" - [ "$status" -eq 0 ] - run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} rm my_ctr" + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} rmi image-committed echo "$output" [ "$status" -eq 0 ] + ${PODMAN_BINARY} ${PODMAN_OPTIONS} rm my_ctr } diff --git a/test/podman_diff.bats b/test/podman_diff.bats index 9ed088807..ed1a17309 100644 --- a/test/podman_diff.bats +++ b/test/podman_diff.bats @@ -23,7 +23,6 @@ function teardown() { } @test "test diff with json output" { - # run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} diff --format json $IMAGE | python -m json.tool" run ${PODMAN_BINARY} $PODMAN_OPTIONS diff --format json $BB echo "$output" [ "$status" -eq 0 ] diff --git a/test/podman_export.bats b/test/podman_export.bats index 3847ab14c..40fc7bb4f 100644 --- a/test/podman_export.bats +++ b/test/podman_export.bats @@ -11,14 +11,14 @@ function setup() { } @test "podman export output flag" { - run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} create $BB ls" + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} create $BB ls echo "$output" [ "$status" -eq 0 ] ctr_id="$output" - run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} export -o container.tar $ctr_id" + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} export -o container.tar $ctr_id echo "$output" [ "$status" -eq 0 ] - run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} rm $ctr_id" + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} rm $ctr_id echo "$output" [ "$status" -eq 0 ] rm -f container.tar diff --git a/test/podman_images.bats b/test/podman_images.bats index 3ea8af793..5812e8f8b 100644 --- a/test/podman_images.bats +++ b/test/podman_images.bats @@ -10,7 +10,7 @@ function setup() { copy_images } @test "podman images" { - run bash -c ${PODMAN_BINARY} ${PODMAN_OPTIONS} images + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} images echo "$output" [ "$status" -eq 0 ] } diff --git a/test/podman_inspect.bats b/test/podman_inspect.bats index 9f9336f48..19e5a0a9b 100644 --- a/test/podman_inspect.bats +++ b/test/podman_inspect.bats @@ -23,11 +23,11 @@ function setup() { } @test "podman inspect with format" { - run bash -c ${PODMAN_BINARY} $PODMAN_OPTIONS inspect --format {{.ID}} ${ALPINE} + run ${PODMAN_BINARY} $PODMAN_OPTIONS inspect --format {{.ID}} ${ALPINE} echo "$output" [ "$status" -eq 0 ] inspectOutput="$output" - run bash -c ${PODMAN_BINARY} $PODMAN_OPTIONS images --no-trunc --quiet ${ALPINE} + bash -c run ${PODMAN_BINARY} $PODMAN_OPTIONS images --no-trunc --quiet ${ALPINE} | sed -e 's/sha256://g' echo "$output" [ "$status" -eq 0 ] [ "$output" = "$inspectOutput" ] @@ -42,7 +42,7 @@ function setup() { } @test "podman inspect container with size" { - run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} create ${BB} ls" + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} create ${BB} ls echo "$output" [ "$status" -eq 0 ] run bash -c "${PODMAN_BINARY} $PODMAN_OPTIONS inspect --size -l | python -m json.tool | grep SizeRootFs" diff --git a/test/podman_kill.bats b/test/podman_kill.bats index bb55ed31d..f24bd0971 100644 --- a/test/podman_kill.bats +++ b/test/podman_kill.bats @@ -11,61 +11,61 @@ function setup() { } @test "kill a bogus container" { - run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} kill foobar" + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} kill foobar echo "$output" [ "$status" -ne 0 ] } @test "kill a running container by id" { - run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} run -d ${ALPINE} sleep 9999" + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} run -d ${ALPINE} sleep 9999 [ "$status" -eq 0 ] ctr_id="$output" - run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} ps" + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} ps [ "$status" -eq 0 ] - run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} kill $ctr_id" + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} kill $ctr_id [ "$status" -eq 0 ] - run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} ps" + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} ps [ "$status" -eq 0 ] } @test "kill a running container by id with TERM" { - run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} run -d ${ALPINE} sleep 9999" + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} run -d ${ALPINE} sleep 9999 [ "$status" -eq 0 ] ctr_id="$output" - run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} ps" + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} ps [ "$status" -eq 0 ] - run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} kill -s TERM $ctr_id" + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} kill -s TERM $ctr_id [ "$status" -eq 0 ] - run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} ps --no-trunc" + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} ps --no-trunc [ "$status" -eq 0 ] } @test "kill a running container by name" { - run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} run --name test1 -d ${ALPINE} sleep 9999" + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} run --name test1 -d ${ALPINE} sleep 9999 [ "$status" -eq 0 ] - run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} ps" + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} ps [ "$status" -eq 0 ] - run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} kill -s TERM test1" + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} kill -s TERM test1 [ "$status" -eq 0 ] - run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} ps --no-trunc" + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} ps --no-trunc [ "$status" -eq 0 ] } @test "kill a running container by id with a bogus signal" { - run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} run -d ${ALPINE} sleep 9999" + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} run -d ${ALPINE} sleep 9999 [ "$status" -eq 0 ] ctr_id="$output" - run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} ps" + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} ps [ "$status" -eq 0 ] - run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} kill -s foobar $ctr_id" + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} kill -s foobar $ctr_id [ "$status" -eq 125 ] - run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} ps --no-trunc" + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} ps --no-trunc [ "$status" -eq 0 ] } @test "kill the latest container run" { ${PODMAN_BINARY} ${PODMAN_OPTIONS} run -d ${ALPINE} sleep 9999 - run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} kill -l" + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} kill -l echo "$output" [ "$status" -eq 0 ] } diff --git a/test/podman_load.bats b/test/podman_load.bats index 6fe8638b6..ca93a5522 100644 --- a/test/podman_load.bats +++ b/test/podman_load.bats @@ -10,36 +10,36 @@ function teardown() { cleanup_test } @test "podman load input flag" { - run bash -c ${PODMAN_BINARY} ${PODMAN_OPTIONS} save -o alpine.tar $ALPINE + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} save -o alpine.tar $ALPINE echo "$output" [ "$status" -eq 0 ] - run bash -c ${PODMAN_BINARY} ${PODMAN_OPTIONS} rmi $ALPINE + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} rmi $ALPINE echo "$output" [ "$status" -eq 0 ] - run bash -c ${PODMAN_BINARY} ${PODMAN_OPTIONS} load -i alpine.tar + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} load -i alpine.tar echo "$output" [ "$status" -eq 0 ] rm -f alpine.tar } @test "podman load oci-archive image" { - run bash -c ${PODMAN_BINARY} ${PODMAN_OPTIONS} save -o alpine.tar --format oci-archive $ALPINE + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} save -o alpine.tar --format oci-archive $ALPINE [ "$status" -eq 0 ] - run bash -c ${PODMAN_BINARY} $PODMAN_OPTIONS rmi $ALPINE + run ${PODMAN_BINARY} $PODMAN_OPTIONS rmi $ALPINE [ "$status" -eq 0 ] - run bash -c ${PODMAN_BINARY} ${PODMAN_OPTIONS} load -i alpine.tar + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} load -i alpine.tar echo "$output" [ "$status" -eq 0 ] rm -f alpine.tar } @test "podman load oci-archive image with signature-policy" { - run bash -c ${PODMAN_BINARY} ${PODMAN_OPTIONS} save -o alpine.tar --format oci-archive $ALPINE + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} save -o alpine.tar --format oci-archive $ALPINE [ "$status" -eq 0 ] - run bash -c ${PODMAN_BINARY} $PODMAN_OPTIONS rmi $ALPINE + run ${PODMAN_BINARY} $PODMAN_OPTIONS rmi $ALPINE [ "$status" -eq 0 ] cp /etc/containers/policy.json /tmp - run bash -c ${PODMAN_BINARY} ${PODMAN_OPTIONS} load --signature-policy /tmp/policy.json -i alpine.tar + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} load --signature-policy /tmp/policy.json -i alpine.tar echo "$output" [ "$status" -eq 0 ] rm -f /tmp/policy.json @@ -47,29 +47,29 @@ function teardown() { } @test "podman load using quiet flag" { - run bash -c ${PODMAN_BINARY} ${PODMAN_OPTIONS} save -o alpine.tar $ALPINE + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} save -o alpine.tar $ALPINE echo "$output" [ "$status" -eq 0 ] - run bash -c ${PODMAN_BINARY} ${PODMAN_OPTIONS} rmi $ALPINE + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} rmi $ALPINE echo "$output" [ "$status" -eq 0 ] - run bash -c ${PODMAN_BINARY} ${PODMAN_OPTIONS} load -q -i alpine.tar + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} load -q -i alpine.tar echo "$output" [ "$status" -eq 0 ] rm -f alpine.tar } @test "podman load directory" { - run bash -c ${PODMAN_BINARY} ${PODMAN_OPTIONS} save --format oci-dir -o alp-dir $ALPINE + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} save --format oci-dir -o alp-dir $ALPINE echo "$output" [ "$status" -eq 0 ] - run bash -c ${PODMAN_BINARY} ${PODMAN_OPTIONS} rmi $ALPINE + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} rmi $ALPINE echo "$output" [ "$status" -eq 0 ] - run bash -c ${PODMAN_BINARY} ${PODMAN_OPTIONS} load -i alp-dir + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} load -i alp-dir echo "$output" [ "$status" -eq 0 ] - run bash -c ${PODMAN_BINARY} ${PODMAN_OPTIONS} rmi alp-dir + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} rmi alp-dir echo "$output" [ "$status" -eq 0 ] } diff --git a/test/podman_logs.bats b/test/podman_logs.bats index 342ffac5e..e76bf665a 100644 --- a/test/podman_logs.bats +++ b/test/podman_logs.bats @@ -11,41 +11,41 @@ function setup() { } @test "display logs for container" { - run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} run -d $BB ls" + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} run -d $BB ls echo "$output" [ "$status" -eq 0 ] ctr_id="$output" - run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} logs $ctr_id" + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} logs $ctr_id echo "$output" [ "$status" -eq 0 ] - run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} rm $ctr_id" + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} rm $ctr_id echo "$output" [ "$status" -eq 0 ] } @test "tail three lines of logs for container" { - run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} run -d $BB ls" + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} run -d $BB ls echo "$output" [ "$status" -eq 0 ] ctr_id="$output" - run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} logs --tail 3 $ctr_id" + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} logs --tail 3 $ctr_id echo "$output" lines=$(echo "$output" | wc -l) [ "$status" -eq 0 ] [[ $(wc -l < "$output" ) -le 3 ]] - run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} rm $ctr_id" + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} rm $ctr_id echo "$output" [ "$status" -eq 0 ] } @test "display logs for container since a given time" { - run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} run -d $BB ls" + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} run -d $BB ls echo "$output" [ "$status" -eq 0 ] - run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} logs --since 2017-08-07T10:10:09.056611202-04:00 -l" + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} logs --since 2017-08-07T10:10:09.056611202-04:00 -l echo "$output" [ "$status" -eq 0 ] - run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} rm -l" + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} rm -l echo "$output" [ "$status" -eq 0 ] } diff --git a/test/podman_mount.bats b/test/podman_mount.bats index bc6be1a19..f3d04fb98 100644 --- a/test/podman_mount.bats +++ b/test/podman_mount.bats @@ -13,26 +13,26 @@ function setup() { } @test "mount" { - run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} create $BB ls" + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} create $BB ls echo "$output" [ "$status" -eq 0 ] ctr_id="$output" - run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} mount $ctr_id" + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} mount $ctr_id echo "$output" [ "$status" -eq 0 ] run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} mount --notruncate | grep $ctr_id" echo "$output" [ "$status" -eq 0 ] - run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} unmount $ctr_id" + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} unmount $ctr_id echo "$output" [ "$status" -eq 0 ] - run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} mount $ctr_id" + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} mount $ctr_id echo "$output" [ "$status" -eq 0 ] run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} mount --format=json | python -m json.tool | grep $ctr_id" echo "$output" [ "$status" -eq 0 ] - run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} unmount $ctr_id" + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} unmount $ctr_id echo "$output" [ "$status" -eq 0 ] } diff --git a/test/podman_networking.bats b/test/podman_networking.bats index 017f24d8f..b27c16634 100644 --- a/test/podman_networking.bats +++ b/test/podman_networking.bats @@ -11,13 +11,49 @@ function setup() { } @test "test network connection with default bridge" { - run ${KPOD_BINARY} ${KPOD_OPTIONS} run -dt ${ALPINE} wget www.yahoo.com + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} run -dt ${ALPINE} wget www.yahoo.com + echo "$output" + [ "$status" -eq 0 ] + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} wait --latest echo "$output" [ "$status" -eq 0 ] } @test "test network connection with host" { - run ${KPOD_BINARY} ${KPOD_OPTIONS} run -dt --network host ${ALPINE} wget www.yahoo.com + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} run -dt --network host ${ALPINE} wget www.yahoo.com + echo "$output" + [ "$status" -eq 0 ] + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} wait --latest + echo "$output" + [ "$status" -eq 0 ] +} + +@test "expose port 222" { + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} run -dt --expose 222-223 ${ALPINE} /bin/sh + echo "$output" + [ "$status" -eq 0 ] + run bash -c "iptables -t nat -L" + echo "$output" + [ "$status" -eq 0 ] + run bash -c "iptables -t nat -L | grep 223" + echo "$output" + [ "$status" -eq 0 ] +} + +@test "expose host port 80 to container port 8000" { + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} run -dt -p 80:8000 ${ALPINE} /bin/sh + echo "$output" + [ "$status" -eq 0 ] + run bash -c "iptables -t nat -L | grep 8000" + echo "$output" + [ "$status" -eq 0 ] +} + +@test "expose ports in image" { + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} run -dt -P docker.io/library/nginx:latest + echo "$output" + [ "$status" -eq 0 ] + run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} inspect -l | grep ': 80,'" echo "$output" [ "$status" -eq 0 ] } diff --git a/test/podman_pause.bats b/test/podman_pause.bats index b8f0a8746..4e98eb130 100644 --- a/test/podman_pause.bats +++ b/test/podman_pause.bats @@ -11,102 +11,102 @@ function teardown() { } @test "pause a bogus container" { - run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} pause foobar" + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} pause foobar echo "$output" [ "$status" -eq 125 ] } @test "unpause a bogus container" { - run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} unpause foobar" + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} unpause foobar echo "$output" [ "$status" -eq 125 ] } @test "pause a created container by id" { - run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} run -d $BB sleep 60" + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} run -d $BB sleep 60 echo "$output" [ "$status" -eq 0 ] ctr_id=`echo "$output" | tail -n 1` - run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} pause $ctr_id" + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} pause $ctr_id echo "$output" [ "$status" -eq 0 ] - run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} unpause $ctr_id" + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} unpause $ctr_id echo "$output" [ "$status" -eq 0 ] - run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} rm -f $ctr_id" + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} rm -f $ctr_id echo "$output" [ "$status" -eq 0 ] } @test "pause a running container by id" { - run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} run -d $BB sleep 60" + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} run -d $BB sleep 60 echo "$output" [ "$status" -eq 0 ] ctr_id=`echo "$output" | tail -n 1` - run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} pause $ctr_id" + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} pause $ctr_id echo "$output" [ "$status" -eq 0 ] - run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} unpause $ctr_id" + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} unpause $ctr_id echo "$output" [ "$status" -eq 0 ] - run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} rm -f $ctr_id" + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} rm -f $ctr_id echo "$output" [ "$status" -eq 0 ] } @test "unpause a running container" { - run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} run -d $BB sleep 60" + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} run -d $BB sleep 60 echo "$output" [ "$status" -eq 0 ] ctr_id=`echo "$output" | tail -n 1` - run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} unpause $ctr_id" + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} unpause $ctr_id echo "$output" [ "$status" -eq 125 ] - run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} rm -f $ctr_id" + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} rm -f $ctr_id echo "$output" [ "$status" -eq 0 ] } @test "remove a paused container by id" { skip "Test needs to wait for --force to work for podman rm" - run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} run -d $BB sleep 60" + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} run -d $BB sleep 60 echo "$output" [ "$status" -eq 0 ] ctr_id=`echo "$output" | tail -n 1` - run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} pause $ctr_id" + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} pause $ctr_id echo "$output" [ "$status" -eq 0 ] - run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} rm $ctr_id" + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} rm $ctr_id echo "$output" [ "$status" -eq 125 ] - run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} rm --force $ctr_id" + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} rm --force $ctr_id echo "$output" [ "$status" -eq 0 ] } @test "stop a paused container created by id" { - run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} run -d $BB sleep 60" + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} run -d $BB sleep 60 echo "$output" [ "$status" -eq 0 ] ctr_id=`echo "$output" | tail -n 1` - run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} pause $ctr_id" + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} pause $ctr_id echo "$output" [ "$status" -eq 0 ] - run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} stop $ctr_id" + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} stop $ctr_id echo "$output" [ "$status" -eq 125 ] - run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} unpause $ctr_id" + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} unpause $ctr_id echo "$output" [ "$status" -eq 0 ] - run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} ps -a --filter id=$ctr_id" + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} ps -a --filter id=$ctr_id echo "$output" [ "$status" -eq 0 ] # Container should be running after unpause and shouldn't # be removable without the force flag. - run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} rm $ctr_id" + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} rm $ctr_id echo "$output" [ "$status" -eq 125 ] - run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} rm -f $ctr_id" + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} rm -f $ctr_id echo "$output" [ "$status" -eq 0 ] } diff --git a/test/podman_ps.bats b/test/podman_ps.bats index b99c84304..8f2232cbf 100644 --- a/test/podman_ps.bats +++ b/test/podman_ps.bats @@ -12,92 +12,92 @@ function teardown() { } @test "podman ps with no containers" { - run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} ps" + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} ps echo "$output" [ "$status" -eq 0 ] } @test "podman ps default" { - run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} run -d ${ALPINE} ls" + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} run -d ${ALPINE} ls echo "$output" [ "$status" -eq 0 ] - run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} ps" + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} ps echo "$output" [ "$status" -eq 0 ] } @test "podman ps all flag" { - run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} run -d ${ALPINE} ls" + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} run -d ${ALPINE} ls echo "$output" [ "$status" -eq 0 ] - run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} ps -a" + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} ps -a echo "$output" [ "$status" -eq 0 ] } @test "podman ps size flag" { - run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} run -d ${ALPINE} ls" + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} run -d ${ALPINE} ls echo "$output" [ "$status" -eq 0 ] - run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} ps -a --size" + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} ps -a --size echo "$output" [ "$status" -eq 0 ] } @test "podman ps quiet flag" { - run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} run -d ${ALPINE} ls" + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} run -d ${ALPINE} ls ctr_id="$output" echo "$output" [ "$status" -eq 0 ] - run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} ps -a --quiet" + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} ps -a --quiet echo "$output" [ "$status" -eq 0 ] } @test "podman ps latest flag" { - run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} run -d ${ALPINE} ls" + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} run -d ${ALPINE} ls echo "$output" [ "$status" -eq 0 ] - run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} ps --latest" + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} ps --latest echo "$output" [ "$status" -eq 0 ] } @test "podman ps last flag" { - run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} run -d ${ALPINE} ls" + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} run -d ${ALPINE} ls echo "$output" [ "$status" -eq 0 ] - run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} run -d ${BB} ls" + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} run -d ${BB} ls echo "$output" [ "$status" -eq 0 ] - run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} run -d ${ALPINE} ls -s" + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} run -d ${ALPINE} ls -s echo "$output" [ "$status" -eq 0 ] - run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} ps --last 2" + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} ps --last 2 echo "$output" [ "$status" -eq 0 ] } @test "podman ps no-trunc flag" { - run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} run -d ${ALPINE} ls" + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} run -d ${ALPINE} ls echo "$output" [ "$status" -eq 0 ] - run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} ps -a --no-trunc" + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} ps -a --no-trunc echo "$output" [ "$status" -eq 0 ] } @test "podman ps namespace flag" { - run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} run -d ${ALPINE} ls" + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} run -d ${ALPINE} ls echo "$output" [ "$status" -eq 0 ] - run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} ps --all --namespace" + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} ps --all --namespace echo "$output" [ "$status" -eq 0 ] } @test "podman ps namespace flag and format flag = json" { - run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} run -d ${ALPINE} ls" + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} run -d ${ALPINE} ls echo "$output" [ "$status" -eq 0 ] run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} ps -a --ns --format json | python -m json.tool | grep namespace" @@ -106,7 +106,7 @@ function teardown() { } @test "podman ps without namespace flag and format flag = json" { - run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} run -d ${ALPINE} ls" + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} run -d ${ALPINE} ls echo "$output" [ "$status" -eq 0 ] run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} ps -a --format json | python -m json.tool | grep namespace" @@ -115,76 +115,76 @@ function teardown() { } @test "podman ps format flag = go template" { - run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} run -d ${ALPINE} ls" + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} run -d ${ALPINE} ls echo "$output" [ "$status" -eq 0 ] - run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} ps -a --format 'table {{.ID}} {{.Image}} {{.Labels}}'" + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} ps -a --format 'table {{.ID}} {{.Image}} {{.Labels}}' echo "$output" [ "$status" -eq 0 ] } @test "podman ps filter flag - ancestor" { - run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} run -d ${ALPINE} ls" + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} run -d ${ALPINE} ls echo "$output" [ "$status" -eq 0 ] - run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} ps -a --filter ancestor=${ALPINE}" + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} ps -a --filter ancestor=${ALPINE} echo "$output" [ "$status" -eq 0 ] } @test "podman ps filter flag - id" { - run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} run -d ${ALPINE} ls" + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} run -d ${ALPINE} ls echo "$output" [ "$status" -eq 0 ] - run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} ps -a --filter id=$ctr_id" + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} ps -a --filter id=$ctr_id echo "$output" [ "$status" -eq 0 ] } @test "podman ps filter flag - status" { - run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} run -d ${ALPINE} sleep 99" + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} run -d ${ALPINE} sleep 99 ctr_id="$output" echo "$output" [ "$status" -eq 0 ] - run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} ps -a --filter status=running" + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} ps -a --filter status=running echo "$output" [ "$status" -eq 0 ] - run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} stop $ctr_id" + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} stop $ctr_id echo "$output" [ "$status" -eq 0 ] } @test "podman ps short options" { - run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} run -d ${ALPINE} sleep 99" + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} run -d ${ALPINE} sleep 99 ctr_id="$output" echo "$output" [ "$status" -eq 0 ] - run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} ps -aq" + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} ps -aq echo "$output" [ "$status" -eq 0 ] - run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} stop $ctr_id" + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} stop $ctr_id echo "$output" [ "$status" -eq 0 ] } @test "podman ps with mutually exclusive flags" { - run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} run -d ${ALPINE} sleep 99" + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} run -d ${ALPINE} sleep 99 ctr_id="$output" echo "$output" [ "$status" -eq 0 ] - run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} ps -aqs" + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} ps -aqs echo "$output" [ "$status" -ne 0 ] - run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} ps -a --ns -s" + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} ps -a --ns -s echo "$output" [ "$status" -ne 0 ] - run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} ps -a --ns format {{.ID}}" + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} ps -a --ns format {{.ID}} echo "$output" [ "$status" -ne 0 ] - run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} ps -a --ns --format json" + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} ps -a --ns --format json echo "$output" [ "$status" -eq 0 ] - run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} stop $ctr_id" + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} stop $ctr_id echo "$output" [ "$status" -eq 0 ] } diff --git a/test/podman_pull.bats b/test/podman_pull.bats index b0de1a8ab..4052d56d5 100644 --- a/test/podman_pull.bats +++ b/test/podman_pull.bats @@ -10,7 +10,7 @@ function teardown() { run ${PODMAN_BINARY} ${PODMAN_OPTIONS} pull debian:6.0.10 echo "$output" [ "$status" -eq 0 ] - run bash -c ${PODMAN_BINARY} $PODMAN_OPTIONS rmi debian:6.0.10 + run ${PODMAN_BINARY} $PODMAN_OPTIONS rmi debian:6.0.10 echo "$output" [ "$status" -eq 0 ] } @@ -19,7 +19,7 @@ function teardown() { run ${PODMAN_BINARY} $PODMAN_OPTIONS pull debian echo "$output" [ "$status" -eq 0 ] - run bash -c ${PODMAN_BINARY} $PODMAN_OPTIONS rmi debian + run ${PODMAN_BINARY} $PODMAN_OPTIONS rmi debian echo "$output" [ "$status" -eq 0 ] } @@ -28,7 +28,7 @@ function teardown() { run ${PODMAN_BINARY} $PODMAN_OPTIONS pull registry.fedoraproject.org/fedora:rawhide echo "$output" [ "$status" -eq 0 ] - run bash -c ${PODMAN_BINARY} $PODMAN_OPTIONS rmi registry.fedoraproject.org/fedora:rawhide + run ${PODMAN_BINARY} $PODMAN_OPTIONS rmi registry.fedoraproject.org/fedora:rawhide echo "$output" [ "$status" -eq 0 ] } @@ -37,7 +37,7 @@ function teardown() { run ${PODMAN_BINARY} $PODMAN_OPTIONS pull registry.fedoraproject.org/fedora echo "$output" [ "$status" -eq 0 ] - run bash -c ${PODMAN_BINARY} $PODMAN_OPTIONS rmi registry.fedoraproject.org/fedora + run ${PODMAN_BINARY} $PODMAN_OPTIONS rmi registry.fedoraproject.org/fedora echo "$output" [ "$status" -eq 0 ] } @@ -46,7 +46,7 @@ function teardown() { run ${PODMAN_BINARY} $PODMAN_OPTIONS pull alpine@sha256:1072e499f3f655a032e88542330cf75b02e7bdf673278f701d7ba61629ee3ebe echo "$output" [ "$status" -eq 0 ] - run bash -c ${PODMAN_BINARY} $PODMAN_OPTIONS rmi alpine:latest + run ${PODMAN_BINARY} $PODMAN_OPTIONS rmi alpine:latest echo "$output" [ "$status" -eq 0 ] } @@ -61,7 +61,7 @@ function teardown() { run ${PODMAN_BINARY} ${PODMAN_OPTIONS} pull debian echo "$output" [ "$status" -eq 0 ] - run bash -c ${PODMAN_BINARY} $PODMAN_OPTIONS rmi docker.io/debian:latest + run ${PODMAN_BINARY} $PODMAN_OPTIONS rmi docker.io/debian:latest echo "$output" [ "$status" -eq 0 ] } @@ -70,7 +70,7 @@ function teardown() { run ${PODMAN_BINARY} ${PODMAN_OPTIONS} pull debian:6.0.10 echo "$output" [ "$status" -eq 0 ] - run bash -c ${PODMAN_BINARY} $PODMAN_OPTIONS rmi docker.io/debian:6.0.10 + run ${PODMAN_BINARY} $PODMAN_OPTIONS rmi docker.io/debian:6.0.10 echo "$output" [ "$status" -eq 0 ] } @@ -79,57 +79,57 @@ function teardown() { run ${PODMAN_BINARY} ${PODMAN_OPTIONS} pull alpine echo "$output" [ "$status" -eq 0 ] - run bash -c ${PODMAN_BINARY} ${PODMAN_OPTIONS} save -o alp.tar alpine + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} save -o alp.tar alpine echo "$output" [ "$status" -eq 0 ] - run bash -c ${PODMAN_BINARY} ${PODMAN_OPTIONS} rmi alpine + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} rmi alpine echo "$output" [ "$status" -eq 0 ] - run bash -c ${PODMAN_BINARY} ${PODMAN_OPTIONS} pull docker-archive:alp.tar + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} pull docker-archive:alp.tar echo "$output" [ "$status" -eq 0 ] - run bash -c ${PODMAN_BINARY} ${PODMAN_OPTIONS} rmi alpine + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} rmi alpine echo "$output" [ "$status" -eq 0 ] rm -f alp.tar } @test "podman pull from oci-archive" { - run bash -c ${PODMAN_BINARY} ${PODMAN_OPTIONS} pull alpine + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} pull alpine echo "$output" [ "$status" -eq 0 ] - run bash -c ${PODMAN_BINARY} ${PODMAN_OPTIONS} save --format oci-archive -o oci-alp.tar alpine + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} save --format oci-archive -o oci-alp.tar alpine echo "$output" [ "$status" -eq 0 ] - run bash -c ${PODMAN_BINARY} ${PODMAN_OPTIONS} rmi alpine + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} rmi alpine echo "$output" [ "$status" -eq 0 ] - run bash -c ${PODMAN_BINARY} ${PODMAN_OPTIONS} pull oci-archive:oci-alp.tar + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} pull oci-archive:oci-alp.tar echo "$output" [ "$status" -eq 0 ] - run bash -c ${PODMAN_BINARY} ${PODMAN_OPTIONS} rmi alpine + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} rmi alpine echo "$output" [ "$status" -eq 0 ] rm -f oci-alp.tar } @test "podman pull from local directory" { - run bash -c ${PODMAN_BINARY} ${PODMAN_OPTIONS} pull alpine + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} pull alpine echo "$output" [ "$status" -eq 0 ] run mkdir test_pull_dir echo "$output" [ "$status" -eq 0 ] - run bash -c ${PODMAN_BINARY} ${PODMAN_OPTIONS} push alpine dir:test_pull_dir + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} push alpine dir:test_pull_dir echo "$output" [ "$status" -eq 0 ] - run bash -c ${PODMAN_BINARY} ${PODMAN_OPTIONS} rmi alpine + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} rmi alpine echo "$output" [ "$status" -eq 0 ] - run bash -c ${PODMAN_BINARY} ${PODMAN_OPTIONS} pull dir:test_pull_dir + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} pull dir:test_pull_dir echo "$output" [ "$status" -eq 0 ] - run bash -c ${PODMAN_BINARY} ${PODMAN_OPTIONS} rmi test_pull_dir + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} rmi test_pull_dir echo "$output" [ "$status" -eq 0 ] rm -rf test_pull_dir diff --git a/test/podman_push.bats b/test/podman_push.bats index 82798b3fc..8308f4e83 100644 --- a/test/podman_push.bats +++ b/test/podman_push.bats @@ -36,7 +36,7 @@ function setup() { echo "$output" [ "$status" -eq 0 ] rm -rf /tmp/busybox - run bash -c ${PODMAN_BINARY} $PODMAN_OPTIONS rmi $ALPINE + run ${PODMAN_BINARY} $PODMAN_OPTIONS rmi $ALPINE echo "$output" [ "$status" -eq 0 ] } @@ -47,7 +47,7 @@ function setup() { echo "--->" [ "$status" -eq 0 ] rm /tmp/busybox-archive - run bash -c ${PODMAN_BINARY} $PODMAN_OPTIONS rmi $ALPINE + run ${PODMAN_BINARY} $PODMAN_OPTIONS rmi $ALPINE echo "$output" [ "$status" -eq 0 ] } @@ -57,18 +57,18 @@ function setup() { echo "$output" [ "$status" -eq 0 ] rm -f /tmp/oci-busybox.tar - run bash -c ${PODMAN_BINARY} $PODMAN_OPTIONS rmi $ALPINE + run ${PODMAN_BINARY} $PODMAN_OPTIONS rmi $ALPINE echo "$output" [ "$status" -eq 0 ] } @test "podman push without signatures" { mkdir /tmp/busybox - run bash -c ${PODMAN_BINARY} $PODMAN_OPTIONS push --remove-signatures $ALPINE dir:/tmp/busybox + run ${PODMAN_BINARY} $PODMAN_OPTIONS push --remove-signatures $ALPINE dir:/tmp/busybox echo "$output" [ "$status" -eq 0 ] rm -rf /tmp/busybox - run bash -c ${PODMAN_BINARY} $PODMAN_OPTIONS rmi $ALPINE + run ${PODMAN_BINARY} $PODMAN_OPTIONS rmi $ALPINE echo "$output" [ "$status" -eq 0 ] } @@ -86,13 +86,13 @@ function setup() { } @test "push with manifest type conversion" { - run bash -c "${PODMAN_BINARY} $PODMAN_OPTIONS push --format oci "${BB}" dir:my-dir" + run ${PODMAN_BINARY} $PODMAN_OPTIONS push --format oci "${BB}" dir:my-dir echo "$output" [ "$status" -eq 0 ] - run bash -c "grep "application/vnd.oci.image.config.v1+json" my-dir/manifest.json" + run grep "application/vnd.oci.image.config.v1+json" my-dir/manifest.json echo "$output" [ "$status" -eq 0 ] - run bash -c "${PODMAN_BINARY} $PODMAN_OPTIONS push --compress --format v2s2 "${BB}" dir:my-dir" + run ${PODMAN_BINARY} $PODMAN_OPTIONS push --compress --format v2s2 "${BB}" dir:my-dir echo "$output" [ "$status" -eq 0 ] run bash -c "grep "application/vnd.docker.distribution.manifest.v2+json" my-dir/manifest.json" diff --git a/test/podman_rm.bats b/test/podman_rm.bats index f6430711f..8382bb3fe 100644 --- a/test/podman_rm.bats +++ b/test/podman_rm.bats @@ -15,7 +15,7 @@ function teardown() { echo "$output" [ "$status" -eq 0 ] ctr_id="$output" - run bash -c ${PODMAN_BINARY} $PODMAN_OPTIONS rm "$ctr_id" + run ${PODMAN_BINARY} $PODMAN_OPTIONS rm "$ctr_id" echo "$output" [ "$status" -eq 0 ] } @@ -35,7 +35,7 @@ function teardown() { echo "$output" [ "$status" -eq 0 ] ctr_id="$output" - run bash -c ${PODMAN_BINARY} $PODMAN_OPTIONS rm -f "$ctr_id" + run ${PODMAN_BINARY} $PODMAN_OPTIONS rm -f "$ctr_id" echo "$output" [ "$status" -eq 0 ] } @@ -45,7 +45,7 @@ function teardown() { echo "$output" [ "$status" -eq 0 ] ctr_id="$output" - run bash -c ${PODMAN_BINARY} $PODMAN_OPTIONS rm -f "$ctr_id" + run ${PODMAN_BINARY} $PODMAN_OPTIONS rm -f "$ctr_id" echo "$output" [ "$status" -eq 0 ] } diff --git a/test/podman_run.bats b/test/podman_run.bats index 7066000d0..9fa048439 100644 --- a/test/podman_run.bats +++ b/test/podman_run.bats @@ -11,19 +11,19 @@ function setup() { } @test "run a container based on local image" { - run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} run $BB ls" + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} run $BB ls echo "$output" [ "$status" -eq 0 ] } @test "run a container based on local image with short options" { - run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} run -dt $BB ls" + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} run -dt $BB ls echo "$output" [ "$status" -eq 0 ] } @test "run a container based on a remote image" { - run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} run ${BB_GLIBC} ls" + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} run ${BB_GLIBC} ls echo "$output" [ "$status" -eq 0 ] } @@ -33,11 +33,11 @@ function setup() { skip "SELinux not enabled" fi - run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} run ${ALPINE} cat /proc/self/attr/current" + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} run ${ALPINE} cat /proc/self/attr/current echo "$output" firstLabel=$output - run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} run ${ALPINE} cat /proc/self/attr/current" + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} run ${ALPINE} cat /proc/self/attr/current echo "$output" [ "$output" != "${firstLabel}" ] } @@ -52,19 +52,19 @@ function setup() { } @test "run capabilities test" { - run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} run --cap-add all ${ALPINE} cat /proc/self/status" + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} run --cap-add all ${ALPINE} cat /proc/self/status echo "$output" [ "$status" -eq 0 ] - run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} run --cap-add sys_admin ${ALPINE} cat /proc/self/status" + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} run --cap-add sys_admin ${ALPINE} cat /proc/self/status echo "$output" [ "$status" -eq 0 ] - run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} run --cap-drop all ${ALPINE} cat /proc/self/status" + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} run --cap-drop all ${ALPINE} cat /proc/self/status echo "$output" [ "$status" -eq 0 ] - run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} run --cap-drop setuid ${ALPINE} cat /proc/self/status" + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} run --cap-drop setuid ${ALPINE} cat /proc/self/status echo "$output" [ "$status" -eq 0 ] @@ -86,7 +86,7 @@ function setup() { [ "$status" -eq 0 ] [ "$output" = "BAR" ] - run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} run --env FOO ${ALPINE} printenv" + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} run --env FOO ${ALPINE} printenv echo "$output" [ "$status" -ne 0 ] @@ -101,7 +101,7 @@ function setup() { IMAGE="docker.io/library/fedora:latest" @test "run limits test" { - run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} run --ulimit rtprio=99 --cap-add=sys_nice ${IMAGE} cat /proc/self/sched" + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} run --ulimit rtprio=99 --cap-add=sys_nice ${IMAGE} cat /proc/self/sched echo $output [ "$status" -eq 0 ] @@ -110,7 +110,12 @@ IMAGE="docker.io/library/fedora:latest" [ "$status" -eq 0 ] [ "$output" = 2048 ] - run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} run --oom-kill-disable=true ${IMAGE} echo memory-hog" + run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} run --ulimit nofile=1024:1028 ${IMAGE} ulimit -n | tr -d '\r'" + echo $output + [ "$status" -eq 0 ] + [ "$output" = 1024 ] + + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} run --oom-kill-disable=true ${IMAGE} echo memory-hog echo $output [ "$status" -eq 0 ] @@ -134,10 +139,24 @@ IMAGE="docker.io/library/fedora:latest" } @test "podman run with cidfile" { - run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} run --cidfile /tmp/cidfile $BB ls" + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} run --cidfile /tmp/cidfile $BB ls echo "$output" [ "$status" -eq 0 ] run rm /tmp/cidfile echo "$output" [ "$status" -eq 0 ] } + +@test "podman run sysctl test" { + run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} run --rm --sysctl net.core.somaxconn=65535 ${ALPINE} sysctl net.core.somaxconn | tr -d '\r'" + echo "$output" + [ "$status" -eq 0 ] + [ "$output" = "net.core.somaxconn = 65535" ] +} + +@test "podman run blkio-weight test" { + run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} run --rm --blkio-weight=15 ${ALPINE} cat /sys/fs/cgroup/blkio/blkio.weight | tr -d '\r'" + echo "$output" + [ "$status" -eq 0 ] + [ "$output" = 15 ] +} diff --git a/test/podman_run_cpu.bats b/test/podman_run_cpu.bats new file mode 100644 index 000000000..ebd411b41 --- /dev/null +++ b/test/podman_run_cpu.bats @@ -0,0 +1,71 @@ +#!/usr/bin/env bats + +load helpers + +function teardown() { + cleanup_test +} + +function setup() { + copy_images +} + +@test "run cpu-period test" { + run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} run --rm --cpu-period=5000 ${ALPINE} cat /sys/fs/cgroup/cpu/cpu.cfs_period_us | tr -d '\r'" + echo $output + [ "$status" -eq 0 ] + [ "$output" = 5000 ] +} + +@test "run cpu-quota test" { + run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} run --rm --cpu-quota=5000 ${ALPINE} cat /sys/fs/cgroup/cpu/cpu.cfs_quota_us | tr -d '\r'" + echo "$output" + [ "$status" -eq 0 ] + [ "$output" = 5000 ] +} + +@test "run cpus test" { + run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} run --rm --cpus=0.5 ${ALPINE} cat /sys/fs/cgroup/cpu/cpu.cfs_period_us | tr -d '\r'" + echo "$output" + [ "$status" -eq 0 ] + [ "$output" = 100000 ] + run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} run --rm --cpus=0.5 ${ALPINE} cat /sys/fs/cgroup/cpu/cpu.cfs_quota_us | tr -d '\r'" + echo "$output" + [ "$status" -eq 0 ] + [ "$output" = 50000 ] +} + +@test "run cpu-shares test" { + run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} run --rm --cpu-shares=2 ${ALPINE} cat /sys/fs/cgroup/cpu/cpu.shares | tr -d '\r'" + echo "$output" + [ "$status" -eq 0 ] + [ "$output" = 2 ] +} + +@test "run cpuset-cpus test" { + run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} run --rm --cpuset-cpus=0 ${ALPINE} cat /sys/fs/cgroup/cpuset/cpuset.cpus | tr -d '\r'" + echo "$output" + [ "$status" -eq 0 ] + [ "$output" = 0 ] +} + +@test "run cpuset-mems test" { + run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} run --rm --cpuset-mems=0 ${ALPINE} cat /sys/fs/cgroup/cpuset/cpuset.mems | tr -d '\r'" + echo "$output" + [ "$status" -eq 0 ] + [ "$output" = 0 ] +} + +@test "run failure if cpus and cpu-period set together test" { + # skip, error code incorrect with bash -c and will fail centos test without bash -c + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} run --rm --cpu-period=5000 --cpus=0.5 ${ALPINE} /bin/bash + echo "$output" + [ "$status" -ne 0 ] +} + +@test "run failure if cpus and cpu-quota set together test" { + # skip, error code incorrect with bash -c and will fail centos test without bash -c + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} run --rm --cpu-quota=5000 --cpus=0.5 ${ALPINE} /bin/bash + echo "$output" + [ "$status" -ne 0 ] +} diff --git a/test/podman_run_dns.bats b/test/podman_run_dns.bats new file mode 100644 index 000000000..d37737093 --- /dev/null +++ b/test/podman_run_dns.bats @@ -0,0 +1,56 @@ +#!/usr/bin/env bats + +load helpers + +function teardown() { + cleanup_test +} + +function setup() { + copy_images +} + +@test "test addition of a search domain" { + run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} run --rm --dns-search=foobar.com ${ALPINE} cat /etc/resolv.conf | grep foo" + echo "$output" + [ "$status" -eq 0 ] +} + +@test "test addition of a bad dns server" { + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} create --dns="foo" ${ALPINE} ls + echo "$output" + [ "$status" -ne 0 ] +} + +@test "test addition of a dns server" { + run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} run --rm --dns='1.2.3.4' ${ALPINE} cat /etc/resolv.conf | grep '1.2.3.4'" + echo "$output" + [ "$status" -eq 0 ] +} + +@test "test addition of a dns option" { + run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} run --rm --dns-opt='debug' ${ALPINE} cat /etc/resolv.conf | grep 'options debug'" + echo "$output" + [ "$status" -eq 0 ] +} + +@test "test addition of a bad add-host" { + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} create --add-host="foo:1.2" ${ALPINE} ls + echo "$output" + [ "$status" -ne 0 ] +} + +@test "test addition of add-host" { + run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} run --rm --add-host='foobar:1.1.1.1' ${ALPINE} cat /etc/hosts | grep 'foobar'" + echo "$output" + [ "$status" -eq 0 ] +} + +@test "test addition of hostname" { + run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} run --rm --hostname='foobar' ${ALPINE} cat /etc/hostname | grep foobar" + echo "$output" + [ "$status" -eq 0 ] + run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} run --rm --hostname='foobar' ${ALPINE} hostname | grep foobar" + echo "$output" + [ "$status" -eq 0 ] +} diff --git a/test/podman_run_memory.bats b/test/podman_run_memory.bats new file mode 100644 index 000000000..2eaebe104 --- /dev/null +++ b/test/podman_run_memory.bats @@ -0,0 +1,39 @@ +#!/usr/bin/env bats + +load helpers + +function teardown() { + cleanup_test +} + +function setup() { + copy_images +} + +@test "run memory test" { + run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} run --rm --memory=40m ${ALPINE} cat /sys/fs/cgroup/memory/memory.limit_in_bytes | tr -d '\r'" + echo $output + [ "$status" -eq 0 ] + [ "$output" = 41943040 ] +} + +@test "run memory-reservation test" { + run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} run --rm --memory-reservation=40m ${ALPINE} cat /sys/fs/cgroup/memory/memory.soft_limit_in_bytes | tr -d '\r'" + echo "$output" + [ "$status" -eq 0 ] + [ "$output" = 41943040 ] +} + +@test "run memory-swappiness test" { + run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} run --rm --memory-swappiness=15 ${ALPINE} cat /sys/fs/cgroup/memory/memory.swappiness | tr -d '\r'" + echo "$output" + [ "$status" -eq 0 ] + [ "$output" = 15 ] +} + +@test "run kernel-memory test" { + run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} run --rm --kernel-memory=40m ${ALPINE} cat /sys/fs/cgroup/memory/memory.kmem.limit_in_bytes | tr -d '\r'" + echo "$output" + [ "$status" -eq 0 ] + [ "$output" = 41943040 ] +} diff --git a/test/podman_save.bats b/test/podman_save.bats index 27e627b8f..9c6fa8b86 100644 --- a/test/podman_save.bats +++ b/test/podman_save.bats @@ -11,14 +11,14 @@ function setup() { } @test "podman save output flag" { - run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} save -o alpine.tar $ALPINE" + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} save -o alpine.tar $ALPINE echo "$output" [ "$status" -eq 0 ] rm -f alpine.tar } @test "podman save oci flag" { - run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} save -o alpine.tar --format oci-archive $ALPINE" + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} save -o alpine.tar --format oci-archive $ALPINE echo "$output" [ "$status" -eq 0 ] rm -f alpine.tar @@ -31,27 +31,27 @@ function setup() { } @test "podman save quiet flag" { - run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} save -q -o alpine.tar $ALPINE" + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} save -q -o alpine.tar $ALPINE echo "$output" [ "$status" -eq 0 ] rm -f alpine.tar } @test "podman save non-existent image" { - run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} save -o alpine.tar FOOBAR" + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} save -o alpine.tar FOOBAR echo "$output" [ "$status" -ne 0 ] } @test "podman save to directory wit oci format" { - run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} save --format oci-dir -o alp-dir $ALPINE" + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} save --format oci-dir -o alp-dir $ALPINE echo "$output" [ "$status" -eq 0 ] rm -rf alp-dir } @test "podman save to directory wit v2s2 (docker) format" { - run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} save --format docker-dir -o alp-dir $ALPINE" + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} save --format docker-dir -o alp-dir $ALPINE echo "$output" [ "$status" -eq 0 ] rm -rf alp-dir diff --git a/test/podman_stop.bats b/test/podman_stop.bats index 839301435..7675ee9a9 100644 --- a/test/podman_stop.bats +++ b/test/podman_stop.bats @@ -11,46 +11,46 @@ function setup() { } @test "stop a bogus container" { - run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} stop foobar" + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} stop foobar echo "$output" [ "$status" -eq 125 ] } @test "stop a running container by id" { - run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} run -d ${ALPINE} sleep 9999" + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} run -d ${ALPINE} sleep 9999 [ "$status" -eq 0 ] ctr_id="$output" - run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} ps" + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} ps [ "$status" -eq 0 ] - run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} stop $ctr_id" + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} stop $ctr_id [ "$status" -eq 0 ] - run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} ps" + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} ps [ "$status" -eq 0 ] } @test "stop a running container by name" { - run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} run --name test1 -d ${ALPINE} sleep 9999" + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} run --name test1 -d ${ALPINE} sleep 9999 [ "$status" -eq 0 ] - run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} ps" + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} ps [ "$status" -eq 0 ] - run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} stop test1" + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} stop test1 [ "$status" -eq 0 ] - run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} ps" + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} ps [ "$status" -eq 0 ] } @test "stop all containers" { - run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} run --name test1 -d ${ALPINE} sleep 9999" - run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} run --name test2 -d ${ALPINE} sleep 9999" - run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} run --name test3 -d ${ALPINE} sleep 9999" - run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} stop -a -t 1" + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} run --name test1 -d ${ALPINE} sleep 9999 + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} run --name test2 -d ${ALPINE} sleep 9999 + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} run --name test3 -d ${ALPINE} sleep 9999 + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} stop -a -t 1 echo "$output" [ "$status" -eq 0 ] } @test "stop a container with latest" { ${PODMAN_BINARY} ${PODMAN_OPTIONS} run -d ${ALPINE} sleep 9999 - run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} stop -t 1 -l" + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} stop -t 1 -l echo "$output" [ "$status" -eq 0 ] } diff --git a/test/podman_tag.bats b/test/podman_tag.bats index 024cf6295..749c3ae2c 100644 --- a/test/podman_tag.bats +++ b/test/podman_tag.bats @@ -11,33 +11,33 @@ function setup() { } @test "podman tag with shortname:latest" { - run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} tag ${ALPINE} foobar:latest" + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} tag ${ALPINE} foobar:latest [ "$status" -eq 0 ] - run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} inspect foobar:latest" + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} inspect foobar:latest echo "$output" [ "$status" -eq 0 ] - run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} rmi --force foobar:latest" + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} rmi --force foobar:latest [ "$status" -eq 0 ] } @test "podman tag with shortname" { - run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} tag ${ALPINE} foobar" + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} tag ${ALPINE} foobar echo "$output" [ "$status" -eq 0 ] - run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} inspect foobar:latest" + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} inspect foobar:latest echo "$output" [ "$status" -eq 0 ] - run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} rmi --force foobar:latest" + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} rmi --force foobar:latest [ "$status" -eq 0 ] } @test "podman tag with shortname:tag" { - run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} tag ${ALPINE} foobar:v" + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} tag ${ALPINE} foobar:v echo "$output" [ "$status" -eq 0 ] - run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} inspect foobar:v" + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} inspect foobar:v echo "$output" [ "$status" -eq 0 ] - run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} rmi --force foobar:v" + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} rmi --force foobar:v [ "$status" -eq 0 ] } diff --git a/test/podman_top.bats b/test/podman_top.bats index a8b92cd44..cfa037aa6 100644 --- a/test/podman_top.bats +++ b/test/podman_top.bats @@ -26,7 +26,7 @@ function setup() { run ${PODMAN_BINARY} ${PODMAN_OPTIONS} create -d ${ALPINE} sleep 60 [ "$status" -eq 0 ] ctr_id="$output" - run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} top $ctr_id" + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} top $ctr_id echo "$output" [ "$status" -eq 125 ] } @@ -36,7 +36,7 @@ function setup() { [ "$status" -eq 0 ] ctr_id="$output" echo $ctr_id - run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} top $ctr_id" + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} top $ctr_id echo "$output" [ "$status" -eq 0 ] } @@ -45,7 +45,7 @@ function setup() { run ${PODMAN_BINARY} ${PODMAN_OPTIONS} run -d ${ALPINE} sleep 60 [ "$status" -eq 0 ] ctr_id="$output" - run bash -c "${PODMAN_BINARY} ${PODMAN_OPTIONS} top $ctr_id -o fuser,f,comm,label" + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} top $ctr_id -o fuser,f,comm,label echo "$output" [ "$status" -eq 0 ] } diff --git a/test/podman_version.bats b/test/podman_version.bats index 0f959277b..a44da5943 100644 --- a/test/podman_version.bats +++ b/test/podman_version.bats @@ -7,7 +7,7 @@ function teardown() { } @test "podman version test" { - run bash -c "${PODMAN_BINARY} version" + run ${PODMAN_BINARY} version echo "$output" [ "$status" -eq 0 ] } diff --git a/vendor/github.com/docker/docker/profiles/seccomp/generate.go b/vendor/github.com/docker/docker/profiles/seccomp/generate.go new file mode 100644 index 000000000..32f22bb37 --- /dev/null +++ b/vendor/github.com/docker/docker/profiles/seccomp/generate.go @@ -0,0 +1,32 @@ +// +build ignore + +package main + +import ( + "encoding/json" + "io/ioutil" + "os" + "path/filepath" + + "github.com/docker/docker/profiles/seccomp" +) + +// saves the default seccomp profile as a json file so people can use it as a +// base for their own custom profiles +func main() { + wd, err := os.Getwd() + if err != nil { + panic(err) + } + f := filepath.Join(wd, "default.json") + + // write the default profile to the file + b, err := json.MarshalIndent(seccomp.DefaultProfile(), "", "\t") + if err != nil { + panic(err) + } + + if err := ioutil.WriteFile(f, b, 0644); err != nil { + panic(err) + } +} diff --git a/vendor/github.com/docker/docker/profiles/seccomp/seccomp.go b/vendor/github.com/docker/docker/profiles/seccomp/seccomp.go new file mode 100644 index 000000000..07d522aad --- /dev/null +++ b/vendor/github.com/docker/docker/profiles/seccomp/seccomp.go @@ -0,0 +1,160 @@ +// +build linux + +package seccomp + +import ( + "encoding/json" + "errors" + "fmt" + + "github.com/docker/docker/api/types" + "github.com/opencontainers/runtime-spec/specs-go" + libseccomp "github.com/seccomp/libseccomp-golang" +) + +//go:generate go run -tags 'seccomp' generate.go + +// GetDefaultProfile returns the default seccomp profile. +func GetDefaultProfile(rs *specs.Spec) (*specs.LinuxSeccomp, error) { + return setupSeccomp(DefaultProfile(), rs) +} + +// LoadProfile takes a json string and decodes the seccomp profile. +func LoadProfile(body string, rs *specs.Spec) (*specs.LinuxSeccomp, error) { + var config types.Seccomp + if err := json.Unmarshal([]byte(body), &config); err != nil { + return nil, fmt.Errorf("Decoding seccomp profile failed: %v", err) + } + return setupSeccomp(&config, rs) +} + +var nativeToSeccomp = map[string]types.Arch{ + "amd64": types.ArchX86_64, + "arm64": types.ArchAARCH64, + "mips64": types.ArchMIPS64, + "mips64n32": types.ArchMIPS64N32, + "mipsel64": types.ArchMIPSEL64, + "mipsel64n32": types.ArchMIPSEL64N32, + "s390x": types.ArchS390X, +} + +// inSlice tests whether a string is contained in a slice of strings or not. +// Comparison is case sensitive +func inSlice(slice []string, s string) bool { + for _, ss := range slice { + if s == ss { + return true + } + } + return false +} + +func setupSeccomp(config *types.Seccomp, rs *specs.Spec) (*specs.LinuxSeccomp, error) { + if config == nil { + return nil, nil + } + + // No default action specified, no syscalls listed, assume seccomp disabled + if config.DefaultAction == "" && len(config.Syscalls) == 0 { + return nil, nil + } + + newConfig := &specs.LinuxSeccomp{} + + var arch string + var native, err = libseccomp.GetNativeArch() + if err == nil { + arch = native.String() + } + + if len(config.Architectures) != 0 && len(config.ArchMap) != 0 { + return nil, errors.New("'architectures' and 'archMap' were specified in the seccomp profile, use either 'architectures' or 'archMap'") + } + + // if config.Architectures == 0 then libseccomp will figure out the architecture to use + if len(config.Architectures) != 0 { + for _, a := range config.Architectures { + newConfig.Architectures = append(newConfig.Architectures, specs.Arch(a)) + } + } + + if len(config.ArchMap) != 0 { + for _, a := range config.ArchMap { + seccompArch, ok := nativeToSeccomp[arch] + if ok { + if a.Arch == seccompArch { + newConfig.Architectures = append(newConfig.Architectures, specs.Arch(a.Arch)) + for _, sa := range a.SubArches { + newConfig.Architectures = append(newConfig.Architectures, specs.Arch(sa)) + } + break + } + } + } + } + + newConfig.DefaultAction = specs.LinuxSeccompAction(config.DefaultAction) + +Loop: + // Loop through all syscall blocks and convert them to libcontainer format after filtering them + for _, call := range config.Syscalls { + if len(call.Excludes.Arches) > 0 { + if inSlice(call.Excludes.Arches, arch) { + continue Loop + } + } + if len(call.Excludes.Caps) > 0 { + for _, c := range call.Excludes.Caps { + if inSlice(rs.Process.Capabilities.Effective, c) { + continue Loop + } + } + } + if len(call.Includes.Arches) > 0 { + if !inSlice(call.Includes.Arches, arch) { + continue Loop + } + } + if len(call.Includes.Caps) > 0 { + for _, c := range call.Includes.Caps { + if !inSlice(rs.Process.Capabilities.Effective, c) { + continue Loop + } + } + } + + if call.Name != "" && len(call.Names) != 0 { + return nil, errors.New("'name' and 'names' were specified in the seccomp profile, use either 'name' or 'names'") + } + + if call.Name != "" { + newConfig.Syscalls = append(newConfig.Syscalls, createSpecsSyscall(call.Name, call.Action, call.Args)) + } + + for _, n := range call.Names { + newConfig.Syscalls = append(newConfig.Syscalls, createSpecsSyscall(n, call.Action, call.Args)) + } + } + + return newConfig, nil +} + +func createSpecsSyscall(name string, action types.Action, args []*types.Arg) specs.LinuxSyscall { + newCall := specs.LinuxSyscall{ + Names: []string{name}, + Action: specs.LinuxSeccompAction(action), + } + + // Loop through all the arguments of the syscall and convert them + for _, arg := range args { + newArg := specs.LinuxSeccompArg{ + Index: arg.Index, + Value: arg.Value, + ValueTwo: arg.ValueTwo, + Op: specs.LinuxSeccompOperator(arg.Op), + } + + newCall.Args = append(newCall.Args, newArg) + } + return newCall +} diff --git a/vendor/github.com/docker/docker/profiles/seccomp/seccomp_default.go b/vendor/github.com/docker/docker/profiles/seccomp/seccomp_default.go new file mode 100644 index 000000000..1b5179c70 --- /dev/null +++ b/vendor/github.com/docker/docker/profiles/seccomp/seccomp_default.go @@ -0,0 +1,639 @@ +// +build linux,seccomp + +package seccomp + +import ( + "github.com/docker/docker/api/types" + "golang.org/x/sys/unix" +) + +func arches() []types.Architecture { + return []types.Architecture{ + { + Arch: types.ArchX86_64, + SubArches: []types.Arch{types.ArchX86, types.ArchX32}, + }, + { + Arch: types.ArchAARCH64, + SubArches: []types.Arch{types.ArchARM}, + }, + { + Arch: types.ArchMIPS64, + SubArches: []types.Arch{types.ArchMIPS, types.ArchMIPS64N32}, + }, + { + Arch: types.ArchMIPS64N32, + SubArches: []types.Arch{types.ArchMIPS, types.ArchMIPS64}, + }, + { + Arch: types.ArchMIPSEL64, + SubArches: []types.Arch{types.ArchMIPSEL, types.ArchMIPSEL64N32}, + }, + { + Arch: types.ArchMIPSEL64N32, + SubArches: []types.Arch{types.ArchMIPSEL, types.ArchMIPSEL64}, + }, + { + Arch: types.ArchS390X, + SubArches: []types.Arch{types.ArchS390}, + }, + } +} + +// DefaultProfile defines the whitelist for the default seccomp profile. +func DefaultProfile() *types.Seccomp { + syscalls := []*types.Syscall{ + { + Names: []string{ + "accept", + "accept4", + "access", + "adjtimex", + "alarm", + "bind", + "brk", + "capget", + "capset", + "chdir", + "chmod", + "chown", + "chown32", + "clock_getres", + "clock_gettime", + "clock_nanosleep", + "close", + "connect", + "copy_file_range", + "creat", + "dup", + "dup2", + "dup3", + "epoll_create", + "epoll_create1", + "epoll_ctl", + "epoll_ctl_old", + "epoll_pwait", + "epoll_wait", + "epoll_wait_old", + "eventfd", + "eventfd2", + "execve", + "execveat", + "exit", + "exit_group", + "faccessat", + "fadvise64", + "fadvise64_64", + "fallocate", + "fanotify_mark", + "fchdir", + "fchmod", + "fchmodat", + "fchown", + "fchown32", + "fchownat", + "fcntl", + "fcntl64", + "fdatasync", + "fgetxattr", + "flistxattr", + "flock", + "fork", + "fremovexattr", + "fsetxattr", + "fstat", + "fstat64", + "fstatat64", + "fstatfs", + "fstatfs64", + "fsync", + "ftruncate", + "ftruncate64", + "futex", + "futimesat", + "getcpu", + "getcwd", + "getdents", + "getdents64", + "getegid", + "getegid32", + "geteuid", + "geteuid32", + "getgid", + "getgid32", + "getgroups", + "getgroups32", + "getitimer", + "getpeername", + "getpgid", + "getpgrp", + "getpid", + "getppid", + "getpriority", + "getrandom", + "getresgid", + "getresgid32", + "getresuid", + "getresuid32", + "getrlimit", + "get_robust_list", + "getrusage", + "getsid", + "getsockname", + "getsockopt", + "get_thread_area", + "gettid", + "gettimeofday", + "getuid", + "getuid32", + "getxattr", + "inotify_add_watch", + "inotify_init", + "inotify_init1", + "inotify_rm_watch", + "io_cancel", + "ioctl", + "io_destroy", + "io_getevents", + "ioprio_get", + "ioprio_set", + "io_setup", + "io_submit", + "ipc", + "kill", + "lchown", + "lchown32", + "lgetxattr", + "link", + "linkat", + "listen", + "listxattr", + "llistxattr", + "_llseek", + "lremovexattr", + "lseek", + "lsetxattr", + "lstat", + "lstat64", + "madvise", + "memfd_create", + "mincore", + "mkdir", + "mkdirat", + "mknod", + "mknodat", + "mlock", + "mlock2", + "mlockall", + "mmap", + "mmap2", + "mprotect", + "mq_getsetattr", + "mq_notify", + "mq_open", + "mq_timedreceive", + "mq_timedsend", + "mq_unlink", + "mremap", + "msgctl", + "msgget", + "msgrcv", + "msgsnd", + "msync", + "munlock", + "munlockall", + "munmap", + "nanosleep", + "newfstatat", + "_newselect", + "open", + "openat", + "pause", + "pipe", + "pipe2", + "poll", + "ppoll", + "prctl", + "pread64", + "preadv", + "preadv2", + "prlimit64", + "pselect6", + "pwrite64", + "pwritev", + "pwritev2", + "read", + "readahead", + "readlink", + "readlinkat", + "readv", + "recv", + "recvfrom", + "recvmmsg", + "recvmsg", + "remap_file_pages", + "removexattr", + "rename", + "renameat", + "renameat2", + "restart_syscall", + "rmdir", + "rt_sigaction", + "rt_sigpending", + "rt_sigprocmask", + "rt_sigqueueinfo", + "rt_sigreturn", + "rt_sigsuspend", + "rt_sigtimedwait", + "rt_tgsigqueueinfo", + "sched_getaffinity", + "sched_getattr", + "sched_getparam", + "sched_get_priority_max", + "sched_get_priority_min", + "sched_getscheduler", + "sched_rr_get_interval", + "sched_setaffinity", + "sched_setattr", + "sched_setparam", + "sched_setscheduler", + "sched_yield", + "seccomp", + "select", + "semctl", + "semget", + "semop", + "semtimedop", + "send", + "sendfile", + "sendfile64", + "sendmmsg", + "sendmsg", + "sendto", + "setfsgid", + "setfsgid32", + "setfsuid", + "setfsuid32", + "setgid", + "setgid32", + "setgroups", + "setgroups32", + "setitimer", + "setpgid", + "setpriority", + "setregid", + "setregid32", + "setresgid", + "setresgid32", + "setresuid", + "setresuid32", + "setreuid", + "setreuid32", + "setrlimit", + "set_robust_list", + "setsid", + "setsockopt", + "set_thread_area", + "set_tid_address", + "setuid", + "setuid32", + "setxattr", + "shmat", + "shmctl", + "shmdt", + "shmget", + "shutdown", + "sigaltstack", + "signalfd", + "signalfd4", + "sigreturn", + "socket", + "socketcall", + "socketpair", + "splice", + "stat", + "stat64", + "statfs", + "statfs64", + "symlink", + "symlinkat", + "sync", + "sync_file_range", + "syncfs", + "sysinfo", + "syslog", + "tee", + "tgkill", + "time", + "timer_create", + "timer_delete", + "timerfd_create", + "timerfd_gettime", + "timerfd_settime", + "timer_getoverrun", + "timer_gettime", + "timer_settime", + "times", + "tkill", + "truncate", + "truncate64", + "ugetrlimit", + "umask", + "uname", + "unlink", + "unlinkat", + "utime", + "utimensat", + "utimes", + "vfork", + "vmsplice", + "wait4", + "waitid", + "waitpid", + "write", + "writev", + }, + Action: types.ActAllow, + Args: []*types.Arg{}, + }, + { + Names: []string{"personality"}, + Action: types.ActAllow, + Args: []*types.Arg{ + { + Index: 0, + Value: 0x0, + Op: types.OpEqualTo, + }, + }, + }, + { + Names: []string{"personality"}, + Action: types.ActAllow, + Args: []*types.Arg{ + { + Index: 0, + Value: 0x0008, + Op: types.OpEqualTo, + }, + }, + }, + { + Names: []string{"personality"}, + Action: types.ActAllow, + Args: []*types.Arg{ + { + Index: 0, + Value: 0x20000, + Op: types.OpEqualTo, + }, + }, + }, + { + Names: []string{"personality"}, + Action: types.ActAllow, + Args: []*types.Arg{ + { + Index: 0, + Value: 0x20008, + Op: types.OpEqualTo, + }, + }, + }, + { + Names: []string{"personality"}, + Action: types.ActAllow, + Args: []*types.Arg{ + { + Index: 0, + Value: 0xffffffff, + Op: types.OpEqualTo, + }, + }, + }, + { + Names: []string{ + "sync_file_range2", + }, + Action: types.ActAllow, + Args: []*types.Arg{}, + Includes: types.Filter{ + Arches: []string{"ppc64le"}, + }, + }, + { + Names: []string{ + "arm_fadvise64_64", + "arm_sync_file_range", + "sync_file_range2", + "breakpoint", + "cacheflush", + "set_tls", + }, + Action: types.ActAllow, + Args: []*types.Arg{}, + Includes: types.Filter{ + Arches: []string{"arm", "arm64"}, + }, + }, + { + Names: []string{ + "arch_prctl", + }, + Action: types.ActAllow, + Args: []*types.Arg{}, + Includes: types.Filter{ + Arches: []string{"amd64", "x32"}, + }, + }, + { + Names: []string{ + "modify_ldt", + }, + Action: types.ActAllow, + Args: []*types.Arg{}, + Includes: types.Filter{ + Arches: []string{"amd64", "x32", "x86"}, + }, + }, + { + Names: []string{ + "s390_pci_mmio_read", + "s390_pci_mmio_write", + "s390_runtime_instr", + }, + Action: types.ActAllow, + Args: []*types.Arg{}, + Includes: types.Filter{ + Arches: []string{"s390", "s390x"}, + }, + }, + { + Names: []string{ + "open_by_handle_at", + }, + Action: types.ActAllow, + Args: []*types.Arg{}, + Includes: types.Filter{ + Caps: []string{"CAP_DAC_READ_SEARCH"}, + }, + }, + { + Names: []string{ + "bpf", + "clone", + "fanotify_init", + "lookup_dcookie", + "mount", + "name_to_handle_at", + "perf_event_open", + "quotactl", + "setdomainname", + "sethostname", + "setns", + "umount", + "umount2", + "unshare", + }, + Action: types.ActAllow, + Args: []*types.Arg{}, + Includes: types.Filter{ + Caps: []string{"CAP_SYS_ADMIN"}, + }, + }, + { + Names: []string{ + "clone", + }, + Action: types.ActAllow, + Args: []*types.Arg{ + { + Index: 0, + Value: unix.CLONE_NEWNS | unix.CLONE_NEWUTS | unix.CLONE_NEWIPC | unix.CLONE_NEWUSER | unix.CLONE_NEWPID | unix.CLONE_NEWNET, + ValueTwo: 0, + Op: types.OpMaskedEqual, + }, + }, + Excludes: types.Filter{ + Caps: []string{"CAP_SYS_ADMIN"}, + Arches: []string{"s390", "s390x"}, + }, + }, + { + Names: []string{ + "clone", + }, + Action: types.ActAllow, + Args: []*types.Arg{ + { + Index: 1, + Value: unix.CLONE_NEWNS | unix.CLONE_NEWUTS | unix.CLONE_NEWIPC | unix.CLONE_NEWUSER | unix.CLONE_NEWPID | unix.CLONE_NEWNET, + ValueTwo: 0, + Op: types.OpMaskedEqual, + }, + }, + Comment: "s390 parameter ordering for clone is different", + Includes: types.Filter{ + Arches: []string{"s390", "s390x"}, + }, + Excludes: types.Filter{ + Caps: []string{"CAP_SYS_ADMIN"}, + }, + }, + { + Names: []string{ + "reboot", + }, + Action: types.ActAllow, + Args: []*types.Arg{}, + Includes: types.Filter{ + Caps: []string{"CAP_SYS_BOOT"}, + }, + }, + { + Names: []string{ + "chroot", + }, + Action: types.ActAllow, + Args: []*types.Arg{}, + Includes: types.Filter{ + Caps: []string{"CAP_SYS_CHROOT"}, + }, + }, + { + Names: []string{ + "delete_module", + "init_module", + "finit_module", + "query_module", + }, + Action: types.ActAllow, + Args: []*types.Arg{}, + Includes: types.Filter{ + Caps: []string{"CAP_SYS_MODULE"}, + }, + }, + { + Names: []string{ + "acct", + }, + Action: types.ActAllow, + Args: []*types.Arg{}, + Includes: types.Filter{ + Caps: []string{"CAP_SYS_PACCT"}, + }, + }, + { + Names: []string{ + "kcmp", + "process_vm_readv", + "process_vm_writev", + "ptrace", + }, + Action: types.ActAllow, + Args: []*types.Arg{}, + Includes: types.Filter{ + Caps: []string{"CAP_SYS_PTRACE"}, + }, + }, + { + Names: []string{ + "iopl", + "ioperm", + }, + Action: types.ActAllow, + Args: []*types.Arg{}, + Includes: types.Filter{ + Caps: []string{"CAP_SYS_RAWIO"}, + }, + }, + { + Names: []string{ + "settimeofday", + "stime", + "clock_settime", + }, + Action: types.ActAllow, + Args: []*types.Arg{}, + Includes: types.Filter{ + Caps: []string{"CAP_SYS_TIME"}, + }, + }, + { + Names: []string{ + "vhangup", + }, + Action: types.ActAllow, + Args: []*types.Arg{}, + Includes: types.Filter{ + Caps: []string{"CAP_SYS_TTY_CONFIG"}, + }, + }, + } + + return &types.Seccomp{ + DefaultAction: types.ActErrno, + ArchMap: arches(), + Syscalls: syscalls, + } +} diff --git a/vendor/github.com/docker/docker/profiles/seccomp/seccomp_unsupported.go b/vendor/github.com/docker/docker/profiles/seccomp/seccomp_unsupported.go new file mode 100644 index 000000000..0130effa6 --- /dev/null +++ b/vendor/github.com/docker/docker/profiles/seccomp/seccomp_unsupported.go @@ -0,0 +1,12 @@ +// +build linux,!seccomp + +package seccomp + +import ( + "github.com/docker/docker/api/types" +) + +// DefaultProfile returns a nil pointer on unsupported systems. +func DefaultProfile() *types.Seccomp { + return nil +} |