diff options
author | Daniel J Walsh <dwalsh@redhat.com> | 2018-12-06 03:20:16 -0500 |
---|---|---|
committer | Daniel J Walsh <dwalsh@redhat.com> | 2018-12-06 03:20:16 -0500 |
commit | 40678b119c0186bf7c56dc09d131e697c55cb9fd (patch) | |
tree | dfb1cc13b0bdc1abad1ada51970bf0ac03d46810 | |
parent | dcb68d1aa24b2dd00d793f5a66f96cc5403b43c3 (diff) | |
parent | 75b19ca8abe1957f3c48035767960a6b20c10519 (diff) | |
download | podman-40678b119c0186bf7c56dc09d131e697c55cb9fd.tar.gz podman-40678b119c0186bf7c56dc09d131e697c55cb9fd.tar.bz2 podman-40678b119c0186bf7c56dc09d131e697c55cb9fd.zip |
Merge branch 'master' of github.com:containers/libpod into vendor
337 files changed, 8934 insertions, 3075 deletions
diff --git a/.cirrus.yml b/.cirrus.yml index ae660394b..625b96fdd 100644 --- a/.cirrus.yml +++ b/.cirrus.yml @@ -18,9 +18,9 @@ gce_instance: env: FEDORA_CNI_COMMIT: "412b6d31280682bb4fab4446f113c22ff1886554" CNI_COMMIT: "7480240de9749f9a0a5c8614b17f1f03e0c06ab9" - CRIO_COMMIT: "662dbb31b5d4f5ed54511a47cde7190c61c28677" - CRIU_COMMIT: "584cbe4643c3fc7dc901ff08bf923ca0fe7326f9" - RUNC_COMMIT: "78ef28e63bec2ee4c139b5e3e0d691eb9bdc748d" + CRIO_COMMIT: "7a283c391abb7bd25086a8ff91dbb36ebdd24466" + CRIU_COMMIT: "c74b83cd49c00589c0c0468ba5fe685b67fdbd0a" + RUNC_COMMIT: "96ec2177ae841256168fcf76954f7177af9446eb" # File to update in home-dir with task-specific env. var values ENVLIB: ".bash_profile" # Overrides default location (/tmp/cirrus) for repo clone @@ -42,11 +42,12 @@ full_vm_testing_task: # 'matrix' combinations. All run in parallel. matrix: # Images are generated separetly, from build_images_task (below) - image_name: "ubuntu-1804-bionic-v20180911-libpod-63a86a18" + image_name: "ubuntu-18-libpod-0c954a67" # TODO: Make these work (also build_images_task below) #image_name: "rhel-server-ec2-7-5-165-1-libpod-fce09afe" #image_name: "centos-7-v20180911-libpod-fce09afe" #image_name: "fedora-cloud-base-28-1-1-7-libpod-fce09afe" + timeout_in: 120m # Every *_script runs in sequence, for each task. The name prefix is for @@ -65,19 +66,49 @@ full_vm_testing_task: success_script: $SCRIPT_BASE/success.sh -# This task build new images for future PR testing, but only after a PR merge. -# These images save needing to install/setup the same environment to test every -# PR. The 'active' image for testing is selected by the 'image_name' items in -# task above. Currently this requires manually updating them, but this could -# be automated (see comment at end). +# Because system tests are stored within the repository, it is sometimes +# necessary to execute them within a PR to validate changes. + +optional_system_testing_task: + + # Only run system tests in PRs (not on merge) if magic string is present + # in the PR description. Post-merge system testing is assumed to happen + # later from OS distribution's build systems. + only_if: >- + $CIRRUS_BRANCH != 'master' && + $CIRRUS_CHANGE_MESSAGE =~ '.*\*\*\*\s*CIRRUS:\s*SYSTEM\s*TEST\s*\*\*\*.*' + + gce_instance: + matrix: + image_name: "ubuntu-1804-bionic-v20180911-libpod-e8d18305" + # TODO: Make these work (also build_images_task below) + #image_name: "rhel-server-ec2-7-5-165-1-libpod-fce09afe" + #image_name: "centos-7-v20180911-libpod-fce09afe" + #image_name: "fedora-cloud-base-28-1-1-7-libpod-fce09afe" + + timeout_in: 60m + + setup_environment_script: $SCRIPT_BASE/setup_environment.sh + system_test_script: $SCRIPT_BASE/system_test.sh + success_script: $SCRIPT_BASE/success.sh + + +# This task builds new cache-images for future PR testing. These images save +# time installing/setting up the environment while an engineer is waiting. +# The 'active' cache-images for full_vm_testing are selected by the +# 'image_name' keys. Updating those items requires manually modification, +# but this could be automated (see comment at end of build_vm_images_task). build_vm_images_task: - # Only produce new images after a PR merge - only_if: $CIRRUS_BRANCH == 'master' + # Only produce new cache-images after a PR merge, and if a magic string + # is present in the most recent commit-message. + only_if: >- + $CIRRUS_BRANCH == 'master' && + $CIRRUS_CHANGE_MESSAGE =~ '.*\*\*\*\s*CIRRUS:\s*REBUILD\s*IMAGES\s*\*\*\*.*' # Require tests to pass first. depends_on: - - test # i.e. 'test_task' + - full_vm_testing # i.e. 'full_vm_testing_task' env: # CSV of packer builder names to enable (see $PACKER_BASE/libpod_images.json) @@ -99,6 +130,8 @@ build_vm_images_task: # Version of packer to use PACKER_VER: "1.3.1" + # VMs created by packer are not cleaned up by cirrus + auto_cancellation: $CI != "true" gce_instance: image_name: "image-builder-image" # Simply CentOS 7 + packer dependencies @@ -139,6 +139,3 @@ if [ $integrationtest -eq 1 ]; then fi make ginkgo GOPATH=/go $INTEGRATION_TEST_ENVS fi - - -exit 0 @@ -122,7 +122,7 @@ packages: - python3-varlink - python3-dateutil - python3-psutil - - https://kojipkgs.fedoraproject.org//packages/runc/1.0.0/55.dev.git578fe65.fc28/x86_64/runc-1.0.0-55.dev.git578fe65.fc28.x86_64.rpm + - https://kojipkgs.fedoraproject.org//packages/runc/1.0.0/54.dev.git00dc700.fc28/x86_64/runc-1.0.0-54.dev.git00dc700.fc28.x86_64.rpm tests: - sed 's/^expand-check.*/expand-check=0/g' -i /etc/selinux/semanage.conf @@ -163,6 +163,7 @@ packages: - python3-dateutil - python3-psutil - container-selinux + - https://kojipkgs.fedoraproject.org//packages/runc/1.0.0/54.dev.git00dc700.fc28/x86_64/runc-1.0.0-54.dev.git00dc700.fc28.x86_64.rpm tests: - sed 's/^expand-check.*/expand-check=0/g' -i /etc/selinux/semanage.conf diff --git a/.papr_prepare.sh b/.papr_prepare.sh index e0657dcd2..5d7d21530 100644 --- a/.papr_prepare.sh +++ b/.papr_prepare.sh @@ -10,6 +10,13 @@ if [[ ${DIST} != "Fedora" ]]; then PYTHON=python fi +# Since CRIU 3.11 has been pushed to Fedora 28 the checkpoint/restore +# test cases are actually run. As CRIU uses iptables to lock and unlock +# the network during checkpoint and restore it needs the following two +# modules loaded. +modprobe ip6table_nat || : +modprobe iptable_nat || : + # Build the test image ${CONTAINER_RUNTIME} build -t ${IMAGE} -f Dockerfile.${DIST} . 2>build.log diff --git a/.tool/lint b/.tool/lint index b7006c8fd..f7bf81c1d 100755 --- a/.tool/lint +++ b/.tool/lint @@ -40,6 +40,8 @@ ${LINTER} \ --exclude='.*_test\.go:.*error return value not checked.*\(errcheck\)$'\ --exclude='duplicate of.*_test.go.*\(dupl\)$'\ --exclude='cmd\/client\/.*\.go.*\(dupl\)$'\ + --exclude='libpod\/.*_easyjson.go:.*'\ + --exclude='.* other occurrence\(s\) of "(container|host|tmpfs|unknown)" found in: .*\(goconst\)$'\ --exclude='vendor\/.*'\ --exclude='podman\/.*'\ --exclude='server\/seccomp\/.*\.go.*$'\ @@ -9,6 +9,14 @@ in the [API.md](https://github.com/containers/libpod/blob/master/API.md) file in [func Commit(name: string, image_name: string, changes: []string, author: string, message: string, pause: bool, manifestType: string) string](#Commit) +[func ContainerCheckpoint(name: string, keep: bool, leaveRunning: bool, tcpEstablished: bool) string](#ContainerCheckpoint) + +[func ContainerExists(name: string) int](#ContainerExists) + +[func ContainerRestore(name: string, keep: bool, tcpEstablished: bool) string](#ContainerRestore) + +[func ContainerRunlabel(runlabel: Runlabel) ](#ContainerRunlabel) + [func CreateContainer(create: Create) string](#CreateContainer) [func CreateImage() NotImplemented](#CreateImage) @@ -43,6 +51,8 @@ in the [API.md](https://github.com/containers/libpod/blob/master/API.md) file in [func HistoryImage(name: string) ImageHistory](#HistoryImage) +[func ImageExists(name: string) int](#ImageExists) + [func ImportImage(source: string, reference: string, message: string, changes: []string) string](#ImportImage) [func InspectContainer(name: string) string](#InspectContainer) @@ -57,6 +67,10 @@ in the [API.md](https://github.com/containers/libpod/blob/master/API.md) file in [func ListContainerChanges(name: string) ContainerChanges](#ListContainerChanges) +[func ListContainerMounts() []string](#ListContainerMounts) + +[func ListContainerPorts(name: string) NotImplemented](#ListContainerPorts) + [func ListContainerProcesses(name: string, opts: []string) []string](#ListContainerProcesses) [func ListContainers() ListContainerData](#ListContainers) @@ -65,6 +79,8 @@ in the [API.md](https://github.com/containers/libpod/blob/master/API.md) file in [func ListPods() ListPodData](#ListPods) +[func MountContainer(name: string) string](#MountContainer) + [func PauseContainer(name: string) string](#PauseContainer) [func PausePod(name: string) string](#PausePod) @@ -103,6 +119,8 @@ in the [API.md](https://github.com/containers/libpod/blob/master/API.md) file in [func TopPod() NotImplemented](#TopPod) +[func UnmountContainer(name: string, force: bool) ](#UnmountContainer) + [func UnpauseContainer(name: string) string](#UnpauseContainer) [func UnpausePod(name: string) string](#UnpausePod) @@ -165,6 +183,8 @@ in the [API.md](https://github.com/containers/libpod/blob/master/API.md) file in [type PodmanInfo](#PodmanInfo) +[type Runlabel](#Runlabel) + [type Sockets](#Sockets) [type StringResponse](#StringResponse) @@ -211,6 +231,31 @@ attributes: _CMD, ENTRYPOINT, ENV, EXPOSE, LABEL, ONBUILD, STOPSIGNAL, USER, VOL container while it is being committed, pass a _true_ bool for the pause argument. If the container cannot be found by the ID or name provided, a (ContainerNotFound)[#ContainerNotFound] error will be returned; otherwise, the resulting image's ID will be returned as a string. +### <a name="ContainerCheckpoint"></a>func ContainerCheckpoint +<div style="background-color: #E8E8E8; padding: 15px; margin: 10px; border-radius: 10px;"> + +method ContainerCheckpoint(name: [string](https://godoc.org/builtin#string), keep: [bool](https://godoc.org/builtin#bool), leaveRunning: [bool](https://godoc.org/builtin#bool), tcpEstablished: [bool](https://godoc.org/builtin#bool)) [string](https://godoc.org/builtin#string)</div> +ContainerCheckPoint performs a checkpopint on a container by its name or full/partial container +ID. On successful checkpoint, the id of the checkpointed container is returned. +### <a name="ContainerExists"></a>func ContainerExists +<div style="background-color: #E8E8E8; padding: 15px; margin: 10px; border-radius: 10px;"> + +method ContainerExists(name: [string](https://godoc.org/builtin#string)) [int](https://godoc.org/builtin#int)</div> +ContainerExists takes a full or partial container ID or name and returns an int as to +whether the container exists in local storage. A result of 0 means the container does +exists; whereas a result of 1 means it could not be found. +### <a name="ContainerRestore"></a>func ContainerRestore +<div style="background-color: #E8E8E8; padding: 15px; margin: 10px; border-radius: 10px;"> + +method ContainerRestore(name: [string](https://godoc.org/builtin#string), keep: [bool](https://godoc.org/builtin#bool), tcpEstablished: [bool](https://godoc.org/builtin#bool)) [string](https://godoc.org/builtin#string)</div> +ContainerRestore restores a container that has been checkpointed. The container to be restored can +be identified by its name or full/partial container ID. A successful restore will result in the return +of the container's ID. +### <a name="ContainerRunlabel"></a>func ContainerRunlabel +<div style="background-color: #E8E8E8; padding: 15px; margin: 10px; border-radius: 10px;"> + +method ContainerRunlabel(runlabel: [Runlabel](#Runlabel)) </div> +ContainerRunlabel runs executes a command as described by a given container image label. ### <a name="CreateContainer"></a>func CreateContainer <div style="background-color: #E8E8E8; padding: 15px; margin: 10px; border-radius: 10px;"> @@ -403,6 +448,13 @@ method HistoryImage(name: [string](https://godoc.org/builtin#string)) [ImageHist HistoryImage takes the name or ID of an image and returns information about its history and layers. The returned history is in the form of an array of ImageHistory structures. If the image cannot be found, an [ImageNotFound](#ImageNotFound) error is returned. +### <a name="ImageExists"></a>func ImageExists +<div style="background-color: #E8E8E8; padding: 15px; margin: 10px; border-radius: 10px;"> + +method ImageExists(name: [string](https://godoc.org/builtin#string)) [int](https://godoc.org/builtin#int)</div> +ImageExists talks a full or partial image ID or name and returns an int as to whether +the image exists in local storage. An int result of 0 means the image does exist in +local storage; whereas 1 indicates the image does not exists in local storage. ### <a name="ImportImage"></a>func ImportImage <div style="background-color: #E8E8E8; padding: 15px; margin: 10px; border-radius: 10px;"> @@ -453,6 +505,17 @@ See also [StopPod](StopPod). method ListContainerChanges(name: [string](https://godoc.org/builtin#string)) [ContainerChanges](#ContainerChanges)</div> ListContainerChanges takes a name or ID of a container and returns changes between the container and its base image. It returns a struct of changed, deleted, and added path names. +### <a name="ListContainerMounts"></a>func ListContainerMounts +<div style="background-color: #E8E8E8; padding: 15px; margin: 10px; border-radius: 10px;"> + +method ListContainerMounts() [[]string](#[]string)</div> +ListContainerMounts gathers all the mounted container mount points and returns them as an array +of strings +### <a name="ListContainerPorts"></a>func ListContainerPorts +<div style="background-color: #E8E8E8; padding: 15px; margin: 10px; border-radius: 10px;"> + +method ListContainerPorts(name: [string](https://godoc.org/builtin#string)) [NotImplemented](#NotImplemented)</div> +This function is not implemented yet. ### <a name="ListContainerProcesses"></a>func ListContainerProcesses <div style="background-color: #E8E8E8; padding: 15px; margin: 10px; border-radius: 10px;"> @@ -491,6 +554,12 @@ an image currently in storage. See also [InspectImage](InspectImage). method ListPods() [ListPodData](#ListPodData)</div> ListPods returns a list of pods in no particular order. They are returned as an array of ListPodData structs. See also [GetPod](#GetPod). +### <a name="MountContainer"></a>func MountContainer +<div style="background-color: #E8E8E8; padding: 15px; margin: 10px; border-radius: 10px;"> + +method MountContainer(name: [string](https://godoc.org/builtin#string)) [string](https://godoc.org/builtin#string)</div> +MountContainer mounts a container by name or full/partial ID. Upon a successful mount, the destination +mount is returned as a string. ### <a name="PauseContainer"></a>func PauseContainer <div style="background-color: #E8E8E8; padding: 15px; margin: 10px; border-radius: 10px;"> @@ -696,6 +765,11 @@ be found, an [ImageNotFound](#ImageNotFound) error will be returned; otherwise, method TopPod() [NotImplemented](#NotImplemented)</div> This method has not been implemented yet. +### <a name="UnmountContainer"></a>func UnmountContainer +<div style="background-color: #E8E8E8; padding: 15px; margin: 10px; border-radius: 10px;"> + +method UnmountContainer(name: [string](https://godoc.org/builtin#string), force: [bool](https://godoc.org/builtin#bool)) </div> +UnmountContainer umounts a container by its name or full/partial container ID. ### <a name="UnpauseContainer"></a>func UnpauseContainer <div style="background-color: #E8E8E8; padding: 15px; margin: 10px; border-radius: 10px;"> @@ -1293,6 +1367,33 @@ insecure_registries [[]string](#[]string) store [InfoStore](#InfoStore) podman [InfoPodmanBinary](#InfoPodmanBinary) +### <a name="Runlabel"></a>type Runlabel + +Runlabel describes the required input for container runlabel + +image [string](https://godoc.org/builtin#string) + +authfile [string](https://godoc.org/builtin#string) + +certDir [string](https://godoc.org/builtin#string) + +creds [string](https://godoc.org/builtin#string) + +display [bool](https://godoc.org/builtin#bool) + +name [string](https://godoc.org/builtin#string) + +pull [bool](https://godoc.org/builtin#bool) + +signaturePolicyPath [string](https://godoc.org/builtin#string) + +tlsVerify [bool](https://godoc.org/builtin#bool) + +label [string](https://godoc.org/builtin#string) + +extraArgs [[]string](#[]string) + +opts [map[string]](#map[string]) ### <a name="Sockets"></a>type Sockets Sockets describes sockets location for a container @@ -1336,7 +1437,7 @@ ImageNotFound means the image could not be found by the provided name or ID in l NoContainerRunning means none of the containers requested are running in a command that requires a running container. ### <a name="NoContainersInPod"></a>type NoContainersInPod -NoContainersInPod means a pod has no containers on which to perform operation. It contains +NoContainersInPod means a pod has no containers on which to perform the operation. It contains the pod ID. ### <a name="PodContainerError"></a>type PodContainerError diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c4e208894..8e921dcf3 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -180,6 +180,27 @@ Use your real name (sorry, no pseudonyms or anonymous contributions.) If you set your `user.name` and `user.email` git configs, you can sign your commit automatically with `git commit -s`. +### Go Format and lint + +All code changes must pass ``make validate`` and ``make lint``, as +executed in a standard container. The container image for this +purpose is provided at: ``quay.io/libpod/gate:latest``. However, +for changes to the image itself, it may also be built locally +from the repository root, with the command: + +``` +sudo podman build -t quay.io/libpod/gate:latest -f contrib/gate/Dockerfile . +``` + +The container executes 'make' by default, on a copy of the repository. +This avoids changing or leaving build artifacts in your working directory. +Execution does not require any special permissions from the host. However, +the repository root must be bind-mounted into the container at +'/usr/src/libpod'. For example, running `make lint` is done (from +the repository root) with the command: + +``sudo podman run -it --rm -v $PWD:/usr/src/libpod:z quay.io/libpod/gate:latest lint`` + ### Integration Tests Our primary means of performing integration testing for libpod is with the diff --git a/Dockerfile b/Dockerfile index 70d1a7629..08af0f851 100644 --- a/Dockerfile +++ b/Dockerfile @@ -52,7 +52,7 @@ ADD . /go/src/github.com/containers/libpod RUN set -x && cd /go/src/github.com/containers/libpod && make install.libseccomp.sudo # Install runc -ENV RUNC_COMMIT 78ef28e63bec2ee4c139b5e3e0d691eb9bdc748d +ENV RUNC_COMMIT 96ec2177ae841256168fcf76954f7177af9446eb RUN set -x \ && export GOPATH="$(mktemp -d)" \ && git clone https://github.com/opencontainers/runc.git "$GOPATH/src/github.com/opencontainers/runc" \ @@ -64,7 +64,7 @@ RUN set -x \ && rm -rf "$GOPATH" # Install conmon -ENV CRIO_COMMIT 662dbb31b5d4f5ed54511a47cde7190c61c28677 +ENV CRIO_COMMIT 7a283c391abb7bd25086a8ff91dbb36ebdd24466 RUN set -x \ && export GOPATH="$(mktemp -d)" \ && git clone https://github.com/kubernetes-sigs/cri-o.git "$GOPATH/src/github.com/kubernetes-sigs/cri-o.git" \ @@ -112,8 +112,7 @@ RUN set -x \ && go get -u github.com/mailru/easyjson/... \ && install -D -m 755 "$GOPATH"/bin/easyjson /usr/bin/ -# Install criu -ENV CRIU_COMMIT 584cbe4643c3fc7dc901ff08bf923ca0fe7326f9 +# Install latest stable criu version RUN set -x \ && cd /tmp \ && git clone https://github.com/checkpoint-restore/criu.git \ diff --git a/Dockerfile.CentOS b/Dockerfile.CentOS index 67b7ddce1..3e14e59db 100644 --- a/Dockerfile.CentOS +++ b/Dockerfile.CentOS @@ -68,7 +68,7 @@ RUN set -x \ && install -D -m 755 "$GOPATH"/bin/easyjson /usr/bin/ # Install conmon -ENV CRIO_COMMIT 662dbb31b5d4f5ed54511a47cde7190c61c28677 +ENV CRIO_COMMIT 7a283c391abb7bd25086a8ff91dbb36ebdd24466 RUN set -x \ && export GOPATH="$(mktemp -d)" \ && git clone https://github.com/kubernetes-sigs/cri-o.git "$GOPATH/src/github.com/kubernetes-sigs/cri-o.git" \ diff --git a/Dockerfile.Fedora b/Dockerfile.Fedora index 38cd599d4..2080b597b 100644 --- a/Dockerfile.Fedora +++ b/Dockerfile.Fedora @@ -17,7 +17,7 @@ RUN dnf -y install btrfs-progs-devel \ libseccomp-devel \ libselinux-devel \ skopeo-containers \ - https://kojipkgs.fedoraproject.org//packages/runc/1.0.0/55.dev.git578fe65.fc28/x86_64/runc-1.0.0-55.dev.git578fe65.fc28.x86_64.rpm \ + runc \ make \ ostree-devel \ python \ @@ -72,7 +72,7 @@ RUN set -x \ && install -D -m 755 "$GOPATH"/bin/easyjson /usr/bin/ # Install conmon -ENV CRIO_COMMIT 662dbb31b5d4f5ed54511a47cde7190c61c28677 +ENV CRIO_COMMIT 7a283c391abb7bd25086a8ff91dbb36ebdd24466 RUN set -x \ && export GOPATH="$(mktemp -d)" \ && git clone https://github.com/kubernetes-sigs/cri-o.git "$GOPATH/src/github.com/kubernetes-sigs/cri-o.git" \ @@ -1,6 +1,6 @@ GO ?= go DESTDIR ?= / -EPOCH_TEST_COMMIT ?= 733cfe96819e1dc044e982b5321b3c902d1a47c6 +EPOCH_TEST_COMMIT ?= 1b52843cfd2ae254a6e52c74e564730f1c875c4c HEAD ?= HEAD CHANGELOG_BASE ?= HEAD~ CHANGELOG_TARGET ?= HEAD @@ -23,7 +23,7 @@ BUILDTAGS_CROSS ?= containers_image_openpgp containers_image_ostree_stub exclude ifneq (,$(findstring varlink,$(BUILDTAGS))) PODMAN_VARLINK_DEPENDENCIES = cmd/podman/varlink/iopodman.go endif -CONTAINER_RUNTIME := $(shell command -v podman 2> /dev/null | echo docker) +CONTAINER_RUNTIME := $(shell command -v podman 2> /dev/null || echo docker) HAS_PYTHON3 := $(shell command -v python3 2>/dev/null) @@ -31,12 +31,13 @@ BASHINSTALLDIR=${PREFIX}/share/bash-completion/completions OCIUMOUNTINSTALLDIR=$(PREFIX)/share/oci-umount/oci-umount.d SELINUXOPT ?= $(shell test -x /usr/sbin/selinuxenabled && selinuxenabled && echo -Z) -PACKAGES ?= $(shell $(GO) list -tags "${BUILDTAGS}" ./... | grep -v github.com/containers/libpod/vendor | grep -v e2e) +PACKAGES ?= $(shell $(GO) list -tags "${BUILDTAGS}" ./... | grep -v github.com/containers/libpod/vendor | grep -v e2e | grep -v system ) COMMIT_NO ?= $(shell git rev-parse HEAD 2> /dev/null || true) GIT_COMMIT ?= $(if $(shell git status --porcelain --untracked-files=no),"${COMMIT_NO}-dirty","${COMMIT_NO}") BUILD_INFO ?= $(shell date +%s) -LDFLAGS_PODMAN ?= $(LDFLAGS) -X main.gitCommit=$(GIT_COMMIT) -X main.buildInfo=$(BUILD_INFO) +LIBPOD := ${PROJECT}/libpod +LDFLAGS_PODMAN ?= $(LDFLAGS) -X $(LIBPOD).gitCommit=$(GIT_COMMIT) -X $(LIBPOD).buildInfo=$(BUILD_INFO) ISODATE ?= $(shell date --iso-8601) LIBSECCOMP_COMMIT := release-2.3 @@ -103,6 +104,9 @@ test/copyimg/copyimg: .gopathok $(wildcard test/copyimg/*.go) test/checkseccomp/checkseccomp: .gopathok $(wildcard test/checkseccomp/*.go) $(GO) build -ldflags '$(LDFLAGS)' -tags "$(BUILDTAGS) containers_image_ostree_stub" -o $@ $(PROJECT)/test/checkseccomp +test/goecho/goecho: .gopathok $(wildcard test/goecho/*.go) + $(GO) build -ldflags '$(LDFLAGS)' -o $@ $(PROJECT)/test/goecho + podman: .gopathok $(PODMAN_VARLINK_DEPENDENCIES) $(GO) build -i -ldflags '$(LDFLAGS_PODMAN)' -tags "$(BUILDTAGS)" -o bin/$@ $(PROJECT)/cmd/podman @@ -129,6 +133,7 @@ clean: test/bin2img/bin2img \ test/checkseccomp/checkseccomp \ test/copyimg/copyimg \ + test/goecho/goecho \ test/testdata/redis-image \ cmd/podman/varlink/iopodman.go \ libpod/container_ffjson.go \ @@ -165,7 +170,7 @@ shell: libpodimage testunit: libpodimage ${CONTAINER_RUNTIME} run -e STORAGE_OPTIONS="--storage-driver=vfs" -e TESTFLAGS -e CGROUP_MANAGER=cgroupfs -e TRAVIS -t --privileged --rm -v ${CURDIR}:/go/src/${PROJECT} ${LIBPOD_IMAGE} make localunit -localunit: varlink_generate +localunit: test/goecho/goecho varlink_generate $(GO) test -tags "$(BUILDTAGS)" -cover $(PACKAGES) ginkgo: @@ -173,6 +178,12 @@ ginkgo: localintegration: varlink_generate test-binaries clientintegration ginkgo +localsystem: .install.ginkgo .install.gomega + ginkgo -v -noColor test/system/ + +system.test-binary: .install.ginkgo .install.gomega + $(GO) test -c ./test/system + clientintegration: $(MAKE) -C contrib/python/podman integration $(MAKE) -C contrib/python/pypodman integration @@ -182,7 +193,7 @@ vagrant-check: binaries: varlink_generate easyjson_generate podman -test-binaries: test/bin2img/bin2img test/copyimg/copyimg test/checkseccomp/checkseccomp +test-binaries: test/bin2img/bin2img test/copyimg/copyimg test/checkseccomp/checkseccomp test/goecho/goecho MANPAGES_MD ?= $(wildcard docs/*.md pkg/*/docs/*.md) MANPAGES ?= $(MANPAGES_MD:%.md=%) @@ -282,7 +293,7 @@ install.tools: .install.gitvalidation .install.gometalinter .install.md2man .ins if [ ! -x "$(GOBIN)/gometalinter" ]; then \ $(GO) get -u github.com/alecthomas/gometalinter; \ cd $(FIRST_GOPATH)/src/github.com/alecthomas/gometalinter; \ - git checkout 23261fa046586808612c61da7a81d75a658e0814; \ + git checkout e8d801238da6f0dfd14078d68f9b53fa50a7eeb5; \ $(GO) install github.com/alecthomas/gometalinter; \ $(GOBIN)/gometalinter --install; \ fi @@ -3,9 +3,14 @@ approvers: - baude - mrunalp - rhatdan + - TomSweeneyRedHat + - umohnani8 + - giuseppe + - vrothberg reviewers: - mheon - baude + - mrunalp - rhatdan - TomSweeneyRedHat - umohnani8 @@ -1,9 +1,11 @@ ![PODMAN logo](logo/podman-logo-source.svg) # libpod - library for running OCI-based containers in Pods -### Latest Version: 0.10.1.3 +### Latest Version: 0.11.1.1 ### Status: Active Development +### Continuous Integration: [![Build Status](https://api.cirrus-ci.com/github/containers/libpod.svg)](https://cirrus-ci.com/github/containers/libpod) + ## What is the scope of this project? libpod provides a library for applications looking to use the Container Pod concept popularized by Kubernetes. diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 6c7bf1a8f..05854c8d7 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -1,5 +1,53 @@ # Release Notes +## 0.11.1.1 +### Bugfixes +- Fixed a bug where Podman was not correctly adding firewall rules for containers, preventing them from accessing the network +- Fixed a bug where full error messages were being lost when creating containers with user namespaces +- Fixed a bug where container state was not properly updated if a failure occurred during network setup, which could cause mounts to be left behind when the container was removed +- Fixed a bug where `podman exec` could time out on slower systems by increasing the relevant timeout + +### Misc +- `podman rm -f` now removes paused containers. As such, `podman rm -af` completing successfully guarantees all Podman containers have been removed +- Added a field to `podman info` to show if Podman is being run as rootless +- Made a small output format change to `podman images` - image sizes now feature a space between number and unit (e.g. `123 MB` now instead of `123MB`) +- Vendored an updated version of `containers/storage` to fix several bugs reported upstream + +## 0.11.1 +### Features +- Added `--all` and `--latest` flags to `podman checkpoint` and `podman restore` +- Added `--max-workers` flag to all Podman commands that support operating in parallel, allowing the maximum number of parallel workers used to be specified +- Added `--all` flag to `podman restart` + +### Bugfixes +- Fixed a bug where `podman port -l` would segfault if no containers were present +- Fixed a bug where `podman stats -a` would error if containers were present but not running +- Fixed a bug where container status checks would sometimes leave zombie OCI runtime processes +- Fixed checkpoint and restore code to verify an appropriate version of `criu` is being used +- Fixed a bug where environment variables with no specified value (e.g. `-e FOO`) caused errors (they are now added as empty) +- Fixed a bug where rootless Podman would attempt to configure the system firewall, causing errors on some systems where iptables is not in the user's PATH +- Fixed a bug where rootless Podman was unable to successfully write the container ID to a file when `--cid-file` was specified to `podman run` +- Fixed a bug where `podman unmount` would refuse to unmount a container if it was running (the unmount will now be deferred until the container stops) +- Fixed a bug where rootless `podman attach` would fail to attach due to a too-long path name +- Fixed a bug where `podman info` was not properly reporting the Git commit Podman was built from +- Fixed a bug where `podman run --interactive` was not holding STDIN open when `-a` flag was specified +- Fixed a bug where Podman with the `cgroupfs` CGroup driver was sometimes not successfully removing pod CGroups +- Fixed a bug where rootless Podman was unable to run systemd containers (note that this also requires an update to systemd) +- Fixed a bug where `podman run` with the `--user` flag would fail if the container image did not contain `/etc/passwd` or `/etc/group` + +### Misc +- `podman rm`, `podman restart`, `podman kill`, `podman pause`, and `podman unpause` now operate in parallel, greatly improving speed when multiple containers are specified +- `podman create`, `podman run`, and `podman ps` have a number of improvements which should greatly increase their speed +- Greatly improved performance and reduced memory utilization of container status checks, which should improve the speed of most Podman commands +- Improve ability of `podman runlabel` to run commands that are not Podman +- Podman containers with an IP address now add their hostnames to `/etc/hosts` +- Changed default location of temporary libpod files in rootless Podman +- Updated the default Podman seccomp profile + +### Compatability +Several paths related to rootless Podman had their default values changed in this release. +If paths were not hardcoded in libpod.conf, your system may lose track of running containers and believe they are newly-created. + ## 0.10.1.3 ### Bugfixes - Fixed a bug where `podman build` would not work while any containers were running diff --git a/changelog.txt b/changelog.txt index ace41f1d9..7b0f0f3af 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,3 +1,133 @@ +- Changelog for v0.11.1.1 (2018-11-15) + * Vendor in containers/storage + * Add release notes for 0.11.1.1 + * Increase pidWaitTimeout to 60s + * Cirrus: Add master branch testing status badge + * rootless: call IsRootless just once + * Bump golang to v1.10 in install.md + * Standardized container image for gofmt and lint + * Make list of approvers same as reviewers + * vendor: update ostree-go + * vendor.conf: fix typo + * Cleanup podman spec to not show git checkout is dirty + * Add space between num & unit in images output + * Update troubleshooting guide to deal with rootless path + * troubleshooting.md: add a recipe for rootless ping + * remove $-prefix from (most) shell examples + * docs: Fix duplicated entry for pod-container-unmount + * Better document rootless containers + * info: add rootless field + * Accurately update state if prepare() partially fails + * Do not hide errors when creating container with UserNSRoot + * rm -f now removes a paused container + * correct assignment of networkStatus + * podman_tutorial: cni build path has changed + * Bump gitvalidation epoch + * Bump to v0.11.2-dev + * Cirrus: Ignore any error from the IRC messenger + * rootless: default to fuse-overlayfs when available + +- Changelog for v0.11.1 (2018-11-08) + * Update release notes for 0.11.1 + * update seccomp.json + * Touch up --log* options and daemons in man pages + * Fix run --hostname test that started failing post-merge + * move defer'd function declaration ahead of prepare error return + * Don't fail if /etc/passwd or /etc/group does not exists + * Print error status code if we fail to parse it + * Properly set Running state when starting containers + * Fix misspelling + * Retrieve container PID from conmon + * If a container ceases to exist in runc, set exit status + * EXPERIMENTAL: Do not call out to runc for sync + * Actually save changes from post-stop sync + * rootless: mount /sys/fs/cgroup/systemd from the host + * rootless: don't bind mount /sys/fs/cgroup/systemd in systemd mode + * Add hostname to /etc/hosts + * Temporarily fix the Python tests to fix some PRs + * Remove conmon cgroup before pod cgroup for cgroupfs + * Fix cleanup for "Pause a bunch of running containers" + * --interactive shall keep STDIN attached even when not explicitly called out + * Do never override podman with docker + * Make kill, pause, and unpause parallel. + * Fix long image name handling + * Make restart parallel and add --all + * Add ChangeAction to parse sub-options from --change + * replace quay.io/baude to quay.io/libpod + * Change humanize to use MB vs MiB. + * allow ppc64le to pass libpod integration tests + * Cirrus-CI: Add option to run system-tests + * Cirrus: Skip rebuilding images unless instructed + * Cirrus: Disable image build job abort on push + * Cirrus: Add a readme + * Ubuntu VM image build: try update twice + * Cirrus: Enable updating F28 image + * rootless: do not add an additional /run to runroot + * rootless: avoid hang on failed slirp4netns + * Fix setting of version information + * runtime: do not allow runroot longer than 50 characters + * attach: fix attach when cuid is too long + * truncate command output in ps by default + * Update the runc commit used for testing + * make various changes to ps output + * Sync default config with libpod.conf + * Use two spaces to pad PS fields + * unmount: fix error logic + * get user and group information using securejoin and runc's user library + * CONTRIBUTING.md: add section about describing changes + * Change to exported name in ParseDevice + * Vendor in latest containers/storage + * fix bug in rm -fa parallel deletes + * Ensure test container in running state + * Add tests for selinux labels + * Add --max-workers and heuristics for parallel operations + * Increase security and performance when looking up groups + * run prepare in parallel + * downgrade runc due a rootless bug + * runlabel: run any command + * Eat our own dogfood + * vendor: update containers/storage + * Add support for /usr/local installation + * create: fix writing cidfile when using rootless + * Explain the device format in man pages + * read conmon output and convert to json in two steps + * Cirrus: Use images w/ buildah fix + * Add --all and --latest to checkpoint/restore + * Use the newly added getAllOrLatestContainers() function + * Use the new checkAllAndLatest() function + * Also factor out getAllOrLatestContainers() function + * Add checkAllAndLatest() function + * Downgrade code to support python3.4 + * Allow containers/storage to handle on SELinux labeling + * Use more reliable check for rootless for firewall init + * Vendor in latest containers/storage opencontainers/selinux + * Make podman ps fast + * Support auth file environment variable in podman build + * fix environment variable parsing + * tests: use existing CRIU version check + * Use the CRIU version check in checkpoint/restore + * Add helper function to read out CRIU version + * vendor in go-criu and dependencies + * oci: cleanup process status + * Handle http/https in registry given to login/out + * re-enable f29 testing + * correct stats err with non-running containers + * Use restoreArtifacts to save time in integration tests + * Make rm faster + * Fix man page to show info on storage + * Move rootless directory handling to the libpod/pkg/util directory + * Fix podman port -l + * Fix trivial missing markup in manpage + * Cirrus: Install CRIU in test images + * Cirrus: Use different CNI_COMMIT for Fedora + * Fix Cirrus/Packer VM image building + * Revert "Cirrus: Enable debugging delay on non-zero exit" + * Cirrus: IRC message when cirrus testing successful + * cirrus: Add simple IRC messenger + * fix NOTIFY_SOCKET in e2e testfix NOTIFY_SOCKET in e2e tests + * Bump gitvalidation epoch + * Bump to v0.10.2-dev + - Changelog for v0.10.1.3 (2018-10-17) * Update release notes for 0.10.1.3 * Vendor in new new buildah/ci diff --git a/cmd/podman/build.go b/cmd/podman/build.go index 14bf226f9..880cb892f 100644 --- a/cmd/podman/build.go +++ b/cmd/podman/build.go @@ -1,6 +1,11 @@ package main import ( + "io/ioutil" + "os" + "path/filepath" + "strings" + "github.com/containers/buildah" "github.com/containers/buildah/imagebuildah" buildahcli "github.com/containers/buildah/pkg/cli" @@ -10,15 +15,15 @@ import ( "github.com/pkg/errors" "github.com/sirupsen/logrus" "github.com/urfave/cli" - "io/ioutil" - "os" - "path/filepath" - "strings" ) var ( layerFlags = []cli.Flag{ cli.BoolTFlag{ + Name: "force-rm", + Usage: "Always remove intermediate containers after a build, even if the build is unsuccessful. (default true)", + }, + cli.BoolTFlag{ Name: "layers", Usage: "cache intermediate layers during build. Use BUILDAH_LAYERS environment variable to override. ", }, @@ -230,7 +235,7 @@ func buildCmd(c *cli.Context) error { Layers: layers, NoCache: c.Bool("no-cache"), RemoveIntermediateCtrs: c.BoolT("rm"), - ForceRmIntermediateCtrs: c.Bool("force-rm"), + ForceRmIntermediateCtrs: c.BoolT("force-rm"), } if c.Bool("quiet") { diff --git a/cmd/podman/checkpoint.go b/cmd/podman/checkpoint.go index bf280920d..824c97662 100644 --- a/cmd/podman/checkpoint.go +++ b/cmd/podman/checkpoint.go @@ -24,6 +24,14 @@ var ( Usage: "keep all temporary checkpoint files", }, cli.BoolFlag{ + Name: "leave-running, R", + Usage: "leave the container running after writing checkpoint to disk", + }, + cli.BoolFlag{ + Name: "tcp-established", + Usage: "checkpoint a container with established TCP connections", + }, + cli.BoolFlag{ Name: "all, a", Usage: "checkpoint all running containers", }, @@ -50,7 +58,11 @@ func checkpointCmd(c *cli.Context) error { } defer runtime.Shutdown(false) - keep := c.Bool("keep") + options := libpod.ContainerCheckpointOptions{ + Keep: c.Bool("keep"), + KeepRunning: c.Bool("leave-running"), + TCPEstablished: c.Bool("tcp-established"), + } if err := checkAllAndLatest(c); err != nil { return err @@ -59,7 +71,7 @@ func checkpointCmd(c *cli.Context) error { containers, lastError := getAllOrLatestContainers(c, runtime, libpod.ContainerStateRunning, "running") for _, ctr := range containers { - if err = ctr.Checkpoint(context.TODO(), keep); err != nil { + if err = ctr.Checkpoint(context.TODO(), options); err != nil { if lastError != nil { fmt.Fprintln(os.Stderr, lastError) } diff --git a/cmd/podman/commit.go b/cmd/podman/commit.go index b09c6b0d9..02ede4f73 100644 --- a/cmd/podman/commit.go +++ b/cmd/podman/commit.go @@ -95,7 +95,7 @@ func commitCmd(c *cli.Context) error { for _, change := range c.StringSlice("change") { splitChange := strings.Split(strings.ToUpper(change), "=") if !util.StringInSlice(splitChange[0], libpod.ChangeCmds) { - return errors.Errorf("invalid syntax for --change ", change) + return errors.Errorf("invalid syntax for --change: %s", change) } } } diff --git a/cmd/podman/common.go b/cmd/podman/common.go index f9e746b28..c4016698a 100644 --- a/cmd/podman/common.go +++ b/cmd/podman/common.go @@ -11,6 +11,7 @@ import ( "github.com/containers/buildah" "github.com/containers/libpod/libpod" + "github.com/containers/libpod/pkg/rootless" "github.com/containers/storage" "github.com/fatih/camelcase" "github.com/pkg/errors" @@ -161,6 +162,13 @@ func getContext() context.Context { return context.TODO() } +func getDefaultNetwork() string { + if rootless.IsRootless() { + return "slirp4netns" + } + return "bridge" +} + // Common flags shared between commands var createFlags = []cli.Flag{ cli.StringSliceFlag{ @@ -372,7 +380,7 @@ var createFlags = []cli.Flag{ cli.StringFlag{ Name: "net, network", Usage: "Connect a container to a network", - Value: "bridge", + Value: getDefaultNetwork(), }, cli.BoolFlag{ Name: "oom-kill-disable", diff --git a/cmd/podman/container.go b/cmd/podman/container.go index ff634278f..b6262f890 100644 --- a/cmd/podman/container.go +++ b/cmd/podman/container.go @@ -9,6 +9,7 @@ var ( attachCommand, checkpointCommand, cleanupCommand, + containerExistsCommand, commitCommand, createCommand, diffCommand, diff --git a/cmd/podman/create.go b/cmd/podman/create.go index 9f6825c95..228438d75 100644 --- a/cmd/podman/create.go +++ b/cmd/podman/create.go @@ -11,6 +11,7 @@ import ( "syscall" "github.com/containers/libpod/cmd/podman/libpodruntime" + "github.com/containers/libpod/cmd/podman/shared" "github.com/containers/libpod/libpod" "github.com/containers/libpod/libpod/image" ann "github.com/containers/libpod/pkg/annotations" @@ -66,7 +67,7 @@ func createCmd(c *cli.Context) error { rootless.SetSkipStorageSetup(true) } - runtime, err := libpodruntime.GetContainerRuntime(c) + runtime, err := libpodruntime.GetRuntime(c) if err != nil { return errors.Wrapf(err, "error creating libpod runtime") } @@ -375,8 +376,8 @@ func configureEntrypoint(c *cli.Context, data *inspect.ImageData) []string { return entrypoint } -func configurePod(c *cli.Context, runtime *libpod.Runtime, namespaces map[string]string) (map[string]string, error) { - pod, err := runtime.LookupPod(c.String("pod")) +func configurePod(c *cli.Context, runtime *libpod.Runtime, namespaces map[string]string, podName string) (map[string]string, error) { + pod, err := runtime.LookupPod(podName) if err != nil { return namespaces, err } @@ -409,6 +410,7 @@ func parseCreateOpts(ctx context.Context, c *cli.Context, runtime *libpod.Runtim inputCommand, command []string memoryLimit, memoryReservation, memorySwap, memoryKernel int64 blkioWeight uint16 + namespaces map[string]string ) idmappings, err := util.ParseIDMapping(c.StringSlice("uidmap"), c.StringSlice("gidmap"), c.String("subuidname"), c.String("subgidname")) if err != nil { @@ -492,12 +494,21 @@ func parseCreateOpts(ctx context.Context, c *cli.Context, runtime *libpod.Runtim return nil, errors.Errorf("--cpu-quota and --cpus cannot be set together") } + // EXPOSED PORTS + var portBindings map[nat.Port][]nat.PortBinding + if data != nil { + portBindings, err = cc.ExposedPorts(c.StringSlice("expose"), c.StringSlice("publish"), c.Bool("publish-all"), data.ContainerConfig.ExposedPorts) + if err != nil { + return nil, err + } + } + // Kernel Namespaces // TODO Fix handling of namespace from pod // Instead of integrating here, should be done in libpod // However, that also involves setting up security opts // when the pod's namespace is integrated - namespaces := map[string]string{ + namespaces = map[string]string{ "pid": c.String("pid"), "net": c.String("net"), "ipc": c.String("ipc"), @@ -505,8 +516,41 @@ func parseCreateOpts(ctx context.Context, c *cli.Context, runtime *libpod.Runtim "uts": c.String("uts"), } + originalPodName := c.String("pod") + podName := strings.Replace(originalPodName, "new:", "", 1) + // after we strip out :new, make sure there is something left for a pod name + if len(podName) < 1 && c.IsSet("pod") { + return nil, errors.Errorf("new pod name must be at least one character") + } if c.IsSet("pod") { - namespaces, err = configurePod(c, runtime, namespaces) + if strings.HasPrefix(originalPodName, "new:") { + // pod does not exist; lets make it + var podOptions []libpod.PodCreateOption + podOptions = append(podOptions, libpod.WithPodName(podName), libpod.WithInfraContainer(), libpod.WithPodCgroups()) + if len(portBindings) > 0 { + ociPortBindings, err := cc.NatToOCIPortBindings(portBindings) + if err != nil { + return nil, err + } + podOptions = append(podOptions, libpod.WithInfraContainerPorts(ociPortBindings)) + } + + podNsOptions, err := shared.GetNamespaceOptions(strings.Split(DefaultKernelNamespaces, ",")) + if err != nil { + return nil, err + } + podOptions = append(podOptions, podNsOptions...) + // make pod + pod, err := runtime.NewPod(ctx, podOptions...) + if err != nil { + return nil, err + } + logrus.Debugf("pod %s created by new container request", pod.ID()) + + // The container now cannot have port bindings; so we reset the map + portBindings = make(map[nat.Port][]nat.PortBinding) + } + namespaces, err = configurePod(c, runtime, namespaces, podName) if err != nil { return nil, err } @@ -535,7 +579,7 @@ func parseCreateOpts(ctx context.Context, c *cli.Context, runtime *libpod.Runtim // Make sure if network is set to container namespace, port binding is not also being asked for netMode := ns.NetworkMode(namespaces["net"]) if netMode.IsContainer() { - if len(c.StringSlice("publish")) > 0 || c.Bool("publish-all") { + if len(portBindings) > 0 { return nil, errors.Errorf("cannot set port bindings on an existing container network namespace") } } @@ -644,15 +688,6 @@ func parseCreateOpts(ctx context.Context, c *cli.Context, runtime *libpod.Runtim return nil, errors.Errorf("No command specified on command line or as CMD or ENTRYPOINT in this image") } - // EXPOSED PORTS - var portBindings map[nat.Port][]nat.PortBinding - if data != nil { - portBindings, err = cc.ExposedPorts(c.StringSlice("expose"), c.StringSlice("publish"), c.Bool("publish-all"), data.ContainerConfig.ExposedPorts) - if err != nil { - return nil, err - } - } - // SHM Size shmSize, err := units.FromHumanSize(c.String("shm-size")) if err != nil { @@ -670,6 +705,11 @@ func parseCreateOpts(ctx context.Context, c *cli.Context, runtime *libpod.Runtim if util.StringInSlice(".", c.StringSlice("dns-search")) && len(c.StringSlice("dns-search")) > 1 { return nil, errors.Errorf("cannot pass additional search domains when also specifying '.'") } + if !netMode.IsPrivate() { + if c.IsSet("dns-search") || c.IsSet("dns") || c.IsSet("dns-opt") { + return nil, errors.Errorf("specifying DNS flags when network mode is shared with the host or another container is not allowed") + } + } // Validate domains are good for _, dom := range c.StringSlice("dns-search") { @@ -741,7 +781,7 @@ func parseCreateOpts(ctx context.Context, c *cli.Context, runtime *libpod.Runtim NetMode: netMode, UtsMode: utsMode, PidMode: pidMode, - Pod: c.String("pod"), + Pod: podName, Privileged: c.Bool("privileged"), Publish: c.StringSlice("publish"), PublishAll: c.Bool("publish-all"), diff --git a/cmd/podman/exists.go b/cmd/podman/exists.go new file mode 100644 index 000000000..2e2559ec7 --- /dev/null +++ b/cmd/podman/exists.go @@ -0,0 +1,120 @@ +package main + +import ( + "os" + + "github.com/containers/libpod/cmd/podman/libpodruntime" + "github.com/containers/libpod/libpod" + "github.com/containers/libpod/libpod/image" + "github.com/pkg/errors" + "github.com/urfave/cli" +) + +var ( + imageExistsDescription = ` + podman image exists + + Check if an image exists in local storage +` + + imageExistsCommand = cli.Command{ + Name: "exists", + Usage: "Check if an image exists in local storage", + Description: imageExistsDescription, + Action: imageExistsCmd, + ArgsUsage: "IMAGE-NAME", + OnUsageError: usageErrorHandler, + } +) + +var ( + containerExistsDescription = ` + podman container exists + + Check if a container exists in local storage +` + + containerExistsCommand = cli.Command{ + Name: "exists", + Usage: "Check if a container exists in local storage", + Description: containerExistsDescription, + Action: containerExistsCmd, + ArgsUsage: "CONTAINER-NAME", + OnUsageError: usageErrorHandler, + } +) + +var ( + podExistsDescription = ` + podman pod exists + + Check if a pod exists in local storage +` + + podExistsCommand = cli.Command{ + Name: "exists", + Usage: "Check if a pod exists in local storage", + Description: podExistsDescription, + Action: podExistsCmd, + ArgsUsage: "POD-NAME", + OnUsageError: usageErrorHandler, + } +) + +func imageExistsCmd(c *cli.Context) error { + args := c.Args() + if len(args) > 1 || len(args) < 1 { + return errors.New("you may only check for the existence of one image at a time") + } + runtime, err := libpodruntime.GetRuntime(c) + if err != nil { + return errors.Wrapf(err, "could not get runtime") + } + defer runtime.Shutdown(false) + if _, err := runtime.ImageRuntime().NewFromLocal(args[0]); err != nil { + if errors.Cause(err) == image.ErrNoSuchImage { + os.Exit(1) + } + return err + } + return nil +} + +func containerExistsCmd(c *cli.Context) error { + args := c.Args() + if len(args) > 1 || len(args) < 1 { + return errors.New("you may only check for the existence of one container at a time") + } + runtime, err := libpodruntime.GetRuntime(c) + if err != nil { + return errors.Wrapf(err, "could not get runtime") + } + defer runtime.Shutdown(false) + if _, err := runtime.LookupContainer(args[0]); err != nil { + if errors.Cause(err) == libpod.ErrNoSuchCtr { + os.Exit(1) + } + return err + } + return nil +} + +func podExistsCmd(c *cli.Context) error { + args := c.Args() + if len(args) > 1 || len(args) < 1 { + return errors.New("you may only check for the existence of one pod at a time") + } + runtime, err := libpodruntime.GetRuntime(c) + if err != nil { + return errors.Wrapf(err, "could not get runtime") + } + defer runtime.Shutdown(false) + + if _, err := runtime.LookupPod(args[0]); err != nil { + if errors.Cause(err) == libpod.ErrNoSuchPod { + os.Exit(1) + } + return err + } + return nil +} diff --git a/cmd/podman/image.go b/cmd/podman/image.go index e67f61799..418b442e3 100644 --- a/cmd/podman/image.go +++ b/cmd/podman/image.go @@ -9,6 +9,7 @@ var ( buildCommand, historyCommand, importCommand, + imageExistsCommand, inspectCommand, loadCommand, lsImagesCommand, diff --git a/cmd/podman/images.go b/cmd/podman/images.go index a8955e49e..3351123ed 100644 --- a/cmd/podman/images.go +++ b/cmd/podman/images.go @@ -6,8 +6,7 @@ import ( "sort" "strings" "time" - - "github.com/sirupsen/logrus" + "unicode" "github.com/containers/libpod/cmd/podman/formats" "github.com/containers/libpod/cmd/podman/libpodruntime" @@ -16,6 +15,7 @@ import ( "github.com/docker/go-units" digest "github.com/opencontainers/go-digest" "github.com/pkg/errors" + "github.com/sirupsen/logrus" "github.com/urfave/cli" ) @@ -289,6 +289,8 @@ func getImagesTemplateOutput(ctx context.Context, runtime *libpod.Runtime, image sizeStr = err.Error() } else { sizeStr = units.HumanSizeWithPrecision(float64(*size), 3) + lastNumIdx := strings.LastIndexFunc(sizeStr, unicode.IsNumber) + sizeStr = sizeStr[:lastNumIdx+1] + " " + sizeStr[lastNumIdx+1:] } params := imagesTemplateParams{ Repository: repo, @@ -374,13 +376,13 @@ func CreateFilterFuncs(ctx context.Context, r *libpod.Runtime, c *cli.Context, i case "before": before, err := r.ImageRuntime().NewFromLocal(splitFilter[1]) if err != nil { - return nil, errors.Wrapf(err, "unable to find image % in local stores", splitFilter[1]) + return nil, errors.Wrapf(err, "unable to find image %s in local stores", splitFilter[1]) } filterFuncs = append(filterFuncs, image.CreatedBeforeFilter(before.Created())) case "after": after, err := r.ImageRuntime().NewFromLocal(splitFilter[1]) if err != nil { - return nil, errors.Wrapf(err, "unable to find image % in local stores", splitFilter[1]) + return nil, errors.Wrapf(err, "unable to find image %s in local stores", splitFilter[1]) } filterFuncs = append(filterFuncs, image.CreatedAfterFilter(after.Created())) case "dangling": diff --git a/cmd/podman/import.go b/cmd/podman/import.go index be516e4fa..144354fa6 100644 --- a/cmd/podman/import.go +++ b/cmd/podman/import.go @@ -139,7 +139,7 @@ func downloadFromURL(source string) (string, error) { _, err = io.Copy(outFile, response.Body) if err != nil { - return "", errors.Wrapf(err, "error saving %q to %q", source, outFile) + return "", errors.Wrapf(err, "error saving %s to %s", source, outFile.Name()) } return outFile.Name(), nil diff --git a/cmd/podman/info.go b/cmd/podman/info.go index 563e63ba3..c0639725e 100644 --- a/cmd/podman/info.go +++ b/cmd/podman/info.go @@ -81,6 +81,7 @@ func debugInfo(c *cli.Context) map[string]interface{} { info["compiler"] = runtime.Compiler info["go version"] = runtime.Version() info["podman version"] = c.App.Version - info["git commit"] = libpod.GitCommit + version, _ := libpod.GetVersion() + info["git commit"] = version.GitCommit return info } diff --git a/cmd/podman/inspect.go b/cmd/podman/inspect.go index bd9e8c13c..6ffcde55f 100644 --- a/cmd/podman/inspect.go +++ b/cmd/podman/inspect.go @@ -119,7 +119,7 @@ func iterateInput(ctx context.Context, c *cli.Context, args []string, runtime *l } libpodInspectData, err := ctr.Inspect(c.Bool("size")) if err != nil { - inspectError = errors.Wrapf(err, "error getting libpod container inspect data %q", ctr.ID) + inspectError = errors.Wrapf(err, "error getting libpod container inspect data %s", ctr.ID()) break } data, err = shared.GetCtrInspectInfo(ctr, libpodInspectData) @@ -154,12 +154,12 @@ func iterateInput(ctx context.Context, c *cli.Context, args []string, runtime *l } else { libpodInspectData, err := ctr.Inspect(c.Bool("size")) if err != nil { - inspectError = errors.Wrapf(err, "error getting libpod container inspect data %q", ctr.ID) + inspectError = errors.Wrapf(err, "error getting libpod container inspect data %s", ctr.ID()) break } data, err = shared.GetCtrInspectInfo(ctr, libpodInspectData) if err != nil { - inspectError = errors.Wrapf(err, "error parsing container data %q", ctr.ID) + inspectError = errors.Wrapf(err, "error parsing container data %s", ctr.ID()) break } } diff --git a/cmd/podman/kill.go b/cmd/podman/kill.go index 7ca5bd7c5..cfe4b4218 100644 --- a/cmd/podman/kill.go +++ b/cmd/podman/kill.go @@ -1,15 +1,16 @@ package main import ( - "os" + "fmt" "syscall" - "fmt" "github.com/containers/libpod/cmd/podman/libpodruntime" + "github.com/containers/libpod/cmd/podman/shared" "github.com/containers/libpod/libpod" "github.com/containers/libpod/pkg/rootless" "github.com/docker/docker/pkg/signal" "github.com/pkg/errors" + "github.com/sirupsen/logrus" "github.com/urfave/cli" ) @@ -41,6 +42,11 @@ var ( // killCmd kills one or more containers with a signal func killCmd(c *cli.Context) error { + var ( + killFuncs []shared.ParallelWorkerInput + killSignal uint = uint(syscall.SIGTERM) + ) + if err := checkAllAndLatest(c); err != nil { return err } @@ -56,7 +62,6 @@ func killCmd(c *cli.Context) error { } defer runtime.Shutdown(false) - var killSignal uint = uint(syscall.SIGTERM) if c.String("signal") != "" { // Check if the signalString provided by the user is valid // Invalid signals will return err @@ -67,17 +72,32 @@ func killCmd(c *cli.Context) error { killSignal = uint(sysSignal) } - containers, lastError := getAllOrLatestContainers(c, runtime, libpod.ContainerStateRunning, "running") + containers, err := getAllOrLatestContainers(c, runtime, libpod.ContainerStateRunning, "running") + if err != nil { + if len(containers) == 0 { + return err + } + fmt.Println(err.Error()) + } for _, ctr := range containers { - if err := ctr.Kill(killSignal); err != nil { - if lastError != nil { - fmt.Fprintln(os.Stderr, lastError) - } - lastError = errors.Wrapf(err, "unable to find container %v", ctr.ID()) - } else { - fmt.Println(ctr.ID()) + con := ctr + f := func() error { + return con.Kill(killSignal) } + + killFuncs = append(killFuncs, shared.ParallelWorkerInput{ + ContainerID: con.ID(), + ParallelFunc: f, + }) } - return lastError + + maxWorkers := shared.Parallelize("kill") + if c.GlobalIsSet("max-workers") { + maxWorkers = c.GlobalInt("max-workers") + } + logrus.Debugf("Setting maximum workers to %d", maxWorkers) + + killErrors, errCount := shared.ParallelExecuteWorkerPool(maxWorkers, killFuncs) + return printParallelOutput(killErrors, errCount) } diff --git a/cmd/podman/kube.go b/cmd/podman/kube.go new file mode 100644 index 000000000..2cb407c09 --- /dev/null +++ b/cmd/podman/kube.go @@ -0,0 +1,23 @@ +package main + +import ( + "github.com/urfave/cli" +) + +var ( + kubeSubCommands = []cli.Command{ + containerKubeCommand, + } + + kubeDescription = "Work with Kubernetes objects" + kubeCommand = cli.Command{ + Name: "kube", + Usage: "Import and export Kubernetes objections from and to Podman", + Description: containerDescription, + ArgsUsage: "", + Subcommands: kubeSubCommands, + UseShortOptionHandling: true, + OnUsageError: usageErrorHandler, + Hidden: true, + } +) diff --git a/cmd/podman/kube_generate.go b/cmd/podman/kube_generate.go new file mode 100644 index 000000000..a18912668 --- /dev/null +++ b/cmd/podman/kube_generate.go @@ -0,0 +1,93 @@ +package main + +import ( + "fmt" + + "github.com/containers/libpod/cmd/podman/libpodruntime" + "github.com/containers/libpod/libpod" + "github.com/containers/libpod/pkg/rootless" + "github.com/ghodss/yaml" + "github.com/pkg/errors" + "github.com/sirupsen/logrus" + "github.com/urfave/cli" +) + +var ( + containerKubeFlags = []cli.Flag{ + cli.BoolFlag{ + Name: "service, s", + Usage: "only generate YAML for kubernetes service object", + }, + LatestFlag, + } + containerKubeDescription = "Generate Kubernetes Pod YAML" + containerKubeCommand = cli.Command{ + Name: "generate", + Usage: "Generate Kubernetes pod YAML for a container", + Description: containerKubeDescription, + Flags: sortFlags(containerKubeFlags), + Action: generateKubeYAMLCmd, + ArgsUsage: "CONTAINER-NAME", + UseShortOptionHandling: true, + OnUsageError: usageErrorHandler, + } +) + +// generateKubeYAMLCmdgenerates or replays kube +func generateKubeYAMLCmd(c *cli.Context) error { + var ( + container *libpod.Container + err error + output []byte + ) + + if rootless.IsRootless() { + return errors.Wrapf(libpod.ErrNotImplemented, "rootless users") + } + args := c.Args() + if len(args) > 1 || (len(args) < 1 && !c.Bool("latest")) { + return errors.Errorf("you must provide one container ID or name or --latest") + } + if c.Bool("service") { + return errors.Wrapf(libpod.ErrNotImplemented, "service generation") + } + + runtime, err := libpodruntime.GetRuntime(c) + if err != nil { + return errors.Wrapf(err, "could not get runtime") + } + defer runtime.Shutdown(false) + + // Get the container in question + if c.Bool("latest") { + container, err = runtime.GetLatestContainer() + } else { + container, err = runtime.LookupContainer(args[0]) + } + if err != nil { + return err + } + + if len(container.Dependencies()) > 0 { + return errors.Wrapf(libpod.ErrNotImplemented, "containers with dependencies") + } + + podYAML, err := container.InspectForKube() + if err != nil { + return err + } + + developmentComment := []byte("# Generation of Kubenetes YAML is still under development!\n") + logrus.Warn("This function is still under heavy development.") + // Marshall the results + b, err := yaml.Marshal(podYAML) + if err != nil { + return err + } + output = append(output, developmentComment...) + output = append(output, b...) + // Output the v1.Pod with the v1.Container + fmt.Println(string(output)) + + return nil +} diff --git a/cmd/podman/libpodruntime/runtime.go b/cmd/podman/libpodruntime/runtime.go index df422eb81..0dc6bcf18 100644 --- a/cmd/podman/libpodruntime/runtime.go +++ b/cmd/podman/libpodruntime/runtime.go @@ -5,43 +5,33 @@ import ( "github.com/containers/libpod/pkg/rootless" "github.com/containers/libpod/pkg/util" "github.com/containers/storage" + "github.com/pkg/errors" "github.com/urfave/cli" ) // GetRuntime generates a new libpod runtime configured by command line options func GetRuntime(c *cli.Context) (*libpod.Runtime, error) { - storageOpts, err := util.GetDefaultStoreOptions() - if err != nil { - return nil, err - } - return GetRuntimeWithStorageOpts(c, &storageOpts) -} - -// GetContainerRuntime generates a new libpod runtime configured by command line options for containers -func GetContainerRuntime(c *cli.Context) (*libpod.Runtime, error) { - mappings, err := util.ParseIDMapping(c.StringSlice("uidmap"), c.StringSlice("gidmap"), c.String("subuidmap"), c.String("subgidmap")) - if err != nil { - return nil, err - } - storageOpts, err := util.GetDefaultStoreOptions() - if err != nil { - return nil, err - } - storageOpts.UIDMap = mappings.UIDMap - storageOpts.GIDMap = mappings.GIDMap - return GetRuntimeWithStorageOpts(c, &storageOpts) -} - -// GetRuntime generates a new libpod runtime configured by command line options -func GetRuntimeWithStorageOpts(c *cli.Context, storageOpts *storage.StoreOptions) (*libpod.Runtime, error) { + storageOpts := new(storage.StoreOptions) options := []libpod.RuntimeOption{} + if c.IsSet("uidmap") || c.IsSet("gidmap") || c.IsSet("subuidmap") || c.IsSet("subgidmap") { + mappings, err := util.ParseIDMapping(c.StringSlice("uidmap"), c.StringSlice("gidmap"), c.String("subuidmap"), c.String("subgidmap")) + if err != nil { + return nil, err + } + storageOpts.UIDMap = mappings.UIDMap + storageOpts.GIDMap = mappings.GIDMap + } + if c.GlobalIsSet("root") { storageOpts.GraphRoot = c.GlobalString("root") } if c.GlobalIsSet("runroot") { storageOpts.RunRoot = c.GlobalString("runroot") } + if len(storageOpts.RunRoot) > 50 { + return nil, errors.New("the specified runroot is longer than 50 characters") + } if c.GlobalIsSet("storage-driver") { storageOpts.GraphDriverName = c.GlobalString("storage-driver") } @@ -86,8 +76,8 @@ func GetRuntimeWithStorageOpts(c *cli.Context, storageOpts *storage.StoreOptions if c.GlobalIsSet("default-mounts-file") { options = append(options, libpod.WithDefaultMountsFile(c.GlobalString("default-mounts-file"))) } - if c.GlobalIsSet("hooks-dir-path") { - options = append(options, libpod.WithHooksDir(c.GlobalString("hooks-dir-path"))) + if c.GlobalIsSet("hooks-dir") { + options = append(options, libpod.WithHooksDir(c.GlobalStringSlice("hooks-dir")...)) } // TODO flag to set CNI plugins dir? diff --git a/cmd/podman/logs.go b/cmd/podman/logs.go index 84aca5e61..75947c34e 100644 --- a/cmd/podman/logs.go +++ b/cmd/podman/logs.go @@ -40,14 +40,15 @@ var ( logsDescription = "The podman logs command batch-retrieves whatever logs are present for a container at the time of execution. This does not guarantee execution" + "order when combined with podman run (i.e. your run may not have generated any logs at the time you execute podman logs" logsCommand = cli.Command{ - Name: "logs", - Usage: "Fetch the logs of a container", - Description: logsDescription, - Flags: sortFlags(logsFlags), - Action: logsCmd, - ArgsUsage: "CONTAINER", - SkipArgReorder: true, - OnUsageError: usageErrorHandler, + Name: "logs", + Usage: "Fetch the logs of a container", + Description: logsDescription, + Flags: sortFlags(logsFlags), + Action: logsCmd, + ArgsUsage: "CONTAINER", + SkipArgReorder: true, + OnUsageError: usageErrorHandler, + UseShortOptionHandling: true, } ) diff --git a/cmd/podman/main.go b/cmd/podman/main.go index 38eac4504..bcae04575 100644 --- a/cmd/podman/main.go +++ b/cmd/podman/main.go @@ -8,7 +8,6 @@ import ( "syscall" "github.com/containers/libpod/libpod" - "github.com/containers/libpod/pkg/hooks" _ "github.com/containers/libpod/pkg/hooks/0.1.0" "github.com/containers/libpod/pkg/rootless" "github.com/containers/libpod/version" @@ -77,6 +76,7 @@ func main() { infoCommand, inspectCommand, killCommand, + kubeCommand, loadCommand, loginCommand, logoutCommand, @@ -205,11 +205,9 @@ func main() { Usage: "path to default mounts file", Hidden: true, }, - cli.StringFlag{ - Name: "hooks-dir-path", - Usage: "set the OCI hooks directory path", - Value: hooks.DefaultDir, - Hidden: true, + cli.StringSliceFlag{ + Name: "hooks-dir", + Usage: "set the OCI hooks directory path (may be set multiple times)", }, cli.IntFlag{ Name: "max-workers", diff --git a/cmd/podman/pause.go b/cmd/podman/pause.go index 203fa6070..fcb2f3cb8 100644 --- a/cmd/podman/pause.go +++ b/cmd/podman/pause.go @@ -1,15 +1,23 @@ package main import ( - "fmt" "os" "github.com/containers/libpod/cmd/podman/libpodruntime" + "github.com/containers/libpod/cmd/podman/shared" + "github.com/containers/libpod/libpod" "github.com/pkg/errors" + "github.com/sirupsen/logrus" "github.com/urfave/cli" ) var ( + pauseFlags = []cli.Flag{ + cli.BoolFlag{ + Name: "all, a", + Usage: "pause all running containers", + }, + } pauseDescription = ` podman pause @@ -19,6 +27,7 @@ var ( Name: "pause", Usage: "Pauses all the processes in one or more containers", Description: pauseDescription, + Flags: pauseFlags, Action: pauseCmd, ArgsUsage: "CONTAINER-NAME [CONTAINER-NAME ...]", OnUsageError: usageErrorHandler, @@ -26,6 +35,10 @@ var ( ) func pauseCmd(c *cli.Context) error { + var ( + pauseContainers []*libpod.Container + pauseFuncs []shared.ParallelWorkerInput + ) if os.Geteuid() != 0 { return errors.New("pause is not supported for rootless containers") } @@ -37,28 +50,44 @@ func pauseCmd(c *cli.Context) error { defer runtime.Shutdown(false) args := c.Args() - if len(args) < 1 { + if len(args) < 1 && !c.Bool("all") { return errors.Errorf("you must provide at least one container name or id") } - - var lastError error - for _, arg := range args { - ctr, err := runtime.LookupContainer(arg) + if c.Bool("all") { + containers, err := getAllOrLatestContainers(c, runtime, libpod.ContainerStateRunning, "running") if err != nil { - if lastError != nil { - fmt.Fprintln(os.Stderr, lastError) - } - lastError = errors.Wrapf(err, "error looking up container %q", arg) - continue + return err } - if err = ctr.Pause(); err != nil { - if lastError != nil { - fmt.Fprintln(os.Stderr, lastError) + pauseContainers = append(pauseContainers, containers...) + } else { + for _, arg := range args { + ctr, err := runtime.LookupContainer(arg) + if err != nil { + return err } - lastError = errors.Wrapf(err, "failed to pause container %v", ctr.ID()) - } else { - fmt.Println(ctr.ID()) + pauseContainers = append(pauseContainers, ctr) + } + } + + // Now assemble the slice of pauseFuncs + for _, ctr := range pauseContainers { + con := ctr + + f := func() error { + return con.Pause() } + pauseFuncs = append(pauseFuncs, shared.ParallelWorkerInput{ + ContainerID: con.ID(), + ParallelFunc: f, + }) } - return lastError + + maxWorkers := shared.Parallelize("pause") + if c.GlobalIsSet("max-workers") { + maxWorkers = c.GlobalInt("max-workers") + } + logrus.Debugf("Setting maximum workers to %d", maxWorkers) + + pauseErrors, errCount := shared.ParallelExecuteWorkerPool(maxWorkers, pauseFuncs) + return printParallelOutput(pauseErrors, errCount) } diff --git a/cmd/podman/pod.go b/cmd/podman/pod.go index 0c6ec5e8c..a30361134 100644 --- a/cmd/podman/pod.go +++ b/cmd/podman/pod.go @@ -11,6 +11,7 @@ Pods are a group of one or more containers sharing the same network, pid and ipc ` podSubCommands = []cli.Command{ podCreateCommand, + podExistsCommand, podInspectCommand, podKillCommand, podPauseCommand, diff --git a/cmd/podman/pod_create.go b/cmd/podman/pod_create.go index 63fa6b294..a3364ac4b 100644 --- a/cmd/podman/pod_create.go +++ b/cmd/podman/pod_create.go @@ -3,11 +3,15 @@ package main import ( "fmt" "os" + "strconv" "strings" "github.com/containers/libpod/cmd/podman/libpodruntime" "github.com/containers/libpod/cmd/podman/shared" "github.com/containers/libpod/libpod" + "github.com/containers/libpod/pkg/rootless" + "github.com/cri-o/ocicni/pkg/ocicni" + "github.com/docker/go-connections/nat" "github.com/pkg/errors" "github.com/sirupsen/logrus" "github.com/urfave/cli" @@ -58,6 +62,10 @@ var podCreateFlags = []cli.Flag{ Name: "pod-id-file", Usage: "Write the pod ID to the file", }, + cli.StringSliceFlag{ + Name: "publish, p", + Usage: "Publish a container's port, or a range of ports, to the host (default [])", + }, cli.StringFlag{ Name: "share", Usage: "A comma delimited list of kernel namespaces the pod will share", @@ -102,6 +110,16 @@ func podCreateCmd(c *cli.Context) error { defer podIdFile.Close() defer podIdFile.Sync() } + + if len(c.StringSlice("publish")) > 0 { + if !c.BoolT("infra") { + return errors.Errorf("you must have an infra container to publish port bindings to the host") + } + if rootless.IsRootless() { + return errors.Errorf("rootless networking does not allow port binding to the host") + } + } + if !c.BoolT("infra") && c.IsSet("share") && c.String("share") != "none" && c.String("share") != "" { return errors.Errorf("You cannot share kernel namespaces on the pod level without an infra container") } @@ -131,6 +149,14 @@ func podCreateCmd(c *cli.Context) error { options = append(options, nsOptions...) } + if len(c.StringSlice("publish")) > 0 { + portBindings, err := CreatePortBindings(c.StringSlice("publish")) + if err != nil { + return err + } + options = append(options, libpod.WithInfraContainerPorts(portBindings)) + + } // always have containers use pod cgroups // User Opt out is not yet supported options = append(options, libpod.WithPodCgroups()) @@ -152,3 +178,36 @@ func podCreateCmd(c *cli.Context) error { return nil } + +// CreatePortBindings iterates ports mappings and exposed ports into a format CNI understands +func CreatePortBindings(ports []string) ([]ocicni.PortMapping, error) { + var portBindings []ocicni.PortMapping + // The conversion from []string to natBindings is temporary while mheon reworks the port + // deduplication code. Eventually that step will not be required. + _, natBindings, err := nat.ParsePortSpecs(ports) + if err != nil { + return nil, err + } + for containerPb, hostPb := range natBindings { + 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) + pm.Protocol = containerPb.Proto() + portBindings = append(portBindings, pm) + } + } + return portBindings, nil +} diff --git a/cmd/podman/ps.go b/cmd/podman/ps.go index 83274c9a8..0b03388a2 100644 --- a/cmd/podman/ps.go +++ b/cmd/podman/ps.go @@ -184,7 +184,7 @@ var ( Usage: "Display the extended information", }, cli.BoolFlag{ - Name: "pod", + Name: "pod, p", Usage: "Print the ID and name of the pod the containers are associated with", }, cli.BoolFlag{ diff --git a/cmd/podman/restart.go b/cmd/podman/restart.go index 7b48ef24e..630493ef4 100644 --- a/cmd/podman/restart.go +++ b/cmd/podman/restart.go @@ -1,18 +1,24 @@ package main import ( - "context" - "fmt" - "os" - "github.com/containers/libpod/cmd/podman/libpodruntime" + "github.com/containers/libpod/cmd/podman/shared" "github.com/containers/libpod/libpod" "github.com/pkg/errors" + "github.com/sirupsen/logrus" "github.com/urfave/cli" ) var ( restartFlags = []cli.Flag{ + cli.BoolFlag{ + Name: "all, a", + Usage: "restart all non-running containers", + }, + cli.BoolFlag{ + Name: "running", + Usage: "restart only running containers when --all is used", + }, cli.UintFlag{ Name: "timeout, time, t", Usage: "Seconds to wait for stop before killing the container", @@ -35,11 +41,18 @@ var ( ) func restartCmd(c *cli.Context) error { + var ( + restartFuncs []shared.ParallelWorkerInput + containers []*libpod.Container + restartContainers []*libpod.Container + ) + args := c.Args() - if len(args) < 1 && !c.Bool("latest") { + runOnly := c.Bool("running") + all := c.Bool("all") + if len(args) < 1 && !c.Bool("latest") && !all { return errors.Wrapf(libpod.ErrInvalidArg, "you must provide at least one container name or ID") } - if err := validateFlags(c, restartFlags); err != nil { return err } @@ -50,8 +63,6 @@ func restartCmd(c *cli.Context) error { } defer runtime.Shutdown(false) - var lastError error - timeout := c.Uint("timeout") useTimeout := c.IsSet("timeout") @@ -59,39 +70,57 @@ func restartCmd(c *cli.Context) error { if c.Bool("latest") { lastCtr, err := runtime.GetLatestContainer() if err != nil { - lastError = errors.Wrapf(err, "unable to get latest container") - } else { - ctrTimeout := lastCtr.StopTimeout() - if useTimeout { - ctrTimeout = timeout - } - - lastError = lastCtr.RestartWithTimeout(context.TODO(), ctrTimeout) + return errors.Wrapf(err, "unable to get latest container") } - } - - for _, id := range args { - ctr, err := runtime.LookupContainer(id) + restartContainers = append(restartContainers, lastCtr) + } else if runOnly { + containers, err = getAllOrLatestContainers(c, runtime, libpod.ContainerStateRunning, "running") if err != nil { - if lastError != nil { - fmt.Fprintln(os.Stderr, lastError) + return err + } + restartContainers = append(restartContainers, containers...) + } else if all { + containers, err = runtime.GetAllContainers() + if err != nil { + return err + } + restartContainers = append(restartContainers, containers...) + } else { + for _, id := range args { + ctr, err := runtime.LookupContainer(id) + if err != nil { + return err } - lastError = errors.Wrapf(err, "unable to find container %s", id) - continue + restartContainers = append(restartContainers, ctr) } + } + // We now have a slice of all the containers to be restarted. Iterate them to + // create restart Funcs with a timeout as needed + for _, ctr := range restartContainers { + con := ctr ctrTimeout := ctr.StopTimeout() if useTimeout { ctrTimeout = timeout } - if err := ctr.RestartWithTimeout(context.TODO(), ctrTimeout); err != nil { - if lastError != nil { - fmt.Fprintln(os.Stderr, lastError) - } - lastError = errors.Wrapf(err, "error restarting container %s", ctr.ID()) + f := func() error { + return con.RestartWithTimeout(getContext(), ctrTimeout) } + + restartFuncs = append(restartFuncs, shared.ParallelWorkerInput{ + ContainerID: con.ID(), + ParallelFunc: f, + }) + } + + maxWorkers := shared.Parallelize("restart") + if c.GlobalIsSet("max-workers") { + maxWorkers = c.GlobalInt("max-workers") } - return lastError + logrus.Debugf("Setting maximum workers to %d", maxWorkers) + + restartErrors, errCount := shared.ParallelExecuteWorkerPool(maxWorkers, restartFuncs) + return printParallelOutput(restartErrors, errCount) } diff --git a/cmd/podman/restore.go b/cmd/podman/restore.go index 067a2b5d4..bc2a71ba0 100644 --- a/cmd/podman/restore.go +++ b/cmd/podman/restore.go @@ -27,6 +27,10 @@ var ( // dedicated state for container which are checkpointed. // TODO: add ContainerStateCheckpointed cli.BoolFlag{ + Name: "tcp-established", + Usage: "checkpoint a container with established TCP connections", + }, + cli.BoolFlag{ Name: "all, a", Usage: "restore all checkpointed containers", }, @@ -53,16 +57,19 @@ func restoreCmd(c *cli.Context) error { } defer runtime.Shutdown(false) - keep := c.Bool("keep") + options := libpod.ContainerCheckpointOptions{ + Keep: c.Bool("keep"), + TCPEstablished: c.Bool("tcp-established"), + } if err := checkAllAndLatest(c); err != nil { return err } - containers, lastError := getAllOrLatestContainers(c, runtime, libpod.ContainerStateRunning, "checkpointed") + containers, lastError := getAllOrLatestContainers(c, runtime, libpod.ContainerStateExited, "checkpointed") for _, ctr := range containers { - if err = ctr.Restore(context.TODO(), keep); err != nil { + if err = ctr.Restore(context.TODO(), options); err != nil { if lastError != nil { fmt.Fprintln(os.Stderr, lastError) } diff --git a/cmd/podman/rm.go b/cmd/podman/rm.go index 0fb5345ee..7c0569b78 100644 --- a/cmd/podman/rm.go +++ b/cmd/podman/rm.go @@ -4,7 +4,6 @@ import ( "fmt" "github.com/containers/libpod/cmd/podman/libpodruntime" "github.com/containers/libpod/cmd/podman/shared" - "github.com/containers/libpod/libpod" "github.com/pkg/errors" "github.com/sirupsen/logrus" "github.com/urfave/cli" @@ -46,9 +45,7 @@ Running containers will not be removed without the -f option. // saveCmd saves the image to either docker-archive or oci func rmCmd(c *cli.Context) error { var ( - delContainers []*libpod.Container - lastError error - deleteFuncs []shared.ParallelWorkerInput + deleteFuncs []shared.ParallelWorkerInput ) ctx := getContext() @@ -65,7 +62,13 @@ func rmCmd(c *cli.Context) error { return err } - delContainers, lastError = getAllOrLatestContainers(c, runtime, -1, "all") + delContainers, err := getAllOrLatestContainers(c, runtime, -1, "all") + if err != nil { + if len(delContainers) == 0 { + return err + } + fmt.Println(err.Error()) + } for _, container := range delContainers { con := container @@ -84,14 +87,7 @@ func rmCmd(c *cli.Context) error { } logrus.Debugf("Setting maximum workers to %d", maxWorkers) - deleteErrors := shared.ParallelExecuteWorkerPool(maxWorkers, deleteFuncs) - for cid, result := range deleteErrors { - if result != nil { - fmt.Println(result.Error()) - lastError = result - continue - } - fmt.Println(cid) - } - return lastError + // Run the parallel funcs + deleteErrors, errCount := shared.ParallelExecuteWorkerPool(maxWorkers, deleteFuncs) + return printParallelOutput(deleteErrors, errCount) } diff --git a/cmd/podman/rmi.go b/cmd/podman/rmi.go index c0a0d69df..0f4f8765b 100644 --- a/cmd/podman/rmi.go +++ b/cmd/podman/rmi.go @@ -91,8 +91,23 @@ func rmiCmd(c *cli.Context) error { if err != nil { return errors.Wrapf(err, "unable to query local images") } - for _, i := range imagesToDelete { - removeImage(i) + lastNumberofImages := 0 + for len(imagesToDelete) > 0 { + if lastNumberofImages == len(imagesToDelete) { + return errors.New("unable to delete all images; re-run the rmi command again.") + } + for _, i := range imagesToDelete { + isParent, err := i.IsParent() + if err != nil { + return err + } + if isParent { + continue + } + removeImage(i) + } + lastNumberofImages = len(imagesToDelete) + imagesToDelete, err = runtime.ImageRuntime().GetImages() } } else { // Create image.image objects for deletion from user input. diff --git a/cmd/podman/run.go b/cmd/podman/run.go index e4b25eaf4..a4b5c918e 100644 --- a/cmd/podman/run.go +++ b/cmd/podman/run.go @@ -44,7 +44,7 @@ func runCmd(c *cli.Context) error { rootless.SetSkipStorageSetup(true) } - runtime, err := libpodruntime.GetContainerRuntime(c) + runtime, err := libpodruntime.GetRuntime(c) if err != nil { return errors.Wrapf(err, "error creating libpod runtime") } @@ -96,8 +96,6 @@ func runCmd(c *cli.Context) error { inputStream = nil } - inputStream = nil - attachTo := c.StringSlice("attach") for _, stream := range attachTo { switch strings.ToLower(stream) { diff --git a/cmd/podman/runlabel.go b/cmd/podman/runlabel.go index e1dee1fb2..b0d87d0d9 100644 --- a/cmd/podman/runlabel.go +++ b/cmd/podman/runlabel.go @@ -6,11 +6,9 @@ import ( "os" "strings" - "github.com/containers/image/types" "github.com/containers/libpod/cmd/podman/libpodruntime" "github.com/containers/libpod/cmd/podman/shared" "github.com/containers/libpod/libpod/image" - "github.com/containers/libpod/pkg/util" "github.com/containers/libpod/utils" "github.com/pkg/errors" "github.com/sirupsen/logrus" @@ -94,7 +92,7 @@ func runlabelCmd(c *cli.Context) error { imageName string stdErr, stdOut io.Writer stdIn io.Reader - newImage *image.Image + extraArgs []string ) // Evil images could trick into recursively executing the runlabel @@ -124,6 +122,9 @@ func runlabelCmd(c *cli.Context) error { return errors.Errorf("the display and quiet flags cannot be used together.") } + if len(args) > 2 { + extraArgs = args[2:] + } pull := c.Bool("pull") label := args[0] @@ -151,75 +152,24 @@ func runlabelCmd(c *cli.Context) error { stdIn = nil } - if pull { - var registryCreds *types.DockerAuthConfig - if c.IsSet("creds") { - creds, err := util.ParseRegistryCreds(c.String("creds")) - if err != nil { - return err - } - registryCreds = creds - } - dockerRegistryOptions := image.DockerRegistryOptions{ - DockerRegistryCreds: registryCreds, - DockerCertPath: c.String("cert-dir"), - DockerInsecureSkipTLSVerify: !c.BoolT("tls-verify"), - } - authfile := getAuthFile(c.String("authfile")) - - newImage, err = runtime.ImageRuntime().New(ctx, runlabelImage, c.String("signature-policy"), authfile, stdOut, &dockerRegistryOptions, image.SigningOptions{}, false, false) - } else { - newImage, err = runtime.ImageRuntime().NewFromLocal(runlabelImage) - } - if err != nil { - return errors.Wrapf(err, "unable to find image") - } - - if len(newImage.Names()) < 1 { - imageName = newImage.ID() - } else { - imageName = newImage.Names()[0] + dockerRegistryOptions := image.DockerRegistryOptions{ + DockerCertPath: c.String("cert-dir"), + DockerInsecureSkipTLSVerify: !c.BoolT("tls-verify"), } - runLabel, err := newImage.GetLabel(ctx, label) + authfile := getAuthFile(c.String("authfile")) + runLabel, imageName, err := shared.GetRunlabel(label, runlabelImage, ctx, runtime, pull, c.String("creds"), dockerRegistryOptions, authfile, c.String("signature-policy"), stdOut) if err != nil { return err } - - // If no label to execute, we return if runLabel == "" { return nil } - // The user provided extra arguments that need to be tacked onto the label's command - if len(args) > 2 { - runLabel = fmt.Sprintf("%s %s", runLabel, strings.Join(args[2:], " ")) - } - - cmd, err := shared.GenerateCommand(runLabel, imageName, c.String("name")) + cmd, env, err := shared.GenerateRunlabelCommand(runLabel, imageName, c.String("name"), opts, extraArgs) if err != nil { - return errors.Wrapf(err, "unable to generate command") - } - env := shared.GenerateRunEnvironment(c.String("name"), imageName, opts) - env = append(env, "PODMAN_RUNLABEL_NESTED=1") - - envmap := envSliceToMap(env) - - envmapper := func(k string) string { - switch k { - case "OPT1": - return envmap["OPT1"] - case "OPT2": - return envmap["OPT2"] - case "OPT3": - return envmap["OPT3"] - } - return "" + return err } - - newS := os.Expand(strings.Join(cmd, " "), envmapper) - cmd = strings.Split(newS, " ") - if !c.Bool("quiet") { fmt.Printf("Command: %s\n", strings.Join(cmd, " ")) if c.Bool("display") { @@ -228,12 +178,3 @@ func runlabelCmd(c *cli.Context) error { } return utils.ExecCmdWithStdStreams(stdIn, stdOut, stdErr, env, cmd[0], cmd[1:]...) } - -func envSliceToMap(env []string) map[string]string { - m := make(map[string]string) - for _, i := range env { - split := strings.Split(i, "=") - m[split[0]] = strings.Join(split[1:], " ") - } - return m -} diff --git a/cmd/podman/shared/container.go b/cmd/podman/shared/container.go index 4404268d4..d0e892961 100644 --- a/cmd/podman/shared/container.go +++ b/cmd/podman/shared/container.go @@ -1,10 +1,15 @@ package shared import ( + "context" "encoding/json" "fmt" + "github.com/containers/image/types" + "github.com/containers/libpod/libpod/image" + "github.com/containers/libpod/pkg/util" "github.com/cri-o/ocicni/pkg/ocicni" "github.com/docker/go-units" + "io" "os" "path/filepath" "regexp" @@ -589,3 +594,79 @@ func portsToString(ports []ocicni.PortMapping) string { } return strings.Join(portDisplay, ", ") } + +// GetRunlabel is a helper function for runlabel; it gets the image if needed and begins the +// contruction of the runlabel output and environment variables +func GetRunlabel(label string, runlabelImage string, ctx context.Context, runtime *libpod.Runtime, pull bool, inputCreds string, dockerRegistryOptions image.DockerRegistryOptions, authfile string, signaturePolicyPath string, output io.Writer) (string, string, error) { + var ( + newImage *image.Image + err error + imageName string + ) + if pull { + var registryCreds *types.DockerAuthConfig + if inputCreds != "" { + creds, err := util.ParseRegistryCreds(inputCreds) + if err != nil { + return "", "", err + } + registryCreds = creds + } + dockerRegistryOptions.DockerRegistryCreds = registryCreds + newImage, err = runtime.ImageRuntime().New(ctx, runlabelImage, signaturePolicyPath, authfile, output, &dockerRegistryOptions, image.SigningOptions{}, false, false) + } else { + newImage, err = runtime.ImageRuntime().NewFromLocal(runlabelImage) + } + if err != nil { + return "", "", errors.Wrapf(err, "unable to find image") + } + + if len(newImage.Names()) < 1 { + imageName = newImage.ID() + } else { + imageName = newImage.Names()[0] + } + + runLabel, err := newImage.GetLabel(ctx, label) + return runLabel, imageName, err +} + +// GenerateRunlabelCommand generates the command that will eventually be execucted by podman +func GenerateRunlabelCommand(runLabel, imageName, name string, opts map[string]string, extraArgs []string) ([]string, []string, error) { + // The user provided extra arguments that need to be tacked onto the label's command + if len(extraArgs) > 0 { + runLabel = fmt.Sprintf("%s %s", runLabel, strings.Join(extraArgs, " ")) + } + cmd, err := GenerateCommand(runLabel, imageName, name) + if err != nil { + return nil, nil, errors.Wrapf(err, "unable to generate command") + } + env := GenerateRunEnvironment(name, imageName, opts) + env = append(env, "PODMAN_RUNLABEL_NESTED=1") + + envmap := envSliceToMap(env) + + envmapper := func(k string) string { + switch k { + case "OPT1": + return envmap["OPT1"] + case "OPT2": + return envmap["OPT2"] + case "OPT3": + return envmap["OPT3"] + } + return "" + } + newS := os.Expand(strings.Join(cmd, " "), envmapper) + cmd = strings.Split(newS, " ") + return cmd, env, nil +} + +func envSliceToMap(env []string) map[string]string { + m := make(map[string]string) + for _, i := range env { + split := strings.Split(i, "=") + m[split[0]] = strings.Join(split[1:], " ") + } + return m +} diff --git a/cmd/podman/shared/funcs.go b/cmd/podman/shared/funcs.go index a92e0d547..8520c0616 100644 --- a/cmd/podman/shared/funcs.go +++ b/cmd/podman/shared/funcs.go @@ -5,6 +5,8 @@ import ( "os" "path/filepath" "strings" + + "github.com/google/shlex" ) func substituteCommand(cmd string) (string, error) { @@ -42,7 +44,11 @@ func GenerateCommand(command, imageName, name string) ([]string, error) { if name == "" { name = imageName } - cmd := strings.Split(command, " ") + + cmd, err := shlex.Split(command) + if err != nil { + return nil, err + } prog, err := substituteCommand(cmd[0]) if err != nil { diff --git a/cmd/podman/shared/funcs_test.go b/cmd/podman/shared/funcs_test.go index 596df84e8..7506b9d9c 100644 --- a/cmd/podman/shared/funcs_test.go +++ b/cmd/podman/shared/funcs_test.go @@ -18,10 +18,11 @@ var ( ) func TestGenerateCommand(t *testing.T) { - inputCommand := "docker run -it --name NAME -e NAME=NAME -e IMAGE=IMAGE IMAGE echo install" - correctCommand := "/proc/self/exe run -it --name bar -e NAME=bar -e IMAGE=foo foo echo install" + inputCommand := "docker run -it --name NAME -e NAME=NAME -e IMAGE=IMAGE IMAGE echo \"hello world\"" + correctCommand := "/proc/self/exe run -it --name bar -e NAME=bar -e IMAGE=foo foo echo hello world" newCommand, err := GenerateCommand(inputCommand, "foo", "bar") assert.Nil(t, err) + assert.Equal(t, "hello world", newCommand[11]) assert.Equal(t, correctCommand, strings.Join(newCommand, " ")) } @@ -108,8 +109,8 @@ func TestGenerateCommandNoSetName(t *testing.T) { } func TestGenerateCommandNoName(t *testing.T) { - inputCommand := "docker run -it -e IMAGE=IMAGE IMAGE echo install" - correctCommand := "/proc/self/exe run -it -e IMAGE=foo foo echo install" + inputCommand := "docker run -it -e IMAGE=IMAGE IMAGE echo install" + correctCommand := "/proc/self/exe run -it -e IMAGE=foo foo echo install" newCommand, err := GenerateCommand(inputCommand, "foo", "") assert.Nil(t, err) assert.Equal(t, correctCommand, strings.Join(newCommand, " ")) diff --git a/cmd/podman/shared/parallel.go b/cmd/podman/shared/parallel.go index 03eba2f0b..e6ce50f95 100644 --- a/cmd/podman/shared/parallel.go +++ b/cmd/podman/shared/parallel.go @@ -30,9 +30,10 @@ func ParallelWorker(wg *sync.WaitGroup, jobs <-chan ParallelWorkerInput, results // ParallelExecuteWorkerPool takes container jobs and performs them in parallel. The worker // int determines how many workers/threads should be premade. -func ParallelExecuteWorkerPool(workers int, functions []ParallelWorkerInput) map[string]error { +func ParallelExecuteWorkerPool(workers int, functions []ParallelWorkerInput) (map[string]error, int) { var ( - wg sync.WaitGroup + wg sync.WaitGroup + errorCount int ) resultChan := make(chan containerError, len(functions)) @@ -62,9 +63,12 @@ func ParallelExecuteWorkerPool(workers int, functions []ParallelWorkerInput) map close(resultChan) for ctrError := range resultChan { results[ctrError.ContainerID] = ctrError.Err + if ctrError.Err != nil { + errorCount += 1 + } } - return results + return results, errorCount } // Parallelize provides the maximum number of parallel workers (int) as calculated by a basic @@ -72,20 +76,37 @@ func ParallelExecuteWorkerPool(workers int, functions []ParallelWorkerInput) map func Parallelize(job string) int { numCpus := runtime.NumCPU() switch job { + case "kill": + if numCpus <= 3 { + return numCpus * 3 + } + return numCpus * 4 + case "pause": + if numCpus <= 3 { + return numCpus * 3 + } + return numCpus * 4 + case "ps": + return 8 + case "restart": + return numCpus * 2 + case "rm": + if numCpus <= 3 { + return numCpus * 3 + } else { + return numCpus * 4 + } case "stop": if numCpus <= 2 { return 4 } else { return numCpus * 3 } - case "rm": + case "unpause": if numCpus <= 3 { return numCpus * 3 - } else { - return numCpus * 4 } - case "ps": - return 8 + return numCpus * 4 } return 3 } diff --git a/cmd/podman/stop.go b/cmd/podman/stop.go index afeb49f76..ade51705e 100644 --- a/cmd/podman/stop.go +++ b/cmd/podman/stop.go @@ -2,6 +2,7 @@ package main import ( "fmt" + "github.com/containers/libpod/cmd/podman/libpodruntime" "github.com/containers/libpod/cmd/podman/shared" "github.com/containers/libpod/libpod" @@ -59,7 +60,13 @@ func stopCmd(c *cli.Context) error { } defer runtime.Shutdown(false) - containers, lastError := getAllOrLatestContainers(c, runtime, libpod.ContainerStateRunning, "running") + containers, err := getAllOrLatestContainers(c, runtime, libpod.ContainerStateRunning, "running") + if err != nil { + if len(containers) == 0 { + return err + } + fmt.Println(err.Error()) + } var stopFuncs []shared.ParallelWorkerInput for _, ctr := range containers { @@ -71,7 +78,11 @@ func stopCmd(c *cli.Context) error { stopTimeout = ctr.StopTimeout() } f := func() error { - return con.StopWithTimeout(stopTimeout) + if err := con.StopWithTimeout(stopTimeout); err != nil && errors.Cause(err) != libpod.ErrCtrStopped { + return err + } + return nil + } stopFuncs = append(stopFuncs, shared.ParallelWorkerInput{ ContainerID: con.ID(), @@ -85,15 +96,6 @@ func stopCmd(c *cli.Context) error { } logrus.Debugf("Setting maximum workers to %d", maxWorkers) - stopErrors := shared.ParallelExecuteWorkerPool(maxWorkers, stopFuncs) - - for cid, result := range stopErrors { - if result != nil && result != libpod.ErrCtrStopped { - fmt.Println(result.Error()) - lastError = result - continue - } - fmt.Println(cid) - } - return lastError + stopErrors, errCount := shared.ParallelExecuteWorkerPool(maxWorkers, stopFuncs) + return printParallelOutput(stopErrors, errCount) } diff --git a/cmd/podman/unpause.go b/cmd/podman/unpause.go index a792aaf6d..d77e056f8 100644 --- a/cmd/podman/unpause.go +++ b/cmd/podman/unpause.go @@ -1,15 +1,23 @@ package main import ( - "fmt" "os" "github.com/containers/libpod/cmd/podman/libpodruntime" + "github.com/containers/libpod/cmd/podman/shared" + "github.com/containers/libpod/libpod" "github.com/pkg/errors" + "github.com/sirupsen/logrus" "github.com/urfave/cli" ) var ( + unpauseFlags = []cli.Flag{ + cli.BoolFlag{ + Name: "all, a", + Usage: "unpause all paused containers", + }, + } unpauseDescription = ` podman unpause @@ -19,6 +27,7 @@ var ( Name: "unpause", Usage: "Unpause the processes in one or more containers", Description: unpauseDescription, + Flags: unpauseFlags, Action: unpauseCmd, ArgsUsage: "CONTAINER-NAME [CONTAINER-NAME ...]", OnUsageError: usageErrorHandler, @@ -26,6 +35,10 @@ var ( ) func unpauseCmd(c *cli.Context) error { + var ( + unpauseContainers []*libpod.Container + unpauseFuncs []shared.ParallelWorkerInput + ) if os.Geteuid() != 0 { return errors.New("unpause is not supported for rootless containers") } @@ -37,28 +50,44 @@ func unpauseCmd(c *cli.Context) error { defer runtime.Shutdown(false) args := c.Args() - if len(args) < 1 { + if len(args) < 1 && !c.Bool("all") { return errors.Errorf("you must provide at least one container name or id") } - - var lastError error - for _, arg := range args { - ctr, err := runtime.LookupContainer(arg) + if c.Bool("all") { + cs, err := getAllOrLatestContainers(c, runtime, libpod.ContainerStatePaused, "paused") if err != nil { - if lastError != nil { - fmt.Fprintln(os.Stderr, lastError) - } - lastError = errors.Wrapf(err, "error looking up container %q", arg) - continue + return err } - if err = ctr.Unpause(); err != nil { - if lastError != nil { - fmt.Fprintln(os.Stderr, lastError) + unpauseContainers = append(unpauseContainers, cs...) + } else { + for _, arg := range args { + ctr, err := runtime.LookupContainer(arg) + if err != nil { + return err } - lastError = errors.Wrapf(err, "failed to unpause container %v", ctr.ID()) - } else { - fmt.Println(ctr.ID()) + unpauseContainers = append(unpauseContainers, ctr) + } + } + + // Assemble the unpause funcs + for _, ctr := range unpauseContainers { + con := ctr + f := func() error { + return con.Unpause() } + + unpauseFuncs = append(unpauseFuncs, shared.ParallelWorkerInput{ + ContainerID: con.ID(), + ParallelFunc: f, + }) } - return lastError + + maxWorkers := shared.Parallelize("unpause") + if c.GlobalIsSet("max-workers") { + maxWorkers = c.GlobalInt("max-workers") + } + logrus.Debugf("Setting maximum workers to %d", maxWorkers) + + unpauseErrors, errCount := shared.ParallelExecuteWorkerPool(maxWorkers, unpauseFuncs) + return printParallelOutput(unpauseErrors, errCount) } diff --git a/cmd/podman/utils.go b/cmd/podman/utils.go index afeccb668..5735156c2 100644 --- a/cmd/podman/utils.go +++ b/cmd/podman/utils.go @@ -207,3 +207,20 @@ func getPodsFromContext(c *cli.Context, r *libpod.Runtime) ([]*libpod.Pod, error } return pods, lastError } + +//printParallelOutput takes the map of parallel worker results and outputs them +// to stdout +func printParallelOutput(m map[string]error, errCount int) error { + var lastError error + for cid, result := range m { + if result != nil { + if errCount > 1 { + fmt.Println(result.Error()) + } + lastError = result + continue + } + fmt.Println(cid) + } + return lastError +} diff --git a/cmd/podman/varlink/io.podman.varlink b/cmd/podman/varlink/io.podman.varlink index 4a4a1854c..b081b60a3 100644 --- a/cmd/podman/varlink/io.podman.varlink +++ b/cmd/podman/varlink/io.podman.varlink @@ -371,6 +371,22 @@ type PodContainerErrorData ( reason: string ) +# Runlabel describes the required input for container runlabel +type Runlabel( + image: string, + authfile: string, + certDir: string, + creds: string, + display: bool, + name: string, + pull: bool, + signaturePolicyPath: string, + tlsVerify: bool, + label: string, + extraArgs: []string, + opts: [string]string +) + # Ping provides a response for developers to ensure their varlink setup is working. # #### Example # ~~~ @@ -804,6 +820,42 @@ method TopPod() -> (notimplemented: NotImplemented) # ~~~ method GetPodStats(name: string) -> (pod: string, containers: []ContainerStats) +# ImageExists talks a full or partial image ID or name and returns an int as to whether +# the image exists in local storage. An int result of 0 means the image does exist in +# local storage; whereas 1 indicates the image does not exists in local storage. +method ImageExists(name: string) -> (exists: int) + +# ContainerExists takes a full or partial container ID or name and returns an int as to +# whether the container exists in local storage. A result of 0 means the container does +# exists; whereas a result of 1 means it could not be found. +method ContainerExists(name: string) -> (exists: int) + +# ContainerCheckPoint performs a checkpopint on a container by its name or full/partial container +# ID. On successful checkpoint, the id of the checkpointed container is returned. +method ContainerCheckpoint(name: string, keep: bool, leaveRunning: bool, tcpEstablished: bool) -> (id: string) + +# ContainerRestore restores a container that has been checkpointed. The container to be restored can +# be identified by its name or full/partial container ID. A successful restore will result in the return +# of the container's ID. +method ContainerRestore(name: string, keep: bool, tcpEstablished: bool) -> (id: string) + +# ContainerRunlabel runs executes a command as described by a given container image label. +method ContainerRunlabel(runlabel: Runlabel) -> () + +# ListContainerMounts gathers all the mounted container mount points and returns them as an array +# of strings +method ListContainerMounts() -> (mounts: []string) + +# MountContainer mounts a container by name or full/partial ID. Upon a successful mount, the destination +# mount is returned as a string. +method MountContainer(name: string) -> (path: string) + +# UnmountContainer umounts a container by its name or full/partial container ID. +method UnmountContainer(name: string, force: bool) -> () + +# This function is not implemented yet. +method ListContainerPorts(name: string) -> (notimplemented: NotImplemented) + # ImageNotFound means the image could not be found by the provided name or ID in local storage. error ImageNotFound (name: string) diff --git a/cmd/podman/version.go b/cmd/podman/version.go index f896229c4..d81deb696 100644 --- a/cmd/podman/version.go +++ b/cmd/podman/version.go @@ -4,6 +4,7 @@ import ( "fmt" "time" + "github.com/containers/libpod/cmd/podman/formats" "github.com/containers/libpod/libpod" "github.com/pkg/errors" "github.com/urfave/cli" @@ -15,13 +16,26 @@ func versionCmd(c *cli.Context) error { if err != nil { errors.Wrapf(err, "unable to determine version") } + + versionOutputFormat := c.String("format") + if versionOutputFormat != "" { + var out formats.Writer + switch versionOutputFormat { + case formats.JSONString: + out = formats.JSONStruct{Output: output} + default: + out = formats.StdoutTemplate{Output: output, Template: versionOutputFormat} + } + formats.Writer(out).Out() + return nil + } fmt.Println("Version: ", output.Version) fmt.Println("Go Version: ", output.GoVersion) if output.GitCommit != "" { fmt.Println("Git Commit: ", output.GitCommit) } // Prints out the build time in readable format - if libpod.BuildInfo != "" { + if output.Built != 0 { fmt.Println("Built: ", time.Unix(output.Built, 0).Format(time.ANSIC)) } @@ -30,8 +44,17 @@ func versionCmd(c *cli.Context) error { } // Cli command to print out the full version of podman -var versionCommand = cli.Command{ - Name: "version", - Usage: "Display the PODMAN Version Information", - Action: versionCmd, -} +var ( + versionCommand = cli.Command{ + Name: "version", + Usage: "Display the Podman Version Information", + Action: versionCmd, + Flags: versionFlags, + } + versionFlags = []cli.Flag{ + cli.StringFlag{ + Name: "format", + Usage: "Change the output format to JSON or a Go template", + }, + } +) diff --git a/completions/bash/podman b/completions/bash/podman index 5cfed348f..9518cfa22 100644 --- a/completions/bash/podman +++ b/completions/bash/podman @@ -1,10 +1,11 @@ : ${PROG:=$(basename ${BASH_SOURCE})} + __podman_previous_extglob_setting=$(shopt -p extglob) shopt -s extglob __podman_q() { - podman ${host:+-H "$host"} ${config:+--config "$config"} 2>/dev/null "$@" + podman ${host:+-H "$host"} ${config:+--config "$config"} 2>/dev/null "$@" } # __podman_containers returns a list of containers. Additional options to @@ -231,7 +232,7 @@ __podman_services() { fields='$2' # names only shift fi - __podman_q service ls "$@" | awk "NR>1 {print $fields}" + __podman_q service ls "$@" | awk "NR>1 {print $fields}" } # __podman_complete_services applies completion of services based on the current @@ -693,20 +694,20 @@ _podman_attach() { --detach-keys " local boolean_options=" - --help - -h - --latest - -l - --no-stdin - --sig-proxy + --help + -h + --latest + -l + --no-stdin + --sig-proxy " case "$cur" in - -*) - COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur")) - ;; - *) - __podman_complete_containers_running - ;; + -*) + COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur")) + ;; + *) + __podman_complete_containers_running + ;; esac } @@ -715,20 +716,26 @@ _podman_container_attach() { } _podman_container_checkpoint() { - local options_with_args=" - --help -h - " local boolean_options=" - --keep + -a + --all + -h + --help -k + --keep + -l + --latest + -R + --leave-running + --tcp-established " case "$cur" in - -*) - COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur")) - ;; - *) - __podman_complete_containers_running - ;; + -*) + COMPREPLY=($(compgen -W "$boolean_options" -- "$cur")) + ;; + *) + __podman_complete_containers_running + ;; esac } @@ -784,6 +791,8 @@ _podman_container_refresh() { local options_with_args=" " local boolean_options=" + --help + -h " _complete_ "$options_with_args" "$boolean_options" } @@ -793,20 +802,24 @@ _podman_container_restart() { } _podman_container_restore() { - local options_with_args=" - --help -h - " local boolean_options=" - --keep - -k + -a + --all + -h + --help + -k + --keep + -l + --latest + --tcp-established " case "$cur" in - -*) - COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur")) - ;; - *) - __podman_complete_containers_created - ;; + -*) + COMPREPLY=($(compgen -W "$boolean_options" -- "$cur")) + ;; + *) + __podman_complete_containers_created + ;; esac } @@ -814,10 +827,6 @@ _podman_container_rm() { _podman_rm } -_podman_container_run() { - _podman_run -} - _podman_container_start() { _podman_start } @@ -920,29 +929,29 @@ _podman_commit() { _complete_ "$options_with_args" "$boolean_options" case "$cur" in - -*) - COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur")) - ;; - *) - __podman_complete_container_names - ;; + -*) + COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur")) + ;; + *) + __podman_complete_container_names + ;; esac } _podman_build() { local boolean_options=" - --force-rm - --help - -h - --layers - --no-cache - --pull - --pull-always - --quiet - -q - --rm - --squash - --tls-verify + --force-rm + --help + -h + --layers + --no-cache + --pull + --pull-always + --quiet + -q + --rm + --squash + --tls-verify " local options_with_args=" @@ -995,18 +1004,18 @@ _podman_build() { local all_options="$options_with_args $boolean_options" case "$prev" in - --runtime) - COMPREPLY=($(compgen -W 'runc runv' -- "$cur")) - ;; - $(__podman_to_extglob "$options_with_args")) + --runtime) + COMPREPLY=($(compgen -W 'runc runv' -- "$cur")) + ;; + $(__podman_to_extglob "$options_with_args")) return ;; esac case "$cur" in -*) - COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur")) - ;; + COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur")) + ;; esac } @@ -1015,16 +1024,18 @@ _podman_diff() { --format " local boolean_options=" - " + --help + -h + " _complete_ "$options_with_args" "$boolean_options" case "$cur" in - -*) - COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur")) - ;; - *) - __podman_complete_container_names - ;; + -*) + COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur")) + ;; + *) + __podman_complete_container_names + ;; esac } @@ -1036,19 +1047,21 @@ _podman_exec() { -u " local boolean_options=" - --latest - -l - --privileged - --tty - -t + --help + -h + --latest + -l + --privileged + --tty + -t " case "$cur" in - -*) - COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur")) - ;; - *) - __podman_complete_containers_running - ;; + -*) + COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur")) + ;; + *) + __podman_complete_containers_running + ;; esac } @@ -1058,14 +1071,16 @@ _podman_export() { -o " local boolean_options=" + --help + -h " case "$cur" in - -*) - COMPREPLY=($(compgen -W "$options_with_args" -- "$cur")) - ;; - *) - __podman_complete_container_names - ;; + -*) + COMPREPLY=($(compgen -W "$options_with_args" -- "$cur")) + ;; + *) + __podman_complete_container_names + ;; esac } @@ -1074,19 +1089,21 @@ _podman_history() { --format " local boolean_options=" - --human -H - --no-trunc - --quiet -q + --help + -h + --human -H + --no-trunc + --quiet -q " _complete_ "$options_with_args" "$boolean_options" case "$cur" in - -*) - COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur")) - ;; - *) - __podman_complete_images --id - ;; + -*) + COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur")) + ;; + *) + __podman_complete_images --id + ;; esac } @@ -1107,20 +1124,20 @@ _podman_import() { _complete_ "$options_with_args" "$boolean_options" case "$cur" in - -*) - COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur")) - ;; - *) - __podman_list_images - ;; + -*) + COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur")) + ;; + *) + __podman_list_images + ;; esac } _podman_info() { local boolean_options=" - --help - -h - --debug + --help + -h + --debug " local options_with_args=" --format @@ -1129,12 +1146,12 @@ _podman_info() { local all_options="$options_with_args $boolean_options" case "$cur" in - -*) - COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur")) - ;; - *) - __podman_list_images - ;; + -*) + COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur")) + ;; + *) + __podman_list_images + ;; esac } @@ -1190,7 +1207,7 @@ _podman_image() { local boolean_options=" --help -h - " + " subcommands=" build history @@ -1221,20 +1238,20 @@ _podman_image() { _podman_images() { local boolean_options=" - -a - --all - --digests - --digests - -f - --filter - -h - --help - --no-trunc - --notruncate - -n - --noheading - -q - --quiet + -a + --all + --digests + --digests + -f + --filter + -h + --help + --no-trunc + --notruncate + -n + --noheading + -q + --quiet " local options_with_args=" --format @@ -1244,22 +1261,22 @@ _podman_images() { local all_options="$options_with_args $boolean_options" case "$cur" in - -*) - COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur")) - ;; - *) - __podman_complete_images --id - ;; + -*) + COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur")) + ;; + *) + __podman_complete_images --id + ;; esac } _podman_inspect() { local boolean_options=" - --help - -h - --latest - -l - " + --help + -h + --latest + -l + " local options_with_args=" --format -f @@ -1324,20 +1341,20 @@ _podman_kill() { --signal -s " local boolean_options=" - --all - -a - --help - -h - --latest - -l + --all + -a + --help + -h + --latest + -l " case "$cur" in - -*) - COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur")) - ;; - *) - __podman_complete_containers_running - ;; + -*) + COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur")) + ;; + *) + __podman_complete_containers_running + ;; esac } @@ -1347,22 +1364,24 @@ _podman_logs() { --tail " local boolean_options=" - --follow - -f - --latest - -l - --timestamps - -t + --follow + -f + --help + -h + --latest + -l + --timestamps + -t " _complete_ "$options_with_args" "$boolean_options" case "$cur" in - -*) - COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur")) - ;; - *) - __podman_list_containers - ;; + -*) + COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur")) + ;; + *) + __podman_list_containers + ;; esac } @@ -1374,10 +1393,13 @@ _podman_pull() { --signature-policy " local boolean_options=" - --all-tags -a - --quiet - -q - --tls-verify + --all-tags + -a + --help + -h + --quiet + -q + --tls-verify " _complete_ "$options_with_args" "$boolean_options" } @@ -1390,7 +1412,9 @@ _podman_search() { --limit " local boolean_options=" - --no-trunc + --help + -h + --no-trunc " _complete_ "$options_with_args" "$boolean_options" } @@ -1401,56 +1425,58 @@ _podman_unmount() { _podman_umount() { local boolean_options=" - --all - -a - --force - -f - --help - -h - " + --all + -a + --help + -h + --force + -f + " local options_with_args=" - " + " local all_options="$options_with_args $boolean_options" case "$cur" in - -*) - COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur")) - ;; - *) - __podman_complete_container_names - ;; + -*) + COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur")) + ;; + *) + __podman_complete_container_names + ;; esac } _podman_mount() { local boolean_options=" - --help - -h - --notruncate - " + --help + -h + --notruncate + " local options_with_args=" - --format - " + --format + " local all_options="$options_with_args $boolean_options" case "$cur" in - -*) - COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur")) - ;; - *) - __podman_complete_container_names - ;; + -*) + COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur")) + ;; + *) + __podman_complete_container_names + ;; esac } _podman_push() { local boolean_options=" - --compress - --quiet - -q - --remove-signatures - --tls-verify + --compress + --help + -h + --quiet + -q + --remove-signatures + --tls-verify " local options_with_args=" @@ -1465,12 +1491,12 @@ _podman_push() { local all_options="$options_with_args $boolean_options" case "$cur" in - -*) - COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur")) - ;; - *) - __podman_complete_images --id - ;; + -*) + COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur")) + ;; + *) + __podman_complete_images --id + ;; esac } @@ -1552,16 +1578,17 @@ _podman_container_run() { " local boolean_options=" - --disable-content-trust=false - --help - --init - --interactive -i - --oom-kill-disable - --privileged - --publish-all -P - --quiet - --read-only - --tty -t + --disable-content-trust=false + --help + -h + --init + --interactive -i + --oom-kill-disable + --privileged + --publish-all -P + --quiet + --read-only + --tty -t " if [ "$command" = "run" -o "$subcommand" = "run" ] ; then @@ -1582,12 +1609,13 @@ _podman_container_run() { fi case "$cur" in - -*) - COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur")) - ;; - *) - __podman_complete_images --id - ;; + -*) + COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur")) + return + ;; + *) + __podman_complete_images --id + ;; esac @@ -1770,28 +1798,38 @@ _podman_restart() { --timeout -t " local boolean_options=" - --latest - -l" + --all + -a + --help + -h + --latest + -l + --running + --timeout + -t + " case "$cur" in - -*) - COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur")) - ;; - *) - __podman_complete_containers_running - ;; + -*) + COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur")) + ;; + *) + __podman_complete_containers_running + ;; esac } _podman_rm() { local boolean_options=" - --all - -a - --force - -f - --latest - -l - --volumes - -v + --all + -a + --force + -f + --help + -h + --latest + -l + --volumes + -v " local options_with_args=" @@ -1800,52 +1838,53 @@ _podman_rm() { local all_options="$options_with_args $boolean_options" case "$cur" in - -*) - COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur")) - ;; - *) - __podman_complete_container_names - ;; + -*) + COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur")) + ;; + *) + __podman_complete_container_names + ;; esac } _podman_rmi() { local boolean_options=" - --help - -h - --force - -f - -a - --all + --all + -a + --force + -f + --help + -h " case "$cur" in - -*) - COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur")) - ;; - *) - __podman_complete_images --id - ;; + -*) + COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur")) + ;; + *) + __podman_complete_images --id + ;; esac } _podman_stats() { local boolean_options=" - --help - --all - -a - --no-stream - --format - --no-reset + --all + -a + --help + -h + --no-stream + --format + --no-reset " case "$cur" in - -*) - COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur")) - ;; - *) - __podman_complete_containers_running - ;; + -*) + COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur")) + ;; + *) + __podman_complete_containers_running + ;; esac } @@ -1853,58 +1892,65 @@ _podman_tag() { local options_with_args=" " local boolean_options=" + --help + -h " case "$cur" in - -*) - COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur")) - ;; - *) - __podman_complete_images - ;; + -*) + COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur")) + ;; + *) + __podman_complete_images + ;; esac } __podman_top_descriptors() { - podman top --list-descriptors + podman top --list-descriptors } __podman_complete_top_descriptors() { - COMPREPLY=($(compgen -W "$(__podman_top_descriptors)" -- "$cur")) + COMPREPLY=($(compgen -W "$(__podman_top_descriptors)" -- "$cur")) } _podman_top() { local options_with_args=" " local boolean_options=" - --help - -h - --latest - -l + --help + -h + --latest + -l " # podman-top works on only *one* container, which means that when we have # three or more arguments, we can complete with top descriptors. if [[ "${COMP_CWORD}" -ge 3 ]]; then - __podman_complete_top_descriptors - return + __podman_complete_top_descriptors + return fi case "$cur" in - -*) - COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur")) - ;; - *) - __podman_complete_containers_running - ;; + -*) + COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur")) + ;; + *) + __podman_complete_containers_running + ;; esac } _podman_version() { - local options_with_args=" - " - local boolean_options=" - " - _complete_ "$options_with_args" "$boolean_options" + local boolean_options=" + --help + -h + " + local options_with_args=" + --format + " + local all_options="$options_with_args $boolean_options" + + _complete_ "$options_with_args" "$boolean_options" } _podman_save() { @@ -1913,52 +1959,61 @@ _podman_save() { --format " local boolean_options=" - --compress - -q - --quiet + --compress + --help + -h + -q + --quiet " case "$cur" in - -*) - COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur")) - ;; - *) - __podman_complete_images --id - ;; + -*) + COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur")) + ;; + *) + __podman_complete_images --id + ;; esac } _podman_pause() { + local boolean_options=" + --all + -a + --help + -h + " local options_with_args=" - --help -h " local boolean_options="" case "$cur" in - -*) - COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur")) - ;; - *) - __podman_complete_containers_running - ;; + -*) + COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur")) + ;; + *) + __podman_complete_containers_running + ;; esac } _podman_port() { local options_with_args=" - --help -h " local boolean_options=" - --all - -a - -l - --latest" + --all + -a + --help + -h + -l + --latest + " case "$cur" in - -*) - COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur")) - ;; - *) - __podman_complete_container_names - ;; + -*) + COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur")) + ;; + *) + __podman_complete_container_names + ;; esac } @@ -1974,13 +2029,14 @@ _podman_ps() { --sort " local boolean_options=" - --all -a - --latest -l - --no-trunc - --pod - --quiet -q - --size -s - --namespace --ns + --all -a + --help -h + --latest -l + --no-trunc + --pod -p + --quiet -q + --size -s + --namespace --ns " _complete_ "$options_with_args" "$boolean_options" } @@ -1991,23 +2047,23 @@ _podman_start() { " local boolean_options=" - -h - --help - -a - --attach - -i - --interactive - --latest - -l - --sig-proxy + --attach + -a + -h + --help + -i + --interactive + --latest + -l + --sig-proxy " case "$cur" in - -*) - COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur")) - ;; - *) - __podman_complete_container_names - ;; + -*) + COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur")) + ;; + *) + __podman_complete_container_names + ;; esac } _podman_stop() { @@ -2015,60 +2071,70 @@ _podman_stop() { --timeout -t " local boolean_options=" - --all - -a - --latest - -l" + --all + -a + -h + --help + --latest + -l + " case "$cur" in - -*) - COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur")) - ;; - *) - __podman_complete_containers_running - ;; + -*) + COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur")) + ;; + *) + __podman_complete_containers_running + ;; esac } _podman_unpause() { + local boolean_options=" + --all + -a + --help + -h + " local options_with_args=" - --help -h " - local boolean_options="" case "$cur" in - -*) - COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur")) - ;; - *) - __podman_complete_containers_unpauseable - ;; + -*) + COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur")) + ;; + *) + __podman_complete_containers_unpauseable + ;; esac } _podman_varlink() { local options_with_args=" - --help -h --timeout -t " - local boolean_options="" + local boolean_options=" + --help + -h + " _complete_ "$options_with_args" "$boolean_options" } _podman_wait() { local options_with_args="" local boolean_options=" - --help - -h - -i + --help + -h + -i -l - --interval - --latest" + --interval + --latest + " case "$cur" in - -*) - COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur")) - ;; - *) - __podman_complete_container_names - ;; + -*) + COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur")) + ;; + *) + __podman_complete_container_names + ;; esac } @@ -2095,8 +2161,10 @@ _podman_load() { --signature-policy " local boolean_options=" - --quiet - -q + --help + -h + --quiet + -q " _complete_ "$options_with_args" "$boolean_options" } @@ -2110,8 +2178,8 @@ _podman_login() { --authfile " local boolean_options=" - --help - -h + --help + -h " _complete_ "$options_with_args" "$boolean_options" } @@ -2121,10 +2189,10 @@ _podman_logout() { --authfile " local boolean_options=" - --all - -a - --help - -h + --all + -a + --help + -h " _complete_ "$options_with_args" "$boolean_options" } @@ -2139,41 +2207,69 @@ _podman_container_runlabel() { " local boolean_options=" - --display - --help - -h - -p - --pull - -q - --quiet - --tls-verify + --display + --help + -h + -p + --pull + -q + --quiet + --tls-verify " case "$cur" in - -*) - COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur")) - ;; - *) - __podman_complete_images --id - ;; + -*) + COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur")) + ;; + *) + __podman_complete_images --id + ;; esac } +_podman_container_exists() { + local options_with_args=" + " + + local boolean_options=" + " +} + +_podman_pod_exists() { + local options_with_args=" + " + + local boolean_options=" + " +} + +_podman_image_exists() { + local options_with_args=" + " + + local boolean_options=" + " +} + _podman_pod_create() { local options_with_args=" --cgroup-parent --infra-command --infra-image - --share - --podidfile --label-file --label -l --name + --podidfile + --publish + -p + --share " local boolean_options=" - --infra + --help + -h + --infra " _complete_ "$options_with_args" "$boolean_options" } @@ -2183,21 +2279,23 @@ _podman_pod_kill() { " local boolean_options=" - --all - -a - --signal - -s - --latest - -l + --all + -a + --help + -h + --signal + -s + --latest + -l " _complete_ "$options_with_args" "$boolean_options" case "$cur" in - -*) - COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur")) - ;; - *) - __podman_complete_pod_names - ;; + -*) + COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur")) + ;; + *) + __podman_complete_pod_names + ;; esac } @@ -2210,16 +2308,18 @@ __podman_pod_ps() { " local boolean_options=" - --cgroup - --ctr-ids - --ctr-names - --ctr-status - -q - --quiet - --no-trunc - --labels - -l - --latest + --cgroup + --ctr-ids + --ctr-names + --ctr-status + --help + -h + -q + --quiet + --no-trunc + --labels + -l + --latest " _complete_ "$options_with_args" "$boolean_options" } @@ -2241,19 +2341,21 @@ _podman_pod_restart() { " local boolean_options=" - --all - -a - --latest - -l + --all + -a + --help + -h + --latest + -l " _complete_ "$options_with_args" "$boolean_options" case "$cur" in - -*) - COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur")) - ;; - *) - __podman_complete_pod_names - ;; + -*) + COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur")) + ;; + *) + __podman_complete_pod_names + ;; esac } @@ -2262,21 +2364,23 @@ _podman_pod_rm() { " local boolean_options=" - -a - --all - -f - --force - --latest - -l + -a + --all + --help + -h + -f + --force + --latest + -l " _complete_ "$options_with_args" "$boolean_options" case "$cur" in - -*) - COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur")) - ;; - *) - __podman_complete_pod_names - ;; + -*) + COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur")) + ;; + *) + __podman_complete_pod_names + ;; esac } @@ -2285,19 +2389,21 @@ _podman_pod_start() { " local boolean_options=" - --all - -a - --latest - -l + --all + -a + --help + -h + --latest + -l " _complete_ "$options_with_args" "$boolean_options" case "$cur" in - -*) - COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur")) - ;; - *) - __podman_complete_pod_names - ;; + -*) + COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur")) + ;; + *) + __podman_complete_pod_names + ;; esac } @@ -2306,20 +2412,22 @@ _podman_pod_stop() { " local boolean_options=" - --all - -a - --cleanup - --latest - -l + --all + -a + --cleanup + --help + -h + --latest + -l " _complete_ "$options_with_args" "$boolean_options" case "$cur" in - -*) - COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur")) - ;; - *) - __podman_complete_pod_names - ;; + -*) + COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur")) + ;; + *) + __podman_complete_pod_names + ;; esac } @@ -2328,19 +2436,21 @@ _podman_pod_pause() { " local boolean_options=" - --all - -a - --latest - -l + --all + -a + --help + -h + --latest + -l " _complete_ "$options_with_args" "$boolean_options" case "$cur" in - -*) - COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur")) - ;; - *) - __podman_complete_pod_names - ;; + -*) + COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur")) + ;; + *) + __podman_complete_pod_names + ;; esac } @@ -2349,26 +2459,28 @@ _podman_pod_unpause() { " local boolean_options=" - --all - -a - --latest - -l + --all + -a + --help + -h + --latest + -l " _complete_ "$options_with_args" "$boolean_options" case "$cur" in - -*) - COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur")) - ;; - *) - __podman_complete_pod_names - ;; + -*) + COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur")) + ;; + *) + __podman_complete_pod_names + ;; esac } _podman_pod() { local boolean_options=" - --help - -h + --help + -h " subcommands=" create @@ -2391,29 +2503,31 @@ _podman_pod() { case "$cur" in -*) - COMPREPLY=( $( compgen -W "--help" -- "$cur" ) ) - ;; + COMPREPLY=( $( compgen -W "--help" -- "$cur" ) ) + ;; *) - COMPREPLY=( $( compgen -W "$subcommands" -- "$cur" ) ) - ;; + COMPREPLY=( $( compgen -W "$subcommands" -- "$cur" ) ) + ;; esac } _podman_podman() { local options_with_args=" - --config -c - --cpu-profile - --root - --runroot - --storage-driver - --storage-opt - --log-level - --namespace + --config -c + --cpu-profile + --root + --runroot + --storage-driver + --storage-opt + --log-level + --namespace " local boolean_options=" - --help -h - --version -v - --syslog + --help + -h + --version + -v + --syslog " commands=" attach diff --git a/contrib/cirrus/README.md b/contrib/cirrus/README.md new file mode 100644 index 000000000..fa233a2cb --- /dev/null +++ b/contrib/cirrus/README.md @@ -0,0 +1,82 @@ +![PODMAN logo](../../logo/podman-logo-source.svg) + +# Cirrus-CI + +Similar to other integrated github CI/CD services, Cirrus utilizes a simple +YAML-based configuration/description file: ``.cirrus.yml``. Ref: https://cirrus-ci.org/ + +## Workflow + +All tasks execute in parallel, unless there are conditions or dependencies +which alter this behavior. Within each task, each script executes in sequence, +so long as any previous script exited successfully. The overall state of each +task (pass or fail) is set based on the exit status of the last script to execute. + +### ``full_vm_testing`` Task + +1. Unconditionally, spin up one VM per ``matrix: image_name`` item defined + in ``.cirrus.yml``. Once accessible, ``ssh`` into each VM and run the following + scripts. + +2. ``setup_environment.sh``: Configure root's ``.bash_profile`` + for all subsequent scripts (each run in a new shell). Any + distribution-specific environment variables are also defined + here. For example, setting tags/flags to use compiling. + +3. ``verify_source.sh``: Perform per-distribution source + verification, lint-checking, etc. This acts as a minimal + gate, blocking extended use of VMs when a PR's code or commits + would otherwise not be accepted. Should run for less than a minute. + +4. ``unit_test.sh``: Execute unit-testing, as defined by the ``Makefile``. + This should execute within 10-minutes, but often much faster. + +5. ``integration_test.sh``: Execute integration-testing. This is + much more involved, and relies on access to external + resources like container images and code from other repositories. + Total execution time is capped at 2-hours (includes all the above) + but this script normally completes in less than an hour. + +### ``optional_system_testing`` Task + +1. Optionally executes in parallel with ``full_vm_testing``. Requires + **prior** to job-start, the magic string ``***CIRRUS: SYSTEM TEST***`` + is found in the pull-request *description*. The *description* is the first + text-box under the main *summary* line in the github WebUI. + +2. ``setup_environment.sh``: Same as for other tasks. + +3. ``system_test.sh``: Build both dependencies and libpod, install them, + then execute `make localsystem` from the repository root. + +### ``build_vm_images`` Task + +1. When a PR is merged (``$CIRRUS_BRANCH`` == ``master``), Cirrus + checks the last commit message. If it contains the magic string + ``***CIRRUS: REBUILD IMAGES***``, then this task continues. + +2. Execute run another round of the ``full_vm_testing`` task (above). + After the tests pass (post-merge), spin up a special VM + (from the `image-builder-image`) capable of communicating with the + GCE API. Once accessible, ``ssh`` into the VM and run the following scripts. + +3. ``setup_environment.sh``: Same as for other tasks. + +4. ``build_vm_images.sh``: Utilize [the packer tool](http://packer.io/docs/) + to produce new VM images. Create a new VM from each base-image, connect + to them with ``ssh``, and perform the steps as defined by the + ``$PACKER_BASE/libpod_images.json`` file: + + 1. On a base-image VM, as root, copy the current state of the repository + into ``/tmp/libpod``. + 2. Execute distribution-specific scripts to prepare the image for + use by the ``full_vm_testing`` task (above). These scripts all + end with the suffix `_setup.sh` within the `$PACKER_BASE` directory. + 3. If successful, shut down each VM and create a new GCE Image + named after the base image and the commit sha of the merge. + +***Note:*** The ``.cirrus.yml`` file must be manually updated with the new +images names, then the change sent in via a secondary pull-request. This +ensures that all the ``full_vm_testing`` tasks can pass with the new images, +before subjecting all future PRs to them. A workflow to automate this +process is described in comments at the end of the ``.cirrus.yml`` file. diff --git a/contrib/cirrus/build_vm_images.sh b/contrib/cirrus/build_vm_images.sh index 80c689a6c..c8ff55445 100755 --- a/contrib/cirrus/build_vm_images.sh +++ b/contrib/cirrus/build_vm_images.sh @@ -22,10 +22,6 @@ SCRIPT_BASE $SCRIPT_BASE PACKER_BASE $PACKER_BASE " -# TODO: Skip building images if $CIRRUS_BRANCH =~ "master" and -# commit message of $CIRRUS_CHANGE_IN_REPO contains a magic word -# produced by 'commit_and_create_upstream_pr.sh' script (see .cirrus.yml) - show_env_vars # Everything here is running on the 'image-builder-image' GCE image diff --git a/contrib/cirrus/lib.sh b/contrib/cirrus/lib.sh index 1e0052a65..04314e5fe 100644 --- a/contrib/cirrus/lib.sh +++ b/contrib/cirrus/lib.sh @@ -106,7 +106,10 @@ ircmsg() { SCRIPT="$GOSRC/$SCRIPT_BASE/podbot.py" NICK="podbot_$CIRRUS_TASK_ID" NICK="${NICK:0:15}" # Any longer will break things + set +e $SCRIPT $NICK $1 + echo "Ignoring exit($?)" + set -e } # Run sudo in directory with GOPATH set @@ -117,7 +120,6 @@ cdsudo() { sudo --preserve-env=GOPATH --non-interactive bash -c "$CMD" } - # Helper/wrapper script to only show stderr/stdout on non-zero exit install_ooe() { req_env_var "SCRIPT_BASE $SCRIPT_BASE" @@ -142,8 +144,8 @@ EOF install_cni_plugins() { echo "Installing CNI Plugins from commit $CNI_COMMIT" req_env_var " - GOPATH $GOPATH - CNI_COMMIT $CNI_COMMIT + GOPATH $GOPATH + CNI_COMMIT $CNI_COMMIT " DEST="$GOPATH/src/github.com/containernetworking/plugins" rm -rf "$DEST" @@ -155,14 +157,27 @@ install_cni_plugins() { sudo cp bin/* /usr/libexec/cni } +install_runc_from_git(){ + wd=$(pwd) + DEST="$GOPATH/src/github.com/opencontainers/runc" + rm -rf "$DEST" + ooe.sh git clone https://github.com/opencontainers/runc.git "$DEST" + cd "$DEST" + ooe.sh git fetch origin --tags + ooe.sh git checkout -q "$RUNC_COMMIT" + ooe.sh make static BUILDTAGS="seccomp selinux" + sudo install -m 755 runc /usr/bin/runc + cd $wd +} + install_runc(){ OS_RELEASE_ID=$(os_release_id) echo "Installing RunC from commit $RUNC_COMMIT" echo "Platform is $OS_RELEASE_ID" req_env_var " - GOPATH $GOPATH - RUNC_COMMIT $RUNC_COMMIT - OS_RELEASE_ID $OS_RELEASE_ID + GOPATH $GOPATH + RUNC_COMMIT $RUNC_COMMIT + OS_RELEASE_ID $OS_RELEASE_ID " if [[ "$OS_RELEASE_ID" =~ "ubuntu" ]]; then echo "Running make install.libseccomp.sudo for ubuntu" @@ -177,14 +192,7 @@ install_runc(){ cd "$GOPATH/src/github.com/containers/libpod" ooe.sh sudo make install.libseccomp.sudo fi - DEST="$GOPATH/src/github.com/opencontainers/runc" - rm -rf "$DEST" - ooe.sh git clone https://github.com/opencontainers/runc.git "$DEST" - cd "$DEST" - ooe.sh git fetch origin --tags - ooe.sh git checkout -q "$RUNC_COMMIT" - ooe.sh make static BUILDTAGS="seccomp selinux" - sudo install -m 755 runc /usr/bin/runc + install_runc_from_git } install_buildah() { @@ -202,8 +210,8 @@ install_buildah() { install_conmon(){ echo "Installing conmon from commit $CRIO_COMMIT" req_env_var " - GOPATH $GOPATH - CRIO_COMMIT $CRIO_COMMIT + GOPATH $GOPATH + CRIO_COMMIT $CRIO_COMMIT " DEST="$GOPATH/src/github.com/kubernetes-sigs/cri-o.git" rm -rf "$DEST" @@ -234,8 +242,8 @@ install_criu(){ install_testing_dependencies() { echo "Installing ginkgo, gomega, and easyjson into \$GOPATH=$GOPATH" req_env_var " - GOPATH $GOPATH - GOSRC $GOSRC + GOPATH $GOPATH + GOSRC $GOSRC " cd "$GOSRC" ooe.sh go get -u github.com/onsi/ginkgo/ginkgo @@ -263,7 +271,7 @@ install_varlink(){ _finalize(){ echo "Removing leftover giblets from cloud-init" cd / - sudo rm -rf /var/lib/cloud + sudo rm -rf /var/lib/cloud/instance? sudo rm -rf /root/.ssh/* sudo rm -rf /home/* } diff --git a/contrib/cirrus/packer/fedora_setup.sh b/contrib/cirrus/packer/fedora_setup.sh index 16b6e4e6b..f9fea04a7 100644 --- a/contrib/cirrus/packer/fedora_setup.sh +++ b/contrib/cirrus/packer/fedora_setup.sh @@ -21,8 +21,7 @@ install_ooe export GOPATH="$(mktemp -d)" trap "sudo rm -rf $GOPATH" EXIT -# breaks networking on f28/29 in GCE -# ooe.sh sudo dnf update -y +ooe.sh sudo dnf update -y ooe.sh sudo dnf install -y \ atomic-registries \ diff --git a/contrib/cirrus/packer/ubuntu_setup.sh b/contrib/cirrus/packer/ubuntu_setup.sh index ff20944dc..ef209a4a4 100644 --- a/contrib/cirrus/packer/ubuntu_setup.sh +++ b/contrib/cirrus/packer/ubuntu_setup.sh @@ -21,10 +21,13 @@ install_ooe export GOPATH="$(mktemp -d)" trap "sudo rm -rf $GOPATH" EXIT -ooe.sh sudo apt-get -qq update -ooe.sh sudo apt-get -qq update # sometimes it needs to get it twice :S -ooe.sh sudo apt-get -qq upgrade -ooe.sh sudo apt-get -qq install --no-install-recommends \ +export DEBIAN_FRONTEND=noninteractive + +# Try twice as workaround for minor networking problems +echo "Updating system and installing package dependencies" +ooe.sh sudo -E apt-get -qq update || sudo -E apt-get -qq update +ooe.sh sudo -E apt-get -qq upgrade || sudo -E apt-get -qq upgrade +ooe.sh sudo -E apt-get -qq install --no-install-recommends \ apparmor \ autoconf \ automake \ diff --git a/contrib/cirrus/setup_environment.sh b/contrib/cirrus/setup_environment.sh index 167db127f..2563b5f43 100755 --- a/contrib/cirrus/setup_environment.sh +++ b/contrib/cirrus/setup_environment.sh @@ -53,6 +53,8 @@ then # Some setup needs to vary between distros case "${OS_RELEASE_ID}-${OS_RELEASE_VER}" in ubuntu-18) + # Always install runc on Ubuntu + install_runc_from_git envstr='export BUILDTAGS="seccomp $($GOSRC/hack/btrfs_tag.sh) $($GOSRC/hack/btrfs_installed_tag.sh) $($GOSRC/hack/ostree_tag.sh) varlink exclude_graphdriver_devicemapper"' ;; fedora-28) ;& # Continue to the next item diff --git a/contrib/cirrus/system_test.sh b/contrib/cirrus/system_test.sh new file mode 100755 index 000000000..7c727d336 --- /dev/null +++ b/contrib/cirrus/system_test.sh @@ -0,0 +1,33 @@ +#!/bin/bash + +set -e +source $(dirname $0)/lib.sh + +req_env_var " +GOSRC $GOSRC +OS_RELEASE_ID $OS_RELEASE_ID +OS_RELEASE_VER $OS_RELEASE_VER +" + +show_env_vars + +set -x +cd "$GOSRC" + +case "${OS_RELEASE_ID}-${OS_RELEASE_VER}" in + ubuntu-18) + make install.tools "BUILDTAGS=$BUILDTAGS" + make "BUILDTAGS=$BUILDTAGS" + make test-binaries "BUILDTAGS=$BUILDTAGS" + ;; + fedora-28) ;& + centos-7) ;& + rhel-7) + make install.tools + make + make test-binaries + ;; + *) bad_os_id_ver ;; +esac + +make localsystem diff --git a/contrib/gate/Dockerfile b/contrib/gate/Dockerfile new file mode 100644 index 000000000..0c0e4aaf9 --- /dev/null +++ b/contrib/gate/Dockerfile @@ -0,0 +1,69 @@ +FROM fedora:28 +RUN dnf -y install \ + atomic-registries \ + btrfs-progs-devel \ + buildah \ + bzip2 \ + conmon \ + container-selinux \ + containernetworking-cni \ + containernetworking-cni-devel \ + device-mapper-devel \ + findutils \ + git \ + glib2-devel \ + glibc-static \ + gnupg \ + golang \ + gpgme-devel \ + iptables \ + libassuan-devel \ + libseccomp-devel \ + libselinux-devel \ + lsof \ + make \ + nmap-ncat \ + ostree-devel \ + procps-ng \ + python \ + python3-dateutil \ + python3-psutil \ + python3-pytoml \ + python3-varlink \ + skopeo-containers \ + slirp4netns \ + rsync \ + which \ + xz \ + && dnf clean all + +ENV GOPATH="/go" \ + PATH="/go/bin:/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin" \ + SRCPATH="/usr/src/libpod" \ + GOSRC="/go/src/github.com/containers/libpod" + +# Only needed for installing build-time dependencies +COPY / $GOSRC + +WORKDIR $GOSRC + +# Install dependencies +RUN set -x && \ + go get -u github.com/mailru/easyjson/... && \ + install -D -m 755 "$GOPATH"/bin/easyjson /usr/bin/ && \ + make install.tools && \ + install -D -m 755 $GOSRC/contrib/gate/entrypoint.sh /usr/local/bin/ && \ + rm -rf "$GOSRC" + +# Install cni config +#RUN make install.cni +RUN mkdir -p /etc/cni/net.d/ +COPY cni/87-podman-bridge.conflist /etc/cni/net.d/87-podman-bridge.conflist + +# Make sure we have some policy for pulling images +RUN mkdir -p /etc/containers +COPY test/policy.json /etc/containers/policy.json +COPY test/redhat_sigstore.yaml /etc/containers/registries.d/registry.access.redhat.com.yaml + +VOLUME ["/usr/src/libpod"] +ENTRYPOINT ["/usr/local/bin/entrypoint.sh"] diff --git a/contrib/gate/README.md b/contrib/gate/README.md new file mode 100644 index 000000000..709e6035f --- /dev/null +++ b/contrib/gate/README.md @@ -0,0 +1,4 @@ +![PODMAN logo](../../logo/podman-logo-source.svg) + +A standard container image for `gofmt` and lint-checking the libpod +repository. The [contributors guide contains the documentation for usage.](https://github.com/containers/libpod/blob/master/CONTRIBUTING.md#go-format-and-lint) diff --git a/contrib/gate/entrypoint.sh b/contrib/gate/entrypoint.sh new file mode 100755 index 000000000..e16094cc0 --- /dev/null +++ b/contrib/gate/entrypoint.sh @@ -0,0 +1,15 @@ +#!/bin/bash + +[[ -n "$SRCPATH" ]] || \ + ( echo "ERROR: \$SRCPATH must be non-empty" && exit 1 ) +[[ -n "$GOSRC" ]] || \ + ( echo "ERROR: \$GOSRC must be non-empty" && exit 2 ) +[[ -r "${SRCPATH}/contrib/gate/Dockerfile" ]] || \ + ( echo "ERROR: Expecting libpod repository root at $SRCPATH" && exit 3 ) + +# Working from a copy avoids needing to perturb the actual source files +mkdir -p "$GOSRC" +/usr/bin/rsync --recursive --links --quiet --safe-links \ + --perms --times "${SRCPATH}/" "${GOSRC}/" +cd "$GOSRC" +make "$@" diff --git a/contrib/python/podman/Makefile b/contrib/python/podman/Makefile index 6ec4159f2..11a7568d1 100644 --- a/contrib/python/podman/Makefile +++ b/contrib/python/podman/Makefile @@ -4,6 +4,7 @@ PODMAN_VERSION ?= '0.0.4' .PHONY: python-podman python-podman: + PODMAN_VERSION=$(PODMAN_VERSION) \ $(PYTHON) setup.py sdist bdist .PHONY: lint @@ -16,6 +17,7 @@ integration: .PHONY: install install: + PODMAN_VERSION=$(PODMAN_VERSION) \ $(PYTHON) setup.py install --root ${DESTDIR} .PHONY: upload diff --git a/contrib/python/podman/podman/libs/_containers_attach.py b/contrib/python/podman/podman/libs/_containers_attach.py index f2dad573b..94247d349 100644 --- a/contrib/python/podman/podman/libs/_containers_attach.py +++ b/contrib/python/podman/podman/libs/_containers_attach.py @@ -19,9 +19,13 @@ class Mixin: """ if stdin is None: stdin = sys.stdin.fileno() + elif hasattr(stdin, 'fileno'): + stdin = stdin.fileno() if stdout is None: stdout = sys.stdout.fileno() + elif hasattr(stdout, 'fileno'): + stdout = stdout.fileno() with self._client() as podman: attach = podman.GetAttachSockets(self._id) @@ -49,7 +53,7 @@ class Mixin: def resize_handler(self): """Send the new window size to conmon.""" - def wrapped(signum, frame): + def wrapped(signum, frame): # pylint: disable=unused-argument packed = fcntl.ioctl(self.pseudo_tty.stdout, termios.TIOCGWINSZ, struct.pack('HHHH', 0, 0, 0, 0)) rows, cols, _, _ = struct.unpack('HHHH', packed) @@ -67,7 +71,7 @@ class Mixin: def log_handler(self): """Send command to reopen log to conmon.""" - def wrapped(signum, frame): + def wrapped(signum, frame): # pylint: disable=unused-argument with open(self.pseudo_tty.control_socket, 'w') as skt: # send conmon reopen log message skt.write('2\n') diff --git a/contrib/python/podman/podman/libs/containers.py b/contrib/python/podman/podman/libs/containers.py index e211a284e..7adecea8f 100644 --- a/contrib/python/podman/podman/libs/containers.py +++ b/contrib/python/podman/podman/libs/containers.py @@ -1,12 +1,12 @@ """Models for manipulating containers and storage.""" import collections -import functools import getpass import json import logging import signal import time +from . import fold_keys from ._containers_attach import Mixin as AttachMixin from ._containers_start import Mixin as StartMixin @@ -14,25 +14,27 @@ from ._containers_start import Mixin as StartMixin class Container(AttachMixin, StartMixin, collections.UserDict): """Model for a container.""" - def __init__(self, client, id, data): + def __init__(self, client, ident, data, refresh=True): """Construct Container Model.""" super(Container, self).__init__(data) - self._client = client - self._id = id + self._id = ident - with client() as podman: - self._refresh(podman) + if refresh: + with client() as podman: + self._refresh(podman) + else: + for k, v in self.data.items(): + setattr(self, k, v) + if 'containerrunning' in self.data: + setattr(self, 'running', self.data['containerrunning']) + self.data['running'] = self.data['containerrunning'] assert self._id == data['id'],\ 'Requested container id({}) does not match store id({})'.format( self._id, data['id'] ) - def __getitem__(self, key): - """Get items from parent dict.""" - return super().__getitem__(key) - def _refresh(self, podman, tries=1): try: ctnr = podman.GetContainer(self._id) @@ -71,18 +73,18 @@ class Container(AttachMixin, StartMixin, collections.UserDict): results = podman.ListContainerChanges(self._id) return results['container'] - def kill(self, signal=signal.SIGTERM, wait=25): + def kill(self, sig=signal.SIGTERM, wait=25): """Send signal to container. default signal is signal.SIGTERM. wait n of seconds, 0 waits forever. """ with self._client() as podman: - podman.KillContainer(self._id, signal) + podman.KillContainer(self._id, sig) timeout = time.time() + wait while True: self._refresh(podman) - if self.status != 'running': + if self.status != 'running': # pylint: disable=no-member return self if wait and timeout < time.time(): @@ -90,20 +92,11 @@ class Container(AttachMixin, StartMixin, collections.UserDict): time.sleep(0.5) - def _lower_hook(self): - """Convert all keys to lowercase.""" - - @functools.wraps(self._lower_hook) - def wrapped(input_): - return {k.lower(): v for (k, v) in input_.items()} - - return wrapped - def inspect(self): """Retrieve details about containers.""" with self._client() as podman: results = podman.InspectContainer(self._id) - obj = json.loads(results['container'], object_hook=self._lower_hook()) + obj = json.loads(results['container'], object_hook=fold_keys()) return collections.namedtuple('ContainerInspect', obj.keys())(**obj) def export(self, target): @@ -115,19 +108,16 @@ class Container(AttachMixin, StartMixin, collections.UserDict): results = podman.ExportContainer(self._id, target) return results['tarfile'] - def commit(self, - image_name, - *args, - changes=[], - message='', - pause=True, - **kwargs): + def commit(self, image_name, **kwargs): """Create image from container. - All changes overwrite existing values. - See inspect() to obtain current settings. + Keyword arguments: + author -- change image's author + message -- change image's message, docker format only. + pause -- pause container during commit + change -- Additional properties to change - Changes: + Change examples: CMD=/usr/bin/zsh ENTRYPOINT=/bin/sh date ENV=TEST=test_containers.TestContainers.test_commit @@ -136,21 +126,23 @@ class Container(AttachMixin, StartMixin, collections.UserDict): USER=bozo:circus VOLUME=/data WORKDIR=/data/application + + All changes overwrite existing values. + See inspect() to obtain current settings. """ - # TODO: Clean up *args, **kwargs after Commit() is complete - try: - author = kwargs.get('author', getpass.getuser()) - except Exception: # pylint: disable=broad-except - author = '' + author = kwargs.get('author', None) or getpass.getuser() + change = kwargs.get('change', None) or [] + message = kwargs.get('message', None) or '' + pause = kwargs.get('pause', None) or True - for c in changes: + for c in change: if c.startswith('LABEL=') and c.count('=') < 2: raise ValueError( 'LABEL should have the format: LABEL=label=value, not {}'. format(c)) with self._client() as podman: - results = podman.Commit(self._id, image_name, changes, author, + results = podman.Commit(self._id, image_name, change, author, message, pause) return results['image'] @@ -175,7 +167,7 @@ class Container(AttachMixin, StartMixin, collections.UserDict): podman.RestartContainer(self._id, timeout) return self._refresh(podman) - def rename(self, target): + def rename(self, target): # pylint: disable=unused-argument """Rename container, return id on success.""" with self._client() as podman: # TODO: Need arguments @@ -183,7 +175,7 @@ class Container(AttachMixin, StartMixin, collections.UserDict): # TODO: fixup objects cached information return results['container'] - def resize_tty(self, width, height): + def resize_tty(self, width, height): # pylint: disable=unused-argument """Resize container tty.""" with self._client() as podman: # TODO: magic re: attach(), arguments @@ -201,7 +193,8 @@ class Container(AttachMixin, StartMixin, collections.UserDict): podman.UnpauseContainer(self._id) return self._refresh(podman) - def update_container(self, *args, **kwargs): + def update_container(self, *args, **kwargs): \ + # pylint: disable=unused-argument """TODO: Update container..., return id on success.""" with self._client() as podman: podman.UpdateContainer() @@ -220,7 +213,7 @@ class Container(AttachMixin, StartMixin, collections.UserDict): obj = results['container'] return collections.namedtuple('StatDetail', obj.keys())(**obj) - def logs(self, *args, **kwargs): + def logs(self, *args, **kwargs): # pylint: disable=unused-argument """Retrieve container logs.""" with self._client() as podman: results = podman.GetContainerLogs(self._id) @@ -239,7 +232,7 @@ class Containers(): with self._client() as podman: results = podman.ListContainers() for cntr in results['containers']: - yield Container(self._client, cntr['id'], cntr) + yield Container(self._client, cntr['id'], cntr, refresh=False) def delete_stopped(self): """Delete all stopped containers.""" diff --git a/contrib/python/podman/podman/libs/images.py b/contrib/python/podman/podman/libs/images.py index 982546cd2..ae1b86390 100644 --- a/contrib/python/podman/podman/libs/images.py +++ b/contrib/python/podman/podman/libs/images.py @@ -27,9 +27,10 @@ class Image(collections.UserDict): @staticmethod def _split_token(values=None, sep='='): + if not values: + return {} return { - k: v1 - for k, v1 in (v0.split(sep, 1) for v0 in values if values) + k: v1 for k, v1 in (v0.split(sep, 1) for v0 in values) } def create(self, *args, **kwargs): @@ -74,7 +75,7 @@ class Image(collections.UserDict): obj = json.loads(results['image'], object_hook=fold_keys()) return collections.namedtuple('ImageInspect', obj.keys())(**obj) - def push(self, target, tlsverify=False): + def push(self, target, tlsverify=True): """Copy image to target, return id on success.""" with self._client() as podman: results = podman.PushImage(self._id, target, tlsverify) @@ -137,7 +138,7 @@ class Images(): results = podman.DeleteUnusedImages() return results['images'] - def import_image(self, source, reference, message=None, changes=None): + def import_image(self, source, reference, message='', changes=None): """Read image tarball from source and save in image store.""" with self._client() as podman: results = podman.ImportImage(source, reference, message, changes) diff --git a/contrib/python/podman/test/test_containers.py b/contrib/python/podman/test/test_containers.py index 3de1e54bc..a7a6ac304 100644 --- a/contrib/python/podman/test/test_containers.py +++ b/contrib/python/podman/test/test_containers.py @@ -152,7 +152,7 @@ class TestContainers(PodmanTestCase): changes.append('WORKDIR=/data/application') id = self.alpine_ctnr.commit( - 'alpine3', author='Bozo the clown', changes=changes, pause=True) + 'alpine3', author='Bozo the clown', change=changes, pause=True) img = self.pclient.images.get(id) self.assertIsNotNone(img) diff --git a/contrib/python/podman/test/test_images.py b/contrib/python/podman/test/test_images.py index f97e13b4c..45f0a2964 100644 --- a/contrib/python/podman/test/test_images.py +++ b/contrib/python/podman/test/test_images.py @@ -102,7 +102,7 @@ class TestImages(PodmanTestCase): def test_push(self): path = '{}/alpine_push'.format(self.tmpdir) target = 'dir:{}'.format(path) - self.alpine_image.push(target) + self.alpine_image.push(target, tlsverify=False) self.assertTrue(os.path.isfile(os.path.join(path, 'manifest.json'))) self.assertTrue(os.path.isfile(os.path.join(path, 'version'))) diff --git a/contrib/python/podman/test/test_pods_ctnrs.py b/contrib/python/podman/test/test_pods_ctnrs.py index 14ce95c8a..009e30720 100644 --- a/contrib/python/podman/test/test_pods_ctnrs.py +++ b/contrib/python/podman/test/test_pods_ctnrs.py @@ -52,7 +52,8 @@ class TestPodsCtnrs(PodmanTestCase): status = FoldedString(pod.containersinfo[0]['status']) self.assertIn(status, ('stopped', 'exited', 'running')) - killed = pod.kill() + # Pod kill is broken, so use stop for now + killed = pod.stop() self.assertEqual(pod, killed) def test_999_remove(self): diff --git a/contrib/python/pypodman/Makefile b/contrib/python/pypodman/Makefile index cd0fcf1de..272145c5d 100644 --- a/contrib/python/pypodman/Makefile +++ b/contrib/python/pypodman/Makefile @@ -4,6 +4,7 @@ PODMAN_VERSION ?= '0.0.4' .PHONY: python-pypodman python-pypodman: + PODMAN_VERSION=$(PODMAN_VERSION) \ $(PYTHON) setup.py sdist bdist .PHONY: lint @@ -16,6 +17,7 @@ integration: .PHONY: install install: + PODMAN_VERSION=$(PODMAN_VERSION) \ $(PYTHON) setup.py install --root ${DESTDIR} .PHONY: upload diff --git a/contrib/python/pypodman/docs/man1/pypodman.1 b/contrib/python/pypodman/docs/man1/pypodman.1 index 09acb205b..45472dab0 100644 --- a/contrib/python/pypodman/docs/man1/pypodman.1 +++ b/contrib/python/pypodman/docs/man1/pypodman.1 @@ -85,7 +85,7 @@ overwriting earlier. Any missing items are ignored. .IP \[bu] 2 From \f[C]\-\-config\-home\f[] command line option + \f[C]pypodman/pypodman.conf\f[] .IP \[bu] 2 -From environment variable, for example: RUN_DIR +From environment variable prefixed with PODMAN_, for example: PODMAN_RUN_DIR .IP \[bu] 2 From command line option, for example: \[en]run\-dir .PP diff --git a/contrib/python/pypodman/pypodman/lib/__init__.py b/contrib/python/pypodman/pypodman/lib/__init__.py index 5525ddaef..be1b5f467 100644 --- a/contrib/python/pypodman/pypodman/lib/__init__.py +++ b/contrib/python/pypodman/pypodman/lib/__init__.py @@ -4,14 +4,15 @@ import sys import podman from pypodman.lib.action_base import AbstractActionBase from pypodman.lib.parser_actions import (BooleanAction, BooleanValidate, - PathAction, PositiveIntAction, - UnitAction) + ChangeAction, PathAction, + PositiveIntAction, UnitAction) from pypodman.lib.podman_parser import PodmanArgumentParser from pypodman.lib.report import Report, ReportColumn # Silence pylint overlording... assert BooleanAction assert BooleanValidate +assert ChangeAction assert PathAction assert PositiveIntAction assert UnitAction diff --git a/contrib/python/pypodman/pypodman/lib/actions/__init__.py b/contrib/python/pypodman/pypodman/lib/actions/__init__.py index 2668cd8ff..c0d77ddb1 100644 --- a/contrib/python/pypodman/pypodman/lib/actions/__init__.py +++ b/contrib/python/pypodman/pypodman/lib/actions/__init__.py @@ -22,6 +22,8 @@ from pypodman.lib.actions.rm_action import Rm from pypodman.lib.actions.rmi_action import Rmi from pypodman.lib.actions.run_action import Run from pypodman.lib.actions.search_action import Search +from pypodman.lib.actions.start_action import Start +from pypodman.lib.actions.version_action import Version __all__ = [ 'Attach', @@ -47,4 +49,6 @@ __all__ = [ 'Rmi', 'Run', 'Search', + 'Start', + 'Version', ] diff --git a/contrib/python/pypodman/pypodman/lib/actions/commit_action.py b/contrib/python/pypodman/pypodman/lib/actions/commit_action.py index 0da6a2078..21924e938 100644 --- a/contrib/python/pypodman/pypodman/lib/actions/commit_action.py +++ b/contrib/python/pypodman/pypodman/lib/actions/commit_action.py @@ -2,7 +2,7 @@ import sys import podman -from pypodman.lib import AbstractActionBase, BooleanAction +from pypodman.lib import AbstractActionBase, BooleanAction, ChangeAction class Commit(AbstractActionBase): @@ -12,7 +12,9 @@ class Commit(AbstractActionBase): def subparser(cls, parent): """Add Commit command to parent parser.""" parser = parent.add_parser( - 'commit', help='create image from container') + 'commit', + help='create image from container', + ) parser.add_argument( '--author', help='Set the author for the committed image', @@ -20,11 +22,7 @@ class Commit(AbstractActionBase): parser.add_argument( '--change', '-c', - choices=('CMD', 'ENTRYPOINT', 'ENV', 'EXPOSE', 'LABEL', 'ONBUILD', - 'STOPSIGNAL', 'USER', 'VOLUME', 'WORKDIR'), - action='append', - type=str.upper, - help='Apply the following possible changes to the created image', + action=ChangeAction, ) parser.add_argument( '--format', @@ -32,7 +30,8 @@ class Commit(AbstractActionBase): choices=('oci', 'docker'), default='oci', type=str.lower, - help='Set the format of the image manifest and metadata', + help='Set the format of the image manifest and metadata.' + ' (Ignored.)', ) parser.add_argument( '--iidfile', @@ -42,7 +41,8 @@ class Commit(AbstractActionBase): parser.add_argument( '--message', '-m', - help='Set commit message for committed image', + help='Set commit message for committed image' + ' (Only on docker images.)', ) parser.add_argument( '--pause', @@ -69,27 +69,11 @@ class Commit(AbstractActionBase): ) parser.set_defaults(class_=cls, method='commit') - def __init__(self, args): - """Construct Commit class.""" - if not args.container: - raise ValueError('You must supply one container id' - ' or name to be used as source.') - if not args.image: - raise ValueError('You must supply one image id' - ' or name to be created.') - super().__init__(args) - - # used only on client - del self.opts['image'] - del self.opts['container'] - def commit(self): """Create image from container.""" try: try: ctnr = self.client.containers.get(self._args.container[0]) - ident = ctnr.commit(**self.opts) - print(ident) except podman.ContainerNotFound as e: sys.stdout.flush() print( @@ -97,6 +81,17 @@ class Commit(AbstractActionBase): file=sys.stderr, flush=True) return 1 + else: + ident = ctnr.commit( + self.opts['image'][0], + change=self.opts.get('change', None), + message=self.opts.get('message', None), + pause=self.opts['pause'], + author=self.opts.get('author', None), + ) + + if not self.opts['quiet']: + print(ident) except podman.ErrorOccurred as e: sys.stdout.flush() print( @@ -104,3 +99,4 @@ class Commit(AbstractActionBase): file=sys.stderr, flush=True) return 1 + return 0 diff --git a/contrib/python/pypodman/pypodman/lib/actions/create_action.py b/contrib/python/pypodman/pypodman/lib/actions/create_action.py index d9631763a..26a312bb1 100644 --- a/contrib/python/pypodman/pypodman/lib/actions/create_action.py +++ b/contrib/python/pypodman/pypodman/lib/actions/create_action.py @@ -21,7 +21,7 @@ class Create(AbstractActionBase): parser.add_argument('image', nargs=1, help='source image id') parser.add_argument( 'command', - nargs='*', + nargs=parent.REMAINDER, help='command and args to run.', ) parser.set_defaults(class_=cls, method='create') diff --git a/contrib/python/pypodman/pypodman/lib/actions/export_action.py b/contrib/python/pypodman/pypodman/lib/actions/export_action.py index f62cd3535..7ef178c4c 100644 --- a/contrib/python/pypodman/pypodman/lib/actions/export_action.py +++ b/contrib/python/pypodman/pypodman/lib/actions/export_action.py @@ -12,13 +12,16 @@ class Export(AbstractActionBase): def subparser(cls, parent): """Add Export command to parent parser.""" parser = parent.add_parser( - 'export', help='export container to tarball') + 'export', + help='export container to tarball', + ) parser.add_argument( '--output', '-o', metavar='PATH', nargs=1, - help='Write to a file', + required=True, + help='Write to this file on host', ) parser.add_argument( 'container', @@ -27,23 +30,11 @@ class Export(AbstractActionBase): ) parser.set_defaults(class_=cls, method='export') - def __init__(self, args): - """Construct Export class.""" - if not args.container: - raise ValueError('You must supply one container id' - ' or name to be used as source.') - - if not args.output: - raise ValueError('You must supply one filename' - ' to be created as tarball using --output.') - super().__init__(args) - def export(self): """Create tarball from container filesystem.""" try: try: ctnr = self.client.containers.get(self._args.container[0]) - ctnr.export(self._args.output[0]) except podman.ContainerNotFound as e: sys.stdout.flush() print( @@ -51,6 +42,8 @@ class Export(AbstractActionBase): file=sys.stderr, flush=True) return 1 + else: + ctnr.export(self._args.output[0]) except podman.ErrorOccurred as e: sys.stdout.flush() print( @@ -58,3 +51,4 @@ class Export(AbstractActionBase): file=sys.stderr, flush=True) return 1 + return 0 diff --git a/contrib/python/pypodman/pypodman/lib/actions/history_action.py b/contrib/python/pypodman/pypodman/lib/actions/history_action.py index 3e3f539fc..f9aaa54f6 100644 --- a/contrib/python/pypodman/pypodman/lib/actions/history_action.py +++ b/contrib/python/pypodman/pypodman/lib/actions/history_action.py @@ -60,7 +60,7 @@ class History(AbstractActionBase): if self._args.human: fields.update({ 'size': - humanize.naturalsize(details.size, binary=True), + humanize.naturalsize(details.size), 'created': humanize.naturaldate( podman.datetime_parse(details.created)), diff --git a/contrib/python/pypodman/pypodman/lib/actions/images_action.py b/contrib/python/pypodman/pypodman/lib/actions/images_action.py index d28e32db9..29bf90dd2 100644 --- a/contrib/python/pypodman/pypodman/lib/actions/images_action.py +++ b/contrib/python/pypodman/pypodman/lib/actions/images_action.py @@ -37,7 +37,7 @@ class Images(AbstractActionBase): self.columns = OrderedDict({ 'name': - ReportColumn('name', 'REPOSITORY', 40), + ReportColumn('name', 'REPOSITORY', 0), 'tag': ReportColumn('tag', 'TAG', 10), 'id': @@ -65,18 +65,18 @@ class Images(AbstractActionBase): 'created': humanize.naturaldate(podman.datetime_parse(image.created)), 'size': - humanize.naturalsize(int(image.size), binary=True), + humanize.naturalsize(int(image.size)), 'repoDigests': ' '.join(image.repoDigests), }) for r in image.repoTags: - name, tag = r.split(':', 1) + name, tag = r.rsplit(':', 1) fields.update({ 'name': name, 'tag': tag, }) - rows.append(fields) + rows.append(fields) if not self._args.digests: del self.columns['repoDigests'] diff --git a/contrib/python/pypodman/pypodman/lib/actions/import_action.py b/contrib/python/pypodman/pypodman/lib/actions/import_action.py index 49b8a5a57..43448144a 100644 --- a/contrib/python/pypodman/pypodman/lib/actions/import_action.py +++ b/contrib/python/pypodman/pypodman/lib/actions/import_action.py @@ -2,7 +2,7 @@ import sys import podman -from pypodman.lib import AbstractActionBase +from pypodman.lib import AbstractActionBase, ChangeAction class Import(AbstractActionBase): @@ -12,18 +12,19 @@ class Import(AbstractActionBase): def subparser(cls, parent): """Add Import command to parent parser.""" parser = parent.add_parser( - 'import', help='import tarball as image filesystem') + 'import', + help='import tarball as image filesystem', + ) parser.add_argument( '--change', '-c', - action='append', - choices=('CMD', 'ENTRYPOINT', 'ENV', 'EXPOSE', 'LABEL', - 'STOPSIGNAL', 'USER', 'VOLUME', 'WORKDIR'), - type=str.upper, - help='Apply the following possible instructions', + action=ChangeAction, ) parser.add_argument( - '--message', '-m', help='Set commit message for imported image.') + '--message', + '-m', + help='Set commit message for imported image.', + ) parser.add_argument( 'source', metavar='PATH', @@ -38,18 +39,25 @@ class Import(AbstractActionBase): ) parser.set_defaults(class_=cls, method='import_') - def __init__(self, args): - """Construct Import class.""" - super().__init__(args) - def import_(self): """Import tarball as image filesystem.""" + # ImportImage() validates it's parameters therefore we need to create + # pristine dict() for keywords + options = {} + if 'message' in self.opts: + options['message'] = self.opts['message'] + if 'change' in self.opts and self.opts['change']: + options['changes'] = self.opts['change'] + + reference = self.opts['reference'][0] if 'reference' in self.opts\ + else None + try: ident = self.client.images.import_image( - self.opts.source, - self.opts.reference, - message=self.opts.message, - changes=self.opts.change) + self.opts['source'][0], + reference, + **options, + ) print(ident) except podman.ErrorOccurred as e: sys.stdout.flush() @@ -58,3 +66,4 @@ class Import(AbstractActionBase): file=sys.stderr, flush=True) return 1 + return 0 diff --git a/contrib/python/pypodman/pypodman/lib/actions/inspect_action.py b/contrib/python/pypodman/pypodman/lib/actions/inspect_action.py index 514b4702a..a581e7e4e 100644 --- a/contrib/python/pypodman/pypodman/lib/actions/inspect_action.py +++ b/contrib/python/pypodman/pypodman/lib/actions/inspect_action.py @@ -23,9 +23,9 @@ class Inspect(AbstractActionBase): help='Type of object to inspect', ) parser.add_argument( - 'size', + '--size', action='store_true', - default=True, + default=False, help='Display the total file size if the type is a container.' ' Always True.') parser.add_argument( @@ -59,7 +59,7 @@ class Inspect(AbstractActionBase): def inspect(self): """Inspect provided podman objects.""" - output = {} + output = [] try: for ident in self._args.objects: obj = None @@ -78,7 +78,13 @@ class Inspect(AbstractActionBase): msg = 'Object "{}" not found'.format(ident) print(msg, file=sys.stderr, flush=True) else: - output.update(obj._asdict()) + fields = obj._asdict() + if not self._args.size: + try: + del fields['sizerootfs'] + except KeyError: + pass + output.append(fields) except podman.ErrorOccurred as e: sys.stdout.flush() print( diff --git a/contrib/python/pypodman/pypodman/lib/actions/ps_action.py b/contrib/python/pypodman/pypodman/lib/actions/ps_action.py index cd7a7947d..62ceb2e67 100644 --- a/contrib/python/pypodman/pypodman/lib/actions/ps_action.py +++ b/contrib/python/pypodman/pypodman/lib/actions/ps_action.py @@ -16,6 +16,7 @@ class Ps(AbstractActionBase): """Add Images command to parent parser.""" parser = parent.add_parser('ps', help='list containers') super().subparser(parser) + parser.add_argument( '--sort', choices=('createdat', 'id', 'image', 'names', 'runningfor', 'size', @@ -32,9 +33,9 @@ class Ps(AbstractActionBase): self.columns = OrderedDict({ 'id': - ReportColumn('id', 'CONTAINER ID', 14), + ReportColumn('id', 'CONTAINER ID', 12), 'image': - ReportColumn('image', 'IMAGE', 30), + ReportColumn('image', 'IMAGE', 31), 'command': ReportColumn('column', 'COMMAND', 20), 'createdat': @@ -49,10 +50,15 @@ class Ps(AbstractActionBase): def list(self): """List containers.""" + if self._args.all: + ictnrs = self.client.containers.list() + else: + ictnrs = filter( + lambda c: podman.FoldedString(c['status']) == 'running', + self.client.containers.list()) + # TODO: Verify sorting on dates and size - ctnrs = sorted( - self.client.containers.list(), - key=operator.attrgetter(self._args.sort)) + ctnrs = sorted(ictnrs, key=operator.attrgetter(self._args.sort)) if not ctnrs: return @@ -65,9 +71,6 @@ class Ps(AbstractActionBase): 'createdat': humanize.naturaldate(podman.datetime_parse(ctnr.createdat)), }) - - if self._args.truncate: - fields.update({'image': ctnr.image[-30:]}) rows.append(fields) with Report(self.columns, heading=self._args.heading) as report: diff --git a/contrib/python/pypodman/pypodman/lib/actions/run_action.py b/contrib/python/pypodman/pypodman/lib/actions/run_action.py index a63eb7917..6a6b3cb2c 100644 --- a/contrib/python/pypodman/pypodman/lib/actions/run_action.py +++ b/contrib/python/pypodman/pypodman/lib/actions/run_action.py @@ -21,7 +21,7 @@ class Run(AbstractActionBase): parser.add_argument('image', nargs=1, help='source image id.') parser.add_argument( 'command', - nargs='*', + nargs=parent.REMAINDER, help='command and args to run.', ) parser.set_defaults(class_=cls, method='run') diff --git a/contrib/python/pypodman/pypodman/lib/actions/start_action.py b/contrib/python/pypodman/pypodman/lib/actions/start_action.py new file mode 100644 index 000000000..f312fb3fa --- /dev/null +++ b/contrib/python/pypodman/pypodman/lib/actions/start_action.py @@ -0,0 +1,76 @@ +"""Remote client command for starting containers.""" +import sys + +import podman +from pypodman.lib import AbstractActionBase, BooleanAction + + +class Start(AbstractActionBase): + """Class for starting container.""" + + @classmethod + def subparser(cls, parent): + """Add Start command to parent parser.""" + parser = parent.add_parser('start', help='start container') + parser.add_argument( + '--attach', + '-a', + action=BooleanAction, + default=False, + help="Attach container's STDOUT and STDERR (default: %(default)s)") + parser.add_argument( + '--detach-keys', + metavar='KEY(s)', + default=4, + help='Override the key sequence for detaching a container.' + ' (format: a single character [a-Z] or ctrl-<value> where' + ' <value> is one of: a-z, @, ^, [, , or _) (default: ^D)') + parser.add_argument( + '--interactive', + '-i', + action=BooleanAction, + default=False, + help="Attach container's STDIN (default: %(default)s)") + # TODO: Implement sig-proxy + parser.add_argument( + '--sig-proxy', + action=BooleanAction, + default=False, + help="Proxy received signals to the process (default: %(default)s)" + ) + parser.add_argument( + 'containers', + nargs='+', + help='containers to start', + ) + parser.set_defaults(class_=cls, method='start') + + def start(self): + """Start provided containers.""" + stdin = sys.stdin if self.opts['interactive'] else None + stdout = sys.stdout if self.opts['attach'] else None + + try: + for ident in self._args.containers: + try: + ctnr = self.client.containers.get(ident) + ctnr.attach( + eot=self.opts['detach_keys'], + stdin=stdin, + stdout=stdout) + ctnr.start() + except podman.ContainerNotFound as e: + sys.stdout.flush() + print( + 'Container "{}" not found'.format(e.name), + file=sys.stderr, + flush=True) + else: + print(ident) + except podman.ErrorOccurred as e: + sys.stdout.flush() + print( + '{}'.format(e.reason).capitalize(), + file=sys.stderr, + flush=True) + return 1 diff --git a/contrib/python/pypodman/pypodman/lib/actions/version_action.py b/contrib/python/pypodman/pypodman/lib/actions/version_action.py new file mode 100644 index 000000000..12b6dc576 --- /dev/null +++ b/contrib/python/pypodman/pypodman/lib/actions/version_action.py @@ -0,0 +1,41 @@ +"""Remote client command for reporting on Podman service.""" +import json +import sys + +import podman +import yaml +from pypodman.lib import AbstractActionBase + + +class Version(AbstractActionBase): + """Class for reporting on Podman Service.""" + + @classmethod + def subparser(cls, parent): + """Add Version command to parent parser.""" + parser = parent.add_parser( + 'version', help='report version on podman service') + parser.set_defaults(class_=cls, method='version') + + def __init__(self, args): + """Construct Version class.""" + super().__init__(args) + + def version(self): + """Report on Podman Service.""" + try: + info = self.client.system.info() + except podman.ErrorOccurred as e: + sys.stdout.flush() + print( + '{}'.format(e.reason).capitalize(), + file=sys.stderr, + flush=True) + return 1 + else: + version = info._asdict()['podman'] + host = info._asdict()['host'] + print("Version {}".format(version['podman_version'])) + print("Go Version {}".format(version['go_version'])) + print("Git Commit {}".format(version['git_commit'])) + print("OS/Arch {}/{}".format(host["os"], host["arch"])) diff --git a/contrib/python/pypodman/pypodman/lib/parser_actions.py b/contrib/python/pypodman/pypodman/lib/parser_actions.py index 2a5859e47..77ee14761 100644 --- a/contrib/python/pypodman/pypodman/lib/parser_actions.py +++ b/contrib/python/pypodman/pypodman/lib/parser_actions.py @@ -4,9 +4,10 @@ Supplimental argparse.Action converters and validaters. The constructors are very verbose but remain for IDE support. """ import argparse +import copy import os -# API defined by argparse.Action shut up pylint +# API defined by argparse.Action therefore shut up pylint # pragma pylint: disable=redefined-builtin # pragma pylint: disable=too-few-public-methods # pragma pylint: disable=too-many-arguments @@ -36,7 +37,7 @@ class BooleanAction(argparse.Action): const=None, default=None, type=None, - choices=('True', 'False'), + choices=None, required=False, help=None, metavar='{True,False}'): @@ -58,11 +59,58 @@ class BooleanAction(argparse.Action): try: val = BooleanValidate()(values) except ValueError: - parser.error('{} must be True or False.'.format(self.dest)) + parser.error('"{}" must be True or False.'.format(option_string)) else: setattr(namespace, self.dest, val) +class ChangeAction(argparse.Action): + """Convert and validate change argument.""" + + def __init__(self, + option_strings, + dest, + nargs=None, + const=None, + default=[], + type=None, + choices=None, + required=False, + help=None, + metavar='OPT=VALUE'): + """Create ChangeAction object.""" + help = (help or '') + ('Apply change(s) to the new image.' + ' May be given multiple times.') + + super().__init__( + option_strings=option_strings, + dest=dest, + nargs=nargs, + const=const, + default=default, + type=type, + choices=choices, + required=required, + help=help, + metavar=metavar) + + def __call__(self, parser, namespace, values, option_string=None): + """Convert and Validate input.""" + items = getattr(namespace, self.dest, None) or [] + items = copy.copy(items) + + choices = ('CMD', 'ENTRYPOINT', 'ENV', 'EXPOSE', 'LABEL', 'ONBUILD', + 'STOPSIGNAL', 'USER', 'VOLUME', 'WORKDIR') + + opt, val = values.split('=', 1) + if opt not in choices: + parser.error('Option "{}" is not supported by argument "{}",' + ' valid options are: {}'.format( + opt, option_string, ', '.join(choices))) + items.append(values) + setattr(namespace, self.dest, items) + + class UnitAction(argparse.Action): """Validate number given is positive integer, with optional suffix.""" @@ -78,8 +126,8 @@ class UnitAction(argparse.Action): help=None, metavar='UNIT'): """Create UnitAction object.""" - help = (help or metavar or dest - ) + ' (format: <number>[<unit>], where unit = b, k, m or g)' + help = (help or metavar or dest)\ + + ' (format: <number>[<unit>], where unit = b, k, m or g)' super().__init__( option_strings=option_strings, dest=dest, @@ -99,15 +147,15 @@ class UnitAction(argparse.Action): except ValueError: if not values[:-1].isdigit(): msg = ('{} must be a positive integer,' - ' with optional suffix').format(self.dest) + ' with optional suffix').format(option_string) parser.error(msg) if not values[-1] in ('b', 'k', 'm', 'g'): msg = '{} only supports suffices of: b, k, m, g'.format( - self.dest) + option_string) parser.error(msg) else: if val <= 0: - msg = '{} must be a positive integer'.format(self.dest) + msg = '{} must be a positive integer'.format(option_string) parser.error(msg) setattr(namespace, self.dest, values) @@ -125,19 +173,16 @@ class PositiveIntAction(argparse.Action): type=int, choices=None, required=False, - help=None, + help='Must be a positive integer.', metavar=None): """Create PositiveIntAction object.""" - self.message = '{} must be a positive integer'.format(dest) - help = help or self.message - super().__init__( option_strings=option_strings, dest=dest, nargs=nargs, const=const, default=default, - type=int, + type=type, choices=choices, required=required, help=help, @@ -149,7 +194,8 @@ class PositiveIntAction(argparse.Action): setattr(namespace, self.dest, values) return - parser.error(self.message) + msg = '{} must be a positive integer'.format(option_string) + parser.error(msg) class PathAction(argparse.Action): diff --git a/contrib/python/pypodman/pypodman/lib/podman_parser.py b/contrib/python/pypodman/pypodman/lib/podman_parser.py index d3c84224f..412c8c8fd 100644 --- a/contrib/python/pypodman/pypodman/lib/podman_parser.py +++ b/contrib/python/pypodman/pypodman/lib/podman_parser.py @@ -97,6 +97,8 @@ class PodmanArgumentParser(argparse.ArgumentParser): actions_parser = self.add_subparsers( dest='subparser_name', help='commands') + # For create/exec/run: don't process options intended for subcommand + actions_parser.REMAINDER = argparse.REMAINDER # import buried here to prevent import loops import pypodman.lib.actions # pylint: disable=cyclic-import @@ -152,7 +154,7 @@ class PodmanArgumentParser(argparse.ArgumentParser): reqattr( 'run_dir', getattr(args, 'run_dir') - or os.environ.get('RUN_DIR') + or os.environ.get('PODMAN_RUN_DIR') or config['default'].get('run_dir') or str(Path(args.xdg_runtime_dir, 'pypodman')) ) # yapf: disable @@ -161,23 +163,24 @@ class PodmanArgumentParser(argparse.ArgumentParser): args, 'host', getattr(args, 'host') - or os.environ.get('HOST') + or os.environ.get('PODMAN_HOST') or config['default'].get('host') ) # yapf:disable reqattr( 'username', getattr(args, 'username') + or os.environ.get('PODMAN_USER') + or config['default'].get('username') or os.environ.get('USER') or os.environ.get('LOGNAME') - or config['default'].get('username') or getpass.getuser() ) # yapf:disable reqattr( 'port', getattr(args, 'port') - or os.environ.get('PORT') + or os.environ.get('PODMAN_PORT') or config['default'].get('port', None) or 22 ) # yapf:disable @@ -185,7 +188,7 @@ class PodmanArgumentParser(argparse.ArgumentParser): reqattr( 'remote_socket_path', getattr(args, 'remote_socket_path') - or os.environ.get('REMOTE_SOCKET_PATH') + or os.environ.get('PODMAN_REMOTE_SOCKET_PATH') or config['default'].get('remote_socket_path') or '/run/podman/io.podman' ) # yapf:disable @@ -193,7 +196,7 @@ class PodmanArgumentParser(argparse.ArgumentParser): reqattr( 'log_level', getattr(args, 'log_level') - or os.environ.get('LOG_LEVEL') + or os.environ.get('PODMAN_LOG_LEVEL') or config['default'].get('log_level') or logging.WARNING ) # yapf:disable @@ -202,7 +205,7 @@ class PodmanArgumentParser(argparse.ArgumentParser): args, 'identity_file', getattr(args, 'identity_file') - or os.environ.get('IDENTITY_FILE') + or os.environ.get('PODMAN_IDENTITY_FILE') or config['default'].get('identity_file') or os.path.expanduser('~{}/.ssh/id_dsa'.format(args.username)) ) # yapf:disable diff --git a/contrib/python/pypodman/pypodman/lib/report.py b/contrib/python/pypodman/pypodman/lib/report.py index 1db4268da..b689390fd 100644 --- a/contrib/python/pypodman/pypodman/lib/report.py +++ b/contrib/python/pypodman/pypodman/lib/report.py @@ -1,8 +1,23 @@ """Report Manager.""" +import string import sys from collections import namedtuple +class ReportFormatter(string.Formatter): + """Custom formatter to default missing keys to '<none>'.""" + + def get_value(self, key, args, kwargs): + """Map missing key to value '<none>'.""" + try: + if isinstance(key, int): + return args[key] + else: + return kwargs[key] + except KeyError: + return '<none>' + + class ReportColumn(namedtuple('ReportColumn', 'key display width default')): """Hold attributes of output column.""" @@ -26,18 +41,24 @@ class Report(): """ self._columns = columns self._file = file + self._format_string = None + self._formatter = ReportFormatter() self._heading = heading self.epilog = epilog - self._format = None def row(self, **fields): """Print row for report.""" if self._heading: hdrs = {k: v.display for (k, v) in self._columns.items()} - print(self._format.format(**hdrs), flush=True, file=self._file) + print( + self._formatter.format(self._format_string, **hdrs), + flush=True, + file=self._file, + ) self._heading = False + fields = {k: str(v) for k, v in fields.items()} - print(self._format.format(**fields)) + print(self._formatter.format(self._format_string, **fields)) def __enter__(self): """Return `self` upon entering the runtime context.""" @@ -63,4 +84,4 @@ class Report(): display_len = info.width fmt.append('{{{0}:{1}.{1}}}'.format(key, display_len)) - self._format = ' '.join(fmt) + self._format_string = ' '.join(fmt) diff --git a/contrib/spec/podman.spec.in b/contrib/spec/podman.spec.in index c2d8fc59d..3192cbfed 100644 --- a/contrib/spec/podman.spec.in +++ b/contrib/spec/podman.spec.in @@ -39,7 +39,7 @@ %global shortcommit_conmon %(c=%{commit_conmon}; echo ${c:0:7}) Name: podman -Version: 0.10.2 +Version: 0.11.2 Release: #COMMITDATE#.git%{shortcommit0}%{?dist} Summary: Manage Pods, Containers and Container Images License: ASL 2.0 @@ -378,10 +378,6 @@ providing packages with %{import_path} prefix. %prep %autosetup -Sgit -n %{repo}-%{shortcommit0} -sed -i '/\/bin\/env/d' completions/bash/%{name} -sed -i 's/0.0.0/%{version}/' contrib/python/%{name}/setup.py -sed -i 's/0.0.0/%{version}/' contrib/python/py%{name}/setup.py -mv pkg/hooks/README.md pkg/hooks/README-hooks.md # untar cri-o tar zxf %{SOURCE1} @@ -416,15 +412,17 @@ popd %install install -dp %{buildroot}%{_unitdir} -%{__make} PREFIX=%{buildroot}%{_prefix} ETCDIR=%{buildroot}%{_sysconfdir} \ +PODMAN_VERSION=%{version} %{__make} PREFIX=%{buildroot}%{_prefix} ETCDIR=%{buildroot}%{_sysconfdir} \ install.bin \ install.man \ install.cni \ install.systemd \ install.completions +mv pkg/hooks/README.md pkg/hooks/README-hooks.md + %if %{with varlink} -%{__make} DESTDIR=%{buildroot} install.python +PODMAN_VERSION=%{version} %{__make} DESTDIR=%{buildroot} install.python %endif # varlink # install libpod.conf diff --git a/docs/libpod.conf.5.md b/docs/libpod.conf.5.md index 198e927ee..d63baeb88 100644 --- a/docs/libpod.conf.5.md +++ b/docs/libpod.conf.5.md @@ -24,6 +24,18 @@ libpod to manage containers. **cgroup_manager**="" Specify the CGroup Manager to use; valid values are "systemd" and "cgroupfs" +**hooks_dir**=["*path*", ...] + + Each `*.json` file in the path configures a hook for Podman containers. For more details on the syntax of the JSON files and the semantics of hook injection, see `oci-hooks(5)`. Podman and libpod currently support both the 1.0.0 and 0.1.0 hook schemas, although the 0.1.0 schema is deprecated. + + Paths listed later in the array higher precedence (`oci-hooks(5)` discusses directory precedence). + + For the annotation conditions, libpod uses any annotations set in the generated OCI configuration. + + For the bind-mount conditions, only mounts explicitly requested by the caller via `--volume` are considered. Bind mounts that libpod inserts by default (e.g. `/dev/shm`) are not considered. + + If `hooks_dir` is unset for root callers, Podman and libpod will currently default to `/usr/share/containers/oci/hooks.d` and `/etc/containers/oci/hooks.d` in order of increasing precedence. Using these defaults is deprecated, and callers should migrate to explicitly setting `hooks_dir`. + **static_dir**="" Directory for persistent libpod files (database, etc) By default this will be configured relative to where containers/storage diff --git a/docs/podman-build.1.md b/docs/podman-build.1.md index 0cbce15c0..f887d68cd 100644 --- a/docs/podman-build.1.md +++ b/docs/podman-build.1.md @@ -171,7 +171,7 @@ value can be entered. The password is entered without echo. **--disable-content-trust** This is a Docker specific option to disable image verification to a Docker -registry and is not supported by Buildah. This flag is a NOOP and provided +registry and is not supported by Podman. This flag is a NOOP and provided soley for scripting compatibility. **--file, -f** *Dockerfile* diff --git a/docs/podman-container-checkpoint.1.md b/docs/podman-container-checkpoint.1.md index 4906e0e12..94e52dc78 100644 --- a/docs/podman-container-checkpoint.1.md +++ b/docs/podman-container-checkpoint.1.md @@ -17,6 +17,25 @@ are not deleted if checkpointing fails for further debugging. If checkpointing s files are theoretically not needed, but if these files are needed Podman can keep the files for further analysis. +**--all, -a** + +Checkpoint all running containers. + +**--latest, -l** + +Instead of providing the container name or ID, checkpoint the last created container. + +**--leave-running, -R** + +Leave the container running after checkpointing instead of stopping it. + +**--tcp-established** + +Checkpoint a container with established TCP connections. If the checkpoint +image contains established TCP connections, this options is required during +restore. Defaults to not checkpointing containers with established TCP +connections. + ## EXAMPLE podman container checkpoint mywebserver diff --git a/docs/podman-container-exists.1.md b/docs/podman-container-exists.1.md new file mode 100644 index 000000000..76701e2c2 --- /dev/null +++ b/docs/podman-container-exists.1.md @@ -0,0 +1,40 @@ +% PODMAN(1) Podman Man Pages +% Brent Baude +% November 2018 +# NAME +podman-container-exists- Check if a container exists in local storage + +# SYNOPSIS +**podman container exists** +[**-h**|**--help**] +CONTAINER + +# DESCRIPTION +**podman container exists** checks if a container exists in local storage. The **ID** or **Name** +of the container may be used as input. Podman will return an exit code +of `0` when the container is found. A `1` will be returned otherwise. An exit code of `125` indicates there +was an issue accessing the local storage. + +## Examples ## + +Check if an container called `webclient` exists in local storage (the container does actually exist). +``` +$ sudo podman container exists webclient +$ echo $? +0 +$ +``` + +Check if an container called `webbackend` exists in local storage (the container does not actually exist). +``` +$ sudo podman container exists webbackend +$ echo $? +1 +$ +``` + +## SEE ALSO +podman(1) + +# HISTORY +November 2018, Originally compiled by Brent Baude (bbaude at redhat dot com) diff --git a/docs/podman-container-restore.1.md b/docs/podman-container-restore.1.md index 6360bccb0..44219f3ef 100644 --- a/docs/podman-container-restore.1.md +++ b/docs/podman-container-restore.1.md @@ -24,6 +24,22 @@ processes in the checkpointed container. Without the **-k**, **--keep** option the checkpoint will be consumed and cannot be used again. +**--all, -a** + +Restore all checkpointed containers. + +**--latest, -l** + +Instead of providing the container name or ID, restore the last created container. + +**--tcp-established** + +Restore a container with established TCP connections. If the checkpoint image +contains established TCP connections, this option is required during restore. +If the checkpoint image does not contain established TCP connections this +option is ignored. Defaults to not restoring containers with established TCP +connections. + ## EXAMPLE podman container restore mywebserver diff --git a/docs/podman-container.1.md b/docs/podman-container.1.md index eac3343d5..aa5dfa82c 100644 --- a/docs/podman-container.1.md +++ b/docs/podman-container.1.md @@ -20,6 +20,7 @@ The container command allows you to manage containers | create | [podman-create(1)](podman-create.1.md) | Create a new container. | | diff | [podman-diff(1)](podman-diff.1.md) | Inspect changes on a container or image's filesystem. | | exec | [podman-exec(1)](podman-exec.1.md) | Execute a command in a running container. | +| exists | [podman-exists(1)](podman-container-exists.1.md) | Check if a container exists in local storage | | export | [podman-export(1)](podman-export.1.md) | Export a container's filesystem contents as a tar archive. | | inspect | [podman-inspect(1)](podman-inspect.1.md) | Display a container or image's configuration. | | kill | [podman-kill(1)](podman-kill.1.md) | Kill the main process in one or more containers. | @@ -38,7 +39,6 @@ The container command allows you to manage containers | stop | [podman-stop(1)](podman-stop.1.md) | Stop one or more running containers. | | top | [podman-top(1)](podman-top.1.md) | Display the running processes of a container. | | umount | [podman-umount(1)](podman-umount.1.md) | Unmount a working container's root filesystem. | -| unmount | [podman-umount(1)](podman-umount.1.md) | Unmount a working container's root filesystem. | | unpause | [podman-unpause(1)](podman-unpause.1.md) | Unpause one or more containers. | | wait | [podman-wait(1)](podman-wait.1.md) | Wait on one or more containers to stop and print their exit codes. | diff --git a/docs/podman-create.1.md b/docs/podman-create.1.md index 5a4d7fb5a..f1409a554 100644 --- a/docs/podman-create.1.md +++ b/docs/podman-create.1.md @@ -66,7 +66,7 @@ Write the container ID to the file **--conmon-pidfile**="" -Write the pid of the `conmon` process to a file. `conmon` daemonizes separate from Podman, so this is necessary when using systemd to restart Podman containers. +Write the pid of the `conmon` process to a file. `conmon` runs in a separate process than Podman, so this is necessary when using systemd to restart Podman containers. **--cpu-count**=*0* @@ -321,13 +321,13 @@ Not implemented **--log-driver**="*json-file*" -Logging driver for the container. Default is defined by daemon `--log-driver` flag. -**Warning**: the `podman logs` command works only for the `json-file` and -`journald` logging drivers. +Logging driver for the container. Currently not supported. This flag is a NOOP provided soley for scripting compatibility. **--log-opt**=[] -Logging driver specific options. +Logging driver specific options. Used to set the path to the container log file. For example: + +`--log-opt path=/var/log/container/mycontainer.json` **--mac-address**="" @@ -414,7 +414,7 @@ UUID short identifier (“f78375b1c487”) Name (“jonah”) podman generates a UUID for each container, and if a name is not assigned -to the container with **--name** then the daemon will also generate a random +to the container with **--name** then it will generate a random string name. The name is useful any place you need to identify a container. This works for both background and foreground containers. @@ -426,7 +426,8 @@ Set the Network mode for the container 'container:<name|id>': reuse another container's network stack 'host': use the podman host network stack. Note: the host mode gives the container full access to local system services such as D-bus and is therefore considered insecure. '<network-name>|<network-id>': connect to a user-defined network - 'ns:<path>' path to a network namespace to join + 'ns:<path>': path to a network namespace to join + 'slirp4netns': use slirp4netns to create a user network stack. This is the default for rootless containers **--network-alias**=[] @@ -454,7 +455,8 @@ Tune the container's pids limit. Set `-1` to have unlimited pids for the contain **--pod**="" -Run container in an existing pod +Run container in an existing pod. If you want podman to make the pod for you, preference the pod name with `new:`. +To make a pod with more granular options, use the `podman pod create` command before creating a container. **--privileged**=*true*|*false* @@ -465,9 +467,10 @@ By default, podman containers are This is because by default a container is not allowed to access any devices. A “privileged” container is given access to all devices. -When the operator executes **podman run --privileged**, podman enables access -to all devices on the host as well as set turn off most of the security measures -protecting the host from the container. +When the operator executes a privileged container, podman enables access +to all devices on the host, turns off graphdriver mount options, as well as +turning off most of the security measures protecting the host from the +container. **-p**, **--publish**=[] diff --git a/docs/podman-image-exists.1.md b/docs/podman-image-exists.1.md new file mode 100644 index 000000000..e04c23721 --- /dev/null +++ b/docs/podman-image-exists.1.md @@ -0,0 +1,40 @@ +% PODMAN(1) Podman Man Pages +% Brent Baude +% November 2018 +# NAME +podman-image-exists- Check if an image exists in local storage + +# SYNOPSIS +**podman image exists** +[**-h**|**--help**] +IMAGE + +# DESCRIPTION +**podman image exists** checks if an image exists in local storage. The **ID** or **Name** +of the image may be used as input. Podman will return an exit code +of `0` when the image is found. A `1` will be returned otherwise. An exit code of `125` indicates there +was an issue accessing the local storage. + +## Examples ## + +Check if an image called `webclient` exists in local storage (the image does actually exist). +``` +$ sudo podman image exists webclient +$ echo $? +0 +$ +``` + +Check if an image called `webbackend` exists in local storage (the image does not actually exist). +``` +$ sudo podman image exists webbackend +$ echo $? +1 +$ +``` + +## SEE ALSO +podman(1) + +# HISTORY +November 2018, Originally compiled by Brent Baude (bbaude at redhat dot com) diff --git a/docs/podman-image.1.md b/docs/podman-image.1.md index 33de0456f..446f8667d 100644 --- a/docs/podman-image.1.md +++ b/docs/podman-image.1.md @@ -14,6 +14,7 @@ The image command allows you to manage images | Command | Man Page | Description | | -------- | ----------------------------------------- | ------------------------------------------------------------------------------ | | build | [podman-build(1)](podman-build.1.md) | Build a container using a Dockerfile. | +| exists | [podman-exists(1)](podman-image-exists.1.md) | Check if a image exists in local storage | | history | [podman-history(1)](podman-history.1.md) | Show the history of an image. | | import | [podman-import(1)](podman-import.1.md) | Import a tarball and save it as a filesystem image. | | inspect | [podman-inspect(1)](podman-inspect.1.md) | Display a image or image's configuration. | diff --git a/docs/podman-images.1.md b/docs/podman-images.1.md index 0bd829a8e..832df0e23 100644 --- a/docs/podman-images.1.md +++ b/docs/podman-images.1.md @@ -49,9 +49,9 @@ Sort by created, id, repository, size or tag (default: created) ``` # podman images REPOSITORY TAG IMAGE ID CREATED SIZE -docker.io/kubernetes/pause latest e3d42bcaf643 3 years ago 251kB -<none> <none> ebb91b73692b 4 weeks ago 27.2MB -docker.io/library/ubuntu latest 4526339ae51c 6 weeks ago 126MB +docker.io/kubernetes/pause latest e3d42bcaf643 3 years ago 251 kB +<none> <none> ebb91b73692b 4 weeks ago 27.2 MB +docker.io/library/ubuntu latest 4526339ae51c 6 weeks ago 126 MB ``` ``` @@ -63,17 +63,17 @@ ebb91b73692b ``` # podman images --noheading -docker.io/kubernetes/pause latest e3d42bcaf643 3 years ago 251kB -<none> <none> ebb91b73692b 4 weeks ago 27.2MB -docker.io/library/ubuntu latest 4526339ae51c 6 weeks ago 126MB +docker.io/kubernetes/pause latest e3d42bcaf643 3 years ago 251 kB +<none> <none> ebb91b73692b 4 weeks ago 27.2 MB +docker.io/library/ubuntu latest 4526339ae51c 6 weeks ago 126 MB ``` ``` # podman images --no-trunc REPOSITORY TAG IMAGE ID CREATED SIZE -docker.io/kubernetes/pause latest sha256:e3d42bcaf643097dd1bb0385658ae8cbe100a80f773555c44690d22c25d16b27 3 years ago 251kB -<none> <none> sha256:ebb91b73692bd27890685846412ae338d13552165eacf7fcd5f139bfa9c2d6d9 4 weeks ago 27.2MB -docker.io/library/ubuntu latest sha256:4526339ae51c3cdc97956a7a961c193c39dfc6bd9733b0d762a36c6881b5583a 6 weeks ago 126MB +docker.io/kubernetes/pause latest sha256:e3d42bcaf643097dd1bb0385658ae8cbe100a80f773555c44690d22c25d16b27 3 years ago 251 kB +<none> <none> sha256:ebb91b73692bd27890685846412ae338d13552165eacf7fcd5f139bfa9c2d6d9 4 weeks ago 27.2 MB +docker.io/library/ubuntu latest sha256:4526339ae51c3cdc97956a7a961c193c39dfc6bd9733b0d762a36c6881b5583a 6 weeks ago 126 MB ``` ``` @@ -87,7 +87,7 @@ ebb91b73692b <none> <none> ``` # podman images --filter dangling=true REPOSITORY TAG IMAGE ID CREATED SIZE -<none> <none> ebb91b73692b 4 weeks ago 27.2MB +<none> <none> ebb91b73692b 4 weeks ago 27.2 MB ``` ``` @@ -126,25 +126,25 @@ REPOSITORY TAG IMAGE ID CREATED SIZE ``` # podman images --sort repository REPOSITORY TAG IMAGE ID CREATED SIZE -<none> <none> 2460217d76fc About a minute ago 4.41MB -docker.io/library/alpine latest 3fd9065eaf02 5 months ago 4.41MB -localhost/myapp latest b2e0ad03474a About a minute ago 4.41MB -registry.access.redhat.com/rhel7 latest 7a840db7f020 2 weeks ago 211MB -registry.fedoraproject.org/fedora 27 801894bc0e43 6 weeks ago 246MB +<none> <none> 2460217d76fc About a minute ago 4.41 MB +docker.io/library/alpine latest 3fd9065eaf02 5 months ago 4.41 MB +localhost/myapp latest b2e0ad03474a About a minute ago 4.41 MB +registry.access.redhat.com/rhel7 latest 7a840db7f020 2 weeks ago 211 MB +registry.fedoraproject.org/fedora 27 801894bc0e43 6 weeks ago 246 MB ``` ``` # podman images REPOSITORY TAG IMAGE ID CREATED SIZE -localhost/test latest 18f0c080cd72 4 seconds ago 4.42MB -docker.io/library/alpine latest 3fd9065eaf02 5 months ago 4.41MB +localhost/test latest 18f0c080cd72 4 seconds ago 4.42 MB +docker.io/library/alpine latest 3fd9065eaf02 5 months ago 4.41 MB # podman images -a REPOSITORY TAG IMAGE ID CREATED SIZE -localhost/test latest 18f0c080cd72 6 seconds ago 4.42MB -<none> <none> 270e70dc54c0 7 seconds ago 4.42MB -<none> <none> 4ed6fbe43414 8 seconds ago 4.41MB -<none> <none> 6b0df8e71508 8 seconds ago 4.41MB -docker.io/library/alpine latest 3fd9065eaf02 5 months ago 4.41MB +localhost/test latest 18f0c080cd72 6 seconds ago 4.42 MB +<none> <none> 270e70dc54c0 7 seconds ago 4.42 MB +<none> <none> 4ed6fbe43414 8 seconds ago 4.41 MB +<none> <none> 6b0df8e71508 8 seconds ago 4.41 MB +docker.io/library/alpine latest 3fd9065eaf02 5 months ago 4.41 MB ``` ## SEE ALSO diff --git a/docs/podman-kill.1.md b/docs/podman-kill.1.md index 14066d151..85f68a73d 100644 --- a/docs/podman-kill.1.md +++ b/docs/podman-kill.1.md @@ -4,7 +4,7 @@ podman\-kill - Kills one or more containers with a signal ## SYNOPSIS -**podman kill** [*options*] *container* ... +**podman kill** [*options*] [*container* ...] ## DESCRIPTION The main process inside each container specified will be sent SIGKILL, or any signal specified with option --signal. diff --git a/docs/podman-pause.1.md b/docs/podman-pause.1.md index b4930de8d..f19fa5d6a 100644 --- a/docs/podman-pause.1.md +++ b/docs/podman-pause.1.md @@ -4,16 +4,33 @@ podman\-pause - Pause one or more containers ## SYNOPSIS -**podman pause** [*options*] *container* ... +**podman pause** [*options*] [*container*...] ## DESCRIPTION Pauses all the processes in one or more containers. You may use container IDs or names as input. +## OPTIONS + +**--all, -a** + +Pause all running containers. + ## EXAMPLE +Pause a container named 'mywebserver' +``` podman pause mywebserver +``` +Pause a container by partial container ID. +``` podman pause 860a4b23 +``` + +Pause all **running** containers. +``` +podman stop -a +``` ## SEE ALSO podman(1), podman-unpause(1) diff --git a/docs/podman-pod-create.1.md b/docs/podman-pod-create.1.md index 673ad9a8c..a63b12d73 100644 --- a/docs/podman-pod-create.1.md +++ b/docs/podman-pod-create.1.md @@ -51,6 +51,15 @@ Assign a name to the pod Write the pod ID to the file +**-p**, **--publish**=[] + +Publish a port or range of ports from the pod to the host + +Format: `ip:hostPort:containerPort | ip::containerPort | hostPort:containerPort | containerPort` +Both hostPort and containerPort can be specified as a range of ports. +When specifying ranges for both, the number of container ports in the range must match the number of host ports in the range. +Use `podman port` to see the actual mapping: `podman port CONTAINER $CONTAINERPORT` + **--share**="" A comma deliminated list of kernel namespaces to share. If none or "" is specified, no namespaces will be shared. The namespaces to choose from are ipc, net, pid, user, uts. diff --git a/docs/podman-pod-exists.1.md b/docs/podman-pod-exists.1.md new file mode 100644 index 000000000..8fb2fc90e --- /dev/null +++ b/docs/podman-pod-exists.1.md @@ -0,0 +1,40 @@ +% podman-pod-exits(1) Podman Man Pages +% Brent Baude +% December 2018 +# NAME +podman-pod-exists- Check if a pod exists in local storage + +# SYNOPSIS +**podman pod exists** +[**-h**|**--help**] +POD + +# DESCRIPTION +**podman pod exists** checks if a pod exists in local storage. The **ID** or **Name** +of the pod may be used as input. Podman will return an exit code +of `0` when the pod is found. A `1` will be returned otherwise. An exit code of `125` indicates there +was an issue accessing the local storage. + +## Examples ## + +Check if a pod called `web` exists in local storage (the pod does actually exist). +``` +$ sudo podman pod exists web +$ echo $? +0 +$ +``` + +Check if a pod called `backend` exists in local storage (the pod does not actually exist). +``` +$ sudo podman pod exists backend +$ echo $? +1 +$ +``` + +## SEE ALSO +podman-pod(1), podman(1) + +# HISTORY +December 2018, Originally compiled by Brent Baude (bbaude at redhat dot com) diff --git a/docs/podman-ps.1.md b/docs/podman-ps.1.md index 2cb77ffed..7333a1095 100644 --- a/docs/podman-ps.1.md +++ b/docs/podman-ps.1.md @@ -24,7 +24,7 @@ all the containers information. By default it lists: Show all the containers, default is only running containers -**--pod** +**--pod, -p** Display the pods the containers are associated with diff --git a/docs/podman-restart.1.md b/docs/podman-restart.1.md index caacaf31d..875afa385 100644 --- a/docs/podman-restart.1.md +++ b/docs/podman-restart.1.md @@ -12,33 +12,51 @@ Containers will be stopped if they are running and then restarted. Stopped containers will not be stopped and will only be started. ## OPTIONS -**--timeout** - -Timeout to wait before forcibly stopping the container +**--all, -a** +Restart all containers regardless of their current state. **--latest, -l** - Instead of providing the container name or ID, use the last created container. If you use methods other than Podman to run containers such as CRI-O, the last started container could be from either of those methods. +**--running** +Restart all containers that are already in the *running* state. + +**--timeout** +Timeout to wait before forcibly stopping the container. + + ## EXAMPLES ## +Restart the latest container ``` $ podman restart -l ec588fc80b05e19d3006bf2e8aa325f0a2e2ff1f609b7afb39176ca8e3e13467 ``` +Restart a specific container by partial container ID ``` $ podman restart ff6cf1 ff6cf1e5e77e6dba1efc7f3fcdb20e8b89ad8947bc0518be1fcb2c78681f226f ``` +Restart two containers by name with a timeout of 4 seconds ``` $ podman restart --timeout 4 test1 test2 c3bb026838c30e5097f079fa365c9a4769d52e1017588278fa00d5c68ebc1502 17e13a63081a995136f907024bcfe50ff532917988a152da229db9d894c5a9ec ``` +Restart all running containers +``` +$ podman restart --running +``` + +Restart all containers +``` +$ podman restart --all +``` + ## SEE ALSO podman(1), podman-run(1), podman-start(1), podman-create(1) diff --git a/docs/podman-rm.1.md b/docs/podman-rm.1.md index 7474a0d1f..56664a8c1 100644 --- a/docs/podman-rm.1.md +++ b/docs/podman-rm.1.md @@ -13,7 +13,7 @@ podman\-rm - Remove one or more containers **--force, f** -Force the removal of a running container +Force the removal of a running and paused containers **--all, a** @@ -29,16 +29,29 @@ to run containers such as CRI-O, the last started container could be from either Remove the volumes associated with the container. (Not yet implemented) ## EXAMPLE - +Remove a container by its name *mywebserver* +``` podman rm mywebserver - +``` +Remove several containers by name and container id. +``` podman rm mywebserver myflaskserver 860a4b23 +``` +Forcibly remove a container by container ID. +``` podman rm -f 860a4b23 +``` +Remove all containers regardless of its run state. +``` podman rm -f -a +``` +Forcibly remove the latest container created. +``` podman rm -f --latest +``` ## SEE ALSO podman(1), podman-rmi(1) diff --git a/docs/podman-run.1.md b/docs/podman-run.1.md index b708e3407..5917f6f7a 100644 --- a/docs/podman-run.1.md +++ b/docs/podman-run.1.md @@ -78,7 +78,7 @@ Write the container ID to the file **--conmon-pidfile**="" -Write the pid of the `conmon` process to a file. `conmon` daemonizes separate from Podman, so this is necessary when using systemd to restart Podman containers. +Write the pid of the `conmon` process to a file. `conmon` runs in a separate process than Podman, so this is necessary when using systemd to restart Podman containers. **--cpu-period**=*0* @@ -333,16 +333,13 @@ Not implemented **--log-driver**="*json-file*" -Logging driver for the container. Default is defined by daemon `--log-driver` flag. - -**Warning**: the `podman logs` command works only for the `json-file` and -`journald` logging drivers. +Logging driver for the container. Currently not supported. This flag is a NOOP provided soley for scripting compatibility. **--log-opt**=[] -Logging driver specific options. +Logging driver specific options. Used to set the path to the container log file. For example: -`path=/var/log/container/mycontainer.json`: Set the path to the container log file. +`--log-opt path=/var/log/container/mycontainer.json` **--mac-address**="" @@ -399,7 +396,7 @@ The operator can identify a container in three ways: - Name (“jonah”) podman generates a UUID for each container, and if a name is not assigned -to the container with **--name** then the daemon will also generate a random +to the container with **--name** then it will generate a random string name. The name is useful any place you need to identify a container. This works for both background and foreground containers. @@ -411,7 +408,8 @@ Set the Network mode for the container: - `container:<name|id>`: reuse another container's network stack - `host`: use the podman host network stack. Note: the host mode gives the container full access to local system services such as D-bus and is therefore considered insecure. - `<network-name>|<network-id>`: connect to a user-defined network -- `ns:<path>` path to a network namespace to join +- `ns:<path>`: path to a network namespace to join +- `slirp4netns`: use slirp4netns to create a user network stack. This is the default for rootless containers **--network-alias**=[] @@ -441,7 +439,8 @@ Tune the container's pids limit. Set `-1` to have unlimited pids for the contain **--pod**="" -Run container in an existing pod +Run container in an existing pod. If you want podman to make the pod for you, preference the pod name with `new:`. +To make a pod with more granular options, use the `podman pod create` command before creating a container. **--privileged**=*true*|*false* @@ -453,8 +452,9 @@ container is not allowed to access any devices. A “privileged” container is given access to all devices. When the operator executes **podman run --privileged**, podman enables access -to all devices on the host as well as set turn off most of the security measures -protecting the host from the container. +to all devices on the host, turns off graphdriver mount options, as well as +turning off most of the security measures protecting the host from the +container. **-p**, **--publish**=[] diff --git a/docs/podman-unpause.1.md b/docs/podman-unpause.1.md index 9404e7648..acfab0930 100644 --- a/docs/podman-unpause.1.md +++ b/docs/podman-unpause.1.md @@ -4,16 +4,34 @@ podman\-unpause - Unpause one or more containers ## SYNOPSIS -**podman unpause** [*options*] *container* ... +**podman unpause** [*options*] [*container*...] ## DESCRIPTION Unpauses the processes in one or more containers. You may use container IDs or names as input. +## OPTIONS + +**--all, -a** + +Unpause all paused containers. + ## EXAMPLE +Unpause a container called 'mywebserver' +``` podman unpause mywebserver +``` +Unpause a container by a partial container ID. + +``` podman unpause 860a4b23 +``` + +Unpause all **paused** containers. +``` +podman unpause -a +``` ## SEE ALSO podman(1), podman-pause(1) diff --git a/docs/podman-version.1.md b/docs/podman-version.1.md index 0c9b9ceed..749a33afd 100644 --- a/docs/podman-version.1.md +++ b/docs/podman-version.1.md @@ -16,8 +16,31 @@ OS, and Architecture. Print usage statement +**--format** + +Change output format to "json" or a Go template. + +## Example + +A sample output of the `version` command: +``` +$ podman version +Version: 0.11.1 +Go Version: go1.11 +Git Commit: "8967a1d691ed44896b81ad48c863033f23c65eb0-dirty" +Built: Thu Nov 8 22:35:40 2018 +OS/Arch: linux/amd64 +``` + +Filtering out only the version: +``` +$ podman version --format '{{.Version}}' +0.11.2 +``` + ## SEE ALSO podman(1), crio(8) ## HISTORY +November 2018, Added --format flag by Tomas Tomecek <ttomecek@redhat.com> July 2017, Originally compiled by Urvashi Mohnani <umohnani@redhat.com> diff --git a/docs/podman.1.md b/docs/podman.1.md index 085af97ff..bde349e6f 100644 --- a/docs/podman.1.md +++ b/docs/podman.1.md @@ -31,6 +31,18 @@ CGroup manager to use for container cgroups. Supported values are cgroupfs or sy Path to where the cpu performance results should be written +**--hooks-dir**=**path** + +Each `*.json` file in the path configures a hook for Podman containers. For more details on the syntax of the JSON files and the semantics of hook injection, see `oci-hooks(5)`. Podman and libpod currently support both the 1.0.0 and 0.1.0 hook schemas, although the 0.1.0 schema is deprecated. + +This option may be set multiple times; paths from later options have higher precedence (`oci-hooks(5)` discusses directory precedence). + +For the annotation conditions, libpod uses any annotations set in the generated OCI configuration. + +For the bind-mount conditions, only mounts explicitly requested by the caller via `--volume` are considered. Bind mounts that libpod inserts by default (e.g. `/dev/shm`) are not considered. + +If `--hooks-dir` is unset for root callers, Podman and libpod will currently default to `/usr/share/containers/oci/hooks.d` and `/etc/containers/oci/hooks.d` in order of increasing precedence. Using these defaults is deprecated, and callers should migrate to explicitly setting `--hooks-dir`. + **--log-level** Log messages above specified level: debug, info, warn, error (default), fatal or panic @@ -56,7 +68,7 @@ Path to the OCI compatible binary used to run containers **--storage-driver, -s**=**value** -Storage driver. The default storage driver for UID 0 is configured in /etc/containers/storage.conf (`$HOME/.config/containers/storage.conf` in rootless mode), and is *vfs* for other users. The `STORAGE_DRIVER` environment variable overrides the default. The --storage-driver specified driver overrides all. +Storage driver. The default storage driver for UID 0 is configured in /etc/containers/storage.conf (`$HOME/.config/containers/storage.conf` in rootless mode), and is *vfs* for non-root users when *fuse-overlayfs* is not available. The `STORAGE_DRIVER` environment variable overrides the default. The --storage-driver specified driver overrides all. Overriding this option will cause the *storage-opt* settings in /etc/containers/storage.conf to be ignored. The user must specify additional options via the `--storage-opt` flag. @@ -161,18 +173,6 @@ the exit codes follow the `chroot` standard, see below: The mounts.conf file specifies volume mount directories that are automatically mounted inside containers when executing the `podman run` or `podman start` commands. When Podman runs in rootless mode, the file `$HOME/.config/containers/mounts.conf` is also used. Please refer to containers-mounts.conf(5) for further details. -**OCI hooks JSON** (`/etc/containers/oci/hooks.d/*.json`, `/usr/share/containers/oci/hooks.d/*.json`) - - Each `*.json` file in `/etc/containers/oci/hooks.d` and `/usr/share/containers/oci/hooks.d` configures a hook for Podman containers, with `/etc/containers/oci/hooks.d` having higher precedence. For more details on the syntax of the JSON files and the semantics of hook injection, see `oci-hooks(5)`. - - Podman and libpod currently support both the 1.0.0 and 0.1.0 hook schemas, although the 0.1.0 schema is deprecated. - - For the annotation conditions, libpod uses any annotations set in the generated OCI configuration. - - For the bind-mount conditions, only mounts explicitly requested by the caller via `--volume` are considered. Bind mounts that libpod inserts by default (e.g. `/dev/shm`) are not considered. - - Hooks are not used when running in rootless mode. - **policy.json** (`/etc/containers/policy.json`) Signature verification policy files are used to specify policy, e.g. trusted keys, applicable when deciding whether to accept an image, or individual signatures of that image, as valid. @@ -192,7 +192,7 @@ the exit codes follow the `chroot` standard, see below: When Podman runs in rootless mode, the file `$HOME/.config/containers/storage.conf` is also loaded. ## Rootless mode -Podman can also be used as non-root user. When podman runs in rootless mode, an user namespace is automatically created. +Podman can also be used as non-root user. When podman runs in rootless mode, a user namespace is automatically created for the user, defined in /etc/subuid and /etc/subgid. Containers created by a non-root user are not visible to other users and are not seen or managed by podman running as root. @@ -209,13 +209,14 @@ Or just add the content manually. $ echo USERNAME:10000:65536 >> /etc/subuid $ echo USERNAME:10000:65536 >> /etc/subgid +See the `subuid(5)` and `subgid(5)` man pages for more information. + Images are pulled under `XDG_DATA_HOME` when specified, otherwise in the home directory of the user under `.local/share/containers/storage`. -Currently it is not possible to create a network device, so rootless containers need to run in the host network namespace. If a rootless container creates a network namespace, -then only the loopback device will be available. +Currently the slirp4netns package is required to be installed to create a network device, otherwise rootless containers need to run in the network namespace of the host. ## SEE ALSO -`containers-mounts.conf(5)`, `containers-registries.conf(5)`, `containers-storage.conf(5)`, `crio(8)`, `libpod.conf(5)`, `oci-hooks(5)`, `policy.json(5)` +`containers-mounts.conf(5)`, `containers-registries.conf(5)`, `containers-storage.conf(5)`, `crio(8)`, `libpod.conf(5)`, `oci-hooks(5)`, `policy.json(5)`, `subuid(5)`, `subgid(5)`, `slirp4netns(1)` ## HISTORY Dec 2016, Originally compiled by Dan Walsh <dwalsh@redhat.com> diff --git a/docs/tutorials/podman_tutorial.md b/docs/tutorials/podman_tutorial.md index 152d65a59..ce94d7d15 100644 --- a/docs/tutorials/podman_tutorial.md +++ b/docs/tutorials/podman_tutorial.md @@ -5,10 +5,13 @@ Podman is a utility provided as part of the libpod library. It can be used to c containers. The following tutorial will teach you how to set up Podman and perform some basic commands with Podman. +**NOTE**: the code samples are intended to be run as a non-root user, and use `sudo` where +root escalation is required. + ## Install Podman on Fedora from RPM Repositories Fedora 27 and later provide Podman via the package manager. ```console -$ sudo dnf install -y podman +sudo dnf install -y podman ``` ## Install Podman on Fedora from Source @@ -18,10 +21,10 @@ acquire the source, and build it. ### Installing build and runtime dependencies ```console -$ sudo dnf install -y git runc libassuan-devel golang golang-github-cpuguy83-go-md2man glibc-static \ - gpgme-devel glib2-devel device-mapper-devel libseccomp-devel \ - atomic-registries iptables skopeo-containers containernetworking-cni \ - conmon +sudo dnf install -y git runc libassuan-devel golang golang-github-cpuguy83-go-md2man glibc-static \ + gpgme-devel glib2-devel device-mapper-devel libseccomp-devel \ + atomic-registries iptables skopeo-containers containernetworking-cni \ + conmon ``` ### Building and installing podman @@ -29,12 +32,12 @@ First, configure a `GOPATH` (if you are using go1.8 or later, this defaults to ` and make libpod. ```console -$ export GOPATH=~/go -$ mkdir -p $GOPATH -$ git clone https://github.com/containers/libpod/ $GOPATH/src/github.com/containers/libpod -$ cd $GOPATH/src/github.com/containers/libpod -$ make -$ sudo make install PREFIX=/usr +export GOPATH=~/go +mkdir -p $GOPATH +git clone https://github.com/containers/libpod/ $GOPATH/src/github.com/containers/libpod +cd $GOPATH/src/github.com/containers/libpod +make +sudo make install PREFIX=/usr ``` You now have a working podman environment. Jump to [Familiarizing yourself with Podman](#familiarizing-yourself-with-podman) @@ -50,8 +53,8 @@ tutorial. For this tutorial, the Ubuntu **artful-server-cloudimg** image was use #### Installing base packages ```console -$ sudo apt-get update -$ sudo apt-get install libdevmapper-dev libglib2.0-dev libgpgme11-dev golang libseccomp-dev \ +sudo apt-get update +sudo apt-get install libdevmapper-dev libglib2.0-dev libgpgme11-dev golang libseccomp-dev \ go-md2man libprotobuf-dev libprotobuf-c0-dev libseccomp-dev python3-setuptools ``` #### Building and installing conmon @@ -59,42 +62,42 @@ First, configure a `GOPATH` (if you are using go1.8 or later, this defaults to ` and make libpod. ```console -$ export GOPATH=~/go -$ mkdir -p $GOPATH -$ git clone https://github.com/kubernetes-sigs/cri-o $GOPATH/src/github.com/kubernetes-sigs/cri-o -$ cd $GOPATH/src/github.com/kubernetes-sigs/cri-o -$ mkdir bin -$ make bin/conmon -$ sudo install -D -m 755 bin/conmon /usr/libexec/podman/conmon +export GOPATH=~/go +mkdir -p $GOPATH +git clone https://github.com/kubernetes-sigs/cri-o $GOPATH/src/github.com/kubernetes-sigs/cri-o +cd $GOPATH/src/github.com/kubernetes-sigs/cri-o +mkdir bin +make bin/conmon +sudo install -D -m 755 bin/conmon /usr/libexec/podman/conmon ``` #### Adding required configuration files ```console -$ sudo mkdir -p /etc/containers -$ sudo curl https://raw.githubusercontent.com/projectatomic/registries/master/registries.fedora -o /etc/containers/registries.conf -$ sudo curl https://raw.githubusercontent.com/containers/skopeo/master/default-policy.json -o /etc/containers/policy.json +sudo mkdir -p /etc/containers +sudo curl https://raw.githubusercontent.com/projectatomic/registries/master/registries.fedora -o /etc/containers/registries.conf +sudo curl https://raw.githubusercontent.com/containers/skopeo/master/default-policy.json -o /etc/containers/policy.json ``` #### Installing CNI plugins ```console -$ git clone https://github.com/containernetworking/plugins.git $GOPATH/src/github.com/containernetworking/plugins -$ cd $GOPATH/src/github.com/containernetworking/plugins -$ ./build.sh -$ sudo mkdir -p /usr/libexec/cni -$ sudo cp bin/* /usr/libexec/cni +git clone https://github.com/containernetworking/plugins.git $GOPATH/src/github.com/containernetworking/plugins +cd $GOPATH/src/github.com/containernetworking/plugins +./build_linux.sh +sudo mkdir -p /usr/libexec/cni +sudo cp bin/* /usr/libexec/cni ``` #### Installing runc ```console -$ git clone https://github.com/opencontainers/runc.git $GOPATH/src/github.com/opencontainers/runc -$ cd $GOPATH/src/github.com/opencontainers/runc -$ make BUILDTAGS="seccomp" -$ sudo cp runc /usr/bin/runc +git clone https://github.com/opencontainers/runc.git $GOPATH/src/github.com/opencontainers/runc +cd $GOPATH/src/github.com/opencontainers/runc +make BUILDTAGS="seccomp" +sudo cp runc /usr/bin/runc ``` ### Building and installing Podman ```console -$ git clone https://github.com/containers/libpod/ $GOPATH/src/github.com/containers/libpod -$ cd $GOPATH/src/github.com/containers/libpod -$ make -$ sudo make install PREFIX=/usr +git clone https://github.com/containers/libpod/ $GOPATH/src/github.com/containers/libpod +cd $GOPATH/src/github.com/containers/libpod +make +sudo make install PREFIX=/usr ``` ## Familiarizing yourself with Podman @@ -103,10 +106,10 @@ $ sudo make install PREFIX=/usr This sample container will run a very basic httpd server that serves only its index page. ```console -$ sudo podman run -dt -e HTTPD_VAR_RUN=/var/run/httpd -e HTTPD_MAIN_CONF_D_PATH=/etc/httpd/conf.d \ - -e HTTPD_MAIN_CONF_PATH=/etc/httpd/conf \ - -e HTTPD_CONTAINER_SCRIPTS_PATH=/usr/share/container-scripts/httpd/ \ - registry.fedoraproject.org/f27/httpd /usr/bin/run-httpd +sudo podman run -dt -e HTTPD_VAR_RUN=/var/run/httpd -e HTTPD_MAIN_CONF_D_PATH=/etc/httpd/conf.d \ + -e HTTPD_MAIN_CONF_PATH=/etc/httpd/conf \ + -e HTTPD_CONTAINER_SCRIPTS_PATH=/usr/share/container-scripts/httpd/ \ + registry.fedoraproject.org/f27/httpd /usr/bin/run-httpd ``` Because the container is being run in detached mode, represented by the *-d* in the podman run command, podman will print the container ID after it has run. @@ -114,7 +117,7 @@ will print the container ID after it has run. ### Listing running containers The Podman *ps* command is used to list creating and running containers. ```console -$ sudo podman ps +sudo podman ps ``` Note: If you add *-a* to the *ps* command, Podman will show all containers. @@ -126,7 +129,7 @@ $ sudo podman inspect -l | grep IPAddress\": "IPAddress": "10.88.6.140", ``` -Note: The -l is convenience arguement for **latest container**. You can also use the container's ID instead +Note: The -l is a convenience argument for **latest container**. You can also use the container's ID instead of -l. ### Testing the httpd server @@ -134,7 +137,7 @@ Now that we have the IP address of the container, we can test the network commun operating system and the container using curl. The following command should display the index page of our containerized httpd server. ```console -# curl http://<IP_address>:8080 +curl http://<IP_address>:8080 ``` ### Viewing the container's logs @@ -163,7 +166,7 @@ With this a container can later be restored and continue running at exactly the checkpoint. This capability requires CRIU 3.11 or later installed on the system. To checkpoint the container use: ```console -$ sudo podman container checkpoint <container_id> +sudo podman container checkpoint <container_id> ``` ### Restoring the container @@ -171,29 +174,29 @@ Restoring a container is only possible for a previously checkpointed container. continue to run at exactly the same point in time it was checkpointed. To restore the container use: ```console -$ sudo podman container restore <container_id> +sudo podman container restore <container_id> ``` After being restored, the container will answer requests again as it did before checkpointing. ```console -# curl http://<IP_address>:8080 +curl http://<IP_address>:8080 ``` ### Stopping the container To stop the httpd container: ```console -$ sudo podman stop --latest +sudo podman stop --latest ``` You can also check the status of one or more containers using the *ps* subcommand. In this case, we should use the *-a* argument to list all containers. ```console -$ sudo podman ps -a +sudo podman ps -a ``` ### Removing the container To remove the httpd container: ```console -$ sudo podman rm --latest +sudo podman rm --latest ``` You can verify the deletion of the container by running *podman ps -a*. diff --git a/install.md b/install.md index 33224c810..efb568b66 100644 --- a/install.md +++ b/install.md @@ -83,7 +83,7 @@ Debian, Ubuntu, and related distributions will also need to do the following set If using an older release or a long-term support release, be careful to double-check that the version of `runc` is new enough (running `runc --version` should produce `spec: 1.0.0`), or else [build](https://github.com/containers/libpod/blob/master/docs/tutorials/podman_tutorial.md#installing-runc) your own. -Be careful to double-check that the version of golang is new enough, version 1.8.x or higher is required. If needed, golang kits are available at https://golang.org/dl/ +Be careful to double-check that the version of golang is new enough, version 1.10.x or higher is required. If needed, golang kits are available at https://golang.org/dl/ **Optional** diff --git a/libpod/boltdb_state.go b/libpod/boltdb_state.go index 42f029379..cb661d4e9 100644 --- a/libpod/boltdb_state.go +++ b/libpod/boltdb_state.go @@ -3,7 +3,6 @@ package libpod import ( "bytes" "encoding/json" - "os" "strings" "sync" @@ -19,7 +18,6 @@ type BoltState struct { dbLock sync.Mutex namespace string namespaceBytes []byte - lockDir string runtime *Runtime } @@ -52,25 +50,15 @@ type BoltState struct { // containers/storage do not occur. // NewBoltState creates a new bolt-backed state database -func NewBoltState(path, lockDir string, runtime *Runtime) (State, error) { +func NewBoltState(path string, runtime *Runtime) (State, error) { state := new(BoltState) state.dbPath = path - state.lockDir = lockDir state.runtime = runtime state.namespace = "" state.namespaceBytes = nil logrus.Debugf("Initializing boltdb state at %s", path) - // Make the directory that will hold container lockfiles - if err := os.MkdirAll(lockDir, 0750); err != nil { - // The directory is allowed to exist - if !os.IsExist(err) { - return nil, errors.Wrapf(err, "error creating lockfiles dir %s", lockDir) - } - } - state.lockDir = lockDir - db, err := bolt.Open(path, 0600, nil) if err != nil { return nil, errors.Wrapf(err, "error opening database %s", path) @@ -115,11 +103,6 @@ func NewBoltState(path, lockDir string, runtime *Runtime) (State, error) { return nil, errors.Wrapf(err, "error creating initial database layout") } - // Check runtime configuration - if err := checkRuntimeConfig(db, runtime); err != nil { - return nil, err - } - state.valid = true return state, nil @@ -240,6 +223,72 @@ func (s *BoltState) Refresh() error { return err } +// GetDBConfig retrieves runtime configuration fields that were created when +// the database was first initialized +func (s *BoltState) GetDBConfig() (*DBConfig, error) { + if !s.valid { + return nil, ErrDBClosed + } + + cfg := new(DBConfig) + + db, err := s.getDBCon() + if err != nil { + return nil, err + } + defer s.closeDBCon(db) + + err = db.View(func(tx *bolt.Tx) error { + configBucket, err := getRuntimeConfigBucket(tx) + if err != nil { + return nil + } + + // Some of these may be nil + // When we convert to string, Go will coerce them to "" + // That's probably fine - we could raise an error if the key is + // missing, but just not including it is also OK. + libpodRoot := configBucket.Get(staticDirKey) + libpodTmp := configBucket.Get(tmpDirKey) + storageRoot := configBucket.Get(graphRootKey) + storageTmp := configBucket.Get(runRootKey) + graphDriver := configBucket.Get(graphDriverKey) + + cfg.LibpodRoot = string(libpodRoot) + cfg.LibpodTmp = string(libpodTmp) + cfg.StorageRoot = string(storageRoot) + cfg.StorageTmp = string(storageTmp) + cfg.GraphDriver = string(graphDriver) + + return nil + }) + if err != nil { + return nil, err + } + + return cfg, nil +} + +// ValidateDBConfig validates paths in the given runtime against the database +func (s *BoltState) ValidateDBConfig(runtime *Runtime) error { + if !s.valid { + return ErrDBClosed + } + + db, err := s.getDBCon() + if err != nil { + return err + } + defer s.closeDBCon(db) + + // Check runtime configuration + if err := checkRuntimeConfig(db, runtime); err != nil { + return err + } + + return nil +} + // SetNamespace sets the namespace that will be used for container and pod // retrieval func (s *BoltState) SetNamespace(ns string) error { diff --git a/libpod/boltdb_state_internal.go b/libpod/boltdb_state_internal.go index cc7d106cc..3f00657ea 100644 --- a/libpod/boltdb_state_internal.go +++ b/libpod/boltdb_state_internal.go @@ -30,6 +30,13 @@ const ( containersName = "containers" podIDName = "pod-id" namespaceName = "namespace" + + staticDirName = "static-dir" + tmpDirName = "tmp-dir" + runRootName = "run-root" + graphRootName = "graph-root" + graphDriverName = "graph-driver-name" + osName = "os" ) var ( @@ -49,21 +56,19 @@ var ( containersBkt = []byte(containersName) podIDKey = []byte(podIDName) namespaceKey = []byte(namespaceName) + + staticDirKey = []byte(staticDirName) + tmpDirKey = []byte(tmpDirName) + runRootKey = []byte(runRootName) + graphRootKey = []byte(graphRootName) + graphDriverKey = []byte(graphDriverName) + osKey = []byte(osName) ) // Check if the configuration of the database is compatible with the // configuration of the runtime opening it // If there is no runtime configuration loaded, load our own func checkRuntimeConfig(db *bolt.DB, rt *Runtime) error { - var ( - staticDir = []byte("static-dir") - tmpDir = []byte("tmp-dir") - runRoot = []byte("run-root") - graphRoot = []byte("graph-root") - graphDriverName = []byte("graph-driver-name") - osKey = []byte("os") - ) - err := db.Update(func(tx *bolt.Tx) error { configBkt, err := getRuntimeConfigBucket(tx) if err != nil { @@ -74,31 +79,31 @@ func checkRuntimeConfig(db *bolt.DB, rt *Runtime) error { return err } - if err := validateDBAgainstConfig(configBkt, "static dir", - rt.config.StaticDir, staticDir, ""); err != nil { + if err := validateDBAgainstConfig(configBkt, "libpod root directory (staticdir)", + rt.config.StaticDir, staticDirKey, ""); err != nil { return err } - if err := validateDBAgainstConfig(configBkt, "tmp dir", - rt.config.TmpDir, tmpDir, ""); err != nil { + if err := validateDBAgainstConfig(configBkt, "libpod temporary files directory (tmpdir)", + rt.config.TmpDir, tmpDirKey, ""); err != nil { return err } - if err := validateDBAgainstConfig(configBkt, "run root", - rt.config.StorageConfig.RunRoot, runRoot, + if err := validateDBAgainstConfig(configBkt, "storage temporary directory (runroot)", + rt.config.StorageConfig.RunRoot, runRootKey, storage.DefaultStoreOptions.RunRoot); err != nil { return err } - if err := validateDBAgainstConfig(configBkt, "graph root", - rt.config.StorageConfig.GraphRoot, graphRoot, + if err := validateDBAgainstConfig(configBkt, "storage graph root directory (graphroot)", + rt.config.StorageConfig.GraphRoot, graphRootKey, storage.DefaultStoreOptions.GraphRoot); err != nil { return err } - return validateDBAgainstConfig(configBkt, "graph driver name", + return validateDBAgainstConfig(configBkt, "storage graph driver", rt.config.StorageConfig.GraphDriverName, - graphDriverName, + graphDriverKey, storage.DefaultStoreOptions.GraphDriverName) }) @@ -261,7 +266,7 @@ func (s *BoltState) getContainerFromDB(id []byte, ctr *Container, ctrsBkt *bolt. } // Get the lock - lockPath := filepath.Join(s.lockDir, string(id)) + lockPath := filepath.Join(s.runtime.lockDir, string(id)) lock, err := storage.GetLockfile(lockPath) if err != nil { return errors.Wrapf(err, "error retrieving lockfile for container %s", string(id)) @@ -297,7 +302,7 @@ func (s *BoltState) getPodFromDB(id []byte, pod *Pod, podBkt *bolt.Bucket) error } // Get the lock - lockPath := filepath.Join(s.lockDir, string(id)) + lockPath := filepath.Join(s.runtime.lockDir, string(id)) lock, err := storage.GetLockfile(lockPath) if err != nil { return errors.Wrapf(err, "error retrieving lockfile for pod %s", string(id)) diff --git a/libpod/container.go b/libpod/container.go index 7bb5b2687..b6155a809 100644 --- a/libpod/container.go +++ b/libpod/container.go @@ -9,6 +9,7 @@ import ( "github.com/containernetworking/cni/pkg/types" cnitypes "github.com/containernetworking/cni/pkg/types/current" + "github.com/containers/libpod/pkg/namespaces" "github.com/containers/storage" "github.com/cri-o/ocicni/pkg/ocicni" spec "github.com/opencontainers/runtime-spec/specs-go" @@ -296,6 +297,8 @@ type ContainerConfig struct { HostAdd []string `json:"hostsAdd,omitempty"` // Network names (CNI) to add container to. Empty to use default network. Networks []string `json:"networks,omitempty"` + // Network mode specified for the default network. + NetMode namespaces.NetworkMode `json:"networkMode,omitempty"` // Image Config @@ -826,7 +829,7 @@ func (c *Container) IPs() ([]net.IPNet, error) { } if !c.config.CreateNetNS { - return nil, errors.Wrapf(ErrInvalidArg, "container %s network namespace is not managed by libpod") + return nil, errors.Wrapf(ErrInvalidArg, "container %s network namespace is not managed by libpod", c.ID()) } ips := make([]net.IPNet, 0) @@ -854,7 +857,7 @@ func (c *Container) Routes() ([]types.Route, error) { } if !c.config.CreateNetNS { - return nil, errors.Wrapf(ErrInvalidArg, "container %s network namespace is not managed by libpod") + return nil, errors.Wrapf(ErrInvalidArg, "container %s network namespace is not managed by libpod", c.ID()) } routes := make([]types.Route, 0) @@ -996,3 +999,15 @@ func (c *Container) IsInfra() bool { func (c *Container) IsReadOnly() bool { return c.config.Spec.Root.Readonly } + +// NetworkDisabled returns whether the container is running with a disabled network +func (c *Container) NetworkDisabled() bool { + if !c.config.PostConfigureNetNS { + for _, ns := range c.config.Spec.Linux.Namespaces { + if ns.Type == spec.NetworkNamespace { + return ns.Path == "" + } + } + } + return false +} diff --git a/libpod/container_api.go b/libpod/container_api.go index e522038a3..bc92cae69 100644 --- a/libpod/container_api.go +++ b/libpod/container_api.go @@ -10,8 +10,8 @@ import ( "time" "github.com/containers/libpod/libpod/driver" - "github.com/containers/libpod/pkg/chrootuser" "github.com/containers/libpod/pkg/inspect" + "github.com/containers/libpod/pkg/lookup" "github.com/containers/storage/pkg/stringid" "github.com/docker/docker/daemon/caps" "github.com/pkg/errors" @@ -39,16 +39,13 @@ func (c *Container) Init(ctx context.Context) (err error) { notRunning, err := c.checkDependenciesRunning() if err != nil { - return errors.Wrapf(err, "error checking dependencies for container %s") + return errors.Wrapf(err, "error checking dependencies for container %s", c.ID()) } if len(notRunning) > 0 { depString := strings.Join(notRunning, ",") return errors.Wrapf(ErrCtrStateInvalid, "some dependencies of container %s are not started: %s", c.ID(), depString) } - if err := c.prepare(); err != nil { - return err - } defer func() { if err != nil { if err2 := c.cleanup(ctx); err2 != nil { @@ -57,6 +54,10 @@ func (c *Container) Init(ctx context.Context) (err error) { } }() + if err := c.prepare(); err != nil { + return err + } + if c.state.State == ContainerStateStopped { // Reinitialize the container return c.reinit(ctx) @@ -92,16 +93,13 @@ func (c *Container) Start(ctx context.Context) (err error) { notRunning, err := c.checkDependenciesRunning() if err != nil { - return errors.Wrapf(err, "error checking dependencies for container %s") + return errors.Wrapf(err, "error checking dependencies for container %s", c.ID()) } if len(notRunning) > 0 { depString := strings.Join(notRunning, ",") return errors.Wrapf(ErrCtrStateInvalid, "some dependencies of container %s are not started: %s", c.ID(), depString) } - if err := c.prepare(); err != nil { - return err - } defer func() { if err != nil { if err2 := c.cleanup(ctx); err2 != nil { @@ -110,6 +108,10 @@ func (c *Container) Start(ctx context.Context) (err error) { } }() + if err := c.prepare(); err != nil { + return err + } + if c.state.State == ContainerStateStopped { // Reinitialize the container if we need to if err := c.reinit(ctx); err != nil { @@ -157,16 +159,13 @@ func (c *Container) StartAndAttach(ctx context.Context, streams *AttachStreams, notRunning, err := c.checkDependenciesRunning() if err != nil { - return nil, errors.Wrapf(err, "error checking dependencies for container %s") + return nil, errors.Wrapf(err, "error checking dependencies for container %s", c.ID()) } if len(notRunning) > 0 { depString := strings.Join(notRunning, ",") return nil, errors.Wrapf(ErrCtrStateInvalid, "some dependencies of container %s are not started: %s", c.ID(), depString) } - if err := c.prepare(); err != nil { - return nil, err - } defer func() { if err != nil { if err2 := c.cleanup(ctx); err2 != nil { @@ -175,6 +174,10 @@ func (c *Container) StartAndAttach(ctx context.Context, streams *AttachStreams, } }() + if err := c.prepare(); err != nil { + return nil, err + } + if c.state.State == ContainerStateStopped { // Reinitialize the container if we need to if err := c.reinit(ctx); err != nil { @@ -292,13 +295,13 @@ func (c *Container) Exec(tty, privileged bool, env, cmd []string, user string) e // the host hostUser := "" if user != "" { - uid, gid, err := chrootuser.GetUser(c.state.Mountpoint, user) + execUser, err := lookup.GetUserGroupInfo(c.state.Mountpoint, user, nil) if err != nil { - return errors.Wrapf(err, "error getting user to launch exec session as") + return err } // runc expects user formatted as uid:gid - hostUser = fmt.Sprintf("%d:%d", uid, gid) + hostUser = fmt.Sprintf("%d:%d", execUser.Uid, execUser.Gid) } // Generate exec session ID @@ -325,25 +328,25 @@ func (c *Container) Exec(tty, privileged bool, env, cmd []string, user string) e if err != nil { return errors.Wrapf(err, "error exec %s", c.ID()) } + chWait := make(chan error) + go func() { + chWait <- execCmd.Wait() + }() + defer close(chWait) pidFile := c.execPidPath(sessionID) - // 1 second seems a reasonable time to wait - // See https://github.com/containers/libpod/issues/1495 - const pidWaitTimeout = 1000 + // 60 second seems a reasonable time to wait + // https://github.com/containers/libpod/issues/1495 + // https://github.com/containers/libpod/issues/1816 + const pidWaitTimeout = 60000 // Wait until the runtime makes the pidfile - // TODO: If runtime errors before the PID file is created, we have to - // wait for timeout here - if err := WaitForFile(pidFile, pidWaitTimeout*time.Millisecond); err != nil { - logrus.Debugf("Timed out waiting for pidfile from runtime for container %s exec", c.ID()) - - // Check if an error occurred in the process before we made a pidfile - // TODO: Wait() here is a poor choice - is there a way to see if - // a process has finished, instead of waiting for it to finish? - if err := execCmd.Wait(); err != nil { + exited, err := WaitForFile(pidFile, chWait, pidWaitTimeout*time.Millisecond) + if err != nil { + if exited { + // If the runtime exited, propagate the error we got from the process. return err } - return errors.Wrapf(err, "timed out waiting for runtime to create pidfile for exec session in container %s", c.ID()) } @@ -385,7 +388,10 @@ func (c *Container) Exec(tty, privileged bool, env, cmd []string, user string) e locked = false } - waitErr := execCmd.Wait() + var waitErr error + if !exited { + waitErr = <-chWait + } // Lock again if !c.batched { @@ -685,7 +691,7 @@ func (c *Container) Sync() error { (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 { + if err := c.runtime.ociRuntime.updateContainerStatus(c, true); err != nil { return err } // Only save back to DB if state changed @@ -712,7 +718,7 @@ func (c *Container) RestartWithTimeout(ctx context.Context, timeout uint) (err e notRunning, err := c.checkDependenciesRunning() if err != nil { - return errors.Wrapf(err, "error checking dependencies for container %s") + return errors.Wrapf(err, "error checking dependencies for container %s", c.ID()) } if len(notRunning) > 0 { depString := strings.Join(notRunning, ",") @@ -797,7 +803,7 @@ func (c *Container) Refresh(ctx context.Context) error { return err } - logrus.Debugf("Successfully refresh container %s state") + logrus.Debugf("Successfully refresh container %s state", c.ID()) // Initialize the container if it was created in runc if wasCreated || wasRunning || wasPaused { @@ -826,9 +832,22 @@ func (c *Container) Refresh(ctx context.Context) error { return nil } +// ContainerCheckpointOptions is a struct used to pass the parameters +// for checkpointing (and restoring) to the corresponding functions +type ContainerCheckpointOptions struct { + // Keep tells the API to not delete checkpoint artifacts + Keep bool + // KeepRunning tells the API to keep the container running + // after writing the checkpoint to disk + KeepRunning bool + // TCPEstablished tells the API to checkpoint a container + // even if it contains established TCP connections + TCPEstablished bool +} + // Checkpoint checkpoints a container -func (c *Container) Checkpoint(ctx context.Context, keep bool) error { - logrus.Debugf("Trying to checkpoint container %s", c) +func (c *Container) Checkpoint(ctx context.Context, options ContainerCheckpointOptions) error { + logrus.Debugf("Trying to checkpoint container %s", c.ID()) if !c.batched { c.lock.Lock() defer c.lock.Unlock() @@ -838,12 +857,12 @@ func (c *Container) Checkpoint(ctx context.Context, keep bool) error { } } - return c.checkpoint(ctx, keep) + return c.checkpoint(ctx, options) } // Restore restores a container -func (c *Container) Restore(ctx context.Context, keep bool) (err error) { - logrus.Debugf("Trying to restore container %s", c) +func (c *Container) Restore(ctx context.Context, options ContainerCheckpointOptions) (err error) { + logrus.Debugf("Trying to restore container %s", c.ID()) if !c.batched { c.lock.Lock() defer c.lock.Unlock() @@ -853,5 +872,5 @@ func (c *Container) Restore(ctx context.Context, keep bool) (err error) { } } - return c.restore(ctx, keep) + return c.restore(ctx, options) } diff --git a/libpod/container_attach.go b/libpod/container_attach.go index 3c4e0775d..f925c3897 100644 --- a/libpod/container_attach.go +++ b/libpod/container_attach.go @@ -16,6 +16,10 @@ import ( "k8s.io/client-go/tools/remotecommand" ) +//#include <sys/un.h> +// extern int unix_path_length(){struct sockaddr_un addr; return sizeof(addr.sun_path) - 1;} +import "C" + /* Sync with stdpipe_t in conmon.c */ const ( AttachPipeStdin = 1 @@ -81,11 +85,19 @@ func (c *Container) attachContainerSocket(resize <-chan remotecommand.TerminalSi logrus.Warnf("Failed to write to control file to resize terminal: %v", err) } }) - logrus.Debug("connecting to socket ", c.AttachSocketPath()) - conn, err := net.DialUnix("unixpacket", nil, &net.UnixAddr{Name: c.AttachSocketPath(), Net: "unixpacket"}) + socketPath := c.AttachSocketPath() + + maxUnixLength := int(C.unix_path_length()) + if maxUnixLength < len(socketPath) { + socketPath = socketPath[0:maxUnixLength] + } + + logrus.Debug("connecting to socket ", socketPath) + + conn, err := net.DialUnix("unixpacket", nil, &net.UnixAddr{Name: socketPath, Net: "unixpacket"}) if err != nil { - return errors.Wrapf(err, "failed to connect to container's attach socket: %v", c.AttachSocketPath()) + return errors.Wrapf(err, "failed to connect to container's attach socket: %v", socketPath) } defer conn.Close() diff --git a/libpod/container_easyjson.go b/libpod/container_easyjson.go index 041cc08ac..8bf5cb64f 100644 --- a/libpod/container_easyjson.go +++ b/libpod/container_easyjson.go @@ -8,6 +8,7 @@ import ( json "encoding/json" types "github.com/containernetworking/cni/pkg/types" current "github.com/containernetworking/cni/pkg/types/current" + namespaces "github.com/containers/libpod/pkg/namespaces" storage "github.com/containers/storage" idtools "github.com/containers/storage/pkg/idtools" ocicni "github.com/cri-o/ocicni/pkg/ocicni" @@ -1550,6 +1551,8 @@ func easyjson1dbef17bDecodeGithubComContainersLibpodLibpod2(in *jlexer.Lexer, ou } in.Delim(']') } + case "networkMode": + out.NetMode = namespaces.NetworkMode(in.String()) case "userVolumes": if in.IsNull() { in.Skip() @@ -2177,6 +2180,16 @@ func easyjson1dbef17bEncodeGithubComContainersLibpodLibpod2(out *jwriter.Writer, out.RawByte(']') } } + if in.NetMode != "" { + const prefix string = ",\"networkMode\":" + if first { + first = false + out.RawString(prefix[1:]) + } else { + out.RawString(prefix) + } + out.String(string(in.NetMode)) + } if len(in.UserVolumes) != 0 { const prefix string = ",\"userVolumes\":" if first { diff --git a/libpod/container_graph_test.go b/libpod/container_graph_test.go index bba3d7aad..25461f1f4 100644 --- a/libpod/container_graph_test.go +++ b/libpod/container_graph_test.go @@ -205,6 +205,7 @@ func TestBuildContainerGraphFourContainersNoEdges(t *testing.T) { ctr3, err := getTestCtrN("3", tmpDir) assert.NoError(t, err) ctr4, err := getTestCtrN("4", tmpDir) + assert.NoError(t, err) graph, err := buildContainerGraph([]*Container{ctr1, ctr2, ctr3, ctr4}) assert.NoError(t, err) @@ -241,6 +242,7 @@ func TestBuildContainerGraphFourContainersTwoInCycle(t *testing.T) { ctr3, err := getTestCtrN("3", tmpDir) assert.NoError(t, err) ctr4, err := getTestCtrN("4", tmpDir) + assert.NoError(t, err) ctr1.config.IPCNsCtr = ctr2.config.ID ctr2.config.UserNsCtr = ctr1.config.ID @@ -260,6 +262,7 @@ func TestBuildContainerGraphFourContainersAllInCycle(t *testing.T) { ctr3, err := getTestCtrN("3", tmpDir) assert.NoError(t, err) ctr4, err := getTestCtrN("4", tmpDir) + assert.NoError(t, err) ctr1.config.IPCNsCtr = ctr2.config.ID ctr2.config.UserNsCtr = ctr3.config.ID ctr3.config.NetNsCtr = ctr4.config.ID @@ -281,6 +284,7 @@ func TestBuildContainerGraphFourContainersNoneInCycle(t *testing.T) { ctr3, err := getTestCtrN("3", tmpDir) assert.NoError(t, err) ctr4, err := getTestCtrN("4", tmpDir) + assert.NoError(t, err) ctr1.config.IPCNsCtr = ctr2.config.ID ctr1.config.NetNsCtr = ctr3.config.ID ctr2.config.UserNsCtr = ctr3.config.ID diff --git a/libpod/container_internal.go b/libpod/container_internal.go index 2af216358..934ad7a22 100644 --- a/libpod/container_internal.go +++ b/libpod/container_internal.go @@ -12,14 +12,14 @@ import ( "strconv" "strings" "syscall" + "time" "github.com/containers/buildah/imagebuildah" - "github.com/containers/libpod/pkg/chrootuser" + "github.com/containers/libpod/pkg/ctime" "github.com/containers/libpod/pkg/hooks" "github.com/containers/libpod/pkg/hooks/exec" - "github.com/containers/libpod/pkg/resolvconf" + "github.com/containers/libpod/pkg/lookup" "github.com/containers/libpod/pkg/rootless" - "github.com/containers/libpod/pkg/secrets" "github.com/containers/storage" "github.com/containers/storage/pkg/archive" "github.com/containers/storage/pkg/chrootarchive" @@ -30,6 +30,7 @@ import ( "github.com/pkg/errors" "github.com/sirupsen/logrus" "golang.org/x/text/language" + kwait "k8s.io/apimachinery/pkg/util/wait" ) const ( @@ -146,6 +147,77 @@ func (c *Container) execPidPath(sessionID string) string { return filepath.Join(c.state.RunDir, "exec_pid_"+sessionID) } +// exitFilePath gets the path to the container's exit file +func (c *Container) exitFilePath() string { + return filepath.Join(c.runtime.ociRuntime.exitsDir, c.ID()) +} + +// Wait for the container's exit file to appear. +// When it does, update our state based on it. +func (c *Container) waitForExitFileAndSync() error { + exitFile := c.exitFilePath() + + err := kwait.ExponentialBackoff( + kwait.Backoff{ + Duration: 500 * time.Millisecond, + Factor: 1.2, + Steps: 6, + }, + func() (bool, error) { + _, err := os.Stat(exitFile) + if err != nil { + // wait longer + return false, nil + } + return true, nil + }) + if err != nil { + // Exit file did not appear + // Reset our state + c.state.ExitCode = -1 + c.state.FinishedTime = time.Now() + c.state.State = ContainerStateStopped + + if err2 := c.save(); err2 != nil { + logrus.Errorf("Error saving container %s state: %v", c.ID(), err2) + } + + return err + } + + if err := c.runtime.ociRuntime.updateContainerStatus(c, false); err != nil { + return err + } + + return c.save() +} + +// Handle the container exit file. +// The exit file is used to supply container exit time and exit code. +// This assumes the exit file already exists. +func (c *Container) handleExitFile(exitFile string, fi os.FileInfo) error { + c.state.FinishedTime = ctime.Created(fi) + statusCodeStr, err := ioutil.ReadFile(exitFile) + if err != nil { + return errors.Wrapf(err, "failed to read exit file for container %s", c.ID()) + } + statusCode, err := strconv.Atoi(string(statusCodeStr)) + if err != nil { + return errors.Wrapf(err, "error converting exit status code (%q) for container %s to int", + c.ID(), statusCodeStr) + } + c.state.ExitCode = int32(statusCode) + + oomFilePath := filepath.Join(c.bundlePath(), "oom") + if _, err = os.Stat(oomFilePath); err == nil { + c.state.OOMKilled = true + } + + c.state.Exited = true + + return nil +} + // Sync this container with on-disk state and runtime status // Should only be called with container lock held // This function should suffice to ensure a container's state is accurate and @@ -161,7 +233,7 @@ func (c *Container) syncContainer() error { (c.state.State != ContainerStateExited) { oldState := c.state.State // TODO: optionally replace this with a stat for the exit file - if err := c.runtime.ociRuntime.updateContainerStatus(c); err != nil { + if err := c.runtime.ociRuntime.updateContainerStatus(c, false); err != nil { return err } // Only save back to DB if state changed @@ -201,6 +273,27 @@ func (c *Container) setupStorage(ctx context.Context) error { }, LabelOpts: c.config.LabelOpts, } + if c.config.Privileged { + privOpt := func(opt string) bool { + for _, privopt := range []string{"nodev", "nosuid", "noexec"} { + if opt == privopt { + return true + } + } + return false + } + defOptions, err := storage.GetDefaultMountOptions() + if err != nil { + return errors.Wrapf(err, "error getting default mount options") + } + var newOptions []string + for _, opt := range defOptions { + if !privOpt(opt) { + newOptions = append(newOptions, opt) + } + } + options.MountOpts = newOptions + } if c.config.Rootfs == "" { options.IDMappingOptions = c.config.IDMappings @@ -508,13 +601,13 @@ func (c *Container) checkDependenciesRunningLocked(depCtrs map[string]*Container } func (c *Container) completeNetworkSetup() error { - if !c.config.PostConfigureNetNS { + if !c.config.PostConfigureNetNS || c.NetworkDisabled() { return nil } if err := c.syncContainer(); err != nil { return err } - if rootless.IsRootless() { + if c.config.NetMode == "slirp4netns" { return c.runtime.setupRootlessNetNS(c) } return c.runtime.setupNetNS(c) @@ -522,10 +615,6 @@ func (c *Container) completeNetworkSetup() error { // Initialize a container, creating it in the runtime func (c *Container) init(ctx context.Context) error { - if err := c.makeBindMounts(); err != nil { - return err - } - // Generate the OCI spec spec, err := c.generateSpec(ctx) if err != nil { @@ -538,7 +627,7 @@ func (c *Container) init(ctx context.Context) error { } // With the spec complete, do an OCI create - if err := c.runtime.ociRuntime.createContainer(c, c.config.CgroupParent, false); err != nil { + if err := c.runtime.ociRuntime.createContainer(c, c.config.CgroupParent, nil); err != nil { return err } @@ -622,9 +711,6 @@ func (c *Container) initAndStart(ctx context.Context) (err error) { return errors.Wrapf(ErrCtrStateInvalid, "cannot start paused container %s", c.ID()) } - if err := c.prepare(); err != nil { - return err - } defer func() { if err != nil { if err2 := c.cleanup(ctx); err2 != nil { @@ -633,6 +719,10 @@ func (c *Container) initAndStart(ctx context.Context) (err error) { } }() + if err := c.prepare(); err != nil { + return err + } + // If we are ContainerStateStopped we need to remove from runtime // And reset to ContainerStateConfigured if c.state.State == ContainerStateStopped { @@ -672,13 +762,8 @@ func (c *Container) stop(timeout uint) error { return err } - // Sync the container's state to pick up return code - if err := c.runtime.ociRuntime.updateContainerStatus(c); err != nil { - return err - } - - // Container should clean itself up - return nil + // Wait until we have an exit file, and sync once we do + return c.waitForExitFileAndSync() } // Internal, non-locking function to pause a container @@ -718,9 +803,6 @@ func (c *Container) restartWithTimeout(ctx context.Context, timeout uint) (err e return err } } - if err := c.prepare(); err != nil { - return err - } defer func() { if err != nil { if err2 := c.cleanup(ctx); err2 != nil { @@ -728,6 +810,9 @@ func (c *Container) restartWithTimeout(ctx context.Context, timeout uint) (err e } } }() + if err := c.prepare(); err != nil { + return err + } if c.state.State == ContainerStateStopped { // Reinitialize the container if we need to @@ -757,28 +842,22 @@ func (c *Container) mountStorage() (string, error) { return c.state.Mountpoint, nil } - if !rootless.IsRootless() { - // 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) - } + 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 := c.mountSHM(shmOptions); err != nil { + return "", err + } if err := os.Chown(c.config.ShmDir, c.RootUID(), c.RootGID()); err != nil { return "", errors.Wrapf(err, "failed to chown %s", c.config.ShmDir) } - - if !mounted { - shmOptions := fmt.Sprintf("mode=1777,size=%d", c.config.ShmSize) - if err := c.mountSHM(shmOptions); err != nil { - return "", err - } - if err := os.Chown(c.config.ShmDir, c.RootUID(), c.RootGID()); err != nil { - return "", errors.Wrapf(err, "failed to chown %s", c.config.ShmDir) - } - } } + // TODO: generalize this mount code so it will mount every mount in ctr.config.Mounts mountPoint := c.config.Rootfs if mountPoint == "" { mountPoint, err = c.mount() @@ -916,86 +995,6 @@ func (c *Container) postDeleteHooks(ctx context.Context) (err error) { return nil } -// Make standard bind mounts to include in the container -func (c *Container) makeBindMounts() error { - if err := os.Chown(c.state.RunDir, c.RootUID(), c.RootGID()); err != nil { - return errors.Wrapf(err, "cannot chown run directory %s", c.state.RunDir) - } - - if c.state.BindMounts == nil { - c.state.BindMounts = make(map[string]string) - } - - // SHM is always added when we mount the container - c.state.BindMounts["/dev/shm"] = c.config.ShmDir - - // Make /etc/resolv.conf - if _, ok := c.state.BindMounts["/etc/resolv.conf"]; ok { - // If it already exists, delete so we can recreate - delete(c.state.BindMounts, "/etc/resolv.conf") - } - newResolv, err := c.generateResolvConf() - if err != nil { - return errors.Wrapf(err, "error creating resolv.conf for container %s", c.ID()) - } - c.state.BindMounts["/etc/resolv.conf"] = newResolv - - newPasswd, err := c.generatePasswd() - if err != nil { - return errors.Wrapf(err, "error creating temporary passwd file for container %s", c.ID()) - } - if newPasswd != "" { - // Make /etc/passwd - if _, ok := c.state.BindMounts["/etc/passwd"]; ok { - // If it already exists, delete so we can recreate - delete(c.state.BindMounts, "/etc/passwd") - } - logrus.Debugf("adding entry to /etc/passwd for non existent default user") - c.state.BindMounts["/etc/passwd"] = newPasswd - } - // Make /etc/hosts - if _, ok := c.state.BindMounts["/etc/hosts"]; ok { - // If it already exists, delete so we can recreate - delete(c.state.BindMounts, "/etc/hosts") - } - newHosts, err := c.generateHosts() - if err != nil { - return errors.Wrapf(err, "error creating hosts file for container %s", c.ID()) - } - c.state.BindMounts["/etc/hosts"] = newHosts - - // Make /etc/hostname - // This should never change, so no need to recreate if it exists - if _, ok := c.state.BindMounts["/etc/hostname"]; !ok { - hostnamePath, err := c.writeStringToRundir("hostname", c.Hostname()) - if err != nil { - return errors.Wrapf(err, "error creating hostname file for container %s", c.ID()) - } - c.state.BindMounts["/etc/hostname"] = hostnamePath - } - - // Make .containerenv - // Empty file, so no need to recreate if it exists - if _, ok := c.state.BindMounts["/run/.containerenv"]; !ok { - // Empty string for now, but we may consider populating this later - containerenvPath, err := c.writeStringToRundir(".containerenv", "") - if err != nil { - return errors.Wrapf(err, "error creating containerenv file for container %s", c.ID()) - } - c.state.BindMounts["/run/.containerenv"] = containerenvPath - } - - // Add Secret Mounts - secretMounts := secrets.SecretMountsWithUIDGID(c.config.MountLabel, c.state.RunDir, c.runtime.config.DefaultMountsFile, c.state.DestinationRunDir, c.RootUID(), c.RootGID()) - for _, mount := range secretMounts { - if _, ok := c.state.BindMounts[mount.Destination]; !ok { - c.state.BindMounts[mount.Destination] = mount.Source - } - } - - return nil -} - // writeStringToRundir copies the provided file to the runtimedir func (c *Container) writeStringToRundir(destFile, output string) (string, error) { destFileName := filepath.Join(c.state.RunDir, destFile) @@ -1024,135 +1023,8 @@ func (c *Container) writeStringToRundir(destFile, output string) (string, error) return filepath.Join(c.state.DestinationRunDir, destFile), nil } -// generatePasswd generates a container specific passwd file, -// iff g.config.User is a number -func (c *Container) generatePasswd() (string, error) { - var ( - groupspec string - gid uint32 - ) - if c.config.User == "" { - return "", nil - } - spec := strings.SplitN(c.config.User, ":", 2) - userspec := spec[0] - if len(spec) > 1 { - groupspec = spec[1] - } - // If a non numeric User, then don't generate passwd - uid, err := strconv.ParseUint(userspec, 10, 32) - if err != nil { - return "", nil - } - // if UID exists inside of container rootfs /etc/passwd then - // don't generate passwd - if _, _, err := chrootuser.LookupUIDInContainer(c.state.Mountpoint, uid); err == nil { - return "", nil - } - if err == nil && groupspec != "" { - if !c.state.Mounted { - return "", errors.Wrapf(ErrCtrStateInvalid, "container %s must be mounted in order to translate group field for passwd record", c.ID()) - } - gid, err = chrootuser.GetGroup(c.state.Mountpoint, groupspec) - if err != nil { - return "", errors.Wrapf(err, "unable to get gid from %s formporary passwd file") - } - } - - originPasswdFile := filepath.Join(c.state.Mountpoint, "/etc/passwd") - orig, err := ioutil.ReadFile(originPasswdFile) - if err != nil { - return "", errors.Wrapf(err, "unable to read passwd file %s", originPasswdFile) - } - - pwd := fmt.Sprintf("%s%d:x:%d:%d:container user:%s:/bin/sh\n", orig, uid, uid, gid, c.WorkingDir()) - passwdFile, err := c.writeStringToRundir("passwd", pwd) - if err != nil { - return "", errors.Wrapf(err, "failed to create temporary passwd file") - } - if os.Chmod(passwdFile, 0644); err != nil { - return "", err - } - return passwdFile, nil -} - -// generateResolvConf generates a containers resolv.conf -func (c *Container) generateResolvConf() (string, error) { - // Determine the endpoint for resolv.conf in case it is a symlink - resolvPath, err := filepath.EvalSymlinks("/etc/resolv.conf") - if err != nil { - return "", err - } - - contents, err := ioutil.ReadFile(resolvPath) - if err != nil { - return "", errors.Wrapf(err, "unable to read %s", resolvPath) - } - - // Process the file to remove localhost nameservers - // TODO: set ipv6 enable bool more sanely - resolv, err := resolvconf.FilterResolvDNS(contents, true) - if err != nil { - return "", errors.Wrapf(err, "error parsing host resolv.conf") - } - - // Make a new resolv.conf - nameservers := resolvconf.GetNameservers(resolv.Content) - if len(c.config.DNSServer) > 0 { - // We store DNS servers as net.IP, so need to convert to string - nameservers = []string{} - for _, server := range c.config.DNSServer { - nameservers = append(nameservers, server.String()) - } - } - - search := resolvconf.GetSearchDomains(resolv.Content) - if len(c.config.DNSSearch) > 0 { - search = c.config.DNSSearch - } - - options := resolvconf.GetOptions(resolv.Content) - if len(c.config.DNSOption) > 0 { - options = c.config.DNSOption - } - - destPath := filepath.Join(c.state.RunDir, "resolv.conf") - - if err := os.Remove(destPath); err != nil && !os.IsNotExist(err) { - return "", errors.Wrapf(err, "error removing resolv.conf for container %s", c.ID()) - } - - // Build resolv.conf - if _, err = resolvconf.Build(destPath, nameservers, search, options); err != nil { - return "", errors.Wrapf(err, "error building resolv.conf for container %s") - } - - // Relabel resolv.conf for the container - if err := label.Relabel(destPath, c.config.MountLabel, false); err != nil { - return "", err - } - - return filepath.Join(c.state.DestinationRunDir, "resolv.conf"), nil -} - -// 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.SplitN(host, ":", 2) - hosts += fmt.Sprintf("%s %s\n", fields[1], fields[0]) - } - } - return c.writeStringToRundir("hosts", hosts) -} - func (c *Container) addLocalVolumes(ctx context.Context, g *generate.Generator) error { + var uid, gid int mountPoint := c.state.Mountpoint if !c.state.Mounted { return errors.Wrapf(ErrInternal, "container is not mounted") @@ -1176,6 +1048,18 @@ func (c *Container) addLocalVolumes(ctx context.Context, g *generate.Generator) } } + 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()) + } + execUser, err := lookup.GetUserGroupInfo(c.state.Mountpoint, c.config.User, nil) + if err != nil { + return err + } + uid = execUser.Uid + gid = execUser.Gid + } + for k := range imageData.ContainerConfig.Volumes { mount := spec.Mount{ Destination: k, @@ -1186,19 +1070,6 @@ func (c *Container) addLocalVolumes(ctx context.Context, g *generate.Generator) continue } volumePath := filepath.Join(c.config.StaticDir, "volumes", k) - var ( - uid uint32 - gid uint32 - ) - 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 - } - } // Ensure the symlinks are resolved resolvedSymlink, err := imagebuildah.ResolveSymLink(mountPoint, k) @@ -1218,7 +1089,7 @@ func (c *Container) addLocalVolumes(ctx context.Context, g *generate.Generator) return errors.Wrapf(err, "error creating directory %q for volume %q in container %q", volumePath, k, c.ID()) } - if err = os.Chown(srcPath, int(uid), int(gid)); err != nil { + if err = os.Chown(srcPath, uid, gid); err != nil { return errors.Wrapf(err, "error chowning directory %q for volume %q in container %q", srcPath, k, c.ID()) } } @@ -1228,7 +1099,7 @@ func (c *Container) addLocalVolumes(ctx context.Context, g *generate.Generator) return errors.Wrapf(err, "error creating directory %q for volume %q in container %q", volumePath, k, c.ID()) } - if err = os.Chown(volumePath, int(uid), int(gid)); err != nil { + if err = os.Chown(volumePath, uid, gid); err != nil { return errors.Wrapf(err, "error chowning directory %q for volume %q in container %q", volumePath, k, c.ID()) } @@ -1297,10 +1168,6 @@ func (c *Container) saveSpec(spec *spec.Spec) error { } func (c *Container) setupOCIHooks(ctx context.Context, config *spec.Spec) (extensionStageHooks map[string][]spec.Hook, err error) { - if len(c.runtime.config.HooksDir) == 0 { - return nil, nil - } - var locale string var ok bool for _, envVar := range []string{ @@ -1328,25 +1195,39 @@ func (c *Container) setupOCIHooks(ctx context.Context, config *spec.Spec) (exten } } - allHooks := make(map[string][]spec.Hook) - for _, hDir := range c.runtime.config.HooksDir { - manager, err := hooks.New(ctx, []string{hDir}, []string{"poststop"}, lang) - if err != nil { - if c.runtime.config.HooksDirNotExistFatal || !os.IsNotExist(err) { - return nil, err - } - logrus.Warnf("failed to load hooks: {}", err) + if c.runtime.config.HooksDir == nil { + if rootless.IsRootless() { return nil, nil } - hooks, err := manager.Hooks(config, c.Spec().Annotations, len(c.config.UserVolumes) > 0) - if err != nil { - return nil, err - } - for i, hook := range hooks { - allHooks[i] = hook + allHooks := make(map[string][]spec.Hook) + for _, hDir := range []string{hooks.DefaultDir, hooks.OverrideDir} { + manager, err := hooks.New(ctx, []string{hDir}, []string{"poststop"}, lang) + if err != nil { + if os.IsNotExist(err) { + continue + } + return nil, err + } + hooks, err := manager.Hooks(config, c.Spec().Annotations, len(c.config.UserVolumes) > 0) + if err != nil { + return nil, err + } + if len(hooks) > 0 || config.Hooks != nil { + logrus.Warnf("implicit hook directories are deprecated; set --hooks-dir=%q explicitly to continue to load hooks from this directory", hDir) + } + for i, hook := range hooks { + allHooks[i] = hook + } } + return allHooks, nil } - return allHooks, nil + + manager, err := hooks.New(ctx, c.runtime.config.HooksDir, []string{"poststop"}, lang) + if err != nil { + return nil, err + } + + return manager.Hooks(config, c.Spec().Annotations, len(c.config.UserVolumes) > 0) } // mount mounts the container's root filesystem diff --git a/libpod/container_internal_linux.go b/libpod/container_internal_linux.go index 0a1784ba7..b540bbeb8 100644 --- a/libpod/container_internal_linux.go +++ b/libpod/container_internal_linux.go @@ -11,6 +11,7 @@ import ( "os" "path" "path/filepath" + "strconv" "strings" "sync" "syscall" @@ -19,11 +20,12 @@ import ( cnitypes "github.com/containernetworking/cni/pkg/types/current" "github.com/containernetworking/plugins/pkg/ns" crioAnnotations "github.com/containers/libpod/pkg/annotations" - "github.com/containers/libpod/pkg/chrootuser" "github.com/containers/libpod/pkg/criu" + "github.com/containers/libpod/pkg/lookup" + "github.com/containers/libpod/pkg/resolvconf" "github.com/containers/libpod/pkg/rootless" + "github.com/containers/libpod/pkg/secrets" "github.com/containers/storage/pkg/idtools" - "github.com/cyphar/filepath-securejoin" "github.com/opencontainers/runc/libcontainer/user" spec "github.com/opencontainers/runtime-spec/specs-go" "github.com/opencontainers/runtime-tools/generate" @@ -59,7 +61,7 @@ func (c *Container) prepare() (err error) { networkStatus []*cnitypes.Result createNetNSErr, mountStorageErr error mountPoint string - saveNetworkStatus bool + tmpStateLock sync.Mutex ) wg.Add(2) @@ -68,17 +70,55 @@ func (c *Container) prepare() (err error) { defer wg.Done() // Set up network namespace if not already set up if c.config.CreateNetNS && c.state.NetNS == nil && !c.config.PostConfigureNetNS { - saveNetworkStatus = true netNS, networkStatus, createNetNSErr = c.runtime.createNetNS(c) + + tmpStateLock.Lock() + defer tmpStateLock.Unlock() + + // Assign NetNS attributes to container + if createNetNSErr == nil { + c.state.NetNS = netNS + c.state.NetworkStatus = networkStatus + } } }() // Mount storage if not mounted go func() { defer wg.Done() mountPoint, mountStorageErr = c.mountStorage() + + if mountStorageErr != nil { + return + } + + tmpStateLock.Lock() + defer tmpStateLock.Unlock() + + // Finish up mountStorage + c.state.Mounted = true + c.state.Mountpoint = mountPoint + if c.state.UserNSRoot == "" { + c.state.RealMountpoint = c.state.Mountpoint + } else { + c.state.RealMountpoint = filepath.Join(c.state.UserNSRoot, "mountpoint") + } + + logrus.Debugf("Created root filesystem for container %s at %s", c.ID(), c.state.Mountpoint) + }() + + defer func() { + if err != nil { + if err2 := c.cleanupNetwork(); err2 != nil { + logrus.Errorf("Error cleaning up container %s network: %v", c.ID(), err2) + } + if err2 := c.cleanupStorage(); err2 != nil { + logrus.Errorf("Error cleaning up container %s storage: %v", c.ID(), err2) + } + } }() wg.Wait() + if createNetNSErr != nil { if mountStorageErr != nil { logrus.Error(createNetNSErr) @@ -90,28 +130,15 @@ func (c *Container) prepare() (err error) { return mountStorageErr } - // Assign NetNS attributes to container - if saveNetworkStatus { - c.state.NetNS = netNS - c.state.NetworkStatus = networkStatus - } - - // Finish up mountStorage - c.state.Mounted = true - c.state.Mountpoint = mountPoint - if c.state.UserNSRoot == "" { - c.state.RealMountpoint = c.state.Mountpoint - } else { - c.state.RealMountpoint = filepath.Join(c.state.UserNSRoot, "mountpoint") - } - - logrus.Debugf("Created root filesystem for container %s at %s", c.ID(), c.state.Mountpoint) // Save the container return c.save() } // cleanupNetwork unmounts and cleans up the container's network func (c *Container) cleanupNetwork() error { + if c.NetworkDisabled() { + return nil + } if c.state.NetNS == nil { logrus.Debugf("Network is already cleaned up, skipping...") return nil @@ -135,6 +162,10 @@ func (c *Container) cleanupNetwork() error { // Generate spec for a container // Accepts a map of the container's dependencies func (c *Container) generateSpec(ctx context.Context) (*spec.Spec, error) { + execUser, err := lookup.GetUserGroupInfo(c.state.Mountpoint, c.config.User, nil) + if err != nil { + return nil, err + } g := generate.NewFromSpec(c.config.Spec) // If network namespace was requested, add it now @@ -145,6 +176,11 @@ func (c *Container) generateSpec(ctx context.Context) (*spec.Spec, error) { g.AddOrReplaceLinuxNamespace(spec.NetworkNamespace, c.state.NetNS.Path()) } } + + if err := c.makeBindMounts(); err != nil { + return nil, err + } + // Check if the spec file mounts contain the label Relabel flags z or Z. // If they do, relabel the source directory and then remove the option. for _, m := range g.Mounts() { @@ -188,11 +224,8 @@ func (c *Container) generateSpec(ctx context.Context) (*spec.Spec, error) { } } - var err error - if !rootless.IsRootless() { - if c.state.ExtensionStageHooks, err = c.setupOCIHooks(ctx, g.Config); err != nil { - return nil, errors.Wrapf(err, "error setting up OCI Hooks") - } + if c.state.ExtensionStageHooks, err = c.setupOCIHooks(ctx, g.Config); err != nil { + return nil, errors.Wrapf(err, "error setting up OCI Hooks") } // Bind builtin image volumes @@ -203,28 +236,15 @@ func (c *Container) generateSpec(ctx context.Context) (*spec.Spec, error) { } if c.config.User != "" { - if !c.state.Mounted { - return nil, 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 nil, err - } // User and Group must go together - g.SetProcessUID(uid) - g.SetProcessGID(gid) + g.SetProcessUID(uint32(execUser.Uid)) + g.SetProcessGID(uint32(execUser.Gid)) } // Add addition groups if c.config.GroupAdd is not empty if len(c.config.Groups) > 0 { - if !c.state.Mounted { - return nil, errors.Wrapf(ErrCtrStateInvalid, "container %s must be mounted in order to add additional groups", c.ID()) - } - for _, group := range c.config.Groups { - gid, err := chrootuser.GetGroup(c.state.Mountpoint, group) - if err != nil { - return nil, err - } + gids, _ := lookup.GetContainerGroups(c.config.Groups, c.state.Mountpoint, nil) + for _, gid := range gids { g.AddProcessAdditionalGid(gid) } } @@ -237,26 +257,6 @@ func (c *Container) generateSpec(ctx context.Context) (*spec.Spec, error) { // Look up and add groups the user belongs to, if a group wasn't directly specified if !rootless.IsRootless() && !strings.Contains(c.config.User, ":") { - var groupDest, passwdDest string - defaultExecUser := user.ExecUser{ - Uid: 0, - Gid: 0, - Home: "/", - } - - // Make sure the /etc/group and /etc/passwd destinations are not a symlink to something naughty - if groupDest, err = securejoin.SecureJoin(c.state.Mountpoint, "/etc/group"); err != nil { - logrus.Debug(err) - return nil, err - } - if passwdDest, err = securejoin.SecureJoin(c.state.Mountpoint, "/etc/passwd"); err != nil { - logrus.Debug(err) - return nil, err - } - execUser, err := user.GetExecUserPath(c.config.User, &defaultExecUser, passwdDest, groupDest) - if err != nil { - return nil, err - } for _, gid := range execUser.Sgids { g.AddProcessAdditionalGid(uint32(gid)) } @@ -351,8 +351,34 @@ func (c *Container) generateSpec(ctx context.Context) (*spec.Spec, error) { // Mounts need to be sorted so paths will not cover other paths mounts := sortMounts(g.Mounts()) g.ClearMounts() + + // Determine property of RootPropagation based on volume properties. If + // a volume is shared, then keep root propagation shared. This should + // work for slave and private volumes too. + // + // For slave volumes, it can be either [r]shared/[r]slave. + // + // For private volumes any root propagation value should work. + rootPropagation := "" for _, m := range mounts { g.AddMount(m) + for _, opt := range m.Options { + switch opt { + case MountShared, MountRShared: + if rootPropagation != MountShared && rootPropagation != MountRShared { + rootPropagation = MountShared + } + case MountSlave, MountRSlave: + if rootPropagation != MountShared && rootPropagation != MountRShared && rootPropagation != MountSlave && rootPropagation != MountRSlave { + rootPropagation = MountRSlave + } + } + } + } + + if rootPropagation != "" { + logrus.Debugf("set root propagation to %q", rootPropagation) + g.SetLinuxRootPropagation(rootPropagation) } return g.Config, nil } @@ -386,19 +412,31 @@ func (c *Container) setupSystemd(mounts []spec.Mount, g generate.Generator) erro g.AddMount(tmpfsMnt) } - cgroupPath, err := c.CGroupPath() - if err != nil { - return err - } - sourcePath := filepath.Join("/sys/fs/cgroup/systemd", cgroupPath) + // rootless containers have no write access to /sys/fs/cgroup, so don't + // add any mount into the container. + if !rootless.IsRootless() { + cgroupPath, err := c.CGroupPath() + if err != nil { + return err + } + sourcePath := filepath.Join("/sys/fs/cgroup/systemd", cgroupPath) - systemdMnt := spec.Mount{ - Destination: "/sys/fs/cgroup/systemd", - Type: "bind", - Source: sourcePath, - Options: []string{"bind", "private"}, + systemdMnt := spec.Mount{ + Destination: "/sys/fs/cgroup/systemd", + Type: "bind", + Source: sourcePath, + Options: []string{"bind", "private"}, + } + g.AddMount(systemdMnt) + } else { + systemdMnt := spec.Mount{ + Destination: "/sys/fs/cgroup/systemd", + Type: "bind", + Source: "/sys/fs/cgroup/systemd", + Options: []string{"bind", "nodev", "noexec", "nosuid"}, + } + g.AddMount(systemdMnt) } - g.AddMount(systemdMnt) return nil } @@ -423,7 +461,7 @@ func (c *Container) addNamespaceContainer(g *generate.Generator, ns LinuxNS, ctr return nil } -func (c *Container) checkpoint(ctx context.Context, keep bool) (err error) { +func (c *Container) checkpoint(ctx context.Context, options ContainerCheckpointOptions) (err error) { if !criu.CheckForCriu() { return errors.Errorf("checkpointing a container requires at least CRIU %d", criu.MinCriuVersion) @@ -432,7 +470,7 @@ func (c *Container) checkpoint(ctx context.Context, keep bool) (err error) { if c.state.State != ContainerStateRunning { return errors.Wrapf(ErrCtrStateInvalid, "%q is not running, cannot checkpoint", c.state.State) } - if err := c.runtime.ociRuntime.checkpointContainer(c); err != nil { + if err := c.runtime.ociRuntime.checkpointContainer(c, options); err != nil { return err } @@ -449,14 +487,16 @@ func (c *Container) checkpoint(ctx context.Context, keep bool) (err error) { logrus.Debugf("Checkpointed container %s", c.ID()) - c.state.State = ContainerStateStopped + if !options.KeepRunning { + c.state.State = ContainerStateStopped - // Cleanup Storage and Network - if err := c.cleanup(ctx); err != nil { - return err + // Cleanup Storage and Network + if err := c.cleanup(ctx); err != nil { + return err + } } - if !keep { + if !options.Keep { // Remove log file os.Remove(filepath.Join(c.bundlePath(), "dump.log")) // Remove statistic file @@ -466,7 +506,7 @@ func (c *Container) checkpoint(ctx context.Context, keep bool) (err error) { return c.save() } -func (c *Container) restore(ctx context.Context, keep bool) (err error) { +func (c *Container) restore(ctx context.Context, options ContainerCheckpointOptions) (err error) { if !criu.CheckForCriu() { return errors.Errorf("restoring a container requires at least CRIU %d", criu.MinCriuVersion) @@ -510,9 +550,6 @@ func (c *Container) restore(ctx context.Context, keep bool) (err error) { } } - if err := c.prepare(); err != nil { - return err - } defer func() { if err != nil { if err2 := c.cleanup(ctx); err2 != nil { @@ -521,6 +558,10 @@ func (c *Container) restore(ctx context.Context, keep bool) (err error) { } }() + if err := c.prepare(); err != nil { + return err + } + // TODO: use existing way to request static IPs, once it is merged in ocicni // https://github.com/cri-o/ocicni/pull/23/ @@ -553,7 +594,7 @@ func (c *Container) restore(ctx context.Context, keep bool) (err error) { // Cleanup for a working restore. c.removeConmonFiles() - if err := c.runtime.ociRuntime.createContainer(c, c.config.CgroupParent, true); err != nil { + if err := c.runtime.ociRuntime.createContainer(c, c.config.CgroupParent, &options); err != nil { return err } @@ -561,7 +602,7 @@ func (c *Container) restore(ctx context.Context, keep bool) (err error) { c.state.State = ContainerStateRunning - if !keep { + if !options.Keep { // Delete all checkpoint related files. At this point, in theory, all files // should exist. Still ignoring errors for now as the container should be // restored and running. Not erroring out just because some cleanup operation @@ -582,3 +623,225 @@ func (c *Container) restore(ctx context.Context, keep bool) (err error) { return c.save() } + +// Make standard bind mounts to include in the container +func (c *Container) makeBindMounts() error { + if err := os.Chown(c.state.RunDir, c.RootUID(), c.RootGID()); err != nil { + return errors.Wrapf(err, "cannot chown run directory %s", c.state.RunDir) + } + + if c.state.BindMounts == nil { + c.state.BindMounts = make(map[string]string) + } + + if !c.NetworkDisabled() { + // Make /etc/resolv.conf + if _, ok := c.state.BindMounts["/etc/resolv.conf"]; ok { + // If it already exists, delete so we can recreate + delete(c.state.BindMounts, "/etc/resolv.conf") + } + newResolv, err := c.generateResolvConf() + if err != nil { + return errors.Wrapf(err, "error creating resolv.conf for container %s", c.ID()) + } + c.state.BindMounts["/etc/resolv.conf"] = newResolv + + // Make /etc/hosts + if _, ok := c.state.BindMounts["/etc/hosts"]; ok { + // If it already exists, delete so we can recreate + delete(c.state.BindMounts, "/etc/hosts") + } + newHosts, err := c.generateHosts() + if err != nil { + return errors.Wrapf(err, "error creating hosts file for container %s", c.ID()) + } + c.state.BindMounts["/etc/hosts"] = newHosts + + } + + // SHM is always added when we mount the container + c.state.BindMounts["/dev/shm"] = c.config.ShmDir + + newPasswd, err := c.generatePasswd() + if err != nil { + return errors.Wrapf(err, "error creating temporary passwd file for container %s", c.ID()) + } + if newPasswd != "" { + // Make /etc/passwd + if _, ok := c.state.BindMounts["/etc/passwd"]; ok { + // If it already exists, delete so we can recreate + delete(c.state.BindMounts, "/etc/passwd") + } + logrus.Debugf("adding entry to /etc/passwd for non existent default user") + c.state.BindMounts["/etc/passwd"] = newPasswd + } + + // Make /etc/hostname + // This should never change, so no need to recreate if it exists + if _, ok := c.state.BindMounts["/etc/hostname"]; !ok { + hostnamePath, err := c.writeStringToRundir("hostname", c.Hostname()) + if err != nil { + return errors.Wrapf(err, "error creating hostname file for container %s", c.ID()) + } + c.state.BindMounts["/etc/hostname"] = hostnamePath + } + + // Make .containerenv + // Empty file, so no need to recreate if it exists + if _, ok := c.state.BindMounts["/run/.containerenv"]; !ok { + // Empty string for now, but we may consider populating this later + containerenvPath, err := c.writeStringToRundir(".containerenv", "") + if err != nil { + return errors.Wrapf(err, "error creating containerenv file for container %s", c.ID()) + } + c.state.BindMounts["/run/.containerenv"] = containerenvPath + } + + // Add Secret Mounts + secretMounts := secrets.SecretMountsWithUIDGID(c.config.MountLabel, c.state.RunDir, c.runtime.config.DefaultMountsFile, c.state.DestinationRunDir, c.RootUID(), c.RootGID()) + for _, mount := range secretMounts { + if _, ok := c.state.BindMounts[mount.Destination]; !ok { + c.state.BindMounts[mount.Destination] = mount.Source + } + } + + return nil +} + +// generateResolvConf generates a containers resolv.conf +func (c *Container) generateResolvConf() (string, error) { + // Determine the endpoint for resolv.conf in case it is a symlink + resolvPath, err := filepath.EvalSymlinks("/etc/resolv.conf") + if err != nil { + return "", err + } + + contents, err := ioutil.ReadFile(resolvPath) + if err != nil { + return "", errors.Wrapf(err, "unable to read %s", resolvPath) + } + + // Ensure that the container's /etc/resolv.conf is compatible with its + // network configuration. + // TODO: set ipv6 enable bool more sanely + resolv, err := resolvconf.FilterResolvDNS(contents, true, c.config.CreateNetNS) + if err != nil { + return "", errors.Wrapf(err, "error parsing host resolv.conf") + } + + // Make a new resolv.conf + nameservers := resolvconf.GetNameservers(resolv.Content) + if len(c.config.DNSServer) > 0 { + // We store DNS servers as net.IP, so need to convert to string + nameservers = []string{} + for _, server := range c.config.DNSServer { + nameservers = append(nameservers, server.String()) + } + } + + search := resolvconf.GetSearchDomains(resolv.Content) + if len(c.config.DNSSearch) > 0 { + search = c.config.DNSSearch + } + + options := resolvconf.GetOptions(resolv.Content) + if len(c.config.DNSOption) > 0 { + options = c.config.DNSOption + } + + destPath := filepath.Join(c.state.RunDir, "resolv.conf") + + if err := os.Remove(destPath); err != nil && !os.IsNotExist(err) { + return "", errors.Wrapf(err, "error removing resolv.conf for container %s", c.ID()) + } + + // Build resolv.conf + if _, err = resolvconf.Build(destPath, nameservers, search, options); err != nil { + return "", errors.Wrapf(err, "error building resolv.conf for container %s", c.ID()) + } + + // Relabel resolv.conf for the container + if err := label.Relabel(destPath, c.config.MountLabel, false); err != nil { + return "", err + } + + return filepath.Join(c.state.DestinationRunDir, "resolv.conf"), nil +} + +// 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.SplitN(host, ":", 2) + hosts += fmt.Sprintf("%s %s\n", fields[1], fields[0]) + } + } + if len(c.state.NetworkStatus) > 0 && len(c.state.NetworkStatus[0].IPs) > 0 { + ipAddress := strings.Split(c.state.NetworkStatus[0].IPs[0].Address.String(), "/")[0] + hosts += fmt.Sprintf("%s\t%s\n", ipAddress, c.Hostname()) + } + return c.writeStringToRundir("hosts", hosts) +} + +// generatePasswd generates a container specific passwd file, +// iff g.config.User is a number +func (c *Container) generatePasswd() (string, error) { + var ( + groupspec string + gid int + ) + if c.config.User == "" { + return "", nil + } + spec := strings.SplitN(c.config.User, ":", 2) + userspec := spec[0] + if len(spec) > 1 { + groupspec = spec[1] + } + // If a non numeric User, then don't generate passwd + uid, err := strconv.ParseUint(userspec, 10, 32) + if err != nil { + return "", nil + } + // Lookup the user to see if it exists in the container image + _, err = lookup.GetUser(c.state.Mountpoint, userspec) + if err != nil && err != user.ErrNoPasswdEntries { + return "", err + } + if err == nil { + return "", nil + } + if groupspec != "" { + ugid, err := strconv.ParseUint(groupspec, 10, 32) + if err == nil { + gid = int(ugid) + } else { + group, err := lookup.GetGroup(c.state.Mountpoint, groupspec) + if err != nil { + return "", errors.Wrapf(err, "unable to get gid %s from group file", groupspec) + } + gid = group.Gid + } + } + originPasswdFile := filepath.Join(c.state.Mountpoint, "/etc/passwd") + orig, err := ioutil.ReadFile(originPasswdFile) + if err != nil && !os.IsNotExist(err) { + return "", errors.Wrapf(err, "unable to read passwd file %s", originPasswdFile) + } + + pwd := fmt.Sprintf("%s%d:x:%d:%d:container user:%s:/bin/sh\n", orig, uid, uid, gid, c.WorkingDir()) + passwdFile, err := c.writeStringToRundir("passwd", pwd) + if err != nil { + return "", errors.Wrapf(err, "failed to create temporary passwd file") + } + if os.Chmod(passwdFile, 0644); err != nil { + return "", err + } + return passwdFile, nil +} diff --git a/libpod/image/errors.go b/libpod/image/errors.go new file mode 100644 index 000000000..4088946cb --- /dev/null +++ b/libpod/image/errors.go @@ -0,0 +1,15 @@ +package image + +import ( + "errors" +) + +// Copied directly from libpod errors to avoid circular imports +var ( + // ErrNoSuchCtr indicates the requested container does not exist + ErrNoSuchCtr = errors.New("no such container") + // ErrNoSuchPod indicates the requested pod does not exist + ErrNoSuchPod = errors.New("no such pod") + // ErrNoSuchImage indicates the requested image does not exist + ErrNoSuchImage = errors.New("no such image") +) diff --git a/libpod/image/image.go b/libpod/image/image.go index 7e520d97e..434f9031e 100644 --- a/libpod/image/image.go +++ b/libpod/image/image.go @@ -252,7 +252,7 @@ func (i *Image) getLocalImage() (*storage.Image, error) { // The image has a registry name in it and we made sure we looked for it locally // with a tag. It cannot be local. if decomposedImage.hasRegistry { - return nil, errors.Errorf("%s", imageError) + return nil, errors.Wrapf(ErrNoSuchImage, imageError) } @@ -275,7 +275,7 @@ func (i *Image) getLocalImage() (*storage.Image, error) { return repoImage, nil } - return nil, errors.Wrapf(err, imageError) + return nil, errors.Wrapf(ErrNoSuchImage, err.Error()) } // ID returns the image ID as a string @@ -869,6 +869,7 @@ func (i *Image) Inspect(ctx context.Context) (*inspect.ImageData, error) { GraphDriver: driver, ManifestType: manifestType, User: ociv1Img.Config.User, + History: ociv1Img.History, } return data, nil } diff --git a/libpod/in_memory_state.go b/libpod/in_memory_state.go index 78e765ccd..77eba0cc6 100644 --- a/libpod/in_memory_state.go +++ b/libpod/in_memory_state.go @@ -73,6 +73,18 @@ func (s *InMemoryState) Refresh() error { return nil } +// GetDBConfig is not implemented for in-memory state. +// As we do not store a config, return an empty one. +func (s *InMemoryState) GetDBConfig() (*DBConfig, error) { + return &DBConfig{}, nil +} + +// ValidateDBConfig is not implemented for the in-memory state. +// Since we do nothing just return no error. +func (s *InMemoryState) ValidateDBConfig(runtime *Runtime) error { + return nil +} + // SetNamespace sets the namespace for container and pod retrieval. func (s *InMemoryState) SetNamespace(ns string) error { s.namespace = ns diff --git a/libpod/info.go b/libpod/info.go index 4cbf3f734..5d8d160c8 100644 --- a/libpod/info.go +++ b/libpod/info.go @@ -12,6 +12,7 @@ import ( "strings" "time" + "github.com/containers/libpod/pkg/rootless" "github.com/containers/libpod/utils" "github.com/containers/storage/pkg/system" "github.com/pkg/errors" @@ -30,6 +31,7 @@ func (r *Runtime) hostInfo() (map[string]interface{}, error) { info["os"] = runtime.GOOS info["arch"] = runtime.GOARCH info["cpus"] = runtime.NumCPU() + info["rootless"] = rootless.IsRootless() mi, err := system.ReadMemInfo() if err != nil { return nil, errors.Wrapf(err, "error reading memory info") diff --git a/libpod/kube.go b/libpod/kube.go new file mode 100644 index 000000000..1a5f80878 --- /dev/null +++ b/libpod/kube.go @@ -0,0 +1,270 @@ +package libpod + +import ( + "fmt" + "strings" + + "github.com/containers/libpod/pkg/lookup" + "github.com/containers/libpod/pkg/util" + "github.com/cri-o/ocicni/pkg/ocicni" + "github.com/opencontainers/runtime-spec/specs-go" + "github.com/pkg/errors" + "github.com/sirupsen/logrus" + "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/resource" + v12 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// InspectForKube takes a slice of libpod containers and generates +// one v1.Pod description that includes just a single container. +func (c *Container) InspectForKube() (*v1.Pod, error) { + // Generate the v1.Pod yaml description + return simplePodWithV1Container(c) +} + +// simplePodWithV1Container is a function used by inspect when kube yaml needs to be generated +// for a single container. we "insert" that container description in a pod. +func simplePodWithV1Container(ctr *Container) (*v1.Pod, error) { + var containers []v1.Container + result, err := containerToV1Container(ctr) + if err != nil { + return nil, err + } + containers = append(containers, result) + + tm := v12.TypeMeta{ + Kind: "Pod", + APIVersion: "v1", + } + + // Add a label called "app" with the containers name as a value + labels := make(map[string]string) + labels["app"] = removeUnderscores(ctr.Name()) + om := v12.ObjectMeta{ + // The name of the pod is container_name-libpod + Name: fmt.Sprintf("%s-libpod", removeUnderscores(ctr.Name())), + Labels: labels, + // CreationTimestamp seems to be required, so adding it; in doing so, the timestamp + // will reflect time this is run (not container create time) because the conversion + // of the container create time to v1 Time is probably not warranted nor worthwhile. + CreationTimestamp: v12.Now(), + } + ps := v1.PodSpec{ + Containers: containers, + } + p := v1.Pod{ + TypeMeta: tm, + ObjectMeta: om, + Spec: ps, + } + return &p, nil +} + +// containerToV1Container converts information we know about a libpod container +// to a V1.Container specification. +func containerToV1Container(c *Container) (v1.Container, error) { + kubeContainer := v1.Container{} + kubeSec, err := generateKubeSecurityContext(c) + if err != nil { + return kubeContainer, err + } + + if len(c.config.Spec.Linux.Devices) > 0 { + // TODO Enable when we can support devices and their names + devices, err := generateKubeVolumeDeviceFromLinuxDevice(c.Spec().Linux.Devices) + if err != nil { + return kubeContainer, err + } + kubeContainer.VolumeDevices = devices + return kubeContainer, errors.Wrapf(ErrNotImplemented, "linux devices") + } + + if len(c.config.UserVolumes) > 0 { + // TODO When we until we can resolve what the volume name should be, this is disabled + // Volume names need to be coordinated "globally" in the kube files. + volumes, err := libpodMountsToKubeVolumeMounts(c) + if err != nil { + return kubeContainer, err + } + kubeContainer.VolumeMounts = volumes + return kubeContainer, errors.Wrapf(ErrNotImplemented, "volume names") + } + + envVariables, err := libpodEnvVarsToKubeEnvVars(c.config.Spec.Process.Env) + if err != nil { + return kubeContainer, nil + } + + ports, err := ocicniPortMappingToContainerPort(c.PortMappings()) + if err != nil { + return kubeContainer, nil + } + + containerCommands := c.Command() + kubeContainer.Name = removeUnderscores(c.Name()) + + _, image := c.Image() + kubeContainer.Image = image + kubeContainer.Stdin = c.Stdin() + kubeContainer.Command = containerCommands + // TODO need to figure out how we handle command vs entry point. Kube appears to prefer entrypoint. + // right now we just take the container's command + //container.Args = args + kubeContainer.WorkingDir = c.WorkingDir() + kubeContainer.Ports = ports + // This should not be applicable + //container.EnvFromSource = + kubeContainer.Env = envVariables + // TODO enable resources when we can support naming conventions + //container.Resources + kubeContainer.SecurityContext = kubeSec + kubeContainer.StdinOnce = false + kubeContainer.TTY = c.config.Spec.Process.Terminal + + return kubeContainer, nil +} + +// ocicniPortMappingToContainerPort takes an ocicni portmapping and converts +// it to a v1.ContainerPort format for kube output +func ocicniPortMappingToContainerPort(portMappings []ocicni.PortMapping) ([]v1.ContainerPort, error) { + var containerPorts []v1.ContainerPort + for _, p := range portMappings { + var protocol v1.Protocol + switch strings.ToUpper(p.Protocol) { + case "TCP": + protocol = v1.ProtocolTCP + case "UDP": + protocol = v1.ProtocolUDP + default: + return containerPorts, errors.Errorf("unknown network protocol %s", p.Protocol) + } + cp := v1.ContainerPort{ + // Name will not be supported + HostPort: p.HostPort, + HostIP: p.HostIP, + ContainerPort: p.ContainerPort, + Protocol: protocol, + } + containerPorts = append(containerPorts, cp) + } + return containerPorts, nil +} + +// libpodEnvVarsToKubeEnvVars converts a key=value string slice to []v1.EnvVar +func libpodEnvVarsToKubeEnvVars(envs []string) ([]v1.EnvVar, error) { + var envVars []v1.EnvVar + for _, e := range envs { + splitE := strings.SplitN(e, "=", 2) + if len(splitE) != 2 { + return envVars, errors.Errorf("environment variable %s is malformed; should be key=value", e) + } + ev := v1.EnvVar{ + Name: splitE[0], + Value: splitE[1], + } + envVars = append(envVars, ev) + } + return envVars, nil +} + +// Is this worth it? +func libpodMaxAndMinToResourceList(c *Container) (v1.ResourceList, v1.ResourceList) { //nolint + // It does not appear we can properly calculate CPU resources from the information + // we know in libpod. Libpod knows CPUs by time, shares, etc. + + // We also only know about a memory limit; no memory minimum + maxResources := make(map[v1.ResourceName]resource.Quantity) + minResources := make(map[v1.ResourceName]resource.Quantity) + config := c.Config() + maxMem := config.Spec.Linux.Resources.Memory.Limit + + _ = maxMem + + return maxResources, minResources +} + +func generateKubeVolumeMount(hostSourcePath string, mounts []specs.Mount) (v1.VolumeMount, error) { + vm := v1.VolumeMount{} + for _, m := range mounts { + if m.Source == hostSourcePath { + // TODO Name is not provided and is required by Kube; therefore, this is disabled earlier + //vm.Name = + vm.MountPath = m.Source + vm.SubPath = m.Destination + if util.StringInSlice("ro", m.Options) { + vm.ReadOnly = true + } + return vm, nil + } + } + return vm, errors.New("unable to find mount source") +} + +// libpodMountsToKubeVolumeMounts converts the containers mounts to a struct kube understands +func libpodMountsToKubeVolumeMounts(c *Container) ([]v1.VolumeMount, error) { + // At this point, I dont think we can distinguish between the default + // volume mounts and user added ones. For now, we pass them all. + var vms []v1.VolumeMount + for _, hostSourcePath := range c.config.UserVolumes { + vm, err := generateKubeVolumeMount(hostSourcePath, c.config.Spec.Mounts) + if err != nil { + return vms, err + } + vms = append(vms, vm) + } + return vms, nil +} + +// generateKubeSecurityContext generates a securityContext based on the existing container +func generateKubeSecurityContext(c *Container) (*v1.SecurityContext, error) { + priv := c.Privileged() + ro := c.IsReadOnly() + allowPrivEscalation := !c.Spec().Process.NoNewPrivileges + + // TODO enable use of capabilities when we can figure out how to extract cap-add|remove + //caps := v1.Capabilities{ + // //Add: c.config.Spec.Process.Capabilities + //} + sc := v1.SecurityContext{ + // TODO enable use of capabilities when we can figure out how to extract cap-add|remove + //Capabilities: &caps, + Privileged: &priv, + // TODO How do we know if selinux were passed into podman + //SELinuxOptions: + // RunAsNonRoot is an optional parameter; our first implementations should be root only; however + // I'm leaving this as a bread-crumb for later + //RunAsNonRoot: &nonRoot, + ReadOnlyRootFilesystem: &ro, + AllowPrivilegeEscalation: &allowPrivEscalation, + } + + if c.User() != "" { + // It is *possible* that + logrus.Debugf("Looking in container for user: %s", c.User()) + u, err := lookup.GetUser(c.state.Mountpoint, c.User()) + if err != nil { + return nil, err + } + user := int64(u.Uid) + sc.RunAsUser = &user + } + return &sc, nil +} + +// generateKubeVolumeDeviceFromLinuxDevice takes a list of devices and makes a VolumeDevice struct for kube +func generateKubeVolumeDeviceFromLinuxDevice(devices []specs.LinuxDevice) ([]v1.VolumeDevice, error) { + var volumeDevices []v1.VolumeDevice + for _, d := range devices { + vd := v1.VolumeDevice{ + // TBD How are we going to sync up these names + //Name: + DevicePath: d.Path, + } + volumeDevices = append(volumeDevices, vd) + } + return volumeDevices, nil +} + +func removeUnderscores(s string) string { + return strings.Replace(s, "_", "", -1) +} diff --git a/libpod/mounts_linux.go b/libpod/mounts_linux.go new file mode 100644 index 000000000..e6aa09eac --- /dev/null +++ b/libpod/mounts_linux.go @@ -0,0 +1,18 @@ +// +build linux + +package libpod + +const ( + // MountPrivate represents the private mount option. + MountPrivate = "private" + // MountRPrivate represents the rprivate mount option. + MountRPrivate = "rprivate" + // MountShared represents the shared mount option. + MountShared = "shared" + // MountRShared represents the rshared mount option. + MountRShared = "rshared" + // MountSlave represents the slave mount option. + MountSlave = "slave" + // MountRSlave represents the rslave mount option. + MountRSlave = "rslave" +) diff --git a/libpod/networking_linux.go b/libpod/networking_linux.go index 0d9ec2809..43d0a61a4 100644 --- a/libpod/networking_linux.go +++ b/libpod/networking_linux.go @@ -12,6 +12,7 @@ import ( "strconv" "strings" "syscall" + "time" cnitypes "github.com/containernetworking/cni/pkg/types/current" "github.com/containernetworking/plugins/pkg/ns" @@ -63,20 +64,20 @@ func (r *Runtime) configureNetNS(ctr *Container, ctrNS ns.NetNS) ([]*cnitypes.Re } }() - networkStatus := make([]*cnitypes.Result, 1) + networkStatus := make([]*cnitypes.Result, 0) for idx, r := range results { logrus.Debugf("[%d] CNI result: %v", idx, r.String()) resultCurrent, err := cnitypes.GetResult(r) if err != nil { return nil, errors.Wrapf(err, "error parsing CNI plugin result %q: %v", r.String(), err) } - networkStatus = append(ctr.state.NetworkStatus, resultCurrent) + networkStatus = append(networkStatus, resultCurrent) } // Add firewall rules to ensure the container has network access. // Will not be necessary once CNI firewall plugin merges upstream. // https://github.com/containernetworking/plugins/pull/75 - for _, netStatus := range ctr.state.NetworkStatus { + for _, netStatus := range networkStatus { firewallConf := &firewall.FirewallNetConf{ PrevResult: netStatus, } @@ -89,13 +90,16 @@ func (r *Runtime) configureNetNS(ctr *Container, ctrNS ns.NetNS) ([]*cnitypes.Re } // Create and configure a new network namespace for a container -func (r *Runtime) createNetNS(ctr *Container) (ns.NetNS, []*cnitypes.Result, error) { +func (r *Runtime) createNetNS(ctr *Container) (n ns.NetNS, q []*cnitypes.Result, err error) { ctrNS, err := netns.NewNS() if err != nil { return nil, nil, errors.Wrapf(err, "error creating network namespace for container %s", ctr.ID()) } defer func() { if err != nil { + if err2 := netns.UnmountNS(ctrNS); err2 != nil { + logrus.Errorf("Error unmounting partially created network namespace for container %s: %v", ctr.ID(), err2) + } if err2 := ctrNS.Close(); err2 != nil { logrus.Errorf("Error closing partially created network namespace for container %s: %v", ctr.ID(), err2) } @@ -134,12 +138,33 @@ func (r *Runtime) setupRootlessNetNS(ctr *Container) (err error) { cmd.ExtraFiles = append(cmd.ExtraFiles, ctr.rootlessSlirpSyncR, syncW) if err := cmd.Start(); err != nil { - return errors.Wrapf(err, "failed to start process") + return errors.Wrapf(err, "failed to start slirp4netns process") } + defer cmd.Process.Release() b := make([]byte, 16) - if _, err := syncR.Read(b); err != nil { - return errors.Wrapf(err, "failed to read from sync pipe") + for { + if err := syncR.SetDeadline(time.Now().Add(1 * time.Second)); err != nil { + return errors.Wrapf(err, "error setting slirp4netns pipe timeout") + } + if _, err := syncR.Read(b); err == nil { + break + } else { + if os.IsTimeout(err) { + // Check if the process is still running. + var status syscall.WaitStatus + _, err := syscall.Wait4(cmd.Process.Pid, &status, syscall.WNOHANG, nil) + if err != nil { + return errors.Wrapf(err, "failed to read slirp4netns process status") + } + if status.Exited() || status.Signaled() { + return errors.New("slirp4netns failed") + } + + continue + } + return errors.Wrapf(err, "failed to read from slirp4netns sync pipe") + } } return nil } diff --git a/libpod/oci.go b/libpod/oci.go index ca8f967c4..3222f9403 100644 --- a/libpod/oci.go +++ b/libpod/oci.go @@ -11,12 +11,10 @@ import ( "os/exec" "path/filepath" "runtime" - "strconv" "strings" "syscall" "time" - "github.com/containers/libpod/pkg/ctime" "github.com/containers/libpod/pkg/rootless" "github.com/containers/libpod/pkg/util" "github.com/coreos/go-systemd/activation" @@ -229,7 +227,7 @@ func bindPorts(ports []ocicni.PortMapping) ([]*os.File, error) { return files, nil } -func (r *OCIRuntime) createOCIContainer(ctr *Container, cgroupParent string, restoreContainer bool) (err error) { +func (r *OCIRuntime) createOCIContainer(ctr *Container, cgroupParent string, restoreOptions *ContainerCheckpointOptions) (err error) { var stderrBuf bytes.Buffer runtimeDir, err := util.GetRootlessRuntimeDir() @@ -291,8 +289,11 @@ func (r *OCIRuntime) createOCIContainer(ctr *Container, cgroupParent string, res args = append(args, "--syslog") } - if restoreContainer { + if restoreOptions != nil { args = append(args, "--restore", ctr.CheckpointPath()) + if restoreOptions.TCPEstablished { + args = append(args, "--restore-arg", "--tcp-established") + } } logrus.WithFields(logrus.Fields{ @@ -318,6 +319,10 @@ func (r *OCIRuntime) createOCIContainer(ctr *Container, cgroupParent string, res cmd.Env = append(r.conmonEnv, fmt.Sprintf("_OCI_SYNCPIPE=%d", 3)) cmd.Env = append(cmd.Env, fmt.Sprintf("_OCI_STARTPIPE=%d", 4)) cmd.Env = append(cmd.Env, fmt.Sprintf("XDG_RUNTIME_DIR=%s", runtimeDir)) + cmd.Env = append(cmd.Env, fmt.Sprintf("_LIBPOD_USERNS_CONFIGURED=%s", os.Getenv("_LIBPOD_USERNS_CONFIGURED"))) + cmd.Env = append(cmd.Env, fmt.Sprintf("_LIBPOD_ROOTLESS_UID=%s", os.Getenv("_LIBPOD_ROOTLESS_UID"))) + cmd.Env = append(cmd.Env, fmt.Sprintf("HOME=%s", os.Getenv("HOME"))) + cmd.Env = append(cmd.Env, fmt.Sprintf("XDG_RUNTIME_DIR=%s", runtimeDir)) if r.reservePorts { ports, err := bindPorts(ctr.config.PortMappings) @@ -331,7 +336,7 @@ func (r *OCIRuntime) createOCIContainer(ctr *Container, cgroupParent string, res cmd.ExtraFiles = append(cmd.ExtraFiles, ports...) } - if rootless.IsRootless() { + if ctr.config.NetMode.IsSlirp4netns() { ctr.rootlessSlirpSyncR, ctr.rootlessSlirpSyncW, err = os.Pipe() if err != nil { return errors.Wrapf(err, "failed to create rootless network sync pipe") @@ -352,7 +357,8 @@ func (r *OCIRuntime) createOCIContainer(ctr *Container, cgroupParent string, res // Set the label of the conmon process to be level :s0 // This will allow the container processes to talk to fifo-files // passed into the container by conmon - plabel, err := selinux.CurrentLabel() + var plabel string + plabel, err = selinux.CurrentLabel() if err != nil { childPipe.Close() return errors.Wrapf(err, "Failed to get current SELinux label") @@ -362,7 +368,7 @@ func (r *OCIRuntime) createOCIContainer(ctr *Container, cgroupParent string, res runtime.LockOSThread() if c["level"] != "s0" && c["level"] != "" { c["level"] = "s0" - if err := label.SetProcessLabel(c.Get()); err != nil { + if err = label.SetProcessLabel(c.Get()); err != nil { runtime.UnlockOSThread() return err } @@ -443,6 +449,7 @@ func (r *OCIRuntime) createOCIContainer(ctr *Container, cgroupParent string, res } return errors.Wrapf(ErrInternal, "container create failed") } + ctr.state.PID = ss.si.Pid case <-time.After(ContainerCreateTimeout): return errors.Wrapf(ErrInternal, "container creation timeout") } @@ -451,17 +458,47 @@ func (r *OCIRuntime) createOCIContainer(ctr *Container, cgroupParent string, res // updateContainerStatus retrieves the current status of the container from the // runtime. It updates the container's state but does not save it. -func (r *OCIRuntime) updateContainerStatus(ctr *Container) error { - state := new(spec.State) +// If useRunc is false, we will not directly hit runc to see the container's +// status, but will instead only check for the existence of the conmon exit file +// and update state to stopped if it exists. +func (r *OCIRuntime) updateContainerStatus(ctr *Container, useRunc bool) error { + exitFile := ctr.exitFilePath() runtimeDir, err := util.GetRootlessRuntimeDir() if err != nil { return err } + // If not using runc, we don't need to do most of this. + if !useRunc { + // If the container's not running, nothing to do. + if ctr.state.State != ContainerStateRunning { + return nil + } + + // Check for the exit file conmon makes + info, err := os.Stat(exitFile) + if err != nil { + if os.IsNotExist(err) { + // Container is still running, no error + return nil + } + + return errors.Wrapf(err, "error running stat on container %s exit file", ctr.ID()) + } + + // Alright, it exists. Transition to Stopped state. + ctr.state.State = ContainerStateStopped + + // Read the exit file to get our stopped time and exit code. + return ctr.handleExitFile(exitFile, info) + } + // Store old state so we know if we were already stopped oldState := ctr.state.State + state := new(spec.State) + cmd := exec.Command(r.path, "state", ctr.ID()) cmd.Env = append(cmd.Env, fmt.Sprintf("XDG_RUNTIME_DIR=%s", runtimeDir)) outPipe, err := cmd.StdoutPipe() @@ -480,6 +517,8 @@ func (r *OCIRuntime) updateContainerStatus(ctr *Container) error { } if strings.Contains(string(out), "does not exist") { ctr.removeConmonFiles() + ctr.state.ExitCode = -1 + ctr.state.FinishedTime = time.Now() ctr.state.State = ContainerStateExited return nil } @@ -514,7 +553,6 @@ func (r *OCIRuntime) updateContainerStatus(ctr *Container) error { // Only grab exit status if we were not already stopped // If we were, it should already be in the database if ctr.state.State == ContainerStateStopped && oldState != ContainerStateStopped { - exitFile := filepath.Join(r.exitsDir, ctr.ID()) var fi os.FileInfo err = kwait.ExponentialBackoff( kwait.Backoff{ @@ -538,24 +576,7 @@ func (r *OCIRuntime) updateContainerStatus(ctr *Container) error { return nil } - ctr.state.FinishedTime = ctime.Created(fi) - statusCodeStr, err := ioutil.ReadFile(exitFile) - if err != nil { - return errors.Wrapf(err, "failed to read exit file for container %s", ctr.ID()) - } - statusCode, err := strconv.Atoi(string(statusCodeStr)) - if err != nil { - return errors.Wrapf(err, "error converting exit status code for container %s to int", - ctr.ID()) - } - ctr.state.ExitCode = int32(statusCode) - - oomFilePath := filepath.Join(ctr.bundlePath(), "oom") - if _, err = os.Stat(oomFilePath); err == nil { - ctr.state.OOMKilled = true - } - - ctr.state.Exited = true + return ctr.handleExitFile(exitFile, fi) } return nil @@ -570,6 +591,9 @@ func (r *OCIRuntime) startContainer(ctr *Container) error { return err } env := []string{fmt.Sprintf("XDG_RUNTIME_DIR=%s", runtimeDir)} + if notify, ok := os.LookupEnv("NOTIFY_SOCKET"); ok { + env = append(env, fmt.Sprintf("NOTIFY_SOCKET=%s", notify)) + } if err := utils.ExecCmdWithStdStreams(os.Stdin, os.Stdout, os.Stderr, env, r.path, "start", ctr.ID()); err != nil { return err } @@ -601,6 +625,8 @@ func (r *OCIRuntime) killContainer(ctr *Container, signal uint) error { // Does not set finished time for container, assumes you will run updateStatus // after to pull the exit code func (r *OCIRuntime) stopContainer(ctr *Container, timeout uint) error { + logrus.Debugf("Stopping container %s (PID %d)", ctr.ID(), ctr.state.PID) + // Ping the container to see if it's alive // If it's not, it's already stopped, return err := unix.Kill(ctr.state.PID, 0) @@ -670,8 +696,12 @@ func (r *OCIRuntime) stopContainer(ctr *Container, timeout uint) error { // deleteContainer deletes a container from the OCI runtime func (r *OCIRuntime) deleteContainer(ctr *Container) error { - _, err := utils.ExecCmd(r.path, "delete", "--force", ctr.ID()) - return err + runtimeDir, err := util.GetRootlessRuntimeDir() + if err != nil { + return err + } + env := []string{fmt.Sprintf("XDG_RUNTIME_DIR=%s", runtimeDir)} + return utils.ExecCmdWithStdStreams(os.Stdin, os.Stdout, os.Stderr, env, r.path, "delete", "--force", ctr.ID()) } // pauseContainer pauses the given container @@ -725,6 +755,8 @@ func (r *OCIRuntime) execContainer(c *Container, cmd, capAdd, env []string, tty if tty { args = append(args, "--tty") + } else { + args = append(args, "--tty=false") } if user != "" { @@ -828,13 +860,25 @@ func (r *OCIRuntime) execStopContainer(ctr *Container, timeout uint) error { } // checkpointContainer checkpoints the given container -func (r *OCIRuntime) checkpointContainer(ctr *Container) error { +func (r *OCIRuntime) checkpointContainer(ctr *Container, options ContainerCheckpointOptions) error { // imagePath is used by CRIU to store the actual checkpoint files imagePath := ctr.CheckpointPath() // workPath will be used to store dump.log and stats-dump workPath := ctr.bundlePath() logrus.Debugf("Writing checkpoint to %s", imagePath) logrus.Debugf("Writing checkpoint logs to %s", workPath) - return utils.ExecCmdWithStdStreams(os.Stdin, os.Stdout, os.Stderr, nil, r.path, "checkpoint", - "--image-path", imagePath, "--work-path", workPath, ctr.ID()) + args := []string{} + args = append(args, "checkpoint") + args = append(args, "--image-path") + args = append(args, imagePath) + args = append(args, "--work-path") + args = append(args, workPath) + if options.KeepRunning { + args = append(args, "--leave-running") + } + if options.TCPEstablished { + args = append(args, "--tcp-established") + } + args = append(args, ctr.ID()) + return utils.ExecCmdWithStdStreams(os.Stdin, os.Stdout, os.Stderr, nil, r.path, args...) } diff --git a/libpod/oci_linux.go b/libpod/oci_linux.go index 0447670b3..2737a641e 100644 --- a/libpod/oci_linux.go +++ b/libpod/oci_linux.go @@ -19,6 +19,8 @@ import ( "golang.org/x/sys/unix" ) +const unknownPackage = "Unknown" + func (r *OCIRuntime) moveConmonToCgroup(ctr *Container, cgroupParent string, cmd *exec.Cmd) error { if os.Geteuid() == 0 { if r.cgroupManager == SystemdCgroupsManager { @@ -63,10 +65,10 @@ func newPipe() (parent *os.File, child *os.File, err error) { // CreateContainer creates a container in the OCI runtime // TODO terminal support for container // Presently just ignoring conmon opts related to it -func (r *OCIRuntime) createContainer(ctr *Container, cgroupParent string, restoreContainer bool) (err error) { +func (r *OCIRuntime) createContainer(ctr *Container, cgroupParent string, restoreOptions *ContainerCheckpointOptions) (err error) { if ctr.state.UserNSRoot == "" { // no need of an intermediate mount ns - return r.createOCIContainer(ctr, cgroupParent, restoreContainer) + return r.createOCIContainer(ctr, cgroupParent, restoreOptions) } var wg sync.WaitGroup wg.Add(1) @@ -74,7 +76,8 @@ func (r *OCIRuntime) createContainer(ctr *Container, cgroupParent string, restor defer wg.Done() runtime.LockOSThread() - fd, err := os.Open(fmt.Sprintf("/proc/%d/task/%d/ns/mnt", os.Getpid(), unix.Gettid())) + var fd *os.File + fd, err = os.Open(fmt.Sprintf("/proc/%d/task/%d/ns/mnt", os.Getpid(), unix.Gettid())) if err != nil { return } @@ -103,7 +106,7 @@ func (r *OCIRuntime) createContainer(ctr *Container, cgroupParent string, restor if err != nil { return } - err = r.createOCIContainer(ctr, cgroupParent, restoreContainer) + err = r.createOCIContainer(ctr, cgroupParent, restoreOptions) }() wg.Wait() @@ -111,7 +114,7 @@ func (r *OCIRuntime) createContainer(ctr *Container, cgroupParent string, restor } func rpmVersion(path string) string { - output := "Unknown" + output := unknownPackage cmd := exec.Command("/usr/bin/rpm", "-q", "-f", path) if outp, err := cmd.Output(); err == nil { output = string(outp) @@ -120,7 +123,7 @@ func rpmVersion(path string) string { } func dpkgVersion(path string) string { - output := "Unknown" + output := unknownPackage cmd := exec.Command("/usr/bin/dpkg", "-S", path) if outp, err := cmd.Output(); err == nil { output = string(outp) @@ -129,14 +132,14 @@ func dpkgVersion(path string) string { } func (r *OCIRuntime) pathPackage() string { - if out := rpmVersion(r.path); out != "Unknown" { + if out := rpmVersion(r.path); out != unknownPackage { return out } return dpkgVersion(r.path) } func (r *OCIRuntime) conmonPackage() string { - if out := rpmVersion(r.conmonPath); out != "Unknown" { + if out := rpmVersion(r.conmonPath); out != unknownPackage { return out } return dpkgVersion(r.conmonPath) diff --git a/libpod/oci_unsupported.go b/libpod/oci_unsupported.go index b133eb402..8c084d1e2 100644 --- a/libpod/oci_unsupported.go +++ b/libpod/oci_unsupported.go @@ -15,7 +15,7 @@ func newPipe() (parent *os.File, child *os.File, err error) { return nil, nil, ErrNotImplemented } -func (r *OCIRuntime) createContainer(ctr *Container, cgroupParent string, restoreContainer bool) (err error) { +func (r *OCIRuntime) createContainer(ctr *Container, cgroupParent string, restoreOptions *ContainerCheckpointOptions) (err error) { return ErrNotImplemented } diff --git a/libpod/options.go b/libpod/options.go index 8d044313b..3e43d73f0 100644 --- a/libpod/options.go +++ b/libpod/options.go @@ -7,6 +7,7 @@ import ( "regexp" "syscall" + "github.com/containers/libpod/pkg/namespaces" "github.com/containers/storage" "github.com/containers/storage/pkg/idtools" "github.com/cri-o/ocicni/pkg/ocicni" @@ -28,19 +29,40 @@ func WithStorageConfig(config storage.StoreOptions) RuntimeOption { return ErrRuntimeFinalized } - rt.config.StorageConfig.RunRoot = config.RunRoot - rt.config.StorageConfig.GraphRoot = config.GraphRoot - rt.config.StorageConfig.GraphDriverName = config.GraphDriverName - rt.config.StaticDir = filepath.Join(config.GraphRoot, "libpod") + if config.RunRoot != "" { + rt.config.StorageConfig.RunRoot = config.RunRoot + rt.configuredFrom.storageRunRootSet = true + } - rt.config.StorageConfig.GraphDriverOptions = make([]string, len(config.GraphDriverOptions)) - copy(rt.config.StorageConfig.GraphDriverOptions, config.GraphDriverOptions) + if config.GraphRoot != "" { + rt.config.StorageConfig.GraphRoot = config.GraphRoot + rt.configuredFrom.storageGraphRootSet = true - rt.config.StorageConfig.UIDMap = make([]idtools.IDMap, len(config.UIDMap)) - copy(rt.config.StorageConfig.UIDMap, config.UIDMap) + // Also set libpod static dir, so we are a subdirectory + // of the c/storage store by default + rt.config.StaticDir = filepath.Join(config.GraphRoot, "libpod") + rt.configuredFrom.libpodStaticDirSet = true + } - rt.config.StorageConfig.GIDMap = make([]idtools.IDMap, len(config.GIDMap)) - copy(rt.config.StorageConfig.GIDMap, config.GIDMap) + if config.GraphDriverName != "" { + rt.config.StorageConfig.GraphDriverName = config.GraphDriverName + rt.configuredFrom.storageGraphDriverSet = true + } + + if config.GraphDriverOptions != nil { + rt.config.StorageConfig.GraphDriverOptions = make([]string, len(config.GraphDriverOptions)) + copy(rt.config.StorageConfig.GraphDriverOptions, config.GraphDriverOptions) + } + + if config.UIDMap != nil { + rt.config.StorageConfig.UIDMap = make([]idtools.IDMap, len(config.UIDMap)) + copy(rt.config.StorageConfig.UIDMap, config.UIDMap) + } + + if config.GIDMap != nil { + rt.config.StorageConfig.GIDMap = make([]idtools.IDMap, len(config.GIDMap)) + copy(rt.config.StorageConfig.GIDMap, config.GIDMap) + } return nil } @@ -173,26 +195,26 @@ func WithStaticDir(dir string) RuntimeOption { } rt.config.StaticDir = dir + rt.configuredFrom.libpodStaticDirSet = true return nil } } -// WithHooksDir sets the directory to look for OCI runtime hooks config. -// Note we are not saving this in database, since this is really just for used -// for testing. -func WithHooksDir(hooksDir string) RuntimeOption { +// WithHooksDir sets the directories to look for OCI runtime hook configuration. +func WithHooksDir(hooksDirs ...string) RuntimeOption { return func(rt *Runtime) error { if rt.valid { return ErrRuntimeFinalized } - if hooksDir == "" { - return errors.Wrap(ErrInvalidArg, "empty-string hook directories are not supported") + for _, hooksDir := range hooksDirs { + if hooksDir == "" { + return errors.Wrap(ErrInvalidArg, "empty-string hook directories are not supported") + } } - rt.config.HooksDir = []string{hooksDir} - rt.config.HooksDirNotExistFatal = true + rt.config.HooksDir = hooksDirs return nil } } @@ -225,6 +247,7 @@ func WithTmpDir(dir string) RuntimeOption { } rt.config.TmpDir = dir + rt.configuredFrom.libpodTmpDirSet = true return nil } @@ -817,7 +840,7 @@ func WithDependencyCtrs(ctrs []*Container) CtrCreateOption { // namespace with a minimal configuration. // An optional array of port mappings can be provided. // Conflicts with WithNetNSFrom(). -func WithNetNS(portMappings []ocicni.PortMapping, postConfigureNetNS bool, networks []string) CtrCreateOption { +func WithNetNS(portMappings []ocicni.PortMapping, postConfigureNetNS bool, netmode string, networks []string) CtrCreateOption { return func(ctr *Container) error { if ctr.valid { return ErrCtrFinalized @@ -831,6 +854,7 @@ func WithNetNS(portMappings []ocicni.PortMapping, postConfigureNetNS bool, netwo ctr.config.CreateNetNS = true ctr.config.PortMappings = portMappings ctr.config.Networks = networks + ctr.config.NetMode = namespaces.NetworkMode(netmode) return nil } @@ -1295,3 +1319,14 @@ func WithInfraContainer() PodCreateOption { return nil } } + +// WithInfraContainerPorts tells the pod to add port bindings to the pause container +func WithInfraContainerPorts(bindings []ocicni.PortMapping) PodCreateOption { + return func(pod *Pod) error { + if pod.valid { + return ErrPodFinalized + } + pod.config.InfraContainer.PortBindings = bindings + return nil + } +} diff --git a/libpod/pod.go b/libpod/pod.go index 8ac976f6a..07f41f5c6 100644 --- a/libpod/pod.go +++ b/libpod/pod.go @@ -4,6 +4,7 @@ import ( "time" "github.com/containers/storage" + "github.com/cri-o/ocicni/pkg/ocicni" "github.com/pkg/errors" ) @@ -96,7 +97,8 @@ type PodContainerInfo struct { // InfraContainerConfig is the configuration for the pod's infra container type InfraContainerConfig struct { - HasInfraContainer bool `json:"makeInfraContainer"` + HasInfraContainer bool `json:"makeInfraContainer"` + PortBindings []ocicni.PortMapping `json:"infraPortBindings"` } // ID retrieves the pod's ID diff --git a/libpod/pod_easyjson.go b/libpod/pod_easyjson.go index 6c1c939f3..8ea9a5e72 100644 --- a/libpod/pod_easyjson.go +++ b/libpod/pod_easyjson.go @@ -6,6 +6,7 @@ package libpod import ( json "encoding/json" + ocicni "github.com/cri-o/ocicni/pkg/ocicni" easyjson "github.com/mailru/easyjson" jlexer "github.com/mailru/easyjson/jlexer" jwriter "github.com/mailru/easyjson/jwriter" @@ -721,6 +722,29 @@ func easyjsonBe091417DecodeGithubComContainersLibpodLibpod5(in *jlexer.Lexer, ou switch key { case "makeInfraContainer": out.HasInfraContainer = bool(in.Bool()) + case "infraPortBindings": + if in.IsNull() { + in.Skip() + out.PortBindings = nil + } else { + in.Delim('[') + if out.PortBindings == nil { + if !in.IsDelim(']') { + out.PortBindings = make([]ocicni.PortMapping, 0, 1) + } else { + out.PortBindings = []ocicni.PortMapping{} + } + } else { + out.PortBindings = (out.PortBindings)[:0] + } + for !in.IsDelim(']') { + var v6 ocicni.PortMapping + easyjsonBe091417DecodeGithubComContainersLibpodVendorGithubComCriOOcicniPkgOcicni(in, &v6) + out.PortBindings = append(out.PortBindings, v6) + in.WantComma() + } + in.Delim(']') + } default: in.SkipRecursive() } @@ -745,5 +769,109 @@ func easyjsonBe091417EncodeGithubComContainersLibpodLibpod5(out *jwriter.Writer, } out.Bool(bool(in.HasInfraContainer)) } + { + const prefix string = ",\"infraPortBindings\":" + if first { + first = false + out.RawString(prefix[1:]) + } else { + out.RawString(prefix) + } + if in.PortBindings == nil && (out.Flags&jwriter.NilSliceAsEmpty) == 0 { + out.RawString("null") + } else { + out.RawByte('[') + for v7, v8 := range in.PortBindings { + if v7 > 0 { + out.RawByte(',') + } + easyjsonBe091417EncodeGithubComContainersLibpodVendorGithubComCriOOcicniPkgOcicni(out, v8) + } + out.RawByte(']') + } + } + out.RawByte('}') +} +func easyjsonBe091417DecodeGithubComContainersLibpodVendorGithubComCriOOcicniPkgOcicni(in *jlexer.Lexer, out *ocicni.PortMapping) { + isTopLevel := in.IsStart() + if in.IsNull() { + if isTopLevel { + in.Consumed() + } + in.Skip() + return + } + in.Delim('{') + for !in.IsDelim('}') { + key := in.UnsafeString() + in.WantColon() + if in.IsNull() { + in.Skip() + in.WantComma() + continue + } + switch key { + case "hostPort": + out.HostPort = int32(in.Int32()) + case "containerPort": + out.ContainerPort = int32(in.Int32()) + case "protocol": + out.Protocol = string(in.String()) + case "hostIP": + out.HostIP = string(in.String()) + default: + in.SkipRecursive() + } + in.WantComma() + } + in.Delim('}') + if isTopLevel { + in.Consumed() + } +} +func easyjsonBe091417EncodeGithubComContainersLibpodVendorGithubComCriOOcicniPkgOcicni(out *jwriter.Writer, in ocicni.PortMapping) { + out.RawByte('{') + first := true + _ = first + { + const prefix string = ",\"hostPort\":" + if first { + first = false + out.RawString(prefix[1:]) + } else { + out.RawString(prefix) + } + out.Int32(int32(in.HostPort)) + } + { + const prefix string = ",\"containerPort\":" + if first { + first = false + out.RawString(prefix[1:]) + } else { + out.RawString(prefix) + } + out.Int32(int32(in.ContainerPort)) + } + { + const prefix string = ",\"protocol\":" + if first { + first = false + out.RawString(prefix[1:]) + } else { + out.RawString(prefix) + } + out.String(string(in.Protocol)) + } + { + const prefix string = ",\"hostIP\":" + if first { + first = false + out.RawString(prefix[1:]) + } else { + out.RawString(prefix) + } + out.String(string(in.HostIP)) + } out.RawByte('}') } diff --git a/libpod/pod_internal.go b/libpod/pod_internal.go index 46162c7ef..39a25c004 100644 --- a/libpod/pod_internal.go +++ b/libpod/pod_internal.go @@ -48,7 +48,7 @@ func (p *Pod) updatePod() error { // Save pod state to database func (p *Pod) save() error { if err := p.runtime.state.SavePod(p); err != nil { - return errors.Wrapf(err, "error saving pod %s state") + return errors.Wrapf(err, "error saving pod %s state", p.ID()) } return nil diff --git a/libpod/runtime.go b/libpod/runtime.go index 318cd0369..6b6a8cc2d 100644 --- a/libpod/runtime.go +++ b/libpod/runtime.go @@ -12,7 +12,6 @@ import ( "github.com/containers/image/types" "github.com/containers/libpod/libpod/image" "github.com/containers/libpod/pkg/firewall" - "github.com/containers/libpod/pkg/hooks" sysreg "github.com/containers/libpod/pkg/registries" "github.com/containers/libpod/pkg/rootless" "github.com/containers/libpod/pkg/util" @@ -84,6 +83,7 @@ type Runtime struct { lock sync.RWMutex imageRuntime *image.Runtime firewallBackend firewall.FirewallBackend + configuredFrom *runtimeConfiguredFrom } // RuntimeConfig contains configuration options used to set up the runtime @@ -141,11 +141,11 @@ type RuntimeConfig struct { // CNIDefaultNetwork is the network name of the default CNI network // to attach pods to CNIDefaultNetwork string `toml:"cni_default_network,omitempty"` - // HooksDir Path to the directory containing hooks configuration files + // HooksDir holds paths to the directories containing hooks + // configuration files. When the same filename is present in in + // multiple directories, the file in the directory listed last in + // this slice takes precedence. HooksDir []string `toml:"hooks_dir"` - // HooksDirNotExistFatal switches between fatal errors and non-fatal - // warnings if the configured HooksDir does not exist. - HooksDirNotExistFatal bool `toml:"hooks_dir_not_exist_fatal"` // DefaultMountsFile is the path to the default mounts file for testing // purposes only DefaultMountsFile string `toml:"-"` @@ -175,6 +175,20 @@ type RuntimeConfig struct { EnableLabeling bool `toml:"label"` } +// runtimeConfiguredFrom is a struct used during early runtime init to help +// assemble the full RuntimeConfig struct from defaults. +// It indicated whether several fields in the runtime configuration were set +// explicitly. +// If they were not, we may override them with information from the database, +// if it exists and differs from what is present in the system already. +type runtimeConfiguredFrom struct { + storageGraphDriverSet bool + storageGraphRootSet bool + storageRunRootSet bool + libpodStaticDirSet bool + libpodTmpDirSet bool +} + var ( defaultRuntimeConfig = RuntimeConfig{ // Leave this empty so containers/storage will use its defaults @@ -203,7 +217,6 @@ var ( "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", }, CgroupManager: SystemdCgroupsManager, - HooksDir: []string{hooks.DefaultDir, hooks.OverrideDir}, StaticDir: filepath.Join(storage.DefaultStoreOptions.GraphRoot, "libpod"), TmpDir: "", MaxLogSize: -1, @@ -253,6 +266,7 @@ func SetXdgRuntimeDir(val string) error { func NewRuntime(options ...RuntimeOption) (runtime *Runtime, err error) { runtime = new(Runtime) runtime.config = new(RuntimeConfig) + runtime.configuredFrom = new(runtimeConfiguredFrom) // Copy the default configuration tmpDir, err := getDefaultTmpDir() @@ -262,8 +276,19 @@ func NewRuntime(options ...RuntimeOption) (runtime *Runtime, err error) { deepcopier.Copy(defaultRuntimeConfig).To(runtime.config) runtime.config.TmpDir = tmpDir + if rootless.IsRootless() { + // If we're rootless, override the default storage config + storageConf, err := util.GetDefaultStoreOptions() + if err != nil { + return nil, errors.Wrapf(err, "error retrieving rootless storage config") + } + runtime.config.StorageConfig = storageConf + runtime.config.StaticDir = filepath.Join(storageConf.GraphRoot, "libpod") + } + configPath := ConfigPath foundConfig := true + rootlessConfigPath := "" if rootless.IsRootless() { home := os.Getenv("HOME") if runtime.config.SignaturePolicyPath == "" { @@ -272,7 +297,10 @@ func NewRuntime(options ...RuntimeOption) (runtime *Runtime, err error) { runtime.config.SignaturePolicyPath = newPath } } - configPath = filepath.Join(home, ".config/containers/libpod.conf") + + rootlessConfigPath = filepath.Join(home, ".config/containers/libpod.conf") + + configPath = rootlessConfigPath if _, err := os.Stat(configPath); err != nil { foundConfig = false } @@ -303,6 +331,25 @@ func NewRuntime(options ...RuntimeOption) (runtime *Runtime, err error) { if err != nil { return nil, errors.Wrapf(err, "error reading configuration file %s", configPath) } + + // This is ugly, but we need to decode twice. + // Once to check if libpod static and tmp dirs were explicitly + // set (not enough to check if they're not the default value, + // might have been explicitly configured to the default). + // A second time to actually get a usable config. + tmpConfig := new(RuntimeConfig) + if _, err := toml.Decode(string(contents), tmpConfig); err != nil { + return nil, errors.Wrapf(err, "error decoding configuration file %s", + configPath) + } + + if tmpConfig.StaticDir != "" { + runtime.configuredFrom.libpodStaticDirSet = true + } + if tmpConfig.TmpDir != "" { + runtime.configuredFrom.libpodTmpDirSet = true + } + if _, err := toml.Decode(string(contents), runtime.config); err != nil { return nil, errors.Wrapf(err, "error decoding configuration file %s", configPath) } @@ -317,6 +364,22 @@ func NewRuntime(options ...RuntimeOption) (runtime *Runtime, err error) { if err := makeRuntime(runtime); err != nil { return nil, err } + + if !foundConfig && rootlessConfigPath != "" { + os.MkdirAll(filepath.Dir(rootlessConfigPath), 0755) + file, err := os.OpenFile(rootlessConfigPath, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0666) + if err != nil && !os.IsExist(err) { + return nil, errors.Wrapf(err, "cannot open file %s", rootlessConfigPath) + } + if err == nil { + defer file.Close() + enc := toml.NewEncoder(file) + if err := enc.Encode(runtime.config); err != nil { + os.Remove(rootlessConfigPath) + } + } + } + return runtime, nil } @@ -328,6 +391,7 @@ func NewRuntime(options ...RuntimeOption) (runtime *Runtime, err error) { func NewRuntimeFromConfig(configPath string, options ...RuntimeOption) (runtime *Runtime, err error) { runtime = new(Runtime) runtime.config = new(RuntimeConfig) + runtime.configuredFrom = new(runtimeConfiguredFrom) // Set two fields not in the TOML config runtime.config.StateType = defaultRuntimeConfig.StateType @@ -406,6 +470,77 @@ func makeRuntime(runtime *Runtime) (err error) { runtime.config.ConmonPath) } + // Make the static files directory if it does not exist + if err := os.MkdirAll(runtime.config.StaticDir, 0700); err != nil { + // The directory is allowed to exist + if !os.IsExist(err) { + return errors.Wrapf(err, "error creating runtime static files directory %s", + runtime.config.StaticDir) + } + } + + // Set up the state + switch runtime.config.StateType { + case InMemoryStateStore: + state, err := NewInMemoryState() + if err != nil { + return err + } + runtime.state = state + case SQLiteStateStore: + return errors.Wrapf(ErrInvalidArg, "SQLite state is currently disabled") + case BoltDBStateStore: + dbPath := filepath.Join(runtime.config.StaticDir, "bolt_state.db") + + state, err := NewBoltState(dbPath, runtime) + if err != nil { + return err + } + runtime.state = state + default: + return errors.Wrapf(ErrInvalidArg, "unrecognized state type passed") + } + + // Grab config from the database so we can reset some defaults + dbConfig, err := runtime.state.GetDBConfig() + if err != nil { + return errors.Wrapf(err, "error retrieving runtime configuration from database") + } + + // Reset defaults if they were not explicitly set + if !runtime.configuredFrom.storageGraphDriverSet && dbConfig.GraphDriver != "" { + runtime.config.StorageConfig.GraphDriverName = dbConfig.GraphDriver + } + if !runtime.configuredFrom.storageGraphRootSet && dbConfig.StorageRoot != "" { + runtime.config.StorageConfig.GraphRoot = dbConfig.StorageRoot + } + if !runtime.configuredFrom.storageRunRootSet && dbConfig.StorageTmp != "" { + runtime.config.StorageConfig.RunRoot = dbConfig.StorageTmp + } + if !runtime.configuredFrom.libpodStaticDirSet && dbConfig.LibpodRoot != "" { + runtime.config.StaticDir = dbConfig.LibpodRoot + } + if !runtime.configuredFrom.libpodTmpDirSet && dbConfig.LibpodTmp != "" { + runtime.config.TmpDir = dbConfig.LibpodTmp + } + + logrus.Debugf("Using graph driver %s", runtime.config.StorageConfig.GraphDriverName) + logrus.Debugf("Using graph root %s", runtime.config.StorageConfig.GraphRoot) + logrus.Debugf("Using run root %s", runtime.config.StorageConfig.RunRoot) + logrus.Debugf("Using static dir %s", runtime.config.StaticDir) + logrus.Debugf("Using tmp dir %s", runtime.config.TmpDir) + + // Validate our config against the database, now that we've set our + // final storage configuration + if err := runtime.state.ValidateDBConfig(runtime); err != nil { + return err + } + + if err := runtime.state.SetNamespace(runtime.config.Namespace); err != nil { + return errors.Wrapf(err, "error setting libpod namespace in state") + } + logrus.Debugf("Set libpod namespace to %q", runtime.config.Namespace) + // Set up containers/storage var store storage.Store if rootless.SkipStorageSetup() { @@ -473,15 +608,6 @@ func makeRuntime(runtime *Runtime) (err error) { } runtime.ociRuntime = ociRuntime - // Make the static files directory if it does not exist - if err := os.MkdirAll(runtime.config.StaticDir, 0755); err != nil { - // The directory is allowed to exist - if !os.IsExist(err) { - return errors.Wrapf(err, "error creating runtime static files directory %s", - runtime.config.StaticDir) - } - } - // Make a directory to hold container lockfiles lockDir := filepath.Join(runtime.config.TmpDir, "lock") if err := os.MkdirAll(lockDir, 0755); err != nil { @@ -503,11 +629,13 @@ func makeRuntime(runtime *Runtime) (err error) { } // Set up the CNI net plugin - netPlugin, err := ocicni.InitCNI(runtime.config.CNIDefaultNetwork, runtime.config.CNIConfigDir, runtime.config.CNIPluginDir...) - if err != nil { - return errors.Wrapf(err, "error configuring CNI network plugin") + if !rootless.IsRootless() { + netPlugin, err := ocicni.InitCNI(runtime.config.CNIDefaultNetwork, runtime.config.CNIConfigDir, runtime.config.CNIPluginDir...) + if err != nil { + return errors.Wrapf(err, "error configuring CNI network plugin") + } + runtime.netPlugin = netPlugin } - runtime.netPlugin = netPlugin // Set up a firewall backend backendType := "" @@ -520,33 +648,6 @@ func makeRuntime(runtime *Runtime) (err error) { } runtime.firewallBackend = fwBackend - // Set up the state - switch runtime.config.StateType { - case InMemoryStateStore: - state, err := NewInMemoryState() - if err != nil { - return err - } - runtime.state = state - case SQLiteStateStore: - return errors.Wrapf(ErrInvalidArg, "SQLite state is currently disabled") - case BoltDBStateStore: - dbPath := filepath.Join(runtime.config.StaticDir, "bolt_state.db") - - state, err := NewBoltState(dbPath, runtime.lockDir, runtime) - if err != nil { - return err - } - runtime.state = state - default: - return errors.Wrapf(ErrInvalidArg, "unrecognized state type passed") - } - - if err := runtime.state.SetNamespace(runtime.config.Namespace); err != nil { - return errors.Wrapf(err, "error setting libpod namespace in state") - } - logrus.Debugf("Set libpod namespace to %q", runtime.config.Namespace) - // We now need to see if the system has restarted // We check for the presence of a file in our tmp directory to verify this // This check must be locked to prevent races diff --git a/libpod/runtime_ctr.go b/libpod/runtime_ctr.go index b63726f29..09d0ec042 100644 --- a/libpod/runtime_ctr.go +++ b/libpod/runtime_ctr.go @@ -246,7 +246,19 @@ func (r *Runtime) removeContainer(ctx context.Context, c *Container, force bool) } if c.state.State == ContainerStatePaused { - return errors.Wrapf(ErrCtrStateInvalid, "container %s is paused, cannot remove until unpaused", c.ID()) + if !force { + return errors.Wrapf(ErrCtrStateInvalid, "container %s is paused, cannot remove until unpaused", c.ID()) + } + if err := c.runtime.ociRuntime.killContainer(c, 9); err != nil { + return err + } + if err := c.unpause(); err != nil { + return err + } + // Need to update container state to make sure we know it's stopped + if err := c.waitForExitFileAndSync(); err != nil { + return err + } } // Check that the container's in a good state to be removed @@ -256,7 +268,7 @@ func (r *Runtime) removeContainer(ctx context.Context, c *Container, force bool) } // Need to update container state to make sure we know it's stopped - if err := c.syncContainer(); err != nil { + if err := c.waitForExitFileAndSync(); err != nil { return err } } else if !(c.state.State == ContainerStateConfigured || diff --git a/libpod/runtime_pod_infra_linux.go b/libpod/runtime_pod_infra_linux.go index fea79e994..8a5dbef56 100644 --- a/libpod/runtime_pod_infra_linux.go +++ b/libpod/runtime_pod_infra_linux.go @@ -7,7 +7,6 @@ import ( "github.com/containers/libpod/libpod/image" "github.com/containers/libpod/pkg/rootless" - "github.com/cri-o/ocicni/pkg/ocicni" spec "github.com/opencontainers/runtime-spec/specs-go" "github.com/opencontainers/runtime-tools/generate" ) @@ -50,9 +49,12 @@ func (r *Runtime) makeInfraContainer(ctx context.Context, p *Pod, imgName, imgID options = append(options, withIsInfra()) // Since user namespace sharing is not implemented, we only need to check if it's rootless - portMappings := make([]ocicni.PortMapping, 0) networks := make([]string, 0) - options = append(options, WithNetNS(portMappings, isRootless, networks)) + netmode := "bridge" + if isRootless { + netmode = "slirp4netns" + } + options = append(options, WithNetNS(p.config.InfraContainer.PortBindings, isRootless, netmode, networks)) return r.newContainer(ctx, g.Config, options...) } diff --git a/libpod/runtime_pod_linux.go b/libpod/runtime_pod_linux.go index eb3d471dd..3d6fad52f 100644 --- a/libpod/runtime_pod_linux.go +++ b/libpod/runtime_pod_linux.go @@ -265,15 +265,26 @@ func (r *Runtime) removePod(ctx context.Context, p *Pod, removeCtrs, force bool) } case CgroupfsCgroupsManager: // Delete the cgroupfs cgroup + // Make sure the conmon cgroup is deleted first + // Since the pod is almost gone, don't bother failing + // hard - instead, just log errors. v1CGroups := GetV1CGroups(getExcludedCGroups()) + conmonCgroupPath := filepath.Join(p.state.CgroupPath, "conmon") + conmonCgroup, err := cgroups.Load(v1CGroups, cgroups.StaticPath(conmonCgroupPath)) + if err != nil && err != cgroups.ErrCgroupDeleted { + return err + } + if err == nil { + if err := conmonCgroup.Delete(); err != nil { + logrus.Errorf("Error deleting pod %s conmon cgroup %s: %v", p.ID(), conmonCgroupPath, err) + } + } cgroup, err := cgroups.Load(v1CGroups, cgroups.StaticPath(p.state.CgroupPath)) if err != nil && err != cgroups.ErrCgroupDeleted { return err - } else if err == nil { + } + if err == nil { if err := cgroup.Delete(); err != nil { - // The pod is already almost gone. - // No point in hard-failing if we fail - // this bit of cleanup. logrus.Errorf("Error deleting pod %s cgroup %s: %v", p.ID(), p.state.CgroupPath, err) } } diff --git a/libpod/state.go b/libpod/state.go index 273e81318..06c2003d8 100644 --- a/libpod/state.go +++ b/libpod/state.go @@ -1,5 +1,15 @@ package libpod +// DBConfig is a set of Libpod runtime configuration settings that are saved +// in a State when it is first created, and can subsequently be retrieved. +type DBConfig struct { + LibpodRoot string + LibpodTmp string + StorageRoot string + StorageTmp string + GraphDriver string +} + // State is a storage backend for libpod's current state. // A State is only initialized once per instance of libpod. // As such, initialization methods for State implementations may safely assume @@ -21,6 +31,22 @@ type State interface { // Refresh clears container and pod states after a reboot Refresh() error + // GetDBConfig retrieves several paths configured within the database + // when it was created - namely, Libpod root and tmp dirs, c/storage + // root and tmp dirs, and c/storage graph driver. + // This is not implemented by the in-memory state, as it has no need to + // validate runtime configuration. + GetDBConfig() (*DBConfig, error) + + // ValidateDBConfig validates the config in the given Runtime struct + // against paths stored in the configured database. + // Libpod root and tmp dirs and c/storage root and tmp dirs and graph + // driver are validated. + // This is not implemented by the in-memory state, as it has no need to + // validate runtime configuration that may change over multiple runs of + // the program. + ValidateDBConfig(runtime *Runtime) error + // SetNamespace() sets the namespace for the store, and will determine // what containers are retrieved with container and pod retrieval calls. // A namespace of "", the empty string, acts as no namespace, and diff --git a/libpod/state_test.go b/libpod/state_test.go index 04572fb29..d93a371f3 100644 --- a/libpod/state_test.go +++ b/libpod/state_test.go @@ -45,11 +45,16 @@ func getEmptyBoltState() (s State, p string, p2 string, err error) { dbPath := filepath.Join(tmpDir, "db.sql") lockDir := filepath.Join(tmpDir, "locks") + if err := os.Mkdir(lockDir, 0755); err != nil { + return nil, "", "", err + } + runtime := new(Runtime) runtime.config = new(RuntimeConfig) runtime.config.StorageConfig = storage.StoreOptions{} + runtime.lockDir = lockDir - state, err := NewBoltState(dbPath, lockDir, runtime) + state, err := NewBoltState(dbPath, runtime) if err != nil { return nil, "", "", err } diff --git a/libpod/testdata/config.toml b/libpod/testdata/config.toml index e19d36017..1d78f2083 100644 --- a/libpod/testdata/config.toml +++ b/libpod/testdata/config.toml @@ -14,7 +14,7 @@ seccomp_profile = "/etc/crio/seccomp.json" apparmor_profile = "crio-default" cgroup_manager = "cgroupfs" - hooks_dir_path = "/usr/share/containers/oci/hooks.d" + hooks_dir = ["/usr/share/containers/oci/hooks.d"] pids_limit = 2048 container_exits_dir = "/var/run/podman/exits" [crio.image] diff --git a/libpod/util.go b/libpod/util.go index 7007b29cd..aa3494529 100644 --- a/libpod/util.go +++ b/libpod/util.go @@ -13,6 +13,7 @@ import ( "github.com/containers/image/signature" "github.com/containers/image/types" "github.com/containers/libpod/pkg/util" + "github.com/fsnotify/fsnotify" spec "github.com/opencontainers/runtime-spec/specs-go" "github.com/pkg/errors" ) @@ -90,31 +91,64 @@ func MountExists(specMounts []spec.Mount, dest string) bool { } // WaitForFile waits until a file has been created or the given timeout has occurred -func WaitForFile(path string, timeout time.Duration) error { +func WaitForFile(path string, chWait chan error, timeout time.Duration) (bool, error) { done := make(chan struct{}) chControl := make(chan struct{}) + + var inotifyEvents chan fsnotify.Event + var timer chan struct{} + watcher, err := fsnotify.NewWatcher() + if err == nil { + if err := watcher.Add(filepath.Dir(path)); err == nil { + inotifyEvents = watcher.Events + } + defer watcher.Close() + } + if inotifyEvents == nil { + // If for any reason we fail to create the inotify + // watcher, fallback to polling the file + timer = make(chan struct{}) + go func() { + select { + case <-chControl: + close(timer) + return + default: + time.Sleep(25 * time.Millisecond) + timer <- struct{}{} + } + }() + } + go func() { for { select { case <-chControl: return - default: + case <-timer: + _, err := os.Stat(path) + if err == nil { + close(done) + return + } + case <-inotifyEvents: _, err := os.Stat(path) if err == nil { close(done) return } - time.Sleep(25 * time.Millisecond) } } }() select { + case e := <-chWait: + return true, e case <-done: - return nil + return false, nil case <-time.After(timeout): close(chControl) - return errors.Wrapf(ErrInternal, "timed out waiting for file %s", path) + return false, errors.Wrapf(ErrInternal, "timed out waiting for file %s", path) } } diff --git a/libpod/version.go b/libpod/version.go index 5e7cd83c9..966588ae9 100644 --- a/libpod/version.go +++ b/libpod/version.go @@ -11,10 +11,10 @@ import ( var ( // GitCommit is the commit that the binary is being built from. // It will be populated by the Makefile. - GitCommit string + gitCommit string // BuildInfo is the time at which the binary was built // It will be populated by the Makefile. - BuildInfo string + buildInfo string ) //Version is an output struct for varlink @@ -30,9 +30,9 @@ type Version struct { func GetVersion() (Version, error) { var err error var buildTime int64 - if BuildInfo != "" { + if buildInfo != "" { // Converts unix time from string to int64 - buildTime, err = strconv.ParseInt(BuildInfo, 10, 64) + buildTime, err = strconv.ParseInt(buildInfo, 10, 64) if err != nil { return Version{}, err @@ -41,7 +41,7 @@ func GetVersion() (Version, error) { return Version{ Version: podmanVersion.Version, GoVersion: runtime.Version(), - GitCommit: GitCommit, + GitCommit: gitCommit, Built: buildTime, OsArch: runtime.GOOS + "/" + runtime.GOARCH, }, nil diff --git a/pkg/inspect/inspect.go b/pkg/inspect/inspect.go index 62ba53147..5bdcf677f 100644 --- a/pkg/inspect/inspect.go +++ b/pkg/inspect/inspect.go @@ -126,6 +126,7 @@ type ImageData struct { Annotations map[string]string `json:"Annotations"` ManifestType string `json:"ManifestType"` User string `json:"User"` + History []v1.History `json:"History"` } // RootFS holds the root fs information of an image diff --git a/pkg/lookup/lookup.go b/pkg/lookup/lookup.go new file mode 100644 index 000000000..70b97144f --- /dev/null +++ b/pkg/lookup/lookup.go @@ -0,0 +1,168 @@ +package lookup + +import ( + "os" + "strconv" + + "github.com/cyphar/filepath-securejoin" + "github.com/opencontainers/runc/libcontainer/user" + "github.com/sirupsen/logrus" +) + +const ( + etcpasswd = "/etc/passwd" + etcgroup = "/etc/group" +) + +// Overrides allows you to override defaults in GetUserGroupInfo +type Overrides struct { + DefaultUser *user.ExecUser + ContainerEtcPasswdPath string + ContainerEtcGroupPath string +} + +// GetUserGroupInfo takes string forms of the the container's mount path and the container user and +// returns a ExecUser with uid, gid, sgids, and home. And override can be provided for defaults. +func GetUserGroupInfo(containerMount, containerUser string, override *Overrides) (*user.ExecUser, error) { + var ( + passwdDest, groupDest string + defaultExecUser *user.ExecUser + err error + ) + passwdPath := etcpasswd + groupPath := etcgroup + + if override != nil { + // Check for an override /etc/passwd path + if override.ContainerEtcPasswdPath != "" { + passwdPath = override.ContainerEtcPasswdPath + } + // Check for an override for /etc/group path + if override.ContainerEtcGroupPath != "" { + groupPath = override.ContainerEtcGroupPath + } + } + + // Check for an override default user + if override != nil && override.DefaultUser != nil { + defaultExecUser = override.DefaultUser + } else { + // Define a default container user + //defaultExecUser = &user.ExecUser{ + // Uid: 0, + // Gid: 0, + // Home: "/", + defaultExecUser = nil + + } + + // Make sure the /etc/group and /etc/passwd destinations are not a symlink to something naughty + if passwdDest, err = securejoin.SecureJoin(containerMount, passwdPath); err != nil { + logrus.Debug(err) + return nil, err + } + if groupDest, err = securejoin.SecureJoin(containerMount, groupPath); err != nil { + logrus.Debug(err) + return nil, err + } + return user.GetExecUserPath(containerUser, defaultExecUser, passwdDest, groupDest) +} + +// GetContainerGroups uses securejoin to get a list of numerical groupids from a container. Per the runc +// function it calls: If a group name cannot be found, an error will be returned. If a group id cannot be found, +// or the given group data is nil, the id will be returned as-is provided it is in the legal range. +func GetContainerGroups(groups []string, containerMount string, override *Overrides) ([]uint32, error) { + var ( + groupDest string + err error + uintgids []uint32 + ) + + groupPath := etcgroup + if override != nil && override.ContainerEtcGroupPath != "" { + groupPath = override.ContainerEtcGroupPath + } + + if groupDest, err = securejoin.SecureJoin(containerMount, groupPath); err != nil { + logrus.Debug(err) + return nil, err + } + + gids, err := user.GetAdditionalGroupsPath(groups, groupDest) + if err != nil { + return nil, err + } + // For libpod, we want []uint32s + for _, gid := range gids { + uintgids = append(uintgids, uint32(gid)) + } + return uintgids, nil +} + +// GetUser takes a containermount path and user name or ID and returns +// a matching User structure from /etc/passwd. If it cannot locate a user +// with the provided information, an ErrNoPasswdEntries is returned. +// When the provided user name was an ID, a User structure with Uid +// set is returned along with ErrNoPasswdEntries. +func GetUser(containerMount, userIDorName string) (*user.User, error) { + var inputIsName bool + uid, err := strconv.Atoi(userIDorName) + if err != nil { + inputIsName = true + } + passwdDest, err := securejoin.SecureJoin(containerMount, etcpasswd) + if err != nil { + return nil, err + } + users, err := user.ParsePasswdFileFilter(passwdDest, func(u user.User) bool { + if inputIsName { + return u.Name == userIDorName + } + return u.Uid == uid + }) + if err != nil && !os.IsNotExist(err) { + return nil, err + } + if len(users) > 0 { + return &users[0], nil + } + if !inputIsName { + return &user.User{Uid: uid}, user.ErrNoPasswdEntries + } + return nil, user.ErrNoPasswdEntries +} + +// GetGroup takes a containermount path and a group name or ID and returns +// a match Group struct from /etc/group. If it cannot locate a group, +// an ErrNoGroupEntries error is returned. When the provided group name +// was an ID, a Group structure with Gid set is returned along with +// ErrNoGroupEntries. +func GetGroup(containerMount, groupIDorName string) (*user.Group, error) { + var inputIsName bool + gid, err := strconv.Atoi(groupIDorName) + if err != nil { + inputIsName = true + } + + groupDest, err := securejoin.SecureJoin(containerMount, etcgroup) + if err != nil { + return nil, err + } + + groups, err := user.ParseGroupFileFilter(groupDest, func(g user.Group) bool { + if inputIsName { + return g.Name == groupIDorName + } + return g.Gid == gid + }) + if err != nil && !os.IsNotExist(err) { + return nil, err + } + if len(groups) > 0 { + return &groups[0], nil + } + if !inputIsName { + return &user.Group{Gid: gid}, user.ErrNoGroupEntries + } + return nil, user.ErrNoGroupEntries +} diff --git a/pkg/namespaces/namespaces.go b/pkg/namespaces/namespaces.go index bee833fa9..832efd554 100644 --- a/pkg/namespaces/namespaces.go +++ b/pkg/namespaces/namespaces.go @@ -223,7 +223,12 @@ func (n NetworkMode) IsBridge() bool { return n == "bridge" } +// IsSlirp4netns indicates if we are running a rootless network stack +func (n NetworkMode) IsSlirp4netns() bool { + return n == "slirp4netns" +} + // IsUserDefined indicates user-created network func (n NetworkMode) IsUserDefined() bool { - return !n.IsDefault() && !n.IsBridge() && !n.IsHost() && !n.IsNone() && !n.IsContainer() + return !n.IsDefault() && !n.IsBridge() && !n.IsHost() && !n.IsNone() && !n.IsContainer() && !n.IsSlirp4netns() } diff --git a/pkg/registries/registries.go b/pkg/registries/registries.go index 73aa93d68..c26f15cb6 100644 --- a/pkg/registries/registries.go +++ b/pkg/registries/registries.go @@ -38,8 +38,10 @@ func GetRegistries() ([]string, error) { func GetInsecureRegistries() ([]string, error) { registryConfigPath := "" - if _, err := os.Stat(userRegistriesFile); err == nil { - registryConfigPath = userRegistriesFile + if rootless.IsRootless() { + if _, err := os.Stat(userRegistriesFile); err == nil { + registryConfigPath = userRegistriesFile + } } envOverride := os.Getenv("REGISTRIES_CONFIG_PATH") diff --git a/pkg/resolvconf/resolvconf.go b/pkg/resolvconf/resolvconf.go index fccd60093..e85bcb377 100644 --- a/pkg/resolvconf/resolvconf.go +++ b/pkg/resolvconf/resolvconf.go @@ -103,13 +103,21 @@ func GetLastModified() *File { } // FilterResolvDNS cleans up the config in resolvConf. It has two main jobs: -// 1. It looks for localhost (127.*|::1) entries in the provided +// 1. If a netns is enabled, it looks for localhost (127.*|::1) entries in the provided // resolv.conf, removing local nameserver entries, and, if the resulting // cleaned config has no defined nameservers left, adds default DNS entries // 2. Given the caller provides the enable/disable state of IPv6, the filter // code will remove all IPv6 nameservers if it is not enabled for containers // -func FilterResolvDNS(resolvConf []byte, ipv6Enabled bool) (*File, error) { +func FilterResolvDNS(resolvConf []byte, ipv6Enabled bool, netnsEnabled bool) (*File, error) { + // If we're using the host netns, we have nothing to do besides hash the file. + if !netnsEnabled { + hash, err := ioutils.HashData(bytes.NewReader(resolvConf)) + if err != nil { + return nil, err + } + return &File{Content: resolvConf, Hash: hash}, nil + } cleanedResolvConf := localhostNSRegexp.ReplaceAll(resolvConf, []byte{}) // if IPv6 is not enabled, also clean out any IPv6 address nameserver if !ipv6Enabled { diff --git a/pkg/rootless/rootless_linux.go b/pkg/rootless/rootless_linux.go index 5c45f2694..07002da3f 100644 --- a/pkg/rootless/rootless_linux.go +++ b/pkg/rootless/rootless_linux.go @@ -12,6 +12,7 @@ import ( "runtime" "strconv" "strings" + "sync" "syscall" "unsafe" @@ -33,9 +34,17 @@ func runInUser() error { return nil } +var ( + isRootlessOnce sync.Once + isRootless bool +) + // IsRootless tells us if we are running in rootless mode func IsRootless() bool { - return os.Geteuid() != 0 || os.Getenv("_LIBPOD_USERNS_CONFIGURED") != "" + isRootlessOnce.Do(func() { + isRootless = os.Geteuid() != 0 || os.Getenv("_LIBPOD_USERNS_CONFIGURED") != "" + }) + return isRootless } var ( @@ -65,7 +74,7 @@ func GetRootlessUID() int { func tryMappingTool(tool string, pid int, hostID int, mappings []idtools.IDMap) error { path, err := exec.LookPath(tool) if err != nil { - return err + return errors.Wrapf(err, "cannot find %s", tool) } appendTriplet := func(l []string, a, b, c int) []string { @@ -83,7 +92,11 @@ func tryMappingTool(tool string, pid int, hostID int, mappings []idtools.IDMap) Path: path, Args: args, } - return cmd.Run() + + if err := cmd.Run(); err != nil { + return errors.Wrapf(err, "cannot setup namespace using %s", tool) + } + return nil } // JoinNS re-exec podman in a new userNS and join the user namespace of the specified @@ -182,11 +195,16 @@ func BecomeRootInUserNS() (bool, int, error) { return false, -1, errors.Errorf("cannot re-exec process") } + allowSingleIDMapping := os.Getenv("PODMAN_ALLOW_SINGLE_ID_MAPPING_IN_USERNS") != "" + var uids, gids []idtools.IDMap username := os.Getenv("USER") if username == "" { user, err := user.LookupId(fmt.Sprintf("%d", os.Getuid())) - if err != nil && os.Getenv("PODMAN_ALLOW_SINGLE_ID_MAPPING_IN_USERNS") == "" { + if err != nil && !allowSingleIDMapping { + if os.IsNotExist(err) { + return false, 0, errors.Wrapf(err, "/etc/subuid or /etc/subgid does not exist, see subuid/subgid man pages for information on these files") + } return false, 0, errors.Wrapf(err, "could not find user by UID nor USER env was set") } if err == nil { @@ -194,7 +212,7 @@ func BecomeRootInUserNS() (bool, int, error) { } } mappings, err := idtools.NewIDMappings(username, username) - if os.Getenv("PODMAN_ALLOW_SINGLE_ID_MAPPING_IN_USERNS") == "" { + if !allowSingleIDMapping { if err != nil { return false, -1, err } @@ -224,7 +242,11 @@ func BecomeRootInUserNS() (bool, int, error) { uidsMapped := false if mappings != nil && uids != nil { - uidsMapped = tryMappingTool("newuidmap", pid, os.Getuid(), uids) == nil + err := tryMappingTool("newuidmap", pid, os.Getuid(), uids) + if !allowSingleIDMapping && err != nil { + return false, 0, err + } + uidsMapped = err == nil } if !uidsMapped { setgroups := fmt.Sprintf("/proc/%d/setgroups", pid) @@ -242,7 +264,11 @@ func BecomeRootInUserNS() (bool, int, error) { gidsMapped := false if mappings != nil && gids != nil { - gidsMapped = tryMappingTool("newgidmap", pid, os.Getgid(), gids) == nil + err := tryMappingTool("newgidmap", pid, os.Getgid(), gids) + if !allowSingleIDMapping && err != nil { + return false, 0, err + } + gidsMapped = err == nil } if !gidsMapped { gidMap := fmt.Sprintf("/proc/%d/gid_map", pid) diff --git a/pkg/secrets/secrets.go b/pkg/secrets/secrets.go index 7208f53b7..242953609 100644 --- a/pkg/secrets/secrets.go +++ b/pkg/secrets/secrets.go @@ -149,6 +149,15 @@ func SecretMountsWithUIDGID(mountLabel, containerWorkingDir, mountFile, mountPre mountFiles = append(mountFiles, []string{OverrideMountsFile, DefaultMountsFile}...) if rootless.IsRootless() { mountFiles = append([]string{UserOverrideMountsFile}, mountFiles...) + _, err := os.Stat(UserOverrideMountsFile) + if err != nil && os.IsNotExist(err) { + os.MkdirAll(filepath.Dir(UserOverrideMountsFile), 0755) + if f, err := os.Create(UserOverrideMountsFile); err != nil { + logrus.Warnf("could not create file %s: %v", UserOverrideMountsFile, err) + } else { + f.Close() + } + } } } else { mountFiles = append(mountFiles, mountFile) diff --git a/pkg/spec/createconfig.go b/pkg/spec/createconfig.go index 6ac9d82da..25f8cd7a1 100644 --- a/pkg/spec/createconfig.go +++ b/pkg/spec/createconfig.go @@ -335,7 +335,6 @@ func (c *CreateConfig) GetContainerCreateOptions(runtime *libpod.Runtime) ([]lib } options = append(options, runtime.WithPod(pod)) } - if len(c.PortBindings) > 0 { portBindings, err = c.CreatePortBindings() if err != nil { @@ -392,11 +391,11 @@ func (c *CreateConfig) GetContainerCreateOptions(runtime *libpod.Runtime) ([]lib options = append(options, libpod.WithNetNSFrom(connectedCtr)) } else if !c.NetMode.IsHost() && !c.NetMode.IsNone() { isRootless := rootless.IsRootless() - postConfigureNetNS := isRootless || (len(c.IDMappings.UIDMap) > 0 || len(c.IDMappings.GIDMap) > 0) && !c.UsernsMode.IsHost() + postConfigureNetNS := c.NetMode.IsSlirp4netns() || (len(c.IDMappings.UIDMap) > 0 || len(c.IDMappings.GIDMap) > 0) && !c.UsernsMode.IsHost() if isRootless && len(portBindings) > 0 { return nil, errors.New("port bindings are not yet supported by rootless containers") } - options = append(options, libpod.WithNetNS(portBindings, postConfigureNetNS, networks)) + options = append(options, libpod.WithNetNS(portBindings, postConfigureNetNS, string(c.NetMode), networks)) } if c.PidMode.IsContainer() { @@ -497,8 +496,13 @@ func (c *CreateConfig) GetContainerCreateOptions(runtime *libpod.Runtime) ([]lib // CreatePortBindings iterates ports mappings and exposed ports into a format CNI understands func (c *CreateConfig) CreatePortBindings() ([]ocicni.PortMapping, error) { + return NatToOCIPortBindings(c.PortBindings) +} + +// NatToOCIPortBindings iterates a nat.portmap slice and creates []ocicni portmapping slice +func NatToOCIPortBindings(ports nat.PortMap) ([]ocicni.PortMapping, error) { var portBindings []ocicni.PortMapping - for containerPb, hostPb := range c.PortBindings { + for containerPb, hostPb := range ports { var pm ocicni.PortMapping pm.ContainerPort = int32(containerPb.Int()) for _, i := range hostPb { diff --git a/pkg/spec/spec.go b/pkg/spec/spec.go index b1cca2c9e..05be00864 100644 --- a/pkg/spec/spec.go +++ b/pkg/spec/spec.go @@ -453,6 +453,9 @@ func addNetNS(config *CreateConfig, g *generate.Generator) error { } else if IsPod(string(netMode)) { logrus.Debug("Using pod netmode, unless pod is not sharing") return nil + } else if netMode.IsSlirp4netns() { + logrus.Debug("Using slirp4netns netmode") + return nil } else if netMode.IsUserDefined() { logrus.Debug("Using user defined netmode") return nil diff --git a/pkg/util/utils.go b/pkg/util/utils.go index 9107eec5c..e483253a4 100644 --- a/pkg/util/utils.go +++ b/pkg/util/utils.go @@ -3,11 +3,13 @@ package util import ( "fmt" "os" + "os/exec" "path/filepath" "strconv" "strings" "syscall" + "github.com/BurntSushi/toml" "github.com/containers/image/types" "github.com/containers/libpod/pkg/rootless" "github.com/containers/storage" @@ -256,7 +258,7 @@ func GetRootlessStorageOpts() (storage.StoreOptions, error) { if err != nil { return opts, err } - opts.RunRoot = filepath.Join(rootlessRuntime, "run") + opts.RunRoot = rootlessRuntime dataDir := os.Getenv("XDG_DATA_HOME") if dataDir == "" { @@ -273,11 +275,45 @@ func GetRootlessStorageOpts() (storage.StoreOptions, error) { dataDir = filepath.Join(resolvedHome, ".local", "share") } opts.GraphRoot = filepath.Join(dataDir, "containers", "storage") - opts.GraphDriverName = "vfs" + if path, err := exec.LookPath("fuse-overlayfs"); err == nil { + opts.GraphDriverName = "overlay" + opts.GraphDriverOptions = []string{fmt.Sprintf("overlay.mount_program=%s", path)} + } else { + opts.GraphDriverName = "vfs" + } return opts, nil } -// GetDefaultStoreOptions returns the storage ops for containers +type tomlOptionsConfig struct { + MountProgram string `toml:"mount_program"` +} + +type tomlConfig struct { + Storage struct { + Driver string `toml:"driver"` + RunRoot string `toml:"runroot"` + GraphRoot string `toml:"graphroot"` + Options struct{ tomlOptionsConfig } `toml:"options"` + } `toml:"storage"` +} + +func getTomlStorage(storeOptions *storage.StoreOptions) *tomlConfig { + config := new(tomlConfig) + + config.Storage.Driver = storeOptions.GraphDriverName + config.Storage.RunRoot = storeOptions.RunRoot + config.Storage.GraphRoot = storeOptions.GraphRoot + for _, i := range storeOptions.GraphDriverOptions { + s := strings.Split(i, "=") + if s[0] == "overlay.mount_program" { + config.Storage.Options.MountProgram = s[1] + } + } + + return config +} + +// GetDefaultStoreOptions returns the default storage options for containers. func GetDefaultStoreOptions() (storage.StoreOptions, error) { storageOpts := storage.DefaultStoreOptions if rootless.IsRootless() { @@ -290,6 +326,19 @@ func GetDefaultStoreOptions() (storage.StoreOptions, error) { storageConf := filepath.Join(os.Getenv("HOME"), ".config/containers/storage.conf") if _, err := os.Stat(storageConf); err == nil { storage.ReloadConfigurationFile(storageConf, &storageOpts) + } else if os.IsNotExist(err) { + os.MkdirAll(filepath.Dir(storageConf), 0755) + file, err := os.OpenFile(storageConf, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0666) + if err != nil { + return storageOpts, errors.Wrapf(err, "cannot open %s", storageConf) + } + + tomlConfiguration := getTomlStorage(&storageOpts) + defer file.Close() + enc := toml.NewEncoder(file) + if err := enc.Encode(tomlConfiguration); err != nil { + os.Remove(storageConf) + } } } return storageOpts, nil diff --git a/pkg/varlinkapi/containers.go b/pkg/varlinkapi/containers.go index f517e9b6e..07d981786 100644 --- a/pkg/varlinkapi/containers.go +++ b/pkg/varlinkapi/containers.go @@ -278,6 +278,18 @@ func (i *LibpodAPI) RestartContainer(call iopodman.VarlinkCall, name string, tim return call.ReplyRestartContainer(ctr.ID()) } +// ContainerExists looks in local storage for the existence of a container +func (i *LibpodAPI) ContainerExists(call iopodman.VarlinkCall, name string) error { + _, err := i.Runtime.LookupContainer(name) + if errors.Cause(err) == libpod.ErrNoSuchCtr { + return call.ReplyContainerExists(1) + } + if err != nil { + return call.ReplyErrorOccurred(err.Error()) + } + return call.ReplyContainerExists(0) +} + // KillContainer kills a running container. If you want to use the default SIGTERM signal, just send a -1 // for the signal arg. func (i *LibpodAPI) KillContainer(call iopodman.VarlinkCall, name string, signal int64) error { @@ -413,3 +425,40 @@ func (i *LibpodAPI) GetAttachSockets(call iopodman.VarlinkCall, name string) err } return call.ReplyGetAttachSockets(s) } + +// ContainerCheckpoint ... +func (i *LibpodAPI) ContainerCheckpoint(call iopodman.VarlinkCall, name string, keep, leaveRunning, tcpEstablished bool) error { + ctx := getContext() + ctr, err := i.Runtime.LookupContainer(name) + if err != nil { + return call.ReplyContainerNotFound(name) + } + + options := libpod.ContainerCheckpointOptions{ + Keep: keep, + TCPEstablished: tcpEstablished, + KeepRunning: leaveRunning, + } + if err := ctr.Checkpoint(ctx, options); err != nil { + return call.ReplyErrorOccurred(err.Error()) + } + return call.ReplyContainerCheckpoint(ctr.ID()) +} + +// ContainerRestore ... +func (i *LibpodAPI) ContainerRestore(call iopodman.VarlinkCall, name string, keep, tcpEstablished bool) error { + ctx := getContext() + ctr, err := i.Runtime.LookupContainer(name) + if err != nil { + return call.ReplyContainerNotFound(name) + } + + options := libpod.ContainerCheckpointOptions{ + Keep: keep, + TCPEstablished: tcpEstablished, + } + if err := ctr.Restore(ctx, options); err != nil { + return call.ReplyErrorOccurred(err.Error()) + } + return call.ReplyContainerRestore(ctr.ID()) +} diff --git a/pkg/varlinkapi/containers_create.go b/pkg/varlinkapi/containers_create.go index ca1a57048..f9a2db9c8 100644 --- a/pkg/varlinkapi/containers_create.go +++ b/pkg/varlinkapi/containers_create.go @@ -13,6 +13,7 @@ import ( "github.com/containers/libpod/libpod/image" "github.com/containers/libpod/pkg/inspect" "github.com/containers/libpod/pkg/namespaces" + "github.com/containers/libpod/pkg/rootless" cc "github.com/containers/libpod/pkg/spec" "github.com/containers/libpod/pkg/util" "github.com/docker/docker/pkg/signal" @@ -126,7 +127,11 @@ func varlinkCreateToCreateConfig(ctx context.Context, create iopodman.Create, ru // NETWORK MODE networkMode := create.Net_mode if networkMode == "" { - networkMode = "bridge" + if rootless.IsRootless() { + networkMode = "slirp4netns" + } else { + networkMode = "bridge" + } } // WORKING DIR diff --git a/pkg/varlinkapi/images.go b/pkg/varlinkapi/images.go index d14c61c39..6d3f19422 100644 --- a/pkg/varlinkapi/images.go +++ b/pkg/varlinkapi/images.go @@ -4,7 +4,9 @@ import ( "bytes" "encoding/json" "fmt" + "github.com/containers/libpod/cmd/podman/shared" "io" + "os" "path/filepath" "strings" "time" @@ -19,6 +21,7 @@ import ( "github.com/containers/libpod/libpod/image" sysreg "github.com/containers/libpod/pkg/registries" "github.com/containers/libpod/pkg/util" + "github.com/containers/libpod/utils" "github.com/docker/go-units" "github.com/opencontainers/image-spec/specs-go/v1" "github.com/opencontainers/runtime-spec/specs-go" @@ -271,6 +274,9 @@ func (i *LibpodAPI) InspectImage(call iopodman.VarlinkCall, name string) error { return call.ReplyImageNotFound(name) } inspectInfo, err := newImage.Inspect(getContext()) + if err != nil { + return call.ReplyErrorOccurred(err.Error()) + } b, err := json.Marshal(inspectInfo) if err != nil { return call.ReplyErrorOccurred(fmt.Sprintf("unable to serialize")) @@ -497,3 +503,45 @@ func (i *LibpodAPI) PullImage(call iopodman.VarlinkCall, name string) error { } return call.ReplyPullImage(newImage.ID()) } + +// ImageExists returns bool as to whether the input image exists in local storage +func (i *LibpodAPI) ImageExists(call iopodman.VarlinkCall, name string) error { + _, err := i.Runtime.ImageRuntime().NewFromLocal(name) + if errors.Cause(err) == libpod.ErrNoSuchImage { + return call.ReplyImageExists(1) + } + if err != nil { + return call.ReplyErrorOccurred(err.Error()) + } + return call.ReplyImageExists(0) +} + +// ContainerRunlabel ... +func (i *LibpodAPI) ContainerRunlabel(call iopodman.VarlinkCall, input iopodman.Runlabel) error { + ctx := getContext() + dockerRegistryOptions := image.DockerRegistryOptions{ + DockerCertPath: input.CertDir, + DockerInsecureSkipTLSVerify: !input.TlsVerify, + } + + stdErr := os.Stderr + stdOut := os.Stdout + stdIn := os.Stdin + + runLabel, imageName, err := shared.GetRunlabel(input.Label, input.Image, ctx, i.Runtime, input.Pull, input.Creds, dockerRegistryOptions, input.Authfile, input.SignaturePolicyPath, nil) + if err != nil { + return err + } + if runLabel == "" { + return nil + } + + cmd, env, err := shared.GenerateRunlabelCommand(runLabel, imageName, input.Name, input.Opts, input.ExtraArgs) + if err != nil { + return err + } + if err := utils.ExecCmdWithStdStreams(stdIn, stdOut, stdErr, env, cmd[0], cmd[1:]...); err != nil { + return call.ReplyErrorOccurred(err.Error()) + } + return call.ReplyContainerRunlabel() +} diff --git a/pkg/varlinkapi/mount.go b/pkg/varlinkapi/mount.go new file mode 100644 index 000000000..84e6b2709 --- /dev/null +++ b/pkg/varlinkapi/mount.go @@ -0,0 +1,49 @@ +package varlinkapi + +import ( + "github.com/containers/libpod/cmd/podman/varlink" +) + +// ListContainerMounts ... +func (i *LibpodAPI) ListContainerMounts(call iopodman.VarlinkCall) error { + var mounts []string + allContainers, err := i.Runtime.GetAllContainers() + if err != nil { + return call.ReplyErrorOccurred(err.Error()) + } + for _, container := range allContainers { + mounted, mountPoint, err := container.Mounted() + if err != nil { + return call.ReplyErrorOccurred(err.Error()) + } + if mounted { + mounts = append(mounts, mountPoint) + } + } + return call.ReplyListContainerMounts(mounts) +} + +// MountContainer ... +func (i *LibpodAPI) MountContainer(call iopodman.VarlinkCall, name string) error { + container, err := i.Runtime.LookupContainer(name) + if err != nil { + return call.ReplyErrorOccurred(err.Error()) + } + path, err := container.Mount() + if err != nil { + return call.ReplyErrorOccurred(err.Error()) + } + return call.ReplyMountContainer(path) +} + +// UnmountContainer ... +func (i *LibpodAPI) UnmountContainer(call iopodman.VarlinkCall, name string, force bool) error { + container, err := i.Runtime.LookupContainer(name) + if err != nil { + return call.ReplyErrorOccurred(err.Error()) + } + if err := container.Unmount(force); err != nil { + return call.ReplyErrorOccurred(err.Error()) + } + return call.ReplyUnmountContainer() +} diff --git a/pkg/varlinkapi/system.go b/pkg/varlinkapi/system.go index 287f42209..a29d22e7d 100644 --- a/pkg/varlinkapi/system.go +++ b/pkg/varlinkapi/system.go @@ -34,6 +34,10 @@ func (i *LibpodAPI) Ping(call iopodman.VarlinkCall) error { // GetInfo returns details about the podman host and its stores func (i *LibpodAPI) GetInfo(call iopodman.VarlinkCall) error { + versionInfo, err := libpod.GetVersion() + if err != nil { + return err + } var ( registries, insecureRegistries []string ) @@ -64,11 +68,10 @@ func (i *LibpodAPI) GetInfo(call iopodman.VarlinkCall) error { podmanInfo.Host = infoHost store := info[1].Data pmaninfo := iopodman.InfoPodmanBinary{ - Compiler: goruntime.Compiler, - Go_version: goruntime.Version(), - // TODO : How are we going to get this here? - //Podman_version: - Git_commit: libpod.GitCommit, + Compiler: goruntime.Compiler, + Go_version: goruntime.Version(), + Podman_version: versionInfo.Version, + Git_commit: versionInfo.GitCommit, } graphStatus := iopodman.InfoGraphStatus{ diff --git a/seccomp.json b/seccomp.json index 19fadb4bb..fd0681a86 100644 --- a/seccomp.json +++ b/seccomp.json @@ -322,13 +322,13 @@ "stat64", "statfs", "statfs64", + "statx", "symlink", "symlinkat", "sync", "sync_file_range", "syncfs", "sysinfo", - "syslog", "tee", "tgkill", "time", @@ -565,6 +565,7 @@ "setdomainname", "sethostname", "setns", + "syslog", "umount", "umount2", "unshare" @@ -750,6 +751,36 @@ ] }, "excludes": {} + }, + { + "names": [ + "get_mempolicy", + "mbind", + "set_mempolicy" + ], + "action": "SCMP_ACT_ALLOW", + "args": [], + "comment": "", + "includes": { + "caps": [ + "CAP_SYS_NICE" + ] + }, + "excludes": {} + }, + { + "names": [ + "syslog" + ], + "action": "SCMP_ACT_ALLOW", + "args": [], + "comment": "", + "includes": { + "caps": [ + "CAP_SYSLOG" + ] + }, + "excludes": {} } ] } diff --git a/test/README.md b/test/README.md index a068bb4f5..2a9a4d4b1 100644 --- a/test/README.md +++ b/test/README.md @@ -1,8 +1,33 @@ ![PODMAN logo](../logo/podman-logo-source.svg) -# Integration Tests +# Test utils +Test utils provide common functions and structs for testing. It includes two structs: +* `PodmanTest`: Handle the *podman* command and other global resources like temporary +directory. It provides basic methods, like checking podman image and pod status. Test +suites should create their owner test *struct* as a composite of `PodmanTest`, and their +owner PodmanMakeOptions(). + +* `PodmanSession`: Store execution session data and related *methods*. Such like get command +output and so on. It can be used directly in the test suite, only embed it to your owner +session struct if you need expend it. + +## Unittest for test/utils +To ensure neither *tests* nor *utils* break, There are unit-tests for each *functions* and +*structs* in `test/utils`. When you adding functions or structs to this *package*, please +update both unit-tests for it and this documentation. + +### Run unit test for test/utils +Run unit test for test/utils. + +``` +make localunit +``` + +## Structure of the test utils and test suites +The test *utils* package is at the same level of test suites. Each test suites also have their +owner common functions and structs stored in `libpod_suite_test.go`. -Our primary means of performing integration testing for libpod is with the -[Ginkgo](https://github.com/onsi/ginkgo) BDD testing framework. This allows +# Ginkgo test framework +[Ginkgo](https://github.com/onsi/ginkgo) is a BDD testing framework. This allows us to use native Golang to perform our tests and there is a strong affiliation between Ginkgo and the Go test framework. @@ -32,8 +57,16 @@ The gomega sources can be simply installed with the command: GOPATH=~/go go get github.com/onsi/gomega/... ``` -### Running the integration tests +# Integration Tests +Test suite for integration test for podman command line. It has its own structs: +* `PodmanTestIntegration`: Integration test *struct* as a composite of `PodmanTest`. It +set up the global options for *podman* command to ignore the environment influence from +different test system. + +* `PodmanSessionIntegration`: This *struct* has it own *methods* for checking command +output with given format JSON by using *structs* defined in inspect package. +## Running the integration tests You can run the entire suite of integration tests with the following command: ``` @@ -67,3 +100,21 @@ make shell ``` This will run a container and give you a shell and you can follow the instructions above. + +# System test +System tests are used for testing the *podman* CLI in the context of a complete system. It +requires that *podman*, all dependencies, and configurations are in place. The intention of +system testing is to match as closely as possible with real-world user/developer use-cases +and environments. The orchestration of the environments and tests is left to external +tooling. + +* `PodmanTestSystem`: System test *struct* as a composite of `PodmanTest`. It will not add any +options to the command by default. When you run system test, you can set GLOBALOPTIONS, +PODMAN_SUBCMD_OPTIONS or PODMAN_BINARY in ENV to run the test suite for different test matrices. + +## Run system test +You can run the test with following command: + +``` +make localsystem +``` diff --git a/test/e2e/attach_test.go b/test/e2e/attach_test.go index 245ccf649..6bc576461 100644 --- a/test/e2e/attach_test.go +++ b/test/e2e/attach_test.go @@ -4,6 +4,7 @@ import ( "fmt" "os" + . "github.com/containers/libpod/test/utils" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" ) @@ -12,7 +13,7 @@ var _ = Describe("Podman attach", func() { var ( tempdir string err error - podmanTest PodmanTest + podmanTest *PodmanTestIntegration ) BeforeEach(func() { @@ -20,7 +21,7 @@ var _ = Describe("Podman attach", func() { if err != nil { os.Exit(1) } - podmanTest = PodmanCreate(tempdir) + podmanTest = PodmanTestCreate(tempdir) podmanTest.RestoreAllArtifacts() }) diff --git a/test/e2e/checkpoint_test.go b/test/e2e/checkpoint_test.go index 928a76324..fe614e911 100644 --- a/test/e2e/checkpoint_test.go +++ b/test/e2e/checkpoint_test.go @@ -2,9 +2,11 @@ package integration import ( "fmt" + "net" "os" "github.com/containers/libpod/pkg/criu" + . "github.com/containers/libpod/test/utils" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" ) @@ -13,7 +15,7 @@ var _ = Describe("Podman checkpoint", func() { var ( tempdir string err error - podmanTest PodmanTest + podmanTest *PodmanTestIntegration ) BeforeEach(func() { @@ -21,11 +23,15 @@ var _ = Describe("Podman checkpoint", func() { if err != nil { os.Exit(1) } - podmanTest = PodmanCreate(tempdir) + podmanTest = PodmanTestCreate(tempdir) podmanTest.RestoreAllArtifacts() if !criu.CheckForCriu() { Skip("CRIU is missing or too old.") } + hostInfo := podmanTest.Host + if hostInfo.Distribution == "fedora" && hostInfo.Version == "29" { + Skip("Checkpoint tests appear to fail on F29.") + } }) AfterEach(func() { @@ -125,4 +131,164 @@ var _ = Describe("Podman checkpoint", func() { Expect(podmanTest.NumberOfContainersRunning()).To(Equal(0)) }) + + It("podman checkpoint latest running container", func() { + session1 := podmanTest.Podman([]string{"run", "-it", "--security-opt", "seccomp=unconfined", "--name", "first", "-d", ALPINE, "top"}) + session1.WaitWithDefaultTimeout() + Expect(session1.ExitCode()).To(Equal(0)) + + session2 := podmanTest.Podman([]string{"run", "-it", "--security-opt", "seccomp=unconfined", "--name", "second", "-d", ALPINE, "top"}) + session2.WaitWithDefaultTimeout() + Expect(session2.ExitCode()).To(Equal(0)) + + result := podmanTest.Podman([]string{"container", "checkpoint", "-l"}) + result.WaitWithDefaultTimeout() + + Expect(result.ExitCode()).To(Equal(0)) + Expect(podmanTest.NumberOfContainersRunning()).To(Equal(1)) + + ps := podmanTest.Podman([]string{"ps", "-q", "--no-trunc"}) + ps.WaitWithDefaultTimeout() + Expect(ps.ExitCode()).To(Equal(0)) + Expect(ps.LineInOutputContains(session1.OutputToString())).To(BeTrue()) + Expect(ps.LineInOutputContains(session2.OutputToString())).To(BeFalse()) + + result = podmanTest.Podman([]string{"container", "restore", "-l"}) + result.WaitWithDefaultTimeout() + + Expect(result.ExitCode()).To(Equal(0)) + Expect(podmanTest.NumberOfContainersRunning()).To(Equal(2)) + Expect(podmanTest.GetContainerStatus()).To(ContainSubstring("Up")) + Expect(podmanTest.GetContainerStatus()).To(Not(ContainSubstring("Exited"))) + + result = podmanTest.Podman([]string{"rm", "-fa"}) + result.WaitWithDefaultTimeout() + Expect(result.ExitCode()).To(Equal(0)) + Expect(podmanTest.NumberOfContainersRunning()).To(Equal(0)) + }) + + It("podman checkpoint all running container", func() { + session1 := podmanTest.Podman([]string{"run", "-it", "--security-opt", "seccomp=unconfined", "--name", "first", "-d", ALPINE, "top"}) + session1.WaitWithDefaultTimeout() + Expect(session1.ExitCode()).To(Equal(0)) + + session2 := podmanTest.Podman([]string{"run", "-it", "--security-opt", "seccomp=unconfined", "--name", "second", "-d", ALPINE, "top"}) + session2.WaitWithDefaultTimeout() + Expect(session2.ExitCode()).To(Equal(0)) + + result := podmanTest.Podman([]string{"container", "checkpoint", "-a"}) + result.WaitWithDefaultTimeout() + + Expect(result.ExitCode()).To(Equal(0)) + Expect(podmanTest.NumberOfContainersRunning()).To(Equal(0)) + + ps := podmanTest.Podman([]string{"ps", "-q", "--no-trunc"}) + ps.WaitWithDefaultTimeout() + Expect(ps.ExitCode()).To(Equal(0)) + Expect(ps.LineInOutputContains(session1.OutputToString())).To(BeFalse()) + Expect(ps.LineInOutputContains(session2.OutputToString())).To(BeFalse()) + + result = podmanTest.Podman([]string{"container", "restore", "-a"}) + result.WaitWithDefaultTimeout() + + Expect(result.ExitCode()).To(Equal(0)) + Expect(podmanTest.NumberOfContainersRunning()).To(Equal(2)) + Expect(podmanTest.GetContainerStatus()).To(ContainSubstring("Up")) + Expect(podmanTest.GetContainerStatus()).To(Not(ContainSubstring("Exited"))) + + result = podmanTest.Podman([]string{"rm", "-fa"}) + result.WaitWithDefaultTimeout() + Expect(result.ExitCode()).To(Equal(0)) + Expect(podmanTest.NumberOfContainersRunning()).To(Equal(0)) + }) + + It("podman checkpoint container with established tcp connections", func() { + Skip("Seems to not work (yet) in CI") + podmanTest.RestoreArtifact(redis) + session := podmanTest.Podman([]string{"run", "-it", "--security-opt", "seccomp=unconfined", "--network", "host", "-d", redis}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + + // Open a network connection to the redis server + conn, err := net.Dial("tcp", "127.0.0.1:6379") + if err != nil { + os.Exit(1) + } + // This should fail as the container has established TCP connections + result := podmanTest.Podman([]string{"container", "checkpoint", "-l"}) + result.WaitWithDefaultTimeout() + + Expect(result.ExitCode()).To(Equal(125)) + Expect(podmanTest.NumberOfContainersRunning()).To(Equal(1)) + Expect(podmanTest.GetContainerStatus()).To(ContainSubstring("Up")) + + // Now it should work thanks to "--tcp-established" + result = podmanTest.Podman([]string{"container", "checkpoint", "-l", "--tcp-established"}) + result.WaitWithDefaultTimeout() + + Expect(result.ExitCode()).To(Equal(0)) + Expect(podmanTest.NumberOfContainersRunning()).To(Equal(0)) + Expect(podmanTest.GetContainerStatus()).To(ContainSubstring("Exited")) + + // Restore should fail as the checkpoint image contains established TCP connections + result = podmanTest.Podman([]string{"container", "restore", "-l"}) + result.WaitWithDefaultTimeout() + + Expect(result.ExitCode()).To(Equal(125)) + Expect(podmanTest.NumberOfContainersRunning()).To(Equal(0)) + Expect(podmanTest.GetContainerStatus()).To(ContainSubstring("Exited")) + + // Now it should work thanks to "--tcp-established" + result = podmanTest.Podman([]string{"container", "restore", "-l", "--tcp-established"}) + result.WaitWithDefaultTimeout() + + Expect(result.ExitCode()).To(Equal(0)) + Expect(podmanTest.NumberOfContainersRunning()).To(Equal(1)) + Expect(podmanTest.GetContainerStatus()).To(ContainSubstring("Up")) + + result = podmanTest.Podman([]string{"rm", "-fa"}) + result.WaitWithDefaultTimeout() + Expect(result.ExitCode()).To(Equal(0)) + Expect(podmanTest.NumberOfContainersRunning()).To(Equal(0)) + + conn.Close() + }) + + It("podman checkpoint with --leave-running", func() { + session := podmanTest.Podman([]string{"run", "-it", "--security-opt", "seccomp=unconfined", "-d", ALPINE, "top"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + cid := session.OutputToString() + + // Checkpoint container, but leave it running + result := podmanTest.Podman([]string{"container", "checkpoint", "--leave-running", cid}) + result.WaitWithDefaultTimeout() + + Expect(result.ExitCode()).To(Equal(0)) + // Make sure it is still running + Expect(podmanTest.NumberOfContainersRunning()).To(Equal(1)) + Expect(podmanTest.GetContainerStatus()).To(ContainSubstring("Up")) + + // Stop the container + result = podmanTest.Podman([]string{"container", "stop", cid}) + result.WaitWithDefaultTimeout() + + Expect(result.ExitCode()).To(Equal(0)) + Expect(podmanTest.NumberOfContainersRunning()).To(Equal(0)) + Expect(podmanTest.GetContainerStatus()).To(ContainSubstring("Exited")) + + // Restore the stopped container from the previous checkpoint + result = podmanTest.Podman([]string{"container", "restore", cid}) + result.WaitWithDefaultTimeout() + + Expect(result.ExitCode()).To(Equal(0)) + Expect(podmanTest.NumberOfContainersRunning()).To(Equal(1)) + Expect(podmanTest.GetContainerStatus()).To(ContainSubstring("Up")) + + result = podmanTest.Podman([]string{"rm", "-fa"}) + result.WaitWithDefaultTimeout() + Expect(result.ExitCode()).To(Equal(0)) + Expect(podmanTest.NumberOfContainersRunning()).To(Equal(0)) + }) + }) diff --git a/test/e2e/commit_test.go b/test/e2e/commit_test.go index c0e050da4..4ee5061f0 100644 --- a/test/e2e/commit_test.go +++ b/test/e2e/commit_test.go @@ -4,6 +4,7 @@ import ( "fmt" "os" + . "github.com/containers/libpod/test/utils" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" ) @@ -12,7 +13,7 @@ var _ = Describe("Podman commit", func() { var ( tempdir string err error - podmanTest PodmanTest + podmanTest *PodmanTestIntegration ) BeforeEach(func() { @@ -20,7 +21,7 @@ var _ = Describe("Podman commit", func() { if err != nil { os.Exit(1) } - podmanTest = PodmanCreate(tempdir) + podmanTest = PodmanTestCreate(tempdir) podmanTest.RestoreAllArtifacts() }) diff --git a/test/e2e/config.go b/test/e2e/config.go new file mode 100644 index 000000000..8116d993b --- /dev/null +++ b/test/e2e/config.go @@ -0,0 +1,9 @@ +package integration + +var ( + redis = "docker.io/library/redis:alpine" + fedoraMinimal = "registry.fedoraproject.org/fedora-minimal:latest" + ALPINE = "docker.io/library/alpine:latest" + infra = "k8s.gcr.io/pause:3.1" + BB = "docker.io/library/busybox:latest" +) diff --git a/test/e2e/config_amd64.go b/test/e2e/config_amd64.go new file mode 100644 index 000000000..3459bea6d --- /dev/null +++ b/test/e2e/config_amd64.go @@ -0,0 +1,11 @@ +package integration + +var ( + STORAGE_OPTIONS = "--storage-driver vfs" + ROOTLESS_STORAGE_OPTIONS = "--storage-driver vfs" + CACHE_IMAGES = []string{ALPINE, BB, fedoraMinimal, nginx, redis, registry, infra, labels} + nginx = "quay.io/libpod/alpine_nginx:latest" + BB_GLIBC = "docker.io/library/busybox:glibc" + registry = "docker.io/library/registry:2" + labels = "quay.io/libpod/alpine_labels:latest" +) diff --git a/test/e2e/config_ppc64le.go b/test/e2e/config_ppc64le.go new file mode 100644 index 000000000..d1737fa6f --- /dev/null +++ b/test/e2e/config_ppc64le.go @@ -0,0 +1,11 @@ +package integration + +var ( + STORAGE_OPTIONS = "--storage-driver overlay" + ROOTLESS_STORAGE_OPTIONS = "--storage-driver vfs" + CACHE_IMAGES = []string{ALPINE, BB, fedoraMinimal, nginx, redis, infra, labels} + nginx = "quay.io/libpod/alpine_nginx-ppc64le:latest" + BB_GLIBC = "docker.io/ppc64le/busybox:glibc" + labels = "quay.io/libpod/alpine_labels-ppc64le:latest" + registry string +) diff --git a/test/e2e/create_staticip_test.go b/test/e2e/create_staticip_test.go new file mode 100644 index 000000000..17ac5cb40 --- /dev/null +++ b/test/e2e/create_staticip_test.go @@ -0,0 +1,88 @@ +package integration + +import ( + "fmt" + "os" + + . "github.com/containers/libpod/test/utils" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +var _ = Describe("Podman create with --ip flag", func() { + var ( + tempdir string + err error + podmanTest *PodmanTestIntegration + ) + + BeforeEach(func() { + tempdir, err = CreateTempDirInTempDir() + if err != nil { + os.Exit(1) + } + podmanTest = PodmanTestCreate(tempdir) + podmanTest.RestoreAllArtifacts() + // Cleanup the CNI networks used by the tests + os.RemoveAll("/var/lib/cni/networks/podman") + }) + + AfterEach(func() { + podmanTest.Cleanup() + f := CurrentGinkgoTestDescription() + timedResult := fmt.Sprintf("Test: %s completed in %f seconds", f.TestText, f.Duration.Seconds()) + GinkgoWriter.Write([]byte(timedResult)) + }) + + It("Podman create --ip with garbage address", func() { + result := podmanTest.Podman([]string{"create", "--name", "test", "--ip", "114232346", ALPINE, "ls"}) + result.WaitWithDefaultTimeout() + Expect(result.ExitCode()).ToNot(Equal(0)) + }) + + It("Podman create --ip with v6 address", func() { + result := podmanTest.Podman([]string{"create", "--name", "test", "--ip", "2001:db8:bad:beef::1", ALPINE, "ls"}) + result.WaitWithDefaultTimeout() + Expect(result.ExitCode()).ToNot(Equal(0)) + }) + + It("Podman create --ip with non-allocatable IP", func() { + result := podmanTest.Podman([]string{"create", "--name", "test", "--ip", "203.0.113.124", ALPINE, "ls"}) + result.WaitWithDefaultTimeout() + Expect(result.ExitCode()).To(Equal(0)) + + result = podmanTest.Podman([]string{"start", "test"}) + result.WaitWithDefaultTimeout() + Expect(result.ExitCode()).ToNot(Equal(0)) + }) + + It("Podman create with specified static IP has correct IP", func() { + result := podmanTest.Podman([]string{"create", "--name", "test", "--ip", "10.88.64.128", ALPINE, "ip", "addr"}) + result.WaitWithDefaultTimeout() + Expect(result.ExitCode()).To(Equal(0)) + + result = podmanTest.Podman([]string{"start", "test"}) + result.WaitWithDefaultTimeout() + Expect(result.ExitCode()).To(Equal(0)) + + result = podmanTest.Podman([]string{"logs", "test"}) + result.WaitWithDefaultTimeout() + Expect(result.ExitCode()).To(Equal(0)) + Expect(result.OutputToString()).To(ContainSubstring("10.88.64.128/16")) + }) + + It("Podman create two containers with the same IP", func() { + result := podmanTest.Podman([]string{"create", "--name", "test1", "--ip", "10.88.64.128", ALPINE, "sleep", "999"}) + result.WaitWithDefaultTimeout() + Expect(result.ExitCode()).To(Equal(0)) + result = podmanTest.Podman([]string{"create", "--name", "test2", "--ip", "10.88.64.128", ALPINE, "ip", "addr"}) + result.WaitWithDefaultTimeout() + Expect(result.ExitCode()).To(Equal(0)) + result = podmanTest.Podman([]string{"start", "test1"}) + result.WaitWithDefaultTimeout() + Expect(result.ExitCode()).To(Equal(0)) + result = podmanTest.Podman([]string{"start", "test2"}) + result.WaitWithDefaultTimeout() + Expect(result.ExitCode()).ToNot(Equal(0)) + }) +}) diff --git a/test/e2e/create_test.go b/test/e2e/create_test.go index c36a8e31f..684a7cd88 100644 --- a/test/e2e/create_test.go +++ b/test/e2e/create_test.go @@ -3,7 +3,9 @@ package integration import ( "fmt" "os" + "path/filepath" + . "github.com/containers/libpod/test/utils" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" ) @@ -12,7 +14,7 @@ var _ = Describe("Podman create", func() { var ( tempdir string err error - podmanTest PodmanTest + podmanTest *PodmanTestIntegration ) BeforeEach(func() { @@ -20,7 +22,7 @@ var _ = Describe("Podman create", func() { if err != nil { os.Exit(1) } - podmanTest = PodmanCreate(tempdir) + podmanTest = PodmanTestCreate(tempdir) podmanTest.RestoreAllArtifacts() }) @@ -109,4 +111,93 @@ var _ = Describe("Podman create", func() { Expect(result.ExitCode()).To(Equal(0)) Expect(result.OutputToString()).To(Equal("/bin/foo -c")) }) + + It("podman create --mount flag with multiple mounts", func() { + vol1 := filepath.Join(podmanTest.TempDir, "vol-test1") + err := os.MkdirAll(vol1, 0755) + Expect(err).To(BeNil()) + vol2 := filepath.Join(podmanTest.TempDir, "vol-test2") + err = os.MkdirAll(vol2, 0755) + Expect(err).To(BeNil()) + + session := podmanTest.Podman([]string{"create", "--name", "test", "--mount", "type=bind,src=" + vol1 + ",target=/myvol1,z", "--mount", "type=bind,src=" + vol2 + ",target=/myvol2,z", ALPINE, "touch", "/myvol2/foo.txt"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + + session = podmanTest.Podman([]string{"start", "test"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + + session = podmanTest.Podman([]string{"logs", "test"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + Expect(session.OutputToString()).ToNot(ContainSubstring("cannot touch")) + }) + + It("podman create with --mount flag", func() { + if podmanTest.Host.Arch == "ppc64le" { + Skip("skip failing test on ppc64le") + } + mountPath := filepath.Join(podmanTest.TempDir, "secrets") + os.Mkdir(mountPath, 0755) + session := podmanTest.Podman([]string{"create", "--name", "test", "--rm", "--mount", fmt.Sprintf("type=bind,src=%s,target=/create/test", mountPath), ALPINE, "grep", "/create/test", "/proc/self/mountinfo"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + session = podmanTest.Podman([]string{"start", "test"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + session = podmanTest.Podman([]string{"logs", "test"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + Expect(session.OutputToString()).To(ContainSubstring("/create/test rw")) + + session = podmanTest.Podman([]string{"create", "--name", "test_ro", "--rm", "--mount", fmt.Sprintf("type=bind,src=%s,target=/create/test,ro", mountPath), ALPINE, "grep", "/create/test", "/proc/self/mountinfo"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + session = podmanTest.Podman([]string{"start", "test_ro"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + session = podmanTest.Podman([]string{"logs", "test_ro"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + Expect(session.OutputToString()).To(ContainSubstring("/create/test ro")) + + session = podmanTest.Podman([]string{"create", "--name", "test_shared", "--rm", "--mount", fmt.Sprintf("type=bind,src=%s,target=/create/test,shared", mountPath), ALPINE, "grep", "/create/test", "/proc/self/mountinfo"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + session = podmanTest.Podman([]string{"start", "test_shared"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + session = podmanTest.Podman([]string{"logs", "test_shared"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + found, matches := session.GrepString("/create/test") + Expect(found).Should(BeTrue()) + Expect(matches[0]).To(ContainSubstring("rw")) + Expect(matches[0]).To(ContainSubstring("shared")) + + mountPath = filepath.Join(podmanTest.TempDir, "scratchpad") + os.Mkdir(mountPath, 0755) + session = podmanTest.Podman([]string{"create", "--name", "test_tmpfs", "--rm", "--mount", "type=tmpfs,target=/create/test", ALPINE, "grep", "/create/test", "/proc/self/mountinfo"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + session = podmanTest.Podman([]string{"start", "test_tmpfs"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + session = podmanTest.Podman([]string{"logs", "test_tmpfs"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + Expect(session.OutputToString()).To(ContainSubstring("/create/test rw,nosuid,nodev,noexec,relatime - tmpfs")) + }) + + It("podman create --pod automatically", func() { + session := podmanTest.Podman([]string{"create", "--pod", "new:foobar", ALPINE, "ls"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + + check := podmanTest.Podman([]string{"pod", "ps", "--no-trunc"}) + check.WaitWithDefaultTimeout() + match, _ := check.GrepString("foobar") + Expect(match).To(BeTrue()) + }) }) diff --git a/test/e2e/diff_test.go b/test/e2e/diff_test.go index a83bb14da..2c0060dd5 100644 --- a/test/e2e/diff_test.go +++ b/test/e2e/diff_test.go @@ -5,6 +5,7 @@ import ( "os" "sort" + . "github.com/containers/libpod/test/utils" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" ) @@ -13,7 +14,7 @@ var _ = Describe("Podman diff", func() { var ( tempdir string err error - podmanTest PodmanTest + podmanTest *PodmanTestIntegration ) BeforeEach(func() { @@ -21,7 +22,7 @@ var _ = Describe("Podman diff", func() { if err != nil { os.Exit(1) } - podmanTest = PodmanCreate(tempdir) + podmanTest = PodmanTestCreate(tempdir) podmanTest.RestoreAllArtifacts() }) diff --git a/test/e2e/exec_test.go b/test/e2e/exec_test.go index 250e08704..fec80717f 100644 --- a/test/e2e/exec_test.go +++ b/test/e2e/exec_test.go @@ -4,6 +4,7 @@ import ( "fmt" "os" + . "github.com/containers/libpod/test/utils" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" ) @@ -12,7 +13,7 @@ var _ = Describe("Podman exec", func() { var ( tempdir string err error - podmanTest PodmanTest + podmanTest *PodmanTestIntegration ) BeforeEach(func() { @@ -20,7 +21,7 @@ var _ = Describe("Podman exec", func() { if err != nil { os.Exit(1) } - podmanTest = PodmanCreate(tempdir) + podmanTest = PodmanTestCreate(tempdir) podmanTest.RestoreAllArtifacts() }) diff --git a/test/e2e/exists_test.go b/test/e2e/exists_test.go new file mode 100644 index 000000000..d9652de4b --- /dev/null +++ b/test/e2e/exists_test.go @@ -0,0 +1,117 @@ +package integration + +import ( + "fmt" + "os" + + . "github.com/containers/libpod/test/utils" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +var _ = Describe("Podman image|container exists", func() { + var ( + tempdir string + err error + podmanTest *PodmanTestIntegration + ) + + BeforeEach(func() { + tempdir, err = CreateTempDirInTempDir() + if err != nil { + os.Exit(1) + } + podmanTest = PodmanTestCreate(tempdir) + podmanTest.RestoreAllArtifacts() + }) + + AfterEach(func() { + podmanTest.Cleanup() + f := CurrentGinkgoTestDescription() + timedResult := fmt.Sprintf("Test: %s completed in %f seconds", f.TestText, f.Duration.Seconds()) + GinkgoWriter.Write([]byte(timedResult)) + + }) + It("podman image exists in local storage by fq name", func() { + session := podmanTest.Podman([]string{"image", "exists", ALPINE}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + }) + It("podman image exists in local storage by short name", func() { + session := podmanTest.Podman([]string{"image", "exists", "alpine"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + }) + It("podman image does not exist in local storage", func() { + session := podmanTest.Podman([]string{"image", "exists", "alpine9999"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(1)) + }) + It("podman container exists in local storage by name", func() { + setup := podmanTest.RunTopContainer("foobar") + setup.WaitWithDefaultTimeout() + Expect(setup.ExitCode()).To(Equal(0)) + + session := podmanTest.Podman([]string{"container", "exists", "foobar"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + }) + It("podman container exists in local storage by container ID", func() { + setup := podmanTest.RunTopContainer("") + setup.WaitWithDefaultTimeout() + Expect(setup.ExitCode()).To(Equal(0)) + cid := setup.OutputToString() + + session := podmanTest.Podman([]string{"container", "exists", cid}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + }) + It("podman container exists in local storage by short container ID", func() { + setup := podmanTest.RunTopContainer("") + setup.WaitWithDefaultTimeout() + Expect(setup.ExitCode()).To(Equal(0)) + cid := setup.OutputToString()[0:12] + + session := podmanTest.Podman([]string{"container", "exists", cid}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + }) + It("podman container does not exist in local storage", func() { + session := podmanTest.Podman([]string{"container", "exists", "foobar"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(1)) + }) + + It("podman pod exists in local storage by name", func() { + setup, rc, _ := podmanTest.CreatePod("foobar") + setup.WaitWithDefaultTimeout() + Expect(rc).To(Equal(0)) + + session := podmanTest.Podman([]string{"pod", "exists", "foobar"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + }) + It("podman pod exists in local storage by container ID", func() { + setup, rc, podID := podmanTest.CreatePod("") + setup.WaitWithDefaultTimeout() + Expect(rc).To(Equal(0)) + + session := podmanTest.Podman([]string{"pod", "exists", podID}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + }) + It("podman pod exists in local storage by short container ID", func() { + setup, rc, podID := podmanTest.CreatePod("") + setup.WaitWithDefaultTimeout() + Expect(rc).To(Equal(0)) + + session := podmanTest.Podman([]string{"pod", "exists", podID[0:12]}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + }) + It("podman pod does not exist in local storage", func() { + session := podmanTest.Podman([]string{"pod", "exists", "foobar"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(1)) + }) +}) diff --git a/test/e2e/export_test.go b/test/e2e/export_test.go index c11fd777b..42ea45041 100644 --- a/test/e2e/export_test.go +++ b/test/e2e/export_test.go @@ -5,6 +5,7 @@ import ( "os" "path/filepath" + . "github.com/containers/libpod/test/utils" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" ) @@ -13,7 +14,7 @@ var _ = Describe("Podman export", func() { var ( tempdir string err error - podmanTest PodmanTest + podmanTest *PodmanTestIntegration ) BeforeEach(func() { @@ -21,7 +22,7 @@ var _ = Describe("Podman export", func() { if err != nil { os.Exit(1) } - podmanTest = PodmanCreate(tempdir) + podmanTest = PodmanTestCreate(tempdir) podmanTest.RestoreAllArtifacts() }) diff --git a/test/e2e/history_test.go b/test/e2e/history_test.go index d4b5ad5c0..9bec9ad13 100644 --- a/test/e2e/history_test.go +++ b/test/e2e/history_test.go @@ -4,6 +4,7 @@ import ( "fmt" "os" + . "github.com/containers/libpod/test/utils" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" ) @@ -12,7 +13,7 @@ var _ = Describe("Podman history", func() { var ( tempdir string err error - podmanTest PodmanTest + podmanTest *PodmanTestIntegration ) BeforeEach(func() { @@ -20,7 +21,7 @@ var _ = Describe("Podman history", func() { if err != nil { os.Exit(1) } - podmanTest = PodmanCreate(tempdir) + podmanTest = PodmanTestCreate(tempdir) podmanTest.RestoreAllArtifacts() }) diff --git a/test/e2e/images_test.go b/test/e2e/images_test.go index a8854d08d..a927088ca 100644 --- a/test/e2e/images_test.go +++ b/test/e2e/images_test.go @@ -5,6 +5,7 @@ import ( "os" "sort" + . "github.com/containers/libpod/test/utils" "github.com/docker/go-units" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" @@ -14,7 +15,7 @@ var _ = Describe("Podman images", func() { var ( tempdir string err error - podmanTest PodmanTest + podmanTest *PodmanTestIntegration ) BeforeEach(func() { @@ -22,7 +23,7 @@ var _ = Describe("Podman images", func() { if err != nil { os.Exit(1) } - podmanTest = PodmanCreate(tempdir) + podmanTest = PodmanTestCreate(tempdir) podmanTest.RestoreAllArtifacts() }) diff --git a/test/e2e/import_test.go b/test/e2e/import_test.go index 80773cf8b..9ed4593c6 100644 --- a/test/e2e/import_test.go +++ b/test/e2e/import_test.go @@ -5,6 +5,7 @@ import ( "os" "path/filepath" + . "github.com/containers/libpod/test/utils" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" ) @@ -13,7 +14,7 @@ var _ = Describe("Podman import", func() { var ( tempdir string err error - podmanTest PodmanTest + podmanTest *PodmanTestIntegration ) BeforeEach(func() { @@ -21,7 +22,7 @@ var _ = Describe("Podman import", func() { if err != nil { os.Exit(1) } - podmanTest = PodmanCreate(tempdir) + podmanTest = PodmanTestCreate(tempdir) podmanTest.RestoreAllArtifacts() }) diff --git a/test/e2e/info_test.go b/test/e2e/info_test.go index dd8645223..e972c86c8 100644 --- a/test/e2e/info_test.go +++ b/test/e2e/info_test.go @@ -4,6 +4,7 @@ import ( "fmt" "os" + . "github.com/containers/libpod/test/utils" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" ) @@ -12,7 +13,7 @@ var _ = Describe("Podman Info", func() { var ( tempdir string err error - podmanTest PodmanTest + podmanTest *PodmanTestIntegration ) BeforeEach(func() { @@ -20,7 +21,7 @@ var _ = Describe("Podman Info", func() { if err != nil { os.Exit(1) } - podmanTest = PodmanCreate(tempdir) + podmanTest = PodmanTestCreate(tempdir) }) AfterEach(func() { diff --git a/test/e2e/inspect_test.go b/test/e2e/inspect_test.go index bff56189e..87c4db935 100644 --- a/test/e2e/inspect_test.go +++ b/test/e2e/inspect_test.go @@ -5,6 +5,7 @@ import ( "os" "strings" + . "github.com/containers/libpod/test/utils" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" ) @@ -13,7 +14,7 @@ var _ = Describe("Podman inspect", func() { var ( tempdir string err error - podmanTest PodmanTest + podmanTest *PodmanTestIntegration ) BeforeEach(func() { @@ -21,7 +22,7 @@ var _ = Describe("Podman inspect", func() { if err != nil { os.Exit(1) } - podmanTest = PodmanCreate(tempdir) + podmanTest = PodmanTestCreate(tempdir) podmanTest.RestoreAllArtifacts() }) diff --git a/test/e2e/kill_test.go b/test/e2e/kill_test.go index fdf42f2b6..913a843cb 100644 --- a/test/e2e/kill_test.go +++ b/test/e2e/kill_test.go @@ -4,6 +4,7 @@ import ( "fmt" "os" + . "github.com/containers/libpod/test/utils" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" ) @@ -12,7 +13,7 @@ var _ = Describe("Podman kill", func() { var ( tempdir string err error - podmanTest PodmanTest + podmanTest *PodmanTestIntegration ) BeforeEach(func() { @@ -20,7 +21,7 @@ var _ = Describe("Podman kill", func() { if err != nil { os.Exit(1) } - podmanTest = PodmanCreate(tempdir) + podmanTest = PodmanTestCreate(tempdir) podmanTest.RestoreAllArtifacts() }) diff --git a/test/e2e/libpod_suite_test.go b/test/e2e/libpod_suite_test.go index 56a603f3e..f4f154ef2 100644 --- a/test/e2e/libpod_suite_test.go +++ b/test/e2e/libpod_suite_test.go @@ -1,8 +1,6 @@ package integration import ( - "bufio" - "context" "encoding/json" "fmt" "io/ioutil" @@ -11,11 +9,10 @@ import ( "path/filepath" "strings" "testing" - "time" "github.com/containers/libpod/libpod" "github.com/containers/libpod/pkg/inspect" - "github.com/containers/storage/pkg/parsers/kernel" + . "github.com/containers/libpod/test/utils" "github.com/containers/storage/pkg/reexec" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" @@ -29,30 +26,14 @@ var ( RUNC_BINARY string INTEGRATION_ROOT string CGROUP_MANAGER = "systemd" - STORAGE_OPTIONS = "--storage-driver vfs" ARTIFACT_DIR = "/tmp/.artifacts" - CACHE_IMAGES = []string{ALPINE, BB, fedoraMinimal, nginx, redis, registry, infra, labels} RESTORE_IMAGES = []string{ALPINE, BB} - ALPINE = "docker.io/library/alpine:latest" - BB = "docker.io/library/busybox:latest" - BB_GLIBC = "docker.io/library/busybox:glibc" - fedoraMinimal = "registry.fedoraproject.org/fedora-minimal:latest" - nginx = "quay.io/baude/alpine_nginx:latest" - redis = "docker.io/library/redis:alpine" - registry = "docker.io/library/registry:2" - infra = "k8s.gcr.io/pause:3.1" - labels = "quay.io/baude/alpine_labels:latest" defaultWaitTimeout = 90 ) -// PodmanSession wrapps the gexec.session so we can extend it -type PodmanSession struct { - *gexec.Session -} - -// PodmanTest struct for command line options -type PodmanTest struct { - PodmanBinary string +// PodmanTestIntegration struct for command line options +type PodmanTestIntegration struct { + PodmanTest ConmonBinary string CrioRoot string CNIConfigDir string @@ -60,16 +41,13 @@ type PodmanTest struct { RunRoot string StorageOptions string SignaturePolicyPath string - ArtifactPath string - TempDir string CgroupManager string Host HostOS } -// HostOS is a simple struct for the test os -type HostOS struct { - Distribution string - Version string +// PodmanSessionIntegration sturct for command line session +type PodmanSessionIntegration struct { + *PodmanSession } // TestLibpod ginkgo master function @@ -89,7 +67,7 @@ var _ = BeforeSuite(func() { //Cache images cwd, _ := os.Getwd() INTEGRATION_ROOT = filepath.Join(cwd, "../../") - podman := PodmanCreate("/tmp") + podman := PodmanTestCreate("/tmp") podman.ArtifactPath = ARTIFACT_DIR if _, err := os.Stat(ARTIFACT_DIR); os.IsNotExist(err) { if err = os.Mkdir(ARTIFACT_DIR, 0777); err != nil { @@ -119,13 +97,8 @@ var _ = BeforeSuite(func() { } }) -// CreateTempDirin -func CreateTempDirInTempDir() (string, error) { - return ioutil.TempDir("", "podman_test") -} - -// PodmanCreate creates a PodmanTest instance for the tests -func PodmanCreate(tempDir string) PodmanTest { +// PodmanTestCreate creates a PodmanTestIntegration instance for the tests +func PodmanTestCreate(tempDir string) *PodmanTestIntegration { host := GetHostDistributionInfo() cwd, _ := os.Getwd() @@ -166,8 +139,12 @@ func PodmanCreate(tempDir string) PodmanTest { CNIConfigDir := "/etc/cni/net.d" - p := PodmanTest{ - PodmanBinary: podmanBinary, + p := &PodmanTestIntegration{ + PodmanTest: PodmanTest{ + PodmanBinary: podmanBinary, + ArtifactPath: ARTIFACT_DIR, + TempDir: tempDir, + }, ConmonBinary: conmonBinary, CrioRoot: filepath.Join(tempDir, "crio"), CNIConfigDir: CNIConfigDir, @@ -175,77 +152,59 @@ func PodmanCreate(tempDir string) PodmanTest { RunRoot: filepath.Join(tempDir, "crio-run"), StorageOptions: storageOptions, SignaturePolicyPath: filepath.Join(INTEGRATION_ROOT, "test/policy.json"), - ArtifactPath: ARTIFACT_DIR, - TempDir: tempDir, CgroupManager: cgroupManager, Host: host, } // Setup registries.conf ENV variable p.setDefaultRegistriesConfigEnv() + // Rewrite the PodmanAsUser function + p.PodmanMakeOptions = p.makeOptions return p } //MakeOptions assembles all the podman main options -func (p *PodmanTest) MakeOptions() []string { - return strings.Split(fmt.Sprintf("--root %s --runroot %s --runtime %s --conmon %s --cni-config-dir %s --cgroup-manager %s", +func (p *PodmanTestIntegration) makeOptions(args []string) []string { + podmanOptions := strings.Split(fmt.Sprintf("--root %s --runroot %s --runtime %s --conmon %s --cni-config-dir %s --cgroup-manager %s", p.CrioRoot, p.RunRoot, p.RunCBinary, p.ConmonBinary, p.CNIConfigDir, p.CgroupManager), " ") -} - -// Podman is the exec call to podman on the filesystem, uid and gid the credentials to use -func (p *PodmanTest) PodmanAsUser(args []string, uid, gid uint32, env []string) *PodmanSession { - podmanOptions := p.MakeOptions() if os.Getenv("HOOK_OPTION") != "" { podmanOptions = append(podmanOptions, os.Getenv("HOOK_OPTION")) } podmanOptions = append(podmanOptions, strings.Split(p.StorageOptions, " ")...) podmanOptions = append(podmanOptions, args...) - if env == nil { - fmt.Printf("Running: %s %s\n", p.PodmanBinary, strings.Join(podmanOptions, " ")) - } else { - fmt.Printf("Running: (env: %v) %s %s\n", env, p.PodmanBinary, strings.Join(podmanOptions, " ")) - } - var command *exec.Cmd - - if uid != 0 || gid != 0 { - nsEnterOpts := append([]string{"--userspec", fmt.Sprintf("%d:%d", uid, gid), "/", p.PodmanBinary}, podmanOptions...) - command = exec.Command("chroot", nsEnterOpts...) - } else { - command = exec.Command(p.PodmanBinary, podmanOptions...) - } - if env != nil { - command.Env = env - } - - session, err := gexec.Start(command, GinkgoWriter, GinkgoWriter) - if err != nil { - Fail(fmt.Sprintf("unable to run podman command: %s\n%v", strings.Join(podmanOptions, " "), err)) - } - return &PodmanSession{session} + return podmanOptions } // Podman is the exec call to podman on the filesystem -func (p *PodmanTest) Podman(args []string) *PodmanSession { - return p.PodmanAsUser(args, 0, 0, nil) +func (p *PodmanTestIntegration) Podman(args []string) *PodmanSessionIntegration { + podmanSession := p.PodmanBase(args) + return &PodmanSessionIntegration{podmanSession} } -//WaitForContainer waits on a started container -func WaitForContainer(p *PodmanTest) bool { - for i := 0; i < 10; i++ { - if p.NumberOfRunningContainers() == 1 { - return true - } - time.Sleep(1 * time.Second) +// PodmanAsUser is the exec call to podman on the filesystem with the specified uid/gid and environment +func (p *PodmanTestIntegration) PodmanAsUser(args []string, uid, gid uint32, env []string) *PodmanSessionIntegration { + podmanSession := p.PodmanAsUserBase(args, uid, gid, env) + return &PodmanSessionIntegration{podmanSession} +} + +// PodmanPID execs podman and returns its PID +func (p *PodmanTestIntegration) PodmanPID(args []string) (*PodmanSessionIntegration, int) { + podmanOptions := p.MakeOptions(args) + fmt.Printf("Running: %s %s\n", p.PodmanBinary, strings.Join(podmanOptions, " ")) + command := exec.Command(p.PodmanBinary, podmanOptions...) + session, err := gexec.Start(command, GinkgoWriter, GinkgoWriter) + if err != nil { + Fail(fmt.Sprintf("unable to run podman command: %s", strings.Join(podmanOptions, " "))) } - return false + podmanSession := &PodmanSession{session} + return &PodmanSessionIntegration{podmanSession}, command.Process.Pid } // Cleanup cleans up the temporary store -func (p *PodmanTest) Cleanup() { +func (p *PodmanTestIntegration) Cleanup() { // Remove all containers stopall := p.Podman([]string{"stop", "-a", "--timeout", "0"}) stopall.WaitWithDefaultTimeout() - session := p.Podman([]string{"rm", "-fa"}) session.Wait(90) // Nuke tempdir @@ -258,7 +217,7 @@ func (p *PodmanTest) Cleanup() { } // CleanupPod cleans up the temporary store -func (p *PodmanTest) CleanupPod() { +func (p *PodmanTestIntegration) CleanupPod() { // Remove all containers session := p.Podman([]string{"pod", "rm", "-fa"}) session.Wait(90) @@ -268,103 +227,26 @@ func (p *PodmanTest) CleanupPod() { } } -// GrepString takes session output and behaves like grep. it returns a bool -// if successful and an array of strings on positive matches -func (s *PodmanSession) GrepString(term string) (bool, []string) { - var ( - greps []string - matches bool - ) - - for _, line := range strings.Split(s.OutputToString(), "\n") { - if strings.Contains(line, term) { - matches = true - greps = append(greps, line) - } - } - return matches, greps -} - -// Pull Images pulls multiple images -func (p *PodmanTest) PullImages(images []string) error { +// PullImages pulls multiple images +func (p *PodmanTestIntegration) PullImages(images []string) error { for _, i := range images { p.PullImage(i) } return nil } -// Pull Image a single image +// PullImage pulls a single image // TODO should the timeout be configurable? -func (p *PodmanTest) PullImage(image string) error { +func (p *PodmanTestIntegration) PullImage(image string) error { session := p.Podman([]string{"pull", image}) session.Wait(60) Expect(session.ExitCode()).To(Equal(0)) return nil } -// OutputToString formats session output to string -func (s *PodmanSession) OutputToString() string { - fields := strings.Fields(fmt.Sprintf("%s", s.Out.Contents())) - return strings.Join(fields, " ") -} - -// OutputToStringArray returns the output as a []string -// where each array item is a line split by newline -func (s *PodmanSession) OutputToStringArray() []string { - var results []string - output := fmt.Sprintf("%s", s.Out.Contents()) - for _, line := range strings.Split(output, "\n") { - if line != "" { - results = append(results, line) - } - } - return results -} - -// ErrorGrepString takes session stderr output and behaves like grep. it returns a bool -// if successful and an array of strings on positive matches -func (s *PodmanSession) ErrorGrepString(term string) (bool, []string) { - var ( - greps []string - matches bool - ) - - for _, line := range strings.Split(s.ErrorToString(), "\n") { - if strings.Contains(line, term) { - matches = true - greps = append(greps, line) - } - } - return matches, greps -} - -// ErrorToString formats session stderr to string -func (s *PodmanSession) ErrorToString() string { - fields := strings.Fields(fmt.Sprintf("%s", s.Err.Contents())) - return strings.Join(fields, " ") -} - -// ErrorToStringArray returns the stderr output as a []string -// where each array item is a line split by newline -func (s *PodmanSession) ErrorToStringArray() []string { - output := fmt.Sprintf("%s", s.Err.Contents()) - return strings.Split(output, "\n") -} - -// IsJSONOutputValid attempts to unmarshal the session buffer -// and if successful, returns true, else false -func (s *PodmanSession) IsJSONOutputValid() bool { - var i interface{} - if err := json.Unmarshal(s.Out.Contents(), &i); err != nil { - fmt.Println(err) - return false - } - return true -} - // InspectContainerToJSON takes the session output of an inspect // container and returns json -func (s *PodmanSession) InspectContainerToJSON() []inspect.ContainerData { +func (s *PodmanSessionIntegration) InspectContainerToJSON() []inspect.ContainerData { var i []inspect.ContainerData err := json.Unmarshal(s.Out.Contents(), &i) Expect(err).To(BeNil()) @@ -372,7 +254,7 @@ func (s *PodmanSession) InspectContainerToJSON() []inspect.ContainerData { } // InspectPodToJSON takes the sessions output from a pod inspect and returns json -func (s *PodmanSession) InspectPodToJSON() libpod.PodInspect { +func (s *PodmanSessionIntegration) InspectPodToJSON() libpod.PodInspect { var i libpod.PodInspect err := json.Unmarshal(s.Out.Contents(), &i) Expect(err).To(BeNil()) @@ -381,30 +263,15 @@ func (s *PodmanSession) InspectPodToJSON() libpod.PodInspect { // InspectImageJSON takes the session output of an inspect // image and returns json -func (s *PodmanSession) InspectImageJSON() []inspect.ImageData { +func (s *PodmanSessionIntegration) InspectImageJSON() []inspect.ImageData { var i []inspect.ImageData err := json.Unmarshal(s.Out.Contents(), &i) Expect(err).To(BeNil()) return i } -func (s *PodmanSession) WaitWithDefaultTimeout() { - s.Wait(defaultWaitTimeout) - fmt.Println("output:", s.OutputToString()) -} - -// SystemExec is used to exec a system command to check its exit code or output -func (p *PodmanTest) SystemExec(command string, args []string) *PodmanSession { - c := exec.Command(command, args...) - session, err := gexec.Start(c, GinkgoWriter, GinkgoWriter) - if err != nil { - Fail(fmt.Sprintf("unable to run command: %s %s", command, strings.Join(args, " "))) - } - return &PodmanSession{session} -} - // CreateArtifact creates a cached image in the artifact dir -func (p *PodmanTest) CreateArtifact(image string) error { +func (p *PodmanTestIntegration) CreateArtifact(image string) error { if os.Getenv("NO_TEST_CACHE") != "" { return nil } @@ -425,7 +292,7 @@ func (p *PodmanTest) CreateArtifact(image string) error { } // RestoreArtifact puts the cached image into our test store -func (p *PodmanTest) RestoreArtifact(image string) error { +func (p *PodmanTestIntegration) RestoreArtifact(image string) error { fmt.Printf("Restoring %s...\n", image) dest := strings.Split(image, "/") destName := fmt.Sprintf("/tmp/%s.tar", strings.Replace(strings.Join(strings.Split(dest[len(dest)-1], "/"), ""), ":", "-", -1)) @@ -435,7 +302,7 @@ func (p *PodmanTest) RestoreArtifact(image string) error { } // RestoreAllArtifacts unpacks all cached images -func (p *PodmanTest) RestoreAllArtifacts() error { +func (p *PodmanTestIntegration) RestoreAllArtifacts() error { if os.Getenv("NO_TEST_CACHE") != "" { return nil } @@ -449,7 +316,7 @@ func (p *PodmanTest) RestoreAllArtifacts() error { // CreatePod creates a pod with no infra container // it optionally takes a pod name -func (p *PodmanTest) CreatePod(name string) (*PodmanSession, int, string) { +func (p *PodmanTestIntegration) CreatePod(name string) (*PodmanSessionIntegration, int, string) { var podmanArgs = []string{"pod", "create", "--infra=false", "--share", ""} if name != "" { podmanArgs = append(podmanArgs, "--name", name) @@ -461,7 +328,7 @@ func (p *PodmanTest) CreatePod(name string) (*PodmanSession, int, string) { //RunTopContainer runs a simple container in the background that // runs top. If the name passed != "", it will have a name -func (p *PodmanTest) RunTopContainer(name string) *PodmanSession { +func (p *PodmanTestIntegration) RunTopContainer(name string) *PodmanSessionIntegration { var podmanArgs = []string{"run"} if name != "" { podmanArgs = append(podmanArgs, "--name", name) @@ -470,7 +337,7 @@ func (p *PodmanTest) RunTopContainer(name string) *PodmanSession { return p.Podman(podmanArgs) } -func (p *PodmanTest) RunTopContainerInPod(name, pod string) *PodmanSession { +func (p *PodmanTestIntegration) RunTopContainerInPod(name, pod string) *PodmanSessionIntegration { var podmanArgs = []string{"run", "--pod", pod} if name != "" { podmanArgs = append(podmanArgs, "--name", name) @@ -481,7 +348,7 @@ func (p *PodmanTest) RunTopContainerInPod(name, pod string) *PodmanSession { //RunLsContainer runs a simple container in the background that // simply runs ls. If the name passed != "", it will have a name -func (p *PodmanTest) RunLsContainer(name string) (*PodmanSession, int, string) { +func (p *PodmanTestIntegration) RunLsContainer(name string) (*PodmanSessionIntegration, int, string) { var podmanArgs = []string{"run"} if name != "" { podmanArgs = append(podmanArgs, "--name", name) @@ -492,7 +359,7 @@ func (p *PodmanTest) RunLsContainer(name string) (*PodmanSession, int, string) { return session, session.ExitCode(), session.OutputToString() } -func (p *PodmanTest) RunLsContainerInPod(name, pod string) (*PodmanSession, int, string) { +func (p *PodmanTestIntegration) RunLsContainerInPod(name, pod string) (*PodmanSessionIntegration, int, string) { var podmanArgs = []string{"run", "--pod", pod} if name != "" { podmanArgs = append(podmanArgs, "--name", name) @@ -503,147 +370,9 @@ func (p *PodmanTest) RunLsContainerInPod(name, pod string) (*PodmanSession, int, return session, session.ExitCode(), session.OutputToString() } -//NumberOfContainersRunning returns an int of how many -// containers are currently running. -func (p *PodmanTest) NumberOfContainersRunning() int { - var containers []string - ps := p.Podman([]string{"ps", "-q"}) - ps.WaitWithDefaultTimeout() - Expect(ps.ExitCode()).To(Equal(0)) - for _, i := range ps.OutputToStringArray() { - if i != "" { - containers = append(containers, i) - } - } - return len(containers) -} - -// NumberOfContainers returns an int of how many -// containers are currently defined. -func (p *PodmanTest) NumberOfContainers() int { - var containers []string - ps := p.Podman([]string{"ps", "-aq"}) - ps.WaitWithDefaultTimeout() - Expect(ps.ExitCode()).To(Equal(0)) - for _, i := range ps.OutputToStringArray() { - if i != "" { - containers = append(containers, i) - } - } - return len(containers) -} - -// NumberOfPods returns an int of how many -// pods are currently defined. -func (p *PodmanTest) NumberOfPods() int { - var pods []string - ps := p.Podman([]string{"pod", "ps", "-q"}) - ps.WaitWithDefaultTimeout() - Expect(ps.ExitCode()).To(Equal(0)) - for _, i := range ps.OutputToStringArray() { - if i != "" { - pods = append(pods, i) - } - } - return len(pods) -} - -// NumberOfRunningContainers returns an int of how many containers are currently -// running -func (p *PodmanTest) NumberOfRunningContainers() int { - var containers []string - ps := p.Podman([]string{"ps", "-q"}) - ps.WaitWithDefaultTimeout() - Expect(ps.ExitCode()).To(Equal(0)) - for _, i := range ps.OutputToStringArray() { - if i != "" { - containers = append(containers, i) - } - } - return len(containers) -} - -// StringInSlice determines if a string is in a string slice, returns bool -func StringInSlice(s string, sl []string) bool { - for _, i := range sl { - if i == s { - return true - } - } - return false -} - -//LineInOutputStartsWith returns true if a line in a -// session output starts with the supplied string -func (s *PodmanSession) LineInOuputStartsWith(term string) bool { - for _, i := range s.OutputToStringArray() { - if strings.HasPrefix(i, term) { - return true - } - } - return false -} - -//LineInOutputContains returns true if a line in a -// session output starts with the supplied string -func (s *PodmanSession) LineInOutputContains(term string) bool { - for _, i := range s.OutputToStringArray() { - if strings.Contains(i, term) { - return true - } - } - return false -} - -//tagOutPutToMap parses each string in imagesOutput and returns -// a map of repo:tag pairs. Notice, the first array item will -// be skipped as it's considered to be the header. -func tagOutputToMap(imagesOutput []string) map[string]string { - m := make(map[string]string) - // iterate over output but skip the header - for _, i := range imagesOutput[1:] { - tmp := []string{} - for _, x := range strings.Split(i, " ") { - if x != "" { - tmp = append(tmp, x) - } - } - // podman-images(1) return a list like output - // in the format of "Repository Tag [...]" - if len(tmp) < 2 { - continue - } - m[tmp[0]] = tmp[1] - } - return m -} - -//LineInOutputContainsTag returns true if a line in the -// session's output contains the repo-tag pair as returned -// by podman-images(1). -func (s *PodmanSession) LineInOutputContainsTag(repo, tag string) bool { - tagMap := tagOutputToMap(s.OutputToStringArray()) - for r, t := range tagMap { - if repo == r && tag == t { - return true - } - } - return false -} - -//GetContainerStatus returns the containers state. -// This function assumes only one container is active. -func (p *PodmanTest) GetContainerStatus() string { - var podmanArgs = []string{"ps"} - podmanArgs = append(podmanArgs, "--all", "--format={{.Status}}") - session := p.Podman(podmanArgs) - session.WaitWithDefaultTimeout() - return session.OutputToString() -} - // BuildImage uses podman build and buildah to build an image // called imageName based on a string dockerfile -func (p *PodmanTest) BuildImage(dockerfile, imageName string, layers string) { +func (p *PodmanTestIntegration) BuildImage(dockerfile, imageName string, layers string) { dockerfilePath := filepath.Join(p.TempDir, "Dockerfile") err := ioutil.WriteFile(dockerfilePath, []byte(dockerfile), 0755) Expect(err).To(BeNil()) @@ -652,33 +381,12 @@ func (p *PodmanTest) BuildImage(dockerfile, imageName string, layers string) { Expect(session.ExitCode()).To(Equal(0)) } -//GetHostDistributionInfo returns a struct with its distribution name and version -func GetHostDistributionInfo() HostOS { - f, err := os.Open("/etc/os-release") - defer f.Close() - if err != nil { - return HostOS{} - } - - l := bufio.NewScanner(f) - host := HostOS{} - for l.Scan() { - if strings.HasPrefix(l.Text(), "ID=") { - host.Distribution = strings.Replace(strings.TrimSpace(strings.Join(strings.Split(l.Text(), "=")[1:], "")), "\"", "", -1) - } - if strings.HasPrefix(l.Text(), "VERSION_ID=") { - host.Version = strings.Replace(strings.TrimSpace(strings.Join(strings.Split(l.Text(), "=")[1:], "")), "\"", "", -1) - } - } - return host -} - -func (p *PodmanTest) setDefaultRegistriesConfigEnv() { +func (p *PodmanTestIntegration) setDefaultRegistriesConfigEnv() { defaultFile := filepath.Join(INTEGRATION_ROOT, "test/registries.conf") os.Setenv("REGISTRIES_CONFIG_PATH", defaultFile) } -func (p *PodmanTest) setRegistriesConfigEnv(b []byte) { +func (p *PodmanTestIntegration) setRegistriesConfigEnv(b []byte) { outfile := filepath.Join(p.TempDir, "registries.conf") os.Setenv("REGISTRIES_CONFIG_PATH", outfile) ioutil.WriteFile(outfile, b, 0644) @@ -687,81 +395,3 @@ func (p *PodmanTest) setRegistriesConfigEnv(b []byte) { func resetRegistriesConfigEnv() { os.Setenv("REGISTRIES_CONFIG_PATH", "") } - -// IsKernelNewThan compares the current kernel version to one provided. If -// the kernel is equal to or greater, returns true -func IsKernelNewThan(version string) (bool, error) { - inputVersion, err := kernel.ParseRelease(version) - if err != nil { - return false, err - } - kv, err := kernel.GetKernelVersion() - if err == nil { - return false, err - } - // CompareKernelVersion compares two kernel.VersionInfo structs. - // Returns -1 if a < b, 0 if a == b, 1 it a > b - result := kernel.CompareKernelVersion(*kv, *inputVersion) - if result >= 0 { - return true, nil - } - return false, nil - -} - -//Wait process or service inside container start, and ready to be used. -func WaitContainerReady(p *PodmanTest, id string, expStr string, timeout int, step int) bool { - startTime := time.Now() - s := p.Podman([]string{"logs", id}) - s.WaitWithDefaultTimeout() - fmt.Println(startTime) - for { - if time.Since(startTime) >= time.Duration(timeout)*time.Second { - return false - } - if strings.Contains(s.OutputToString(), expStr) { - return true - } - time.Sleep(time.Duration(step) * time.Second) - s = p.Podman([]string{"logs", id}) - s.WaitWithDefaultTimeout() - } -} - -//IsCommandAvaible check if command exist -func IsCommandAvailable(command string) bool { - check := exec.Command("bash", "-c", strings.Join([]string{"command -v", command}, " ")) - err := check.Run() - if err != nil { - return false - } - return true -} - -// WriteJsonFile write json format data to a json file -func WriteJsonFile(data []byte, filePath string) error { - var jsonData map[string]interface{} - json.Unmarshal(data, &jsonData) - formatJson, _ := json.MarshalIndent(jsonData, "", " ") - return ioutil.WriteFile(filePath, formatJson, 0644) -} - -func getTestContext() context.Context { - return context.Background() -} - -func containerized() bool { - container := os.Getenv("container") - if container != "" { - return true - } - b, err := ioutil.ReadFile("/proc/1/cgroup") - if err != nil { - // shrug, if we cannot read that file, return false - return false - } - if strings.Index(string(b), "docker") > -1 { - return true - } - return false -} diff --git a/test/e2e/load_test.go b/test/e2e/load_test.go index 4f288c216..4d7007191 100644 --- a/test/e2e/load_test.go +++ b/test/e2e/load_test.go @@ -5,6 +5,7 @@ import ( "os" "path/filepath" + . "github.com/containers/libpod/test/utils" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" ) @@ -13,7 +14,7 @@ var _ = Describe("Podman load", func() { var ( tempdir string err error - podmanTest PodmanTest + podmanTest *PodmanTestIntegration ) BeforeEach(func() { @@ -21,7 +22,7 @@ var _ = Describe("Podman load", func() { if err != nil { os.Exit(1) } - podmanTest = PodmanCreate(tempdir) + podmanTest = PodmanTestCreate(tempdir) podmanTest.RestoreAllArtifacts() }) @@ -55,7 +56,7 @@ var _ = Describe("Podman load", func() { save.WaitWithDefaultTimeout() Expect(save.ExitCode()).To(Equal(0)) - compress := podmanTest.SystemExec("gzip", []string{outfile}) + compress := SystemExec("gzip", []string{outfile}) compress.WaitWithDefaultTimeout() outfile = outfile + ".gz" @@ -139,6 +140,9 @@ var _ = Describe("Podman load", func() { }) It("podman load multiple tags", func() { + if podmanTest.Host.Arch == "ppc64le" { + Skip("skip on ppc64le") + } outfile := filepath.Join(podmanTest.TempDir, "alpine.tar") alpVersion := "docker.io/library/alpine:3.2" @@ -250,7 +254,7 @@ var _ = Describe("Podman load", func() { save := podmanTest.Podman([]string{"save", "-o", outfile, BB}) save.WaitWithDefaultTimeout() Expect(save.ExitCode()).To(Equal(0)) - session := podmanTest.SystemExec("xz", []string{outfile}) + session := SystemExec("xz", []string{outfile}) session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) diff --git a/test/e2e/logs_test.go b/test/e2e/logs_test.go index 871987db0..236ddb221 100644 --- a/test/e2e/logs_test.go +++ b/test/e2e/logs_test.go @@ -4,6 +4,7 @@ import ( "fmt" "os" + . "github.com/containers/libpod/test/utils" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" ) @@ -12,7 +13,7 @@ var _ = Describe("Podman logs", func() { var ( tempdir string err error - podmanTest PodmanTest + podmanTest *PodmanTestIntegration ) BeforeEach(func() { @@ -20,7 +21,7 @@ var _ = Describe("Podman logs", func() { if err != nil { os.Exit(1) } - podmanTest = PodmanCreate(tempdir) + podmanTest = PodmanTestCreate(tempdir) podmanTest.RestoreAllArtifacts() }) @@ -33,7 +34,6 @@ var _ = Describe("Podman logs", func() { //sudo bin/podman run -it --rm fedora-minimal bash -c 'for a in `seq 5`; do echo hello; done' It("podman logs for container", func() { - podmanTest.RestoreArtifact(fedoraMinimal) logc := podmanTest.Podman([]string{"run", "-dt", ALPINE, "sh", "-c", "echo podman; echo podman; echo podman"}) logc.WaitWithDefaultTimeout() Expect(logc.ExitCode()).To(Equal(0)) @@ -46,7 +46,6 @@ var _ = Describe("Podman logs", func() { }) It("podman logs tail two lines", func() { - podmanTest.RestoreArtifact(fedoraMinimal) logc := podmanTest.Podman([]string{"run", "-dt", ALPINE, "sh", "-c", "echo podman; echo podman; echo podman"}) logc.WaitWithDefaultTimeout() Expect(logc.ExitCode()).To(Equal(0)) @@ -59,7 +58,6 @@ var _ = Describe("Podman logs", func() { }) It("podman logs tail 99 lines", func() { - podmanTest.RestoreArtifact(fedoraMinimal) logc := podmanTest.Podman([]string{"run", "-dt", ALPINE, "sh", "-c", "echo podman; echo podman; echo podman"}) logc.WaitWithDefaultTimeout() Expect(logc.ExitCode()).To(Equal(0)) @@ -72,7 +70,6 @@ var _ = Describe("Podman logs", func() { }) It("podman logs tail 2 lines with timestamps", func() { - podmanTest.RestoreArtifact(fedoraMinimal) logc := podmanTest.Podman([]string{"run", "-dt", ALPINE, "sh", "-c", "echo podman; echo podman; echo podman"}) logc.WaitWithDefaultTimeout() Expect(logc.ExitCode()).To(Equal(0)) @@ -85,7 +82,6 @@ var _ = Describe("Podman logs", func() { }) It("podman logs latest with since time", func() { - podmanTest.RestoreArtifact(fedoraMinimal) logc := podmanTest.Podman([]string{"run", "-dt", ALPINE, "sh", "-c", "echo podman; echo podman; echo podman"}) logc.WaitWithDefaultTimeout() Expect(logc.ExitCode()).To(Equal(0)) @@ -98,7 +94,6 @@ var _ = Describe("Podman logs", func() { }) It("podman logs latest with since duration", func() { - podmanTest.RestoreArtifact(fedoraMinimal) logc := podmanTest.Podman([]string{"run", "-dt", ALPINE, "sh", "-c", "echo podman; echo podman; echo podman"}) logc.WaitWithDefaultTimeout() Expect(logc.ExitCode()).To(Equal(0)) diff --git a/test/e2e/mount_test.go b/test/e2e/mount_test.go index fbb0a3eb7..a93a0aa4a 100644 --- a/test/e2e/mount_test.go +++ b/test/e2e/mount_test.go @@ -4,6 +4,7 @@ import ( "fmt" "os" + . "github.com/containers/libpod/test/utils" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" ) @@ -12,7 +13,7 @@ var _ = Describe("Podman mount", func() { var ( tempdir string err error - podmanTest PodmanTest + podmanTest *PodmanTestIntegration ) BeforeEach(func() { @@ -20,7 +21,7 @@ var _ = Describe("Podman mount", func() { if err != nil { os.Exit(1) } - podmanTest = PodmanCreate(tempdir) + podmanTest = PodmanTestCreate(tempdir) podmanTest.RestoreAllArtifacts() }) diff --git a/test/e2e/namespace_test.go b/test/e2e/namespace_test.go index 017edd231..ebce09f54 100644 --- a/test/e2e/namespace_test.go +++ b/test/e2e/namespace_test.go @@ -4,6 +4,7 @@ import ( "fmt" "os" + . "github.com/containers/libpod/test/utils" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" ) @@ -12,7 +13,7 @@ var _ = Describe("Podman namespaces", func() { var ( tempdir string err error - podmanTest PodmanTest + podmanTest *PodmanTestIntegration ) BeforeEach(func() { @@ -20,7 +21,7 @@ var _ = Describe("Podman namespaces", func() { if err != nil { os.Exit(1) } - podmanTest = PodmanCreate(tempdir) + podmanTest = PodmanTestCreate(tempdir) podmanTest.RestoreAllArtifacts() }) diff --git a/test/e2e/pause_test.go b/test/e2e/pause_test.go index c34964f59..e109bc077 100644 --- a/test/e2e/pause_test.go +++ b/test/e2e/pause_test.go @@ -4,6 +4,7 @@ import ( "fmt" "os" + . "github.com/containers/libpod/test/utils" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" ) @@ -12,7 +13,7 @@ var _ = Describe("Podman pause", func() { var ( tempdir string err error - podmanTest PodmanTest + podmanTest *PodmanTestIntegration ) pausedState := "Paused" @@ -23,7 +24,7 @@ var _ = Describe("Podman pause", func() { if err != nil { os.Exit(1) } - podmanTest = PodmanCreate(tempdir) + podmanTest = PodmanTestCreate(tempdir) podmanTest.RestoreAllArtifacts() }) @@ -91,7 +92,7 @@ var _ = Describe("Podman pause", func() { }) - It("podman remove a paused container by id", func() { + It("podman remove a paused container by id without force", func() { session := podmanTest.RunTopContainer("") session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) @@ -111,25 +112,26 @@ var _ = Describe("Podman pause", func() { Expect(podmanTest.NumberOfContainersRunning()).To(Equal(0)) Expect(podmanTest.GetContainerStatus()).To(ContainSubstring(pausedState)) - result = podmanTest.Podman([]string{"rm", "--force", cid}) - result.WaitWithDefaultTimeout() + }) - Expect(result.ExitCode()).To(Equal(125)) - Expect(podmanTest.NumberOfContainersRunning()).To(Equal(0)) - Expect(podmanTest.GetContainerStatus()).To(ContainSubstring(pausedState)) + It("podman remove a paused container by id with force", func() { + session := podmanTest.RunTopContainer("") + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + cid := session.OutputToString() - result = podmanTest.Podman([]string{"unpause", cid}) + result := podmanTest.Podman([]string{"pause", cid}) result.WaitWithDefaultTimeout() Expect(result.ExitCode()).To(Equal(0)) - Expect(podmanTest.NumberOfContainersRunning()).To(Equal(1)) + Expect(podmanTest.NumberOfContainersRunning()).To(Equal(0)) + Expect(podmanTest.GetContainerStatus()).To(ContainSubstring(pausedState)) result = podmanTest.Podman([]string{"rm", "--force", cid}) result.WaitWithDefaultTimeout() Expect(result.ExitCode()).To(Equal(0)) Expect(podmanTest.NumberOfContainersRunning()).To(Equal(0)) - }) It("podman stop a paused container by id", func() { @@ -213,4 +215,70 @@ var _ = Describe("Podman pause", func() { result.WaitWithDefaultTimeout() }) + It("Pause all containers (no containers exist)", func() { + result := podmanTest.Podman([]string{"pause", "--all"}) + result.WaitWithDefaultTimeout() + Expect(result.ExitCode()).To(Equal(0)) + Expect(podmanTest.NumberOfContainersRunning()).To(Equal(0)) + + }) + + It("Unpause all containers (no paused containers exist)", func() { + result := podmanTest.Podman([]string{"unpause", "--all"}) + result.WaitWithDefaultTimeout() + Expect(result.ExitCode()).To(Equal(0)) + Expect(podmanTest.NumberOfContainersRunning()).To(Equal(0)) + }) + + It("Pause a bunch of running containers", func() { + podmanTest.RestoreArtifact(nginx) + for i := 0; i < 3; i++ { + name := fmt.Sprintf("test%d", i) + run := podmanTest.Podman([]string{"run", "-dt", "--name", name, nginx}) + run.WaitWithDefaultTimeout() + Expect(run.ExitCode()).To(Equal(0)) + + } + running := podmanTest.Podman([]string{"ps", "-q"}) + running.WaitWithDefaultTimeout() + Expect(running.ExitCode()).To(Equal(0)) + Expect(len(running.OutputToStringArray())).To(Equal(3)) + + pause := podmanTest.Podman([]string{"pause", "--all"}) + pause.WaitWithDefaultTimeout() + Expect(pause.ExitCode()).To(Equal(0)) + + running = podmanTest.Podman([]string{"ps", "-q"}) + running.WaitWithDefaultTimeout() + Expect(running.ExitCode()).To(Equal(0)) + Expect(len(running.OutputToStringArray())).To(Equal(0)) + + unpause := podmanTest.Podman([]string{"unpause", "--all"}) + unpause.WaitWithDefaultTimeout() + Expect(unpause.ExitCode()).To(Equal(0)) + }) + + It("Unpause a bunch of running containers", func() { + podmanTest.RestoreArtifact(nginx) + for i := 0; i < 3; i++ { + name := fmt.Sprintf("test%d", i) + run := podmanTest.Podman([]string{"run", "-dt", "--name", name, nginx}) + run.WaitWithDefaultTimeout() + Expect(run.ExitCode()).To(Equal(0)) + + } + pause := podmanTest.Podman([]string{"pause", "--all"}) + pause.WaitWithDefaultTimeout() + Expect(pause.ExitCode()).To(Equal(0)) + + unpause := podmanTest.Podman([]string{"unpause", "--all"}) + unpause.WaitWithDefaultTimeout() + Expect(unpause.ExitCode()).To(Equal(0)) + + running := podmanTest.Podman([]string{"ps", "-q"}) + running.WaitWithDefaultTimeout() + Expect(running.ExitCode()).To(Equal(0)) + Expect(len(running.OutputToStringArray())).To(Equal(3)) + }) + }) diff --git a/test/e2e/pod_create_test.go b/test/e2e/pod_create_test.go index 0ce1e22a8..5abf9613b 100644 --- a/test/e2e/pod_create_test.go +++ b/test/e2e/pod_create_test.go @@ -4,6 +4,7 @@ import ( "fmt" "os" + . "github.com/containers/libpod/test/utils" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" ) @@ -12,7 +13,7 @@ var _ = Describe("Podman pod create", func() { var ( tempdir string err error - podmanTest PodmanTest + podmanTest *PodmanTestIntegration ) BeforeEach(func() { @@ -20,7 +21,7 @@ var _ = Describe("Podman pod create", func() { if err != nil { os.Exit(1) } - podmanTest = PodmanCreate(tempdir) + podmanTest = PodmanTestCreate(tempdir) podmanTest.RestoreAllArtifacts() }) @@ -79,4 +80,43 @@ var _ = Describe("Podman pod create", func() { check.WaitWithDefaultTimeout() Expect(len(check.OutputToStringArray())).To(Equal(0)) }) + + It("podman create pod without network portbindings", func() { + name := "test" + session := podmanTest.Podman([]string{"pod", "create", "--name", name}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + pod := session.OutputToString() + + webserver := podmanTest.Podman([]string{"run", "--pod", pod, "-dt", nginx}) + webserver.WaitWithDefaultTimeout() + Expect(webserver.ExitCode()).To(Equal(0)) + + check := SystemExec("nc", []string{"-z", "localhost", "80"}) + check.WaitWithDefaultTimeout() + Expect(check.ExitCode()).To(Equal(1)) + }) + + It("podman create pod with network portbindings", func() { + name := "test" + session := podmanTest.Podman([]string{"pod", "create", "--name", name, "-p", "80:80"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + pod := session.OutputToString() + + webserver := podmanTest.Podman([]string{"run", "--pod", pod, "-dt", nginx}) + webserver.WaitWithDefaultTimeout() + Expect(webserver.ExitCode()).To(Equal(0)) + + check := SystemExec("nc", []string{"-z", "localhost", "80"}) + check.WaitWithDefaultTimeout() + Expect(check.ExitCode()).To(Equal(0)) + }) + + It("podman create pod with no infra but portbindings should fail", func() { + name := "test" + session := podmanTest.Podman([]string{"pod", "create", "--infra=false", "--name", name, "-p", "80:80"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(125)) + }) }) diff --git a/test/e2e/pod_infra_container_test.go b/test/e2e/pod_infra_container_test.go index f1e2375ce..8c7c09c97 100644 --- a/test/e2e/pod_infra_container_test.go +++ b/test/e2e/pod_infra_container_test.go @@ -5,6 +5,7 @@ import ( "os" "strconv" + . "github.com/containers/libpod/test/utils" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" ) @@ -13,7 +14,7 @@ var _ = Describe("Podman pod create", func() { var ( tempdir string err error - podmanTest PodmanTest + podmanTest *PodmanTestIntegration ) BeforeEach(func() { @@ -21,7 +22,7 @@ var _ = Describe("Podman pod create", func() { if err != nil { os.Exit(1) } - podmanTest = PodmanCreate(tempdir) + podmanTest = PodmanTestCreate(tempdir) podmanTest.RestoreAllArtifacts() podmanTest.RestoreArtifact(infra) }) diff --git a/test/e2e/pod_inspect_test.go b/test/e2e/pod_inspect_test.go index 667e59f38..51e95f788 100644 --- a/test/e2e/pod_inspect_test.go +++ b/test/e2e/pod_inspect_test.go @@ -4,6 +4,7 @@ import ( "fmt" "os" + . "github.com/containers/libpod/test/utils" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" ) @@ -12,7 +13,7 @@ var _ = Describe("Podman pod inspect", func() { var ( tempdir string err error - podmanTest PodmanTest + podmanTest *PodmanTestIntegration ) BeforeEach(func() { @@ -20,7 +21,7 @@ var _ = Describe("Podman pod inspect", func() { if err != nil { os.Exit(1) } - podmanTest = PodmanCreate(tempdir) + podmanTest = PodmanTestCreate(tempdir) podmanTest.RestoreAllArtifacts() }) diff --git a/test/e2e/pod_kill_test.go b/test/e2e/pod_kill_test.go index b29fe1e17..d9cec2cad 100644 --- a/test/e2e/pod_kill_test.go +++ b/test/e2e/pod_kill_test.go @@ -4,6 +4,7 @@ import ( "fmt" "os" + . "github.com/containers/libpod/test/utils" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" ) @@ -12,7 +13,7 @@ var _ = Describe("Podman pod kill", func() { var ( tempdir string err error - podmanTest PodmanTest + podmanTest *PodmanTestIntegration ) BeforeEach(func() { @@ -20,7 +21,7 @@ var _ = Describe("Podman pod kill", func() { if err != nil { os.Exit(1) } - podmanTest = PodmanCreate(tempdir) + podmanTest = PodmanTestCreate(tempdir) podmanTest.RestoreAllArtifacts() }) diff --git a/test/e2e/pod_pause_test.go b/test/e2e/pod_pause_test.go index 384cbfcb7..8f766d3db 100644 --- a/test/e2e/pod_pause_test.go +++ b/test/e2e/pod_pause_test.go @@ -4,6 +4,7 @@ import ( "fmt" "os" + . "github.com/containers/libpod/test/utils" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" ) @@ -12,7 +13,7 @@ var _ = Describe("Podman pod pause", func() { var ( tempdir string err error - podmanTest PodmanTest + podmanTest *PodmanTestIntegration ) pausedState := "Paused" @@ -22,7 +23,7 @@ var _ = Describe("Podman pod pause", func() { if err != nil { os.Exit(1) } - podmanTest = PodmanCreate(tempdir) + podmanTest = PodmanTestCreate(tempdir) podmanTest.RestoreAllArtifacts() }) diff --git a/test/e2e/pod_pod_namespaces.go b/test/e2e/pod_pod_namespaces.go index 3e84005c3..b1d5abb1c 100644 --- a/test/e2e/pod_pod_namespaces.go +++ b/test/e2e/pod_pod_namespaces.go @@ -4,6 +4,7 @@ import ( "fmt" "os" + . "github.com/containers/libpod/test/utils" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" ) @@ -12,7 +13,7 @@ var _ = Describe("Podman pod create", func() { var ( tempdir string err error - podmanTest PodmanTest + podmanTest *PodmanTestIntegration ) BeforeEach(func() { @@ -20,7 +21,7 @@ var _ = Describe("Podman pod create", func() { if err != nil { os.Exit(1) } - podmanTest = PodmanCreate(tempdir) + podmanTest = PodmanTestCreate(tempdir) podmanTest.RestoreAllArtifacts() podmanTest.RestoreArtifact(infra) }) diff --git a/test/e2e/pod_ps_test.go b/test/e2e/pod_ps_test.go index b48cb9578..9e816bcfa 100644 --- a/test/e2e/pod_ps_test.go +++ b/test/e2e/pod_ps_test.go @@ -5,6 +5,7 @@ import ( "os" "sort" + . "github.com/containers/libpod/test/utils" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" ) @@ -13,7 +14,7 @@ var _ = Describe("Podman ps", func() { var ( tempdir string err error - podmanTest PodmanTest + podmanTest *PodmanTestIntegration ) BeforeEach(func() { @@ -21,7 +22,7 @@ var _ = Describe("Podman ps", func() { if err != nil { os.Exit(1) } - podmanTest = PodmanCreate(tempdir) + podmanTest = PodmanTestCreate(tempdir) podmanTest.RestoreAllArtifacts() }) diff --git a/test/e2e/pod_restart_test.go b/test/e2e/pod_restart_test.go index e486f8791..d0964e8de 100644 --- a/test/e2e/pod_restart_test.go +++ b/test/e2e/pod_restart_test.go @@ -4,6 +4,7 @@ import ( "fmt" "os" + . "github.com/containers/libpod/test/utils" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" ) @@ -12,7 +13,7 @@ var _ = Describe("Podman pod restart", func() { var ( tempdir string err error - podmanTest PodmanTest + podmanTest *PodmanTestIntegration ) BeforeEach(func() { @@ -20,7 +21,7 @@ var _ = Describe("Podman pod restart", func() { if err != nil { os.Exit(1) } - podmanTest = PodmanCreate(tempdir) + podmanTest = PodmanTestCreate(tempdir) podmanTest.RestoreAllArtifacts() }) diff --git a/test/e2e/pod_rm_test.go b/test/e2e/pod_rm_test.go index 09002e954..48767b33f 100644 --- a/test/e2e/pod_rm_test.go +++ b/test/e2e/pod_rm_test.go @@ -4,6 +4,7 @@ import ( "fmt" "os" + . "github.com/containers/libpod/test/utils" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" ) @@ -12,7 +13,7 @@ var _ = Describe("Podman pod rm", func() { var ( tempdir string err error - podmanTest PodmanTest + podmanTest *PodmanTestIntegration ) BeforeEach(func() { @@ -20,7 +21,7 @@ var _ = Describe("Podman pod rm", func() { if err != nil { os.Exit(1) } - podmanTest = PodmanCreate(tempdir) + podmanTest = PodmanTestCreate(tempdir) podmanTest.RestoreAllArtifacts() }) diff --git a/test/e2e/pod_start_test.go b/test/e2e/pod_start_test.go index 9d2ea9b26..346346425 100644 --- a/test/e2e/pod_start_test.go +++ b/test/e2e/pod_start_test.go @@ -4,6 +4,7 @@ import ( "fmt" "os" + . "github.com/containers/libpod/test/utils" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" ) @@ -12,7 +13,7 @@ var _ = Describe("Podman pod start", func() { var ( tempdir string err error - podmanTest PodmanTest + podmanTest *PodmanTestIntegration ) BeforeEach(func() { @@ -20,7 +21,7 @@ var _ = Describe("Podman pod start", func() { if err != nil { os.Exit(1) } - podmanTest = PodmanCreate(tempdir) + podmanTest = PodmanTestCreate(tempdir) podmanTest.RestoreAllArtifacts() }) diff --git a/test/e2e/pod_stats_test.go b/test/e2e/pod_stats_test.go index f9c8e06c4..d7b9a8f48 100644 --- a/test/e2e/pod_stats_test.go +++ b/test/e2e/pod_stats_test.go @@ -4,6 +4,7 @@ import ( "fmt" "os" + . "github.com/containers/libpod/test/utils" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" ) @@ -12,7 +13,7 @@ var _ = Describe("Podman pod stats", func() { var ( tempdir string err error - podmanTest PodmanTest + podmanTest *PodmanTestIntegration ) BeforeEach(func() { @@ -20,7 +21,7 @@ var _ = Describe("Podman pod stats", func() { if err != nil { os.Exit(1) } - podmanTest = PodmanCreate(tempdir) + podmanTest = PodmanTestCreate(tempdir) podmanTest.RestoreAllArtifacts() }) diff --git a/test/e2e/pod_stop_test.go b/test/e2e/pod_stop_test.go index 32f8559ad..6c5319a3d 100644 --- a/test/e2e/pod_stop_test.go +++ b/test/e2e/pod_stop_test.go @@ -4,6 +4,7 @@ import ( "fmt" "os" + . "github.com/containers/libpod/test/utils" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" ) @@ -12,7 +13,7 @@ var _ = Describe("Podman pod stop", func() { var ( tempdir string err error - podmanTest PodmanTest + podmanTest *PodmanTestIntegration ) BeforeEach(func() { @@ -20,7 +21,7 @@ var _ = Describe("Podman pod stop", func() { if err != nil { os.Exit(1) } - podmanTest = PodmanCreate(tempdir) + podmanTest = PodmanTestCreate(tempdir) podmanTest.RestoreAllArtifacts() }) diff --git a/test/e2e/pod_top_test.go b/test/e2e/pod_top_test.go index f72456307..3dc80ddfb 100644 --- a/test/e2e/pod_top_test.go +++ b/test/e2e/pod_top_test.go @@ -4,6 +4,7 @@ import ( "fmt" "os" + . "github.com/containers/libpod/test/utils" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" ) @@ -12,7 +13,7 @@ var _ = Describe("Podman top", func() { var ( tempdir string err error - podmanTest PodmanTest + podmanTest *PodmanTestIntegration ) BeforeEach(func() { @@ -20,7 +21,7 @@ var _ = Describe("Podman top", func() { if err != nil { os.Exit(1) } - podmanTest = PodmanCreate(tempdir) + podmanTest = PodmanTestCreate(tempdir) podmanTest.RestoreAllArtifacts() }) diff --git a/test/e2e/port_test.go b/test/e2e/port_test.go index ed15b54ac..09f3ab53a 100644 --- a/test/e2e/port_test.go +++ b/test/e2e/port_test.go @@ -5,6 +5,7 @@ import ( "os" "strings" + . "github.com/containers/libpod/test/utils" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" ) @@ -13,7 +14,7 @@ var _ = Describe("Podman port", func() { var ( tempdir string err error - podmanTest PodmanTest + podmanTest *PodmanTestIntegration ) BeforeEach(func() { @@ -21,7 +22,7 @@ var _ = Describe("Podman port", func() { if err != nil { os.Exit(1) } - podmanTest = PodmanCreate(tempdir) + podmanTest = PodmanTestCreate(tempdir) podmanTest.RestoreAllArtifacts() }) diff --git a/test/e2e/ps_test.go b/test/e2e/ps_test.go index a873b57bb..9caa6e7f1 100644 --- a/test/e2e/ps_test.go +++ b/test/e2e/ps_test.go @@ -6,6 +6,7 @@ import ( "regexp" "sort" + . "github.com/containers/libpod/test/utils" "github.com/docker/go-units" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" @@ -15,7 +16,7 @@ var _ = Describe("Podman ps", func() { var ( tempdir string err error - podmanTest PodmanTest + podmanTest *PodmanTestIntegration ) BeforeEach(func() { @@ -23,7 +24,7 @@ var _ = Describe("Podman ps", func() { if err != nil { os.Exit(1) } - podmanTest = PodmanCreate(tempdir) + podmanTest = PodmanTestCreate(tempdir) podmanTest.RestoreAllArtifacts() }) diff --git a/test/e2e/pull_test.go b/test/e2e/pull_test.go index 821476f39..ad8742984 100644 --- a/test/e2e/pull_test.go +++ b/test/e2e/pull_test.go @@ -4,6 +4,7 @@ import ( "os" "fmt" + . "github.com/containers/libpod/test/utils" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" "strings" @@ -13,7 +14,7 @@ var _ = Describe("Podman pull", func() { var ( tempdir string err error - podmanTest PodmanTest + podmanTest *PodmanTestIntegration ) BeforeEach(func() { @@ -21,7 +22,7 @@ var _ = Describe("Podman pull", func() { if err != nil { os.Exit(1) } - podmanTest = PodmanCreate(tempdir) + podmanTest = PodmanTestCreate(tempdir) podmanTest.RestoreAllArtifacts() }) @@ -63,11 +64,11 @@ var _ = Describe("Podman pull", func() { }) It("podman pull from alternate registry without tag", func() { - session := podmanTest.Podman([]string{"pull", "quay.io/baude/alpine_nginx"}) + session := podmanTest.Podman([]string{"pull", "quay.io/libpod/alpine_nginx"}) session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) - session = podmanTest.Podman([]string{"rmi", "quay.io/baude/alpine_nginx"}) + session = podmanTest.Podman([]string{"rmi", "quay.io/libpod/alpine_nginx"}) session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) }) @@ -101,7 +102,7 @@ var _ = Describe("Podman pull", func() { session = podmanTest.Podman([]string{"rmi", "alpine"}) session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) - clean := podmanTest.SystemExec("rm", []string{"/tmp/alp.tar"}) + clean := SystemExec("rm", []string{"/tmp/alp.tar"}) clean.WaitWithDefaultTimeout() Expect(clean.ExitCode()).To(Equal(0)) }) @@ -119,12 +120,12 @@ var _ = Describe("Podman pull", func() { session = podmanTest.Podman([]string{"rmi", "alpine"}) session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) - clean := podmanTest.SystemExec("rm", []string{"/tmp/oci-alp.tar"}) + clean := SystemExec("rm", []string{"/tmp/oci-alp.tar"}) clean.WaitWithDefaultTimeout() }) It("podman pull from local directory", func() { - setup := podmanTest.SystemExec("mkdir", []string{"-p", "/tmp/podmantestdir"}) + setup := SystemExec("mkdir", []string{"-p", "/tmp/podmantestdir"}) setup.WaitWithDefaultTimeout() session := podmanTest.Podman([]string{"push", "alpine", "dir:/tmp/podmantestdir"}) session.WaitWithDefaultTimeout() @@ -139,7 +140,7 @@ var _ = Describe("Podman pull", func() { session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) - clean := podmanTest.SystemExec("rm", []string{"-fr", "/tmp/podmantestdir"}) + clean := SystemExec("rm", []string{"-fr", "/tmp/podmantestdir"}) clean.WaitWithDefaultTimeout() }) diff --git a/test/e2e/push_test.go b/test/e2e/push_test.go index 3ee150551..3447cd57e 100644 --- a/test/e2e/push_test.go +++ b/test/e2e/push_test.go @@ -6,6 +6,7 @@ import ( "path/filepath" "strings" + . "github.com/containers/libpod/test/utils" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" ) @@ -14,7 +15,7 @@ var _ = Describe("Podman push", func() { var ( tempdir string err error - podmanTest PodmanTest + podmanTest *PodmanTestIntegration ) BeforeEach(func() { @@ -22,7 +23,7 @@ var _ = Describe("Podman push", func() { if err != nil { os.Exit(1) } - podmanTest = PodmanCreate(tempdir) + podmanTest = PodmanTestCreate(tempdir) podmanTest.RestoreAllArtifacts() }) @@ -52,18 +53,21 @@ var _ = Describe("Podman push", func() { session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) - clean := podmanTest.SystemExec("rm", []string{"-fr", "/tmp/busybox"}) + clean := SystemExec("rm", []string{"-fr", "/tmp/busybox"}) clean.WaitWithDefaultTimeout() Expect(clean.ExitCode()).To(Equal(0)) }) It("podman push to local registry", func() { + if podmanTest.Host.Arch == "ppc64le" { + Skip("No registry image for ppc64le") + } podmanTest.RestoreArtifact(registry) session := podmanTest.Podman([]string{"run", "-d", "--name", "registry", "-p", "5000:5000", registry, "/entrypoint.sh", "/etc/docker/registry/config.yml"}) session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) - if !WaitContainerReady(&podmanTest, "registry", "listening on", 20, 1) { + if !WaitContainerReady(podmanTest, "registry", "listening on", 20, 1) { Skip("Can not start docker registry.") } @@ -73,23 +77,26 @@ var _ = Describe("Podman push", func() { }) It("podman push to local registry with authorization", func() { + if podmanTest.Host.Arch == "ppc64le" { + Skip("No registry image for ppc64le") + } authPath := filepath.Join(podmanTest.TempDir, "auth") os.Mkdir(authPath, os.ModePerm) os.MkdirAll("/etc/containers/certs.d/localhost:5000", os.ModePerm) - debug := podmanTest.SystemExec("ls", []string{"-l", podmanTest.TempDir}) + debug := SystemExec("ls", []string{"-l", podmanTest.TempDir}) debug.WaitWithDefaultTimeout() cwd, _ := os.Getwd() certPath := filepath.Join(cwd, "../", "certs") if IsCommandAvailable("getenforce") { - ge := podmanTest.SystemExec("getenforce", []string{}) + ge := SystemExec("getenforce", []string{}) ge.WaitWithDefaultTimeout() if ge.OutputToString() == "Enforcing" { - se := podmanTest.SystemExec("setenforce", []string{"0"}) + se := SystemExec("setenforce", []string{"0"}) se.WaitWithDefaultTimeout() - defer podmanTest.SystemExec("setenforce", []string{"1"}) + defer SystemExec("setenforce", []string{"1"}) } } podmanTest.RestoreArtifact(registry) @@ -102,7 +109,7 @@ var _ = Describe("Podman push", func() { f.WriteString(session.OutputToString()) f.Sync() - debug = podmanTest.SystemExec("cat", []string{filepath.Join(authPath, "htpasswd")}) + debug = SystemExec("cat", []string{filepath.Join(authPath, "htpasswd")}) debug.WaitWithDefaultTimeout() session = podmanTest.Podman([]string{"run", "-d", "-p", "5000:5000", "--name", "registry", "-v", @@ -113,7 +120,7 @@ var _ = Describe("Podman push", func() { session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) - if !WaitContainerReady(&podmanTest, "registry", "listening on", 20, 1) { + if !WaitContainerReady(podmanTest, "registry", "listening on", 20, 1) { Skip("Can not start docker registry.") } @@ -128,7 +135,7 @@ var _ = Describe("Podman push", func() { push.WaitWithDefaultTimeout() Expect(push.ExitCode()).To(Equal(0)) - setup := podmanTest.SystemExec("cp", []string{filepath.Join(certPath, "domain.crt"), "/etc/containers/certs.d/localhost:5000/ca.crt"}) + setup := SystemExec("cp", []string{filepath.Join(certPath, "domain.crt"), "/etc/containers/certs.d/localhost:5000/ca.crt"}) setup.WaitWithDefaultTimeout() defer os.RemoveAll("/etc/containers/certs.d/localhost:5000") @@ -149,20 +156,20 @@ var _ = Describe("Podman push", func() { session := podmanTest.Podman([]string{"push", ALPINE, "docker-archive:/tmp/alp:latest"}) session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) - clean := podmanTest.SystemExec("rm", []string{"/tmp/alp"}) + clean := SystemExec("rm", []string{"/tmp/alp"}) clean.WaitWithDefaultTimeout() Expect(clean.ExitCode()).To(Equal(0)) }) It("podman push to docker daemon", func() { - setup := podmanTest.SystemExec("bash", []string{"-c", "systemctl status docker 2>&1"}) + setup := SystemExec("bash", []string{"-c", "systemctl status docker 2>&1"}) setup.WaitWithDefaultTimeout() if setup.LineInOutputContains("Active: inactive") { - setup = podmanTest.SystemExec("systemctl", []string{"start", "docker"}) + setup = SystemExec("systemctl", []string{"start", "docker"}) setup.WaitWithDefaultTimeout() - defer podmanTest.SystemExec("systemctl", []string{"stop", "docker"}) + defer SystemExec("systemctl", []string{"stop", "docker"}) } else if setup.ExitCode() != 0 { Skip("Docker is not available") } @@ -171,12 +178,12 @@ var _ = Describe("Podman push", func() { session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) - check := podmanTest.SystemExec("docker", []string{"images", "--format", "{{.Repository}}:{{.Tag}}"}) + check := SystemExec("docker", []string{"images", "--format", "{{.Repository}}:{{.Tag}}"}) check.WaitWithDefaultTimeout() Expect(check.ExitCode()).To(Equal(0)) Expect(check.OutputToString()).To(ContainSubstring("alpine:podmantest")) - clean := podmanTest.SystemExec("docker", []string{"rmi", "alpine:podmantest"}) + clean := SystemExec("docker", []string{"rmi", "alpine:podmantest"}) clean.WaitWithDefaultTimeout() Expect(clean.ExitCode()).To(Equal(0)) }) @@ -185,7 +192,7 @@ var _ = Describe("Podman push", func() { session := podmanTest.Podman([]string{"push", ALPINE, "oci-archive:/tmp/alp.tar:latest"}) session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) - clean := podmanTest.SystemExec("rm", []string{"/tmp/alp.tar"}) + clean := SystemExec("rm", []string{"/tmp/alp.tar"}) clean.WaitWithDefaultTimeout() Expect(clean.ExitCode()).To(Equal(0)) }) @@ -198,14 +205,14 @@ var _ = Describe("Podman push", func() { ostreePath := filepath.Join(podmanTest.TempDir, "ostree/repo") os.MkdirAll(ostreePath, os.ModePerm) - setup := podmanTest.SystemExec("ostree", []string{strings.Join([]string{"--repo=", ostreePath}, ""), "init"}) + setup := SystemExec("ostree", []string{strings.Join([]string{"--repo=", ostreePath}, ""), "init"}) setup.WaitWithDefaultTimeout() session := podmanTest.Podman([]string{"push", ALPINE, strings.Join([]string{"ostree:alp@", ostreePath}, "")}) session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) - clean := podmanTest.SystemExec("rm", []string{"-rf", ostreePath}) + clean := SystemExec("rm", []string{"-rf", ostreePath}) clean.WaitWithDefaultTimeout() Expect(clean.ExitCode()).To(Equal(0)) }) @@ -214,7 +221,7 @@ var _ = Describe("Podman push", func() { session := podmanTest.Podman([]string{"push", ALPINE, "docker-archive:/tmp/alp"}) session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) - clean := podmanTest.SystemExec("rm", []string{"/tmp/alp"}) + clean := SystemExec("rm", []string{"/tmp/alp"}) clean.WaitWithDefaultTimeout() Expect(clean.ExitCode()).To(Equal(0)) }) @@ -223,7 +230,7 @@ var _ = Describe("Podman push", func() { session := podmanTest.Podman([]string{"push", ALPINE, "oci-archive:/tmp/alp-oci"}) session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) - clean := podmanTest.SystemExec("rm", []string{"/tmp/alp-oci"}) + clean := SystemExec("rm", []string{"/tmp/alp-oci"}) clean.WaitWithDefaultTimeout() Expect(clean.ExitCode()).To(Equal(0)) }) diff --git a/test/e2e/refresh_test.go b/test/e2e/refresh_test.go index c4a65aa47..bf8fff105 100644 --- a/test/e2e/refresh_test.go +++ b/test/e2e/refresh_test.go @@ -5,6 +5,7 @@ import ( "os" "time" + . "github.com/containers/libpod/test/utils" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" ) @@ -13,7 +14,7 @@ var _ = Describe("Podman refresh", func() { var ( tmpdir string err error - podmanTest PodmanTest + podmanTest *PodmanTestIntegration ) BeforeEach(func() { @@ -21,7 +22,7 @@ var _ = Describe("Podman refresh", func() { if err != nil { os.Exit(1) } - podmanTest = PodmanCreate(tmpdir) + podmanTest = PodmanTestCreate(tmpdir) podmanTest.RestoreAllArtifacts() }) @@ -43,13 +44,13 @@ var _ = Describe("Podman refresh", func() { createSession.WaitWithDefaultTimeout() Expect(createSession.ExitCode()).To(Equal(0)) Expect(podmanTest.NumberOfContainers()).To(Equal(1)) - Expect(podmanTest.NumberOfRunningContainers()).To(Equal(0)) + Expect(podmanTest.NumberOfContainersRunning()).To(Equal(0)) refreshSession := podmanTest.Podman([]string{"container", "refresh"}) refreshSession.WaitWithDefaultTimeout() Expect(refreshSession.ExitCode()).To(Equal(0)) Expect(podmanTest.NumberOfContainers()).To(Equal(1)) - Expect(podmanTest.NumberOfRunningContainers()).To(Equal(0)) + Expect(podmanTest.NumberOfContainersRunning()).To(Equal(0)) }) Specify("Refresh with running container restarts container", func() { @@ -57,7 +58,7 @@ var _ = Describe("Podman refresh", func() { createSession.WaitWithDefaultTimeout() Expect(createSession.ExitCode()).To(Equal(0)) Expect(podmanTest.NumberOfContainers()).To(Equal(1)) - Expect(podmanTest.NumberOfRunningContainers()).To(Equal(1)) + Expect(podmanTest.NumberOfContainersRunning()).To(Equal(1)) // HACK: ensure container starts before we move on time.Sleep(1 * time.Second) @@ -66,6 +67,6 @@ var _ = Describe("Podman refresh", func() { refreshSession.WaitWithDefaultTimeout() Expect(refreshSession.ExitCode()).To(Equal(0)) Expect(podmanTest.NumberOfContainers()).To(Equal(1)) - Expect(podmanTest.NumberOfRunningContainers()).To(Equal(1)) + Expect(podmanTest.NumberOfContainersRunning()).To(Equal(1)) }) }) diff --git a/test/e2e/restart_test.go b/test/e2e/restart_test.go index d2fc35485..30801c272 100644 --- a/test/e2e/restart_test.go +++ b/test/e2e/restart_test.go @@ -5,6 +5,7 @@ import ( "os" "time" + . "github.com/containers/libpod/test/utils" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" ) @@ -13,7 +14,7 @@ var _ = Describe("Podman restart", func() { var ( tempdir string err error - podmanTest PodmanTest + podmanTest *PodmanTestIntegration ) BeforeEach(func() { @@ -21,7 +22,7 @@ var _ = Describe("Podman restart", func() { if err != nil { os.Exit(1) } - podmanTest = PodmanCreate(tempdir) + podmanTest = PodmanTestCreate(tempdir) podmanTest.RestoreAllArtifacts() }) @@ -74,7 +75,7 @@ var _ = Describe("Podman restart", func() { It("Podman restart running container", func() { _ = podmanTest.RunTopContainer("test1") - ok := WaitForContainer(&podmanTest) + ok := WaitForContainer(podmanTest) Expect(ok).To(BeTrue()) startTime := podmanTest.Podman([]string{"inspect", "--format='{{.State.StartedAt}}'", "test1"}) startTime.WaitWithDefaultTimeout() @@ -136,4 +137,44 @@ var _ = Describe("Podman restart", func() { Expect(timeSince < 10*time.Second).To(BeTrue()) Expect(timeSince > 2*time.Second).To(BeTrue()) }) + + It("Podman restart --all", func() { + _, exitCode, _ := podmanTest.RunLsContainer("test1") + Expect(exitCode).To(Equal(0)) + + test2 := podmanTest.RunTopContainer("test2") + test2.WaitWithDefaultTimeout() + Expect(test2.ExitCode()).To(Equal(0)) + + startTime := podmanTest.Podman([]string{"inspect", "--format='{{.State.StartedAt}}'", "test1", "test2"}) + startTime.WaitWithDefaultTimeout() + + session := podmanTest.Podman([]string{"restart", "-all"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + restartTime := podmanTest.Podman([]string{"inspect", "--format='{{.State.StartedAt}}'", "test1", "test2"}) + restartTime.WaitWithDefaultTimeout() + Expect(restartTime.OutputToStringArray()[0]).To(Not(Equal(startTime.OutputToStringArray()[0]))) + Expect(restartTime.OutputToStringArray()[1]).To(Not(Equal(startTime.OutputToStringArray()[1]))) + }) + + It("Podman restart --all --running", func() { + _, exitCode, _ := podmanTest.RunLsContainer("test1") + Expect(exitCode).To(Equal(0)) + + test2 := podmanTest.RunTopContainer("test2") + test2.WaitWithDefaultTimeout() + Expect(test2.ExitCode()).To(Equal(0)) + + startTime := podmanTest.Podman([]string{"inspect", "--format='{{.State.StartedAt}}'", "test1", "test2"}) + startTime.WaitWithDefaultTimeout() + + session := podmanTest.Podman([]string{"restart", "-a", "--running"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + restartTime := podmanTest.Podman([]string{"inspect", "--format='{{.State.StartedAt}}'", "test1", "test2"}) + restartTime.WaitWithDefaultTimeout() + Expect(restartTime.OutputToStringArray()[0]).To(Equal(startTime.OutputToStringArray()[0])) + Expect(restartTime.OutputToStringArray()[1]).To(Not(Equal(startTime.OutputToStringArray()[1]))) + }) }) diff --git a/test/e2e/rm_test.go b/test/e2e/rm_test.go index cbc03a078..c6a2b61ee 100644 --- a/test/e2e/rm_test.go +++ b/test/e2e/rm_test.go @@ -4,6 +4,7 @@ import ( "fmt" "os" + . "github.com/containers/libpod/test/utils" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" ) @@ -12,7 +13,7 @@ var _ = Describe("Podman rm", func() { var ( tempdir string err error - podmanTest PodmanTest + podmanTest *PodmanTestIntegration ) BeforeEach(func() { @@ -20,7 +21,7 @@ var _ = Describe("Podman rm", func() { if err != nil { os.Exit(1) } - podmanTest = PodmanCreate(tempdir) + podmanTest = PodmanTestCreate(tempdir) podmanTest.RestoreAllArtifacts() }) diff --git a/test/e2e/rmi_test.go b/test/e2e/rmi_test.go index 8224cd345..22bfbbe8c 100644 --- a/test/e2e/rmi_test.go +++ b/test/e2e/rmi_test.go @@ -4,6 +4,7 @@ import ( "fmt" "os" + . "github.com/containers/libpod/test/utils" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" ) @@ -12,9 +13,7 @@ var _ = Describe("Podman rmi", func() { var ( tempdir string err error - podmanTest PodmanTest - image1 = "docker.io/library/alpine:latest" - image3 = "docker.io/library/busybox:glibc" + podmanTest *PodmanTestIntegration ) BeforeEach(func() { @@ -22,7 +21,7 @@ var _ = Describe("Podman rmi", func() { if err != nil { os.Exit(1) } - podmanTest = PodmanCreate(tempdir) + podmanTest = PodmanTestCreate(tempdir) podmanTest.RestoreAllArtifacts() }) @@ -42,7 +41,7 @@ var _ = Describe("Podman rmi", func() { }) It("podman rmi with fq name", func() { - session := podmanTest.Podman([]string{"rmi", image1}) + session := podmanTest.Podman([]string{"rmi", ALPINE}) session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) @@ -56,15 +55,18 @@ var _ = Describe("Podman rmi", func() { }) It("podman rmi all images", func() { - podmanTest.PullImages([]string{image3}) + podmanTest.PullImages([]string{nginx}) session := podmanTest.Podman([]string{"rmi", "-a"}) session.WaitWithDefaultTimeout() + images := podmanTest.Podman([]string{"images"}) + images.WaitWithDefaultTimeout() + fmt.Println(images.OutputToStringArray()) Expect(session.ExitCode()).To(Equal(0)) }) It("podman rmi all images forcibly with short options", func() { - podmanTest.PullImages([]string{image3}) + podmanTest.PullImages([]string{nginx}) session := podmanTest.Podman([]string{"rmi", "-fa"}) session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) @@ -248,4 +250,25 @@ var _ = Describe("Podman rmi", func() { session2.WaitWithDefaultTimeout() Expect(session2.ExitCode()).To(Equal(0)) }) + + It("podman rmi -a with parent|child images", func() { + dockerfile := `FROM docker.io/library/alpine:latest AS base +RUN touch /1 +ENV LOCAL=/1 +RUN find $LOCAL +FROM base +RUN find $LOCAL + +` + podmanTest.BuildImage(dockerfile, "test", "true") + session := podmanTest.Podman([]string{"rmi", "-a"}) + session.WaitWithDefaultTimeout() + fmt.Println(session.OutputToString()) + Expect(session.ExitCode()).To(Equal(0)) + + images := podmanTest.Podman([]string{"images", "--all"}) + images.WaitWithDefaultTimeout() + Expect(images.ExitCode()).To(Equal(0)) + Expect(len(images.OutputToStringArray())).To(Equal(0)) + }) }) diff --git a/test/e2e/rootless_test.go b/test/e2e/rootless_test.go index bd782a5fc..b15e6731b 100644 --- a/test/e2e/rootless_test.go +++ b/test/e2e/rootless_test.go @@ -9,6 +9,7 @@ import ( "runtime" "syscall" + . "github.com/containers/libpod/test/utils" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" ) @@ -30,7 +31,7 @@ var _ = Describe("Podman rootless", func() { var ( tempdir string err error - podmanTest PodmanTest + podmanTest *PodmanTestIntegration ) BeforeEach(func() { @@ -38,8 +39,9 @@ var _ = Describe("Podman rootless", func() { if err != nil { os.Exit(1) } - podmanTest = PodmanCreate(tempdir) + podmanTest = PodmanTestCreate(tempdir) podmanTest.CgroupManager = "cgroupfs" + podmanTest.StorageOptions = ROOTLESS_STORAGE_OPTIONS podmanTest.RestoreAllArtifacts() }) @@ -54,6 +56,7 @@ var _ = Describe("Podman rootless", func() { commands := []string{"help", "version"} for _, v := range commands { env := os.Environ() + env = append(env, "USER=foo") cmd := podmanTest.PodmanAsUser([]string{v}, 1000, 1000, env) cmd.WaitWithDefaultTimeout() Expect(cmd.ExitCode()).To(Equal(0)) @@ -67,7 +70,7 @@ var _ = Describe("Podman rootless", func() { return os.Lchown(p, 1000, 1000) } - type rootlessCB func(test PodmanTest, xdgRuntimeDir string, home string, mountPath string) + type rootlessCB func(test *PodmanTestIntegration, xdgRuntimeDir string, home string, mountPath string) runInRootlessContext := func(cb rootlessCB) { // Check if we can create an user namespace @@ -90,8 +93,9 @@ var _ = Describe("Podman rootless", func() { tempdir, err := CreateTempDirInTempDir() Expect(err).To(BeNil()) - rootlessTest := PodmanCreate(tempdir) + rootlessTest := PodmanTestCreate(tempdir) rootlessTest.CgroupManager = "cgroupfs" + rootlessTest.StorageOptions = ROOTLESS_STORAGE_OPTIONS err = filepath.Walk(tempdir, chownFunc) Expect(err).To(BeNil()) @@ -114,11 +118,12 @@ var _ = Describe("Podman rootless", func() { } It("podman rootless pod", func() { - f := func(rootlessTest PodmanTest, xdgRuntimeDir string, home string, mountPath string) { + f := func(rootlessTest *PodmanTestIntegration, xdgRuntimeDir string, home string, mountPath string) { env := os.Environ() env = append(env, fmt.Sprintf("XDG_RUNTIME_DIR=%s", xdgRuntimeDir)) env = append(env, fmt.Sprintf("HOME=%s", home)) env = append(env, "PODMAN_ALLOW_SINGLE_ID_MAPPING_IN_USERNS=1") + env = append(env, "USER=foo") cmd := rootlessTest.PodmanAsUser([]string{"pod", "create", "--infra=false"}, 1000, 1000, env) cmd.WaitWithDefaultTimeout() @@ -149,19 +154,21 @@ var _ = Describe("Podman rootless", func() { env := os.Environ() env = append(env, fmt.Sprintf("XDG_RUNTIME_DIR=%s", xdgRuntimeDir)) env = append(env, fmt.Sprintf("HOME=%s", home)) + env = append(env, "USER=foo") cmd := podmanTest.PodmanAsUser([]string{"search", "docker.io/busybox"}, 1000, 1000, env) cmd.WaitWithDefaultTimeout() Expect(cmd.ExitCode()).To(Equal(0)) }) runRootlessHelper := func(args []string) { - f := func(rootlessTest PodmanTest, xdgRuntimeDir string, home string, mountPath string) { + f := func(rootlessTest *PodmanTestIntegration, xdgRuntimeDir string, home string, mountPath string) { runtime.LockOSThread() defer runtime.UnlockOSThread() env := os.Environ() env = append(env, fmt.Sprintf("XDG_RUNTIME_DIR=%s", xdgRuntimeDir)) env = append(env, fmt.Sprintf("HOME=%s", home)) env = append(env, "PODMAN_ALLOW_SINGLE_ID_MAPPING_IN_USERNS=1") + env = append(env, "USER=foo") allArgs := append([]string{"run"}, args...) allArgs = append(allArgs, "--rootfs", mountPath, "echo", "hello") @@ -202,6 +209,10 @@ var _ = Describe("Podman rootless", func() { cmd.WaitWithDefaultTimeout() Expect(cmd.ExitCode()).To(Equal(0)) + cmd = rootlessTest.PodmanAsUser([]string{"inspect", "-l", "--type", "container", "--format", "{{ .State.Status }}"}, 1000, 1000, env) + cmd.WaitWithDefaultTimeout() + Expect(cmd.LineInOutputContains("exited")).To(BeTrue()) + cmd = rootlessTest.PodmanAsUser([]string{"start", "-l"}, 1000, 1000, env) cmd.WaitWithDefaultTimeout() Expect(cmd.ExitCode()).To(Equal(0)) @@ -214,6 +225,14 @@ var _ = Describe("Podman rootless", func() { cmd.WaitWithDefaultTimeout() Expect(cmd.ExitCode()).To(Equal(0)) + if len(args) == 0 { + cmd = rootlessTest.PodmanAsUser([]string{"inspect", "-l"}, 1000, 1000, env) + cmd.WaitWithDefaultTimeout() + Expect(cmd.ExitCode()).To(Equal(0)) + data := cmd.InspectContainerToJSON() + Expect(data[0].HostConfig.NetworkMode).To(ContainSubstring("slirp4netns")) + } + if !canUseExec { Skip("ioctl(NS_GET_PARENT) not supported.") } diff --git a/test/e2e/run_cgroup_parent_test.go b/test/e2e/run_cgroup_parent_test.go index f266fafa4..57b3aa6b1 100644 --- a/test/e2e/run_cgroup_parent_test.go +++ b/test/e2e/run_cgroup_parent_test.go @@ -4,6 +4,7 @@ import ( "fmt" "os" + . "github.com/containers/libpod/test/utils" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" ) @@ -12,7 +13,7 @@ var _ = Describe("Podman run with --cgroup-parent", func() { var ( tempdir string err error - podmanTest PodmanTest + podmanTest *PodmanTestIntegration ) BeforeEach(func() { @@ -20,7 +21,7 @@ var _ = Describe("Podman run with --cgroup-parent", func() { if err != nil { os.Exit(1) } - podmanTest = PodmanCreate(tempdir) + podmanTest = PodmanTestCreate(tempdir) podmanTest.RestoreArtifact(fedoraMinimal) }) @@ -32,7 +33,7 @@ var _ = Describe("Podman run with --cgroup-parent", func() { }) Specify("valid --cgroup-parent using cgroupfs", func() { - if !containerized() { + if !Containerized() { Skip("Must be containerized to run this test.") } cgroup := "/zzz" @@ -45,7 +46,7 @@ var _ = Describe("Podman run with --cgroup-parent", func() { Specify("no --cgroup-parent", func() { cgroup := "/libpod_parent" - if !containerized() && podmanTest.CgroupManager != "cgroupfs" { + if !Containerized() && podmanTest.CgroupManager != "cgroupfs" { cgroup = "/machine.slice" } run := podmanTest.Podman([]string{"run", fedoraMinimal, "cat", "/proc/self/cgroup"}) @@ -56,7 +57,7 @@ var _ = Describe("Podman run with --cgroup-parent", func() { }) Specify("valid --cgroup-parent using slice", func() { - if containerized() || podmanTest.CgroupManager == "cgroupfs" { + if Containerized() || podmanTest.CgroupManager == "cgroupfs" { Skip("Requires Systemd cgroup manager support") } cgroup := "aaaa.slice" diff --git a/test/e2e/run_cleanup_test.go b/test/e2e/run_cleanup_test.go index 02c70734a..5b60efa86 100644 --- a/test/e2e/run_cleanup_test.go +++ b/test/e2e/run_cleanup_test.go @@ -4,6 +4,7 @@ import ( "fmt" "os" + . "github.com/containers/libpod/test/utils" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" ) @@ -12,7 +13,7 @@ var _ = Describe("Podman run exit", func() { var ( tempdir string err error - podmanTest PodmanTest + podmanTest *PodmanTestIntegration ) BeforeEach(func() { @@ -20,7 +21,7 @@ var _ = Describe("Podman run exit", func() { if err != nil { os.Exit(1) } - podmanTest = PodmanCreate(tempdir) + podmanTest = PodmanTestCreate(tempdir) podmanTest.RestoreAllArtifacts() }) @@ -32,14 +33,14 @@ var _ = Describe("Podman run exit", func() { }) It("podman run -d mount cleanup test", func() { - mount := podmanTest.SystemExec("mount", nil) + mount := SystemExec("mount", nil) mount.WaitWithDefaultTimeout() out1 := mount.OutputToString() result := podmanTest.Podman([]string{"create", "-dt", ALPINE, "echo", "hello"}) result.WaitWithDefaultTimeout() Expect(result.ExitCode()).To(Equal(0)) - mount = podmanTest.SystemExec("mount", nil) + mount = SystemExec("mount", nil) mount.WaitWithDefaultTimeout() out2 := mount.OutputToString() Expect(out1).To(Equal(out2)) diff --git a/test/e2e/run_cpu_test.go b/test/e2e/run_cpu_test.go index d56dfac64..343fe656c 100644 --- a/test/e2e/run_cpu_test.go +++ b/test/e2e/run_cpu_test.go @@ -4,6 +4,7 @@ import ( "fmt" "os" + . "github.com/containers/libpod/test/utils" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" ) @@ -12,7 +13,7 @@ var _ = Describe("Podman run cpu", func() { var ( tempdir string err error - podmanTest PodmanTest + podmanTest *PodmanTestIntegration ) BeforeEach(func() { @@ -20,7 +21,7 @@ var _ = Describe("Podman run cpu", func() { if err != nil { os.Exit(1) } - podmanTest = PodmanCreate(tempdir) + podmanTest = PodmanTestCreate(tempdir) podmanTest.RestoreAllArtifacts() }) diff --git a/test/e2e/run_device_test.go b/test/e2e/run_device_test.go index fedd696d1..7f1f7b2d0 100644 --- a/test/e2e/run_device_test.go +++ b/test/e2e/run_device_test.go @@ -4,6 +4,7 @@ import ( "fmt" "os" + . "github.com/containers/libpod/test/utils" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" ) @@ -12,7 +13,7 @@ var _ = Describe("Podman run device", func() { var ( tempdir string err error - podmanTest PodmanTest + podmanTest *PodmanTestIntegration ) BeforeEach(func() { @@ -20,7 +21,7 @@ var _ = Describe("Podman run device", func() { if err != nil { os.Exit(1) } - podmanTest = PodmanCreate(tempdir) + podmanTest = PodmanTestCreate(tempdir) podmanTest.RestoreAllArtifacts() }) diff --git a/test/e2e/run_dns_test.go b/test/e2e/run_dns_test.go index c5a02c776..444c568e0 100644 --- a/test/e2e/run_dns_test.go +++ b/test/e2e/run_dns_test.go @@ -1,9 +1,10 @@ package integration import ( + "fmt" "os" - "fmt" + . "github.com/containers/libpod/test/utils" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" ) @@ -12,7 +13,7 @@ var _ = Describe("Podman run dns", func() { var ( tempdir string err error - podmanTest PodmanTest + podmanTest *PodmanTestIntegration ) BeforeEach(func() { @@ -20,7 +21,7 @@ var _ = Describe("Podman run dns", func() { if err != nil { os.Exit(1) } - podmanTest = PodmanCreate(tempdir) + podmanTest = PodmanTestCreate(tempdir) podmanTest.RestoreAllArtifacts() }) @@ -83,4 +84,11 @@ var _ = Describe("Podman run dns", func() { Expect(session.ExitCode()).To(Equal(0)) Expect(session.OutputToString()).To(Equal("foobar")) }) + + It("podman run add hostname sets /etc/hosts", func() { + session := podmanTest.Podman([]string{"run", "-t", "-i", "--hostname=foobar", ALPINE, "cat", "/etc/hosts"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + Expect(session.LineInOutputContains("foobar")).To(BeTrue()) + }) }) diff --git a/test/e2e/run_entrypoint_test.go b/test/e2e/run_entrypoint_test.go index 5e4ef75e1..227037f92 100644 --- a/test/e2e/run_entrypoint_test.go +++ b/test/e2e/run_entrypoint_test.go @@ -4,6 +4,7 @@ import ( "fmt" "os" + . "github.com/containers/libpod/test/utils" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" ) @@ -12,7 +13,7 @@ var _ = Describe("Podman run entrypoint", func() { var ( tempdir string err error - podmanTest PodmanTest + podmanTest *PodmanTestIntegration ) BeforeEach(func() { @@ -20,7 +21,7 @@ var _ = Describe("Podman run entrypoint", func() { if err != nil { os.Exit(1) } - podmanTest = PodmanCreate(tempdir) + podmanTest = PodmanTestCreate(tempdir) podmanTest.RestoreArtifact(ALPINE) }) diff --git a/test/e2e/run_exit_test.go b/test/e2e/run_exit_test.go index bb38f7222..788cbd8dd 100644 --- a/test/e2e/run_exit_test.go +++ b/test/e2e/run_exit_test.go @@ -4,6 +4,7 @@ import ( "fmt" "os" + . "github.com/containers/libpod/test/utils" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" ) @@ -12,7 +13,7 @@ var _ = Describe("Podman run exit", func() { var ( tempdir string err error - podmanTest PodmanTest + podmanTest *PodmanTestIntegration ) BeforeEach(func() { @@ -20,7 +21,7 @@ var _ = Describe("Podman run exit", func() { if err != nil { os.Exit(1) } - podmanTest = PodmanCreate(tempdir) + podmanTest = PodmanTestCreate(tempdir) podmanTest.RestoreAllArtifacts() }) diff --git a/test/e2e/run_memory_test.go b/test/e2e/run_memory_test.go index d1768138b..91a311e85 100644 --- a/test/e2e/run_memory_test.go +++ b/test/e2e/run_memory_test.go @@ -4,6 +4,7 @@ import ( "fmt" "os" + . "github.com/containers/libpod/test/utils" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" ) @@ -12,7 +13,7 @@ var _ = Describe("Podman run memory", func() { var ( tempdir string err error - podmanTest PodmanTest + podmanTest *PodmanTestIntegration ) BeforeEach(func() { @@ -20,7 +21,7 @@ var _ = Describe("Podman run memory", func() { if err != nil { os.Exit(1) } - podmanTest = PodmanCreate(tempdir) + podmanTest = PodmanTestCreate(tempdir) podmanTest.RestoreAllArtifacts() }) diff --git a/test/e2e/run_networking_test.go b/test/e2e/run_networking_test.go index 021825d4b..1b05ac031 100644 --- a/test/e2e/run_networking_test.go +++ b/test/e2e/run_networking_test.go @@ -4,6 +4,7 @@ import ( "fmt" "os" + . "github.com/containers/libpod/test/utils" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" ) @@ -12,7 +13,7 @@ var _ = Describe("Podman rmi", func() { var ( tempdir string err error - podmanTest PodmanTest + podmanTest *PodmanTestIntegration hostname, _ = os.Hostname() ) @@ -21,7 +22,7 @@ var _ = Describe("Podman rmi", func() { if err != nil { os.Exit(1) } - podmanTest = PodmanCreate(tempdir) + podmanTest = PodmanTestCreate(tempdir) podmanTest.RestoreAllArtifacts() }) @@ -54,7 +55,7 @@ var _ = Describe("Podman rmi", func() { session := podmanTest.Podman([]string{"run", "-dt", "--expose", "222-223", "-P", ALPINE, "/bin/sh"}) session.Wait(30) Expect(session.ExitCode()).To(Equal(0)) - results := podmanTest.SystemExec("iptables", []string{"-t", "nat", "-L"}) + results := SystemExec("iptables", []string{"-t", "nat", "-L"}) results.Wait(30) Expect(results.ExitCode()).To(Equal(0)) Expect(results.OutputToString()).To(ContainSubstring("222")) @@ -65,12 +66,12 @@ var _ = Describe("Podman rmi", func() { session := podmanTest.Podman([]string{"run", "-dt", "-p", "80:8000", ALPINE, "/bin/sh"}) session.Wait(30) Expect(session.ExitCode()).To(Equal(0)) - results := podmanTest.SystemExec("iptables", []string{"-t", "nat", "-L"}) + results := SystemExec("iptables", []string{"-t", "nat", "-L"}) results.Wait(30) Expect(results.ExitCode()).To(Equal(0)) Expect(results.OutputToString()).To(ContainSubstring("8000")) - ncBusy := podmanTest.SystemExec("nc", []string{"-l", "-p", "80"}) + ncBusy := SystemExec("nc", []string{"-l", "-p", "80"}) ncBusy.Wait(10) Expect(ncBusy.ExitCode()).ToNot(Equal(0)) }) diff --git a/test/e2e/run_ns_test.go b/test/e2e/run_ns_test.go index 88c0b1ad2..e4dcc5adc 100644 --- a/test/e2e/run_ns_test.go +++ b/test/e2e/run_ns_test.go @@ -5,6 +5,7 @@ import ( "os" "strings" + . "github.com/containers/libpod/test/utils" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" ) @@ -13,7 +14,7 @@ var _ = Describe("Podman run ns", func() { var ( tempdir string err error - podmanTest PodmanTest + podmanTest *PodmanTestIntegration ) BeforeEach(func() { @@ -21,7 +22,7 @@ var _ = Describe("Podman run ns", func() { if err != nil { os.Exit(1) } - podmanTest = PodmanCreate(tempdir) + podmanTest = PodmanTestCreate(tempdir) podmanTest.RestoreArtifact(fedoraMinimal) }) @@ -49,7 +50,7 @@ var _ = Describe("Podman run ns", func() { }) It("podman run ipcns test", func() { - setup := podmanTest.SystemExec("ls", []string{"--inode", "-d", "/dev/shm"}) + setup := SystemExec("ls", []string{"--inode", "-d", "/dev/shm"}) setup.WaitWithDefaultTimeout() Expect(setup.ExitCode()).To(Equal(0)) hostShm := setup.OutputToString() @@ -61,7 +62,7 @@ var _ = Describe("Podman run ns", func() { }) It("podman run ipcns ipcmk host test", func() { - setup := podmanTest.SystemExec("ipcmk", []string{"-M", "1024"}) + setup := SystemExec("ipcmk", []string{"-M", "1024"}) setup.WaitWithDefaultTimeout() Expect(setup.ExitCode()).To(Equal(0)) output := strings.Split(setup.OutputToString(), " ") @@ -70,7 +71,7 @@ var _ = Describe("Podman run ns", func() { session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) - setup = podmanTest.SystemExec("ipcrm", []string{"-m", ipc}) + setup = SystemExec("ipcrm", []string{"-m", ipc}) setup.WaitWithDefaultTimeout() Expect(setup.ExitCode()).To(Equal(0)) }) diff --git a/test/e2e/run_passwd_test.go b/test/e2e/run_passwd_test.go index 0bea092bb..891f4fbd8 100644 --- a/test/e2e/run_passwd_test.go +++ b/test/e2e/run_passwd_test.go @@ -4,6 +4,7 @@ import ( "os" "fmt" + . "github.com/containers/libpod/test/utils" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" ) @@ -12,7 +13,7 @@ var _ = Describe("Podman run passwd", func() { var ( tempdir string err error - podmanTest PodmanTest + podmanTest *PodmanTestIntegration ) BeforeEach(func() { @@ -20,7 +21,7 @@ var _ = Describe("Podman run passwd", func() { if err != nil { os.Exit(1) } - podmanTest = PodmanCreate(tempdir) + podmanTest = PodmanTestCreate(tempdir) podmanTest.RestoreAllArtifacts() }) diff --git a/test/e2e/run_privileged_test.go b/test/e2e/run_privileged_test.go index 0a62d8505..770ea3e6b 100644 --- a/test/e2e/run_privileged_test.go +++ b/test/e2e/run_privileged_test.go @@ -5,6 +5,7 @@ import ( "os" "strings" + . "github.com/containers/libpod/test/utils" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" ) @@ -13,7 +14,7 @@ var _ = Describe("Podman privileged container tests", func() { var ( tempdir string err error - podmanTest PodmanTest + podmanTest *PodmanTestIntegration ) BeforeEach(func() { @@ -21,7 +22,7 @@ var _ = Describe("Podman privileged container tests", func() { if err != nil { os.Exit(1) } - podmanTest = PodmanCreate(tempdir) + podmanTest = PodmanTestCreate(tempdir) podmanTest.RestoreAllArtifacts() }) @@ -42,7 +43,7 @@ var _ = Describe("Podman privileged container tests", func() { }) It("podman privileged CapEff", func() { - cap := podmanTest.SystemExec("grep", []string{"CapEff", "/proc/self/status"}) + cap := SystemExec("grep", []string{"CapEff", "/proc/self/status"}) cap.WaitWithDefaultTimeout() Expect(cap.ExitCode()).To(Equal(0)) @@ -53,7 +54,7 @@ var _ = Describe("Podman privileged container tests", func() { }) It("podman cap-add CapEff", func() { - cap := podmanTest.SystemExec("grep", []string{"CapEff", "/proc/self/status"}) + cap := SystemExec("grep", []string{"CapEff", "/proc/self/status"}) cap.WaitWithDefaultTimeout() Expect(cap.ExitCode()).To(Equal(0)) @@ -87,13 +88,13 @@ var _ = Describe("Podman privileged container tests", func() { It("run no-new-privileges test", func() { // Check if our kernel is new enough - k, err := IsKernelNewThan("4.14") + k, err := IsKernelNewerThan("4.14") Expect(err).To(BeNil()) if !k { Skip("Kernel is not new enough to test this feature") } - cap := podmanTest.SystemExec("grep", []string{"NoNewPrivs", "/proc/self/status"}) + cap := SystemExec("grep", []string{"NoNewPrivs", "/proc/self/status"}) cap.WaitWithDefaultTimeout() if cap.ExitCode() != 0 { Skip("Can't determine NoNewPrivs") @@ -103,12 +104,12 @@ var _ = Describe("Podman privileged container tests", func() { session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) - privs := strings.Split(cap.OutputToString(), ":") + privs := strings.Split(session.OutputToString(), ":") session = podmanTest.Podman([]string{"run", "--security-opt", "no-new-privileges", "busybox", "grep", "NoNewPrivs", "/proc/self/status"}) session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) - noprivs := strings.Split(cap.OutputToString(), ":") + noprivs := strings.Split(session.OutputToString(), ":") Expect(privs[1]).To(Not(Equal(noprivs[1]))) }) diff --git a/test/e2e/run_restart_test.go b/test/e2e/run_restart_test.go index a2f0b8b41..018c66b45 100644 --- a/test/e2e/run_restart_test.go +++ b/test/e2e/run_restart_test.go @@ -4,6 +4,7 @@ import ( "fmt" "os" + . "github.com/containers/libpod/test/utils" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" ) @@ -12,7 +13,7 @@ var _ = Describe("Podman run restart containers", func() { var ( tempdir string err error - podmanTest PodmanTest + podmanTest *PodmanTestIntegration ) BeforeEach(func() { @@ -20,7 +21,7 @@ var _ = Describe("Podman run restart containers", func() { if err != nil { os.Exit(1) } - podmanTest = PodmanCreate(tempdir) + podmanTest = PodmanTestCreate(tempdir) podmanTest.RestoreAllArtifacts() }) @@ -43,7 +44,7 @@ var _ = Describe("Podman run restart containers", func() { It("Podman start after signal kill", func() { _ = podmanTest.RunTopContainer("test1") - ok := WaitForContainer(&podmanTest) + ok := WaitForContainer(podmanTest) Expect(ok).To(BeTrue()) killSession := podmanTest.Podman([]string{"kill", "-s", "9", "test1"}) diff --git a/test/e2e/run_selinux_test.go b/test/e2e/run_selinux_test.go index a1a18c780..418382e16 100644 --- a/test/e2e/run_selinux_test.go +++ b/test/e2e/run_selinux_test.go @@ -4,6 +4,7 @@ import ( "fmt" "os" + . "github.com/containers/libpod/test/utils" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" "github.com/opencontainers/selinux/go-selinux" @@ -13,7 +14,7 @@ var _ = Describe("Podman run", func() { var ( tempdir string err error - podmanTest PodmanTest + podmanTest *PodmanTestIntegration ) BeforeEach(func() { @@ -21,7 +22,7 @@ var _ = Describe("Podman run", func() { if err != nil { os.Exit(1) } - podmanTest = PodmanCreate(tempdir) + podmanTest = PodmanTestCreate(tempdir) podmanTest.RestoreAllArtifacts() if !selinux.GetEnabled() { Skip("SELinux not enabled") diff --git a/test/e2e/run_signal_test.go b/test/e2e/run_signal_test.go index 02b6f4941..8f7894db8 100644 --- a/test/e2e/run_signal_test.go +++ b/test/e2e/run_signal_test.go @@ -4,39 +4,24 @@ import ( "fmt" "io" "os" - "os/exec" "path/filepath" "strings" "syscall" "time" + . "github.com/containers/libpod/test/utils" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" - "github.com/onsi/gomega/gexec" "golang.org/x/sys/unix" ) -// PodmanPID execs podman and returns its PID -func (p *PodmanTest) PodmanPID(args []string) (*PodmanSession, int) { - podmanOptions := p.MakeOptions() - podmanOptions = append(podmanOptions, strings.Split(p.StorageOptions, " ")...) - podmanOptions = append(podmanOptions, args...) - fmt.Printf("Running: %s %s\n", p.PodmanBinary, strings.Join(podmanOptions, " ")) - command := exec.Command(p.PodmanBinary, podmanOptions...) - session, err := gexec.Start(command, GinkgoWriter, GinkgoWriter) - if err != nil { - Fail(fmt.Sprintf("unable to run podman command: %s", strings.Join(podmanOptions, " "))) - } - return &PodmanSession{session}, command.Process.Pid -} - const sigCatch = "trap \"echo FOO >> /h/fifo \" 8; echo READY >> /h/fifo; while :; do sleep 0.25; done" var _ = Describe("Podman run with --sig-proxy", func() { var ( tmpdir string err error - podmanTest PodmanTest + podmanTest *PodmanTestIntegration ) BeforeEach(func() { @@ -44,7 +29,7 @@ var _ = Describe("Podman run with --sig-proxy", func() { if err != nil { os.Exit(1) } - podmanTest = PodmanCreate(tmpdir) + podmanTest = PodmanTestCreate(tmpdir) podmanTest.RestoreArtifact(fedoraMinimal) }) @@ -56,6 +41,9 @@ var _ = Describe("Podman run with --sig-proxy", func() { }) Specify("signals are forwarded to container using sig-proxy", func() { + if podmanTest.Host.Arch == "ppc64le" { + Skip("Doesnt work on ppc64le") + } signal := syscall.SIGFPE // Set up a socket for communication udsDir := filepath.Join(tmpdir, "socket") @@ -119,7 +107,7 @@ var _ = Describe("Podman run with --sig-proxy", func() { signal := syscall.SIGPOLL session, pid := podmanTest.PodmanPID([]string{"run", "--name", "test2", "--sig-proxy=false", fedoraMinimal, "bash", "-c", sigCatch}) - ok := WaitForContainer(&podmanTest) + ok := WaitForContainer(podmanTest) Expect(ok).To(BeTrue()) // Kill with given signal diff --git a/test/e2e/run_staticip_test.go b/test/e2e/run_staticip_test.go index b69d15cee..749835b47 100644 --- a/test/e2e/run_staticip_test.go +++ b/test/e2e/run_staticip_test.go @@ -4,6 +4,7 @@ import ( "fmt" "os" + . "github.com/containers/libpod/test/utils" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" ) @@ -12,7 +13,7 @@ var _ = Describe("Podman run with --ip flag", func() { var ( tempdir string err error - podmanTest PodmanTest + podmanTest *PodmanTestIntegration ) BeforeEach(func() { @@ -20,8 +21,10 @@ var _ = Describe("Podman run with --ip flag", func() { if err != nil { os.Exit(1) } - podmanTest = PodmanCreate(tempdir) + podmanTest = PodmanTestCreate(tempdir) podmanTest.RestoreAllArtifacts() + // Cleanup the CNI networks used by the tests + os.RemoveAll("/var/lib/cni/networks/podman") }) AfterEach(func() { @@ -55,4 +58,13 @@ var _ = Describe("Podman run with --ip flag", func() { Expect(result.ExitCode()).To(Equal(0)) Expect(result.OutputToString()).To(ContainSubstring("10.88.64.128/16")) }) + + It("Podman run two containers with the same IP", func() { + result := podmanTest.Podman([]string{"run", "-d", "--ip", "10.88.64.128", ALPINE, "sleep", "999"}) + result.WaitWithDefaultTimeout() + Expect(result.ExitCode()).To(Equal(0)) + result = podmanTest.Podman([]string{"run", "-ti", "--ip", "10.88.64.128", ALPINE, "ip", "addr"}) + result.WaitWithDefaultTimeout() + Expect(result.ExitCode()).ToNot(Equal(0)) + }) }) diff --git a/test/e2e/run_test.go b/test/e2e/run_test.go index cb436ccca..4a9bd4e46 100644 --- a/test/e2e/run_test.go +++ b/test/e2e/run_test.go @@ -3,10 +3,12 @@ package integration import ( "fmt" "io/ioutil" + "net" "os" "path/filepath" "strings" + . "github.com/containers/libpod/test/utils" "github.com/mrunalp/fileutils" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" @@ -16,7 +18,7 @@ var _ = Describe("Podman run", func() { var ( tempdir string err error - podmanTest PodmanTest + podmanTest *PodmanTestIntegration ) BeforeEach(func() { @@ -24,7 +26,7 @@ var _ = Describe("Podman run", func() { if err != nil { os.Exit(1) } - podmanTest = PodmanCreate(tempdir) + podmanTest = PodmanTestCreate(tempdir) podmanTest.RestoreAllArtifacts() }) @@ -42,8 +44,9 @@ var _ = Describe("Podman run", func() { }) It("podman run a container based on a complex local image name", func() { + imageName := strings.TrimPrefix(nginx, "quay.io/") podmanTest.RestoreArtifact(nginx) - session := podmanTest.Podman([]string{"run", "baude/alpine_nginx:latest", "ls"}) + session := podmanTest.Podman([]string{"run", imageName, "ls"}) session.WaitWithDefaultTimeout() Expect(session.ErrorToString()).ToNot(ContainSubstring("Trying to pull")) Expect(session.ExitCode()).To(Equal(0)) @@ -51,13 +54,13 @@ var _ = Describe("Podman run", func() { It("podman run a container based on on a short name with localhost", func() { podmanTest.RestoreArtifact(nginx) - tag := podmanTest.Podman([]string{"tag", nginx, "localhost/baude/alpine_nginx:latest"}) + tag := podmanTest.Podman([]string{"tag", nginx, "localhost/libpod/alpine_nginx:latest"}) tag.WaitWithDefaultTimeout() rmi := podmanTest.Podman([]string{"rmi", nginx}) rmi.WaitWithDefaultTimeout() - session := podmanTest.Podman([]string{"run", "baude/alpine_nginx:latest", "ls"}) + session := podmanTest.Podman([]string{"run", "libpod/alpine_nginx:latest", "ls"}) session.WaitWithDefaultTimeout() Expect(session.ErrorToString()).ToNot(ContainSubstring("Trying to pull")) Expect(session.ExitCode()).To(Equal(0)) @@ -202,7 +205,10 @@ var _ = Describe("Podman run", func() { Expect(session.OutputToString()).To(ContainSubstring("/run/test rw,relatime, shared")) }) - It("podman run with mount flag", func() { + It("podman run with --mount flag", func() { + if podmanTest.Host.Arch == "ppc64le" { + Skip("skip failing test on ppc64le") + } mountPath := filepath.Join(podmanTest.TempDir, "secrets") os.Mkdir(mountPath, 0755) session := podmanTest.Podman([]string{"run", "--rm", "--mount", fmt.Sprintf("type=bind,src=%s,target=/run/test", mountPath), ALPINE, "grep", "/run/test", "/proc/self/mountinfo"}) @@ -221,7 +227,6 @@ var _ = Describe("Podman run", func() { found, matches := session.GrepString("/run/test") Expect(found).Should(BeTrue()) Expect(matches[0]).To(ContainSubstring("rw")) - Expect(matches[0]).To(ContainSubstring("relatime")) Expect(matches[0]).To(ContainSubstring("shared")) mountPath = filepath.Join(podmanTest.TempDir, "scratchpad") @@ -283,14 +288,27 @@ var _ = Describe("Podman run", func() { }) It("podman run notify_socket", func() { - sock := "/run/notify" + host := GetHostDistributionInfo() + if host.Distribution != "rhel" && host.Distribution != "centos" && host.Distribution != "fedora" { + Skip("this test requires a working runc") + } + sock := filepath.Join(podmanTest.TempDir, "notify") + addr := net.UnixAddr{ + Name: sock, + Net: "unixgram", + } + socket, err := net.ListenUnixgram("unixgram", &addr) + Expect(err).To(BeNil()) + defer os.Remove(sock) + defer socket.Close() + os.Setenv("NOTIFY_SOCKET", sock) + defer os.Unsetenv("NOTIFY_SOCKET") + session := podmanTest.Podman([]string{"run", "--rm", ALPINE, "printenv", "NOTIFY_SOCKET"}) session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) - match, _ := session.GrepString(sock) - Expect(match).Should(BeTrue()) - os.Unsetenv("NOTIFY_SOCKET") + Expect(len(session.OutputToStringArray())).To(BeNumerically(">", 0)) }) It("podman run log-opt", func() { @@ -318,7 +336,7 @@ var _ = Describe("Podman run", func() { hooksDir := tempdir + "/hooks" os.Mkdir(hooksDir, 0755) fileutils.CopyFile("hooks/hooks.json", hooksDir) - os.Setenv("HOOK_OPTION", fmt.Sprintf("--hooks-dir-path=%s", hooksDir)) + os.Setenv("HOOK_OPTION", fmt.Sprintf("--hooks-dir=%s", hooksDir)) os.Remove(hcheck) session := podmanTest.Podman([]string{"run", ALPINE, "ls"}) session.Wait(10) @@ -351,7 +369,7 @@ var _ = Describe("Podman run", func() { keyFile := filepath.Join(targetDir, "key.pem") err = ioutil.WriteFile(keyFile, []byte(mountString), 0755) Expect(err).To(BeNil()) - execSession := podmanTest.SystemExec("ln", []string{"-s", targetDir, filepath.Join(secretsDir, "mysymlink")}) + execSession := SystemExec("ln", []string{"-s", targetDir, filepath.Join(secretsDir, "mysymlink")}) execSession.WaitWithDefaultTimeout() Expect(execSession.ExitCode()).To(Equal(0)) @@ -604,7 +622,21 @@ USER mail` session := podmanTest.Podman([]string{"run", "--volume", vol1 + ":/myvol1:z", "--volume", vol2 + ":/myvol2:shared,z", fedoraMinimal, "findmnt", "-o", "TARGET,PROPAGATION"}) session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) - match, _ := session.GrepString("shared") + match, shared := session.GrepString("shared") Expect(match).Should(BeTrue()) + // make sure it's only shared (and not 'shared,slave') + isSharedOnly := !strings.Contains(shared[0], "shared,") + Expect(isSharedOnly).Should(BeTrue()) + }) + + It("podman run --pod automatically", func() { + session := podmanTest.Podman([]string{"run", "--pod", "new:foobar", ALPINE, "ls"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + + check := podmanTest.Podman([]string{"pod", "ps", "--no-trunc"}) + check.WaitWithDefaultTimeout() + match, _ := check.GrepString("foobar") + Expect(match).To(BeTrue()) }) }) diff --git a/test/e2e/run_userns_test.go b/test/e2e/run_userns_test.go index f2a9af6bf..b1f3d08b4 100644 --- a/test/e2e/run_userns_test.go +++ b/test/e2e/run_userns_test.go @@ -4,6 +4,7 @@ import ( "os" "fmt" + . "github.com/containers/libpod/test/utils" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" ) @@ -12,7 +13,7 @@ var _ = Describe("Podman UserNS support", func() { var ( tempdir string err error - podmanTest PodmanTest + podmanTest *PodmanTestIntegration ) BeforeEach(func() { @@ -20,7 +21,7 @@ var _ = Describe("Podman UserNS support", func() { if err != nil { os.Exit(1) } - podmanTest = PodmanCreate(tempdir) + podmanTest = PodmanTestCreate(tempdir) podmanTest.RestoreAllArtifacts() }) diff --git a/test/e2e/runlabel_test.go b/test/e2e/runlabel_test.go index 8d10d3c24..93a19ba30 100644 --- a/test/e2e/runlabel_test.go +++ b/test/e2e/runlabel_test.go @@ -4,6 +4,7 @@ import ( "fmt" "os" + . "github.com/containers/libpod/test/utils" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" ) @@ -20,7 +21,7 @@ var _ = Describe("podman container runlabel", func() { var ( tempdir string err error - podmanTest PodmanTest + podmanTest *PodmanTestIntegration ) BeforeEach(func() { @@ -28,7 +29,7 @@ var _ = Describe("podman container runlabel", func() { if err != nil { os.Exit(1) } - podmanTest = PodmanCreate(tempdir) + podmanTest = PodmanTestCreate(tempdir) podmanTest.RestoreAllArtifacts() }) diff --git a/test/e2e/save_test.go b/test/e2e/save_test.go index 586215c46..9f64e49a7 100644 --- a/test/e2e/save_test.go +++ b/test/e2e/save_test.go @@ -5,6 +5,7 @@ import ( "os" "path/filepath" + . "github.com/containers/libpod/test/utils" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" ) @@ -13,7 +14,7 @@ var _ = Describe("Podman save", func() { var ( tempdir string err error - podmanTest PodmanTest + podmanTest *PodmanTestIntegration ) BeforeEach(func() { @@ -21,7 +22,7 @@ var _ = Describe("Podman save", func() { if err != nil { os.Exit(1) } - podmanTest = PodmanCreate(tempdir) + podmanTest = PodmanTestCreate(tempdir) podmanTest.RestoreAllArtifacts() }) diff --git a/test/e2e/search_test.go b/test/e2e/search_test.go index 2848da259..0167e9062 100644 --- a/test/e2e/search_test.go +++ b/test/e2e/search_test.go @@ -5,6 +5,7 @@ import ( "os" "strconv" + . "github.com/containers/libpod/test/utils" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" ) @@ -13,7 +14,7 @@ var _ = Describe("Podman search", func() { var ( tempdir string err error - podmanTest PodmanTest + podmanTest *PodmanTestIntegration ) const regFileContents = ` [registries.search] @@ -40,7 +41,7 @@ var _ = Describe("Podman search", func() { if err != nil { os.Exit(1) } - podmanTest = PodmanCreate(tempdir) + podmanTest = PodmanTestCreate(tempdir) podmanTest.RestoreAllArtifacts() }) @@ -128,12 +129,15 @@ var _ = Describe("Podman search", func() { }) It("podman search attempts HTTP if tls-verify flag is set false", func() { + if podmanTest.Host.Arch == "ppc64le" { + Skip("No registry image for ppc64le") + } podmanTest.RestoreArtifact(registry) fakereg := podmanTest.Podman([]string{"run", "-d", "--name", "registry", "-p", "5000:5000", registry, "/entrypoint.sh", "/etc/docker/registry/config.yml"}) fakereg.WaitWithDefaultTimeout() Expect(fakereg.ExitCode()).To(Equal(0)) - if !WaitContainerReady(&podmanTest, "registry", "listening on", 20, 1) { + if !WaitContainerReady(podmanTest, "registry", "listening on", 20, 1) { Skip("Can not start docker registry.") } @@ -148,12 +152,15 @@ var _ = Describe("Podman search", func() { }) It("podman search in local registry", func() { + if podmanTest.Host.Arch == "ppc64le" { + Skip("No registry image for ppc64le") + } podmanTest.RestoreArtifact(registry) registry := podmanTest.Podman([]string{"run", "-d", "--name", "registry3", "-p", "5000:5000", registry, "/entrypoint.sh", "/etc/docker/registry/config.yml"}) registry.WaitWithDefaultTimeout() Expect(registry.ExitCode()).To(Equal(0)) - if !WaitContainerReady(&podmanTest, "registry3", "listening on", 20, 1) { + if !WaitContainerReady(podmanTest, "registry3", "listening on", 20, 1) { Skip("Can not start docker registry.") } @@ -168,12 +175,15 @@ var _ = Describe("Podman search", func() { }) It("podman search attempts HTTP if registry is in registries.insecure and force secure is false", func() { + if podmanTest.Host.Arch == "ppc64le" { + Skip("No registry image for ppc64le") + } podmanTest.RestoreArtifact(registry) registry := podmanTest.Podman([]string{"run", "-d", "--name", "registry4", "-p", "5000:5000", registry, "/entrypoint.sh", "/etc/docker/registry/config.yml"}) registry.WaitWithDefaultTimeout() Expect(registry.ExitCode()).To(Equal(0)) - if !WaitContainerReady(&podmanTest, "registry4", "listening on", 20, 1) { + if !WaitContainerReady(podmanTest, "registry4", "listening on", 20, 1) { Skip("Can not start docker registry.") } @@ -197,12 +207,15 @@ var _ = Describe("Podman search", func() { }) It("podman search doesn't attempt HTTP if force secure is true", func() { + if podmanTest.Host.Arch == "ppc64le" { + Skip("No registry image for ppc64le") + } podmanTest.RestoreArtifact(registry) registry := podmanTest.Podman([]string{"run", "-d", "-p", "5000:5000", "--name", "registry5", registry}) registry.WaitWithDefaultTimeout() Expect(registry.ExitCode()).To(Equal(0)) - if !WaitContainerReady(&podmanTest, "registry5", "listening on", 20, 1) { + if !WaitContainerReady(podmanTest, "registry5", "listening on", 20, 1) { Skip("Can not start docker registry.") } push := podmanTest.Podman([]string{"push", "--tls-verify=false", "--remove-signatures", ALPINE, "localhost:5000/my-alpine"}) @@ -225,12 +238,15 @@ var _ = Describe("Podman search", func() { }) It("podman search doesn't attempt HTTP if registry is not listed as insecure", func() { + if podmanTest.Host.Arch == "ppc64le" { + Skip("No registry image for ppc64le") + } podmanTest.RestoreArtifact(registry) registry := podmanTest.Podman([]string{"run", "-d", "-p", "5000:5000", "--name", "registry6", registry}) registry.WaitWithDefaultTimeout() Expect(registry.ExitCode()).To(Equal(0)) - if !WaitContainerReady(&podmanTest, "registry6", "listening on", 20, 1) { + if !WaitContainerReady(podmanTest, "registry6", "listening on", 20, 1) { Skip("Can not start docker registry.") } push := podmanTest.Podman([]string{"push", "--tls-verify=false", "--remove-signatures", ALPINE, "localhost:5000/my-alpine"}) @@ -253,12 +269,15 @@ var _ = Describe("Podman search", func() { }) It("podman search doesn't attempt HTTP if one registry is not listed as insecure", func() { + if podmanTest.Host.Arch == "ppc64le" { + Skip("No registry image for ppc64le") + } podmanTest.RestoreArtifact(registry) registryLocal := podmanTest.Podman([]string{"run", "-d", "-p", "5000:5000", "--name", "registry7", registry}) registryLocal.WaitWithDefaultTimeout() Expect(registryLocal.ExitCode()).To(Equal(0)) - if !WaitContainerReady(&podmanTest, "registry7", "listening on", 20, 1) { + if !WaitContainerReady(podmanTest, "registry7", "listening on", 20, 1) { Skip("Can not start docker registry.") } @@ -266,7 +285,7 @@ var _ = Describe("Podman search", func() { registryLocal.WaitWithDefaultTimeout() Expect(registryLocal.ExitCode()).To(Equal(0)) - if !WaitContainerReady(&podmanTest, "registry8", "listening on", 20, 1) { + if !WaitContainerReady(podmanTest, "registry8", "listening on", 20, 1) { Skip("Can not start docker registry.") } push := podmanTest.Podman([]string{"push", "--tls-verify=false", "--remove-signatures", ALPINE, "localhost:6000/my-alpine"}) diff --git a/test/e2e/start_test.go b/test/e2e/start_test.go index 9218cda69..c11511d1f 100644 --- a/test/e2e/start_test.go +++ b/test/e2e/start_test.go @@ -4,6 +4,7 @@ import ( "fmt" "os" + . "github.com/containers/libpod/test/utils" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" ) @@ -12,7 +13,7 @@ var _ = Describe("Podman start", func() { var ( tempdir string err error - podmanTest PodmanTest + podmanTest *PodmanTestIntegration ) BeforeEach(func() { @@ -20,7 +21,7 @@ var _ = Describe("Podman start", func() { if err != nil { os.Exit(1) } - podmanTest = PodmanCreate(tempdir) + podmanTest = PodmanTestCreate(tempdir) podmanTest.RestoreAllArtifacts() }) diff --git a/test/e2e/stats_test.go b/test/e2e/stats_test.go index e456d7114..be00d68b2 100644 --- a/test/e2e/stats_test.go +++ b/test/e2e/stats_test.go @@ -4,6 +4,7 @@ import ( "fmt" "os" + . "github.com/containers/libpod/test/utils" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" ) @@ -12,7 +13,7 @@ var _ = Describe("Podman stats", func() { var ( tempdir string err error - podmanTest PodmanTest + podmanTest *PodmanTestIntegration ) BeforeEach(func() { @@ -20,7 +21,7 @@ var _ = Describe("Podman stats", func() { if err != nil { os.Exit(1) } - podmanTest = PodmanCreate(tempdir) + podmanTest = PodmanTestCreate(tempdir) podmanTest.RestoreAllArtifacts() }) diff --git a/test/e2e/stop_test.go b/test/e2e/stop_test.go index 9698a3110..5c229b9b4 100644 --- a/test/e2e/stop_test.go +++ b/test/e2e/stop_test.go @@ -4,6 +4,7 @@ import ( "fmt" "os" + . "github.com/containers/libpod/test/utils" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" ) @@ -12,7 +13,7 @@ var _ = Describe("Podman stop", func() { var ( tempdir string err error - podmanTest PodmanTest + podmanTest *PodmanTestIntegration ) BeforeEach(func() { @@ -20,7 +21,7 @@ var _ = Describe("Podman stop", func() { if err != nil { os.Exit(1) } - podmanTest = PodmanCreate(tempdir) + podmanTest = PodmanTestCreate(tempdir) podmanTest.RestoreAllArtifacts() }) @@ -56,6 +57,20 @@ var _ = Describe("Podman stop", func() { Expect(session.ExitCode()).To(Equal(0)) }) + It("podman stop stopped container", func() { + session := podmanTest.RunTopContainer("test1") + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + + session2 := podmanTest.Podman([]string{"stop", "test1"}) + session2.WaitWithDefaultTimeout() + Expect(session2.ExitCode()).To(Equal(0)) + + session3 := podmanTest.Podman([]string{"stop", "test1"}) + session3.WaitWithDefaultTimeout() + Expect(session3.ExitCode()).To(Equal(0)) + }) + It("podman stop all containers", func() { session := podmanTest.RunTopContainer("test1") session.WaitWithDefaultTimeout() diff --git a/test/e2e/tag_test.go b/test/e2e/tag_test.go index 1b58fbd30..53896d1a2 100644 --- a/test/e2e/tag_test.go +++ b/test/e2e/tag_test.go @@ -4,6 +4,7 @@ import ( "fmt" "os" + . "github.com/containers/libpod/test/utils" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" ) @@ -12,7 +13,7 @@ var _ = Describe("Podman tag", func() { var ( tempdir string err error - podmanTest PodmanTest + podmanTest *PodmanTestIntegration ) BeforeEach(func() { @@ -20,7 +21,7 @@ var _ = Describe("Podman tag", func() { if err != nil { os.Exit(1) } - podmanTest = PodmanCreate(tempdir) + podmanTest = PodmanTestCreate(tempdir) podmanTest.RestoreAllArtifacts() }) diff --git a/test/e2e/top_test.go b/test/e2e/top_test.go index 9537c2f50..cfcf2a959 100644 --- a/test/e2e/top_test.go +++ b/test/e2e/top_test.go @@ -4,6 +4,7 @@ import ( "fmt" "os" + . "github.com/containers/libpod/test/utils" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" ) @@ -12,7 +13,7 @@ var _ = Describe("Podman top", func() { var ( tempdir string err error - podmanTest PodmanTest + podmanTest *PodmanTestIntegration ) BeforeEach(func() { @@ -20,7 +21,7 @@ var _ = Describe("Podman top", func() { if err != nil { os.Exit(1) } - podmanTest = PodmanCreate(tempdir) + podmanTest = PodmanTestCreate(tempdir) podmanTest.RestoreAllArtifacts() }) diff --git a/test/e2e/version_test.go b/test/e2e/version_test.go index 6caf0e3dd..68a462bdb 100644 --- a/test/e2e/version_test.go +++ b/test/e2e/version_test.go @@ -4,6 +4,7 @@ import ( "fmt" "os" + . "github.com/containers/libpod/test/utils" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" ) @@ -12,7 +13,7 @@ var _ = Describe("Podman version", func() { var ( tempdir string err error - podmanTest PodmanTest + podmanTest *PodmanTestIntegration ) BeforeEach(func() { @@ -20,7 +21,7 @@ var _ = Describe("Podman version", func() { if err != nil { os.Exit(1) } - podmanTest = PodmanCreate(tempdir) + podmanTest = PodmanTestCreate(tempdir) }) AfterEach(func() { diff --git a/test/e2e/wait_test.go b/test/e2e/wait_test.go index 8e7035204..a7e9b4c06 100644 --- a/test/e2e/wait_test.go +++ b/test/e2e/wait_test.go @@ -4,6 +4,7 @@ import ( "fmt" "os" + . "github.com/containers/libpod/test/utils" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" ) @@ -12,7 +13,7 @@ var _ = Describe("Podman wait", func() { var ( tempdir string err error - podmanTest PodmanTest + podmanTest *PodmanTestIntegration ) BeforeEach(func() { @@ -20,7 +21,7 @@ var _ = Describe("Podman wait", func() { if err != nil { os.Exit(1) } - podmanTest = PodmanCreate(tempdir) + podmanTest = PodmanTestCreate(tempdir) podmanTest.RestoreAllArtifacts() }) diff --git a/test/goecho/goecho.go b/test/goecho/goecho.go new file mode 100644 index 000000000..1c8d2f586 --- /dev/null +++ b/test/goecho/goecho.go @@ -0,0 +1,29 @@ +package main + +import ( + "fmt" + "os" + "strconv" + "time" +) + +func main() { + args := os.Args[1:] + exitCode := 0 + + for i := 0; i < len(args); i++ { + fmt.Fprintln(os.Stdout, args[i]) + fmt.Fprintln(os.Stderr, args[i]) + } + + if len(args) > 1 { + num, _ := strconv.Atoi(args[1]) + if args[0] == "exitcode" { + exitCode = num + } + if args[0] == "sleep" { + time.Sleep(time.Duration(num) * time.Second) + } + } + os.Exit(exitCode) +} diff --git a/test/system/libpod_suite_test.go b/test/system/libpod_suite_test.go new file mode 100644 index 000000000..5de50e4e7 --- /dev/null +++ b/test/system/libpod_suite_test.go @@ -0,0 +1,217 @@ +package system + +import ( + "fmt" + "os" + "strings" + "testing" + + . "github.com/containers/libpod/test/utils" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +var ( + PODMAN_BINARY string + GLOBALOPTIONS = []string{"--cgroup-manager", + "--cni-config-dir", + "--config", "-c", + "--conmon", + "--cpu-profile", + "--log-level", + "--root", + "--tmpdir", + "--runroot", + "--runtime", + "--storage-driver", + "--storage-opt", + "--syslog", + } + PODMAN_SUBCMD = []string{"attach", + "commit", + "container", + "build", + "create", + "diff", + "exec", + "export", + "history", + "image", + "images", + "import", + "info", + "inspect", + "kill", + "load", + "login", + "logout", + "logs", + "mount", + "pause", + "ps", + "pod", + "port", + "pull", + "push", + "restart", + "rm", + "rmi", + "run", + "save", + "search", + "start", + "stats", + "stop", + "tag", + "top", + "umount", + "unpause", + "version", + "wait", + "h", + } + INTEGRATION_ROOT string + ARTIFACT_DIR = "/tmp/.artifacts" + ALPINE = "docker.io/library/alpine:latest" + BB = "docker.io/library/busybox:latest" + BB_GLIBC = "docker.io/library/busybox:glibc" + fedoraMinimal = "registry.fedoraproject.org/fedora-minimal:latest" + nginx = "quay.io/baude/alpine_nginx:latest" + redis = "docker.io/library/redis:alpine" + registry = "docker.io/library/registry:2" + infra = "k8s.gcr.io/pause:3.1" + defaultWaitTimeout = 90 +) + +// PodmanTestSystem struct for command line options +type PodmanTestSystem struct { + PodmanTest + GlobalOptions map[string]string + PodmanCmdOptions map[string][]string +} + +// TestLibpod ginkgo master function +func TestLibpod(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "Libpod Suite") +} + +var _ = BeforeSuite(func() { +}) + +// PodmanTestCreate creates a PodmanTestSystem instance for the tests +func PodmanTestCreate(tempDir string) *PodmanTestSystem { + var envKey string + globalOptions := make(map[string]string) + podmanCmdOptions := make(map[string][]string) + + for _, n := range GLOBALOPTIONS { + envKey = strings.Replace(strings.ToUpper(strings.Trim(n, "-")), "-", "_", -1) + if isEnvSet(envKey) { + globalOptions[n] = os.Getenv(envKey) + } + } + + for _, n := range PODMAN_SUBCMD { + envKey = strings.Replace("PODMAN_SUBCMD_OPTIONS", "SUBCMD", strings.ToUpper(n), -1) + if isEnvSet(envKey) { + podmanCmdOptions[n] = strings.Split(os.Getenv(envKey), " ") + } + } + + podmanBinary := "podman" + if os.Getenv("PODMAN_BINARY") != "" { + podmanBinary = os.Getenv("PODMAN_BINARY") + } + + p := &PodmanTestSystem{ + PodmanTest: PodmanTest{ + PodmanBinary: podmanBinary, + ArtifactPath: ARTIFACT_DIR, + TempDir: tempDir, + }, + GlobalOptions: globalOptions, + PodmanCmdOptions: podmanCmdOptions, + } + + p.PodmanMakeOptions = p.makeOptions + + return p +} + +func (p *PodmanTestSystem) Podman(args []string) *PodmanSession { + return p.PodmanBase(args) +} + +//MakeOptions assembles all the podman options +func (p *PodmanTestSystem) makeOptions(args []string) []string { + var addOptions, subArgs []string + for _, n := range GLOBALOPTIONS { + if p.GlobalOptions[n] != "" { + addOptions = append(addOptions, n, p.GlobalOptions[n]) + } + } + + if len(args) == 0 { + return addOptions + } + + subCmd := args[0] + addOptions = append(addOptions, subCmd) + if subCmd == "unmount" { + subCmd = "umount" + } + if subCmd == "help" { + subCmd = "h" + } + + if _, ok := p.PodmanCmdOptions[subCmd]; ok { + m := make(map[string]bool) + subArgs = p.PodmanCmdOptions[subCmd] + for i := 0; i < len(subArgs); i++ { + m[subArgs[i]] = true + } + for i := 1; i < len(args); i++ { + if _, ok := m[args[i]]; !ok { + subArgs = append(subArgs, args[i]) + } + } + } else { + subArgs = args[1:] + } + + addOptions = append(addOptions, subArgs...) + + return addOptions +} + +// Cleanup cleans up the temporary store +func (p *PodmanTestSystem) Cleanup() { + // Remove all containers + stopall := p.Podman([]string{"stop", "-a", "--timeout", "0"}) + stopall.WaitWithDefaultTimeout() + + session := p.Podman([]string{"rm", "-fa"}) + session.Wait(90) + // Nuke tempdir + if err := os.RemoveAll(p.TempDir); err != nil { + fmt.Printf("%q\n", err) + } +} + +// CleanupPod cleans up the temporary store +func (p *PodmanTestSystem) CleanupPod() { + // Remove all containers + session := p.Podman([]string{"pod", "rm", "-fa"}) + session.Wait(90) + // Nuke tempdir + if err := os.RemoveAll(p.TempDir); err != nil { + fmt.Printf("%q\n", err) + } +} + +// Check if the key is set in Env +func isEnvSet(key string) bool { + _, set := os.LookupEnv(key) + return set +} diff --git a/test/system/version_test.go b/test/system/version_test.go new file mode 100644 index 000000000..ada0093b7 --- /dev/null +++ b/test/system/version_test.go @@ -0,0 +1,51 @@ +package system + +import ( + "fmt" + "os" + "regexp" + + . "github.com/containers/libpod/test/utils" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +var _ = Describe("Podman version test", func() { + var ( + tempdir string + err error + podmanTest *PodmanTestSystem + ) + + BeforeEach(func() { + tempdir, err = CreateTempDirInTempDir() + if err != nil { + os.Exit(1) + } + podmanTest = PodmanTestCreate(tempdir) + }) + + AfterEach(func() { + podmanTest.Cleanup() + f := CurrentGinkgoTestDescription() + timedResult := fmt.Sprintf("Test: %s completed in %f seconds", f.TestText, f.Duration.Seconds()) + GinkgoWriter.Write([]byte(timedResult)) + }) + + It("Smoking test: podman version with extra args", func() { + logc := podmanTest.Podman([]string{"version", "anything", "-", "--"}) + logc.WaitWithDefaultTimeout() + Expect(logc.ExitCode()).To(Equal(0)) + ver := logc.OutputToString() + Expect(regexp.MatchString("Version:.*?Go Version:.*?OS/Arch", ver)).To(BeTrue()) + }) + + It("Negative test: podman version with extra flag", func() { + logc := podmanTest.Podman([]string{"version", "--foo"}) + logc.WaitWithDefaultTimeout() + Expect(logc.ExitCode()).NotTo(Equal(0)) + err, _ := logc.GrepString("Incorrect Usage: flag provided but not defined: -foo") + Expect(err).To(BeTrue()) + }) + +}) diff --git a/test/utils/common_function_test.go b/test/utils/common_function_test.go new file mode 100644 index 000000000..1648a4899 --- /dev/null +++ b/test/utils/common_function_test.go @@ -0,0 +1,150 @@ +package utils_test + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "os" + "reflect" + "strings" + + . "github.com/containers/libpod/test/utils" + . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/extensions/table" + . "github.com/onsi/gomega" +) + +var _ = Describe("Common functions test", func() { + var defaultOSPath string + var defaultCgroupPath string + + BeforeEach(func() { + defaultOSPath = OSReleasePath + defaultCgroupPath = ProcessOneCgroupPath + }) + + AfterEach(func() { + OSReleasePath = defaultOSPath + ProcessOneCgroupPath = defaultCgroupPath + }) + + It("Test CreateTempDirInTempDir", func() { + tmpDir, _ := CreateTempDirInTempDir() + _, err := os.Stat(tmpDir) + Expect(os.IsNotExist(err)).ShouldNot(BeTrue(), "Directory is not created as expect") + }) + + It("Test SystemExec", func() { + session := SystemExec(GoechoPath, []string{}) + Expect(session.Command.Process).ShouldNot(BeNil(), "SystemExec can not start a process") + }) + + It("Test StringInSlice", func() { + testSlice := []string{"apple", "peach", "pear"} + Expect(StringInSlice("apple", testSlice)).To(BeTrue(), "apple should in ['apple', 'peach', 'pear']") + Expect(StringInSlice("banana", testSlice)).ShouldNot(BeTrue(), "banana should not in ['apple', 'peach', 'pear']") + Expect(StringInSlice("anything", []string{})).ShouldNot(BeTrue(), "anything should not in empty slice") + }) + + DescribeTable("Test GetHostDistributionInfo", + func(path, id, ver string, empty bool) { + txt := fmt.Sprintf("ID=%s\nVERSION_ID=%s", id, ver) + if !empty { + f, _ := os.Create(path) + f.WriteString(txt) + f.Close() + } + + OSReleasePath = path + host := GetHostDistributionInfo() + if empty { + Expect(host).To(Equal(HostOS{}), "HostOs should be empty.") + } else { + Expect(host.Distribution).To(Equal(strings.Trim(id, "\""))) + Expect(host.Version).To(Equal(strings.Trim(ver, "\""))) + } + }, + Entry("Configure file is not exist.", "/tmp/notexist", "", "", true), + Entry("Item value with and without \"", "/tmp/os-release.test", "fedora", "\"28\"", false), + Entry("Item empty with and without \"", "/tmp/os-release.test", "", "\"\"", false), + ) + + DescribeTable("Test IsKernelNewerThan", + func(kv string, expect, isNil bool) { + newer, err := IsKernelNewerThan(kv) + Expect(newer).To(Equal(expect), "Version compare results is not as expect.") + Expect(err == nil).To(Equal(isNil), "Error is not as expect.") + }, + Entry("Invlid kernel version: 0", "0", false, false), + Entry("Older kernel version:0.0", "0.0", true, true), + Entry("Newer kernel version: 100.17.14", "100.17.14", false, true), + Entry("Invlid kernel version: I am not a kernel version", "I am not a kernel version", false, false), + ) + + DescribeTable("Test TestIsCommandAvailable", + func(cmd string, expect bool) { + cmdExist := IsCommandAvailable(cmd) + Expect(cmdExist).To(Equal(expect)) + }, + Entry("Command exist", GoechoPath, true), + Entry("Command exist", "Fakecmd", false), + ) + + It("Test WriteJsonFile", func() { + type testJson struct { + Item1 int + Item2 []string + } + compareData := &testJson{} + + testData := &testJson{ + Item1: 5, + Item2: []string{"test"}, + } + + testByte, _ := json.Marshal(testData) + err := WriteJsonFile(testByte, "/tmp/testJson") + + Expect(err).To(BeNil(), "Failed to write JSON to file.") + + read, err := os.Open("/tmp/testJson") + defer read.Close() + + Expect(err).To(BeNil(), "Can not find the JSON file after we write it.") + + bytes, _ := ioutil.ReadAll(read) + json.Unmarshal(bytes, compareData) + + Expect(reflect.DeepEqual(testData, compareData)).To(BeTrue(), "Data chaned after we store it to file.") + }) + + DescribeTable("Test Containerized", + func(path string, setEnv, createFile, expect bool) { + if setEnv && (os.Getenv("container") == "") { + os.Setenv("container", "test") + defer os.Setenv("container", "") + } + if !setEnv && (os.Getenv("container") != "") { + containerized := os.Getenv("container") + os.Setenv("container", "") + defer os.Setenv("container", containerized) + } + txt := "1:test:/" + if expect { + txt = "2:docker:/" + } + if createFile { + f, _ := os.Create(path) + f.WriteString(txt) + f.Close() + } + ProcessOneCgroupPath = path + Expect(Containerized()).To(Equal(expect)) + }, + Entry("Set container in env", "", true, false, true), + Entry("Can not read from file", "/tmp/notexist", false, false, false), + Entry("Docker in cgroup file", "/tmp/cgroup.test", false, true, true), + Entry("Docker not in cgroup file", "/tmp/cgroup.test", false, true, false), + ) + +}) diff --git a/test/utils/podmansession_test.go b/test/utils/podmansession_test.go new file mode 100644 index 000000000..de8c20b24 --- /dev/null +++ b/test/utils/podmansession_test.go @@ -0,0 +1,90 @@ +package utils_test + +import ( + . "github.com/containers/libpod/test/utils" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +var _ = Describe("PodmanSession test", func() { + var session *PodmanSession + + BeforeEach(func() { + session = StartFakeCmdSession([]string{"PodmanSession", "test", "Podman Session"}) + session.WaitWithDefaultTimeout() + }) + + It("Test OutputToString", func() { + Expect(session.OutputToString()).To(Equal("PodmanSession test Podman Session")) + }) + + It("Test OutputToStringArray", func() { + Expect(session.OutputToStringArray()).To(Equal([]string{"PodmanSession", "test", "Podman Session"})) + }) + + It("Test ErrorToString", func() { + Expect(session.ErrorToString()).To(Equal("PodmanSession test Podman Session")) + }) + + It("Test ErrorToStringArray", func() { + Expect(session.ErrorToStringArray()).To(Equal([]string{"PodmanSession", "test", "Podman Session", ""})) + }) + + It("Test GrepString", func() { + match, backStr := session.GrepString("Session") + Expect(match).To(BeTrue()) + Expect(backStr).To(Equal([]string{"PodmanSession", "Podman Session"})) + + match, backStr = session.GrepString("I am not here") + Expect(match).To(Not(BeTrue())) + Expect(backStr).To(BeNil()) + + }) + + It("Test ErrorGrepString", func() { + match, backStr := session.ErrorGrepString("Session") + Expect(match).To(BeTrue()) + Expect(backStr).To(Equal([]string{"PodmanSession", "Podman Session"})) + + match, backStr = session.ErrorGrepString("I am not here") + Expect(match).To(Not(BeTrue())) + Expect(backStr).To(BeNil()) + + }) + + It("Test LineInOutputStartsWith", func() { + Expect(session.LineInOuputStartsWith("Podman")).To(BeTrue()) + Expect(session.LineInOuputStartsWith("Session")).To(Not(BeTrue())) + }) + + It("Test LineInOutputContains", func() { + Expect(session.LineInOutputContains("Podman")).To(BeTrue()) + Expect(session.LineInOutputContains("Session")).To(BeTrue()) + Expect(session.LineInOutputContains("I am not here")).To(Not(BeTrue())) + }) + + It("Test LineInOutputContainsTag", func() { + session = StartFakeCmdSession([]string{"HEAD LINE", "docker.io/library/busybox latest e1ddd7948a1c 5 weeks ago 1.38MB"}) + session.WaitWithDefaultTimeout() + Expect(session.LineInOutputContainsTag("docker.io/library/busybox", "latest")).To(BeTrue()) + Expect(session.LineInOutputContainsTag("busybox", "latest")).To(Not(BeTrue())) + }) + + It("Test IsJSONOutputValid", func() { + session = StartFakeCmdSession([]string{`{"page":1,"fruits":["apple","peach","pear"]}`}) + session.WaitWithDefaultTimeout() + Expect(session.IsJSONOutputValid()).To(BeTrue()) + + session = StartFakeCmdSession([]string{"I am not JSON"}) + session.WaitWithDefaultTimeout() + Expect(session.IsJSONOutputValid()).To(Not(BeTrue())) + }) + + It("Test WaitWithDefaultTimeout", func() { + session = StartFakeCmdSession([]string{"sleep", "2"}) + Expect(session.ExitCode()).Should(Equal(-1)) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).Should(Equal(0)) + }) + +}) diff --git a/test/utils/podmantest_test.go b/test/utils/podmantest_test.go new file mode 100644 index 000000000..60e3e2a97 --- /dev/null +++ b/test/utils/podmantest_test.go @@ -0,0 +1,74 @@ +package utils_test + +import ( + "os" + + . "github.com/containers/libpod/test/utils" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +var _ = Describe("PodmanTest test", func() { + var podmanTest *FakePodmanTest + + BeforeEach(func() { + podmanTest = FakePodmanTestCreate() + }) + + AfterEach(func() { + FakeOutputs = make(map[string][]string) + }) + + It("Test PodmanAsUserBase", func() { + FakeOutputs["check"] = []string{"check"} + os.Setenv("HOOK_OPTION", "hook_option") + env := os.Environ() + session := podmanTest.PodmanAsUserBase([]string{"check"}, 1000, 1000, env) + os.Unsetenv("HOOK_OPTION") + session.WaitWithDefaultTimeout() + Expect(session.Command.Process).ShouldNot(BeNil()) + }) + + It("Test NumberOfContainersRunning", func() { + FakeOutputs["ps -q"] = []string{"one", "two"} + Expect(podmanTest.NumberOfContainersRunning()).To(Equal(2)) + }) + + It("Test NumberOfContainers", func() { + FakeOutputs["ps -aq"] = []string{"one", "two"} + Expect(podmanTest.NumberOfContainers()).To(Equal(2)) + }) + + It("Test NumberOfPods", func() { + FakeOutputs["pod ps -q"] = []string{"one", "two"} + Expect(podmanTest.NumberOfPods()).To(Equal(2)) + }) + + It("Test WaitForContainer", func() { + FakeOutputs["ps -q"] = []string{"one", "two"} + Expect(WaitForContainer(podmanTest)).To(BeTrue()) + + FakeOutputs["ps -q"] = []string{"one"} + Expect(WaitForContainer(podmanTest)).To(BeTrue()) + + FakeOutputs["ps -q"] = []string{""} + Expect(WaitForContainer(podmanTest)).To(Not(BeTrue())) + }) + + It("Test GetContainerStatus", func() { + FakeOutputs["ps --all --format={{.Status}}"] = []string{"Need func update"} + Expect(podmanTest.GetContainerStatus()).To(Equal("Need func update")) + }) + + It("Test WaitContainerReady", func() { + FakeOutputs["logs testimage"] = []string{""} + Expect(WaitContainerReady(podmanTest, "testimage", "ready", 2, 1)).To(Not(BeTrue())) + + FakeOutputs["logs testimage"] = []string{"I am ready"} + Expect(WaitContainerReady(podmanTest, "testimage", "am ready", 2, 1)).To(BeTrue()) + + FakeOutputs["logs testimage"] = []string{"I am ready"} + Expect(WaitContainerReady(podmanTest, "testimage", "", 2, 1)).To(BeTrue()) + }) + +}) diff --git a/test/utils/utils.go b/test/utils/utils.go new file mode 100644 index 000000000..288c768d4 --- /dev/null +++ b/test/utils/utils.go @@ -0,0 +1,432 @@ +package utils + +import ( + "bufio" + "encoding/json" + "fmt" + "io/ioutil" + "os" + "os/exec" + "runtime" + "strings" + "time" + + "github.com/containers/storage/pkg/parsers/kernel" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + "github.com/onsi/gomega/gexec" +) + +var ( + defaultWaitTimeout = 90 + OSReleasePath = "/etc/os-release" + ProcessOneCgroupPath = "/proc/1/cgroup" +) + +// PodmanTestCommon contains common functions will be updated later in +// the inheritance structs +type PodmanTestCommon interface { + MakeOptions(args []string) []string + WaitForContainer() bool + WaitContainerReady(id string, expStr string, timeout int, step int) bool +} + +// PodmanTest struct for command line options +type PodmanTest struct { + PodmanMakeOptions func(args []string) []string + PodmanBinary string + ArtifactPath string + TempDir string +} + +// PodmanSession wraps the gexec.session so we can extend it +type PodmanSession struct { + *gexec.Session +} + +// HostOS is a simple struct for the test os +type HostOS struct { + Distribution string + Version string + Arch string +} + +// MakeOptions assembles all podman options +func (p *PodmanTest) MakeOptions(args []string) []string { + return p.PodmanMakeOptions(args) +} + +// PodmanAsUserBase exec podman as user. uid and gid is set for credentials useage. env is used +// to record the env for debugging +func (p *PodmanTest) PodmanAsUserBase(args []string, uid, gid uint32, env []string) *PodmanSession { + var command *exec.Cmd + podmanOptions := p.MakeOptions(args) + + if env == nil { + fmt.Printf("Running: %s %s\n", p.PodmanBinary, strings.Join(podmanOptions, " ")) + } else { + fmt.Printf("Running: (env: %v) %s %s\n", env, p.PodmanBinary, strings.Join(podmanOptions, " ")) + } + if uid != 0 || gid != 0 { + nsEnterOpts := append([]string{"--userspec", fmt.Sprintf("%d:%d", uid, gid), "/", p.PodmanBinary}, podmanOptions...) + command = exec.Command("chroot", nsEnterOpts...) + } else { + command = exec.Command(p.PodmanBinary, podmanOptions...) + } + if env != nil { + command.Env = env + } + + session, err := gexec.Start(command, GinkgoWriter, GinkgoWriter) + if err != nil { + Fail(fmt.Sprintf("unable to run podman command: %s\n%v", strings.Join(podmanOptions, " "), err)) + } + return &PodmanSession{session} +} + +// PodmanBase exec podman with default env. +func (p *PodmanTest) PodmanBase(args []string) *PodmanSession { + return p.PodmanAsUserBase(args, 0, 0, nil) +} + +// WaitForContainer waits on a started container +func (p *PodmanTest) WaitForContainer() bool { + for i := 0; i < 10; i++ { + if p.NumberOfContainersRunning() > 0 { + return true + } + time.Sleep(1 * time.Second) + } + return false +} + +// NumberOfContainersRunning returns an int of how many +// containers are currently running. +func (p *PodmanTest) NumberOfContainersRunning() int { + var containers []string + ps := p.PodmanBase([]string{"ps", "-q"}) + ps.WaitWithDefaultTimeout() + Expect(ps.ExitCode()).To(Equal(0)) + for _, i := range ps.OutputToStringArray() { + if i != "" { + containers = append(containers, i) + } + } + return len(containers) +} + +// NumberOfContainers returns an int of how many +// containers are currently defined. +func (p *PodmanTest) NumberOfContainers() int { + var containers []string + ps := p.PodmanBase([]string{"ps", "-aq"}) + ps.WaitWithDefaultTimeout() + Expect(ps.ExitCode()).To(Equal(0)) + for _, i := range ps.OutputToStringArray() { + if i != "" { + containers = append(containers, i) + } + } + return len(containers) +} + +// NumberOfPods returns an int of how many +// pods are currently defined. +func (p *PodmanTest) NumberOfPods() int { + var pods []string + ps := p.PodmanBase([]string{"pod", "ps", "-q"}) + ps.WaitWithDefaultTimeout() + Expect(ps.ExitCode()).To(Equal(0)) + for _, i := range ps.OutputToStringArray() { + if i != "" { + pods = append(pods, i) + } + } + return len(pods) +} + +// GetContainerStatus returns the containers state. +// This function assumes only one container is active. +func (p *PodmanTest) GetContainerStatus() string { + var podmanArgs = []string{"ps"} + podmanArgs = append(podmanArgs, "--all", "--format={{.Status}}") + session := p.PodmanBase(podmanArgs) + session.WaitWithDefaultTimeout() + return session.OutputToString() +} + +// WaitContainerReady waits process or service inside container start, and ready to be used. +func (p *PodmanTest) WaitContainerReady(id string, expStr string, timeout int, step int) bool { + startTime := time.Now() + s := p.PodmanBase([]string{"logs", id}) + s.WaitWithDefaultTimeout() + + for { + if time.Since(startTime) >= time.Duration(timeout)*time.Second { + fmt.Printf("Container %s is not ready in %ds", id, timeout) + return false + } + + if strings.Contains(s.OutputToString(), expStr) { + return true + } + time.Sleep(time.Duration(step) * time.Second) + s = p.PodmanBase([]string{"logs", id}) + s.WaitWithDefaultTimeout() + } +} + +// WaitForContainer is a wrapper function for accept inheritance PodmanTest struct. +func WaitForContainer(p PodmanTestCommon) bool { + return p.WaitForContainer() +} + +// WaitForContainerReady is a wrapper function for accept inheritance PodmanTest struct. +func WaitContainerReady(p PodmanTestCommon, id string, expStr string, timeout int, step int) bool { + return p.WaitContainerReady(id, expStr, timeout, step) +} + +// OutputToString formats session output to string +func (s *PodmanSession) OutputToString() string { + fields := strings.Fields(fmt.Sprintf("%s", s.Out.Contents())) + return strings.Join(fields, " ") +} + +// OutputToStringArray returns the output as a []string +// where each array item is a line split by newline +func (s *PodmanSession) OutputToStringArray() []string { + var results []string + output := fmt.Sprintf("%s", s.Out.Contents()) + for _, line := range strings.Split(output, "\n") { + if line != "" { + results = append(results, line) + } + } + return results +} + +// ErrorToString formats session stderr to string +func (s *PodmanSession) ErrorToString() string { + fields := strings.Fields(fmt.Sprintf("%s", s.Err.Contents())) + return strings.Join(fields, " ") +} + +// ErrorToStringArray returns the stderr output as a []string +// where each array item is a line split by newline +func (s *PodmanSession) ErrorToStringArray() []string { + output := fmt.Sprintf("%s", s.Err.Contents()) + return strings.Split(output, "\n") +} + +// GrepString takes session output and behaves like grep. it returns a bool +// if successful and an array of strings on positive matches +func (s *PodmanSession) GrepString(term string) (bool, []string) { + var ( + greps []string + matches bool + ) + + for _, line := range s.OutputToStringArray() { + if strings.Contains(line, term) { + matches = true + greps = append(greps, line) + } + } + return matches, greps +} + +// ErrorGrepString takes session stderr output and behaves like grep. it returns a bool +// if successful and an array of strings on positive matches +func (s *PodmanSession) ErrorGrepString(term string) (bool, []string) { + var ( + greps []string + matches bool + ) + + for _, line := range s.ErrorToStringArray() { + if strings.Contains(line, term) { + matches = true + greps = append(greps, line) + } + } + return matches, greps +} + +//LineInOutputStartsWith returns true if a line in a +// session output starts with the supplied string +func (s *PodmanSession) LineInOuputStartsWith(term string) bool { + for _, i := range s.OutputToStringArray() { + if strings.HasPrefix(i, term) { + return true + } + } + return false +} + +//LineInOutputContains returns true if a line in a +// session output starts with the supplied string +func (s *PodmanSession) LineInOutputContains(term string) bool { + for _, i := range s.OutputToStringArray() { + if strings.Contains(i, term) { + return true + } + } + return false +} + +//LineInOutputContainsTag returns true if a line in the +// session's output contains the repo-tag pair as returned +// by podman-images(1). +func (s *PodmanSession) LineInOutputContainsTag(repo, tag string) bool { + tagMap := tagOutputToMap(s.OutputToStringArray()) + for r, t := range tagMap { + if repo == r && tag == t { + return true + } + } + return false +} + +// IsJSONOutputValid attempts to unmarshal the session buffer +// and if successful, returns true, else false +func (s *PodmanSession) IsJSONOutputValid() bool { + var i interface{} + if err := json.Unmarshal(s.Out.Contents(), &i); err != nil { + fmt.Println(err) + return false + } + return true +} + +// WaitWithDefaultTimeout waits for process finished with defaultWaitTimeout +func (s *PodmanSession) WaitWithDefaultTimeout() { + s.Wait(defaultWaitTimeout) + fmt.Println("output:", s.OutputToString()) +} + +// CreateTempDirinTempDir create a temp dir with prefix podman_test +func CreateTempDirInTempDir() (string, error) { + return ioutil.TempDir("", "podman_test") +} + +// SystemExec is used to exec a system command to check its exit code or output +func SystemExec(command string, args []string) *PodmanSession { + c := exec.Command(command, args...) + session, err := gexec.Start(c, GinkgoWriter, GinkgoWriter) + if err != nil { + Fail(fmt.Sprintf("unable to run command: %s %s", command, strings.Join(args, " "))) + } + return &PodmanSession{session} +} + +// StringInSlice determines if a string is in a string slice, returns bool +func StringInSlice(s string, sl []string) bool { + for _, i := range sl { + if i == s { + return true + } + } + return false +} + +//tagOutPutToMap parses each string in imagesOutput and returns +// a map of repo:tag pairs. Notice, the first array item will +// be skipped as it's considered to be the header. +func tagOutputToMap(imagesOutput []string) map[string]string { + m := make(map[string]string) + // iterate over output but skip the header + for _, i := range imagesOutput[1:] { + tmp := []string{} + for _, x := range strings.Split(i, " ") { + if x != "" { + tmp = append(tmp, x) + } + } + // podman-images(1) return a list like output + // in the format of "Repository Tag [...]" + if len(tmp) < 2 { + continue + } + m[tmp[0]] = tmp[1] + } + return m +} + +//GetHostDistributionInfo returns a struct with its distribution name and version +func GetHostDistributionInfo() HostOS { + f, err := os.Open(OSReleasePath) + defer f.Close() + if err != nil { + return HostOS{} + } + + l := bufio.NewScanner(f) + host := HostOS{} + host.Arch = runtime.GOARCH + for l.Scan() { + if strings.HasPrefix(l.Text(), "ID=") { + host.Distribution = strings.Replace(strings.TrimSpace(strings.Join(strings.Split(l.Text(), "=")[1:], "")), "\"", "", -1) + } + if strings.HasPrefix(l.Text(), "VERSION_ID=") { + host.Version = strings.Replace(strings.TrimSpace(strings.Join(strings.Split(l.Text(), "=")[1:], "")), "\"", "", -1) + } + } + return host +} + +// IsKernelNewerThan compares the current kernel version to one provided. If +// the kernel is equal to or greater, returns true +func IsKernelNewerThan(version string) (bool, error) { + inputVersion, err := kernel.ParseRelease(version) + if err != nil { + return false, err + } + kv, err := kernel.GetKernelVersion() + if err != nil { + return false, err + } + + // CompareKernelVersion compares two kernel.VersionInfo structs. + // Returns -1 if a < b, 0 if a == b, 1 it a > b + result := kernel.CompareKernelVersion(*kv, *inputVersion) + if result >= 0 { + return true, nil + } + return false, nil + +} + +//IsCommandAvaible check if command exist +func IsCommandAvailable(command string) bool { + check := exec.Command("bash", "-c", strings.Join([]string{"command -v", command}, " ")) + err := check.Run() + if err != nil { + return false + } + return true +} + +// WriteJsonFile write json format data to a json file +func WriteJsonFile(data []byte, filePath string) error { + var jsonData map[string]interface{} + json.Unmarshal(data, &jsonData) + formatJson, _ := json.MarshalIndent(jsonData, "", " ") + return ioutil.WriteFile(filePath, formatJson, 0644) +} + +// Containerized check the podman command run inside container +func Containerized() bool { + container := os.Getenv("container") + if container != "" { + return true + } + b, err := ioutil.ReadFile(ProcessOneCgroupPath) + if err != nil { + // shrug, if we cannot read that file, return false + return false + } + if strings.Index(string(b), "docker") > -1 { + return true + } + return false +} diff --git a/test/utils/utils_suite_test.go b/test/utils/utils_suite_test.go new file mode 100644 index 000000000..b1100892b --- /dev/null +++ b/test/utils/utils_suite_test.go @@ -0,0 +1,52 @@ +package utils_test + +import ( + "fmt" + "io" + "os/exec" + "strings" + "testing" + + . "github.com/containers/libpod/test/utils" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + "github.com/onsi/gomega/gexec" +) + +var FakeOutputs map[string][]string +var GoechoPath = "../goecho/goecho" + +type FakePodmanTest struct { + PodmanTest +} + +func FakePodmanTestCreate() *FakePodmanTest { + FakeOutputs = make(map[string][]string) + p := &FakePodmanTest{ + PodmanTest: PodmanTest{ + PodmanBinary: GoechoPath, + }, + } + + p.PodmanMakeOptions = p.makeOptions + return p +} + +func (p *FakePodmanTest) makeOptions(args []string) []string { + return FakeOutputs[strings.Join(args, " ")] +} + +func StartFakeCmdSession(args []string) *PodmanSession { + var outWriter, errWriter io.Writer + command := exec.Command(GoechoPath, args...) + session, err := gexec.Start(command, outWriter, errWriter) + if err != nil { + fmt.Println(err) + } + return &PodmanSession{session} +} + +func TestUtils(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "Unit test for test utils package") +} diff --git a/troubleshooting.md b/troubleshooting.md index db36d1bb8..574196a69 100644 --- a/troubleshooting.md +++ b/troubleshooting.md @@ -10,7 +10,7 @@ A large number of issues reported against Podman are often found to already be fixed in more current versions of the project. Before reporting an issue, please verify the version you are running with `podman version` and compare it to the lastest release -documented on the top of Podman's [README.md](README.md). +documented on the top of Podman's [README.md](README.md). If they differ, please update your version of PODMAN to the latest possible and retry your command before reporting the issue. @@ -68,4 +68,62 @@ communicate with a registry and not use tls verification. * Turn off tls verification by passing false to the tls-verification option. * I.e. `podman push --tls-verify=false alpine docker://localhost:5000/myalpine:latest` + +--- +### 4) Rootless: could not get runtime - database configuration mismatch + +In Podman release 0.11.1, a default path for rootless containers was changed, +potentially causing rootless Podman to be unable to function. The new default +path is not a problem for new installations, but existing installations will +need to work around it with the following fix. + +#### Symptom + +```console +$ podman info +could not get runtime: database run root /run/user/1000/run does not match our run root /run/user/1000: database configuration mismatch +``` + +#### Solution + +To work around the new default path, we can manually set the path Podman is +expecting in a configuration file. + +First, we need to make a new local configuration file for rootless Podman. +* `mkdir -p ~/.config/containers` +* `cp /usr/share/containers/libpod.conf ~/.config/containers` + +Next, edit the new local configuration file +(`~/.config/containers/libpod.conf`) with your favorite editor. Comment out the +line starting with `cgroup_manager` by adding a `#` character at the beginning +of the line, and change the path in the line starting with `tmp_dir` to point to +the first path in the error message Podman gave (in this case, +`/run/user/1000/run`). + --- +### 4) rootless containers cannot ping hosts + +When using the ping command from a non-root container, the command may +fail because of a lack of privileges. + +#### Symptom + +```console +$ podman run --rm fedora ping -W10 -c1 redhat.com +PING redhat.com (209.132.183.105): 56 data bytes + +--- redhat.com ping statistics --- +1 packets transmitted, 0 packets received, 100% packet loss +``` + +#### Solution + +It is most likely necessary to enable unprivileged pings on the host. +Be sure the UID of the user is part of the range in the +`/proc/sys/net/ipv4/ping_group_range` file. + +To change its value you can use something like: `sysctl -w +"net.ipv4.ping_group_range=0 2000000"`. + +To make the change persistent, you'll need to add a file in +`/etc/sysctl.d` that contains `net.ipv4.ping_group_range=0 $MAX_UID`. diff --git a/vendor.conf b/vendor.conf index 85b784d9b..51907f763 100644 --- a/vendor.conf +++ b/vendor.conf @@ -12,7 +12,7 @@ github.com/containerd/continuity master github.com/containernetworking/cni v0.7.0-alpha1 github.com/containernetworking/plugins 1562a1e60ed101aacc5e08ed9dbeba8e9f3d4ec1 github.com/containers/image bd10b1b53b2976f215b3f2f848fb8e7cad779aeb -github.com/containers/storage 09abf3a26b8a3aa69e29fd7faeb260b98d675759 +github.com/containers/storage ad0f9c4dfa38fcb160f430ff1d653dc3dae03810 github.com/containers/psgo 5dde6da0bc8831b35243a847625bcf18183bd1ee github.com/coreos/go-systemd v14 github.com/cri-o/ocicni 2d2983e40c242322a56c22a903785e7f83eb378c @@ -51,7 +51,7 @@ github.com/opencontainers/image-spec v1.0.0 github.com/opencontainers/runc b4e2ecb452d9ee4381137cc0a7e6715b96bed6de github.com/opencontainers/runtime-spec d810dbc60d8c5aeeb3d054bd1132fab2121968ce github.com/opencontainers/runtime-tools master -github.com/opencontainers/selinux 36a9bc45a08c85f2c52bd9eb32e20267876773bd +github.com/opencontainers/selinux 6ba084dd09db3dfe49a839bab0bbe97fd9274d80 github.com/ostreedev/ostree-go master github.com/pkg/errors v0.8.0 github.com/pmezard/go-difflib 792786c7400a136282c1664665ae0a8db921c6c2 @@ -77,7 +77,7 @@ golang.org/x/sys master golang.org/x/text f72d8390a633d5dfb0cc84043294db9f6c935756 golang.org/x/time f51c12702a4d776e4c1fa9b0fabab841babae631 golang.org/x/sync master -google.golang.org/grpc v1.0.4 https://github.com/grpc/grpc-go< +google.golang.org/grpc v1.0.4 https://github.com/grpc/grpc-go gopkg.in/cheggaaa/pb.v1 v1.0.7 gopkg.in/inf.v0 v0.9.0 gopkg.in/mgo.v2 v2 @@ -92,10 +92,11 @@ k8s.io/kube-openapi 275e2ce91dec4c05a4094a7b1daee5560b555ac9 https://github.com/ k8s.io/utils 258e2a2fa64568210fbd6267cf1d8fd87c3cb86e https://github.com/kubernetes/utils github.com/mrunalp/fileutils master github.com/varlink/go master -github.com/containers/buildah 46c577c87d5a7ab30ef40cfa695cd2b96b32b117 +github.com/containers/buildah 2ac987a52ff8412fb8f2908a191009751a6a1c62 github.com/Nvveen/Gotty master github.com/fsouza/go-dockerclient master github.com/openshift/imagebuilder master github.com/ulikunitz/xz v0.5.4 github.com/mailru/easyjson 03f2033d19d5860aef995fe360ac7d395cd8ce65 github.com/coreos/go-iptables 25d087f3cffd9aedc0c2b7eff25f23cbf3c20fe1 +github.com/google/shlex c34317bd91bf98fab745d77b03933cf8769299fe diff --git a/vendor/github.com/containers/buildah/README.md b/vendor/github.com/containers/buildah/README.md index 6a79e524b..2b539bba8 100644 --- a/vendor/github.com/containers/buildah/README.md +++ b/vendor/github.com/containers/buildah/README.md @@ -107,6 +107,7 @@ $ sudo ./lighttpd.sh | [buildah-images(1)](/docs/buildah-images.md) | List images in local storage. | | [buildah-inspect(1)](/docs/buildah-inspect.md) | Inspects the configuration of a container or image. | | [buildah-mount(1)](/docs/buildah-mount.md) | Mount the working container's root filesystem. | +| [buildah-pull(1)](/docs/buildah-pull.md) | Pull an image from the specified location. | | [buildah-push(1)](/docs/buildah-push.md) | Push an image from local storage to elsewhere. | | [buildah-rename(1)](/docs/buildah-rename.md) | Rename a local container. | | [buildah-rm(1)](/docs/buildah-rm.md) | Removes one or more working containers. | diff --git a/vendor/github.com/containers/buildah/buildah.go b/vendor/github.com/containers/buildah/buildah.go index 9994d6cd0..1a642ed3d 100644 --- a/vendor/github.com/containers/buildah/buildah.go +++ b/vendor/github.com/containers/buildah/buildah.go @@ -224,6 +224,7 @@ func GetBuildInfo(b *Builder) BuilderInfo { ContainerID: b.ContainerID, MountPoint: b.MountPoint, ProcessLabel: b.ProcessLabel, + MountLabel: b.MountLabel, ImageAnnotations: b.ImageAnnotations, ImageCreatedBy: b.ImageCreatedBy, OCIv1: b.OCIv1, diff --git a/vendor/github.com/containers/buildah/chroot/run.go b/vendor/github.com/containers/buildah/chroot/run.go index 51e2d2bd4..6a1400e61 100644 --- a/vendor/github.com/containers/buildah/chroot/run.go +++ b/vendor/github.com/containers/buildah/chroot/run.go @@ -955,6 +955,20 @@ func setRlimits(spec *specs.Spec, onlyLower, onlyRaise bool) error { return nil } +func makeReadOnly(mntpoint string, flags uintptr) error { + var fs unix.Statfs_t + // Make sure it's read-only. + if err := unix.Statfs(mntpoint, &fs); err != nil { + return errors.Wrapf(err, "error checking if directory %q was bound read-only", mntpoint) + } + if fs.Flags&unix.ST_RDONLY == 0 { + if err := unix.Mount(mntpoint, mntpoint, "bind", flags|unix.MS_REMOUNT, ""); err != nil { + return errors.Wrapf(err, "error remounting %s in mount namespace read-only", mntpoint) + } + } + return nil +} + // setupChrootBindMounts actually bind mounts things under the rootfs, and returns a // callback that will clean up its work. func setupChrootBindMounts(spec *specs.Spec, bundlePath string) (undoBinds func() error, err error) { @@ -976,7 +990,7 @@ func setupChrootBindMounts(spec *specs.Spec, bundlePath string) (undoBinds func( bindFlags := commonFlags | unix.MS_NODEV devFlags := commonFlags | unix.MS_NOEXEC | unix.MS_NOSUID | unix.MS_RDONLY procFlags := devFlags | unix.MS_NODEV - sysFlags := devFlags | unix.MS_NODEV | unix.MS_RDONLY + sysFlags := devFlags | unix.MS_NODEV // Bind /dev read-only. subDev := filepath.Join(spec.Root.Path, "/dev") @@ -1030,13 +1044,22 @@ func setupChrootBindMounts(spec *specs.Spec, bundlePath string) (undoBinds func( return undoBinds, errors.Wrapf(err, "error bind mounting /sys from host into mount namespace") } } - // Make sure it's read-only. - if err = unix.Statfs(subSys, &fs); err != nil { - return undoBinds, errors.Wrapf(err, "error checking if directory %q was bound read-only", subSys) + if err := makeReadOnly(subSys, sysFlags); err != nil { + return undoBinds, err } - if fs.Flags&unix.ST_RDONLY == 0 { - if err := unix.Mount(subSys, subSys, "bind", sysFlags|unix.MS_REMOUNT, ""); err != nil { - return undoBinds, errors.Wrapf(err, "error remounting /sys in mount namespace read-only") + + mnts, _ := mount.GetMounts() + for _, m := range mnts { + if !strings.HasPrefix(m.Mountpoint, "/sys/") && + m.Mountpoint != "/sys" { + continue + } + subSys := filepath.Join(spec.Root.Path, m.Mountpoint) + if err := unix.Mount(m.Mountpoint, subSys, "bind", sysFlags, ""); err != nil { + return undoBinds, errors.Wrapf(err, "error bind mounting /sys from host into mount namespace") + } + if err := makeReadOnly(subSys, sysFlags); err != nil { + return undoBinds, err } } logrus.Debugf("bind mounted %q to %q", "/sys", filepath.Join(spec.Root.Path, "/sys")) @@ -1044,10 +1067,6 @@ func setupChrootBindMounts(spec *specs.Spec, bundlePath string) (undoBinds func( // Add /sys/fs/selinux to the set of masked paths, to ensure that we don't have processes // attempting to interact with labeling, when they aren't allowed to do so. spec.Linux.MaskedPaths = append(spec.Linux.MaskedPaths, "/sys/fs/selinux") - // Add /sys/fs/cgroup to the set of masked paths, to ensure that we don't have processes - // attempting to mess with cgroup configuration, when they aren't allowed to do so. - spec.Linux.MaskedPaths = append(spec.Linux.MaskedPaths, "/sys/fs/cgroup") - // Bind mount in everything we've been asked to mount. for _, m := range spec.Mounts { // Skip anything that we just mounted. @@ -1143,11 +1162,11 @@ func setupChrootBindMounts(spec *specs.Spec, bundlePath string) (undoBinds func( logrus.Debugf("mounted a tmpfs to %q", target) } if err = unix.Statfs(target, &fs); err != nil { - return undoBinds, errors.Wrapf(err, "error checking if directory %q was bound read-only", subSys) + return undoBinds, errors.Wrapf(err, "error checking if directory %q was bound read-only", target) } if uintptr(fs.Flags)&expectedFlags != expectedFlags { if err := unix.Mount(target, target, "bind", requestFlags|unix.MS_REMOUNT, ""); err != nil { - return undoBinds, errors.Wrapf(err, "error remounting %q in mount namespace with expected flags") + return undoBinds, errors.Wrapf(err, "error remounting %q in mount namespace with expected flags", target) } } } diff --git a/vendor/github.com/containers/buildah/common.go b/vendor/github.com/containers/buildah/common.go index 56a901925..be59215df 100644 --- a/vendor/github.com/containers/buildah/common.go +++ b/vendor/github.com/containers/buildah/common.go @@ -2,12 +2,14 @@ package buildah import ( "io" - - "github.com/sirupsen/logrus" + "os" + "path/filepath" cp "github.com/containers/image/copy" "github.com/containers/image/transports" "github.com/containers/image/types" + "github.com/containers/libpod/pkg/rootless" + "github.com/sirupsen/logrus" ) const ( @@ -17,10 +19,20 @@ const ( DOCKER = "docker" ) +// userRegistriesFile is the path to the per user registry configuration file. +var userRegistriesFile = filepath.Join(os.Getenv("HOME"), ".config/containers/registries.conf") + func getCopyOptions(reportWriter io.Writer, sourceReference types.ImageReference, sourceSystemContext *types.SystemContext, destinationReference types.ImageReference, destinationSystemContext *types.SystemContext, manifestType string) *cp.Options { sourceCtx := &types.SystemContext{} if sourceSystemContext != nil { *sourceCtx = *sourceSystemContext + } else { + if rootless.IsRootless() { + if _, err := os.Stat(userRegistriesFile); err == nil { + sourceCtx.SystemRegistriesConfPath = userRegistriesFile + } + + } } sourceInsecure, err := isReferenceInsecure(sourceReference, sourceCtx) if err != nil { @@ -33,6 +45,12 @@ func getCopyOptions(reportWriter io.Writer, sourceReference types.ImageReference destinationCtx := &types.SystemContext{} if destinationSystemContext != nil { *destinationCtx = *destinationSystemContext + } else { + if rootless.IsRootless() { + if _, err := os.Stat(userRegistriesFile); err == nil { + destinationCtx.SystemRegistriesConfPath = userRegistriesFile + } + } } destinationInsecure, err := isReferenceInsecure(destinationReference, destinationCtx) if err != nil { @@ -58,5 +76,11 @@ func getSystemContext(defaults *types.SystemContext, signaturePolicyPath string) if signaturePolicyPath != "" { sc.SignaturePolicyPath = signaturePolicyPath } + if sc.SystemRegistriesConfPath == "" && rootless.IsRootless() { + if _, err := os.Stat(userRegistriesFile); err == nil { + sc.SystemRegistriesConfPath = userRegistriesFile + } + + } return sc } diff --git a/vendor/github.com/containers/buildah/delete.go b/vendor/github.com/containers/buildah/delete.go index 25f76cf74..e3bddba20 100644 --- a/vendor/github.com/containers/buildah/delete.go +++ b/vendor/github.com/containers/buildah/delete.go @@ -1,7 +1,6 @@ package buildah import ( - "github.com/opencontainers/selinux/go-selinux/label" "github.com/pkg/errors" ) @@ -14,5 +13,5 @@ func (b *Builder) Delete() error { b.MountPoint = "" b.Container = "" b.ContainerID = "" - return label.ReleaseLabel(b.ProcessLabel) + return nil } diff --git a/vendor/github.com/containers/buildah/image.go b/vendor/github.com/containers/buildah/image.go index 31aff9eea..c0bf90ddd 100644 --- a/vendor/github.com/containers/buildah/image.go +++ b/vendor/github.com/containers/buildah/image.go @@ -107,7 +107,6 @@ func expectedDockerDiffIDs(image docker.V2Image) int { // compression that we'll be applying. func (i *containerImageRef) computeLayerMIMEType(what string) (omediaType, dmediaType string, err error) { omediaType = v1.MediaTypeImageLayer - //TODO: Convert to manifest.DockerV2Schema2LayerUncompressedMediaType once available dmediaType = docker.V2S2MediaTypeUncompressedLayer if i.compression != archive.Uncompressed { switch i.compression { diff --git a/vendor/github.com/containers/buildah/imagebuildah/build.go b/vendor/github.com/containers/buildah/imagebuildah/build.go index 41d85cbc6..701241683 100644 --- a/vendor/github.com/containers/buildah/imagebuildah/build.go +++ b/vendor/github.com/containers/buildah/imagebuildah/build.go @@ -222,7 +222,7 @@ type Executor struct { forceRmIntermediateCtrs bool containerIDs []string // Stores the IDs of the successful intermediate containers used during layer build imageMap map[string]string // Used to map images that we create to handle the AS construct. - + copyFrom string // Used to keep track of the --from flag from COPY and ADD } // withName creates a new child executor that will be used whenever a COPY statement uses --from=NAME. @@ -563,39 +563,39 @@ func NewExecutor(store storage.Store, options BuildOptions) (*Executor, error) { registry: options.Registry, transport: options.Transport, ignoreUnrecognizedInstructions: options.IgnoreUnrecognizedInstructions, - quiet: options.Quiet, - runtime: options.Runtime, - runtimeArgs: options.RuntimeArgs, - transientMounts: options.TransientMounts, - compression: options.Compression, - output: options.Output, - outputFormat: options.OutputFormat, - additionalTags: options.AdditionalTags, - signaturePolicyPath: options.SignaturePolicyPath, - systemContext: options.SystemContext, - volumeCache: make(map[string]string), - volumeCacheInfo: make(map[string]os.FileInfo), - log: options.Log, - in: options.In, - out: options.Out, - err: options.Err, - reportWriter: options.ReportWriter, - isolation: options.Isolation, - namespaceOptions: options.NamespaceOptions, - configureNetwork: options.ConfigureNetwork, - cniPluginPath: options.CNIPluginPath, - cniConfigDir: options.CNIConfigDir, - idmappingOptions: options.IDMappingOptions, - commonBuildOptions: options.CommonBuildOpts, - defaultMountsFilePath: options.DefaultMountsFilePath, - iidfile: options.IIDFile, - squash: options.Squash, - labels: append([]string{}, options.Labels...), - annotations: append([]string{}, options.Annotations...), - layers: options.Layers, - noCache: options.NoCache, - removeIntermediateCtrs: options.RemoveIntermediateCtrs, - forceRmIntermediateCtrs: options.ForceRmIntermediateCtrs, + quiet: options.Quiet, + runtime: options.Runtime, + runtimeArgs: options.RuntimeArgs, + transientMounts: options.TransientMounts, + compression: options.Compression, + output: options.Output, + outputFormat: options.OutputFormat, + additionalTags: options.AdditionalTags, + signaturePolicyPath: options.SignaturePolicyPath, + systemContext: options.SystemContext, + volumeCache: make(map[string]string), + volumeCacheInfo: make(map[string]os.FileInfo), + log: options.Log, + in: options.In, + out: options.Out, + err: options.Err, + reportWriter: options.ReportWriter, + isolation: options.Isolation, + namespaceOptions: options.NamespaceOptions, + configureNetwork: options.ConfigureNetwork, + cniPluginPath: options.CNIPluginPath, + cniConfigDir: options.CNIConfigDir, + idmappingOptions: options.IDMappingOptions, + commonBuildOptions: options.CommonBuildOpts, + defaultMountsFilePath: options.DefaultMountsFilePath, + iidfile: options.IIDFile, + squash: options.Squash, + labels: append([]string{}, options.Labels...), + annotations: append([]string{}, options.Annotations...), + layers: options.Layers, + noCache: options.NoCache, + removeIntermediateCtrs: options.RemoveIntermediateCtrs, + forceRmIntermediateCtrs: options.ForceRmIntermediateCtrs, } if exec.err == nil { exec.err = os.Stderr @@ -764,7 +764,7 @@ func (b *Executor) resolveNameToImageRef() (types.ImageReference, error) { if err != nil { candidates, _, err := util.ResolveName(b.output, "", b.systemContext, b.store) if err != nil { - return nil, errors.Wrapf(err, "error parsing target image name %q: %v", b.output) + return nil, errors.Wrapf(err, "error parsing target image name %q", b.output) } if len(candidates) == 0 { return nil, errors.Errorf("error parsing target image name %q", b.output) @@ -826,6 +826,18 @@ func (b *Executor) Execute(ctx context.Context, stage imagebuilder.Stage) error err error imgID string ) + + b.copyFrom = "" + // Check if --from exists in the step command of COPY or ADD + // If it exists, set b.copyfrom to that value + for _, n := range step.Flags { + if strings.Contains(n, "--from") && (step.Command == "copy" || step.Command == "add") { + arr := strings.Split(n, "=") + b.copyFrom = b.named[arr[1]].mountPoint + break + } + } + // checkForLayers will be true if b.layers is true and a cached intermediate image is found. // checkForLayers is set to false when either there is no cached image or a break occurs where // the instructions in the Dockerfile change from a previous build. @@ -848,6 +860,7 @@ func (b *Executor) Execute(ctx context.Context, stage imagebuilder.Stage) error if err := b.copyExistingImage(ctx, cacheID); err != nil { return err } + b.containerIDs = append(b.containerIDs, b.builder.ContainerID) break } @@ -1009,6 +1022,11 @@ func (b *Executor) getFilesToCopy(node *parser.Node) ([]string, error) { currNode = currNode.Next continue } + if b.copyFrom != "" { + src = append(src, filepath.Join(b.copyFrom, currNode.Value)) + currNode = currNode.Next + continue + } matches, err := filepath.Glob(filepath.Join(b.contextDir, currNode.Value)) if err != nil { return nil, errors.Wrapf(err, "error finding match for pattern %q", currNode.Value) @@ -1044,17 +1062,19 @@ func (b *Executor) copiedFilesMatch(node *parser.Node, historyTime *time.Time) ( } continue } - // For local files, walk the file tree and check the time stamps. - timeIsGreater := false - err := filepath.Walk(item, func(path string, info os.FileInfo, err error) error { - if info.ModTime().After(*historyTime) { - timeIsGreater = true - return nil - } - return nil - }) + // Walks the file tree for local files and uses chroot to ensure we don't escape out of the allowed path + // when resolving any symlinks. + // Change the time format to ensure we don't run into a parsing error when converting again from string + // to time.Time. It is a known Go issue that the conversions cause errors sometimes, so specifying a particular + // time format here when converting to a string. + // If the COPY has --from in the command, change the rootdir to mountpoint of the container it is copying from + rootdir := b.contextDir + if b.copyFrom != "" { + rootdir = b.copyFrom + } + timeIsGreater, err := resolveModifiedTime(rootdir, item, historyTime.Format(time.RFC3339Nano)) if err != nil { - return false, errors.Wrapf(err, "error walking file tree %q", item) + return false, errors.Wrapf(err, "error resolving symlinks and comparing modified times: %q", item) } if timeIsGreater { return false, nil @@ -1289,15 +1309,24 @@ func BuildDockerfiles(ctx context.Context, store storage.Store, options BuildOpt } else { // If the Dockerfile isn't found try prepending the // context directory to it. - if _, err := os.Stat(dfile); os.IsNotExist(err) { + dinfo, err := os.Stat(dfile) + if os.IsNotExist(err) { dfile = filepath.Join(options.ContextDirectory, dfile) } + dinfo, err = os.Stat(dfile) + if err != nil { + return "", nil, errors.Wrapf(err, "error reading info about %q", dfile) + } + // If given a directory, add '/Dockerfile' to it. + if dinfo.Mode().IsDir() { + dfile = filepath.Join(dfile, "Dockerfile") + } logrus.Debugf("reading local Dockerfile %q", dfile) contents, err := os.Open(dfile) if err != nil { return "", nil, errors.Wrapf(err, "error reading %q", dfile) } - dinfo, err := contents.Stat() + dinfo, err = contents.Stat() if err != nil { contents.Close() return "", nil, errors.Wrapf(err, "error reading info about %q", dfile) @@ -1336,7 +1365,10 @@ func BuildDockerfiles(ctx context.Context, store storage.Store, options BuildOpt return "", nil, errors.Wrapf(err, "error creating build executor") } b := imagebuilder.NewBuilder(options.Args) - stages := imagebuilder.NewStages(mainNode, b) + stages, err := imagebuilder.NewStages(mainNode, b) + if err != nil { + return "", nil, errors.Wrap(err, "error reading multiple stages") + } return exec.Build(ctx, stages) } diff --git a/vendor/github.com/containers/buildah/imagebuildah/chroot_symlink.go b/vendor/github.com/containers/buildah/imagebuildah/chroot_symlink.go index 20e396f1f..edb5837db 100644 --- a/vendor/github.com/containers/buildah/imagebuildah/chroot_symlink.go +++ b/vendor/github.com/containers/buildah/imagebuildah/chroot_symlink.go @@ -6,6 +6,7 @@ import ( "os" "path/filepath" "strings" + "time" "github.com/containers/storage/pkg/reexec" "github.com/pkg/errors" @@ -14,13 +15,18 @@ import ( const ( symlinkChrootedCommand = "chrootsymlinks-resolve" + symlinkModifiedTime = "modtimesymlinks-resolve" maxSymlinksResolved = 40 ) func init() { reexec.Register(symlinkChrootedCommand, resolveChrootedSymlinks) + reexec.Register(symlinkModifiedTime, resolveSymlinkTimeModified) } +// main() for grandparent subprocess. Its main job is to shuttle stdio back +// and forth, managing a pseudo-terminal if we want one, for our child, the +// parent subprocess. func resolveChrootedSymlinks() { status := 0 flag.Parse() @@ -39,7 +45,7 @@ func resolveChrootedSymlinks() { } // Our second parameter is the path name to evaluate for symbolic links - symLink, err := getSymbolicLink(flag.Arg(0), flag.Arg(1)) + symLink, err := getSymbolicLink(flag.Arg(1)) if err != nil { fmt.Fprintf(os.Stderr, "error getting symbolic links: %v\n", err) os.Exit(1) @@ -51,7 +57,8 @@ func resolveChrootedSymlinks() { os.Exit(status) } -// ResolveSymlink resolves any symlink in filename in the context of rootdir. +// ResolveSymLink (in the grandparent process) resolves any symlink in filename +// in the context of rootdir. func ResolveSymLink(rootdir, filename string) (string, error) { // The child process expects a chroot and one path that // will be consulted relative to the chroot directory and evaluated @@ -62,32 +69,128 @@ func ResolveSymLink(rootdir, filename string) (string, error) { return "", errors.Wrapf(err, string(output)) } - // Hand back the resolved symlink, will be "" if a symlink is not found + // Hand back the resolved symlink, will be filename if a symlink is not found return string(output), nil } +// main() for grandparent subprocess. Its main job is to shuttle stdio back +// and forth, managing a pseudo-terminal if we want one, for our child, the +// parent subprocess. +func resolveSymlinkTimeModified() { + status := 0 + flag.Parse() + if len(flag.Args()) < 1 { + os.Exit(1) + } + // Our first parameter is the directory to chroot into. + if err := unix.Chdir(flag.Arg(0)); err != nil { + fmt.Fprintf(os.Stderr, "chdir(): %v\n", err) + os.Exit(1) + } + if err := unix.Chroot(flag.Arg(0)); err != nil { + fmt.Fprintf(os.Stderr, "chroot(): %v\n", err) + os.Exit(1) + } + + // Our second parameter is the path name to evaluate for symbolic links. + // Our third parameter is the time the cached intermediate image was created. + // We check whether the modified time of the filepath we provide is after the time the cached image was created. + timeIsGreater, err := modTimeIsGreater(flag.Arg(0), flag.Arg(1), flag.Arg(2)) + if err != nil { + fmt.Fprintf(os.Stderr, "error checking if modified time of resolved symbolic link is greater: %v\n", err) + os.Exit(1) + } + if _, err := os.Stdout.WriteString(fmt.Sprintf("%v", timeIsGreater)); err != nil { + fmt.Fprintf(os.Stderr, "error writing string to stdout: %v\n", err) + os.Exit(1) + } + os.Exit(status) +} + +// resolveModifiedTime (in the grandparent process) checks filename for any symlinks, +// resolves it and compares the modified time of the file with historyTime, which is +// the creation time of the cached image. It returns true if filename was modified after +// historyTime, otherwise returns false. +func resolveModifiedTime(rootdir, filename, historyTime string) (bool, error) { + // The child process expects a chroot and one path that + // will be consulted relative to the chroot directory and evaluated + // for any symbolic links present. + cmd := reexec.Command(symlinkModifiedTime, rootdir, filename, historyTime) + output, err := cmd.CombinedOutput() + if err != nil { + return false, errors.Wrapf(err, string(output)) + } + // Hand back true/false depending on in the file was modified after the caches image was created. + return string(output) == "true", nil +} + +// modTimeIsGreater goes through the files added/copied in using the Dockerfile and +// checks the time stamp (follows symlinks) with the time stamp of when the cached +// image was created. IT compares the two and returns true if the file was modified +// after the cached image was created, otherwise it returns false. +func modTimeIsGreater(rootdir, path string, historyTime string) (bool, error) { + var timeIsGreater bool + + // Convert historyTime from string to time.Time for comparison + histTime, err := time.Parse(time.RFC3339Nano, historyTime) + if err != nil { + return false, errors.Wrapf(err, "error converting string to time.Time %q", historyTime) + } + // Walk the file tree and check the time stamps. + // Since we are chroot in rootdir, only want the path of the actual filename, i.e path - rootdir. + // +1 to account for the extra "/" (e.g rootdir=/home/user/mydir, path=/home/user/mydir/myfile.json) + err = filepath.Walk(path[len(rootdir)+1:], func(path string, info os.FileInfo, err error) error { + // If using cached images, it is possible for files that are being copied to come from + // previous build stages. But if using cached images, then the copied file won't exist + // since a container won't have been created for the previous build stage and info will be nil. + // In that case just return nil and continue on with using the cached image for the whole build process. + if info == nil { + return nil + } + modTime := info.ModTime() + if info.Mode()&os.ModeSymlink == os.ModeSymlink { + // Evaluate any symlink that occurs to get updated modified information + resolvedPath, err := filepath.EvalSymlinks(path) + if err != nil { + return errors.Wrapf(err, "error evaluating symlink %q", path) + } + fileInfo, err := os.Stat(resolvedPath) + if err != nil { + return errors.Wrapf(err, "error getting file info %q", resolvedPath) + } + modTime = fileInfo.ModTime() + } + if modTime.After(histTime) { + timeIsGreater = true + return nil + } + return nil + }) + if err != nil { + return false, errors.Wrapf(err, "error walking file tree %q", path) + } + return timeIsGreater, err +} + // getSymbolic link goes through each part of the path and continues resolving symlinks as they appear. // Returns what the whole target path for what "path" resolves to. -func getSymbolicLink(rootdir, path string) (string, error) { +func getSymbolicLink(path string) (string, error) { var ( symPath string symLinksResolved int ) - - // Splitting path as we need to resolve each parth of the path at a time + // Splitting path as we need to resolve each part of the path at a time splitPath := strings.Split(path, "/") if splitPath[0] == "" { splitPath = splitPath[1:] symPath = "/" } - for _, p := range splitPath { // If we have resolved 40 symlinks, that means something is terribly wrong // will return an error and exit if symLinksResolved >= maxSymlinksResolved { return "", errors.Errorf("have resolved %q symlinks, something is terribly wrong!", maxSymlinksResolved) } - symPath = filepath.Join(symPath, p) isSymlink, resolvedPath, err := hasSymlink(symPath) if err != nil { @@ -119,16 +222,21 @@ func getSymbolicLink(rootdir, path string) (string, error) { // otherwise it returns false and path func hasSymlink(path string) (bool, string, error) { info, err := os.Lstat(path) - if os.IsNotExist(err) { - if err = os.MkdirAll(path, 0755); err != nil { - return false, "", errors.Wrapf(err, "error ensuring volume path %q exists", path) - } - info, err = os.Lstat(path) - if err != nil { - return false, "", errors.Wrapf(err, "error running lstat on %q", path) + if err != nil { + if os.IsNotExist(err) { + if err = os.MkdirAll(path, 0755); err != nil { + return false, "", errors.Wrapf(err, "error ensuring volume path %q exists", path) + } + info, err = os.Lstat(path) + if err != nil { + return false, "", errors.Wrapf(err, "error running lstat on %q", path) + } + } else { + return false, path, errors.Wrapf(err, "error get stat of path %q", path) } } - // Return false and path as path is not a symlink + + // Return false and path as path if not a symlink if info.Mode()&os.ModeSymlink != os.ModeSymlink { return false, path, nil } diff --git a/vendor/github.com/containers/buildah/imagebuildah/util.go b/vendor/github.com/containers/buildah/imagebuildah/util.go index 35dc5438a..4f5301b73 100644 --- a/vendor/github.com/containers/buildah/imagebuildah/util.go +++ b/vendor/github.com/containers/buildah/imagebuildah/util.go @@ -111,3 +111,28 @@ func TempDirForURL(dir, prefix, url string) (name string, subdir string, err err func InitReexec() bool { return buildah.InitReexec() } + +// ReposToMap parses the specified repotags and returns a map with repositories +// as keys and the corresponding arrays of tags as values. +func ReposToMap(repotags []string) map[string][]string { + // map format is repo -> tag + repos := make(map[string][]string) + for _, repo := range repotags { + var repository, tag string + if strings.Contains(repo, ":") { + li := strings.LastIndex(repo, ":") + repository = repo[0:li] + tag = repo[li+1:] + } else if len(repo) > 0 { + repository = repo + tag = "<none>" + } else { + logrus.Warnf("Found image with empty name") + } + repos[repository] = append(repos[repository], tag) + } + if len(repos) == 0 { + repos["<none>"] = []string{"<none>"} + } + return repos +} diff --git a/vendor/github.com/containers/buildah/new.go b/vendor/github.com/containers/buildah/new.go index 8b0e774ba..13bebf420 100644 --- a/vendor/github.com/containers/buildah/new.go +++ b/vendor/github.com/containers/buildah/new.go @@ -3,6 +3,7 @@ package buildah import ( "context" "fmt" + "math/rand" "strings" "github.com/containers/buildah/util" @@ -12,7 +13,6 @@ import ( "github.com/containers/image/transports/alltransports" "github.com/containers/image/types" "github.com/containers/storage" - "github.com/opencontainers/selinux/go-selinux/label" "github.com/openshift/imagebuilder" "github.com/pkg/errors" "github.com/sirupsen/logrus" @@ -23,11 +23,6 @@ const ( // as "no image". BaseImageFakeName = imagebuilder.NoBaseImageSpecifier - // DefaultTransport is a prefix that we apply to an image name if we - // can't find one in the local Store, in order to generate a source - // reference for the image that we can then copy to the local Store. - DefaultTransport = "docker://" - // minimumTruncatedIDLength is the minimum length of an identifier that // we'll accept as possibly being a truncated image ID. minimumTruncatedIDLength = 3 @@ -150,7 +145,7 @@ func resolveImage(ctx context.Context, systemContext *types.SystemContext, store } logrus.Debugf("error parsing image name %q as given, trying with transport %q: %v", image, options.Transport, err) transport := options.Transport - if transport != DefaultTransport { + if transport != util.DefaultTransport { transport = transport + ":" } srcRef2, err := alltransports.ParseImageName(transport + image) @@ -232,6 +227,27 @@ func resolveImage(ctx context.Context, systemContext *types.SystemContext, store } } +func containerNameExist(name string, containers []storage.Container) bool { + for _, container := range containers { + for _, cname := range container.Names { + if cname == name { + return true + } + } + } + return false +} + +func findUnusedContainer(name string, containers []storage.Container) string { + suffix := 1 + tmpName := name + for containerNameExist(tmpName, containers) { + tmpName = fmt.Sprintf("%s-%d", name, suffix) + suffix++ + } + return tmpName +} + func newBuilder(ctx context.Context, store storage.Store, options BuilderOptions) (*Builder, error) { var ref types.ImageReference var img *storage.Image @@ -241,7 +257,7 @@ func newBuilder(ctx context.Context, store storage.Store, options BuilderOptions options.FromImage = "" } if options.Transport == "" { - options.Transport = DefaultTransport + options.Transport = util.DefaultTransport } systemContext := getSystemContext(options.SystemContext, options.SignaturePolicyPath) @@ -277,23 +293,33 @@ func newBuilder(ctx context.Context, store storage.Store, options BuilderOptions name = imageNamePrefix(image) + "-" + name } } + var container *storage.Container + tmpName := name + if options.Container == "" { + containers, err := store.Containers() + if err != nil { + return nil, errors.Wrapf(err, "unable to check for container names") + } + tmpName = findUnusedContainer(tmpName, containers) + } - coptions := storage.ContainerOptions{} - coptions.IDMappingOptions = newContainerIDMappingOptions(options.IDMappingOptions) - - container, err := store.CreateContainer("", []string{name}, imageID, "", "", &coptions) - suffix := 1 - for err != nil && errors.Cause(err) == storage.ErrDuplicateName && options.Container == "" { - suffix++ - tmpName := fmt.Sprintf("%s-%d", name, suffix) - if container, err = store.CreateContainer("", []string{tmpName}, imageID, "", "", &coptions); err == nil { + conflict := 100 + for true { + coptions := storage.ContainerOptions{ + LabelOpts: options.CommonBuildOpts.LabelOpts, + IDMappingOptions: newContainerIDMappingOptions(options.IDMappingOptions), + } + container, err = store.CreateContainer("", []string{tmpName}, imageID, "", "", &coptions) + if err == nil { name = tmpName + break } + if errors.Cause(err) != storage.ErrDuplicateName || options.Container != "" { + return nil, errors.Wrapf(err, "error creating container") + } + tmpName = fmt.Sprintf("%s-%d", name, rand.Int()%conflict) + conflict = conflict * 10 } - if err != nil { - return nil, errors.Wrapf(err, "error creating container") - } - defer func() { if err != nil { if err2 := store.DeleteContainer(container.ID); err2 != nil { @@ -302,13 +328,6 @@ func newBuilder(ctx context.Context, store storage.Store, options BuilderOptions } }() - if err = ReserveSELinuxLabels(store, container.ID); err != nil { - return nil, err - } - processLabel, mountLabel, err := label.InitLabels(options.CommonBuildOpts.LabelOpts) - if err != nil { - return nil, err - } uidmap, gidmap := convertStorageIDMaps(container.UIDMap, container.GIDMap) defaultNamespaceOptions, err := DefaultNamespaceOptions() @@ -328,8 +347,8 @@ func newBuilder(ctx context.Context, store storage.Store, options BuilderOptions ContainerID: container.ID, ImageAnnotations: map[string]string{}, ImageCreatedBy: "", - ProcessLabel: processLabel, - MountLabel: mountLabel, + ProcessLabel: container.ProcessLabel(), + MountLabel: container.MountLabel(), DefaultMountsFilePath: options.DefaultMountsFilePath, Isolation: options.Isolation, NamespaceOptions: namespaceOptions, @@ -351,7 +370,7 @@ func newBuilder(ctx context.Context, store storage.Store, options BuilderOptions } if options.Mount { - _, err = builder.Mount(mountLabel) + _, err = builder.Mount(container.MountLabel()) if err != nil { return nil, errors.Wrapf(err, "error mounting build container %q", builder.ContainerID) } diff --git a/vendor/github.com/containers/buildah/pkg/cli/common.go b/vendor/github.com/containers/buildah/pkg/cli/common.go index b54663f5d..03b340294 100644 --- a/vendor/github.com/containers/buildah/pkg/cli/common.go +++ b/vendor/github.com/containers/buildah/pkg/cli/common.go @@ -71,6 +71,10 @@ var ( LayerFlags = []cli.Flag{ cli.BoolFlag{ + Name: "force-rm", + Usage: "Always remove intermediate containers after a build, even if the build is unsuccessful.", + }, + cli.BoolFlag{ Name: "layers", Usage: fmt.Sprintf("cache intermediate layers during build. Use BUILDAH_LAYERS environment variable to override. (default %t)", UseLayers()), }, @@ -115,10 +119,6 @@ var ( Name: "file, f", Usage: "`pathname or URL` of a Dockerfile", }, - cli.BoolFlag{ - Name: "force-rm", - Usage: "Always remove intermediate containers after a build, even if the build is unsuccessful.", - }, cli.StringFlag{ Name: "format", Usage: "`format` of the built image's manifest and metadata. Use BUILDAH_FORMAT environment variable to override.", diff --git a/vendor/github.com/containers/buildah/pull.go b/vendor/github.com/containers/buildah/pull.go index 52269541a..1a51edb0e 100644 --- a/vendor/github.com/containers/buildah/pull.go +++ b/vendor/github.com/containers/buildah/pull.go @@ -146,11 +146,11 @@ func pullImage(ctx context.Context, store storage.Store, imageName string, optio srcRef, err := alltransports.ParseImageName(spec) if err != nil { if options.Transport == "" { - options.Transport = DefaultTransport + options.Transport = util.DefaultTransport } logrus.Debugf("error parsing image name %q, trying with transport %q: %v", spec, options.Transport, err) transport := options.Transport - if transport != DefaultTransport { + if transport != util.DefaultTransport { transport = transport + ":" } spec = transport + spec @@ -201,6 +201,7 @@ func pullImage(ctx context.Context, store storage.Store, imageName string, optio logrus.Debugf("copying %q to %q", spec, destName) if _, err := cp.Image(ctx, policyContext, destRef, srcRef, getCopyOptions(options.ReportWriter, srcRef, sc, destRef, nil, "")); err != nil { + logrus.Debugf("error copying src image [%q] to dest image [%q] err: %v", spec, destName, err) return nil, err } return destRef, nil diff --git a/vendor/github.com/containers/buildah/run.go b/vendor/github.com/containers/buildah/run.go index 718ef4e36..5d2cd6a32 100644 --- a/vendor/github.com/containers/buildah/run.go +++ b/vendor/github.com/containers/buildah/run.go @@ -451,7 +451,7 @@ func (b *Builder) setupMounts(mountPoint string, spec *specs.Spec, bundlePath st // Add temporary copies of the contents of volume locations at the // volume locations, unless we already have something there. copyWithTar := b.copyWithTar(nil, nil) - builtins, err := runSetupBuiltinVolumes(b.MountLabel, mountPoint, cdir, copyWithTar, builtinVolumes) + builtins, err := runSetupBuiltinVolumes(b.MountLabel, mountPoint, cdir, copyWithTar, builtinVolumes, int(rootUID), int(rootGID)) if err != nil { return err } @@ -493,15 +493,21 @@ func runSetupBoundFiles(bundlePath string, bindFiles map[string]string) (mounts return mounts, nil } -func runSetupBuiltinVolumes(mountLabel, mountPoint, containerDir string, copyWithTar func(srcPath, dstPath string) error, builtinVolumes []string) ([]specs.Mount, error) { +func runSetupBuiltinVolumes(mountLabel, mountPoint, containerDir string, copyWithTar func(srcPath, dstPath string) error, builtinVolumes []string, rootUID, rootGID int) ([]specs.Mount, error) { var mounts []specs.Mount + hostOwner := idtools.IDPair{UID: rootUID, GID: rootGID} // Add temporary copies of the contents of volume locations at the // volume locations, unless we already have something there. for _, volume := range builtinVolumes { subdir := digest.Canonical.FromString(volume).Hex() volumePath := filepath.Join(containerDir, "buildah-volumes", subdir) + srcPath := filepath.Join(mountPoint, volume) + initializeVolume := false // If we need to, initialize the volume path's initial contents. - if _, err := os.Stat(volumePath); err != nil && os.IsNotExist(err) { + if _, err := os.Stat(volumePath); err != nil { + if !os.IsNotExist(err) { + return nil, errors.Wrapf(err, "failed to stat %q for volume %q", volumePath, volume) + } logrus.Debugf("setting up built-in volume at %q", volumePath) if err = os.MkdirAll(volumePath, 0755); err != nil { return nil, errors.Wrapf(err, "error creating directory %q for volume %q", volumePath, volume) @@ -509,11 +515,21 @@ func runSetupBuiltinVolumes(mountLabel, mountPoint, containerDir string, copyWit if err = label.Relabel(volumePath, mountLabel, false); err != nil { return nil, errors.Wrapf(err, "error relabeling directory %q for volume %q", volumePath, volume) } - srcPath := filepath.Join(mountPoint, volume) - stat, err := os.Stat(srcPath) - if err != nil { + initializeVolume = true + } + stat, err := os.Stat(srcPath) + if err != nil { + if !os.IsNotExist(err) { return nil, errors.Wrapf(err, "failed to stat %q for volume %q", srcPath, volume) } + if err = idtools.MkdirAllAndChownNew(srcPath, 0755, hostOwner); err != nil { + return nil, errors.Wrapf(err, "error creating directory %q for volume %q", srcPath, volume) + } + if stat, err = os.Stat(srcPath); err != nil { + return nil, errors.Wrapf(err, "failed to stat %q for volume %q", srcPath, volume) + } + } + if initializeVolume { if err = os.Chmod(volumePath, stat.Mode().Perm()); err != nil { return nil, errors.Wrapf(err, "failed to chmod %q for volume %q", volumePath, volume) } @@ -1044,24 +1060,31 @@ func (b *Builder) Run(command []string, options RunOptions) error { } rootIDPair := &idtools.IDPair{UID: int(rootUID), GID: int(rootGID)} - hostFile, err := b.addNetworkConfig(path, "/etc/hosts", rootIDPair) - if err != nil { - return err - } - resolvFile, err := b.addNetworkConfig(path, "/etc/resolv.conf", rootIDPair) - if err != nil { - return err - } + bindFiles := make(map[string]string) + namespaceOptions := append(b.NamespaceOptions, options.NamespaceOptions...) + volumes := b.Volumes() - if err := addHostsToFile(b.CommonBuildOpts.AddHost, hostFile); err != nil { - return err + if !contains(volumes, "/etc/hosts") { + hostFile, err := b.addNetworkConfig(path, "/etc/hosts", rootIDPair) + if err != nil { + return err + } + bindFiles["/etc/hosts"] = hostFile + + if err := addHostsToFile(b.CommonBuildOpts.AddHost, hostFile); err != nil { + return err + } } - bindFiles := map[string]string{ - "/etc/hosts": hostFile, - "/etc/resolv.conf": resolvFile, + if !contains(volumes, "/etc/resolv.conf") { + resolvFile, err := b.addNetworkConfig(path, "/etc/resolv.conf", rootIDPair) + if err != nil { + return err + } + bindFiles["/etc/resolv.conf"] = resolvFile } - err = b.setupMounts(mountPoint, spec, path, options.Mounts, bindFiles, b.Volumes(), b.CommonBuildOpts.Volumes, b.CommonBuildOpts.ShmSize, append(b.NamespaceOptions, options.NamespaceOptions...)) + + err = b.setupMounts(mountPoint, spec, path, options.Mounts, bindFiles, volumes, b.CommonBuildOpts.Volumes, b.CommonBuildOpts.ShmSize, namespaceOptions) if err != nil { return errors.Wrapf(err, "error resolving mountpoints for container %q", b.ContainerID) } @@ -1081,41 +1104,35 @@ func (b *Builder) Run(command []string, options RunOptions) error { switch isolation { case IsolationOCI: - // The default is --rootless=auto, which makes troubleshooting a bit harder. - // rootlessFlag := []string{"--rootless=false"} - // for _, arg := range options.Args { - // if strings.HasPrefix(arg, "--rootless") { - // rootlessFlag = nil - // } - // } - // options.Args = append(options.Args, rootlessFlag...) var moreCreateArgs []string if options.NoPivot { moreCreateArgs = []string{"--no-pivot"} } else { moreCreateArgs = nil } - err = b.runUsingRuntimeSubproc(options, configureNetwork, configureNetworks, moreCreateArgs, spec, mountPoint, path, Package+"-"+filepath.Base(path)) + err = b.runUsingRuntimeSubproc(isolation, options, configureNetwork, configureNetworks, moreCreateArgs, spec, mountPoint, path, Package+"-"+filepath.Base(path)) case IsolationChroot: err = chroot.RunUsingChroot(spec, path, options.Stdin, options.Stdout, options.Stderr) case IsolationOCIRootless: if err := setupRootlessSpecChanges(spec, path, rootUID, rootGID); err != nil { return err } - rootlessFlag := []string{"--rootless=true"} - for _, arg := range options.Args { - if strings.HasPrefix(arg, "--rootless") { - rootlessFlag = nil - } - } - options.Args = append(options.Args, rootlessFlag...) - err = b.runUsingRuntimeSubproc(options, configureNetwork, configureNetworks, []string{"--no-new-keyring"}, spec, mountPoint, path, Package+"-"+filepath.Base(path)) + err = b.runUsingRuntimeSubproc(isolation, options, configureNetwork, configureNetworks, []string{"--no-new-keyring"}, spec, mountPoint, path, Package+"-"+filepath.Base(path)) default: err = errors.Errorf("don't know how to run this command") } return err } +func contains(volumes []string, v string) bool { + for _, i := range volumes { + if i == v { + return true + } + } + return false +} + func checkAndOverrideIsolationOptions(isolation Isolation, options *RunOptions) error { switch isolation { case IsolationOCIRootless: @@ -1123,10 +1140,22 @@ func checkAndOverrideIsolationOptions(isolation Isolation, options *RunOptions) logrus.Debugf("Forcing use of an IPC namespace.") } options.NamespaceOptions.AddOrReplace(NamespaceOption{Name: string(specs.IPCNamespace)}) - if ns := options.NamespaceOptions.Find(string(specs.NetworkNamespace)); ns != nil && !ns.Host { - logrus.Debugf("Disabling network namespace.") + _, err := exec.LookPath("slirp4netns") + hostNetworking := err != nil + networkNamespacePath := "" + if ns := options.NamespaceOptions.Find(string(specs.NetworkNamespace)); ns != nil { + hostNetworking = ns.Host + networkNamespacePath = ns.Path + if !hostNetworking && networkNamespacePath != "" && !filepath.IsAbs(networkNamespacePath) { + logrus.Debugf("Disabling network namespace configuration.") + networkNamespacePath = "" + } } - options.NamespaceOptions.AddOrReplace(NamespaceOption{Name: string(specs.NetworkNamespace), Host: true}) + options.NamespaceOptions.AddOrReplace(NamespaceOption{ + Name: string(specs.NetworkNamespace), + Host: hostNetworking, + Path: networkNamespacePath, + }) if ns := options.NamespaceOptions.Find(string(specs.PIDNamespace)); ns == nil || ns.Host { logrus.Debugf("Forcing use of a PID namespace.") } @@ -1227,9 +1256,10 @@ type runUsingRuntimeSubprocOptions struct { ConfigureNetworks []string MoreCreateArgs []string ContainerName string + Isolation Isolation } -func (b *Builder) runUsingRuntimeSubproc(options RunOptions, configureNetwork bool, configureNetworks, moreCreateArgs []string, spec *specs.Spec, rootPath, bundlePath, containerName string) (err error) { +func (b *Builder) runUsingRuntimeSubproc(isolation Isolation, options RunOptions, configureNetwork bool, configureNetworks, moreCreateArgs []string, spec *specs.Spec, rootPath, bundlePath, containerName string) (err error) { var confwg sync.WaitGroup config, conferr := json.Marshal(runUsingRuntimeSubprocOptions{ Options: options, @@ -1240,6 +1270,7 @@ func (b *Builder) runUsingRuntimeSubproc(options RunOptions, configureNetwork bo ConfigureNetworks: configureNetworks, MoreCreateArgs: moreCreateArgs, ContainerName: containerName, + Isolation: isolation, }) if conferr != nil { return errors.Wrapf(conferr, "error encoding configuration for %q", runUsingRuntimeCommand) @@ -1318,7 +1349,7 @@ func runUsingRuntimeMain() { os.Exit(1) } // Run the container, start to finish. - status, err := runUsingRuntime(options.Options, options.ConfigureNetwork, options.ConfigureNetworks, options.MoreCreateArgs, options.Spec, options.RootPath, options.BundlePath, options.ContainerName) + status, err := runUsingRuntime(options.Isolation, options.Options, options.ConfigureNetwork, options.ConfigureNetworks, options.MoreCreateArgs, options.Spec, options.RootPath, options.BundlePath, options.ContainerName) if err != nil { fmt.Fprintf(os.Stderr, "error running container: %v\n", err) os.Exit(1) @@ -1333,7 +1364,7 @@ func runUsingRuntimeMain() { os.Exit(1) } -func runUsingRuntime(options RunOptions, configureNetwork bool, configureNetworks, moreCreateArgs []string, spec *specs.Spec, rootPath, bundlePath, containerName string) (wstatus unix.WaitStatus, err error) { +func runUsingRuntime(isolation Isolation, options RunOptions, configureNetwork bool, configureNetworks, moreCreateArgs []string, spec *specs.Spec, rootPath, bundlePath, containerName string) (wstatus unix.WaitStatus, err error) { // Lock the caller to a single OS-level thread. runtime.LockOSThread() @@ -1490,7 +1521,7 @@ func runUsingRuntime(options RunOptions, configureNetwork bool, configureNetwork }() if configureNetwork { - teardown, err := runConfigureNetwork(options, configureNetworks, pid, containerName, spec.Process.Args) + teardown, err := runConfigureNetwork(isolation, options, configureNetworks, pid, containerName, spec.Process.Args) if teardown != nil { defer teardown() } @@ -1623,9 +1654,81 @@ func runCollectOutput(fds, closeBeforeReadingFds []int) string { } return b.String() } +func setupRootlessNetwork(pid int) (teardown func(), err error) { + slirp4netns, err := exec.LookPath("slirp4netns") + if err != nil { + return nil, errors.Wrapf(err, "cannot find slirp4netns") + } + + rootlessSlirpSyncR, rootlessSlirpSyncW, err := os.Pipe() + if err != nil { + return nil, errors.Wrapf(err, "cannot create slirp4netns sync pipe") + } + defer rootlessSlirpSyncR.Close() + + // Be sure there are no fds inherited to slirp4netns except the sync pipe + files, err := ioutil.ReadDir("/proc/self/fd") + if err != nil { + return nil, errors.Wrapf(err, "cannot list open fds") + } + for _, f := range files { + fd, err := strconv.Atoi(f.Name()) + if err != nil { + return nil, errors.Wrapf(err, "cannot parse fd") + } + if fd == int(rootlessSlirpSyncW.Fd()) { + continue + } + unix.CloseOnExec(fd) + } + + cmd := exec.Command(slirp4netns, "-r", "3", "-c", fmt.Sprintf("%d", pid), "tap0") + cmd.Stdin, cmd.Stdout, cmd.Stderr = nil, nil, nil + cmd.ExtraFiles = []*os.File{rootlessSlirpSyncW} + + err = cmd.Start() + rootlessSlirpSyncW.Close() + if err != nil { + return nil, errors.Wrapf(err, "cannot start slirp4netns") + } -func runConfigureNetwork(options RunOptions, configureNetworks []string, pid int, containerName string, command []string) (teardown func(), err error) { + b := make([]byte, 1) + for { + if err := rootlessSlirpSyncR.SetDeadline(time.Now().Add(1 * time.Second)); err != nil { + return nil, errors.Wrapf(err, "error setting slirp4netns pipe timeout") + } + if _, err := rootlessSlirpSyncR.Read(b); err == nil { + break + } else { + if os.IsTimeout(err) { + // Check if the process is still running. + var status syscall.WaitStatus + _, err := syscall.Wait4(cmd.Process.Pid, &status, syscall.WNOHANG, nil) + if err != nil { + return nil, errors.Wrapf(err, "failed to read slirp4netns process status") + } + if status.Exited() || status.Signaled() { + return nil, errors.New("slirp4netns failed") + } + + continue + } + return nil, errors.Wrapf(err, "failed to read from slirp4netns sync pipe") + } + } + + return func() { + cmd.Process.Kill() + cmd.Wait() + }, nil +} + +func runConfigureNetwork(isolation Isolation, options RunOptions, configureNetworks []string, pid int, containerName string, command []string) (teardown func(), err error) { var netconf, undo []*libcni.NetworkConfigList + + if isolation == IsolationOCIRootless { + return setupRootlessNetwork(pid) + } // Scan for CNI configuration files. confdir := options.CNIConfigDir files, err := libcni.ConfFiles(confdir, []string{".conf"}) @@ -1956,7 +2059,7 @@ func runAcceptTerminal(consoleListener *net.UnixListener, terminalSize *specs.Bo for i := range scm { fds, err := unix.ParseUnixRights(&scm[i]) if err != nil { - return -1, errors.Wrapf(err, "error parsing unix rights control message: %v") + return -1, errors.Wrapf(err, "error parsing unix rights control message: %v", &scm[i]) } logrus.Debugf("fds: %v", fds) if len(fds) == 0 { diff --git a/vendor/github.com/containers/buildah/unshare/unshare.go b/vendor/github.com/containers/buildah/unshare/unshare.go index fbe623660..2a970b8d6 100644 --- a/vendor/github.com/containers/buildah/unshare/unshare.go +++ b/vendor/github.com/containers/buildah/unshare/unshare.go @@ -55,6 +55,10 @@ func (c *Cmd) Start() error { } c.Env = append(c.Env, fmt.Sprintf("_Buildah-unshare=%d", c.UnshareFlags)) + // Please the libpod "rootless" package to find the expected env variables. + c.Env = append(c.Env, "_LIBPOD_USERNS_CONFIGURED=done") + c.Env = append(c.Env, fmt.Sprintf("_LIBPOD_ROOTLESS_UID=%d", os.Geteuid())) + // Create the pipe for reading the child's PID. pidRead, pidWrite, err := os.Pipe() if err != nil { diff --git a/vendor/github.com/containers/buildah/util/util.go b/vendor/github.com/containers/buildah/util/util.go index 3a415a7f3..b2451b78b 100644 --- a/vendor/github.com/containers/buildah/util/util.go +++ b/vendor/github.com/containers/buildah/util/util.go @@ -7,10 +7,8 @@ import ( "net/url" "os" "path" - "path/filepath" "strconv" "strings" - "syscall" "github.com/containers/image/directory" dockerarchive "github.com/containers/image/docker/archive" @@ -31,6 +29,10 @@ import ( const ( minimumTruncatedIDLength = 3 + // DefaultTransport is a prefix that we apply to an image name if we + // can't find one in the local Store, in order to generate a source + // reference for the image that we can then copy to the local Store. + DefaultTransport = "docker://" ) var ( @@ -89,6 +91,7 @@ func ResolveName(name string, firstRegistry string, sc *types.SystemContext, sto } } + name = strings.TrimPrefix(name, DefaultTransport) // If the image name already included a domain component, we're done. named, err := reference.ParseNormalizedNamed(name) if err != nil { @@ -450,60 +453,6 @@ func ParseIDMappings(uidmap, gidmap []string) ([]idtools.IDMap, []idtools.IDMap, return uid, gid, nil } -// UnsharedRootPath returns a location under ($XDG_DATA_HOME/containers/storage, -// or $HOME/.local/share/containers/storage, or -// (the user's home directory)/.local/share/containers/storage, or an error. -func UnsharedRootPath(homedir string) (string, error) { - // If $XDG_DATA_HOME is defined... - if envDataHome, haveDataHome := os.LookupEnv("XDG_DATA_HOME"); haveDataHome { - return filepath.Join(envDataHome, "containers", "storage"), nil - } - // If $XDG_DATA_HOME is not defined, but $HOME is defined... - if envHomedir, haveHomedir := os.LookupEnv("HOME"); haveHomedir { - // Default to the user's $HOME/.local/share/containers/storage subdirectory. - return filepath.Join(envHomedir, ".local", "share", "containers", "storage"), nil - } - // If we know where our home directory is... - if homedir != "" { - // Default to the user's homedir/.local/share/containers/storage subdirectory. - return filepath.Join(homedir, ".local", "share", "containers", "storage"), nil - } - return "", errors.New("unable to determine a --root location: neither $XDG_DATA_HOME nor $HOME is set") -} - -// UnsharedRunrootPath returns $XDG_RUNTIME_DIR/run, /var/run/user/(the user's UID)/run, or an error. -func UnsharedRunrootPath(uid string) (string, error) { - // If $XDG_RUNTIME_DIR is defined... - if envRuntimeDir, haveRuntimeDir := os.LookupEnv("XDG_RUNTIME_DIR"); haveRuntimeDir { - return filepath.Join(envRuntimeDir, "run"), nil - } - var runtimeDir string - // If $XDG_RUNTIME_DIR is not defined, but we know our UID... - if uid != "" { - tmpDir := filepath.Join("/var/run/user", uid) - os.MkdirAll(tmpDir, 0700) - st, err := os.Stat(tmpDir) - if err == nil && int(st.Sys().(*syscall.Stat_t).Uid) == os.Getuid() && st.Mode().Perm() == 0700 { - runtimeDir = tmpDir - } - } - if runtimeDir == "" { - home := os.Getenv("HOME") - if home == "" { - return "", errors.New("neither XDG_RUNTIME_DIR nor HOME was set non-empty") - } - resolvedHome, err := filepath.EvalSymlinks(home) - if err != nil { - return "", errors.Wrapf(err, "cannot resolve %s", home) - } - runtimeDir = filepath.Join(resolvedHome, "rundir") - } - if err := os.Setenv("XDG_RUNTIME_DIR", runtimeDir); err != nil { - return "", errors.New("could not set XDG_RUNTIME_DIR") - } - return runtimeDir, nil -} - // GetPolicyContext sets up, initializes and returns a new context for the specified policy func GetPolicyContext(ctx *types.SystemContext) (*signature.PolicyContext, error) { policy, err := signature.DefaultPolicy(ctx) diff --git a/vendor/github.com/containers/buildah/vendor.conf b/vendor/github.com/containers/buildah/vendor.conf index d79412afc..185cde449 100644 --- a/vendor/github.com/containers/buildah/vendor.conf +++ b/vendor/github.com/containers/buildah/vendor.conf @@ -3,9 +3,9 @@ github.com/blang/semver master github.com/BurntSushi/toml master github.com/containerd/continuity master github.com/containernetworking/cni v0.7.0-alpha1 -github.com/containers/image 5e5b67d6b1cf43cc349128ec3ed7d5283a6cc0d1 -github.com/containers/libpod 2afadeec6696fefac468a49c8ba24b0bc275aa75 -github.com/containers/storage 41294c85d97bef688e18f710402895dbecde3308 +github.com/containers/image de7be82ee3c7fb676bf6cfdc9090be7cc28f404c +github.com/containers/libpod fe4f09493f41f675d24c969d1b60d1a6a45ddb9e +github.com/containers/storage 3161726d1db0d0d4e86a9667dd476f09b997f497 github.com/docker/distribution 5f6282db7d65e6d72ad7c2cc66310724a57be716 github.com/docker/docker 86f080cff0914e9694068ed78d503701667c4c00 github.com/docker/docker-credential-helpers d68f9aeca33f5fd3f08eeae5e9d175edf4e731d1 @@ -36,9 +36,9 @@ github.com/opencontainers/image-spec v1.0.0 github.com/opencontainers/runc master github.com/opencontainers/runtime-spec v1.0.0 github.com/opencontainers/runtime-tools master -github.com/opencontainers/selinux b6fa367ed7f534f9ba25391cc2d467085dbb445a +github.com/opencontainers/selinux master github.com/openshift/imagebuilder master -github.com/ostreedev/ostree-go aeb02c6b6aa2889db3ef62f7855650755befd460 +github.com/ostreedev/ostree-go 9ab99253d365aac3a330d1f7281cf29f3d22820b github.com/pborman/uuid master github.com/pkg/errors master github.com/pquerna/ffjson d49c2bc1aa135aad0c6f4fc2056623ec78f5d5ac diff --git a/vendor/github.com/containers/storage/README.md b/vendor/github.com/containers/storage/README.md index f68cc55c3..fef46a689 100644 --- a/vendor/github.com/containers/storage/README.md +++ b/vendor/github.com/containers/storage/README.md @@ -2,7 +2,7 @@ layers, container images, and containers. A `containers-storage` CLI wrapper is also included for manual and scripting use. -To build the CLI wrapper, use 'make build-binary'. +To build the CLI wrapper, use 'make binary'. Operations which use VMs expect to launch them using 'vagrant', defaulting to using its 'libvirt' provider. The boxes used are also available for the diff --git a/vendor/github.com/containers/storage/containers.go b/vendor/github.com/containers/storage/containers.go index 0a125331d..beaf41f39 100644 --- a/vendor/github.com/containers/storage/containers.go +++ b/vendor/github.com/containers/storage/containers.go @@ -147,6 +147,13 @@ func (c *Container) ProcessLabel() string { return "" } +func (c *Container) MountOpts() []string { + if mountOpts, ok := c.Flags["MountOpts"].([]string); ok { + return mountOpts + } + return nil +} + func (r *containerStore) Containers() ([]Container, error) { containers := make([]Container, len(r.containers)) for i := range r.containers { @@ -293,6 +300,9 @@ func (r *containerStore) Create(id string, names []string, image, layer, metadat if _, idInUse := r.byid[id]; idInUse { return nil, ErrDuplicateID } + if options.MountOpts != nil { + options.Flags["MountOpts"] = append([]string{}, options.MountOpts...) + } names = dedupeNames(names) for _, name := range names { if _, nameInUse := r.byname[name]; nameInUse { diff --git a/vendor/github.com/containers/storage/containers_ffjson.go b/vendor/github.com/containers/storage/containers_ffjson.go index 6e83808d4..aef6becfe 100644 --- a/vendor/github.com/containers/storage/containers_ffjson.go +++ b/vendor/github.com/containers/storage/containers_ffjson.go @@ -1,6 +1,5 @@ // Code generated by ffjson <https://github.com/pquerna/ffjson>. DO NOT EDIT. // source: containers.go -// package storage diff --git a/vendor/github.com/containers/storage/drivers/aufs/aufs.go b/vendor/github.com/containers/storage/drivers/aufs/aufs.go index f14ba24b9..ca69816be 100644 --- a/vendor/github.com/containers/storage/drivers/aufs/aufs.go +++ b/vendor/github.com/containers/storage/drivers/aufs/aufs.go @@ -405,7 +405,7 @@ func atomicRemove(source string) error { case os.IsExist(err): // Got error saying the target dir already exists, maybe the source doesn't exist due to a previous (failed) remove if _, e := os.Stat(source); !os.IsNotExist(e) { - return errors.Wrapf(err, "target rename dir '%s' exists but should not, this needs to be manually cleaned up") + return errors.Wrapf(err, "target rename dir '%s' exists but should not, this needs to be manually cleaned up", target) } default: return errors.Wrapf(err, "error preparing atomic delete") @@ -441,7 +441,7 @@ func (a *Driver) Get(id string, options graphdriver.MountOpts) (string, error) { // If a dir does not have a parent ( no layers )do not try to mount // just return the diff path to the data if len(parents) > 0 { - if err := a.mount(id, m, options.MountLabel, parents); err != nil { + if err := a.mount(id, m, parents, options); err != nil { return "", err } } @@ -585,7 +585,7 @@ func (a *Driver) getParentLayerPaths(id string) ([]string, error) { return layers, nil } -func (a *Driver) mount(id string, target string, mountLabel string, layers []string) error { +func (a *Driver) mount(id string, target string, layers []string, options graphdriver.MountOpts) error { a.Lock() defer a.Unlock() @@ -596,7 +596,7 @@ func (a *Driver) mount(id string, target string, mountLabel string, layers []str rw := a.getDiffPath(id) - if err := a.aufsMount(layers, rw, target, mountLabel); err != nil { + if err := a.aufsMount(layers, rw, target, options); err != nil { return fmt.Errorf("error creating aufs mount to %s: %v", target, err) } return nil @@ -643,7 +643,7 @@ func (a *Driver) Cleanup() error { return mountpk.Unmount(a.root) } -func (a *Driver) aufsMount(ro []string, rw, target, mountLabel string) (err error) { +func (a *Driver) aufsMount(ro []string, rw, target string, options graphdriver.MountOpts) (err error) { defer func() { if err != nil { Unmount(target) @@ -657,7 +657,7 @@ func (a *Driver) aufsMount(ro []string, rw, target, mountLabel string) (err erro if useDirperm() { offset += len(",dirperm1") } - b := make([]byte, unix.Getpagesize()-len(mountLabel)-offset) // room for xino & mountLabel + b := make([]byte, unix.Getpagesize()-len(options.MountLabel)-offset) // room for xino & mountLabel bp := copy(b, fmt.Sprintf("br:%s=rw", rw)) index := 0 @@ -670,21 +670,25 @@ func (a *Driver) aufsMount(ro []string, rw, target, mountLabel string) (err erro } opts := "dio,xino=/dev/shm/aufs.xino" - if a.mountOptions != "" { - opts += fmt.Sprintf(",%s", a.mountOptions) + mountOptions := a.mountOptions + if len(options.Options) > 0 { + mountOptions = strings.Join(options.Options, ",") + } + if mountOptions != "" { + opts += fmt.Sprintf(",%s", mountOptions) } if useDirperm() { opts += ",dirperm1" } - data := label.FormatMountLabel(fmt.Sprintf("%s,%s", string(b[:bp]), opts), mountLabel) + data := label.FormatMountLabel(fmt.Sprintf("%s,%s", string(b[:bp]), opts), options.MountLabel) if err = mount("none", target, "aufs", 0, data); err != nil { return } for ; index < len(ro); index++ { layer := fmt.Sprintf(":%s=ro+wh", ro[index]) - data := label.FormatMountLabel(fmt.Sprintf("append%s", layer), mountLabel) + data := label.FormatMountLabel(fmt.Sprintf("append%s", layer), options.MountLabel) if err = mount("none", target, "aufs", unix.MS_REMOUNT, data); err != nil { return } diff --git a/vendor/github.com/containers/storage/drivers/btrfs/btrfs.go b/vendor/github.com/containers/storage/drivers/btrfs/btrfs.go index adc34d209..567cda9d3 100644 --- a/vendor/github.com/containers/storage/drivers/btrfs/btrfs.go +++ b/vendor/github.com/containers/storage/drivers/btrfs/btrfs.go @@ -640,6 +640,9 @@ func (d *Driver) Get(id string, options graphdriver.MountOpts) (string, error) { if err != nil { return "", err } + if len(options.Options) > 0 { + return "", fmt.Errorf("btrfs driver does not support mount options") + } if !st.IsDir() { return "", fmt.Errorf("%s: not a directory", dir) diff --git a/vendor/github.com/containers/storage/drivers/devmapper/deviceset.go b/vendor/github.com/containers/storage/drivers/devmapper/deviceset.go index cbf67b3eb..2801dfdc5 100644 --- a/vendor/github.com/containers/storage/drivers/devmapper/deviceset.go +++ b/vendor/github.com/containers/storage/drivers/devmapper/deviceset.go @@ -2364,7 +2364,7 @@ func (devices *DeviceSet) xfsSetNospaceRetries(info *devInfo) error { } // MountDevice mounts the device if not already mounted. -func (devices *DeviceSet) MountDevice(hash, path, mountLabel string) error { +func (devices *DeviceSet) MountDevice(hash, path string, moptions graphdriver.MountOpts) error { info, err := devices.lookupDeviceWithLock(hash) if err != nil { return err @@ -2396,8 +2396,17 @@ func (devices *DeviceSet) MountDevice(hash, path, mountLabel string) error { options = joinMountOptions(options, "nouuid") } - options = joinMountOptions(options, devices.mountOptions) - options = joinMountOptions(options, label.FormatMountLabel("", mountLabel)) + mountOptions := devices.mountOptions + if len(moptions.Options) > 0 { + addNouuid := strings.Contains("nouuid", mountOptions) + mountOptions = strings.Join(moptions.Options, ",") + if addNouuid { + mountOptions = fmt.Sprintf("nouuid,", mountOptions) + } + } + + options = joinMountOptions(options, mountOptions) + options = joinMountOptions(options, label.FormatMountLabel("", moptions.MountLabel)) if err := mount.Mount(info.DevName(), path, fstype, options); err != nil { return fmt.Errorf("devmapper: Error mounting '%s' on '%s': %s\n%v", info.DevName(), path, err, string(dmesg.Dmesg(256))) diff --git a/vendor/github.com/containers/storage/drivers/devmapper/driver.go b/vendor/github.com/containers/storage/drivers/devmapper/driver.go index 9fc082d7d..39a4fbe2c 100644 --- a/vendor/github.com/containers/storage/drivers/devmapper/driver.go +++ b/vendor/github.com/containers/storage/drivers/devmapper/driver.go @@ -9,8 +9,6 @@ import ( "path" "strconv" - "github.com/sirupsen/logrus" - "github.com/containers/storage/drivers" "github.com/containers/storage/pkg/devicemapper" "github.com/containers/storage/pkg/idtools" @@ -18,6 +16,7 @@ import ( "github.com/containers/storage/pkg/mount" "github.com/containers/storage/pkg/system" units "github.com/docker/go-units" + "github.com/sirupsen/logrus" ) func init() { @@ -189,7 +188,7 @@ func (d *Driver) Get(id string, options graphdriver.MountOpts) (string, error) { } // Mount the device - if err := d.DeviceSet.MountDevice(id, mp, options.MountLabel); err != nil { + if err := d.DeviceSet.MountDevice(id, mp, options); err != nil { d.ctr.Decrement(mp) return "", err } diff --git a/vendor/github.com/containers/storage/drivers/driver.go b/vendor/github.com/containers/storage/drivers/driver.go index 4569c7b59..476b55160 100644 --- a/vendor/github.com/containers/storage/drivers/driver.go +++ b/vendor/github.com/containers/storage/drivers/driver.go @@ -49,6 +49,7 @@ type MountOpts struct { // UidMaps & GidMaps are the User Namespace mappings to be assigned to content in the mount point UidMaps []idtools.IDMap GidMaps []idtools.IDMap + Options []string } // InitFunc initializes the storage driver. diff --git a/vendor/github.com/containers/storage/drivers/fsdiff.go b/vendor/github.com/containers/storage/drivers/fsdiff.go index 19da7d101..ab2c41e58 100644 --- a/vendor/github.com/containers/storage/drivers/fsdiff.go +++ b/vendor/github.com/containers/storage/drivers/fsdiff.go @@ -8,6 +8,7 @@ import ( "github.com/containers/storage/pkg/chrootarchive" "github.com/containers/storage/pkg/idtools" "github.com/containers/storage/pkg/ioutils" + rsystem "github.com/opencontainers/runc/libcontainer/system" "github.com/sirupsen/logrus" ) @@ -167,7 +168,9 @@ func (gdw *NaiveDiffDriver) ApplyDiff(id string, applyMappings *idtools.IDMappin } defer driver.Put(id) - options := &archive.TarOptions{} + options := &archive.TarOptions{ + InUserNS: rsystem.RunningInUserNS(), + } if applyMappings != nil { options.UIDMaps = applyMappings.UIDs() options.GIDMaps = applyMappings.GIDs() diff --git a/vendor/github.com/containers/storage/drivers/overlay/overlay.go b/vendor/github.com/containers/storage/drivers/overlay/overlay.go index d2cc65bca..df736c0a9 100644 --- a/vendor/github.com/containers/storage/drivers/overlay/overlay.go +++ b/vendor/github.com/containers/storage/drivers/overlay/overlay.go @@ -29,6 +29,7 @@ import ( "github.com/containers/storage/pkg/parsers" "github.com/containers/storage/pkg/system" units "github.com/docker/go-units" + rsystem "github.com/opencontainers/runc/libcontainer/system" "github.com/opencontainers/selinux/go-selinux/label" "github.com/pkg/errors" "github.com/sirupsen/logrus" @@ -340,6 +341,10 @@ func supportsOverlay(home string, homeMagic graphdriver.FsMagic, rootUID, rootGI func (d *Driver) useNaiveDiff() bool { useNaiveDiffLock.Do(func() { + if d.options.mountProgram != "" { + useNaiveDiffOnly = true + return + } if err := doesSupportNativeDiff(d.home, d.options.mountOptions); err != nil { logrus.Warnf("Not using native diff for overlay, this may cause degraded performance for building images: %v", err) useNaiveDiffOnly = true @@ -739,7 +744,9 @@ func (d *Driver) get(id string, disableShifting bool, options graphdriver.MountO workDir := path.Join(dir, "work") opts := fmt.Sprintf("lowerdir=%s,upperdir=%s,workdir=%s", strings.Join(absLowers, ":"), diffDir, workDir) - if d.options.mountOptions != "" { + if len(options.Options) > 0 { + opts = fmt.Sprintf("%s,%s", strings.Join(options.Options, ","), opts) + } else if d.options.mountOptions != "" { opts = fmt.Sprintf("%s,%s", d.options.mountOptions, opts) } mountData := label.FormatMountLabel(opts, options.MountLabel) @@ -841,6 +848,17 @@ func (d *Driver) isParent(id, parent string) bool { return ld == parentDir } +func (d *Driver) getWhiteoutFormat() archive.WhiteoutFormat { + whiteoutFormat := archive.OverlayWhiteoutFormat + if d.options.mountProgram != "" { + // If we are using a mount program, we are most likely running + // as an unprivileged user that cannot use mknod, so fallback to the + // AUFS whiteout format. + whiteoutFormat = archive.AUFSWhiteoutFormat + } + return whiteoutFormat +} + // ApplyDiff applies the new layer into a root func (d *Driver) ApplyDiff(id string, idMappings *idtools.IDMappings, parent string, mountLabel string, diff io.Reader) (size int64, err error) { if !d.isParent(id, parent) { @@ -858,7 +876,8 @@ func (d *Driver) ApplyDiff(id string, idMappings *idtools.IDMappings, parent str if err := untar(diff, applyDir, &archive.TarOptions{ UIDMaps: idMappings.UIDs(), GIDMaps: idMappings.GIDs(), - WhiteoutFormat: archive.OverlayWhiteoutFormat, + WhiteoutFormat: d.getWhiteoutFormat(), + InUserNS: rsystem.RunningInUserNS(), }); err != nil { return 0, err } @@ -911,7 +930,7 @@ func (d *Driver) Diff(id string, idMappings *idtools.IDMappings, parent string, Compression: archive.Uncompressed, UIDMaps: idMappings.UIDs(), GIDMaps: idMappings.GIDs(), - WhiteoutFormat: archive.OverlayWhiteoutFormat, + WhiteoutFormat: d.getWhiteoutFormat(), WhiteoutData: lowerDirs, }) } diff --git a/vendor/github.com/containers/storage/drivers/vfs/driver.go b/vendor/github.com/containers/storage/drivers/vfs/driver.go index d10fb2607..e3a67a69b 100644 --- a/vendor/github.com/containers/storage/drivers/vfs/driver.go +++ b/vendor/github.com/containers/storage/drivers/vfs/driver.go @@ -181,6 +181,9 @@ func (d *Driver) Remove(id string) error { // Get returns the directory for the given id. func (d *Driver) Get(id string, options graphdriver.MountOpts) (_ string, retErr error) { dir := d.dir(id) + if len(options.Options) > 0 { + return "", fmt.Errorf("vfs driver does not support mount options") + } if st, err := os.Stat(dir); err != nil { return "", err } else if !st.IsDir() { diff --git a/vendor/github.com/containers/storage/drivers/windows/windows.go b/vendor/github.com/containers/storage/drivers/windows/windows.go index 4ccf657dc..c6d86a4ab 100644 --- a/vendor/github.com/containers/storage/drivers/windows/windows.go +++ b/vendor/github.com/containers/storage/drivers/windows/windows.go @@ -367,6 +367,9 @@ func (d *Driver) Get(id string, options graphdriver.MountOpts) (string, error) { logrus.Debugf("WindowsGraphDriver Get() id %s mountLabel %s", id, options.MountLabel) var dir string + if len(options.Options) > 0 { + return "", fmt.Errorf("windows driver does not support mount options") + } rID, err := d.resolveID(id) if err != nil { return "", err diff --git a/vendor/github.com/containers/storage/drivers/zfs/zfs.go b/vendor/github.com/containers/storage/drivers/zfs/zfs.go index cb4424f2d..c3ce6e869 100644 --- a/vendor/github.com/containers/storage/drivers/zfs/zfs.go +++ b/vendor/github.com/containers/storage/drivers/zfs/zfs.go @@ -52,7 +52,7 @@ func Init(base string, opt []string, uidMaps, gidMaps []idtools.IDMap) (graphdri return nil, errors.Wrap(graphdriver.ErrPrerequisites, "the 'zfs' command is not available") } - file, err := os.OpenFile("/dev/zfs", os.O_RDWR, 600) + file, err := os.OpenFile("/dev/zfs", os.O_RDWR, 0600) if err != nil { logrus.Debugf("[zfs] cannot open /dev/zfs: %v", err) return nil, errors.Wrapf(graphdriver.ErrPrerequisites, "could not open /dev/zfs: %v", err) @@ -366,8 +366,13 @@ func (d *Driver) Get(id string, options graphdriver.MountOpts) (string, error) { return mountpoint, nil } + mountOptions := d.options.mountOptions + if len(options.Options) > 0 { + mountOptions = strings.Join(options.Options, ",") + } + filesystem := d.zfsPath(id) - opts := label.FormatMountLabel(d.options.mountOptions, options.MountLabel) + opts := label.FormatMountLabel(mountOptions, options.MountLabel) logrus.Debugf(`[zfs] mount("%s", "%s", "%s")`, filesystem, mountpoint, opts) rootUID, rootGID, err := idtools.GetRootUIDGID(d.uidMaps, d.gidMaps) diff --git a/vendor/github.com/containers/storage/layers.go b/vendor/github.com/containers/storage/layers.go index 1275ab47c..0b532eb77 100644 --- a/vendor/github.com/containers/storage/layers.go +++ b/vendor/github.com/containers/storage/layers.go @@ -28,9 +28,8 @@ import ( ) const ( - tarSplitSuffix = ".tar-split.gz" - incompleteFlag = "incomplete" - compressionFlag = "diff-compression" + tarSplitSuffix = ".tar-split.gz" + incompleteFlag = "incomplete" ) // A Layer is a record of a copy-on-write layer that's stored by the lower @@ -542,8 +541,8 @@ func (r *layerStore) Put(id string, parentLayer *Layer, names []string, mountLab _, idInUse = r.byid[id] } } - if _, idInUse := r.byid[id]; idInUse { - return nil, -1, ErrDuplicateID + if duplicateLayer, idInUse := r.byid[id]; idInUse { + return duplicateLayer, -1, ErrDuplicateID } names = dedupeNames(names) for _, name := range names { @@ -841,8 +840,12 @@ func (r *layerStore) Delete(id string) error { return ErrLayerUnknown } id = layer.ID - if _, err := r.Unmount(id, true); err != nil { - return err + // This check is needed for idempotency of delete where the layer could have been + // already unmounted (since c/storage gives you that API directly) + for layer.MountCount > 0 { + if _, err := r.Unmount(id, false); err != nil { + return err + } } err := r.driver.Remove(id) if err == nil { diff --git a/vendor/github.com/containers/storage/layers_ffjson.go b/vendor/github.com/containers/storage/layers_ffjson.go index 09b5d0f33..125b5d8c9 100644 --- a/vendor/github.com/containers/storage/layers_ffjson.go +++ b/vendor/github.com/containers/storage/layers_ffjson.go @@ -1,5 +1,5 @@ // Code generated by ffjson <https://github.com/pquerna/ffjson>. DO NOT EDIT. -// source: ./layers.go +// source: layers.go package storage diff --git a/vendor/github.com/containers/storage/pkg/archive/archive.go b/vendor/github.com/containers/storage/pkg/archive/archive.go index 4c4382625..8d6eaacf3 100644 --- a/vendor/github.com/containers/storage/pkg/archive/archive.go +++ b/vendor/github.com/containers/storage/pkg/archive/archive.go @@ -22,6 +22,7 @@ import ( "github.com/containers/storage/pkg/pools" "github.com/containers/storage/pkg/promise" "github.com/containers/storage/pkg/system" + rsystem "github.com/opencontainers/runc/libcontainer/system" "github.com/sirupsen/logrus" ) @@ -1054,6 +1055,7 @@ func (archiver *Archiver) TarUntar(src, dst string) error { GIDMaps: tarMappings.GIDs(), Compression: Uncompressed, CopyPass: true, + InUserNS: rsystem.RunningInUserNS(), } archive, err := TarWithOptions(src, options) if err != nil { @@ -1068,6 +1070,7 @@ func (archiver *Archiver) TarUntar(src, dst string) error { UIDMaps: untarMappings.UIDs(), GIDMaps: untarMappings.GIDs(), ChownOpts: archiver.ChownOpts, + InUserNS: rsystem.RunningInUserNS(), } return archiver.Untar(archive, dst, options) } @@ -1087,6 +1090,7 @@ func (archiver *Archiver) UntarPath(src, dst string) error { UIDMaps: untarMappings.UIDs(), GIDMaps: untarMappings.GIDs(), ChownOpts: archiver.ChownOpts, + InUserNS: rsystem.RunningInUserNS(), } return archiver.Untar(archive, dst, options) } @@ -1186,6 +1190,7 @@ func (archiver *Archiver) CopyFileWithTar(src, dst string) (err error) { UIDMaps: archiver.UntarIDMappings.UIDs(), GIDMaps: archiver.UntarIDMappings.GIDs(), ChownOpts: archiver.ChownOpts, + InUserNS: rsystem.RunningInUserNS(), } err = archiver.Untar(r, filepath.Dir(dst), options) if err != nil { diff --git a/vendor/github.com/containers/storage/pkg/archive/example_changes.go b/vendor/github.com/containers/storage/pkg/archive/example_changes.go deleted file mode 100644 index 70f9c5564..000000000 --- a/vendor/github.com/containers/storage/pkg/archive/example_changes.go +++ /dev/null @@ -1,97 +0,0 @@ -// +build ignore - -// Simple tool to create an archive stream from an old and new directory -// -// By default it will stream the comparison of two temporary directories with junk files -package main - -import ( - "flag" - "fmt" - "io" - "io/ioutil" - "os" - "path" - - "github.com/containers/storage/pkg/archive" - "github.com/sirupsen/logrus" -) - -var ( - flDebug = flag.Bool("D", false, "debugging output") - flNewDir = flag.String("newdir", "", "") - flOldDir = flag.String("olddir", "", "") - log = logrus.New() -) - -func main() { - flag.Usage = func() { - fmt.Println("Produce a tar from comparing two directory paths. By default a demo tar is created of around 200 files (including hardlinks)") - fmt.Printf("%s [OPTIONS]\n", os.Args[0]) - flag.PrintDefaults() - } - flag.Parse() - log.Out = os.Stderr - if (len(os.Getenv("DEBUG")) > 0) || *flDebug { - logrus.SetLevel(logrus.DebugLevel) - } - var newDir, oldDir string - - if len(*flNewDir) == 0 { - var err error - newDir, err = ioutil.TempDir("", "storage-test-newDir") - if err != nil { - log.Fatal(err) - } - defer os.RemoveAll(newDir) - if _, err := prepareUntarSourceDirectory(100, newDir, true); err != nil { - log.Fatal(err) - } - } else { - newDir = *flNewDir - } - - if len(*flOldDir) == 0 { - oldDir, err := ioutil.TempDir("", "storage-test-oldDir") - if err != nil { - log.Fatal(err) - } - defer os.RemoveAll(oldDir) - } else { - oldDir = *flOldDir - } - - changes, err := archive.ChangesDirs(newDir, oldDir) - if err != nil { - log.Fatal(err) - } - - a, err := archive.ExportChanges(newDir, changes) - if err != nil { - log.Fatal(err) - } - defer a.Close() - - i, err := io.Copy(os.Stdout, a) - if err != nil && err != io.EOF { - log.Fatal(err) - } - fmt.Fprintf(os.Stderr, "wrote archive of %d bytes", i) -} - -func prepareUntarSourceDirectory(numberOfFiles int, targetPath string, makeLinks bool) (int, error) { - fileData := []byte("fooo") - for n := 0; n < numberOfFiles; n++ { - fileName := fmt.Sprintf("file-%d", n) - if err := ioutil.WriteFile(path.Join(targetPath, fileName), fileData, 0700); err != nil { - return 0, err - } - if makeLinks { - if err := os.Link(path.Join(targetPath, fileName), path.Join(targetPath, fileName+"-link")); err != nil { - return 0, err - } - } - } - totalSize := numberOfFiles * len(fileData) - return totalSize, nil -} diff --git a/vendor/github.com/containers/storage/pkg/chrootarchive/archive.go b/vendor/github.com/containers/storage/pkg/chrootarchive/archive.go index b9fa228e6..dde8d44d3 100644 --- a/vendor/github.com/containers/storage/pkg/chrootarchive/archive.go +++ b/vendor/github.com/containers/storage/pkg/chrootarchive/archive.go @@ -9,6 +9,7 @@ import ( "github.com/containers/storage/pkg/archive" "github.com/containers/storage/pkg/idtools" + rsystem "github.com/opencontainers/runc/libcontainer/system" ) // NewArchiver returns a new Archiver which uses chrootarchive.Untar @@ -52,6 +53,7 @@ func untarHandler(tarArchive io.Reader, dest string, options *archive.TarOptions } if options == nil { options = &archive.TarOptions{} + options.InUserNS = rsystem.RunningInUserNS() } if options.ExcludePatterns == nil { options.ExcludePatterns = []string{} diff --git a/vendor/github.com/containers/storage/pkg/idtools/parser.go b/vendor/github.com/containers/storage/pkg/idtools/parser.go new file mode 100644 index 000000000..c56aa86a2 --- /dev/null +++ b/vendor/github.com/containers/storage/pkg/idtools/parser.go @@ -0,0 +1,56 @@ +package idtools + +import ( + "fmt" + "strconv" + "strings" +) + +func nonDigitsToWhitespace(r rune) rune { + if !strings.ContainsRune("0123456789", r) { + return ' ' + } + return r +} + +func parseTriple(spec []string) (container, host, size uint32, err error) { + cid, err := strconv.ParseUint(spec[0], 10, 32) + if err != nil { + return 0, 0, 0, fmt.Errorf("error parsing id map value %q: %v", spec[0], err) + } + hid, err := strconv.ParseUint(spec[1], 10, 32) + if err != nil { + return 0, 0, 0, fmt.Errorf("error parsing id map value %q: %v", spec[1], err) + } + sz, err := strconv.ParseUint(spec[2], 10, 32) + if err != nil { + return 0, 0, 0, fmt.Errorf("error parsing id map value %q: %v", spec[2], err) + } + return uint32(cid), uint32(hid), uint32(sz), nil +} + +// ParseIDMap parses idmap triples from string. +func ParseIDMap(mapSpec []string, mapSetting string) (idmap []IDMap, err error) { + for _, idMapSpec := range mapSpec { + idSpec := strings.Fields(strings.Map(nonDigitsToWhitespace, idMapSpec)) + if len(idSpec)%3 != 0 { + return nil, fmt.Errorf("error initializing ID mappings: %s setting is malformed", mapSetting) + } + for i := range idSpec { + if i%3 != 0 { + continue + } + cid, hid, size, err := parseTriple(idSpec[i : i+3]) + if err != nil { + return nil, fmt.Errorf("error initializing ID mappings: %s setting is malformed", mapSetting) + } + mapping := IDMap{ + ContainerID: int(cid), + HostID: int(hid), + Size: int(size), + } + idmap = append(idmap, mapping) + } + } + return idmap, nil +} diff --git a/vendor/github.com/containers/storage/store.go b/vendor/github.com/containers/storage/store.go index 7eaa82910..e0dd1b92f 100644 --- a/vendor/github.com/containers/storage/store.go +++ b/vendor/github.com/containers/storage/store.go @@ -8,7 +8,6 @@ import ( "os" "path/filepath" "reflect" - "strconv" "strings" "sync" "time" @@ -22,6 +21,7 @@ import ( "github.com/containers/storage/pkg/directory" "github.com/containers/storage/pkg/idtools" "github.com/containers/storage/pkg/ioutils" + "github.com/containers/storage/pkg/parsers" "github.com/containers/storage/pkg/stringid" "github.com/containers/storage/pkg/stringutils" digest "github.com/opencontainers/go-digest" @@ -502,6 +502,7 @@ type ContainerOptions struct { IDMappingOptions LabelOpts []string Flags map[string]interface{} + MountOpts []string } type store struct { @@ -1069,7 +1070,7 @@ func (s *store) imageTopLayerForMapping(image *Image, ristore ROImageStore, read } mappedLayer, _, err := rlstore.Put("", parentLayer, nil, layer.MountLabel, nil, &layerOptions, false, nil, rc) if err != nil { - return nil, errors.Wrapf(err, "error creating ID-mapped copy of layer %q") + return nil, errors.Wrapf(err, "error creating ID-mapped copy of layer %q", parentLayer.ID) } if err = istore.addMappedTopLayer(image.ID, mappedLayer.ID); err != nil { if err2 := rlstore.Delete(mappedLayer.ID); err2 != nil { @@ -2145,21 +2146,20 @@ func (s *store) DeleteContainer(id string) error { if err = rlstore.Delete(container.LayerID); err != nil { return err } - if err = rcstore.Delete(id); err != nil { - return err - } - middleDir := s.graphDriverName + "-containers" - gcpath := filepath.Join(s.GraphRoot(), middleDir, container.ID) - if err = os.RemoveAll(gcpath); err != nil { - return err - } - rcpath := filepath.Join(s.RunRoot(), middleDir, container.ID) - if err = os.RemoveAll(rcpath); err != nil { - return err - } - return nil } - return ErrNotALayer + if err = rcstore.Delete(id); err != nil { + return err + } + middleDir := s.graphDriverName + "-containers" + gcpath := filepath.Join(s.GraphRoot(), middleDir, container.ID) + if err = os.RemoveAll(gcpath); err != nil { + return err + } + rcpath := filepath.Join(s.RunRoot(), middleDir, container.ID) + if err = os.RemoveAll(rcpath); err != nil { + return err + } + return nil } } return ErrNotAContainer @@ -2280,10 +2280,14 @@ func (s *store) Version() ([][2]string, error) { func (s *store) Mount(id, mountLabel string) (string, error) { container, err := s.Container(id) - var uidMap, gidMap []idtools.IDMap + var ( + uidMap, gidMap []idtools.IDMap + mountOpts []string + ) if err == nil { uidMap, gidMap = container.UIDMap, container.GIDMap id = container.LayerID + mountOpts = container.MountOpts() } rlstore, err := s.LayerStore() if err != nil { @@ -2299,6 +2303,7 @@ func (s *store) Mount(id, mountLabel string) (string, error) { MountLabel: mountLabel, UidMaps: uidMap, GidMaps: gidMap, + Options: mountOpts, } return rlstore.Mount(id, options) } @@ -3203,56 +3208,19 @@ func ReloadConfigurationFile(configFile string, storeOptions *StoreOptions) { storeOptions.UIDMap = mappings.UIDs() storeOptions.GIDMap = mappings.GIDs() } - nonDigitsToWhitespace := func(r rune) rune { - if strings.IndexRune("0123456789", r) == -1 { - return ' ' - } else { - return r - } - } - parseTriple := func(spec []string) (container, host, size uint32, err error) { - cid, err := strconv.ParseUint(spec[0], 10, 32) - if err != nil { - return 0, 0, 0, fmt.Errorf("error parsing id map value %q: %v", spec[0], err) - } - hid, err := strconv.ParseUint(spec[1], 10, 32) - if err != nil { - return 0, 0, 0, fmt.Errorf("error parsing id map value %q: %v", spec[1], err) - } - sz, err := strconv.ParseUint(spec[2], 10, 32) - if err != nil { - return 0, 0, 0, fmt.Errorf("error parsing id map value %q: %v", spec[2], err) - } - return uint32(cid), uint32(hid), uint32(sz), nil + + uidmap, err := idtools.ParseIDMap([]string{config.Storage.Options.RemapUIDs}, "remap-uids") + if err != nil { + fmt.Print(err) + } else { + storeOptions.UIDMap = append(storeOptions.UIDMap, uidmap...) } - parseIDMap := func(idMapSpec, mapSetting string) (idmap []idtools.IDMap) { - if len(idMapSpec) > 0 { - idSpec := strings.Fields(strings.Map(nonDigitsToWhitespace, idMapSpec)) - if len(idSpec)%3 != 0 { - fmt.Printf("Error initializing ID mappings: %s setting is malformed.\n", mapSetting) - return nil - } - for i := range idSpec { - if i%3 != 0 { - continue - } - cid, hid, size, err := parseTriple(idSpec[i : i+3]) - if err != nil { - fmt.Printf("Error initializing ID mappings: %s setting is malformed.\n", mapSetting) - return nil - } - mapping := idtools.IDMap{ - ContainerID: int(cid), - HostID: int(hid), - Size: int(size), - } - idmap = append(idmap, mapping) - } - } - return idmap + gidmap, err := idtools.ParseIDMap([]string{config.Storage.Options.RemapGIDs}, "remap-gids") + if err != nil { + fmt.Print(err) + } else { + storeOptions.GIDMap = append(storeOptions.GIDMap, gidmap...) } - storeOptions.UIDMap = append(storeOptions.UIDMap, parseIDMap(config.Storage.Options.RemapUIDs, "remap-uids")...) - storeOptions.GIDMap = append(storeOptions.GIDMap, parseIDMap(config.Storage.Options.RemapGIDs, "remap-gids")...) if os.Getenv("STORAGE_DRIVER") != "" { storeOptions.GraphDriverName = os.Getenv("STORAGE_DRIVER") } @@ -3271,3 +3239,23 @@ func init() { ReloadConfigurationFile(defaultConfigFile, &DefaultStoreOptions) } + +func GetDefaultMountOptions() ([]string, error) { + mountOpts := []string{ + ".mountopt", + fmt.Sprintf("%s.mountopt", DefaultStoreOptions.GraphDriverName), + } + for _, option := range DefaultStoreOptions.GraphDriverOptions { + key, val, err := parsers.ParseKeyValueOpt(option) + if err != nil { + return nil, err + } + key = strings.ToLower(key) + for _, m := range mountOpts { + if m == key { + return strings.Split(val, ","), nil + } + } + } + return nil, nil +} diff --git a/vendor/github.com/containers/storage/vendor.conf b/vendor/github.com/containers/storage/vendor.conf index 2276d5531..059ae94f0 100644 --- a/vendor/github.com/containers/storage/vendor.conf +++ b/vendor/github.com/containers/storage/vendor.conf @@ -2,13 +2,14 @@ github.com/BurntSushi/toml master github.com/Microsoft/go-winio 307e919c663683a9000576fdc855acaf9534c165 github.com/Microsoft/hcsshim a8d9cc56cbce765a7eebdf4792e6ceceeff3edb8 github.com/davecgh/go-spew 346938d642f2ec3594ed81d874461961cd0faa76 -github.com/docker/engine-api 4290f40c056686fcaa5c9caf02eac1dde9315adf +github.com/docker/docker 86f080cff0914e9694068ed78d503701667c4c00 github.com/docker/go-units 0dadbb0345b35ec7ef35e228dabb8de89a65bf52 github.com/mattn/go-shellwords 753a2322a99f87c0eff284980e77f53041555bc6 github.com/mistifyio/go-zfs c0224de804d438efd11ea6e52ada8014537d6062 github.com/opencontainers/go-digest master github.com/opencontainers/runc 6c22e77604689db8725fa866f0f2ec0b3e8c3a07 github.com/opencontainers/selinux 36a9bc45a08c85f2c52bd9eb32e20267876773bd +github.com/ostreedev/ostree-go aeb02c6b6aa2889db3ef62f7855650755befd460 github.com/pborman/uuid 1b00554d822231195d1babd97ff4a781231955c9 github.com/pkg/errors master github.com/pmezard/go-difflib v1.0.0 @@ -20,4 +21,3 @@ github.com/tchap/go-patricia v2.2.6 github.com/vbatts/tar-split v0.10.2 golang.org/x/net 7dcfb8076726a3fdd9353b6b8a1f1b6be6811bd6 golang.org/x/sys 07c182904dbd53199946ba614a412c61d3c548f5 -github.com/ostreedev/ostree-go aeb02c6b6aa2889db3ef62f7855650755befd460 diff --git a/vendor/github.com/google/shlex/COPYING b/vendor/github.com/google/shlex/COPYING new file mode 100644 index 000000000..d64569567 --- /dev/null +++ b/vendor/github.com/google/shlex/COPYING @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/vendor/github.com/google/shlex/README b/vendor/github.com/google/shlex/README new file mode 100644 index 000000000..c86bcc066 --- /dev/null +++ b/vendor/github.com/google/shlex/README @@ -0,0 +1,2 @@ +go-shlex is a simple lexer for go that supports shell-style quoting, +commenting, and escaping. diff --git a/vendor/github.com/google/shlex/shlex.go b/vendor/github.com/google/shlex/shlex.go new file mode 100644 index 000000000..d98308bce --- /dev/null +++ b/vendor/github.com/google/shlex/shlex.go @@ -0,0 +1,416 @@ +/* +Copyright 2012 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +/* +Package shlex implements a simple lexer which splits input in to tokens using +shell-style rules for quoting and commenting. + +The basic use case uses the default ASCII lexer to split a string into sub-strings: + + shlex.Split("one \"two three\" four") -> []string{"one", "two three", "four"} + +To process a stream of strings: + + l := NewLexer(os.Stdin) + for ; token, err := l.Next(); err != nil { + // process token + } + +To access the raw token stream (which includes tokens for comments): + + t := NewTokenizer(os.Stdin) + for ; token, err := t.Next(); err != nil { + // process token + } + +*/ +package shlex + +import ( + "bufio" + "fmt" + "io" + "strings" +) + +// TokenType is a top-level token classification: A word, space, comment, unknown. +type TokenType int + +// runeTokenClass is the type of a UTF-8 character classification: A quote, space, escape. +type runeTokenClass int + +// the internal state used by the lexer state machine +type lexerState int + +// Token is a (type, value) pair representing a lexographical token. +type Token struct { + tokenType TokenType + value string +} + +// Equal reports whether tokens a, and b, are equal. +// Two tokens are equal if both their types and values are equal. A nil token can +// never be equal to another token. +func (a *Token) Equal(b *Token) bool { + if a == nil || b == nil { + return false + } + if a.tokenType != b.tokenType { + return false + } + return a.value == b.value +} + +// Named classes of UTF-8 runes +const ( + spaceRunes = " \t\r\n" + escapingQuoteRunes = `"` + nonEscapingQuoteRunes = "'" + escapeRunes = `\` + commentRunes = "#" +) + +// Classes of rune token +const ( + unknownRuneClass runeTokenClass = iota + spaceRuneClass + escapingQuoteRuneClass + nonEscapingQuoteRuneClass + escapeRuneClass + commentRuneClass + eofRuneClass +) + +// Classes of lexographic token +const ( + UnknownToken TokenType = iota + WordToken + SpaceToken + CommentToken +) + +// Lexer state machine states +const ( + startState lexerState = iota // no runes have been seen + inWordState // processing regular runes in a word + escapingState // we have just consumed an escape rune; the next rune is literal + escapingQuotedState // we have just consumed an escape rune within a quoted string + quotingEscapingState // we are within a quoted string that supports escaping ("...") + quotingState // we are within a string that does not support escaping ('...') + commentState // we are within a comment (everything following an unquoted or unescaped # +) + +// tokenClassifier is used for classifying rune characters. +type tokenClassifier map[rune]runeTokenClass + +func (typeMap tokenClassifier) addRuneClass(runes string, tokenType runeTokenClass) { + for _, runeChar := range runes { + typeMap[runeChar] = tokenType + } +} + +// newDefaultClassifier creates a new classifier for ASCII characters. +func newDefaultClassifier() tokenClassifier { + t := tokenClassifier{} + t.addRuneClass(spaceRunes, spaceRuneClass) + t.addRuneClass(escapingQuoteRunes, escapingQuoteRuneClass) + t.addRuneClass(nonEscapingQuoteRunes, nonEscapingQuoteRuneClass) + t.addRuneClass(escapeRunes, escapeRuneClass) + t.addRuneClass(commentRunes, commentRuneClass) + return t +} + +// ClassifyRune classifiees a rune +func (t tokenClassifier) ClassifyRune(runeVal rune) runeTokenClass { + return t[runeVal] +} + +// Lexer turns an input stream into a sequence of tokens. Whitespace and comments are skipped. +type Lexer Tokenizer + +// NewLexer creates a new lexer from an input stream. +func NewLexer(r io.Reader) *Lexer { + + return (*Lexer)(NewTokenizer(r)) +} + +// Next returns the next word, or an error. If there are no more words, +// the error will be io.EOF. +func (l *Lexer) Next() (string, error) { + for { + token, err := (*Tokenizer)(l).Next() + if err != nil { + return "", err + } + switch token.tokenType { + case WordToken: + return token.value, nil + case CommentToken: + // skip comments + default: + return "", fmt.Errorf("Unknown token type: %v", token.tokenType) + } + } +} + +// Tokenizer turns an input stream into a sequence of typed tokens +type Tokenizer struct { + input bufio.Reader + classifier tokenClassifier +} + +// NewTokenizer creates a new tokenizer from an input stream. +func NewTokenizer(r io.Reader) *Tokenizer { + input := bufio.NewReader(r) + classifier := newDefaultClassifier() + return &Tokenizer{ + input: *input, + classifier: classifier} +} + +// scanStream scans the stream for the next token using the internal state machine. +// It will panic if it encounters a rune which it does not know how to handle. +func (t *Tokenizer) scanStream() (*Token, error) { + state := startState + var tokenType TokenType + var value []rune + var nextRune rune + var nextRuneType runeTokenClass + var err error + + for { + nextRune, _, err = t.input.ReadRune() + nextRuneType = t.classifier.ClassifyRune(nextRune) + + if err == io.EOF { + nextRuneType = eofRuneClass + err = nil + } else if err != nil { + return nil, err + } + + switch state { + case startState: // no runes read yet + { + switch nextRuneType { + case eofRuneClass: + { + return nil, io.EOF + } + case spaceRuneClass: + { + } + case escapingQuoteRuneClass: + { + tokenType = WordToken + state = quotingEscapingState + } + case nonEscapingQuoteRuneClass: + { + tokenType = WordToken + state = quotingState + } + case escapeRuneClass: + { + tokenType = WordToken + state = escapingState + } + case commentRuneClass: + { + tokenType = CommentToken + state = commentState + } + default: + { + tokenType = WordToken + value = append(value, nextRune) + state = inWordState + } + } + } + case inWordState: // in a regular word + { + switch nextRuneType { + case eofRuneClass: + { + token := &Token{ + tokenType: tokenType, + value: string(value)} + return token, err + } + case spaceRuneClass: + { + token := &Token{ + tokenType: tokenType, + value: string(value)} + return token, err + } + case escapingQuoteRuneClass: + { + state = quotingEscapingState + } + case nonEscapingQuoteRuneClass: + { + state = quotingState + } + case escapeRuneClass: + { + state = escapingState + } + default: + { + value = append(value, nextRune) + } + } + } + case escapingState: // the rune after an escape character + { + switch nextRuneType { + case eofRuneClass: + { + err = fmt.Errorf("EOF found after escape character") + token := &Token{ + tokenType: tokenType, + value: string(value)} + return token, err + } + default: + { + state = inWordState + value = append(value, nextRune) + } + } + } + case escapingQuotedState: // the next rune after an escape character, in double quotes + { + switch nextRuneType { + case eofRuneClass: + { + err = fmt.Errorf("EOF found after escape character") + token := &Token{ + tokenType: tokenType, + value: string(value)} + return token, err + } + default: + { + state = quotingEscapingState + value = append(value, nextRune) + } + } + } + case quotingEscapingState: // in escaping double quotes + { + switch nextRuneType { + case eofRuneClass: + { + err = fmt.Errorf("EOF found when expecting closing quote") + token := &Token{ + tokenType: tokenType, + value: string(value)} + return token, err + } + case escapingQuoteRuneClass: + { + state = inWordState + } + case escapeRuneClass: + { + state = escapingQuotedState + } + default: + { + value = append(value, nextRune) + } + } + } + case quotingState: // in non-escaping single quotes + { + switch nextRuneType { + case eofRuneClass: + { + err = fmt.Errorf("EOF found when expecting closing quote") + token := &Token{ + tokenType: tokenType, + value: string(value)} + return token, err + } + case nonEscapingQuoteRuneClass: + { + state = inWordState + } + default: + { + value = append(value, nextRune) + } + } + } + case commentState: // in a comment + { + switch nextRuneType { + case eofRuneClass: + { + token := &Token{ + tokenType: tokenType, + value: string(value)} + return token, err + } + case spaceRuneClass: + { + if nextRune == '\n' { + state = startState + token := &Token{ + tokenType: tokenType, + value: string(value)} + return token, err + } else { + value = append(value, nextRune) + } + } + default: + { + value = append(value, nextRune) + } + } + } + default: + { + return nil, fmt.Errorf("Unexpected state: %v", state) + } + } + } +} + +// Next returns the next token in the stream. +func (t *Tokenizer) Next() (*Token, error) { + return t.scanStream() +} + +// Split partitions a string into a slice of strings. +func Split(s string) ([]string, error) { + l := NewLexer(strings.NewReader(s)) + subStrings := make([]string, 0) + for { + word, err := l.Next() + if err != nil { + if err == io.EOF { + return subStrings, nil + } + return subStrings, err + } + subStrings = append(subStrings, word) + } +} diff --git a/vendor/github.com/opencontainers/selinux/go-selinux/selinux_linux.go b/vendor/github.com/opencontainers/selinux/go-selinux/selinux_linux.go index 2cd54eac1..bbaa1e0d7 100644 --- a/vendor/github.com/opencontainers/selinux/go-selinux/selinux_linux.go +++ b/vendor/github.com/opencontainers/selinux/go-selinux/selinux_linux.go @@ -687,7 +687,11 @@ func Chcon(fpath string, label string, recurse bool) error { return err } callback := func(p string, info os.FileInfo, err error) error { - return SetFileLabel(p, label) + e := SetFileLabel(p, label) + if os.IsNotExist(e) { + return nil + } + return e } if recurse { diff --git a/vendor/github.com/openshift/imagebuilder/builder.go b/vendor/github.com/openshift/imagebuilder/builder.go index 1c1afb119..d37965df6 100644 --- a/vendor/github.com/openshift/imagebuilder/builder.go +++ b/vendor/github.com/openshift/imagebuilder/builder.go @@ -172,8 +172,11 @@ type Stage struct { Node *parser.Node } -func NewStages(node *parser.Node, b *Builder) Stages { +func NewStages(node *parser.Node, b *Builder) (Stages, error) { var stages Stages + if err := b.extractHeadingArgsFromNode(node); err != nil { + return stages, err + } for i, root := range SplitBy(node, command.From) { name, _ := extractNameFromNode(root.Children[0]) if len(name) == 0 { @@ -189,7 +192,36 @@ func NewStages(node *parser.Node, b *Builder) Stages { Node: root, }) } - return stages + return stages, nil +} + +func (b *Builder) extractHeadingArgsFromNode(node *parser.Node) error { + var args []*parser.Node + var children []*parser.Node + extract := true + for _, child := range node.Children { + if extract && child.Value == command.Arg { + args = append(args, child) + } else { + if child.Value == command.From { + extract = false + } + children = append(children, child) + } + } + + for _, c := range args { + step := b.Step() + if err := step.Resolve(c); err != nil { + return err + } + if err := b.Run(step, NoopExecutor, false); err != nil { + return err + } + } + + node.Children = children + return nil } func extractNameFromNode(node *parser.Node) (string, bool) { @@ -345,6 +377,9 @@ var ErrNoFROM = fmt.Errorf("no FROM statement found") // is set to the first From found, or left unchanged if already // set. func (b *Builder) From(node *parser.Node) (string, error) { + if err := b.extractHeadingArgsFromNode(node); err != nil { + return "", err + } children := SplitChildren(node, command.From) switch { case len(children) == 0: diff --git a/vendor/github.com/openshift/imagebuilder/dispatchers.go b/vendor/github.com/openshift/imagebuilder/dispatchers.go index 068d5cc6f..f6510c2fd 100644 --- a/vendor/github.com/openshift/imagebuilder/dispatchers.go +++ b/vendor/github.com/openshift/imagebuilder/dispatchers.go @@ -27,11 +27,6 @@ var ( obRgex = regexp.MustCompile(`(?i)^\s*ONBUILD\s*`) ) -// dispatch with no layer / parsing. This is effectively not a command. -func nullDispatch(b *Builder, args []string, attributes map[string]bool, flagArgs []string, original string) error { - return nil -} - // ENV foo bar // // Sets the environment variable foo to bar, also makes interpolation @@ -181,6 +176,17 @@ func from(b *Builder, args []string, attributes map[string]bool, flagArgs []stri } name := args[0] + + // Support ARG before from + argStrs := []string{} + for n, v := range b.Args { + argStrs = append(argStrs, n+"="+v) + } + var err error + if name, err = ProcessWord(name, argStrs); err != nil { + return err + } + // Windows cannot support a container with no base image. if name == NoBaseImageSpecifier { if runtime.GOOS == "windows" { @@ -438,6 +444,7 @@ func healthcheck(b *Builder, args []string, attributes map[string]bool, flagArgs healthcheck := docker.HealthConfig{} flags := flag.NewFlagSet("", flag.ContinueOnError) + flags.String("start-period", "", "") flags.String("interval", "", "") flags.String("timeout", "", "") flRetries := flags.String("retries", "", "") @@ -462,6 +469,12 @@ func healthcheck(b *Builder, args []string, attributes map[string]bool, flagArgs return fmt.Errorf("Unknown type %#v in HEALTHCHECK (try CMD)", typ) } + period, err := parseOptInterval(flags.Lookup("start-period")) + if err != nil { + return err + } + healthcheck.StartPeriod = period + interval, err := parseOptInterval(flags.Lookup("interval")) if err != nil { return err diff --git a/vendor/github.com/openshift/imagebuilder/evaluator.go b/vendor/github.com/openshift/imagebuilder/evaluator.go index 83263127e..e1cd5d6d6 100644 --- a/vendor/github.com/openshift/imagebuilder/evaluator.go +++ b/vendor/github.com/openshift/imagebuilder/evaluator.go @@ -122,8 +122,7 @@ func (b *Step) Resolve(ast *parser.Node) error { envs := b.Env for ast.Next != nil { ast = ast.Next - var str string - str = ast.Value + str := ast.Value if replaceEnvAllowed[cmd] { var err error var words []string diff --git a/vendor/github.com/ostreedev/ostree-go/pkg/otadmin/admin.go b/vendor/github.com/ostreedev/ostree-go/pkg/otadmin/admin.go deleted file mode 100644 index e69de29bb..000000000 --- a/vendor/github.com/ostreedev/ostree-go/pkg/otadmin/admin.go +++ /dev/null diff --git a/vendor/github.com/ostreedev/ostree-go/pkg/otadmin/admin.go.h b/vendor/github.com/ostreedev/ostree-go/pkg/otadmin/admin.go.h deleted file mode 100644 index e69de29bb..000000000 --- a/vendor/github.com/ostreedev/ostree-go/pkg/otadmin/admin.go.h +++ /dev/null diff --git a/vendor/github.com/ostreedev/ostree-go/pkg/otadmin/admincleanup.go b/vendor/github.com/ostreedev/ostree-go/pkg/otadmin/admincleanup.go deleted file mode 100644 index e69de29bb..000000000 --- a/vendor/github.com/ostreedev/ostree-go/pkg/otadmin/admincleanup.go +++ /dev/null diff --git a/vendor/github.com/ostreedev/ostree-go/pkg/otadmin/admindeploy.go b/vendor/github.com/ostreedev/ostree-go/pkg/otadmin/admindeploy.go deleted file mode 100644 index e69de29bb..000000000 --- a/vendor/github.com/ostreedev/ostree-go/pkg/otadmin/admindeploy.go +++ /dev/null diff --git a/vendor/github.com/ostreedev/ostree-go/pkg/otadmin/admindiff.go b/vendor/github.com/ostreedev/ostree-go/pkg/otadmin/admindiff.go deleted file mode 100644 index e69de29bb..000000000 --- a/vendor/github.com/ostreedev/ostree-go/pkg/otadmin/admindiff.go +++ /dev/null diff --git a/vendor/github.com/ostreedev/ostree-go/pkg/otadmin/admininit.go b/vendor/github.com/ostreedev/ostree-go/pkg/otadmin/admininit.go deleted file mode 100644 index e69de29bb..000000000 --- a/vendor/github.com/ostreedev/ostree-go/pkg/otadmin/admininit.go +++ /dev/null diff --git a/vendor/github.com/ostreedev/ostree-go/pkg/otadmin/admininstutil.go b/vendor/github.com/ostreedev/ostree-go/pkg/otadmin/admininstutil.go deleted file mode 100644 index e69de29bb..000000000 --- a/vendor/github.com/ostreedev/ostree-go/pkg/otadmin/admininstutil.go +++ /dev/null diff --git a/vendor/github.com/ostreedev/ostree-go/pkg/otadmin/adminosinit.go b/vendor/github.com/ostreedev/ostree-go/pkg/otadmin/adminosinit.go deleted file mode 100644 index e69de29bb..000000000 --- a/vendor/github.com/ostreedev/ostree-go/pkg/otadmin/adminosinit.go +++ /dev/null diff --git a/vendor/github.com/ostreedev/ostree-go/pkg/otadmin/adminsetorigin.go b/vendor/github.com/ostreedev/ostree-go/pkg/otadmin/adminsetorigin.go deleted file mode 100644 index e69de29bb..000000000 --- a/vendor/github.com/ostreedev/ostree-go/pkg/otadmin/adminsetorigin.go +++ /dev/null diff --git a/vendor/github.com/ostreedev/ostree-go/pkg/otadmin/adminstatus.go b/vendor/github.com/ostreedev/ostree-go/pkg/otadmin/adminstatus.go deleted file mode 100644 index e69de29bb..000000000 --- a/vendor/github.com/ostreedev/ostree-go/pkg/otadmin/adminstatus.go +++ /dev/null diff --git a/vendor/github.com/ostreedev/ostree-go/pkg/otadmin/adminswitch.go b/vendor/github.com/ostreedev/ostree-go/pkg/otadmin/adminswitch.go deleted file mode 100644 index e69de29bb..000000000 --- a/vendor/github.com/ostreedev/ostree-go/pkg/otadmin/adminswitch.go +++ /dev/null diff --git a/vendor/github.com/ostreedev/ostree-go/pkg/otadmin/adminundeploy.go b/vendor/github.com/ostreedev/ostree-go/pkg/otadmin/adminundeploy.go deleted file mode 100644 index e69de29bb..000000000 --- a/vendor/github.com/ostreedev/ostree-go/pkg/otadmin/adminundeploy.go +++ /dev/null diff --git a/vendor/github.com/ostreedev/ostree-go/pkg/otadmin/adminunlock.go b/vendor/github.com/ostreedev/ostree-go/pkg/otadmin/adminunlock.go deleted file mode 100644 index e69de29bb..000000000 --- a/vendor/github.com/ostreedev/ostree-go/pkg/otadmin/adminunlock.go +++ /dev/null diff --git a/vendor/github.com/ostreedev/ostree-go/pkg/otadmin/adminupgrade.go b/vendor/github.com/ostreedev/ostree-go/pkg/otadmin/adminupgrade.go deleted file mode 100644 index e69de29bb..000000000 --- a/vendor/github.com/ostreedev/ostree-go/pkg/otadmin/adminupgrade.go +++ /dev/null diff --git a/vendor/github.com/ostreedev/ostree-go/pkg/otbuiltin/builtin.go b/vendor/github.com/ostreedev/ostree-go/pkg/otbuiltin/builtin.go index d3a8ae5fd..24822b2b7 100644 --- a/vendor/github.com/ostreedev/ostree-go/pkg/otbuiltin/builtin.go +++ b/vendor/github.com/ostreedev/ostree-go/pkg/otbuiltin/builtin.go @@ -18,76 +18,102 @@ import ( // #include "builtin.go.h" import "C" +// Repo represents a local ostree repository type Repo struct { - //*glib.GObject ptr unsafe.Pointer } -// Converts an ostree repo struct to its C equivalent +// isInitialized checks if the repo has been initialized +func (r *Repo) isInitialized() bool { + if r == nil || r.ptr == nil { + return false + } + return true +} + +// native converts an ostree repo struct to its C equivalent func (r *Repo) native() *C.OstreeRepo { - //return (*C.OstreeRepo)(r.Ptr()) + if !r.isInitialized() { + return nil + } return (*C.OstreeRepo)(r.ptr) } -// Takes a C ostree repo and converts it to a Go struct -func repoFromNative(p *C.OstreeRepo) *Repo { - if p == nil { +// repoFromNative takes a C ostree repo and converts it to a Go struct +func repoFromNative(or *C.OstreeRepo) *Repo { + if or == nil { return nil } - //o := (*glib.GObject)(unsafe.Pointer(p)) - //r := &Repo{o} - r := &Repo{unsafe.Pointer(p)} + r := &Repo{unsafe.Pointer(or)} return r } -// Checks if the repo has been initialized -func (r *Repo) isInitialized() bool { - if r.ptr != nil { - return true +// OpenRepo attempts to open the repo at the given path +func OpenRepo(path string) (*Repo, error) { + if path == "" { + return nil, errors.New("empty path") } - return false -} -// Attempts to open the repo at the given path -func OpenRepo(path string) (*Repo, error) { - var cerr *C.GError = nil cpath := C.CString(path) - pathc := C.g_file_new_for_path(cpath) - defer C.g_object_unref(C.gpointer(pathc)) - crepo := C.ostree_repo_new(pathc) + defer C.free(unsafe.Pointer(cpath)) + repoPath := C.g_file_new_for_path(cpath) + defer C.g_object_unref(C.gpointer(repoPath)) + crepo := C.ostree_repo_new(repoPath) repo := repoFromNative(crepo) + + var cerr *C.GError r := glib.GoBool(glib.GBoolean(C.ostree_repo_open(crepo, nil, &cerr))) if !r { return nil, generateError(cerr) } + return repo, nil } -// Enable support for tombstone commits, which allow the repo to distinguish between -// commits that were intentionally deleted and commits that were removed accidentally -func enableTombstoneCommits(repo *Repo) error { - var tombstoneCommits bool - var config *C.GKeyFile = C.ostree_repo_get_config(repo.native()) - var cerr *C.GError +// enableTombstoneCommits enables support for tombstone commits. +// +// This allows to distinguish between intentional deletions and accidental removals +// of commits. +func (r *Repo) enableTombstoneCommits() error { + if !r.isInitialized() { + return errors.New("repo not initialized") + } - tombstoneCommits = glib.GoBool(glib.GBoolean(C.g_key_file_get_boolean(config, (*C.gchar)(C.CString("core")), (*C.gchar)(C.CString("tombstone-commits")), nil))) + config := C.ostree_repo_get_config(r.native()) + groupC := C.CString("core") + defer C.free(unsafe.Pointer(groupC)) + keyC := C.CString("tombstone-commits") + defer C.free(unsafe.Pointer(keyC)) + valueC := C.g_key_file_get_boolean(config, (*C.gchar)(groupC), (*C.gchar)(keyC), nil) + tombstoneCommits := glib.GoBool(glib.GBoolean(valueC)) - //tombstoneCommits is false only if it really is false or if it is set to FALSE in the config file + // tombstoneCommits is false only if it really is false or if it is set to FALSE in the config file if !tombstoneCommits { - C.g_key_file_set_boolean(config, (*C.gchar)(C.CString("core")), (*C.gchar)(C.CString("tombstone-commits")), C.TRUE) - if !glib.GoBool(glib.GBoolean(C.ostree_repo_write_config(repo.native(), config, &cerr))) { + var cerr *C.GError + C.g_key_file_set_boolean(config, (*C.gchar)(groupC), (*C.gchar)(keyC), C.TRUE) + if !glib.GoBool(glib.GBoolean(C.ostree_repo_write_config(r.native(), config, &cerr))) { return generateError(cerr) } } return nil } +// generateError wraps a GLib error into a Go one. func generateError(err *C.GError) error { + if err == nil { + return errors.New("nil GError") + } + goErr := glib.ConvertGError(glib.ToGError(unsafe.Pointer(err))) _, file, line, ok := runtime.Caller(1) if ok { - return errors.New(fmt.Sprintf("%s:%d - %s", file, line, goErr)) - } else { - return goErr + return fmt.Errorf("%s:%d - %s", file, line, goErr) } + return goErr +} + +// isOk wraps a return value (gboolean/gint) into a bool. +// 0 is false/error, everything else is true/ok. +func isOk(v C.int) bool { + return glib.GoBool(glib.GBoolean(v)) } diff --git a/vendor/github.com/ostreedev/ostree-go/pkg/otbuiltin/builtin.go.h b/vendor/github.com/ostreedev/ostree-go/pkg/otbuiltin/builtin.go.h index 734de9821..76171554d 100644 --- a/vendor/github.com/ostreedev/ostree-go/pkg/otbuiltin/builtin.go.h +++ b/vendor/github.com/ostreedev/ostree-go/pkg/otbuiltin/builtin.go.h @@ -33,24 +33,12 @@ _ostree_repo_file(GFile *file) return OSTREE_REPO_FILE (file); } -static guint -_gpointer_to_uint (gpointer ptr) -{ - return GPOINTER_TO_UINT (ptr); -} - static gpointer _guint_to_pointer (guint u) { return GUINT_TO_POINTER (u); } -static void -_g_clear_object (volatile GObject **object_ptr) -{ - g_clear_object(object_ptr); -} - static const GVariantType* _g_variant_type (char *type) { diff --git a/vendor/github.com/ostreedev/ostree-go/pkg/otbuiltin/cat.go b/vendor/github.com/ostreedev/ostree-go/pkg/otbuiltin/cat.go deleted file mode 100644 index d43ea07c7..000000000 --- a/vendor/github.com/ostreedev/ostree-go/pkg/otbuiltin/cat.go +++ /dev/null @@ -1 +0,0 @@ -package otbuiltin diff --git a/vendor/github.com/ostreedev/ostree-go/pkg/otbuiltin/checkout.go b/vendor/github.com/ostreedev/ostree-go/pkg/otbuiltin/checkout.go index 55b51bfbd..04ada1792 100644 --- a/vendor/github.com/ostreedev/ostree-go/pkg/otbuiltin/checkout.go +++ b/vendor/github.com/ostreedev/ostree-go/pkg/otbuiltin/checkout.go @@ -1,7 +1,7 @@ package otbuiltin import ( - "strings" + "errors" "unsafe" glib "github.com/ostreedev/ostree-go/pkg/glibobject" @@ -14,34 +14,42 @@ import ( // #include "builtin.go.h" import "C" -// Global variable for options -var checkoutOpts checkoutOptions - -// Contains all of the options for checking commits out of -// an ostree repo +// checkoutOptions defines all of the options for checking commits +// out of an ostree repo +// +// Note: while this is private, fields are public and part of the API. type checkoutOptions struct { - UserMode bool // Do not change file ownership or initialize extended attributes - Union bool // Keep existing directories and unchanged files, overwriting existing filesystem - AllowNoent bool // Do nothing if the specified filepath does not exist - DisableCache bool // Do not update or use the internal repository uncompressed object caceh - Whiteouts bool // Process 'whiteout' (docker style) entries - RequireHardlinks bool // Do not fall back to full copies if hard linking fails - Subpath string // Checkout sub-directory path - FromFile string // Process many checkouts from the given file + // UserMode defines whether to checkout a repo in `bare-user` mode + UserMode bool + // Union specifies whether to overwrite existing filesystem entries + Union bool + // AllowNoEnt defines whether to skip filepaths that do not exist + AllowNoent bool + // DisableCache defines whether to disable internal repository uncompressed object cache + DisableCache bool + // Whiteouts defines whether to Process 'whiteout' (docker style) entries + Whiteouts bool + // RequireHardlinks defines whether to fall back to full copies if hard linking fails + RequireHardlinks bool + // SubPath specifies a sub-directory to use for checkout + Subpath string + // FromFile specifies an optional file containing many checkouts to process + FromFile string } -// Instantiates and returns a checkoutOptions struct with default values set +// NewCheckoutOptions instantiates and returns a checkoutOptions struct with default values set func NewCheckoutOptions() checkoutOptions { return checkoutOptions{} } -// Checks out a commit with the given ref from a repository at the location of repo path to to the destination. Returns an error if the checkout could not be processed -func Checkout(repoPath, destination, commit string, opts checkoutOptions) error { - checkoutOpts = opts - +// Checkout checks out commit `commitRef` from a repository at `repoPath`, +// writing it to `destination`. Returns an error if the checkout could not be processed. +func Checkout(repoPath, destination, commitRef string, opts checkoutOptions) error { var cancellable *glib.GCancellable - ccommit := C.CString(commit) + + ccommit := C.CString(commitRef) defer C.free(unsafe.Pointer(ccommit)) + var gerr = glib.NewGError() cerr := (*C.GError)(gerr.Ptr()) defer C.free(unsafe.Pointer(cerr)) @@ -53,50 +61,48 @@ func Checkout(repoPath, destination, commit string, opts checkoutOptions) error return generateError(cerr) } - if strings.Compare(checkoutOpts.FromFile, "") != 0 { - err := processManyCheckouts(crepo, destination, cancellable) - if err != nil { - return err - } - } else { - var resolvedCommit *C.char - defer C.free(unsafe.Pointer(resolvedCommit)) - if !glib.GoBool(glib.GBoolean(C.ostree_repo_resolve_rev(crepo, ccommit, C.FALSE, &resolvedCommit, &cerr))) { - return generateError(cerr) - } - err := processOneCheckout(crepo, resolvedCommit, checkoutOpts.Subpath, destination, cancellable) - if err != nil { - return err - } + // Multiple checkouts to process + if opts.FromFile != "" { + return processManyCheckouts(crepo, destination, cancellable) } - return nil + + // Simple single checkout + var resolvedCommit *C.char + defer C.free(unsafe.Pointer(resolvedCommit)) + if !glib.GoBool(glib.GBoolean(C.ostree_repo_resolve_rev(crepo, ccommit, C.FALSE, &resolvedCommit, &cerr))) { + return generateError(cerr) + } + + return processOneCheckout(crepo, resolvedCommit, destination, opts, cancellable) } -// Processes one checkout from the repo -func processOneCheckout(crepo *C.OstreeRepo, resolvedCommit *C.char, subpath, destination string, cancellable *glib.GCancellable) error { +// processOneCheckout processes one checkout from the repo +func processOneCheckout(crepo *C.OstreeRepo, resolvedCommit *C.char, destination string, opts checkoutOptions, cancellable *glib.GCancellable) error { cdest := C.CString(destination) defer C.free(unsafe.Pointer(cdest)) + var gerr = glib.NewGError() cerr := (*C.GError)(gerr.Ptr()) defer C.free(unsafe.Pointer(cerr)) - var repoCheckoutAtOptions C.OstreeRepoCheckoutAtOptions - if checkoutOpts.UserMode { + // Process options into bitflags + var repoCheckoutAtOptions C.OstreeRepoCheckoutAtOptions + if opts.UserMode { repoCheckoutAtOptions.mode = C.OSTREE_REPO_CHECKOUT_MODE_USER } - if checkoutOpts.Union { + if opts.Union { repoCheckoutAtOptions.overwrite_mode = C.OSTREE_REPO_CHECKOUT_OVERWRITE_UNION_FILES } - checkedOut := glib.GoBool(glib.GBoolean(C.ostree_repo_checkout_at(crepo, &repoCheckoutAtOptions, C._at_fdcwd(), cdest, resolvedCommit, nil, &cerr))) - if !checkedOut { + // Checkout commit to destination + if !glib.GoBool(glib.GBoolean(C.ostree_repo_checkout_at(crepo, &repoCheckoutAtOptions, C._at_fdcwd(), cdest, resolvedCommit, nil, &cerr))) { return generateError(cerr) } return nil } -// process many checkouts +// processManyCheckouts processes many checkouts in a single batch func processManyCheckouts(crepo *C.OstreeRepo, target string, cancellable *glib.GCancellable) error { - return nil + return errors.New("batch checkouts processing: not implemented") } diff --git a/vendor/github.com/ostreedev/ostree-go/pkg/otbuiltin/checksum.go b/vendor/github.com/ostreedev/ostree-go/pkg/otbuiltin/checksum.go deleted file mode 100644 index d43ea07c7..000000000 --- a/vendor/github.com/ostreedev/ostree-go/pkg/otbuiltin/checksum.go +++ /dev/null @@ -1 +0,0 @@ -package otbuiltin diff --git a/vendor/github.com/ostreedev/ostree-go/pkg/otbuiltin/commit.go b/vendor/github.com/ostreedev/ostree-go/pkg/otbuiltin/commit.go index 9550f802c..ccaff7a10 100644 --- a/vendor/github.com/ostreedev/ostree-go/pkg/otbuiltin/commit.go +++ b/vendor/github.com/ostreedev/ostree-go/pkg/otbuiltin/commit.go @@ -59,11 +59,11 @@ func NewCommitOptions() commitOptions { } type OstreeRepoTransactionStats struct { - metadata_objects_total int32 + metadata_objects_total int32 metadata_objects_written int32 - content_objects_total int32 - content_objects_written int32 - content_bytes_written uint64 + content_objects_total int32 + content_objects_written int32 + content_bytes_written uint64 } func (repo *Repo) PrepareTransaction() (bool, error) { @@ -125,6 +125,7 @@ func (repo *Repo) RegenerateSummary() error { // Commits a directory, specified by commitPath, to an ostree repo as a given branch func (repo *Repo) Commit(commitPath, branch string, opts commitOptions) (string, error) { + // TODO(lucab): `options` is global un-synchronized mutable state, get rid of it. options = opts var err error @@ -140,7 +141,7 @@ func (repo *Repo) Commit(commitPath, branch string, opts commitOptions) (string, var cerr *C.GError defer C.free(unsafe.Pointer(cerr)) var metadata *C.GVariant = nil - defer func(){ + defer func() { if metadata != nil { defer C.g_variant_unref(metadata) } @@ -196,7 +197,7 @@ func (repo *Repo) Commit(commitPath, branch string, opts commitOptions) (string, } if options.AddDetachedMetadataString != nil { - _, err := parseKeyValueStrings(options.AddDetachedMetadataString) + _, err = parseKeyValueStrings(options.AddDetachedMetadataString) if err != nil { goto out } @@ -476,7 +477,7 @@ func handleStatOverrideLine(line string, table *glib.GHashTable) error { // Handle an individual line from a Skiplist file func handleSkipListline(line string, table *glib.GHashTable) error { - C.g_hash_table_add((*C.GHashTable)(table.Ptr()), C.gpointer( C.g_strdup((*C.gchar)(C.CString(line))))) + C.g_hash_table_add((*C.GHashTable)(table.Ptr()), C.gpointer(C.g_strdup((*C.gchar)(C.CString(line))))) return nil } diff --git a/vendor/github.com/ostreedev/ostree-go/pkg/otbuiltin/config.go b/vendor/github.com/ostreedev/ostree-go/pkg/otbuiltin/config.go deleted file mode 100644 index d43ea07c7..000000000 --- a/vendor/github.com/ostreedev/ostree-go/pkg/otbuiltin/config.go +++ /dev/null @@ -1 +0,0 @@ -package otbuiltin diff --git a/vendor/github.com/ostreedev/ostree-go/pkg/otbuiltin/diff.go b/vendor/github.com/ostreedev/ostree-go/pkg/otbuiltin/diff.go deleted file mode 100644 index d43ea07c7..000000000 --- a/vendor/github.com/ostreedev/ostree-go/pkg/otbuiltin/diff.go +++ /dev/null @@ -1 +0,0 @@ -package otbuiltin diff --git a/vendor/github.com/ostreedev/ostree-go/pkg/otbuiltin/export.go b/vendor/github.com/ostreedev/ostree-go/pkg/otbuiltin/export.go deleted file mode 100644 index d43ea07c7..000000000 --- a/vendor/github.com/ostreedev/ostree-go/pkg/otbuiltin/export.go +++ /dev/null @@ -1 +0,0 @@ -package otbuiltin diff --git a/vendor/github.com/ostreedev/ostree-go/pkg/otbuiltin/fsck.go b/vendor/github.com/ostreedev/ostree-go/pkg/otbuiltin/fsck.go deleted file mode 100644 index d43ea07c7..000000000 --- a/vendor/github.com/ostreedev/ostree-go/pkg/otbuiltin/fsck.go +++ /dev/null @@ -1 +0,0 @@ -package otbuiltin diff --git a/vendor/github.com/ostreedev/ostree-go/pkg/otbuiltin/gpgsign.go b/vendor/github.com/ostreedev/ostree-go/pkg/otbuiltin/gpgsign.go deleted file mode 100644 index d43ea07c7..000000000 --- a/vendor/github.com/ostreedev/ostree-go/pkg/otbuiltin/gpgsign.go +++ /dev/null @@ -1 +0,0 @@ -package otbuiltin diff --git a/vendor/github.com/ostreedev/ostree-go/pkg/otbuiltin/init.go b/vendor/github.com/ostreedev/ostree-go/pkg/otbuiltin/init.go index c1ca2dc7e..6ee6671b4 100644 --- a/vendor/github.com/ostreedev/ostree-go/pkg/otbuiltin/init.go +++ b/vendor/github.com/ostreedev/ostree-go/pkg/otbuiltin/init.go @@ -1,11 +1,8 @@ package otbuiltin import ( - "errors" "strings" "unsafe" - - glib "github.com/ostreedev/ostree-go/pkg/glibobject" ) // #cgo pkg-config: ostree-1 @@ -15,43 +12,37 @@ import ( // #include "builtin.go.h" import "C" -// Declare variables for options -var initOpts initOptions - -// Contains all of the options for initializing an ostree repo +// initOptions contains all of the options for initializing an ostree repo +// +// Note: while this is private, exported fields are public and part of the API. type initOptions struct { - Mode string // either bare, archive-z2, or bare-user - - repoMode C.OstreeRepoMode + // Mode defines repository mode: either bare, archive-z2, or bare-user + Mode string } -// Instantiates and returns an initOptions struct with default values set +// NewInitOptions instantiates and returns an initOptions struct with default values set func NewInitOptions() initOptions { - io := initOptions{} - io.Mode = "bare" - io.repoMode = C.OSTREE_REPO_MODE_BARE - return io + return initOptions{ + Mode: "bare", + } } -// Initializes a new ostree repository at the given path. Returns true +// Init initializes a new ostree repository at the given path. Returns true // if the repo exists at the location, regardless of whether it was initialized // by the function or if it already existed. Returns an error if the repo could // not be initialized func Init(path string, options initOptions) (bool, error) { - initOpts = options - err := parseMode() + repoMode, err := parseRepoMode(options.Mode) if err != nil { return false, err } // Create a repo struct from the path - var cerr *C.GError - defer C.free(unsafe.Pointer(cerr)) cpath := C.CString(path) defer C.free(unsafe.Pointer(cpath)) pathc := C.g_file_new_for_path(cpath) defer C.g_object_unref(C.gpointer(pathc)) - crepo := C.ostree_repo_new(pathc) + repo := C.ostree_repo_new(pathc) // If the repo exists in the filesystem, return an error but set exists to true /* var exists C.gboolean = 0 @@ -63,28 +54,31 @@ func Init(path string, options initOptions) (bool, error) { return false, generateError(cerr) }*/ - cerr = nil - created := glib.GoBool(glib.GBoolean(C.ostree_repo_create(crepo, initOpts.repoMode, nil, &cerr))) - if !created { - errString := generateError(cerr).Error() - if strings.Contains(errString, "File exists") { - return true, generateError(cerr) + var cErr *C.GError + defer C.free(unsafe.Pointer(cErr)) + if r := C.ostree_repo_create(repo, repoMode, nil, &cErr); !isOk(r) { + err := generateError(cErr) + if strings.Contains(err.Error(), "File exists") { + return true, err } - return false, generateError(cerr) + return false, err } return true, nil } -// Converts the mode string to a C.OSTREE_REPO_MODE enum value -func parseMode() error { - if strings.EqualFold(initOpts.Mode, "bare") { - initOpts.repoMode = C.OSTREE_REPO_MODE_BARE - } else if strings.EqualFold(initOpts.Mode, "bare-user") { - initOpts.repoMode = C.OSTREE_REPO_MODE_BARE_USER - } else if strings.EqualFold(initOpts.Mode, "archive-z2") { - initOpts.repoMode = C.OSTREE_REPO_MODE_ARCHIVE_Z2 - } else { - return errors.New("Invalid option for mode") +// parseRepoMode converts a mode string to a C.OSTREE_REPO_MODE enum value +func parseRepoMode(modeLabel string) (C.OstreeRepoMode, error) { + var cErr *C.GError + defer C.free(unsafe.Pointer(cErr)) + + cModeLabel := C.CString(modeLabel) + defer C.free(unsafe.Pointer(cModeLabel)) + + var retMode C.OstreeRepoMode + if r := C.ostree_repo_mode_from_string(cModeLabel, &retMode, &cErr); !isOk(r) { + // NOTE(lucab): zero-value for this C enum has no special/invalid meaning. + return C.OSTREE_REPO_MODE_BARE, generateError(cErr) } - return nil + + return retMode, nil } diff --git a/vendor/github.com/ostreedev/ostree-go/pkg/otbuiltin/log.go b/vendor/github.com/ostreedev/ostree-go/pkg/otbuiltin/log.go index 2ceea0925..d57498215 100644 --- a/vendor/github.com/ostreedev/ostree-go/pkg/otbuiltin/log.go +++ b/vendor/github.com/ostreedev/ostree-go/pkg/otbuiltin/log.go @@ -2,7 +2,6 @@ package otbuiltin import ( "fmt" - "strings" "time" "unsafe" @@ -16,13 +15,7 @@ import ( // #include "builtin.go.h" import "C" -// Declare variables for options -var logOpts logOptions - -// Set the format of the strings in the log -const formatString = "2006-01-02 03:04;05 -0700" - -// Struct for the various pieces of data in a log entry +// LogEntry is a struct for the various pieces of data in a log entry type LogEntry struct { Checksum []byte Variant []byte @@ -39,24 +32,25 @@ func (l LogEntry) String() string { return fmt.Sprintf("%s\n%s\n\n", l.Checksum, l.Variant) } -type OstreeDumpFlags uint +type ostreeDumpFlags uint const ( - OSTREE_DUMP_NONE OstreeDumpFlags = 0 - OSTREE_DUMP_RAW OstreeDumpFlags = 1 << iota + ostreeDumpNone ostreeDumpFlags = 0 + ostreeDumpRaw ostreeDumpFlags = 1 << iota ) -// Contains all of the options for initializing an ostree repo +// logOptions contains all of the options for initializing an ostree repo type logOptions struct { - Raw bool // Show raw variant data + // Raw determines whether to show raw variant data + Raw bool } -//Instantiates and returns a logOptions struct with default values set +// NewLogOptions instantiates and returns a logOptions struct with default values set func NewLogOptions() logOptions { return logOptions{} } -// Show the logs of a branch starting with a given commit or ref. Returns a +// Log shows the logs of a branch starting with a given commit or ref. Returns a // slice of log entries on success and an error otherwise func Log(repoPath, branch string, options logOptions) ([]LogEntry, error) { // attempt to open the repository @@ -69,12 +63,12 @@ func Log(repoPath, branch string, options logOptions) ([]LogEntry, error) { defer C.free(unsafe.Pointer(cbranch)) var checksum *C.char defer C.free(unsafe.Pointer(checksum)) - var flags OstreeDumpFlags = OSTREE_DUMP_NONE var cerr *C.GError defer C.free(unsafe.Pointer(cerr)) - if logOpts.Raw { - flags |= OSTREE_DUMP_RAW + flags := ostreeDumpNone + if options.Raw { + flags |= ostreeDumpRaw } if !glib.GoBool(glib.GBoolean(C.ostree_repo_resolve_rev(repo.native(), cbranch, C.FALSE, &checksum, &cerr))) { @@ -84,84 +78,86 @@ func Log(repoPath, branch string, options logOptions) ([]LogEntry, error) { return logCommit(repo, checksum, false, flags) } -func logCommit(repo *Repo, checksum *C.char, isRecursive bool, flags OstreeDumpFlags) ([]LogEntry, error) { +func logCommit(repo *Repo, checksum *C.char, isRecursive bool, flags ostreeDumpFlags) ([]LogEntry, error) { var variant *C.GVariant - var parent *C.char - defer C.free(unsafe.Pointer(parent)) var gerr = glib.NewGError() var cerr = (*C.GError)(gerr.Ptr()) defer C.free(unsafe.Pointer(cerr)) - entries := make([]LogEntry, 0, 1) - var err error if !glib.GoBool(glib.GBoolean(C.ostree_repo_load_variant(repo.native(), C.OSTREE_OBJECT_TYPE_COMMIT, checksum, &variant, &cerr))) { if isRecursive && glib.GoBool(glib.GBoolean(C.g_error_matches(cerr, C.g_io_error_quark(), C.G_IO_ERROR_NOT_FOUND))) { return nil, nil } - return entries, generateError(cerr) + return nil, generateError(cerr) } - nextLogEntry := dumpLogObject(C.OSTREE_OBJECT_TYPE_COMMIT, checksum, variant, flags) - - // get the parent of this commit - parent = (*C.char)(C.ostree_commit_get_parent(variant)) + // Get the parent of this commit + parent := (*C.char)(C.ostree_commit_get_parent(variant)) defer C.free(unsafe.Pointer(parent)) + + entries := make([]LogEntry, 0, 1) if parent != nil { + var err error entries, err = logCommit(repo, parent, true, flags) if err != nil { return nil, err } } - entries = append(entries, *nextLogEntry) + + nextLogEntry := dumpLogObject(C.OSTREE_OBJECT_TYPE_COMMIT, checksum, variant, flags) + entries = append(entries, nextLogEntry) + return entries, nil } -func dumpLogObject(objectType C.OstreeObjectType, checksum *C.char, variant *C.GVariant, flags OstreeDumpFlags) *LogEntry { - objLog := new(LogEntry) - objLog.Checksum = []byte(C.GoString(checksum)) +func dumpLogObject(objectType C.OstreeObjectType, checksum *C.char, variant *C.GVariant, flags ostreeDumpFlags) LogEntry { + csum := []byte(C.GoString(checksum)) - if (flags & OSTREE_DUMP_RAW) != 0 { - dumpVariant(objLog, variant) - return objLog + if (flags & ostreeDumpRaw) != 0 { + return dumpVariant(variant, csum) } switch objectType { case C.OSTREE_OBJECT_TYPE_COMMIT: - dumpCommit(objLog, variant, flags) - return objLog + return dumpCommit(variant, flags, csum) default: - return objLog + return LogEntry{ + Checksum: csum, + } } } -func dumpVariant(log *LogEntry, variant *C.GVariant) { - var byteswappedVariant *C.GVariant - +func dumpVariant(variant *C.GVariant, csum []byte) LogEntry { + var logVariant []byte if C.G_BYTE_ORDER != C.G_BIG_ENDIAN { - byteswappedVariant = C.g_variant_byteswap(variant) - log.Variant = []byte(C.GoString((*C.char)(C.g_variant_print(byteswappedVariant, C.TRUE)))) + byteswappedVariant := C.g_variant_byteswap(variant) + logVariant = []byte(C.GoString((*C.char)(C.g_variant_print(byteswappedVariant, C.TRUE)))) } else { - log.Variant = []byte(C.GoString((*C.char)(C.g_variant_print(byteswappedVariant, C.TRUE)))) + logVariant = []byte(C.GoString((*C.char)(C.g_variant_print(variant, C.TRUE)))) + } + + return LogEntry{ + Checksum: csum, + Variant: logVariant, } } -func dumpCommit(log *LogEntry, variant *C.GVariant, flags OstreeDumpFlags) { - var subject, body *C.char +func dumpCommit(variant *C.GVariant, flags ostreeDumpFlags, csum []byte) LogEntry { + var subject *C.char defer C.free(unsafe.Pointer(subject)) + var body *C.char defer C.free(unsafe.Pointer(body)) - var timestamp C.guint64 + var timeBigE C.guint64 - C._g_variant_get_commit_dump(variant, C.CString("(a{sv}aya(say)&s&stayay)"), &subject, &body, ×tamp) + C._g_variant_get_commit_dump(variant, C.CString("(a{sv}aya(say)&s&stayay)"), &subject, &body, &timeBigE) - // Timestamp is now a Unix formatted timestamp as a guint64 - timestamp = C._guint64_from_be(timestamp) - log.Timestamp = time.Unix((int64)(timestamp), 0) - - if strings.Compare(C.GoString(subject), "") != 0 { - log.Subject = C.GoString(subject) - } + // Translate to a host-endian epoch and convert to Go timestamp + timeHostE := C._guint64_from_be(timeBigE) + timestamp := time.Unix((int64)(timeHostE), 0) - if strings.Compare(C.GoString(body), "") != 0 { - log.Body = C.GoString(body) + return LogEntry{ + Timestamp: timestamp, + Subject: C.GoString(subject), + Body: C.GoString(body), } } diff --git a/vendor/github.com/ostreedev/ostree-go/pkg/otbuiltin/ls.go b/vendor/github.com/ostreedev/ostree-go/pkg/otbuiltin/ls.go deleted file mode 100644 index d43ea07c7..000000000 --- a/vendor/github.com/ostreedev/ostree-go/pkg/otbuiltin/ls.go +++ /dev/null @@ -1 +0,0 @@ -package otbuiltin diff --git a/vendor/github.com/ostreedev/ostree-go/pkg/otbuiltin/prune.go b/vendor/github.com/ostreedev/ostree-go/pkg/otbuiltin/prune.go index 8dfa40a55..532522fc5 100644 --- a/vendor/github.com/ostreedev/ostree-go/pkg/otbuiltin/prune.go +++ b/vendor/github.com/ostreedev/ostree-go/pkg/otbuiltin/prune.go @@ -145,7 +145,7 @@ func deleteCommit(repo *Repo, commitToDelete string, cancellable *glib.GCancella } } - if err := enableTombstoneCommits(repo); err != nil { + if err := repo.enableTombstoneCommits(); err != nil { return err } @@ -169,7 +169,7 @@ func pruneCommitsKeepYoungerThanDate(repo *Repo, date time.Time, cancellable *gl var cerr = (*C.GError)(gerr.Ptr()) defer C.free(unsafe.Pointer(cerr)) - if err := enableTombstoneCommits(repo); err != nil { + if err := repo.enableTombstoneCommits(); err != nil { return err } diff --git a/vendor/github.com/ostreedev/ostree-go/pkg/otbuiltin/pull.go b/vendor/github.com/ostreedev/ostree-go/pkg/otbuiltin/pull.go deleted file mode 100644 index d43ea07c7..000000000 --- a/vendor/github.com/ostreedev/ostree-go/pkg/otbuiltin/pull.go +++ /dev/null @@ -1 +0,0 @@ -package otbuiltin diff --git a/vendor/github.com/ostreedev/ostree-go/pkg/otbuiltin/pulllocal.go b/vendor/github.com/ostreedev/ostree-go/pkg/otbuiltin/pulllocal.go deleted file mode 100644 index d43ea07c7..000000000 --- a/vendor/github.com/ostreedev/ostree-go/pkg/otbuiltin/pulllocal.go +++ /dev/null @@ -1 +0,0 @@ -package otbuiltin diff --git a/vendor/github.com/ostreedev/ostree-go/pkg/otbuiltin/refs.go b/vendor/github.com/ostreedev/ostree-go/pkg/otbuiltin/refs.go deleted file mode 100644 index d43ea07c7..000000000 --- a/vendor/github.com/ostreedev/ostree-go/pkg/otbuiltin/refs.go +++ /dev/null @@ -1 +0,0 @@ -package otbuiltin diff --git a/vendor/github.com/ostreedev/ostree-go/pkg/otbuiltin/remote.go b/vendor/github.com/ostreedev/ostree-go/pkg/otbuiltin/remote.go deleted file mode 100644 index d43ea07c7..000000000 --- a/vendor/github.com/ostreedev/ostree-go/pkg/otbuiltin/remote.go +++ /dev/null @@ -1 +0,0 @@ -package otbuiltin diff --git a/vendor/github.com/ostreedev/ostree-go/pkg/otbuiltin/reset.go b/vendor/github.com/ostreedev/ostree-go/pkg/otbuiltin/reset.go deleted file mode 100644 index d43ea07c7..000000000 --- a/vendor/github.com/ostreedev/ostree-go/pkg/otbuiltin/reset.go +++ /dev/null @@ -1 +0,0 @@ -package otbuiltin diff --git a/vendor/github.com/ostreedev/ostree-go/pkg/otbuiltin/revparse.go b/vendor/github.com/ostreedev/ostree-go/pkg/otbuiltin/revparse.go deleted file mode 100644 index d43ea07c7..000000000 --- a/vendor/github.com/ostreedev/ostree-go/pkg/otbuiltin/revparse.go +++ /dev/null @@ -1 +0,0 @@ -package otbuiltin diff --git a/vendor/github.com/ostreedev/ostree-go/pkg/otbuiltin/show.go b/vendor/github.com/ostreedev/ostree-go/pkg/otbuiltin/show.go deleted file mode 100644 index d43ea07c7..000000000 --- a/vendor/github.com/ostreedev/ostree-go/pkg/otbuiltin/show.go +++ /dev/null @@ -1 +0,0 @@ -package otbuiltin diff --git a/vendor/github.com/ostreedev/ostree-go/pkg/otbuiltin/staticdelta.go b/vendor/github.com/ostreedev/ostree-go/pkg/otbuiltin/staticdelta.go deleted file mode 100644 index d43ea07c7..000000000 --- a/vendor/github.com/ostreedev/ostree-go/pkg/otbuiltin/staticdelta.go +++ /dev/null @@ -1 +0,0 @@ -package otbuiltin diff --git a/vendor/github.com/ostreedev/ostree-go/pkg/otbuiltin/summary.go b/vendor/github.com/ostreedev/ostree-go/pkg/otbuiltin/summary.go deleted file mode 100644 index d43ea07c7..000000000 --- a/vendor/github.com/ostreedev/ostree-go/pkg/otbuiltin/summary.go +++ /dev/null @@ -1 +0,0 @@ -package otbuiltin diff --git a/vendor/github.com/ostreedev/ostree-go/pkg/otbuiltin/trivialhttpd.go b/vendor/github.com/ostreedev/ostree-go/pkg/otbuiltin/trivialhttpd.go deleted file mode 100644 index d43ea07c7..000000000 --- a/vendor/github.com/ostreedev/ostree-go/pkg/otbuiltin/trivialhttpd.go +++ /dev/null @@ -1 +0,0 @@ -package otbuiltin diff --git a/vendor/github.com/ostreedev/ostree-go/pkg/otremote/remote.go.h b/vendor/github.com/ostreedev/ostree-go/pkg/otremote/remote.go.h deleted file mode 100644 index e69de29bb..000000000 --- a/vendor/github.com/ostreedev/ostree-go/pkg/otremote/remote.go.h +++ /dev/null diff --git a/vendor/github.com/ostreedev/ostree-go/pkg/otremote/remoteadd.go b/vendor/github.com/ostreedev/ostree-go/pkg/otremote/remoteadd.go deleted file mode 100644 index e69de29bb..000000000 --- a/vendor/github.com/ostreedev/ostree-go/pkg/otremote/remoteadd.go +++ /dev/null diff --git a/vendor/github.com/ostreedev/ostree-go/pkg/otremote/remotedelete.go b/vendor/github.com/ostreedev/ostree-go/pkg/otremote/remotedelete.go deleted file mode 100644 index e69de29bb..000000000 --- a/vendor/github.com/ostreedev/ostree-go/pkg/otremote/remotedelete.go +++ /dev/null diff --git a/vendor/github.com/ostreedev/ostree-go/pkg/otremote/remotegpgimport.go b/vendor/github.com/ostreedev/ostree-go/pkg/otremote/remotegpgimport.go deleted file mode 100644 index e69de29bb..000000000 --- a/vendor/github.com/ostreedev/ostree-go/pkg/otremote/remotegpgimport.go +++ /dev/null diff --git a/vendor/github.com/ostreedev/ostree-go/pkg/otremote/remotelist.go b/vendor/github.com/ostreedev/ostree-go/pkg/otremote/remotelist.go deleted file mode 100644 index e69de29bb..000000000 --- a/vendor/github.com/ostreedev/ostree-go/pkg/otremote/remotelist.go +++ /dev/null diff --git a/vendor/github.com/ostreedev/ostree-go/pkg/otremote/remoterefs.go b/vendor/github.com/ostreedev/ostree-go/pkg/otremote/remoterefs.go deleted file mode 100644 index e69de29bb..000000000 --- a/vendor/github.com/ostreedev/ostree-go/pkg/otremote/remoterefs.go +++ /dev/null diff --git a/vendor/github.com/ostreedev/ostree-go/pkg/otremote/remoteshowurl.go b/vendor/github.com/ostreedev/ostree-go/pkg/otremote/remoteshowurl.go deleted file mode 100644 index e69de29bb..000000000 --- a/vendor/github.com/ostreedev/ostree-go/pkg/otremote/remoteshowurl.go +++ /dev/null diff --git a/vendor/github.com/ostreedev/ostree-go/pkg/otremote/remotesummary.go b/vendor/github.com/ostreedev/ostree-go/pkg/otremote/remotesummary.go deleted file mode 100644 index e69de29bb..000000000 --- a/vendor/github.com/ostreedev/ostree-go/pkg/otremote/remotesummary.go +++ /dev/null diff --git a/version/version.go b/version/version.go index 0fd4e5aeb..01b9b7a8d 100644 --- a/version/version.go +++ b/version/version.go @@ -4,4 +4,4 @@ package version // NOTE: remember to bump the version at the top // of the top-level README.md file when this is // bumped. -const Version = "0.10.2-dev" +const Version = "0.11.2-dev" |