diff options
97 files changed, 1486 insertions, 1610 deletions
diff --git a/.cirrus.yml b/.cirrus.yml index 7bc00dbb5..325176179 100644 --- a/.cirrus.yml +++ b/.cirrus.yml @@ -67,9 +67,9 @@ env: RHEL_BASE_IMAGE: "rhel-guest-image-7-6-210-x86-64-qcow2-1548099756" #### - #### Default to NOT running in rootless-testing mode + #### Default to NOT operating in any special-case testing mode #### - ROOTLESS_USER: "" + SPECIALMODE: "none" # don't do anything special #### #### Credentials and other secret-sauces, decrypted at runtime when authorized. @@ -121,6 +121,7 @@ gating_task: gate_script: # N/B: entrypoint.sh resets $GOSRC (same as make clean) + - '/usr/local/bin/entrypoint.sh install.tools' - '/usr/local/bin/entrypoint.sh validate' - '/usr/local/bin/entrypoint.sh lint' - '${CIRRUS_WORKING_DIR}/${SCRIPT_BASE}/test/test_dot_cirrus_yaml.py' @@ -179,9 +180,9 @@ build_each_commit_task: gce_instance: image_project: "libpod-218412" zone: "us-central1-a" # Required by Cirrus for the time being - cpu: 2 - memory: "4Gb" - disk: 40 + cpu: 8 + memory: "8Gb" + disk: 200 image_name: "${FEDORA_CACHE_IMAGE_NAME}" timeout_in: 30m @@ -257,8 +258,8 @@ testing_task: master_script: '$CIRRUS_WORKING_DIR/$SCRIPT_BASE/notice_master_failure.sh' -# This task executes tests as a regular user on a system -rootless_testing_task: +# This task executes tests under unique environments/conditions +special_testing_task: depends_on: - "gating" @@ -274,15 +275,14 @@ rootless_testing_task: image_name: "${FEDORA_CACHE_IMAGE_NAME}" env: - ROOTLESS_USER: "olympiclongjumpingwithjesus" + matrix: + SPECIALMODE: 'rootless' # See docs + SPECIALMODE: 'in_podman' # See docs timeout_in: 120m setup_environment_script: '$SCRIPT_BASE/setup_environment.sh' - rootless_test_script: >- - ssh $ROOTLESS_USER@localhost - -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -o CheckHostIP=no - $CIRRUS_WORKING_DIR/$SCRIPT_BASE/rootless_test.sh + integration_test_script: '$SCRIPT_BASE/integration_test.sh' on_failure: master_script: '$CIRRUS_WORKING_DIR/$SCRIPT_BASE/notice_master_failure.sh' diff --git a/.papr.yml b/.papr.yml deleted file mode 100644 index ed20c6039..000000000 --- a/.papr.yml +++ /dev/null @@ -1,97 +0,0 @@ -branches: - - master - - auto - - try - -host: - distro: fedora/28/atomic - specs: - ram: 8192 - cpus: 4 -required: true -timeout: 90m - -tests: - - rpm-ostree usroverlay && rpm -Uvh https://kojipkgs.fedoraproject.org//packages/podman/0.10.1/1.gite4a1553.fc28/x86_64/podman-0.10.1-1.gite4a1553.fc28.x86_64.rpm - - CONTAINER_RUNTIME="podman" sh .papr_prepare.sh - -artifacts: - - build.log - -context: "FAH28 - Containerized (Podman in Podman)" - ---- - -host: - distro: centos/7/atomic/smoketested - specs: - ram: 8192 - cpus: 4 -extra-repos: - - name: epel - metalink: https://mirrors.fedoraproject.org/metalink?repo=epel-7&arch=$basearch - gpgcheck: 0 - - name: cri-o - baseurl: https://cbs.centos.org/repos/virt7-container-common-candidate/$basearch/os - gpgcheck: 0 - -required: true - -timeout: 90m - -tests: - - CONTAINER_RUNTIME="docker" sh .papr_prepare.sh - -artifacts: - - build.log - -context: "CAH 7-smoketested - Containerized (Podman in Docker)" - -#--- -# -#host: -# distro: centos/7/cloud -# specs: -# ram: 8192 -# cpus: 4 -#extra-repos: -# - name: epel -# metalink: https://mirrors.fedoraproject.org/metalink?repo=epel-7&arch=$basearch -# gpgcheck: 0 -# - name: cri-o -# baseurl: https://cbs.centos.org/repos/virt7-container-common-candidate/$basearch/os -# gpgcheck: 0 -# -#packages: -# - btrfs-progs-devel -# - glib2-devel -# - glibc-devel -# - glibc-static -# - git -# - go-md2man -# - gpgme-devel -# - libassuan-devel -# - libgpg-error-devel -# - libseccomp-devel -# - libselinux-devel -# - ostree-devel -# - pkgconfig -# - make -# - nc -# - go-compilers-golang-compiler -# - podman -# -#required: true -# -#timeout: 90m -# -#tests: -# - sed 's/^expand-check.*/expand-check=0/g' -i /etc/selinux/semanage.conf -# - sh .papr.sh -b -i -t -# -#artifacts: -# - build.log -# -#context: "CentOS 7 Cloud" -# -#--- diff --git a/.papr_prepare.sh b/.papr_prepare.sh deleted file mode 100644 index b93f7b91f..000000000 --- a/.papr_prepare.sh +++ /dev/null @@ -1,22 +0,0 @@ -#!/bin/bash -set -xeuo pipefail - -DIST=${DIST:=Fedora} -CONTAINER_RUNTIME=${CONTAINER_RUNTIME:=docker} -IMAGE=fedorapodmanbuild -if [[ ${DIST} != "Fedora" ]]; then - IMAGE=centospodmanbuild -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 - -# Run the tests -${CONTAINER_RUNTIME} run --rm --privileged --net=host -v $PWD:/go/src/github.com/containers/libpod:Z --workdir /go/src/github.com/containers/libpod -e CGROUP_MANAGER=cgroupfs -e STORAGE_OPTIONS="--storage-driver=vfs" -e CRIO_ROOT="/go/src/github.com/containers/libpod" -e PODMAN_BINARY="/usr/bin/podman" -e CONMON_BINARY="/usr/libexec/podman/conmon" -e DIST=$DIST -e CONTAINER_RUNTIME=$CONTAINER_RUNTIME $IMAGE sh ./.papr.sh -b -i -t @@ -5,6 +5,8 @@ in the [API.md](https://github.com/containers/libpod/blob/master/API.md) file in [func BuildImage(build: BuildInfo) MoreResponse](#BuildImage) +[func BuildImageHierarchyMap(name: string) string](#BuildImageHierarchyMap) + [func Commit(name: string, image_name: string, changes: []string, author: string, message: string, pause: bool, manifestType: string) string](#Commit) [func ContainerArtifacts(name: string, artifactName: string) string](#ContainerArtifacts) @@ -31,6 +33,8 @@ in the [API.md](https://github.com/containers/libpod/blob/master/API.md) file in [func DeleteUnusedImages() []string](#DeleteUnusedImages) +[func Diff(name: string) DiffInfo](#Diff) + [func ExportContainer(name: string, path: string) string](#ExportContainer) [func ExportImage(name: string, destination: string, compress: bool, tags: []string) string](#ExportImage) @@ -55,6 +59,8 @@ in the [API.md](https://github.com/containers/libpod/blob/master/API.md) file in [func GetInfo() PodmanInfo](#GetInfo) +[func GetLayersMapWithImageInfo() string](#GetLayersMapWithImageInfo) + [func GetPod(name: string) ListPodData](#GetPod) [func GetPodStats(name: string) string, ContainerStats](#GetPodStats) @@ -173,6 +179,8 @@ in the [API.md](https://github.com/containers/libpod/blob/master/API.md) file in [type CreateResourceConfig](#CreateResourceConfig) +[type DiffInfo](#DiffInfo) + [type Event](#Event) [type IDMap](#IDMap) @@ -255,6 +263,11 @@ method BuildImage(build: [BuildInfo](#BuildInfo)) [MoreResponse](#MoreResponse)< BuildImage takes a [BuildInfo](#BuildInfo) structure and builds an image. At a minimum, you must provide the 'dockerfile' and 'tags' options in the BuildInfo structure. It will return a [MoreResponse](#MoreResponse) structure that contains the build logs and resulting image ID. +### <a name="BuildImageHierarchyMap"></a>func BuildImageHierarchyMap +<div style="background-color: #E8E8E8; padding: 15px; margin: 10px; border-radius: 10px;"> + +method BuildImageHierarchyMap(name: [string](https://godoc.org/builtin#string)) [string](https://godoc.org/builtin#string)</div> +BuildImageHierarchyMap is for the development of Podman and should not be used. ### <a name="Commit"></a>func Commit <div style="background-color: #E8E8E8; padding: 15px; margin: 10px; border-radius: 10px;"> @@ -388,6 +401,11 @@ $ varlink call -m unix:/run/podman/io.podman/io.podman.DeleteUnusedImages ] } ~~~ +### <a name="Diff"></a>func Diff +<div style="background-color: #E8E8E8; padding: 15px; margin: 10px; border-radius: 10px;"> + +method Diff(name: [string](https://godoc.org/builtin#string)) [DiffInfo](#DiffInfo)</div> +Diff returns a diff between libpod objects ### <a name="ExportContainer"></a>func ExportContainer <div style="background-color: #E8E8E8; padding: 15px; margin: 10px; border-radius: 10px;"> @@ -511,6 +529,11 @@ If the image caGetImage returns be found, [ImageNotFound](#ImageNotFound) will b method GetInfo() [PodmanInfo](#PodmanInfo)</div> GetInfo returns a [PodmanInfo](#PodmanInfo) struct that describes podman and its host such as storage stats, build information of Podman, and system-wide registries. +### <a name="GetLayersMapWithImageInfo"></a>func GetLayersMapWithImageInfo +<div style="background-color: #E8E8E8; padding: 15px; margin: 10px; border-radius: 10px;"> + +method GetLayersMapWithImageInfo() [string](https://godoc.org/builtin#string)</div> +GetLayersMapWithImageInfo is for the development of Podman and should not be used. ### <a name="GetPod"></a>func GetPod <div style="background-color: #E8E8E8; padding: 15px; margin: 10px; border-radius: 10px;"> @@ -1431,6 +1454,13 @@ pids_limit [int](https://godoc.org/builtin#int) shm_size [int](https://godoc.org/builtin#int) ulimit [[]string](#[]string) +### <a name="DiffInfo"></a>type DiffInfo + + + +path [string](https://godoc.org/builtin#string) + +changeType [string](https://godoc.org/builtin#string) ### <a name="Event"></a>type Event Event describes a libpod struct @@ -1491,6 +1521,8 @@ containers [int](https://godoc.org/builtin#int) labels [map[string]](#map[string]) isParent [bool](https://godoc.org/builtin#bool) + +topLayer [string](https://godoc.org/builtin#string) ### <a name="ImageHistory"></a>type ImageHistory ImageHistory describes the returned structure from ImageHistory. diff --git a/Dockerfile b/Dockerfile index 6d44b963f..83cd3fccd 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,4 @@ -FROM golang:1.11 - -RUN echo 'deb http://httpredir.debian.org/debian jessie-backports main' > /etc/apt/sources.list.d/backports.list +FROM golang:1.12 RUN apt-get update && apt-get install -y \ apparmor \ @@ -23,6 +21,8 @@ RUN apt-get update && apt-get install -y \ libostree-dev \ libprotobuf-dev \ libprotobuf-c0-dev \ + libseccomp2 \ + libseccomp-dev \ libtool \ libudev-dev \ protobuf-c-compiler \ @@ -38,20 +38,11 @@ RUN apt-get update && apt-get install -y \ socat \ lsof \ xz-utils \ + unzip \ + python3-yaml \ --no-install-recommends \ && apt-get clean -ENV LIBSECCOMP_COMMIT release-2.3 -RUN set -x \ - && git clone https://github.com/seccomp/libseccomp "$GOPATH/src/github.com/seccomp/libseccomp" \ - && cd "$GOPATH/src/github.com/seccomp/libseccomp" \ - && git fetch origin --tags \ - && git checkout -q "$LIBSECCOMP_COMMIT" \ - && ./autogen.sh \ - && ./configure --prefix=/usr \ - && make all \ - && make install - # Install runc ENV RUNC_COMMIT 96ec2177ae841256168fcf76954f7177af9446eb RUN set -x \ diff --git a/Dockerfile.CentOS b/Dockerfile.centos index 605dc9df4..605dc9df4 100644 --- a/Dockerfile.CentOS +++ b/Dockerfile.centos diff --git a/Dockerfile.Fedora b/Dockerfile.fedora index e38e2e056..d4bcc11ea 100644 --- a/Dockerfile.Fedora +++ b/Dockerfile.fedora @@ -1,4 +1,4 @@ -FROM registry.fedoraproject.org/fedora:28 +FROM registry.fedoraproject.org/fedora:29 RUN dnf -y install btrfs-progs-devel \ atomic-registries \ @@ -1,6 +1,6 @@ GO ?= go DESTDIR ?= / -EPOCH_TEST_COMMIT ?= 1c45b42e9ff972d9645735118635e4186e6411f8 +EPOCH_TEST_COMMIT ?= 7b7397481960c85379d8eb1ed21e76da2ce8a4fc HEAD ?= HEAD CHANGELOG_BASE ?= HEAD~ CHANGELOG_TARGET ?= HEAD @@ -30,7 +30,6 @@ BASHINSTALLDIR=${PREFIX}/share/bash-completion/completions ZSHINSTALLDIR=${PREFIX}/share/zsh/site-functions 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 | 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}) @@ -172,7 +171,13 @@ testunit: libpodimage ## Run unittest on the built image ${CONTAINER_RUNTIME} run -e STORAGE_OPTIONS="--storage-driver=vfs" -e TESTFLAGS -e CGROUP_MANAGER=cgroupfs -e OCI_RUNTIME -e TRAVIS -t --privileged --rm -v ${CURDIR}:/go/src/${PROJECT} ${LIBPOD_IMAGE} make localunit localunit: test/goecho/goecho varlink_generate - $(GO) test -tags "$(BUILDTAGS)" -cover $(PACKAGES) + ginkgo \ + -r \ + --skipPackage test/e2e,pkg/apparmor \ + --cover \ + --covermode atomic \ + --tags "$(BUILDTAGS)" \ + --succinct $(MAKE) -C contrib/cirrus/packer test ginkgo: @@ -5,7 +5,7 @@ Libpod provides a library for applications looking to use the Container Pod concept, popularized by Kubernetes. Libpod also contains the Pod Manager tool `(Podman)`. Podman manages pods, containers, container images, and container volumes. -* [Latest Version: 1.0.0](https://github.com/containers/libpod/releases/latest) +* [Latest Version: 1.2.0](https://github.com/containers/libpod/releases/latest) * [Continuous Integration:](contrib/cirrus/README.md) [![Build Status](https://api.cirrus-ci.com/github/containers/libpod.svg)](https://cirrus-ci.com/github/containers/libpod/master) ## Overview and scope diff --git a/changelog.txt b/changelog.txt index 7db579f3a..92a17f8d0 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,3 +1,208 @@ +- Changelog for v1.2.0 (2019-03-30) + * Update release notes for v1.2.0 + * Remove wait event + * Vendor Buildah 1.7.2 + * Add locking to ensure events file is concurrency-safe + * Alter container/pod/volume name regexp to match Docker + * test: test that an unprivileged user cannot access the storage + * userns: do not use an intermediate mount namespace + * volumes: push the chown logic to runtime_volume_linux.go + * Cleanup image2 -> image for imports + * Set blob cache directory based on GraphDriver + * utils: call GetRootlessRuntimeDir once + * rootless: set sticky bit on rundir + * oci: drop reference to runc + * Fix lint + * Ensure that we make a netns for CNI non-default nets + * rootless: change env prefix + * vendor buildah, image, storage, cni + * Default to SELinux private label for play kube mounts + * Add watch mode to podman ps + * Add all container status states to the podman-ps manual page. + * fix bug `system df` add blank space to the output + * fix bug remote-podman images --digests + * Use spaces instead of tab for JSON marshal indent + * Fix gofmt + * Remove ulele/deepcopier in favor of JSON deep copy + * doc: add note that pod publish ports are static once defined + * Sigh; disable pod-top test, it's unreliable (#2780) + * Resolve review comments + * Add a test that --add-host conflicts with --no-hosts + * Add manpages and completions for dns=none and no-hosts + * Add --no-hosts flag to disable management of /etc/hosts + * Add for --dns=none to disable creation of resolv.conf + * Add support to disable creation of network config files + * system df: reject invalid arguments + * rootless: fix regression when using exec on old containers + * Touchup commands.md + * size is optional for container inspection + * Add three test cases for podman attach test + * system df to show podman disk usage + * Add "died" event + * docs/podman-pod-create.1.md: add example with port mapping + * podman health check phase3 + * userns: use the intermediate mountns for volumes + * volume: create new volumes with right ownership + * utils: drop dead function + * troubleshooting: explain setup user: invalid argument + * Cirrus: Verify manpages for all subcommands exist + * Make "stopped" a valid state that maps to "exited" + * fix Bug 1688041-podman image save removes existing image + * podman: do not split --env on comma + * Need to pass the true paramater with --syslog in cobra + * Fix man page to mention race condition + * docs/podman-run.1.md: remove extra whitespace in --read-only + * man pages - consistency fixes + * Add new key and never-expiring test certificate + * Cirrus: Run vendor check in parallel + * Cirrus: Various fixes for rootless testing + * ps: fix segfault if the store is not initialized + * tests: re-enable some tests for rootless mode + * rootless: implement pod restart + * rootless: reimplement restart with rootless.Argument() + * test: fix SkipIfRootless() helper + * rootless, rm: fix retcode when the container is not found + * rootless: fix ps command + * rootless: fix pod kill + * Enable rootless integration tests + * BATS: new tests, and improvements to existing ones + * podman umount: error out if called with no args + * Export ConmonPidFile in 'podman inspect' for containers + * support GO template {{ json . }} + * Incorporate user from image inspect data in play kube + * Cirrus: Disable master-success IRC notices + * Cleanup messages on podman load + * Cirrus: Update VM Cache images + * podman logs on created container should exit + * Fix cut and paste errors in podman-pod-inspect + * rootless: fix pod top + * pod: fix segfault when there are no arguments to inspect + * output of port grouping in ps command added as example + * utils: split generation and writing of storage.conf + * Cirrus: Fix post-merge failure notice + * utils: avoid too long tmp directory + * podman image tree: fix usage message + * Cirrus: Notify on IRC if post-merge testing fails + * rootless: change default path for conmon.pid + * Add CLI storage conf example to run manpage + * Integration test tweaks + * display logs for multiple containers at the same time + * Make 'podman rm' exit with 125 if it had a bogus & a running container + * rootless: write the custom config file before reload + * Add support for SCTP port forwarding + * Make sure buildin volumes have the same ownership and permissions as image + * rootless: do not override user settings + * runtime: refactor NewRuntime and NewRuntimeFromConfig + * events: use os.SEEK_END instead of its value + * container: check containerInfo.Config before accessing it + * rootless: use Geteuid instead of Getuid + * rootless: use /tmp/libpod-rundir-$EUID for fallback + * build: fix build DIR -t TAG + * testcase added for listing range of ports in ps command + * port grouping in ps command output + * Update pull and pull-always in bud man page + * cirrus: upgrade slirp4netns + * rootless: fix CI regression when using slirp4netns + * save-load-export: clear cli-parsing default + * Bump timeout on a podman info test to default + * Replace skopeo-containers with containers-common + * slirp4netns: use --disable-host-loopback + * slirp4netns: set mtu to 65520 + * Tree implementation for podman images + * Replace buildah with podman in build doc + * zsh completion + * Usage messages: deduplicate '(default true)' et al + * Corrected detach man pages and code comments + * Add --replace flag to "podman container runlabel" + * rm: fix cleanup race + * Add gating tasks + * Add 'podman events' to podman(1) + * Vendor docker/docker, fsouza and more #2 + * Usability cleanup for 'inspect' + * Add event on container death + * Update vendor of Buildah and imagebuilder + * minor typo fix in 'podman top' usage + * healtcheck phase 2 + * Add event logging to libpod, even display to podman + * Fix SELinux on host shared systems in userns + * Fix broken link in io.podman.varlink + * move formats pkg to and vendor from buildah + * Ensure that tmpfs mounts do not have symlinks + * Update troubleshooting guide for Podman-in-Podman + * Buffer stdin to a file when importing "-" + * vendor psgo v1.2 + * preparation for remote-client create container + * Initialize field in InfoHost struct + * rootless: allow single mappings + * Remove --rm and --detach don't coexist note + * rootless: fix pod stop|rm if uid in the container != 0 + * rootless: fix rm when uid in the container != 0 + * rootless: disable pod stats + * rootless: do not create automatically a userns for pod kill + * rootless: support a custom arg to the new process + * slirp4netns: add builtin DNS server to resolv.conf + * errors: fix error cause comparison + * libpod: allow to configure path to the network-cmd binary + * build: honor --net + * pull: promote debug statement to error + * Fix generation of infra container command + * Remove an unused if statement I added + * Don't delete another container's resolv and hosts files + * Fix a potential segfault during infra container create + * We don't use crio-umount.conf + * Move secrets package to buildah + * Add troublshoot information about SELinux labeling of containers/storage + * test docs fixups + * Default to image entrypoint for infra container + * ginkgo status improvements + * rootless: propagate errors from info + * podman play kube defaults + * container runlabel respect $PWD + * Remove 'podman ps' restarting filter and fix stopped + * label parsing in non-quoted field + * More cleanup for failures on missing commands. + * add podman-healthcheck(1) to podman(1) + * Implement review feedback + * new system tests under BATS + * fix bug in podman images list all images with same name + * Fix help commands to show short and long description. + * implement showerror and accept HOST_PORT env which defaults to 8080 + * create: join also the mount ns of the dependency + * rootless: exec join the user+mount namespace + * oci: make explicit the extra files to the exec + * add test to cover networking + * tests to cover locks and parallel execution #2551 + * Yet another seemingly minor tweak to usage message + * Change LookupContainer logic to match Docker + * Implement podman-remote wait command and container subcommand + * Cirrus: Use imgts container to record metadata + * System-test: Documentation and TODO list + * podman-remote pod top|stats + * fix bug --device enable specifying directory as device + * add flag --extract tar file in podman cp + * Fix incorrect pod create failure + * libpod/container_internal: Split locale at the first dot, etc. + * Add volume mounting to podman play kube + * podman healthcheck run (phase 1) + * Append hosts to dependency container's /etc/hosts file + * rootless: fix clone syscall on s390 and cris archs + * Cirrus: Add dedicated rootless mode testing + * rootless: fill in correct storage conf default + * rm: set exit code to 1 if a specified container is not found + * Support filter image by reference to the repo name + * Bump gitvalidation epoch + * Bump to v1.2.0-dev + * Support podman-remote kill container(s) + * cirrus: Drop ginkgo, gomega, easyjson install + * Cirrus: Stop testing on RHEL + * Cirrus: Stop testing on RHEL + * Globally increase test timeout to 90-minutes + * cirrus: Drop ginkgo, gomega, easyjson install + * Cirrus: Add BATS package for all platforms + * Globally increase test timeout to 90-minutes + * exec: support --preserve-fds + * get_ci_vm.sh: Fix conflicting homedir files + - Changelog for v1.1.2 (2019-03-04) * Fix #2521 * Update release notes for v1.1.2 diff --git a/cmd/podman/commands.go b/cmd/podman/commands.go index 875b2aec8..baa349e1a 100644 --- a/cmd/podman/commands.go +++ b/cmd/podman/commands.go @@ -14,7 +14,6 @@ func getMainCommands() []*cobra.Command { _attachCommand, _commitCommand, _createCommand, - _diffCommand, _execCommand, _generateCommand, _playCommand, @@ -58,7 +57,6 @@ func getContainerSubCommands() []*cobra.Command { _cleanupCommand, _commitCommand, _createCommand, - _diffCommand, _execCommand, _exportCommand, _killCommand, diff --git a/cmd/podman/container.go b/cmd/podman/container.go index 2e9cedbaa..743dec32f 100644 --- a/cmd/podman/container.go +++ b/cmd/podman/container.go @@ -52,6 +52,7 @@ var ( containerCommands = []*cobra.Command{ _containerExistsCommand, _contInspectSubCommand, + _diffCommand, _listSubCommand, _logsCommand, } diff --git a/cmd/podman/cp.go b/cmd/podman/cp.go index 18fb2cb73..a0dd46260 100644 --- a/cmd/podman/cp.go +++ b/cmd/podman/cp.go @@ -1,10 +1,8 @@ package main import ( - "io/ioutil" "os" "path/filepath" - "strconv" "strings" "github.com/containers/buildah/pkg/chrootuser" @@ -12,7 +10,6 @@ import ( "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/cmd/podman/libpodruntime" "github.com/containers/libpod/libpod" - "github.com/containers/libpod/pkg/rootless" "github.com/containers/storage" "github.com/containers/storage/pkg/archive" "github.com/containers/storage/pkg/chrootarchive" @@ -58,9 +55,6 @@ func cpCmd(c *cliconfig.CpValues) error { if len(args) != 2 { return errors.Errorf("you must provide a source path and a destination path") } - if os.Geteuid() != 0 { - rootless.SetSkipStorageSetup(true) - } runtime, err := libpodruntime.GetRuntime(&c.PodmanCommand) if err != nil { @@ -90,34 +84,6 @@ func copyBetweenHostAndContainer(runtime *libpod.Runtime, src string, dest strin ctr = destCtr } - if os.Geteuid() != 0 { - s, err := ctr.State() - if err != nil { - return err - } - var became bool - var ret int - if s == libpod.ContainerStateRunning || s == libpod.ContainerStatePaused { - data, err := ioutil.ReadFile(ctr.Config().ConmonPidFile) - if err != nil { - return errors.Wrapf(err, "cannot read conmon PID file %q", ctr.Config().ConmonPidFile) - } - conmonPid, err := strconv.Atoi(string(data)) - if err != nil { - return errors.Wrapf(err, "cannot parse PID %q", data) - } - became, ret, err = rootless.JoinDirectUserAndMountNS(uint(conmonPid)) - } else { - became, ret, err = rootless.BecomeRootInUserNS() - } - if err != nil { - return err - } - if became { - os.Exit(ret) - } - } - mountPoint, err := ctr.Mount() if err != nil { return err diff --git a/cmd/podman/create.go b/cmd/podman/create.go index bceb606f6..984323653 100644 --- a/cmd/podman/create.go +++ b/cmd/podman/create.go @@ -2,12 +2,10 @@ package main import ( "fmt" - "os" "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/cmd/podman/libpodruntime" "github.com/containers/libpod/cmd/podman/shared" - "github.com/containers/libpod/pkg/rootless" "github.com/opentracing/opentracing-go" "github.com/pkg/errors" "github.com/spf13/cobra" @@ -54,10 +52,6 @@ func createCmd(c *cliconfig.CreateValues) error { return err } - if os.Geteuid() != 0 { - rootless.SetSkipStorageSetup(true) - } - runtime, err := libpodruntime.GetRuntime(&c.PodmanCommand) if err != nil { return errors.Wrapf(err, "error creating libpod runtime") diff --git a/cmd/podman/diff.go b/cmd/podman/diff.go index e77e562d4..7f5a313f8 100644 --- a/cmd/podman/diff.go +++ b/cmd/podman/diff.go @@ -4,7 +4,7 @@ import ( "fmt" "github.com/containers/buildah/pkg/formats" "github.com/containers/libpod/cmd/podman/cliconfig" - "github.com/containers/libpod/cmd/podman/libpodruntime" + "github.com/containers/libpod/pkg/adapter" "github.com/containers/storage/pkg/archive" "github.com/pkg/errors" "github.com/spf13/cobra" @@ -86,18 +86,17 @@ func diffCmd(c *cliconfig.DiffValues) error { return errors.Errorf("container, image, or layer name must be specified: podman diff [options [...]] ID-NAME") } - runtime, err := libpodruntime.GetRuntime(&c.PodmanCommand) + runtime, err := adapter.GetRuntime(&c.PodmanCommand) if err != nil { return errors.Wrapf(err, "could not get runtime") } defer runtime.Shutdown(false) to := c.InputArgs[0] - changes, err := runtime.GetDiff("", to) + changes, err := runtime.Diff(c, to) if err != nil { return errors.Wrapf(err, "could not get changes for %q", to) } - diffOutput := []diffOutputParams{} outputFormat := c.Format diff --git a/cmd/podman/exec.go b/cmd/podman/exec.go index fc1c76e9f..f720a9aff 100644 --- a/cmd/podman/exec.go +++ b/cmd/podman/exec.go @@ -10,7 +10,6 @@ import ( "github.com/containers/libpod/cmd/podman/libpodruntime" "github.com/containers/libpod/cmd/podman/shared/parse" "github.com/containers/libpod/libpod" - "github.com/containers/libpod/pkg/rootless" "github.com/pkg/errors" "github.com/spf13/cobra" ) @@ -67,7 +66,6 @@ func execCmd(c *cliconfig.ExecValues) error { if c.Latest { argStart = 0 } - rootless.SetSkipStorageSetup(true) cmd := args[argStart:] runtime, err := libpodruntime.GetRuntime(&c.PodmanCommand) if err != nil { @@ -107,32 +105,6 @@ func execCmd(c *cliconfig.ExecValues) error { } - if os.Geteuid() != 0 { - var became bool - var ret int - - data, err := ioutil.ReadFile(ctr.Config().ConmonPidFile) - if err == nil { - conmonPid, err := strconv.Atoi(string(data)) - if err != nil { - return errors.Wrapf(err, "cannot parse PID %q", data) - } - became, ret, err = rootless.JoinDirectUserAndMountNS(uint(conmonPid)) - } else { - pid, err := ctr.PID() - if err != nil { - return err - } - became, ret, err = rootless.JoinNS(uint(pid), c.PreserveFDs) - } - if err != nil { - return err - } - if became { - os.Exit(ret) - } - } - // ENVIRONMENT VARIABLES env := map[string]string{} diff --git a/cmd/podman/export.go b/cmd/podman/export.go index 92633facd..db031aaf2 100644 --- a/cmd/podman/export.go +++ b/cmd/podman/export.go @@ -6,7 +6,6 @@ import ( "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/cmd/podman/shared/parse" "github.com/containers/libpod/pkg/adapter" - "github.com/containers/libpod/pkg/rootless" "github.com/pkg/errors" "github.com/sirupsen/logrus" "github.com/spf13/cobra" @@ -41,10 +40,6 @@ func init() { // exportCmd saves a container to a tarball on disk func exportCmd(c *cliconfig.ExportValues) error { - if os.Geteuid() != 0 { - rootless.SetSkipStorageSetup(true) - } - runtime, err := adapter.GetRuntime(&c.PodmanCommand) if err != nil { return errors.Wrapf(err, "could not get runtime") diff --git a/cmd/podman/generate_kube.go b/cmd/podman/generate_kube.go index 42cfba8d8..c58372899 100644 --- a/cmd/podman/generate_kube.go +++ b/cmd/podman/generate_kube.go @@ -5,7 +5,6 @@ import ( "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/cmd/podman/libpodruntime" "github.com/containers/libpod/libpod" - "github.com/containers/libpod/pkg/rootless" podmanVersion "github.com/containers/libpod/version" "github.com/ghodss/yaml" "github.com/pkg/errors" @@ -53,9 +52,6 @@ func generateKubeYAMLCmd(c *cliconfig.GenerateKubeValues) error { servicePorts []v1.ServicePort ) - if rootless.IsRootless() { - return errors.Wrapf(libpod.ErrNotImplemented, "rootless users") - } args := c.InputArgs if len(args) != 1 { return errors.Errorf("you must provide exactly one container|pod ID or name") diff --git a/cmd/podman/kill.go b/cmd/podman/kill.go index 2c1e13eaf..6019fbfec 100644 --- a/cmd/podman/kill.go +++ b/cmd/podman/kill.go @@ -4,12 +4,10 @@ import ( "fmt" "reflect" - "github.com/containers/libpod/pkg/adapter" - "github.com/opentracing/opentracing-go" - "github.com/containers/libpod/cmd/podman/cliconfig" - "github.com/containers/libpod/pkg/rootless" + "github.com/containers/libpod/pkg/adapter" "github.com/docker/docker/pkg/signal" + "github.com/opentracing/opentracing-go" "github.com/pkg/errors" "github.com/spf13/cobra" ) @@ -63,7 +61,6 @@ func killCmd(c *cliconfig.KillValues) error { return err } - rootless.SetSkipStorageSetup(true) runtime, err := adapter.GetRuntime(&c.PodmanCommand) if err != nil { return errors.Wrapf(err, "could not get runtime") diff --git a/cmd/podman/main.go b/cmd/podman/main.go index 1ea7f74bf..1ba58d1f3 100644 --- a/cmd/podman/main.go +++ b/cmd/podman/main.go @@ -3,13 +3,16 @@ package main import ( "context" "io" + "io/ioutil" "log/syslog" "os" "runtime/pprof" + "strconv" "strings" "syscall" "github.com/containers/libpod/cmd/podman/cliconfig" + "github.com/containers/libpod/cmd/podman/libpodruntime" "github.com/containers/libpod/libpod" _ "github.com/containers/libpod/pkg/hooks/0.1.0" "github.com/containers/libpod/pkg/rootless" @@ -36,6 +39,7 @@ var ( // implemented. var mainCommands = []*cobra.Command{ _buildCommand, + _diffCommand, _eventsCommand, _exportCommand, _historyCommand, @@ -59,36 +63,6 @@ var mainCommands = []*cobra.Command{ systemCommand.Command, } -var cmdsNotRequiringRootless = map[*cobra.Command]bool{ - _versionCommand: true, - _createCommand: true, - _execCommand: true, - _cpCommand: true, - _exportCommand: true, - //// `info` must be executed in an user namespace. - //// If this change, please also update libpod.refreshRootless() - _loginCommand: true, - _logoutCommand: true, - _mountCommand: true, - _killCommand: true, - _pauseCommand: true, - _podRmCommand: true, - _podKillCommand: true, - _podRestartCommand: true, - _podStatsCommand: true, - _podStopCommand: true, - _podTopCommand: true, - _restartCommand: true, - &_psCommand: true, - _rmCommand: true, - _runCommand: true, - _unpauseCommand: true, - _searchCommand: true, - _statsCommand: true, - _stopCommand: true, - _topCommand: true, -} - var rootCmd = &cobra.Command{ Use: "podman", Long: "manage pods and images", @@ -152,18 +126,52 @@ func before(cmd *cobra.Command, args []string) error { logrus.Errorf(err.Error()) os.Exit(1) } - if rootless.IsRootless() { - notRequireRootless := cmdsNotRequiringRootless[cmd] - if !notRequireRootless && !strings.HasPrefix(cmd.Use, "help") { - became, ret, err := rootless.BecomeRootInUserNS() - if err != nil { - logrus.Errorf(err.Error()) - os.Exit(1) - } - if became { - os.Exit(ret) + if os.Geteuid() != 0 && cmd != _searchCommand && cmd != _versionCommand && !strings.HasPrefix(cmd.Use, "help") { + podmanCmd := cliconfig.PodmanCommand{ + cmd, + args, + MainGlobalOpts, + } + runtime, err := libpodruntime.GetRuntime(&podmanCmd) + if err != nil { + return errors.Wrapf(err, "could not get runtime") + } + defer runtime.Shutdown(false) + + ctrs, err := runtime.GetRunningContainers() + if err != nil { + logrus.Errorf(err.Error()) + os.Exit(1) + } + var became bool + var ret int + if len(ctrs) == 0 { + became, ret, err = rootless.BecomeRootInUserNS() + } else { + for _, ctr := range ctrs { + data, err := ioutil.ReadFile(ctr.Config().ConmonPidFile) + if err != nil { + logrus.Errorf(err.Error()) + os.Exit(1) + } + conmonPid, err := strconv.Atoi(string(data)) + if err != nil { + logrus.Errorf(err.Error()) + os.Exit(1) + } + became, ret, err = rootless.JoinUserAndMountNS(uint(conmonPid)) + if err == nil { + break + } } } + if err != nil { + logrus.Errorf(err.Error()) + os.Exit(1) + } + if became { + os.Exit(ret) + } } if MainGlobalOpts.Syslog { diff --git a/cmd/podman/mount.go b/cmd/podman/mount.go index 138548097..a70684a39 100644 --- a/cmd/podman/mount.go +++ b/cmd/podman/mount.go @@ -60,10 +60,6 @@ type jsonMountPoint struct { } func mountCmd(c *cliconfig.MountValues) error { - if os.Geteuid() != 0 { - rootless.SetSkipStorageSetup(true) - } - runtime, err := libpodruntime.GetRuntime(&c.PodmanCommand) if err != nil { return errors.Wrapf(err, "could not get runtime") diff --git a/cmd/podman/play_kube.go b/cmd/podman/play_kube.go index b468a7a89..cbe961279 100644 --- a/cmd/podman/play_kube.go +++ b/cmd/podman/play_kube.go @@ -15,7 +15,6 @@ import ( "github.com/containers/libpod/libpod" "github.com/containers/libpod/libpod/image" ns "github.com/containers/libpod/pkg/namespaces" - "github.com/containers/libpod/pkg/rootless" "github.com/containers/libpod/pkg/spec" "github.com/containers/storage" "github.com/cri-o/ocicni/pkg/ocicni" @@ -73,9 +72,6 @@ func playKubeYAMLCmd(c *cliconfig.KubePlayValues) error { ) ctx := getContext() - if rootless.IsRootless() { - return errors.Wrapf(libpod.ErrNotImplemented, "rootless users") - } args := c.InputArgs if len(args) > 1 { return errors.New("you can only play one kubernetes file at a time") @@ -243,6 +239,9 @@ func kubeContainerToCreateConfig(ctx context.Context, containerYAML v1.Container envs map[string]string ) + // The default for MemorySwappiness is -1, not 0 + containerConfig.Resources.MemorySwappiness = -1 + containerConfig.Runtime = runtime containerConfig.Image = containerYAML.Image containerConfig.ImageID = newImage.ID() @@ -270,7 +269,19 @@ func kubeContainerToCreateConfig(ctx context.Context, containerYAML v1.Container } } - containerConfig.Command = containerYAML.Command + containerConfig.Command = []string{} + if imageData != nil && imageData.Config != nil { + containerConfig.Command = append(containerConfig.Command, imageData.Config.Entrypoint...) + } + if len(containerConfig.Command) != 0 { + containerConfig.Command = append(containerConfig.Command, containerYAML.Command...) + } else if imageData != nil && imageData.Config != nil { + containerConfig.Command = append(containerConfig.Command, imageData.Config.Cmd...) + } + if imageData != nil && len(containerConfig.Command) == 0 { + return nil, errors.Errorf("No command specified in container YAML or as CMD or ENTRYPOINT in this image for %s", containerConfig.Name) + } + containerConfig.StopSignal = 15 // If the user does not pass in ID mappings, just set to basics diff --git a/cmd/podman/pod.go b/cmd/podman/pod.go index 9a9c7a702..2d9bca21d 100644 --- a/cmd/podman/pod.go +++ b/cmd/podman/pod.go @@ -1,12 +1,7 @@ package main import ( - "os" - "github.com/containers/libpod/cmd/podman/cliconfig" - "github.com/containers/libpod/pkg/adapter" - "github.com/containers/libpod/pkg/rootless" - "github.com/pkg/errors" "github.com/spf13/cobra" ) @@ -39,48 +34,6 @@ var podSubCommands = []*cobra.Command{ _podUnpauseCommand, } -func joinPodNS(runtime *adapter.LocalRuntime, all, latest bool, inputArgs []string) ([]string, bool, bool, error) { - if rootless.IsRootless() { - if os.Geteuid() == 0 { - return []string{rootless.Argument()}, false, false, nil - } else { - var err error - var pods []*adapter.Pod - if all { - pods, err = runtime.GetAllPods() - if err != nil { - return nil, false, false, errors.Wrapf(err, "unable to get pods") - } - } else if latest { - pod, err := runtime.GetLatestPod() - if err != nil { - return nil, false, false, errors.Wrapf(err, "unable to get latest pod") - } - pods = append(pods, pod) - } else { - for _, i := range inputArgs { - pod, err := runtime.LookupPod(i) - if err != nil { - return nil, false, false, errors.Wrapf(err, "unable to lookup pod %s", i) - } - pods = append(pods, pod) - } - } - for _, p := range pods { - _, ret, err := runtime.JoinOrCreateRootlessPod(p) - if err != nil { - return nil, false, false, err - } - if ret != 0 { - os.Exit(ret) - } - } - os.Exit(0) - } - } - return inputArgs, all, latest, nil -} - func init() { podCommand.AddCommand(podSubCommands...) podCommand.SetHelpTemplate(HelpTemplate()) diff --git a/cmd/podman/pod_kill.go b/cmd/podman/pod_kill.go index c538674a4..ebd7db762 100644 --- a/cmd/podman/pod_kill.go +++ b/cmd/podman/pod_kill.go @@ -6,7 +6,6 @@ import ( "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/pkg/adapter" - "github.com/containers/libpod/pkg/rootless" "github.com/docker/docker/pkg/signal" "github.com/pkg/errors" "github.com/sirupsen/logrus" @@ -49,7 +48,6 @@ func init() { // podKillCmd kills one or more pods with a signal func podKillCmd(c *cliconfig.PodKillValues) error { - rootless.SetSkipStorageSetup(true) runtime, err := adapter.GetRuntime(&c.PodmanCommand) if err != nil { return errors.Wrapf(err, "could not get runtime") diff --git a/cmd/podman/pod_restart.go b/cmd/podman/pod_restart.go index 9c8d28424..0765b98db 100644 --- a/cmd/podman/pod_restart.go +++ b/cmd/podman/pod_restart.go @@ -2,11 +2,9 @@ package main import ( "fmt" - "os" "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/pkg/adapter" - "github.com/containers/libpod/pkg/rootless" "github.com/pkg/errors" "github.com/sirupsen/logrus" "github.com/spf13/cobra" @@ -48,24 +46,12 @@ func init() { func podRestartCmd(c *cliconfig.PodRestartValues) error { var lastError error - if os.Geteuid() != 0 { - rootless.SetSkipStorageSetup(true) - } runtime, err := adapter.GetRuntime(&c.PodmanCommand) if err != nil { return errors.Wrapf(err, "could not get runtime") } defer runtime.Shutdown(false) - if rootless.IsRootless() { - var err error - - c.InputArgs, c.All, c.Latest, err = joinPodNS(runtime, c.All, c.Latest, c.InputArgs) - if err != nil { - return err - } - } - restartIDs, conErrors, restartErrors := runtime.RestartPods(getContext(), c) for _, p := range restartIDs { diff --git a/cmd/podman/pod_rm.go b/cmd/podman/pod_rm.go index 735676f8a..cd9f23fe1 100644 --- a/cmd/podman/pod_rm.go +++ b/cmd/podman/pod_rm.go @@ -2,11 +2,9 @@ package main import ( "fmt" - "os" "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/pkg/adapter" - "github.com/containers/libpod/pkg/rootless" "github.com/pkg/errors" "github.com/sirupsen/logrus" "github.com/spf13/cobra" @@ -48,23 +46,12 @@ func init() { // podRmCmd deletes pods func podRmCmd(c *cliconfig.PodRmValues) error { - if os.Geteuid() != 0 { - rootless.SetSkipStorageSetup(true) - } runtime, err := adapter.GetRuntime(&c.PodmanCommand) if err != nil { return errors.Wrapf(err, "could not get runtime") } defer runtime.Shutdown(false) - if rootless.IsRootless() { - var err error - c.InputArgs, c.All, c.Latest, err = joinPodNS(runtime, c.All, c.Latest, c.InputArgs) - if err != nil { - return err - } - } - podRmIds, podRmErrors := runtime.RemovePods(getContext(), c) for _, p := range podRmIds { fmt.Println(p) diff --git a/cmd/podman/pod_stop.go b/cmd/podman/pod_stop.go index 754a3a7db..f1b0ac51f 100644 --- a/cmd/podman/pod_stop.go +++ b/cmd/podman/pod_stop.go @@ -2,11 +2,9 @@ package main import ( "fmt" - "os" "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/pkg/adapter" - "github.com/containers/libpod/pkg/rootless" "github.com/pkg/errors" "github.com/sirupsen/logrus" "github.com/spf13/cobra" @@ -48,24 +46,12 @@ func init() { } func podStopCmd(c *cliconfig.PodStopValues) error { - if os.Geteuid() != 0 { - rootless.SetSkipStorageSetup(true) - } - runtime, err := adapter.GetRuntime(&c.PodmanCommand) if err != nil { return errors.Wrapf(err, "could not get runtime") } defer runtime.Shutdown(false) - if rootless.IsRootless() { - var err error - c.InputArgs, c.All, c.Latest, err = joinPodNS(runtime, c.All, c.Latest, c.InputArgs) - if err != nil { - return err - } - } - podStopIds, podStopErrors := runtime.StopPods(getContext(), c) for _, p := range podStopIds { fmt.Println(p) diff --git a/cmd/podman/pod_top.go b/cmd/podman/pod_top.go index f65d66df6..0d74dc3d6 100644 --- a/cmd/podman/pod_top.go +++ b/cmd/podman/pod_top.go @@ -9,7 +9,6 @@ import ( "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/libpod" - "github.com/containers/libpod/pkg/rootless" "github.com/pkg/errors" "github.com/spf13/cobra" ) @@ -54,10 +53,6 @@ func podTopCmd(c *cliconfig.PodTopValues) error { ) args := c.InputArgs - if os.Geteuid() != 0 { - rootless.SetSkipStorageSetup(true) - } - if c.ListDescriptors { descriptors, err := libpod.GetContainerPidInformationDescriptors() if err != nil { @@ -83,26 +78,6 @@ func podTopCmd(c *cliconfig.PodTopValues) error { descriptors = args[1:] } - if os.Geteuid() != 0 { - var pod *adapter.Pod - var err error - if c.Latest { - pod, err = runtime.GetLatestPod() - } else { - pod, err = runtime.LookupPod(c.InputArgs[0]) - } - if err != nil { - return errors.Wrapf(err, "unable to lookup requested container") - } - became, ret, err := runtime.JoinOrCreateRootlessPod(pod) - if err != nil { - return err - } - if became { - os.Exit(ret) - } - } - w := tabwriter.NewWriter(os.Stdout, 5, 1, 3, ' ', 0) psOutput, err := runtime.PodTop(c, descriptors) if err != nil { diff --git a/cmd/podman/ps.go b/cmd/podman/ps.go index 1f8db2739..759a03b86 100644 --- a/cmd/podman/ps.go +++ b/cmd/podman/ps.go @@ -17,7 +17,6 @@ import ( "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/containers/libpod/pkg/util" "github.com/cri-o/ocicni/pkg/ocicni" "github.com/docker/go-units" @@ -202,13 +201,14 @@ func init() { } func psCmd(c *cliconfig.PsValues) error { - if os.Geteuid() != 0 { - rootless.SetSkipStorageSetup(true) - } if c.Bool("trace") { span, _ := opentracing.StartSpanFromContext(Ctx, "psCmd") defer span.Finish() } + // TODO disable when single rootless userns merges + if c.Bool("size") && os.Geteuid() != 0 { + return errors.New("the --size option is not presently supported without root") + } var watch bool diff --git a/cmd/podman/restart.go b/cmd/podman/restart.go index e6a6d8434..1553ab805 100644 --- a/cmd/podman/restart.go +++ b/cmd/podman/restart.go @@ -1,13 +1,10 @@ package main import ( - "os" - "github.com/containers/libpod/cmd/podman/cliconfig" "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/pkg/errors" "github.com/sirupsen/logrus" "github.com/spf13/cobra" @@ -57,19 +54,6 @@ func restartCmd(c *cliconfig.RestartValues) error { restartContainers []*libpod.Container ) - if os.Geteuid() != 0 { - rootless.SetSkipStorageSetup(true) - } - if rootless.IsRootless() { - // If we are in the re-execed rootless environment, - // override the arg to deal only with one container. - if os.Geteuid() == 0 { - c.All = false - c.Latest = false - c.InputArgs = []string{rootless.Argument()} - } - } - args := c.InputArgs runOnly := c.Running all := c.All @@ -115,20 +99,6 @@ func restartCmd(c *cliconfig.RestartValues) error { } } - if os.Geteuid() != 0 { - // In rootless mode we can deal with one container at at time. - for _, c := range restartContainers { - _, ret, err := joinContainerOrCreateRootlessUserNS(runtime, c) - if err != nil { - return err - } - if ret != 0 { - os.Exit(ret) - } - } - os.Exit(0) - } - maxWorkers := shared.Parallelize("restart") if c.GlobalIsSet("max-workers") { maxWorkers = c.GlobalFlags.MaxWorks diff --git a/cmd/podman/rm.go b/cmd/podman/rm.go index 253771e14..52e281402 100644 --- a/cmd/podman/rm.go +++ b/cmd/podman/rm.go @@ -2,16 +2,12 @@ package main import ( "fmt" - "io/ioutil" - "os" - "strconv" "github.com/containers/libpod/cmd/podman/cliconfig" "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" - "github.com/containers/libpod/pkg/rootless" "github.com/pkg/errors" "github.com/sirupsen/logrus" "github.com/spf13/cobra" @@ -52,39 +48,11 @@ func init() { markFlagHiddenForRemoteClient("latest", flags) } -func joinContainerOrCreateRootlessUserNS(runtime *libpod.Runtime, ctr *libpod.Container) (bool, int, error) { - if os.Geteuid() == 0 { - return false, 0, nil - } - s, err := ctr.State() - if err != nil { - return false, -1, err - } - opts := rootless.Opts{ - Argument: ctr.ID(), - } - if s == libpod.ContainerStateRunning || s == libpod.ContainerStatePaused { - data, err := ioutil.ReadFile(ctr.Config().ConmonPidFile) - if err != nil { - return false, -1, errors.Wrapf(err, "cannot read conmon PID file %q", ctr.Config().ConmonPidFile) - } - conmonPid, err := strconv.Atoi(string(data)) - if err != nil { - return false, -1, errors.Wrapf(err, "cannot parse PID %q", data) - } - return rootless.JoinDirectUserAndMountNSWithOpts(uint(conmonPid), &opts) - } - return rootless.BecomeRootInUserNSWithOpts(&opts) -} - // saveCmd saves the image to either docker-archive or oci func rmCmd(c *cliconfig.RmValues) error { var ( deleteFuncs []shared.ParallelWorkerInput ) - if os.Geteuid() != 0 { - rootless.SetSkipStorageSetup(true) - } ctx := getContext() runtime, err := libpodruntime.GetRuntime(&c.PodmanCommand) @@ -93,58 +61,6 @@ func rmCmd(c *cliconfig.RmValues) error { } defer runtime.Shutdown(false) - if rootless.IsRootless() { - // When running in rootless mode we cannot manage different containers and - // user namespaces from the same context, so be sure to re-exec once for each - // container we are dealing with. - // What we do is to first collect all the containers we want to delete, then - // we re-exec in each of the container namespaces and from there remove the single - // container. - var container *libpod.Container - if os.Geteuid() == 0 { - // We are in the namespace, override InputArgs with the single - // argument that was passed down to us. - c.All = false - c.Latest = false - c.InputArgs = []string{rootless.Argument()} - } else { - exitCode = 0 - var containers []*libpod.Container - if c.All { - containers, err = runtime.GetContainers() - } else if c.Latest { - container, err = runtime.GetLatestContainer() - if err != nil { - return errors.Wrapf(err, "unable to get latest pod") - } - containers = append(containers, container) - } else { - for _, c := range c.InputArgs { - container, err = runtime.LookupContainer(c) - if err != nil { - if errors.Cause(err) == libpod.ErrNoSuchCtr { - exitCode = 1 - continue - } - return err - } - containers = append(containers, container) - } - } - // Now we really delete the containers. - for _, c := range containers { - _, ret, err := joinContainerOrCreateRootlessUserNS(runtime, c) - if err != nil { - return err - } - if ret != 0 { - os.Exit(ret) - } - } - os.Exit(exitCode) - } - } - failureCnt := 0 delContainers, err := getAllOrLatestContainers(&c.PodmanCommand, runtime, -1, "all") if err != nil { diff --git a/cmd/podman/run.go b/cmd/podman/run.go index 3c26e98c1..4bd469106 100644 --- a/cmd/podman/run.go +++ b/cmd/podman/run.go @@ -12,7 +12,6 @@ import ( "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" opentracing "github.com/opentracing/opentracing-go" "github.com/pkg/errors" "github.com/sirupsen/logrus" @@ -57,9 +56,6 @@ func runCmd(c *cliconfig.RunValues) error { if err := createInit(&c.PodmanCommand); err != nil { return err } - if os.Geteuid() != 0 { - rootless.SetSkipStorageSetup(true) - } runtime, err := libpodruntime.GetRuntime(&c.PodmanCommand) if err != nil { diff --git a/cmd/podman/run_test.go b/cmd/podman/run_test.go index a896f1dc7..0bf9cb4d9 100644 --- a/cmd/podman/run_test.go +++ b/cmd/podman/run_test.go @@ -8,6 +8,7 @@ import ( "github.com/containers/libpod/cmd/podman/shared" "github.com/containers/libpod/pkg/inspect" cc "github.com/containers/libpod/pkg/spec" + "github.com/containers/libpod/pkg/sysinfo" "github.com/docker/go-units" ociv1 "github.com/opencontainers/image-spec/specs-go/v1" spec "github.com/opencontainers/runtime-spec/specs-go" @@ -16,8 +17,9 @@ import ( ) var ( - cmd = []string{"podman", "test", "alpine"} - CLI *cliconfig.PodmanCommand + sysInfo = sysinfo.New(true) + cmd = []string{"podman", "test", "alpine"} + CLI *cliconfig.PodmanCommand ) // generates a mocked ImageData structure based on alpine @@ -100,6 +102,9 @@ func TestPIDsLimit(t *testing.T) { if runtime.GOOS != "linux" { t.Skip("seccomp, which is enabled by default, is only supported on Linux") } + if !sysInfo.PidsLimit { + t.Skip("running test not supported by the host system") + } args := []string{"--pids-limit", "22"} a := createCLI(args) a.InputArgs = args @@ -119,6 +124,9 @@ func TestBLKIOWeightDevice(t *testing.T) { if runtime.GOOS != "linux" { t.Skip("seccomp, which is enabled by default, is only supported on Linux") } + if !sysInfo.BlkioWeightDevice { + t.Skip("running test not supported by the host system") + } args := []string{"--blkio-weight-device", "/dev/zero:100"} a := createCLI(args) a.InputArgs = args @@ -137,6 +145,9 @@ func TestMemorySwap(t *testing.T) { if runtime.GOOS != "linux" { t.Skip("seccomp, which is enabled by default, is only supported on Linux") } + if !sysInfo.SwapLimit { + t.Skip("running test not supported by the host system") + } args := []string{"--memory-swap", "45m", "--memory", "40m"} a := createCLI(args) a.InputArgs = args diff --git a/cmd/podman/search.go b/cmd/podman/search.go index a10b9d419..e614887fc 100644 --- a/cmd/podman/search.go +++ b/cmd/podman/search.go @@ -83,11 +83,25 @@ func searchCmd(c *cliconfig.SearchValues) error { if len(results) == 0 { return nil } - out := formats.StdoutTemplateArray{Output: searchToGeneric(results), Template: format, Fields: genSearchOutputMap()} + out := formats.StdoutTemplateArray{Output: searchToGeneric(results), Template: format, Fields: searchHeaderMap()} formats.Writer(out).Out() return nil } +// searchHeaderMap returns the headers of a SearchResult. +func searchHeaderMap() map[string]string { + s := new(image.SearchResult) + v := reflect.Indirect(reflect.ValueOf(s)) + values := make(map[string]string, v.NumField()) + + for i := 0; i < v.NumField(); i++ { + key := v.Type().Field(i).Name + value := key + values[key] = strings.ToUpper(splitCamelCase(value)) + } + return values +} + func genSearchFormat(format string) string { if format != "" { // "\t" from the command line is not being recognized as a tab diff --git a/cmd/podman/shared/create.go b/cmd/podman/shared/create.go index d927e5bf6..cd82e4f1c 100644 --- a/cmd/podman/shared/create.go +++ b/cmd/podman/shared/create.go @@ -5,7 +5,6 @@ import ( "encoding/json" "fmt" "io" - "io/ioutil" "os" "path/filepath" "strconv" @@ -75,7 +74,8 @@ func CreateContainer(ctx context.Context, c *cliconfig.PodmanCommand, runtime *l imageName := "" var data *inspect.ImageData = nil - if rootfs == "" && !rootless.SkipStorageSetup() { + // Set the storage if we are running as euid == 0 and there is no rootfs specified + if rootfs == "" && os.Geteuid() == 0 { var writer io.Writer if !c.Bool("quiet") { writer = os.Stderr @@ -758,71 +758,6 @@ type namespace interface { Container() string } -func joinOrCreateRootlessUserNamespace(createConfig *cc.CreateConfig, runtime *libpod.Runtime) (bool, int, error) { - if os.Geteuid() == 0 { - return false, 0, nil - } - - if createConfig.Pod != "" { - pod, err := runtime.LookupPod(createConfig.Pod) - if err != nil { - return false, -1, err - } - inspect, err := pod.Inspect() - for _, ctr := range inspect.Containers { - prevCtr, err := runtime.LookupContainer(ctr.ID) - if err != nil { - return false, -1, err - } - s, err := prevCtr.State() - if err != nil { - return false, -1, err - } - if s != libpod.ContainerStateRunning && s != libpod.ContainerStatePaused { - continue - } - data, err := ioutil.ReadFile(prevCtr.Config().ConmonPidFile) - if err != nil { - return false, -1, errors.Wrapf(err, "cannot read conmon PID file %q", prevCtr.Config().ConmonPidFile) - } - conmonPid, err := strconv.Atoi(string(data)) - if err != nil { - return false, -1, errors.Wrapf(err, "cannot parse PID %q", data) - } - return rootless.JoinDirectUserAndMountNS(uint(conmonPid)) - } - } - - namespacesStr := []string{string(createConfig.IpcMode), string(createConfig.NetMode), string(createConfig.UsernsMode), string(createConfig.PidMode), string(createConfig.UtsMode)} - for _, i := range namespacesStr { - if cc.IsNS(i) { - return rootless.JoinNSPath(cc.NS(i)) - } - } - - namespaces := []namespace{createConfig.IpcMode, createConfig.NetMode, createConfig.UsernsMode, createConfig.PidMode, createConfig.UtsMode} - for _, i := range namespaces { - if i.IsContainer() { - ctr, err := runtime.LookupContainer(i.Container()) - if err != nil { - return false, -1, err - } - pid, err := ctr.PID() - if err != nil { - return false, -1, err - } - if pid == 0 { - if createConfig.Pod != "" { - continue - } - return false, -1, errors.Errorf("dependency container %s is not running", ctr.ID()) - } - return rootless.JoinNS(uint(pid), 0) - } - } - return rootless.BecomeRootInUserNS() -} - func CreateContainerFromCreateConfig(r *libpod.Runtime, createConfig *cc.CreateConfig, ctx context.Context, pod *libpod.Pod) (*libpod.Container, error) { runtimeSpec, err := cc.CreateConfigToOCISpec(createConfig) if err != nil { @@ -833,13 +768,6 @@ func CreateContainerFromCreateConfig(r *libpod.Runtime, createConfig *cc.CreateC if err != nil { return nil, err } - became, ret, err := joinOrCreateRootlessUserNamespace(createConfig, r) - if err != nil { - return nil, err - } - if became { - os.Exit(ret) - } ctr, err := r.NewContainer(ctx, runtimeSpec, options...) if err != nil { diff --git a/cmd/podman/stop.go b/cmd/podman/stop.go index 2a1470ad0..e27be64f6 100644 --- a/cmd/podman/stop.go +++ b/cmd/podman/stop.go @@ -7,7 +7,6 @@ import ( "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/libpod" "github.com/containers/libpod/pkg/adapter" - "github.com/containers/libpod/pkg/rootless" "github.com/opentracing/opentracing-go" "github.com/pkg/errors" "github.com/spf13/cobra" @@ -59,7 +58,6 @@ func stopCmd(c *cliconfig.StopValues) error { defer span.Finish() } - rootless.SetSkipStorageSetup(true) runtime, err := adapter.GetRuntime(&c.PodmanCommand) if err != nil { return errors.Wrapf(err, "could not get runtime") diff --git a/cmd/podman/top.go b/cmd/podman/top.go index 2512631c1..5d394d2d6 100644 --- a/cmd/podman/top.go +++ b/cmd/podman/top.go @@ -9,7 +9,6 @@ import ( "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/cmd/podman/libpodruntime" "github.com/containers/libpod/libpod" - "github.com/containers/libpod/pkg/rootless" "github.com/pkg/errors" "github.com/spf13/cobra" ) @@ -77,7 +76,6 @@ func topCmd(c *cliconfig.TopValues) error { return errors.Errorf("you must provide the name or id of a running container") } - rootless.SetSkipStorageSetup(true) runtime, err := libpodruntime.GetRuntime(&c.PodmanCommand) if err != nil { return errors.Wrapf(err, "error creating libpod runtime") @@ -104,18 +102,6 @@ func topCmd(c *cliconfig.TopValues) error { if conStat != libpod.ContainerStateRunning { return errors.Errorf("top can only be used on running containers") } - - pid, err := container.PID() - if err != nil { - return err - } - became, ret, err := rootless.JoinNS(uint(pid), 0) - if err != nil { - return err - } - if became { - os.Exit(ret) - } psOutput, err := container.GetContainerPidInformation(descriptors) if err != nil { return err diff --git a/cmd/podman/tree.go b/cmd/podman/tree.go index c56e35aef..371e88495 100644 --- a/cmd/podman/tree.go +++ b/cmd/podman/tree.go @@ -5,9 +5,9 @@ import ( "fmt" "github.com/containers/libpod/cmd/podman/cliconfig" - "github.com/containers/libpod/cmd/podman/libpodruntime" "github.com/containers/libpod/libpod/image" - units "github.com/docker/go-units" + "github.com/containers/libpod/pkg/adapter" + "github.com/docker/go-units" "github.com/pkg/errors" "github.com/spf13/cobra" ) @@ -41,16 +41,6 @@ func init() { treeCommand.Flags().BoolVar(&treeCommand.WhatRequires, "whatrequires", false, "Show all child images and layers of the specified image") } -// infoImage keep information of Image along with all associated layers -type infoImage struct { - // id of image - id string - // tags of image - tags []string - // layers stores all layers of image. - layers []image.LayerInfo -} - func treeCmd(c *cliconfig.TreeValues) error { args := c.InputArgs if len(args) == 0 { @@ -60,46 +50,33 @@ func treeCmd(c *cliconfig.TreeValues) error { return errors.Errorf("you must provide at most 1 argument") } - runtime, err := libpodruntime.GetRuntime(&c.PodmanCommand) + runtime, err := adapter.GetRuntime(&c.PodmanCommand) if err != nil { return errors.Wrapf(err, "error creating libpod runtime") } defer runtime.Shutdown(false) - - img, err := runtime.ImageRuntime().NewFromLocal(args[0]) + imageInfo, layerInfoMap, img, err := runtime.Tree(c) if err != nil { return err } + return printTree(imageInfo, layerInfoMap, img, c.WhatRequires) +} - // Fetch map of image-layers, which is used for printing output. - layerInfoMap, err := image.GetLayersMapWithImageInfo(runtime.ImageRuntime()) - if err != nil { - return errors.Wrapf(err, "error while retriving layers of image %q", img.InputName) - } - - // Create an imageInfo and fill the image and layer info - imageInfo := &infoImage{ - id: img.ID(), - tags: img.Names(), - } - +func printTree(imageInfo *image.InfoImage, layerInfoMap map[string]*image.LayerInfo, img *adapter.ContainerImage, whatRequires bool) error { size, err := img.Size(context.Background()) if err != nil { - return errors.Wrapf(err, "error while retriving image size") + return err } - fmt.Printf("Image ID: %s\n", imageInfo.id[:12]) - fmt.Printf("Tags:\t %s\n", imageInfo.tags) + + fmt.Printf("Image ID: %s\n", imageInfo.ID[:12]) + fmt.Printf("Tags:\t %s\n", imageInfo.Tags) fmt.Printf("Size:\t %v\n", units.HumanSizeWithPrecision(float64(*size), 4)) fmt.Printf(fmt.Sprintf("Image Layers\n")) - if !c.WhatRequires { + if !whatRequires { // fill imageInfo with layers associated with image. // the layers will be filled such that // (Start)RootLayer->...intermediate Parent Layer(s)-> TopLayer(End) - err := buildImageHierarchyMap(imageInfo, layerInfoMap, img.TopLayer()) - if err != nil { - return err - } // Build output from imageInfo into buffer printImageHierarchy(imageInfo) @@ -108,30 +85,8 @@ func treeCmd(c *cliconfig.TreeValues) error { // the layers will be filled such that // (Start)TopLayer->...intermediate Child Layer(s)-> Child TopLayer(End) // (Forks)... intermediate Child Layer(s) -> Child Top Layer(End) - err := printImageChildren(layerInfoMap, img.TopLayer(), "", true) - if err != nil { - return err - } - } - - return nil -} - -// Stores hierarchy of images such that all parent layers using which image is built are stored in imageInfo -// Layers are added such that (Start)RootLayer->...intermediate Parent Layer(s)-> TopLayer(End) -func buildImageHierarchyMap(imageInfo *infoImage, layerMap map[string]*image.LayerInfo, layerID string) error { - if layerID == "" { - return nil - } - ll, ok := layerMap[layerID] - if !ok { - return fmt.Errorf("lookup error: layerid %s not found", layerID) + return printImageChildren(layerInfoMap, img.TopLayer(), "", true) } - if err := buildImageHierarchyMap(imageInfo, layerMap, ll.ParentID); err != nil { - return err - } - - imageInfo.layers = append(imageInfo.layers, *ll) return nil } @@ -175,14 +130,14 @@ func printImageChildren(layerMap map[string]*image.LayerInfo, layerID string, pr } // prints the layers info of image -func printImageHierarchy(imageInfo *infoImage) { - for count, l := range imageInfo.layers { +func printImageHierarchy(imageInfo *image.InfoImage) { + for count, l := range imageInfo.Layers { var tags string intend := middleItem if len(l.RepoTags) > 0 { tags = fmt.Sprintf(" Top Layer of: %s", l.RepoTags) } - if count == len(imageInfo.layers)-1 { + if count == len(imageInfo.Layers)-1 { intend = lastItem } fmt.Printf("%s ID: %s Size: %7v%s\n", intend, l.ID[:12], units.HumanSizeWithPrecision(float64(l.Size), 4), tags) diff --git a/cmd/podman/varlink/io.podman.varlink b/cmd/podman/varlink/io.podman.varlink index 5e996f46b..92fdcd20f 100644 --- a/cmd/podman/varlink/io.podman.varlink +++ b/cmd/podman/varlink/io.podman.varlink @@ -68,7 +68,8 @@ type Image ( virtualSize: int, containers: int, labels: [string]string, - isParent: bool + isParent: bool, + topLayer: string ) # ImageHistory describes the returned structure from ImageHistory. @@ -461,6 +462,13 @@ type Event( type: string ) +type DiffInfo( + # path that is different + path: string, + # Add, Delete, Modify + changeType: string +) + # GetVersion returns version and build information of the podman service method GetVersion() -> ( version: string, @@ -1154,6 +1162,15 @@ method LoadImage(name: string, inputFile: string, quiet: bool, deleteFile: bool) # GetEvents returns known libpod events filtered by the options provided. method GetEvents(filter: []string, since: string, until: string) -> (events: Event) +# Diff returns a diff between libpod objects +method Diff(name: string) -> (diffs: []DiffInfo) + +# GetLayersMapWithImageInfo is for the development of Podman and should not be used. +method GetLayersMapWithImageInfo() -> (layerMap: string) + +# BuildImageHierarchyMap is for the development of Podman and should not be used. +method BuildImageHierarchyMap(name: string) -> (imageInfo: string) + # ImageNotFound means the image could not be found by the provided name or ID in local storage. error ImageNotFound (id: string, reason: string) diff --git a/contrib/cirrus/README.md b/contrib/cirrus/README.md index 0dabf5df6..ea358d2d7 100644 --- a/contrib/cirrus/README.md +++ b/contrib/cirrus/README.md @@ -63,26 +63,26 @@ task (pass or fail) is set based on the exit status of the last script to execut Total execution time is capped at 2-hours (includes all the above) but this script normally completes in less than an hour. -### ``rootless_testing`` Task +### ``special_testing`` Task + +This task exercises podman under specialized environments or conditions. +The specific differences from the ``testing`` task depend upon the +contents of the ``$SPECIALMODE`` environment variable. + +| Value | Meaning | +| rootless | Setup a regular user to build/run integration tests. | +| in_podman | Setup a container image, build/run integration tests inside container | ***N/B: Steps below are performed by automation*** 1. After `gating` passes, spin up one VM per - `matrix: image_name` item. Once accessible, ``ssh`` - into each VM as the `root` user. + `matrix: image_name` item. + +2. ``setup_environment.sh``: Mostly the same as + in ``testing`` task, then specialized depending on ``$SPECIALMODE``. + +3. Which tests and how they execute depends on ``$SPECIALMODE``. -2. ``setup_environment.sh``: Configure root's `.bash_profile` - the same as for other tasks. However, also add a regular - user account, chown all the source code to them. Set up - fresh ssh pub/priv. keys for the root user, adding the - public part to the user's `authorized_keys` file. - -3. As root, call ssh to connect to localhost as the user, - and run the ``rootless_test.sh`` script from the source - tree. This is needed so the user has a clean process tree - and environment - i.e. without `sudo`, `su`, `runuser`, - etc. in the mix. From here, all testing as the user may - be performed. ### ``optional_testing`` Task diff --git a/.papr.sh b/contrib/cirrus/container_test.sh index c5aada904..e6c1a3a47 100644 --- a/.papr.sh +++ b/contrib/cirrus/container_test.sh @@ -1,7 +1,7 @@ #!/bin/bash set -xeuo pipefail -export GOPATH=/go +export GOPATH=/var/tmp/go export PATH=$HOME/gopath/bin:$PATH:$GOPATH/bin export GOSRC=$GOPATH/src/github.com/containers/libpod @@ -125,7 +125,7 @@ fi # Run integration tests if [ $integrationtest -eq 1 ]; then make TAGS="${TAGS}" test-binaries - make varlink_generate GOPATH=/go - make ginkgo GOPATH=/go $INTEGRATION_TEST_ENVS - make ginkgo-remote GOPATH=/go $INTEGRATION_TEST_ENVS + make varlink_generate + make ginkgo $INTEGRATION_TEST_ENVS + make ginkgo-remote $INTEGRATION_TEST_ENVS fi diff --git a/contrib/cirrus/integration_test.sh b/contrib/cirrus/integration_test.sh index 58c8af289..8a2507f38 100755 --- a/contrib/cirrus/integration_test.sh +++ b/contrib/cirrus/integration_test.sh @@ -5,33 +5,64 @@ source $(dirname $0)/lib.sh req_env_var " GOSRC $GOSRC +SCRIPT_BASE $SCRIPT_BASE OS_RELEASE_ID $OS_RELEASE_ID OS_RELEASE_VER $OS_RELEASE_VER +CONTAINER_RUNTIME $CONTAINER_RUNTIME " -record_timestamp "integration test start" +exit_handler() { + set +ex + record_timestamp "integration test end" +} +trap exit_handler EXIT -clean_env +record_timestamp "integration test start" -set -x cd "$GOSRC" -case "${OS_RELEASE_ID}-${OS_RELEASE_VER}" in - ubuntu-18) - make install PREFIX=/usr ETCDIR=/etc - make test-binaries - SKIP_USERNS=1 make localintegration - ;; - fedora-29) ;& # Continue to the next item - fedora-28) ;& - centos-7) ;& - rhel-7) - make install PREFIX=/usr ETCDIR=/etc - make podman-remote - install bin/podman-remote /usr/bin - make test-binaries - make localintegration - ;; - *) bad_os_id_ver ;; -esac - -record_timestamp "integration test end" + +if [[ "$SPECIALMODE" == "in_podman" ]] +then + set -x + ${CONTAINER_RUNTIME} run --rm --privileged --net=host \ + -v $GOSRC:$GOSRC:Z \ + --workdir $GOSRC \ + -e "CGROUP_MANAGER=cgroupfs" \ + -e "STORAGE_OPTIONS=--storage-driver=vfs" \ + -e "CRIO_ROOT=$GOSRC" \ + -e "PODMAN_BINARY=/usr/bin/podman" \ + -e "CONMON_BINARY=/usr/libexec/podman/conmon" \ + -e "DIST=$OS_RELEASE_ID" \ + -e "CONTAINER_RUNTIME=$CONTAINER_RUNTIME" \ + ${OS_RELEASE_ID}podmanbuild bash $GOSRC/$SCRIPT_BASE/container_test.sh -b -i -t + + exit $? +elif [[ "$SPECIALMODE" == "rootless" ]] +then + req_env_var "ROOTLESS_USER $ROOTLESS_USER" + set -x + ssh $ROOTLESS_USER@localhost \ + -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -o CheckHostIP=no \ + $GOSRC/$SCRIPT_BASE/rootless_test.sh + exit $? +else + set -x + make + make install PREFIX=/usr ETCDIR=/etc + make test-binaries + clean_env + + case "${OS_RELEASE_ID}-${OS_RELEASE_VER}" in + ubuntu-18) ;; + fedora-29) ;& # Continue to the next item + fedora-28) ;& + centos-7) ;& + rhel-7) + make podman-remote + install bin/podman-remote /usr/bin + ;; + *) bad_os_id_ver ;; + esac + make localintegration + exit $? +fi diff --git a/contrib/cirrus/lib.sh b/contrib/cirrus/lib.sh index e941610e2..6c45b2c5d 100644 --- a/contrib/cirrus/lib.sh +++ b/contrib/cirrus/lib.sh @@ -18,6 +18,8 @@ CIRRUS_BUILD_ID=${CIRRUS_BUILD_ID:-DEADBEEF} # a human CIRRUS_BASE_SHA=${CIRRUS_BASE_SHA:-HEAD} CIRRUS_CHANGE_IN_REPO=${CIRRUS_CHANGE_IN_REPO:-FETCH_HEAD} TIMESTAMPS_FILEPATH="${TIMESTAMPS_FILEPATH:-/var/tmp/timestamps}" +SPECIALMODE="${SPECIALMODE:-none}" +export CONTAINER_RUNTIME=${CONTAINER_RUNTIME:-podman} if ! [[ "$PATH" =~ "/usr/local/bin" ]] then @@ -81,6 +83,7 @@ CIRRUS_USER_COLLABORATOR $CIRRUS_USER_COLLABORATOR CIRRUS_USER_PERMISSION $CIRRUS_USER_PERMISSION CIRRUS_WORKING_DIR $CIRRUS_WORKING_DIR CIRRUS_HTTP_CACHE_HOST $CIRRUS_HTTP_CACHE_HOST +SPECIALMODE $SPECIALMODE $(go env) PACKER_BUILDS $PACKER_BUILDS " | while read NAME VALUE @@ -127,15 +130,6 @@ bad_os_id_ver() { exit 42 } -run_rootless() { - if [[ -z "$ROOTLESS_USER" ]] - then - return 1 - else - return 0 - fi -} - stub() { echo "STUB: Pretending to do $1" } @@ -179,6 +173,13 @@ setup_rootless() { return 0 fi + # Only do this once + cd $GOSRC + make install.catatonit + go get github.com/onsi/ginkgo/ginkgo + go get github.com/onsi/gomega/... + dnf -y update runc + # Guarantee independence from specific values ROOTLESS_UID=$[RANDOM+1000] ROOTLESS_GID=$[RANDOM+1000] diff --git a/contrib/cirrus/rootless_test.sh b/contrib/cirrus/rootless_test.sh index d0e2ceb95..88b38f45b 100755 --- a/contrib/cirrus/rootless_test.sh +++ b/contrib/cirrus/rootless_test.sh @@ -12,9 +12,9 @@ OS_RELEASE_ID $OS_RELEASE_ID OS_RELEASE_VER $OS_RELEASE_VER " -if ! run_rootless +if [[ "$UID" == "0" ]] then - echo "Error: Expected rootless env. vars not set or empty" + echo "Error: Expected to be running as a regular user" exit 1 fi @@ -24,16 +24,9 @@ echo "Hello, my name is $USER and I live in $PWD can I be your friend?" record_timestamp "rootless test start" cd "$GOSRC" -case "${OS_RELEASE_ID}-${OS_RELEASE_VER}" in - ubuntu-18) ;& # Continue to the next item - fedora-29) ;& - fedora-28) - make - make varlink_generate - make test-binaries - make ginkgo - ;; - *) bad_os_id_ver ;; -esac +make +make varlink_generate +make test-binaries +make ginkgo record_timestamp "rootless test end" diff --git a/contrib/cirrus/setup_container_environment.sh b/contrib/cirrus/setup_container_environment.sh new file mode 100755 index 000000000..23df4fe8b --- /dev/null +++ b/contrib/cirrus/setup_container_environment.sh @@ -0,0 +1,23 @@ +#!/bin/bash +set -e + +source $(dirname $0)/lib.sh + +req_env_var " +GOSRC $GOSRC +OS_RELEASE_ID $OS_RELEASE_ID +CONTAINER_RUNTIME $CONTAINER_RUNTIME +" + +DIST=$OS_RELEASE_ID +IMAGE=${DIST}podmanbuild + +# 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} . diff --git a/contrib/cirrus/setup_environment.sh b/contrib/cirrus/setup_environment.sh index 96d0e1b55..55706954e 100755 --- a/contrib/cirrus/setup_environment.sh +++ b/contrib/cirrus/setup_environment.sh @@ -43,7 +43,6 @@ then "export OS_RELEASE_ID=\"$(os_release_id)\"" \ "export OS_RELEASE_VER=\"$(os_release_ver)\"" \ "export OS_REL_VER=\"$(os_release_id)-$(os_release_ver)\"" \ - "export ROOTLESS_USER=$ROOTLESS_USER" \ "export BUILT_IMAGE_SUFFIX=\"-$CIRRUS_REPO_NAME-${CIRRUS_CHANGE_IN_REPO:0:8}\"" \ "export GOPATH=\"/var/tmp/go\"" \ 'export PATH="$HOME/bin:$GOPATH/bin:/usr/local/bin:$PATH"' \ @@ -75,14 +74,17 @@ then # Reload to incorporate any changes from above source "$SCRIPT_BASE/lib.sh" - if run_rootless - then - setup_rootless - make install.catatonit - go get github.com/onsi/ginkgo/ginkgo - go get github.com/onsi/gomega/... - dnf -y update runc - fi + case "$SPECIALMODE" in + rootless) + X=$(echo "export ROOTLESS_USER='some${RANDOM}dude'" | \ + tee -a "$HOME/$ENVLIB") && eval "$X" && echo "$X" + setup_rootless + ;; + in_podman) # Assumed to be Fedora + dnf install -y podman buildah + $SCRIPT_BASE/setup_container_environment.sh + ;; + esac fi show_env_vars diff --git a/contrib/cirrus/unit_test.sh b/contrib/cirrus/unit_test.sh index fd9e82509..4ace19d10 100755 --- a/contrib/cirrus/unit_test.sh +++ b/contrib/cirrus/unit_test.sh @@ -15,17 +15,8 @@ clean_env set -x cd "$GOSRC" -case "${OS_RELEASE_ID}-${OS_RELEASE_VER}" in - ubuntu-18) ;& # Continue to the next item - fedora-29) ;& - fedora-28) ;& - centos-7) ;& - rhel-7) - make install.tools - make localunit - make - ;; - *) bad_os_id_ver ;; -esac +make install.tools +make localunit +make record_timestamp "unit test end" diff --git a/contrib/spec/podman.spec.in b/contrib/spec/podman.spec.in index 319bbe979..396304f8d 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: 1.2.0 +Version: 1.3.0 Release: #COMMITDATE#.git%{shortcommit0}%{?dist} Summary: Manage Pods, Containers and Container Images License: ASL 2.0 diff --git a/docs/podman-container-checkpoint.1.md b/docs/podman-container-checkpoint.1.md index 666dbbb80..79dc12261 100644 --- a/docs/podman-container-checkpoint.1.md +++ b/docs/podman-container-checkpoint.1.md @@ -10,7 +10,7 @@ podman\-container\-checkpoint - Checkpoints one or more running containers Checkpoints all the processes in one or more containers. You may use container IDs or names as input. ## OPTIONS -**-k**, **--keep** +**--keep**, **-k** Keep all temporary log and statistics files created by CRIU during checkpointing. These files are not deleted if checkpointing fails for further debugging. If checkpointing succeeds these diff --git a/docs/podman-container-restore.1.md b/docs/podman-container-restore.1.md index e47d585cc..e41f7c1d8 100644 --- a/docs/podman-container-restore.1.md +++ b/docs/podman-container-restore.1.md @@ -10,7 +10,7 @@ podman\-container\-restore - Restores one or more running containers Restores a container from a checkpoint. You may use container IDs or names as input. ## OPTIONS -**-k**, **--keep** +**--keep**, **-k** Keep all temporary log and statistics files created by CRIU during checkpointing as well as restoring. These files are not deleted if restoring diff --git a/docs/podman-container-runlabel.1.md b/docs/podman-container-runlabel.1.md index 7fa9805e6..39d798804 100644 --- a/docs/podman-container-runlabel.1.md +++ b/docs/podman-container-runlabel.1.md @@ -76,11 +76,12 @@ The [username[:password]] to use to authenticate with the registry if required. If one or both values are not supplied, a command line prompt will appear and the value can be entered. The password is entered without echo. -**-h** **--help** +**--help** **-h** Print usage statement -**-n** **--name**="" - Use this name for creating content for the container. NAME will default to the IMAGENAME if it is not specified. +**--name** **-n**="" + +Use this name for creating content for the container. NAME will default to the IMAGENAME if it is not specified. **--quiet, -q** diff --git a/docs/podman-generate-kube.1.md b/docs/podman-generate-kube.1.md index b6fa78606..99029be90 100644 --- a/docs/podman-generate-kube.1.md +++ b/docs/podman-generate-kube.1.md @@ -5,7 +5,7 @@ podman-generate-kube - Generate Kubernetes YAML # SYNOPSIS -**podman generate kube** [*-s*][*--service*] *container* | *pod* +**podman generate kube** [*-s*|*--service*] *container* | *pod* # DESCRIPTION **podman generate kube** will generate Kubernetes Pod YAML (v1 specification) from a podman container or pod. Whether @@ -20,7 +20,8 @@ Note that the generated Kubernetes YAML file can be used to re-run the deploymen # OPTIONS: -**s** **--service** +**--service** **-s** + Generate a Kubernetes service object in addition to the Pods. ## Examples ## diff --git a/docs/podman-inspect.1.md b/docs/podman-inspect.1.md index 712891ad6..8b67c7dac 100644 --- a/docs/podman-inspect.1.md +++ b/docs/podman-inspect.1.md @@ -17,7 +17,7 @@ unspecified type. If a format is specified, the given template will be executed ## OPTIONS -**--type, t="TYPE"** +**--type, -t="TYPE"** Return JSON for the specified type. Type can be 'container', 'image' or 'all' (default: all) (Only meaningful when invoked as *podman inspect*) diff --git a/docs/podman-pod-kill.1.md b/docs/podman-pod-kill.1.md index 2a863d3d9..d617acd66 100644 --- a/docs/podman-pod-kill.1.md +++ b/docs/podman-pod-kill.1.md @@ -21,7 +21,7 @@ to run pods such as CRI-O, the last started pod could be from either of those me The latest option is not supported on the remote client. -**--signal, s** +**--signal, -s** Signal to send to the containers in the pod. For more information on Linux signals, refer to *man signal(7)*. diff --git a/docs/podman-pod-pause.1.md b/docs/podman-pod-pause.1.md index 418a9ee2a..0ed63a7f8 100644 --- a/docs/podman-pod-pause.1.md +++ b/docs/podman-pod-pause.1.md @@ -11,7 +11,7 @@ Pauses all the running processes in the containers of one or more pods. You may ## OPTIONS -**--all, a** +**--all, -a** Pause all pods. diff --git a/docs/podman-pod-rm.1.md b/docs/podman-pod-rm.1.md index aa26a1bbb..7cd7c26bc 100644 --- a/docs/podman-pod-rm.1.md +++ b/docs/podman-pod-rm.1.md @@ -11,7 +11,7 @@ podman\-pod\-rm - Remove one or more pods ## OPTIONS -**--all, a** +**--all, -a** Remove all pods. Can be used in conjunction with \-f as well. @@ -21,7 +21,7 @@ Instead of providing the pod name or ID, remove the last created pod. The latest option is not supported on the remote client. -**--force, f** +**--force, -f** Stop running containers and delete all stopped containers before removal of pod. diff --git a/docs/podman-pod-stop.1.md b/docs/podman-pod-stop.1.md index 338e04d67..787c672bd 100644 --- a/docs/podman-pod-stop.1.md +++ b/docs/podman-pod-stop.1.md @@ -21,7 +21,7 @@ Instead of providing the pod name or ID, stop the last created pod. The latest option is not supported on the remote client. -**--timeout, --time, t** +**--timeout, --time, -t** Timeout to wait before forcibly stopping the containers in the pod. diff --git a/docs/podman-pod-unpause.1.md b/docs/podman-pod-unpause.1.md index 1004e09f9..8930fde32 100644 --- a/docs/podman-pod-unpause.1.md +++ b/docs/podman-pod-unpause.1.md @@ -11,7 +11,7 @@ Unpauses all the paused processes in the containers of one or more pods. You ma ## OPTIONS -**--all, a** +**--all, -a** Unpause all pods. diff --git a/docs/podman-port.1.md b/docs/podman-port.1.md index 020a25d32..5adfae5f3 100644 --- a/docs/podman-port.1.md +++ b/docs/podman-port.1.md @@ -11,7 +11,7 @@ List port mappings for the *container* or lookup the public-facing port that is ## OPTIONS -**--all, a** +**--all, -a** List all known port mappings for running containers. When using this option, you cannot pass any container names or private ports/protocols as filters. diff --git a/docs/podman-rm.1.md b/docs/podman-rm.1.md index dc1729188..16d4027c9 100644 --- a/docs/podman-rm.1.md +++ b/docs/podman-rm.1.md @@ -13,11 +13,11 @@ podman\-container\-rm (podman\-rm) - Remove one or more containers ## OPTIONS -**--all, a** +**--all, -a** Remove all containers. Can be used in conjunction with -f as well. -**--force, f** +**--force, -f** Force the removal of running and paused containers. Forcing a containers removal also removes containers from container storage even if the container is not known to podman. @@ -32,7 +32,7 @@ The latest option is not supported on the remote client. **--volumes, -v** -Remove the volumes associated with the container. (Not yet implemented) +Remove the volumes associated with the container. ## EXAMPLE Remove a container by its name *mywebserver* diff --git a/docs/podman-volume-inspect.1.md b/docs/podman-volume-inspect.1.md index c22c80bb7..88cc3cf3e 100644 --- a/docs/podman-volume-inspect.1.md +++ b/docs/podman-volume-inspect.1.md @@ -15,7 +15,7 @@ existing volumes, use the **--all** flag. ## OPTIONS -**-a**, **--all**="" +**-a**, **--all** Inspect all volumes. diff --git a/docs/podman-volume-rm.1.md b/docs/podman-volume-rm.1.md index c23d7675c..8c3765235 100644 --- a/docs/podman-volume-rm.1.md +++ b/docs/podman-volume-rm.1.md @@ -21,7 +21,8 @@ Remove all volumes. **-f**, **--force**="" -Remove a volume by force, even if it is being used by a container +Remove a volume by force. +If it is being used by containers, the containers will be removed first. **--help** diff --git a/docs/podman-wait.1.md b/docs/podman-wait.1.md index 2d145527b..ed5e11ac7 100644 --- a/docs/podman-wait.1.md +++ b/docs/podman-wait.1.md @@ -17,7 +17,7 @@ After the container stops, the container's return code is printed. Print usage statement -**--interval, i**" +**--interval, -i**" Microseconds to wait before polling for completion **--latest, -l** diff --git a/libpod/boltdb_state.go b/libpod/boltdb_state.go index 92a7b1538..d8cfa2bda 100644 --- a/libpod/boltdb_state.go +++ b/libpod/boltdb_state.go @@ -1358,56 +1358,6 @@ func (s *BoltState) AddVolume(volume *Volume) error { return err } -// RemoveVolCtrDep updates the container dependencies sub bucket of the given volume. -// It deletes it from the bucket when found. -// This is important when force removing a volume and we want to get rid of the dependencies. -func (s *BoltState) RemoveVolCtrDep(volume *Volume, ctrID string) error { - if ctrID == "" { - return nil - } - - if !s.valid { - return ErrDBBadConfig - } - - if !volume.valid { - return ErrVolumeRemoved - } - - volName := []byte(volume.Name()) - - db, err := s.getDBCon() - if err != nil { - return err - } - defer s.closeDBCon(db) - - err = db.Update(func(tx *bolt.Tx) error { - volBkt, err := getVolBucket(tx) - if err != nil { - return err - } - - volDB := volBkt.Bucket(volName) - if volDB == nil { - volume.valid = false - return errors.Wrapf(ErrNoSuchVolume, "no volume with name %s found in database", volume.Name()) - } - - // Make a subbucket for the containers using the volume - ctrDepsBkt := volDB.Bucket(volDependenciesBkt) - depCtrID := []byte(ctrID) - if depExists := ctrDepsBkt.Get(depCtrID); depExists != nil { - if err := ctrDepsBkt.Delete(depCtrID); err != nil { - return errors.Wrapf(err, "error deleting container dependencies %q for volume %s in ctrDependencies bucket in DB", ctrID, volume.Name()) - } - } - - return nil - }) - return err -} - // RemoveVolume removes the given volume from the state func (s *BoltState) RemoveVolume(volume *Volume) error { if !s.valid { @@ -1433,6 +1383,11 @@ func (s *BoltState) RemoveVolume(volume *Volume) error { return err } + ctrBkt, err := getCtrBucket(tx) + if err != nil { + return err + } + // Check if the volume exists volDB := volBkt.Bucket(volName) if volDB == nil { @@ -1448,6 +1403,18 @@ func (s *BoltState) RemoveVolume(volume *Volume) error { if volCtrsBkt != nil { var deps []string err = volCtrsBkt.ForEach(func(id, value []byte) error { + // Alright, this is ugly. + // But we need it to work around the change in + // volume dependency handling, to make sure that + // older Podman versions don't cause DB + // corruption. + // Look up all dependencies and see that they + // still exist before appending. + ctrExists := ctrBkt.Bucket(id) + if ctrExists == nil { + return nil + } + deps = append(deps, string(id)) return nil }) @@ -1629,6 +1596,11 @@ func (s *BoltState) VolumeInUse(volume *Volume) ([]string, error) { return err } + ctrBucket, err := getCtrBucket(tx) + if err != nil { + return err + } + volDB := volBucket.Bucket([]byte(volume.Name())) if volDB == nil { volume.valid = false @@ -1642,6 +1614,13 @@ func (s *BoltState) VolumeInUse(volume *Volume) ([]string, error) { // Iterate through and add dependencies err = dependsBkt.ForEach(func(id, value []byte) error { + // Look up all dependencies and see that they + // still exist before appending. + ctrExists := ctrBucket.Bucket(id) + if ctrExists == nil { + return nil + } + depCtrs = append(depCtrs, string(id)) return nil diff --git a/libpod/boltdb_state_internal.go b/libpod/boltdb_state_internal.go index b6a0759b1..a6900a6d3 100644 --- a/libpod/boltdb_state_internal.go +++ b/libpod/boltdb_state_internal.go @@ -564,23 +564,17 @@ func (s *BoltState) addContainer(ctr *Container, pod *Pod) error { } } - // Add container to volume dependencies bucket if container is using a named volume - if ctr.runtime.config.VolumePath == "" { - return nil - } - for _, vol := range ctr.config.Spec.Mounts { - if strings.Contains(vol.Source, ctr.runtime.config.VolumePath) { - volName := strings.Split(vol.Source[len(ctr.runtime.config.VolumePath)+1:], "/")[0] - volDB := volBkt.Bucket([]byte(volName)) - if volDB == nil { - return errors.Wrapf(ErrNoSuchVolume, "no volume with name %s found in database", volName) - } + // Add container to named volume dependencies buckets + for _, vol := range ctr.config.NamedVolumes { + volDB := volBkt.Bucket([]byte(vol.Name)) + if volDB == nil { + return errors.Wrapf(ErrNoSuchVolume, "no volume with name %s found in database when adding container %s", vol.Name, ctr.ID()) + } - ctrDepsBkt := volDB.Bucket(volDependenciesBkt) - if depExists := ctrDepsBkt.Get(ctrID); depExists == nil { - if err := ctrDepsBkt.Put(ctrID, ctrID); err != nil { - return errors.Wrapf(err, "error storing container dependencies %q for volume %s in ctrDependencies bucket in DB", ctr.ID(), volName) - } + ctrDepsBkt := volDB.Bucket(volDependenciesBkt) + if depExists := ctrDepsBkt.Get(ctrID); depExists == nil { + if err := ctrDepsBkt.Put(ctrID, ctrID); err != nil { + return errors.Wrapf(err, "error adding container %s to volume %s dependencies", ctr.ID(), vol.Name) } } } @@ -745,22 +739,19 @@ func (s *BoltState) removeContainer(ctr *Container, pod *Pod, tx *bolt.Tx) error } } - // Remove container from volume dependencies bucket if container is using a named volume - for _, vol := range ctr.config.Spec.Mounts { - if strings.Contains(vol.Source, ctr.runtime.config.VolumePath) { - volName := strings.Split(vol.Source[len(ctr.runtime.config.VolumePath)+1:], "/")[0] - - volDB := volBkt.Bucket([]byte(volName)) - if volDB == nil { - // Let's assume the volume was already deleted and continue to remove the container - continue - } + // Remove container from named volume dependencies buckets + for _, vol := range ctr.config.NamedVolumes { + volDB := volBkt.Bucket([]byte(vol.Name)) + if volDB == nil { + // Let's assume the volume was already deleted and + // continue to remove the container + continue + } - ctrDepsBkt := volDB.Bucket(volDependenciesBkt) - if depExists := ctrDepsBkt.Get(ctrID); depExists != nil { - if err := ctrDepsBkt.Delete(ctrID); err != nil { - return errors.Wrapf(err, "error deleting container dependencies %q for volume %s in ctrDependencies bucket in DB", ctr.ID(), volName) - } + ctrDepsBkt := volDB.Bucket(volDependenciesBkt) + if depExists := ctrDepsBkt.Get(ctrID); depExists == nil { + if err := ctrDepsBkt.Delete(ctrID); err != nil { + return errors.Wrapf(err, "error deleting container %s dependency on volume %s", ctr.ID(), vol.Name) } } } diff --git a/libpod/container.go b/libpod/container.go index 739406e42..6d5e063ab 100644 --- a/libpod/container.go +++ b/libpod/container.go @@ -234,6 +234,8 @@ type ContainerConfig struct { // These include the SHM mount. // These must be unmounted before the container's rootfs is unmounted. Mounts []string `json:"mounts,omitempty"` + // NamedVolumes lists the named volumes to mount into the container. + NamedVolumes []*ContainerNamedVolume `json:"namedVolumes,omitempty"` // Security Config @@ -354,9 +356,6 @@ type ContainerConfig struct { // ExitCommand is the container's exit command. // This Command will be executed when the container exits ExitCommand []string `json:"exitCommand,omitempty"` - // LocalVolumes are the built-in volumes we get from the --volumes-from flag - // It picks up the built-in volumes of the container used by --volumes-from - LocalVolumes []spec.Mount // IsInfra is a bool indicating whether this container is an infra container used for // sharing kernel namespaces in a pod IsInfra bool `json:"pause"` @@ -368,6 +367,18 @@ type ContainerConfig struct { HealthCheckConfig *manifest.Schema2HealthConfig `json:"healthcheck"` } +// ContainerNamedVolume is a named volume that will be mounted into the +// container. Each named volume is a libpod Volume present in the state. +type ContainerNamedVolume struct { + // Name is the name of the volume to mount in. + // Must resolve to a valid volume present in this Podman. + Name string `json:"volumeName"` + // Dest is the mount's destination + Dest string `json:"dest"` + // Options are fstab style mount options + Options []string `json:"options,omitempty"` +} + // ContainerStatus returns a string representation for users // of a container state func (t ContainerStatus) String() string { @@ -488,6 +499,22 @@ func (c *Container) StaticDir() string { return c.config.StaticDir } +// NamedVolumes returns the container's named volumes. +// The name of each is guaranteed to point to a valid libpod Volume present in +// the state. +func (c *Container) NamedVolumes() []*ContainerNamedVolume { + volumes := []*ContainerNamedVolume{} + for _, vol := range c.config.NamedVolumes { + newVol := new(ContainerNamedVolume) + newVol.Name = vol.Name + newVol.Dest = vol.Dest + newVol.Options = vol.Options + volumes = append(volumes, newVol) + } + + return volumes +} + // Privileged returns whether the container is privileged func (c *Container) Privileged() bool { return c.config.Privileged diff --git a/libpod/container_internal.go b/libpod/container_internal.go index daa32007a..22df36c11 100644 --- a/libpod/container_internal.go +++ b/libpod/container_internal.go @@ -1403,22 +1403,6 @@ func getExcludedCGroups() (excludes []string) { return } -// namedVolumes returns named volumes for the container -func (c *Container) namedVolumes() ([]string, error) { - var volumes []string - for _, vol := range c.config.Spec.Mounts { - if strings.HasPrefix(vol.Source, c.runtime.config.VolumePath) { - volume := strings.TrimPrefix(vol.Source, c.runtime.config.VolumePath+"/") - split := strings.Split(volume, "/") - volume = split[0] - if _, err := c.runtime.state.Volume(volume); err == nil { - volumes = append(volumes, volume) - } - } - } - return volumes, nil -} - // this should be from chrootarchive. func (c *Container) copyWithTarFromImage(src, dest string) error { mountpoint, err := c.mount() diff --git a/libpod/container_internal_linux.go b/libpod/container_internal_linux.go index 504d6c135..4d6bf61a3 100644 --- a/libpod/container_internal_linux.go +++ b/libpod/container_internal_linux.go @@ -195,6 +195,7 @@ func (c *Container) generateSpec(ctx context.Context) (*spec.Spec, error) { 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 i := range g.Config.Mounts { @@ -218,6 +219,23 @@ func (c *Container) generateSpec(ctx context.Context) (*spec.Spec, error) { g.SetProcessSelinuxLabel(c.ProcessLabel()) g.SetLinuxMountLabel(c.MountLabel()) + + // Add named volumes + for _, namedVol := range c.config.NamedVolumes { + volume, err := c.runtime.GetVolume(namedVol.Name) + if err != nil { + return nil, errors.Wrapf(err, "error retrieving volume %s to add to container %s", namedVol.Name, c.ID()) + } + mountPoint := volume.MountPoint() + volMount := spec.Mount{ + Type: "bind", + Source: mountPoint, + Destination: namedVol.Dest, + Options: namedVol.Options, + } + g.AddMount(volMount) + } + // Add bind mounts to container for dstPath, srcPath := range c.state.BindMounts { newMount := spec.Mount{ diff --git a/libpod/image/image.go b/libpod/image/image.go index 4862bf1d6..cc056b816 100644 --- a/libpod/image/image.go +++ b/libpod/image/image.go @@ -68,6 +68,16 @@ type Runtime struct { EventsLogFilePath string } +// InfoImage keep information of Image along with all associated layers +type InfoImage struct { + // ID of image + ID string + // Tags of image + Tags []string + // Layers stores all layers of image. + Layers []LayerInfo +} + // ErrRepoTagNotFound is the error returned when the image id given doesn't match a rep tag in store var ErrRepoTagNotFound = errors.New("unable to match user input to any specific repotag") @@ -1277,3 +1287,21 @@ func GetLayersMapWithImageInfo(imageruntime *Runtime) (map[string]*LayerInfo, er } return layerInfoMap, nil } + +// BuildImageHierarchyMap stores hierarchy of images such that all parent layers using which image is built are stored in imageInfo +// Layers are added such that (Start)RootLayer->...intermediate Parent Layer(s)-> TopLayer(End) +func BuildImageHierarchyMap(imageInfo *InfoImage, layerMap map[string]*LayerInfo, layerID string) error { + if layerID == "" { + return nil + } + ll, ok := layerMap[layerID] + if !ok { + return fmt.Errorf("lookup error: layerid %s not found", layerID) + } + if err := BuildImageHierarchyMap(imageInfo, layerMap, ll.ParentID); err != nil { + return err + } + + imageInfo.Layers = append(imageInfo.Layers, *ll) + return nil +} diff --git a/libpod/image/search.go b/libpod/image/search.go index 2c66ce284..03a67636b 100644 --- a/libpod/image/search.go +++ b/libpod/image/search.go @@ -2,7 +2,6 @@ package image import ( "context" - "reflect" "strconv" "strings" "sync" @@ -10,7 +9,6 @@ import ( "github.com/containers/image/docker" "github.com/containers/image/types" sysreg "github.com/containers/libpod/pkg/registries" - "github.com/fatih/camelcase" "github.com/pkg/errors" "github.com/sirupsen/logrus" "golang.org/x/sync/semaphore" @@ -63,24 +61,6 @@ type SearchFilter struct { IsOfficial types.OptionalBool } -func splitCamelCase(src string) string { - entries := camelcase.Split(src) - return strings.Join(entries, " ") -} - -// HeaderMap returns the headers of a SearchResult. -func (s *SearchResult) HeaderMap() map[string]string { - v := reflect.Indirect(reflect.ValueOf(s)) - values := make(map[string]string, v.NumField()) - - for i := 0; i < v.NumField(); i++ { - key := v.Type().Field(i).Name - value := key - values[key] = strings.ToUpper(splitCamelCase(value)) - } - return values -} - // SearchImages searches images based on term and the specified SearchOptions // in all registries. func SearchImages(term string, options SearchOptions) ([]SearchResult, error) { diff --git a/libpod/in_memory_state.go b/libpod/in_memory_state.go index ab4fc8ba7..2669206df 100644 --- a/libpod/in_memory_state.go +++ b/libpod/in_memory_state.go @@ -249,11 +249,8 @@ func (s *InMemoryState) AddContainer(ctr *Container) error { } // Add container to volume dependencies - for _, vol := range ctr.config.Spec.Mounts { - if strings.Contains(vol.Source, ctr.runtime.config.VolumePath) { - volName := strings.Split(vol.Source[len(ctr.runtime.config.VolumePath)+1:], "/")[0] - s.addCtrToVolDependsMap(ctr.ID(), volName) - } + for _, vol := range ctr.config.NamedVolumes { + s.addCtrToVolDependsMap(ctr.ID(), vol.Name) } return nil @@ -306,12 +303,9 @@ func (s *InMemoryState) RemoveContainer(ctr *Container) error { s.removeCtrFromDependsMap(ctr.ID(), depCtr) } - // Remove container from volume dependencies - for _, vol := range ctr.config.Spec.Mounts { - if strings.Contains(vol.Source, ctr.runtime.config.VolumePath) { - volName := strings.Split(vol.Source[len(ctr.runtime.config.VolumePath)+1:], "/")[0] - s.removeCtrFromVolDependsMap(ctr.ID(), volName) - } + // Remove this container from volume dependencies + for _, vol := range ctr.config.NamedVolumes { + s.removeCtrFromVolDependsMap(ctr.ID(), vol.Name) } return nil @@ -492,22 +486,6 @@ func (s *InMemoryState) RemoveVolume(volume *Volume) error { return nil } -// RemoveVolCtrDep updates the container dependencies of the volume -func (s *InMemoryState) RemoveVolCtrDep(volume *Volume, ctrID string) error { - if !volume.valid { - return errors.Wrapf(ErrVolumeRemoved, "volume with name %s is not valid", volume.Name()) - } - - if _, ok := s.volumes[volume.Name()]; !ok { - return errors.Wrapf(ErrNoSuchVolume, "volume with name %s doesn't exists in state", volume.Name()) - } - - // Remove container that is using this volume - s.removeCtrFromVolDependsMap(ctrID, volume.Name()) - - return nil -} - // VolumeInUse checks if the given volume is being used by at least one container func (s *InMemoryState) VolumeInUse(volume *Volume) ([]string, error) { if !volume.valid { diff --git a/libpod/options.go b/libpod/options.go index 24f126e66..9326e54e4 100644 --- a/libpod/options.go +++ b/libpod/options.go @@ -13,7 +13,6 @@ import ( "github.com/containers/storage" "github.com/containers/storage/pkg/idtools" "github.com/cri-o/ocicni/pkg/ocicni" - spec "github.com/opencontainers/runtime-spec/specs-go" "github.com/pkg/errors" ) @@ -1111,24 +1110,6 @@ func WithUserVolumes(volumes []string) CtrCreateOption { } } -// WithLocalVolumes sets the built-in volumes of the container retrieved -// from a container passed in to the --volumes-from flag. -// This stores the built-in volume information in the Config so we can -// add them when creating the container. -func WithLocalVolumes(volumes []spec.Mount) CtrCreateOption { - return func(ctr *Container) error { - if ctr.valid { - return ErrCtrFinalized - } - - if volumes != nil { - ctr.config.LocalVolumes = append(ctr.config.LocalVolumes, volumes...) - } - - return nil - } -} - // WithEntrypoint sets the entrypoint of the container. // This is not used to change the container's spec, but will instead be used // during commit to populate the entrypoint of the new image. @@ -1255,6 +1236,35 @@ func withIsInfra() CtrCreateOption { } } +// WithNamedVolumes adds the given named volumes to the container. +func WithNamedVolumes(volumes []*ContainerNamedVolume) CtrCreateOption { + return func(ctr *Container) error { + if ctr.valid { + return ErrCtrFinalized + } + + destinations := make(map[string]bool) + + for _, vol := range volumes { + // Don't check if they already exist. + // If they don't we will automatically create them. + + if _, ok := destinations[vol.Dest]; ok { + return errors.Wrapf(ErrInvalidArg, "two volumes found with destination %s", vol.Dest) + } + destinations[vol.Dest] = true + + ctr.config.NamedVolumes = append(ctr.config.NamedVolumes, &ContainerNamedVolume{ + Name: vol.Name, + Dest: vol.Dest, + Options: vol.Options, + }) + } + + return nil + } +} + // Volume Creation Options // WithVolumeName sets the name of the volume. @@ -1274,68 +1284,72 @@ func WithVolumeName(name string) VolumeCreateOption { } } -// WithVolumeUID sets the uid of the owner. -func WithVolumeUID(uid int) VolumeCreateOption { +// WithVolumeLabels sets the labels of the volume. +func WithVolumeLabels(labels map[string]string) VolumeCreateOption { return func(volume *Volume) error { if volume.valid { return ErrVolumeFinalized } - volume.config.UID = uid + + volume.config.Labels = make(map[string]string) + for key, value := range labels { + volume.config.Labels[key] = value + } + return nil } } -// WithVolumeGID sets the gid of the owner. -func WithVolumeGID(gid int) VolumeCreateOption { +// WithVolumeDriver sets the driver of the volume. +func WithVolumeDriver(driver string) VolumeCreateOption { return func(volume *Volume) error { if volume.valid { return ErrVolumeFinalized } - volume.config.GID = gid + + volume.config.Driver = driver + return nil } } -// WithVolumeLabels sets the labels of the volume. -func WithVolumeLabels(labels map[string]string) VolumeCreateOption { +// WithVolumeOptions sets the options of the volume. +func WithVolumeOptions(options map[string]string) VolumeCreateOption { return func(volume *Volume) error { if volume.valid { return ErrVolumeFinalized } - volume.config.Labels = make(map[string]string) - for key, value := range labels { - volume.config.Labels[key] = value + volume.config.Options = make(map[string]string) + for key, value := range options { + volume.config.Options[key] = value } return nil } } -// WithVolumeDriver sets the driver of the volume. -func WithVolumeDriver(driver string) VolumeCreateOption { +// WithVolumeUID sets the UID that the volume will be created as. +func WithVolumeUID(uid int) VolumeCreateOption { return func(volume *Volume) error { if volume.valid { return ErrVolumeFinalized } - volume.config.Driver = driver + volume.config.UID = uid return nil } } -// WithVolumeOptions sets the options of the volume. -func WithVolumeOptions(options map[string]string) VolumeCreateOption { +// WithVolumeGID sets the GID that the volume will be created as. +func WithVolumeGID(gid int) VolumeCreateOption { return func(volume *Volume) error { if volume.valid { return ErrVolumeFinalized } - volume.config.Options = make(map[string]string) - for key, value := range options { - volume.config.Options[key] = value - } + volume.config.GID = gid return nil } diff --git a/libpod/runtime.go b/libpod/runtime.go index 6e54de558..4dd2707e8 100644 --- a/libpod/runtime.go +++ b/libpod/runtime.go @@ -4,7 +4,6 @@ import ( "fmt" "io/ioutil" "os" - "os/exec" "path/filepath" "sync" "syscall" @@ -742,7 +741,7 @@ func makeRuntime(runtime *Runtime) (err error) { // Set up containers/storage var store storage.Store - if rootless.SkipStorageSetup() { + if os.Geteuid() != 0 { logrus.Debug("Not configuring container store") } else { store, err = storage.GetStore(runtime.config.StorageConfig) @@ -926,16 +925,8 @@ func makeRuntime(runtime *Runtime) (err error) { // If we need to refresh the state, do it now - things are guaranteed to // be set up by now. if doRefresh { - if os.Geteuid() != 0 { - aliveLock.Unlock() - locked = false - if err2 := runtime.refreshRootless(); err2 != nil { - return err2 - } - } else { - if err2 := runtime.refresh(runtimeAliveFile); err2 != nil { - return err2 - } + if err2 := runtime.refresh(runtimeAliveFile); err2 != nil { + return err2 } } @@ -1009,21 +1000,6 @@ func (r *Runtime) Shutdown(force bool) error { return lastError } -// Reconfigures the runtime after a reboot for a rootless process -func (r *Runtime) refreshRootless() error { - // Take advantage of a command that requires a new userns - // so that we are running as the root user and able to use refresh() - cmd := exec.Command(os.Args[0], "info") - - if output, err := cmd.CombinedOutput(); err != nil { - if _, ok := err.(*exec.ExitError); !ok { - return errors.Wrapf(err, "Error waiting for info while refreshing state: %s", os.Args[0]) - } - return errors.Wrapf(err, "Error running %s info while refreshing state: %s", os.Args[0], output) - } - return nil -} - // Reconfigures the runtime after a reboot // Refreshes the state, recreating temporary files // Does not check validity as the runtime is not valid until after this has run diff --git a/libpod/runtime_ctr.go b/libpod/runtime_ctr.go index 506aee477..800b42851 100644 --- a/libpod/runtime_ctr.go +++ b/libpod/runtime_ctr.go @@ -2,11 +2,9 @@ package libpod import ( "context" - "io/ioutil" "os" "path" "path/filepath" - "strconv" "strings" "time" @@ -101,9 +99,6 @@ func (r *Runtime) newContainer(ctx context.Context, rSpec *spec.Spec, options .. ctr.state.State = ContainerStateConfigured ctr.runtime = r - ctr.valid = true - ctr.state.State = ContainerStateConfigured - var pod *Pod if ctr.config.Pod != "" { // Get the pod from state @@ -175,24 +170,29 @@ func (r *Runtime) newContainer(ctx context.Context, rSpec *spec.Spec, options .. ctr.config.ConmonPidFile = filepath.Join(ctr.config.StaticDir, "conmon.pid") } - // Go through the volume mounts and check for named volumes - // If the named volme already exists continue, otherwise create - // the storage for the named volume. - for i, vol := range ctr.config.Spec.Mounts { - if vol.Source[0] != '/' && isNamedVolume(vol.Source) { - volInfo, err := r.state.Volume(vol.Source) - if err != nil { - newVol, err := r.newVolume(ctx, WithVolumeName(vol.Source), withSetCtrSpecific(), WithVolumeUID(ctr.RootUID()), WithVolumeGID(ctr.RootGID())) - if err != nil { - return nil, errors.Wrapf(err, "error creating named volume %q", vol.Source) - } - ctr.config.Spec.Mounts[i].Source = newVol.MountPoint() - if err := ctr.copyWithTarFromImage(ctr.config.Spec.Mounts[i].Destination, ctr.config.Spec.Mounts[i].Source); err != nil && !os.IsNotExist(err) { - return nil, errors.Wrapf(err, "failed to copy content into new volume mount %q", vol.Source) - } - continue - } - ctr.config.Spec.Mounts[i].Source = volInfo.MountPoint() + // Go through named volumes and add them. + // If they don't exist they will be created using basic options. + for _, vol := range ctr.config.NamedVolumes { + // Check if it exists already + _, err := r.state.Volume(vol.Name) + if err == nil { + // The volume exists, we're good + continue + } else if errors.Cause(err) != ErrNoSuchVolume { + return nil, errors.Wrapf(err, "error retrieving named volume %s for new container", vol.Name) + } + + logrus.Debugf("Creating new volume %s for container", vol.Name) + + // The volume does not exist, so we need to create it. + newVol, err := r.newVolume(ctx, WithVolumeName(vol.Name), withSetCtrSpecific(), + WithVolumeUID(ctr.RootUID()), WithVolumeGID(ctr.RootGID())) + if err != nil { + return nil, errors.Wrapf(err, "error creating named volume %q", vol.Name) + } + + if err := ctr.copyWithTarFromImage(vol.Dest, newVol.MountPoint()); err != nil && !os.IsNotExist(err) { + return nil, errors.Wrapf(err, "Failed to copy content into new volume mount %q", vol.Name) } } @@ -346,13 +346,6 @@ func (r *Runtime) removeContainer(ctx context.Context, c *Container, force bool, return errors.Wrapf(ErrCtrExists, "container %s has dependent containers which must be removed before it: %s", c.ID(), depsStr) } - var volumes []string - if removeVolume { - volumes, err = c.namedVolumes() - if err != nil { - logrus.Errorf("unable to retrieve builtin volumes for container %v: %v", c.ID(), err) - } - } var cleanupErr error // Remove the container from the state if c.config.Pod != "" { @@ -417,8 +410,12 @@ func (r *Runtime) removeContainer(ctx context.Context, c *Container, force bool, } } - for _, v := range volumes { - if volume, err := runtime.state.Volume(v); err == nil { + if !removeVolume { + return cleanupErr + } + + for _, v := range c.config.NamedVolumes { + if volume, err := runtime.state.Volume(v.Name); err == nil { if !volume.IsCtrSpecific() { continue } @@ -550,51 +547,12 @@ func (r *Runtime) GetLatestContainer() (*Container, error) { return ctrs[lastCreatedIndex], nil } -// Check if volName is a named volume and not one of the default mounts we add to containers -func isNamedVolume(volName string) bool { - if volName != "proc" && volName != "tmpfs" && volName != "devpts" && volName != "shm" && volName != "mqueue" && volName != "sysfs" && volName != "cgroup" { - return true - } - return false -} - // Export is the libpod portion of exporting a container to a tar file func (r *Runtime) Export(name string, path string) error { ctr, err := r.LookupContainer(name) if err != nil { return err } - if os.Geteuid() != 0 { - state, err := ctr.State() - if err != nil { - return errors.Wrapf(err, "cannot read container state %q", ctr.ID()) - } - if state == ContainerStateRunning || state == ContainerStatePaused { - data, err := ioutil.ReadFile(ctr.Config().ConmonPidFile) - if err != nil { - return errors.Wrapf(err, "cannot read conmon PID file %q", ctr.Config().ConmonPidFile) - } - conmonPid, err := strconv.Atoi(string(data)) - if err != nil { - return errors.Wrapf(err, "cannot parse PID %q", data) - } - became, ret, err := rootless.JoinDirectUserAndMountNS(uint(conmonPid)) - if err != nil { - return err - } - if became { - os.Exit(ret) - } - } else { - became, ret, err := rootless.BecomeRootInUserNS() - if err != nil { - return err - } - if became { - os.Exit(ret) - } - } - } return ctr.Export(path) } diff --git a/libpod/runtime_volume_linux.go b/libpod/runtime_volume_linux.go index db5c29242..40040fc52 100644 --- a/libpod/runtime_volume_linux.go +++ b/libpod/runtime_volume_linux.go @@ -98,12 +98,26 @@ func (r *Runtime) removeVolume(ctx context.Context, v *Volume, force bool) error if !force { return errors.Wrapf(ErrVolumeBeingUsed, "volume %s is being used by the following container(s): %s", v.Name(), depsStr) } - // If using force, log the warning that the volume is being used by at least one container - logrus.Warnf("volume %s is being used by the following container(s): %s", v.Name(), depsStr) - // Remove the container dependencies so we can go ahead and delete the volume + + // We need to remove all containers using the volume for _, dep := range deps { - if err := r.state.RemoveVolCtrDep(v, dep); err != nil { - return errors.Wrapf(err, "unable to remove container dependency %q from volume %q while trying to delete volume by force", dep, v.Name()) + ctr, err := r.state.Container(dep) + if err != nil { + // If the container's removed, no point in + // erroring. + if errors.Cause(err) == ErrNoSuchCtr || errors.Cause(err) == ErrCtrRemoved { + continue + } + + return errors.Wrapf(err, "error removing container %s that depends on volume %s", dep, v.Name()) + } + + // TODO: do we want to set force here when removing + // containers? + // I'm inclined to say no, in case someone accidentally + // wipes a container they're using... + if err := r.removeContainer(ctx, ctr, false, false); err != nil { + return errors.Wrapf(err, "error removing container %s that depends on volume %s", ctr.ID(), v.Name()) } } } diff --git a/libpod/state.go b/libpod/state.go index 4296fc3cd..d0ad1a1f8 100644 --- a/libpod/state.go +++ b/libpod/state.go @@ -192,10 +192,6 @@ type State interface { // AddVolume adds the specified volume to state. The volume's name // must be unique within the list of existing volumes AddVolume(volume *Volume) error - // RemoveVolCtrDep updates the list of container dependencies that the - // volume has. It either deletes the dependent container ID from - // the sub-bucket - RemoveVolCtrDep(volume *Volume, ctrID string) error // RemoveVolume removes the specified volume. // Only volumes that have no container dependencies can be removed RemoveVolume(volume *Volume) error diff --git a/pkg/adapter/images.go b/pkg/adapter/images.go new file mode 100644 index 000000000..c8ea1cdea --- /dev/null +++ b/pkg/adapter/images.go @@ -0,0 +1,34 @@ +// +build !remoteclient + +package adapter + +import ( + "github.com/containers/libpod/cmd/podman/cliconfig" + "github.com/containers/libpod/libpod/image" + "github.com/pkg/errors" +) + +// Tree ... +func (r *LocalRuntime) Tree(c *cliconfig.TreeValues) (*image.InfoImage, map[string]*image.LayerInfo, *ContainerImage, error) { + img, err := r.NewImageFromLocal(c.InputArgs[0]) + if err != nil { + return nil, nil, nil, err + } + + // Fetch map of image-layers, which is used for printing output. + layerInfoMap, err := image.GetLayersMapWithImageInfo(r.Runtime.ImageRuntime()) + if err != nil { + return nil, nil, nil, errors.Wrapf(err, "error while retrieving layers of image %q", img.InputName) + } + + // Create an imageInfo and fill the image and layer info + imageInfo := &image.InfoImage{ + ID: img.ID(), + Tags: img.Names(), + } + + if err := image.BuildImageHierarchyMap(imageInfo, layerInfoMap, img.TopLayer()); err != nil { + return nil, nil, nil, err + } + return imageInfo, layerInfoMap, img, nil +} diff --git a/pkg/adapter/images_remote.go b/pkg/adapter/images_remote.go index e7b38dccc..722058d4a 100644 --- a/pkg/adapter/images_remote.go +++ b/pkg/adapter/images_remote.go @@ -6,8 +6,11 @@ import ( "context" "encoding/json" + "github.com/containers/libpod/cmd/podman/cliconfig" iopodman "github.com/containers/libpod/cmd/podman/varlink" + "github.com/containers/libpod/libpod/image" "github.com/containers/libpod/pkg/inspect" + "github.com/pkg/errors" ) // Inspect returns returns an ImageData struct from over a varlink connection @@ -22,3 +25,32 @@ func (i *ContainerImage) Inspect(ctx context.Context) (*inspect.ImageData, error } return &data, nil } + +// Tree ... +func (r *LocalRuntime) Tree(c *cliconfig.TreeValues) (*image.InfoImage, map[string]*image.LayerInfo, *ContainerImage, error) { + layerInfoMap := make(map[string]*image.LayerInfo) + imageInfo := &image.InfoImage{} + + img, err := r.NewImageFromLocal(c.InputArgs[0]) + if err != nil { + return nil, nil, nil, err + } + + reply, err := iopodman.GetLayersMapWithImageInfo().Call(r.Conn) + if err != nil { + return nil, nil, nil, errors.Wrap(err, "failed to obtain image layers") + } + if err := json.Unmarshal([]byte(reply), &layerInfoMap); err != nil { + return nil, nil, nil, errors.Wrap(err, "failed to unmarshal image layers") + } + + reply, err = iopodman.BuildImageHierarchyMap().Call(r.Conn, c.InputArgs[0]) + if err != nil { + return nil, nil, nil, errors.Wrap(err, "failed to get build image map") + } + if err := json.Unmarshal([]byte(reply), imageInfo); err != nil { + return nil, nil, nil, errors.Wrap(err, "failed to unmarshal build image map") + } + + return imageInfo, layerInfoMap, img, nil +} diff --git a/pkg/adapter/runtime.go b/pkg/adapter/runtime.go index 6a68a3aea..182a04044 100644 --- a/pkg/adapter/runtime.go +++ b/pkg/adapter/runtime.go @@ -8,7 +8,6 @@ import ( "io" "io/ioutil" "os" - "strconv" "text/template" "github.com/containers/buildah" @@ -23,6 +22,7 @@ import ( "github.com/containers/libpod/libpod/events" "github.com/containers/libpod/libpod/image" "github.com/containers/libpod/pkg/rootless" + "github.com/containers/storage/pkg/archive" "github.com/pkg/errors" ) @@ -123,38 +123,6 @@ func (r *LocalRuntime) Export(name string, path string) error { if err != nil { return errors.Wrapf(err, "error looking up container %q", name) } - if os.Geteuid() != 0 { - state, err := ctr.State() - if err != nil { - return errors.Wrapf(err, "cannot read container state %q", ctr.ID()) - } - if state == libpod.ContainerStateRunning || state == libpod.ContainerStatePaused { - data, err := ioutil.ReadFile(ctr.Config().ConmonPidFile) - if err != nil { - return errors.Wrapf(err, "cannot read conmon PID file %q", ctr.Config().ConmonPidFile) - } - conmonPid, err := strconv.Atoi(string(data)) - if err != nil { - return errors.Wrapf(err, "cannot parse PID %q", data) - } - became, ret, err := rootless.JoinDirectUserAndMountNS(uint(conmonPid)) - if err != nil { - return err - } - if became { - os.Exit(ret) - } - } else { - became, ret, err := rootless.BecomeRootInUserNS() - if err != nil { - return err - } - if became { - os.Exit(ret) - } - } - } - return ctr.Export(path) } @@ -342,46 +310,6 @@ func (r *LocalRuntime) HealthCheck(c *cliconfig.HealthCheckValues) (libpod.Healt return r.Runtime.HealthCheck(c.InputArgs[0]) } -// JoinOrCreateRootlessPod joins the specified pod if it is running or it creates a new user namespace -// if the pod is stopped -func (r *LocalRuntime) JoinOrCreateRootlessPod(pod *Pod) (bool, int, error) { - if os.Geteuid() == 0 { - return false, 0, nil - } - opts := rootless.Opts{ - Argument: pod.ID(), - } - - inspect, err := pod.Inspect() - if err != nil { - return false, 0, err - } - for _, ctr := range inspect.Containers { - prevCtr, err := r.LookupContainer(ctr.ID) - if err != nil { - return false, -1, err - } - s, err := prevCtr.State() - if err != nil { - return false, -1, err - } - if s != libpod.ContainerStateRunning && s != libpod.ContainerStatePaused { - continue - } - data, err := ioutil.ReadFile(prevCtr.Config().ConmonPidFile) - if err != nil { - return false, -1, errors.Wrapf(err, "cannot read conmon PID file %q", prevCtr.Config().ConmonPidFile) - } - conmonPid, err := strconv.Atoi(string(data)) - if err != nil { - return false, -1, errors.Wrapf(err, "cannot parse PID %q", data) - } - return rootless.JoinDirectUserAndMountNSWithOpts(uint(conmonPid), &opts) - } - - return rootless.BecomeRootInUserNSWithOpts(&opts) -} - // Events is a wrapper to libpod to obtain libpod/podman events func (r *LocalRuntime) Events(c *cliconfig.EventValues) error { var ( @@ -430,3 +358,8 @@ func (r *LocalRuntime) Events(c *cliconfig.EventValues) error { } return nil } + +// Diff shows the difference in two objects +func (r *LocalRuntime) Diff(c *cliconfig.DiffValues, to string) ([]archive.Change, error) { + return r.Runtime.GetDiff("", to) +} diff --git a/pkg/adapter/runtime_remote.go b/pkg/adapter/runtime_remote.go index dcc2d5aa6..807a9ad8f 100644 --- a/pkg/adapter/runtime_remote.go +++ b/pkg/adapter/runtime_remote.go @@ -82,6 +82,7 @@ type remoteImage struct { Digest digest.Digest isParent bool Runtime *LocalRuntime + TopLayer string } // Container ... @@ -147,6 +148,7 @@ func imageInListToContainerImage(i iopodman.Image, name string, runtime *LocalRu Names: i.RepoTags, isParent: i.IsParent, Runtime: runtime, + TopLayer: i.TopLayer, } return &ContainerImage{ri}, nil } @@ -280,6 +282,11 @@ func (ci *ContainerImage) Dangling() bool { return len(ci.Names()) == 0 } +// TopLayer returns an images top layer as a string +func (ci *ContainerImage) TopLayer() string { + return ci.remoteImage.TopLayer +} + // TagImage ... func (ci *ContainerImage) TagImage(tag string) error { _, err := iopodman.TagImage().Call(ci.Runtime.Conn, ci.ID(), tag) @@ -755,13 +762,6 @@ func (r *LocalRuntime) HealthCheck(c *cliconfig.HealthCheckValues) (libpod.Healt return -1, libpod.ErrNotImplemented } -// JoinOrCreateRootlessPod joins the specified pod if it is running or it creates a new user namespace -// if the pod is stopped -func (r *LocalRuntime) JoinOrCreateRootlessPod(pod *Pod) (bool, int, error) { - // Nothing to do in the remote case - return true, 0, nil -} - // Events monitors libpod/podman events over a varlink connection func (r *LocalRuntime) Events(c *cliconfig.EventValues) error { var more uint64 @@ -831,3 +831,30 @@ func (r *LocalRuntime) Events(c *cliconfig.EventValues) error { } return nil } + +// Diff ... +func (r *LocalRuntime) Diff(c *cliconfig.DiffValues, to string) ([]archive.Change, error) { + var changes []archive.Change + reply, err := iopodman.Diff().Call(r.Conn, to) + if err != nil { + return nil, err + } + for _, change := range reply { + changes = append(changes, archive.Change{Path: change.Path, Kind: stringToChangeType(change.ChangeType)}) + } + return changes, nil +} + +func stringToChangeType(change string) archive.ChangeType { + switch change { + case "A": + return archive.ChangeAdd + case "D": + return archive.ChangeDelete + default: + logrus.Errorf("'%s' is unknown archive type", change) + fallthrough + case "C": + return archive.ChangeModify + } +} diff --git a/pkg/annotations/annotations.go b/pkg/annotations/annotations.go index 008cca7ee..fe2591a0c 100644 --- a/pkg/annotations/annotations.go +++ b/pkg/annotations/annotations.go @@ -19,9 +19,18 @@ const ( // HostName is the container host name annotation HostName = "io.kubernetes.cri-o.HostName" + // CgroupParent is the sandbox cgroup parent + CgroupParent = "io.kubernetes.cri-o.CgroupParent" + // IP is the container ipv4 or ipv6 address IP = "io.kubernetes.cri-o.IP" + // NamespaceOptions store the options for namespaces + NamespaceOptions = "io.kubernetes.cri-o.NamespaceOptions" + + // SeccompProfilePath is the node seccomp profile path + SeccompProfilePath = "io.kubernetes.cri-o.SeccompProfilePath" + // Image is the container image ID annotation Image = "io.kubernetes.cri-o.Image" @@ -34,6 +43,9 @@ const ( // KubeName is the kubernetes name annotation KubeName = "io.kubernetes.cri-o.KubeName" + // PortMappings holds the port mappings for the sandbox + PortMappings = "io.kubernetes.cri-o.PortMappings" + // Labels are the kubernetes labels annotation Labels = "io.kubernetes.cri-o.Labels" @@ -46,6 +58,9 @@ const ( // Name is the pod name annotation Name = "io.kubernetes.cri-o.Name" + // Namespace is the pod namespace annotation + Namespace = "io.kubernetes.cri-o.Namespace" + // PrivilegedRuntime is the annotation for the privileged runtime path PrivilegedRuntime = "io.kubernetes.cri-o.PrivilegedRuntime" @@ -67,8 +82,8 @@ const ( // MountPoint is the mount point of the container rootfs MountPoint = "io.kubernetes.cri-o.MountPoint" - // TrustedSandbox is the annotation for trusted sandboxes - TrustedSandbox = "io.kubernetes.cri-o.TrustedSandbox" + // RuntimeHandler is the annotation for runtime handler + RuntimeHandler = "io.kubernetes.cri-o.RuntimeHandler" // TTY is the terminal path annotation TTY = "io.kubernetes.cri-o.TTY" @@ -79,8 +94,14 @@ const ( // StdinOnce is the stdin_once annotation StdinOnce = "io.kubernetes.cri-o.StdinOnce" - // Volumes is the volumes annotation + // Volumes is the volumes annotatoin Volumes = "io.kubernetes.cri-o.Volumes" + + // HostNetwork indicates whether the host network namespace is used or not + HostNetwork = "io.kubernetes.cri-o.HostNetwork" + + // CNIResult is the JSON string representation of the Result from CNI + CNIResult = "io.kubernetes.cri-o.CNIResult" ) // ContainerType values diff --git a/pkg/registrar/registrar_test.go b/pkg/registrar/registrar_test.go index 0c1ef312a..50af95915 100644 --- a/pkg/registrar/registrar_test.go +++ b/pkg/registrar/registrar_test.go @@ -1,119 +1,213 @@ -package registrar +package registrar_test import ( - "reflect" "testing" -) - -func TestReserve(t *testing.T) { - r := NewRegistrar() - - obj := "test1" - if err := r.Reserve("test", obj); err != nil { - t.Fatal(err) - } - - if err := r.Reserve("test", obj); err != nil { - t.Fatal(err) - } - - obj2 := "test2" - err := r.Reserve("test", obj2) - if err == nil { - t.Fatalf("expected error when reserving an already reserved name to another object") - } - if err != ErrNameReserved { - t.Fatal("expected `ErrNameReserved` error when attempting to reserve an already reserved name") - } -} - -func TestRelease(t *testing.T) { - r := NewRegistrar() - obj := "testing" - - if err := r.Reserve("test", obj); err != nil { - t.Fatal(err) - } - r.Release("test") - r.Release("test") // Ensure there is no panic here - if err := r.Reserve("test", obj); err != nil { - t.Fatal(err) - } -} - -func TestGetNames(t *testing.T) { - r := NewRegistrar() - obj := "testing" - names := []string{"test1", "test2"} - - for _, name := range names { - if err := r.Reserve(name, obj); err != nil { - t.Fatal(err) - } - } - r.Reserve("test3", "other") - - names2, err := r.GetNames(obj) - if err != nil { - t.Fatal(err) - } - - if !reflect.DeepEqual(names, names2) { - t.Fatalf("Exepected: %v, Got: %v", names, names2) - } -} + "github.com/containers/libpod/pkg/registrar" + . "github.com/containers/libpod/test/framework" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) -func TestDelete(t *testing.T) { - r := NewRegistrar() - obj := "testing" - names := []string{"test1", "test2"} - for _, name := range names { - if err := r.Reserve(name, obj); err != nil { - t.Fatal(err) - } - } - - r.Reserve("test3", "other") - r.Delete(obj) - - _, err := r.GetNames(obj) - if err == nil { - t.Fatal("expected error getting names for deleted key") - } - - if err != ErrNoSuchKey { - t.Fatal("expected `ErrNoSuchKey`") - } +// TestRegistrar runs the created specs +func TestRegistrar(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "Registrar") } -func TestGet(t *testing.T) { - r := NewRegistrar() - obj := "testing" - name := "test" - - _, err := r.Get(name) - if err == nil { - t.Fatal("expected error when key does not exist") - } - if err != ErrNameNotReserved { - t.Fatal(err) - } - - if err := r.Reserve(name, obj); err != nil { - t.Fatal(err) - } - - if _, err = r.Get(name); err != nil { - t.Fatal(err) - } - - r.Delete(obj) - _, err = r.Get(name) - if err == nil { - t.Fatal("expected error when key does not exist") - } - if err != ErrNameNotReserved { - t.Fatal(err) - } -} +// nolint: gochecknoglobals +var t *TestFramework + +var _ = BeforeSuite(func() { + t = NewTestFramework(NilFunc, NilFunc) + t.Setup() +}) + +var _ = AfterSuite(func() { + t.Teardown() +}) + +// The actual test suite +var _ = t.Describe("Registrar", func() { + // Constant test data needed by some tests + const ( + testKey = "testKey" + testName = "testName" + anotherKey = "anotherKey" + ) + + // The system under test + var sut *registrar.Registrar + + // Prepare the system under test and register a test name and key before + // each test + BeforeEach(func() { + sut = registrar.NewRegistrar() + Expect(sut.Reserve(testName, testKey)).To(BeNil()) + }) + + t.Describe("Reserve", func() { + It("should succeed to reserve a new registrar", func() { + // Given + // When + err := sut.Reserve("name", "key") + + // Then + Expect(err).To(BeNil()) + }) + + It("should succeed to reserve a registrar twice", func() { + // Given + // When + err := sut.Reserve(testName, testKey) + + // Then + Expect(err).To(BeNil()) + }) + + It("should fail to reserve an already reserved registrar", func() { + // Given + // When + err := sut.Reserve(testName, anotherKey) + + // Then + Expect(err).NotTo(BeNil()) + Expect(err).To(Equal(registrar.ErrNameReserved)) + }) + }) + + t.Describe("Release", func() { + It("should succeed to release a registered registrar multiple times", func() { + // Given + // When + // Then + sut.Release(testName) + sut.Release(testName) + }) + + It("should succeed to release a unknown registrar multiple times", func() { + // Given + // When + // Then + sut.Release(anotherKey) + sut.Release(anotherKey) + }) + + It("should succeed to release and re-register a registrar", func() { + // Given + // When + sut.Release(testName) + err := sut.Reserve(testName, testKey) + + // Then + Expect(err).To(BeNil()) + }) + }) + + t.Describe("GetNames", func() { + It("should succeed to retrieve a single name for a registrar", func() { + // Given + // When + names, err := sut.GetNames(testKey) + + // Then + Expect(err).To(BeNil()) + Expect(len(names)).To(Equal(1)) + Expect(names[0]).To(Equal(testName)) + }) + + It("should succeed to retrieve all names for a registrar", func() { + // Given + testNames := []string{"test1", "test2"} + for _, name := range testNames { + Expect(sut.Reserve(name, anotherKey)).To(BeNil()) + } + + // When + names, err := sut.GetNames(anotherKey) + + // Then + Expect(err).To(BeNil()) + Expect(len(names)).To(Equal(2)) + Expect(names).To(Equal(testNames)) + }) + }) + + t.Describe("GetNames", func() { + It("should succeed to retrieve a single name for a registrar", func() { + // Given + // When + names, err := sut.GetNames(testKey) + + // Then + Expect(err).To(BeNil()) + Expect(len(names)).To(Equal(1)) + Expect(names[0]).To(Equal(testName)) + }) + + It("should succeed to retrieve all names for a registrar", func() { + // Given + anotherKey := "anotherKey" + testNames := []string{"test1", "test2"} + for _, name := range testNames { + Expect(sut.Reserve(name, anotherKey)).To(BeNil()) + } + + // When + names, err := sut.GetNames(anotherKey) + + // Then + Expect(err).To(BeNil()) + Expect(len(names)).To(Equal(2)) + Expect(names).To(Equal(testNames)) + }) + }) + + t.Describe("Delete", func() { + It("should succeed to delete a registrar", func() { + // Given + // When + sut.Delete(testKey) + + // Then + names, err := sut.GetNames(testKey) + Expect(len(names)).To(BeZero()) + Expect(err).To(Equal(registrar.ErrNoSuchKey)) + }) + }) + + t.Describe("Get", func() { + It("should succeed to get a key for a registrar", func() { + // Given + // When + key, err := sut.Get(testName) + + // Then + Expect(err).To(BeNil()) + Expect(key).To(Equal(testKey)) + }) + + It("should fail to get a key for a not existing registrar", func() { + // Given + // When + key, err := sut.Get("notExistingName") + + // Then + Expect(key).To(BeEmpty()) + Expect(err).To(Equal(registrar.ErrNameNotReserved)) + }) + }) + + t.Describe("GetAll", func() { + It("should succeed to get all names", func() { + // Given + // When + names := sut.GetAll() + + // Then + Expect(len(names)).To(Equal(1)) + Expect(len(names[testKey])).To(Equal(1)) + Expect(names[testKey][0]).To(Equal(testName)) + }) + }) +}) diff --git a/pkg/rootless/rootless.go b/pkg/rootless/rootless.go deleted file mode 100644 index a531e43ce..000000000 --- a/pkg/rootless/rootless.go +++ /dev/null @@ -1,9 +0,0 @@ -package rootless - -// Opts allows to customize how re-execing to a rootless process is done -type Opts struct { - // Argument overrides the arguments on the command line - // for the re-execed process. The process in the namespace - // must use rootless.Argument() to read its value. - Argument string -} diff --git a/pkg/rootless/rootless_linux.c b/pkg/rootless/rootless_linux.c index 2e2c3acac..9cb79ed4d 100644 --- a/pkg/rootless/rootless_linux.c +++ b/pkg/rootless/rootless_linux.c @@ -13,10 +13,36 @@ #include <sys/wait.h> #include <string.h> #include <stdbool.h> +#include <sys/types.h> +#include <sys/prctl.h> +#include <dirent.h> static const char *_max_user_namespaces = "/proc/sys/user/max_user_namespaces"; static const char *_unprivileged_user_namespaces = "/proc/sys/kernel/unprivileged_userns_clone"; +static int n_files; + +static void __attribute__((constructor)) init() +{ + DIR *d; + + /* Store how many FDs were open before the Go runtime kicked in. */ + d = opendir ("/proc/self/fd"); + if (d) + { + struct dirent *ent; + + for (ent = readdir (d); ent; ent = readdir (d)) + { + int fd = atoi (ent->d_name); + if (fd > n_files && fd != dirfd (d)) + n_files = fd; + } + closedir (d); + } +} + + static int syscall_setresuid (uid_t ruid, uid_t euid, uid_t suid) { @@ -133,12 +159,25 @@ reexec_userns_join (int userns, int mountns) pid = fork (); if (pid < 0) fprintf (stderr, "cannot fork: %s\n", strerror (errno)); + if (pid) - return pid; + { + /* We passed down these fds, close them. */ + int f; + for (f = 3; f < n_files; f++) + close (f); + return pid; + } setenv ("_CONTAINERS_USERNS_CONFIGURED", "init", 1); setenv ("_CONTAINERS_ROOTLESS_UID", uid, 1); + if (prctl (PR_SET_PDEATHSIG, SIGTERM, 0, 0, 0) < 0) + { + fprintf (stderr, "cannot prctl(PR_SET_PDEATHSIG): %s\n", strerror (errno)); + _exit (EXIT_FAILURE); + } + if (setns (userns, 0) < 0) { fprintf (stderr, "cannot setns: %s\n", strerror (errno)); diff --git a/pkg/rootless/rootless_linux.go b/pkg/rootless/rootless_linux.go index 0be0e08bf..1d1b1713d 100644 --- a/pkg/rootless/rootless_linux.go +++ b/pkg/rootless/rootless_linux.go @@ -46,25 +46,6 @@ func IsRootless() bool { return isRootless } -var ( - skipStorageSetup = false -) - -// SetSkipStorageSetup tells the runtime to not setup containers/storage -func SetSkipStorageSetup(v bool) { - skipStorageSetup = v -} - -// SkipStorageSetup tells if we should skip the containers/storage setup -func SkipStorageSetup() bool { - return skipStorageSetup -} - -// Argument returns the argument that was set for the rootless session. -func Argument() string { - return os.Getenv("_CONTAINERS_ROOTLESS_ARG") -} - // GetRootlessUID returns the UID of the user in the parent userNS func GetRootlessUID() int { uidEnv := os.Getenv("_CONTAINERS_ROOTLESS_UID") @@ -104,51 +85,86 @@ func tryMappingTool(tool string, pid int, hostID int, mappings []idtools.IDMap) return nil } -// JoinNS re-exec podman in a new userNS and join the user namespace of the specified -// PID. -func JoinNS(pid uint, preserveFDs int) (bool, int, error) { - if os.Geteuid() == 0 || os.Getenv("_CONTAINERS_USERNS_CONFIGURED") != "" { - return false, -1, nil +func readUserNs(path string) (string, error) { + b := make([]byte, 256) + _, err := syscall.Readlink(path, b) + if err != nil { + return "", err } + return string(b), nil +} - userNS, err := getUserNSForPid(pid) +func readUserNsFd(fd uintptr) (string, error) { + return readUserNs(fmt.Sprintf("/proc/self/fd/%d", fd)) +} + +func getParentUserNs(fd uintptr) (uintptr, error) { + const nsGetParent = 0xb702 + ret, _, errno := syscall.Syscall(syscall.SYS_IOCTL, fd, uintptr(nsGetParent), 0) + if errno != 0 { + return 0, errno + } + return (uintptr)(unsafe.Pointer(ret)), nil +} + +// getUserNSFirstChild returns an open FD for the first direct child user namespace that created the process +// Each container creates a new user namespace where the runtime runs. The current process in the container +// might have created new user namespaces that are child of the initial namespace we created. +// This function finds the initial namespace created for the container that is a child of the current namespace. +// +// current ns +// / \ +// TARGET -> a [other containers] +// / +// b +// / +// NS READ USING THE PID -> c +func getUserNSFirstChild(fd uintptr) (*os.File, error) { + currentNS, err := readUserNs("/proc/self/ns/user") if err != nil { - return false, -1, err + return nil, err } - defer userNS.Close() - pidC := C.reexec_userns_join(C.int(userNS.Fd()), -1) - if int(pidC) < 0 { - return false, -1, errors.Errorf("cannot re-exec process") + ns, err := readUserNsFd(fd) + if err != nil { + return nil, errors.Wrapf(err, "cannot read user namespace") } - if preserveFDs > 0 { - for fd := 3; fd < 3+preserveFDs; fd++ { - // These fds were passed down to the runtime. Close them - // and not interfere - os.NewFile(uintptr(fd), fmt.Sprintf("fd-%d", fd)).Close() - } + if ns == currentNS { + return nil, errors.New("process running in the same user namespace") } - ret := C.reexec_in_user_namespace_wait(pidC) - if ret < 0 { - return false, -1, errors.New("error waiting for the re-exec process") - } + for { + nextFd, err := getParentUserNs(fd) + if err != nil { + return nil, errors.Wrapf(err, "cannot get parent user namespace") + } - return true, int(ret), nil -} + ns, err = readUserNsFd(nextFd) + if err != nil { + return nil, errors.Wrapf(err, "cannot read user namespace") + } -// JoinDirectUserAndMountNS re-exec podman in a new userNS and join the user and mount -// namespace of the specified PID without looking up its parent. Useful to join directly -// the conmon process. It is a convenience function for JoinDirectUserAndMountNSWithOpts -// with a default configuration. -func JoinDirectUserAndMountNS(pid uint) (bool, int, error) { - return JoinDirectUserAndMountNSWithOpts(pid, nil) + if ns == currentNS { + syscall.Close(int(nextFd)) + + // Drop O_CLOEXEC for the fd. + _, _, errno := syscall.Syscall(syscall.SYS_FCNTL, fd, syscall.F_SETFD, 0) + if errno != 0 { + syscall.Close(int(fd)) + return nil, errno + } + + return os.NewFile(fd, "userns child"), nil + } + syscall.Close(int(fd)) + fd = nextFd + } } -// JoinDirectUserAndMountNSWithOpts re-exec podman in a new userNS and join the user and -// mount namespace of the specified PID without looking up its parent. Useful to join -// directly the conmon process. -func JoinDirectUserAndMountNSWithOpts(pid uint, opts *Opts) (bool, int, error) { +// JoinUserAndMountNS re-exec podman in a new userNS and join the user and mount +// namespace of the specified PID without looking up its parent. Useful to join directly +// the conmon process. +func JoinUserAndMountNS(pid uint) (bool, int, error) { if os.Geteuid() == 0 || os.Getenv("_CONTAINERS_USERNS_CONFIGURED") != "" { return false, -1, nil } @@ -165,39 +181,11 @@ func JoinDirectUserAndMountNSWithOpts(pid uint, opts *Opts) (bool, int, error) { } defer userNS.Close() - if opts != nil && opts.Argument != "" { - if err := os.Setenv("_CONTAINERS_ROOTLESS_ARG", opts.Argument); err != nil { - return false, -1, err - } - } - - pidC := C.reexec_userns_join(C.int(userNS.Fd()), C.int(mountNS.Fd())) - if int(pidC) < 0 { - return false, -1, errors.Errorf("cannot re-exec process") - } - - ret := C.reexec_in_user_namespace_wait(pidC) - if ret < 0 { - return false, -1, errors.New("error waiting for the re-exec process") - } - - return true, int(ret), nil -} - -// JoinNSPath re-exec podman in a new userNS and join the owner user namespace of the -// specified path. -func JoinNSPath(path string) (bool, int, error) { - if os.Geteuid() == 0 || os.Getenv("_CONTAINERS_USERNS_CONFIGURED") != "" { - return false, -1, nil - } - - userNS, err := getUserNSForPath(path) + fd, err := getUserNSFirstChild(userNS.Fd()) if err != nil { return false, -1, err } - defer userNS.Close() - - pidC := C.reexec_userns_join(C.int(userNS.Fd()), -1) + pidC := C.reexec_userns_join(C.int(fd.Fd()), C.int(mountNS.Fd())) if int(pidC) < 0 { return false, -1, errors.Errorf("cannot re-exec process") } @@ -213,16 +201,8 @@ func JoinNSPath(path string) (bool, int, error) { // BecomeRootInUserNS re-exec podman in a new userNS. It returns whether podman was re-executed // into a new user namespace and the return code from the re-executed podman process. // If podman was re-executed the caller needs to propagate the error code returned by the child -// process. It is a convenience function for BecomeRootInUserNSWithOpts with a default configuration. -func BecomeRootInUserNS() (bool, int, error) { - return BecomeRootInUserNSWithOpts(nil) -} - -// BecomeRootInUserNSWithOpts re-exec podman in a new userNS. It returns whether podman was -// re-execute into a new user namespace and the return code from the re-executed podman process. -// If podman was re-executed the caller needs to propagate the error code returned by the child // process. -func BecomeRootInUserNSWithOpts(opts *Opts) (bool, int, error) { +func BecomeRootInUserNS() (bool, int, error) { if os.Geteuid() == 0 || os.Getenv("_CONTAINERS_USERNS_CONFIGURED") != "" { if os.Getenv("_CONTAINERS_USERNS_CONFIGURED") == "init" { return false, 0, runInUser() @@ -241,12 +221,6 @@ func BecomeRootInUserNSWithOpts(opts *Opts) (bool, int, error) { defer w.Close() defer w.Write([]byte("0")) - if opts != nil && opts.Argument != "" { - if err := os.Setenv("_CONTAINERS_ROOTLESS_ARG", opts.Argument); err != nil { - return false, -1, err - } - } - pidC := C.reexec_in_user_namespace(C.int(r.Fd())) pid := int(pidC) if pid < 0 { @@ -328,112 +302,3 @@ func BecomeRootInUserNSWithOpts(opts *Opts) (bool, int, error) { return true, int(ret), nil } - -func readUserNs(path string) (string, error) { - b := make([]byte, 256) - _, err := syscall.Readlink(path, b) - if err != nil { - return "", err - } - return string(b), nil -} - -func readUserNsFd(fd uintptr) (string, error) { - return readUserNs(fmt.Sprintf("/proc/self/fd/%d", fd)) -} - -func getOwner(fd uintptr) (uintptr, error) { - const nsGetUserns = 0xb701 - ret, _, errno := syscall.Syscall(syscall.SYS_IOCTL, fd, uintptr(nsGetUserns), 0) - if errno != 0 { - return 0, errno - } - return (uintptr)(unsafe.Pointer(ret)), nil -} - -func getParentUserNs(fd uintptr) (uintptr, error) { - const nsGetParent = 0xb702 - ret, _, errno := syscall.Syscall(syscall.SYS_IOCTL, fd, uintptr(nsGetParent), 0) - if errno != 0 { - return 0, errno - } - return (uintptr)(unsafe.Pointer(ret)), nil -} - -func getUserNSForPath(path string) (*os.File, error) { - u, err := os.Open(path) - if err != nil { - return nil, errors.Wrapf(err, "cannot open %s", path) - } - defer u.Close() - fd, err := getOwner(u.Fd()) - if err != nil { - return nil, err - } - - return getUserNSFirstChild(fd) -} - -func getUserNSForPid(pid uint) (*os.File, error) { - path := fmt.Sprintf("/proc/%d/ns/user", pid) - u, err := os.Open(path) - if err != nil { - return nil, errors.Wrapf(err, "cannot open %s", path) - } - - return getUserNSFirstChild(u.Fd()) -} - -// getUserNSFirstChild returns an open FD for the first direct child user namespace that created the process -// Each container creates a new user namespace where the runtime runs. The current process in the container -// might have created new user namespaces that are child of the initial namespace we created. -// This function finds the initial namespace created for the container that is a child of the current namespace. -// -// current ns -// / \ -// TARGET -> a [other containers] -// / -// b -// / -// NS READ USING THE PID -> c -func getUserNSFirstChild(fd uintptr) (*os.File, error) { - currentNS, err := readUserNs("/proc/self/ns/user") - if err != nil { - return nil, err - } - - ns, err := readUserNsFd(fd) - if err != nil { - return nil, errors.Wrapf(err, "cannot read user namespace") - } - if ns == currentNS { - return nil, errors.New("process running in the same user namespace") - } - - for { - nextFd, err := getParentUserNs(fd) - if err != nil { - return nil, errors.Wrapf(err, "cannot get parent user namespace") - } - - ns, err = readUserNsFd(nextFd) - if err != nil { - return nil, errors.Wrapf(err, "cannot read user namespace") - } - - if ns == currentNS { - syscall.Close(int(nextFd)) - - // Drop O_CLOEXEC for the fd. - _, _, errno := syscall.Syscall(syscall.SYS_FCNTL, fd, syscall.F_SETFD, 0) - if errno != 0 { - syscall.Close(int(fd)) - return nil, errno - } - - return os.NewFile(fd, "userns child"), nil - } - syscall.Close(int(fd)) - fd = nextFd - } -} diff --git a/pkg/rootless/rootless_unsupported.go b/pkg/rootless/rootless_unsupported.go index e01d7855c..47b5dd7cc 100644 --- a/pkg/rootless/rootless_unsupported.go +++ b/pkg/rootless/rootless_unsupported.go @@ -19,54 +19,15 @@ func BecomeRootInUserNS() (bool, int, error) { return false, -1, errors.New("this function is not supported on this os") } -// BecomeRootInUserNS is a stub function that always returns false and an -// error on unsupported OS's -func BecomeRootInUserNSWithOpts(opts *Opts) (bool, int, error) { - return false, -1, errors.New("this function is not supported on this os") -} - // GetRootlessUID returns the UID of the user in the parent userNS func GetRootlessUID() int { return -1 } -// SetSkipStorageSetup tells the runtime to not setup containers/storage -func SetSkipStorageSetup(bool) { -} - -// SkipStorageSetup tells if we should skip the containers/storage setup -func SkipStorageSetup() bool { - return false -} - -// JoinNS re-exec podman in a new userNS and join the user namespace of the specified -// PID. -func JoinNS(pid uint, preserveFDs int) (bool, int, error) { - return false, -1, errors.New("this function is not supported on this os") -} - -// JoinNSPath re-exec podman in a new userNS and join the owner user namespace of the -// specified path. -func JoinNSPath(path string) (bool, int, error) { - return false, -1, errors.New("this function is not supported on this os") -} - -// JoinDirectUserAndMountNSWithOpts re-exec podman in a new userNS and join the user and -// mount namespace of the specified PID without looking up its parent. Useful to join -// directly the conmon process. -func JoinDirectUserAndMountNSWithOpts(pid uint, opts *Opts) (bool, int, error) { - return false, -1, errors.New("this function is not supported on this os") -} - -// JoinDirectUserAndMountNS re-exec podman in a new userNS and join the user and mount +// JoinUserAndMountNS re-exec podman in a new userNS and join the user and mount // namespace of the specified PID without looking up its parent. Useful to join directly -// the conmon process. It is a convenience function for JoinDirectUserAndMountNSWithOpts +// the conmon process. It is a convenience function for JoinUserAndMountNSWithOpts // with a default configuration. -func JoinDirectUserAndMountNS(pid uint) (bool, int, error) { +func JoinUserAndMountNS(pid uint) (bool, int, error) { return false, -1, errors.New("this function is not supported on this os") } - -// Argument returns the argument that was set for the rootless session. -func Argument() string { - return "" -} diff --git a/pkg/spec/createconfig.go b/pkg/spec/createconfig.go index 0a12e3dca..e71d9d3db 100644 --- a/pkg/spec/createconfig.go +++ b/pkg/spec/createconfig.go @@ -1,7 +1,6 @@ package createconfig import ( - "encoding/json" "fmt" "net" "os" @@ -12,7 +11,6 @@ import ( "github.com/containers/image/manifest" "github.com/containers/libpod/libpod" "github.com/containers/libpod/pkg/namespaces" - "github.com/containers/libpod/pkg/rootless" "github.com/containers/storage" "github.com/containers/storage/pkg/stringid" "github.com/cri-o/ocicni/pkg/ocicni" @@ -24,18 +22,16 @@ import ( "golang.org/x/sys/unix" ) -type mountType string - // Type constants const ( bps = iota iops // TypeBind is the type for mounting host dir - TypeBind mountType = "bind" + TypeBind = "bind" // TypeVolume is the type for remote storage volumes - // TypeVolume mountType = "volume" // re-enable upon use + // TypeVolume = "volume" // re-enable upon use // TypeTmpfs is the type for mounting tmpfs - TypeTmpfs mountType = "tmpfs" + TypeTmpfs = "tmpfs" ) // CreateResourceConfig represents resource elements in CreateConfig @@ -131,15 +127,15 @@ type CreateConfig struct { Mounts []spec.Mount //mounts Volumes []string //volume VolumesFrom []string - WorkDir string //workdir - LabelOpts []string //SecurityOpts - NoNewPrivs bool //SecurityOpts - ApparmorProfile string //SecurityOpts - SeccompProfilePath string //SecurityOpts + NamedVolumes []*libpod.ContainerNamedVolume // Filled in by CreateConfigToOCISpec + WorkDir string //workdir + LabelOpts []string //SecurityOpts + NoNewPrivs bool //SecurityOpts + ApparmorProfile string //SecurityOpts + SeccompProfilePath string //SecurityOpts SecurityOpts []string Rootfs string - LocalVolumes []spec.Mount //Keeps track of the built-in volumes of container used in the --volumes-from flag - Syslog bool // Whether to enable syslog on exit commands + Syslog bool // Whether to enable syslog on exit commands } func u32Ptr(i int64) *uint32 { u := uint32(i); return &u } @@ -173,9 +169,9 @@ func (c *CreateConfig) AddContainerInitBinary(path string) error { c.Command = append([]string{"/dev/init", "--"}, c.Command...) c.Mounts = append(c.Mounts, spec.Mount{ Destination: "/dev/init", - Type: "bind", + Type: TypeBind, Source: path, - Options: []string{"bind", "ro"}, + Options: []string{TypeBind, "ro"}, }) return nil } @@ -218,9 +214,9 @@ func (c *CreateConfig) initFSMounts() []spec.Mount { return mounts } -//GetVolumeMounts takes user provided input for bind mounts and creates Mount structs +// GetVolumeMounts takes user provided input for bind mounts and creates Mount structs func (c *CreateConfig) GetVolumeMounts(specMounts []spec.Mount) ([]spec.Mount, error) { - m := c.LocalVolumes + m := []spec.Mount{} for _, i := range c.Volumes { var options []string spliti := strings.Split(i, ":") @@ -256,9 +252,11 @@ func (c *CreateConfig) GetVolumeMounts(specMounts []spec.Mount) ([]spec.Mount, e mount.Source = "tmpfs" mount.Options = append(mount.Options, "tmpcopyup") } else { + // TODO: Move support for this and tmpfs into libpod + // Should tmpfs also be handled as named volumes? Wouldn't be hard // This will cause a new local Volume to be created on your system mount.Source = stringid.GenerateNonCryptoID() - mount.Options = append(mount.Options, "bind") + mount.Options = append(mount.Options, TypeBind) } m = append(m, mount) } @@ -269,13 +267,12 @@ func (c *CreateConfig) GetVolumeMounts(specMounts []spec.Mount) ([]spec.Mount, e // GetVolumesFrom reads the create-config artifact of the container to get volumes from // and adds it to c.Volumes of the current container. func (c *CreateConfig) GetVolumesFrom() error { - var options string - - if rootless.SkipStorageSetup() { + if os.Geteuid() != 0 { return nil } for _, vol := range c.VolumesFrom { + options := "" splitVol := strings.SplitN(vol, ":", 2) if len(splitVol) == 2 { options = splitVol[1] @@ -284,41 +281,60 @@ func (c *CreateConfig) GetVolumesFrom() error { if err != nil { return errors.Wrapf(err, "error looking up container %q", splitVol[0]) } - inspect, err := ctr.Inspect(false) - if err != nil { - return errors.Wrapf(err, "error inspecting %q", splitVol[0]) - } - var createArtifact CreateConfig - artifact, err := ctr.GetArtifact("create-config") - if err != nil { - return errors.Wrapf(err, "error getting create-config artifact for %q", splitVol[0]) + + logrus.Debugf("Adding volumes from container %s", ctr.ID()) + + // Look up the container's user volumes. This gets us the + // destinations of all mounts the user added to the container. + userVolumesArr := ctr.UserVolumes() + + // We're going to need to access them a lot, so convert to a map + // to reduce looping. + // We'll also use the map to indicate if we missed any volumes along the way. + userVolumes := make(map[string]bool) + for _, dest := range userVolumesArr { + userVolumes[dest] = false } - if err := json.Unmarshal(artifact, &createArtifact); err != nil { - return err + + // Now we get the container's spec and loop through its volumes + // and append them in if we can find them. + spec := ctr.Spec() + if spec == nil { + return errors.Errorf("error retrieving container %s spec", ctr.ID()) } - for key := range createArtifact.BuiltinImgVolumes { - for _, m := range inspect.Mounts { - if m.Destination == key { - c.LocalVolumes = append(c.LocalVolumes, m) - break + for _, mnt := range spec.Mounts { + if mnt.Type != TypeBind { + continue + } + if _, exists := userVolumes[mnt.Destination]; exists { + userVolumes[mnt.Destination] = true + localOptions := options + if localOptions == "" { + localOptions = strings.Join(mnt.Options, ",") } + c.Volumes = append(c.Volumes, fmt.Sprintf("%s:%s:%s", mnt.Source, mnt.Destination, localOptions)) } } - for _, i := range createArtifact.Volumes { - // Volumes format is host-dir:ctr-dir[:options], so get the host and ctr dir - // and add on the options given by the user to the flag. - spliti := strings.SplitN(i, ":", 3) - // Throw error if mounting volume from container with Z option (private label) - // Override this by adding 'z' to options. - if len(spliti) > 2 && strings.Contains(spliti[2], "Z") && !strings.Contains(options, "z") { - return errors.Errorf("volume mounted with private option 'Z' in %q. Use option 'z' to mount in current container", ctr.ID()) + // We're done with the spec mounts. Add named volumes. + // Add these unconditionally - none of them are automatically + // part of the container, as some spec mounts are. + namedVolumes := ctr.NamedVolumes() + for _, namedVol := range namedVolumes { + if _, exists := userVolumes[namedVol.Dest]; exists { + userVolumes[namedVol.Dest] = true } - if options == "" { - // Mount the volumes with the default options - c.Volumes = append(c.Volumes, createArtifact.Volumes...) - } else { - c.Volumes = append(c.Volumes, spliti[0]+":"+spliti[1]+":"+options) + localOptions := options + if localOptions == "" { + localOptions = strings.Join(namedVol.Options, ",") + } + c.Volumes = append(c.Volumes, fmt.Sprintf("%s:%s:%s", namedVol.Name, namedVol.Dest, localOptions)) + } + + // Check if we missed any volumes + for volDest, found := range userVolumes { + if !found { + logrus.Warnf("Unable to match volume %s from container %s for volumes-from", volDest, ctr.ID()) } } } @@ -418,14 +434,20 @@ func (c *CreateConfig) GetContainerCreateOptions(runtime *libpod.Runtime, pod *l // others, if they are included volumes := make([]string, 0, len(c.Volumes)) for _, vol := range c.Volumes { - volumes = append(volumes, strings.SplitN(vol, ":", 2)[0]) + // We always want the volume destination + splitVol := strings.SplitN(vol, ":", 3) + if len(splitVol) > 1 { + volumes = append(volumes, splitVol[1]) + } else { + volumes = append(volumes, splitVol[0]) + } } options = append(options, libpod.WithUserVolumes(volumes)) } - if len(c.LocalVolumes) != 0 { - options = append(options, libpod.WithLocalVolumes(c.LocalVolumes)) + if len(c.NamedVolumes) != 0 { + options = append(options, libpod.WithNamedVolumes(c.NamedVolumes)) } if len(c.Command) != 0 { @@ -539,7 +561,7 @@ func (c *CreateConfig) GetContainerCreateOptions(runtime *libpod.Runtime, pod *l options = append(options, libpod.WithPrivileged(c.Privileged)) - useImageVolumes := c.ImageVolumeType == "bind" + useImageVolumes := c.ImageVolumeType == TypeBind // Gather up the options for NewContainer which consist of With... funcs options = append(options, libpod.WithRootFSFromImage(c.ImageID, c.Image, useImageVolumes)) options = append(options, libpod.WithSecLabels(c.LabelOpts)) diff --git a/pkg/spec/spec.go b/pkg/spec/spec.go index a61741f73..9b6bd089e 100644 --- a/pkg/spec/spec.go +++ b/pkg/spec/spec.go @@ -6,6 +6,7 @@ import ( "path/filepath" "strings" + "github.com/containers/libpod/libpod" "github.com/containers/libpod/pkg/rootless" "github.com/containers/storage/pkg/mount" pmount "github.com/containers/storage/pkg/mount" @@ -48,6 +49,33 @@ func supercedeUserMounts(mounts []spec.Mount, configMount []spec.Mount) []spec.M return configMount } +// Split named volumes from normal volumes +func splitNamedVolumes(mounts []spec.Mount) ([]spec.Mount, []*libpod.ContainerNamedVolume) { + newMounts := make([]spec.Mount, 0) + namedVolumes := make([]*libpod.ContainerNamedVolume, 0) + for _, mount := range mounts { + // If it's not a named volume, append unconditionally + if mount.Type != TypeBind { + newMounts = append(newMounts, mount) + continue + } + // Volumes that are not named volumes must be an absolute or + // relative path. + // Volume names may not begin with a non-alphanumeric character + // so the HasPrefix() check is safe here. + if strings.HasPrefix(mount.Source, "/") || strings.HasPrefix(mount.Source, ".") { + newMounts = append(newMounts, mount) + } else { + namedVolume := new(libpod.ContainerNamedVolume) + namedVolume.Name = mount.Source + namedVolume.Dest = mount.Destination + namedVolume.Options = mount.Options + namedVolumes = append(namedVolumes, namedVolume) + } + } + return newMounts, namedVolumes +} + func getAvailableGids() (int64, error) { idMap, err := user.ParseIDMapFile("/proc/self/gid_map") if err != nil { @@ -99,7 +127,7 @@ func CreateConfigToOCISpec(config *CreateConfig) (*spec.Spec, error) { //nolint } sysMnt := spec.Mount{ Destination: "/sys", - Type: "bind", + Type: TypeBind, Source: "/sys", Options: []string{"rprivate", "nosuid", "noexec", "nodev", r, "rbind"}, } @@ -126,7 +154,7 @@ func CreateConfigToOCISpec(config *CreateConfig) (*spec.Spec, error) { //nolint g.RemoveMount("/dev/mqueue") devMqueue := spec.Mount{ Destination: "/dev/mqueue", - Type: "bind", + Type: TypeBind, Source: "/dev/mqueue", Options: []string{"bind", "nosuid", "noexec", "nodev"}, } @@ -136,7 +164,7 @@ func CreateConfigToOCISpec(config *CreateConfig) (*spec.Spec, error) { //nolint g.RemoveMount("/proc") procMount := spec.Mount{ Destination: "/proc", - Type: "bind", + Type: TypeBind, Source: "/proc", Options: []string{"rbind", "nosuid", "noexec", "nodev"}, } @@ -377,6 +405,12 @@ func CreateConfigToOCISpec(config *CreateConfig) (*spec.Spec, error) { //nolint configSpec.Mounts = supercedeUserMounts(volumeMounts, configSpec.Mounts) //--mount configSpec.Mounts = supercedeUserMounts(config.initFSMounts(), configSpec.Mounts) + + // Split normal mounts and named volumes + newMounts, namedVolumes := splitNamedVolumes(configSpec.Mounts) + configSpec.Mounts = newMounts + config.NamedVolumes = namedVolumes + // BLOCK IO blkio, err := config.CreateBlockIO() if err != nil { diff --git a/pkg/varlinkapi/images.go b/pkg/varlinkapi/images.go index 0ca867410..8cd13e251 100644 --- a/pkg/varlinkapi/images.go +++ b/pkg/varlinkapi/images.go @@ -103,6 +103,7 @@ func (i *LibpodAPI) GetImage(call iopodman.VarlinkCall, id string) error { VirtualSize: newImage.VirtualSize, Containers: int64(len(containers)), Labels: labels, + TopLayer: newImage.TopLayer(), } return call.ReplyGetImage(il) } @@ -910,3 +911,53 @@ func (i *LibpodAPI) LoadImage(call iopodman.VarlinkCall, name, inputFile string, } return call.ReplyLoadImage(br) } + +// Diff ... +func (i *LibpodAPI) Diff(call iopodman.VarlinkCall, name string) error { + var response []iopodman.DiffInfo + changes, err := i.Runtime.GetDiff("", name) + if err != nil { + return call.ReplyErrorOccurred(err.Error()) + } + for _, change := range changes { + response = append(response, iopodman.DiffInfo{Path: change.Path, ChangeType: change.Kind.String()}) + } + return call.ReplyDiff(response) +} + +// GetLayersMapWithImageInfo is a development only endpoint to obtain layer information for an image. +func (i *LibpodAPI) GetLayersMapWithImageInfo(call iopodman.VarlinkCall) error { + layerInfo, err := image.GetLayersMapWithImageInfo(i.Runtime.ImageRuntime()) + if err != nil { + return call.ReplyErrorOccurred(err.Error()) + } + b, err := json.Marshal(layerInfo) + if err != nil { + return call.ReplyErrorOccurred(err.Error()) + } + return call.ReplyGetLayersMapWithImageInfo(string(b)) +} + +// BuildImageHierarchyMap ... +func (i *LibpodAPI) BuildImageHierarchyMap(call iopodman.VarlinkCall, name string) error { + img, err := i.Runtime.ImageRuntime().NewFromLocal(name) + if err != nil { + return call.ReplyErrorOccurred(err.Error()) + } + imageInfo := &image.InfoImage{ + ID: img.ID(), + Tags: img.Names(), + } + layerInfo, err := image.GetLayersMapWithImageInfo(i.Runtime.ImageRuntime()) + if err != nil { + return call.ReplyErrorOccurred(err.Error()) + } + if err := image.BuildImageHierarchyMap(imageInfo, layerInfo, img.TopLayer()); err != nil { + return call.ReplyErrorOccurred(err.Error()) + } + b, err := json.Marshal(imageInfo) + if err != nil { + return call.ReplyErrorOccurred(err.Error()) + } + return call.ReplyBuildImageHierarchyMap(string(b)) +} diff --git a/test/e2e/commit_test.go b/test/e2e/commit_test.go index bf9c88de5..fe4ae64cf 100644 --- a/test/e2e/commit_test.go +++ b/test/e2e/commit_test.go @@ -144,7 +144,7 @@ var _ = Describe("Podman commit", func() { inspect.WaitWithDefaultTimeout() Expect(inspect.ExitCode()).To(Equal(0)) image := inspect.InspectImageJSON() - _, ok := image[0].Config.Volumes["/tmp"] + _, ok := image[0].Config.Volumes["/foo"] Expect(ok).To(BeTrue()) r := podmanTest.Podman([]string{"run", "newimage"}) diff --git a/test/e2e/generate_kube_test.go b/test/e2e/generate_kube_test.go index 5bcf3b347..2f0af7e5f 100644 --- a/test/e2e/generate_kube_test.go +++ b/test/e2e/generate_kube_test.go @@ -48,7 +48,6 @@ var _ = Describe("Podman generate kube", func() { }) It("podman generate kube on container", func() { - SkipIfRootless() session := podmanTest.RunTopContainer("top") session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) @@ -62,7 +61,6 @@ var _ = Describe("Podman generate kube", func() { }) It("podman generate service kube on container", func() { - SkipIfRootless() session := podmanTest.RunTopContainer("top") session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) @@ -76,7 +74,6 @@ var _ = Describe("Podman generate kube", func() { }) It("podman generate kube on pod", func() { - SkipIfRootless() _, rc, _ := podmanTest.CreatePod("toppod") Expect(rc).To(Equal(0)) @@ -93,7 +90,6 @@ var _ = Describe("Podman generate kube", func() { }) It("podman generate service kube on pod", func() { - SkipIfRootless() _, rc, _ := podmanTest.CreatePod("toppod") Expect(rc).To(Equal(0)) diff --git a/test/e2e/healthcheck_run_test.go b/test/e2e/healthcheck_run_test.go index cd2365ce7..60be86ebc 100644 --- a/test/e2e/healthcheck_run_test.go +++ b/test/e2e/healthcheck_run_test.go @@ -42,7 +42,6 @@ var _ = Describe("Podman healthcheck run", func() { }) It("podman healthcheck on valid container", func() { - SkipIfRootless() podmanTest.RestoreArtifact(healthcheck) session := podmanTest.Podman([]string{"run", "-dt", "--name", "hc", healthcheck}) session.WaitWithDefaultTimeout() @@ -135,7 +134,6 @@ var _ = Describe("Podman healthcheck run", func() { }) It("podman healthcheck good check results in healthy even in start-period", func() { - SkipIfRootless() session := podmanTest.Podman([]string{"run", "-dt", "--name", "hc", "--healthcheck-start-period", "2m", "--healthcheck-retries", "2", "--healthcheck-command", "\"CMD-SHELL\" \"ls\" \"||\" \"exit\" \"1\"", ALPINE, "top"}) session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) @@ -149,7 +147,6 @@ var _ = Describe("Podman healthcheck run", func() { }) It("podman healthcheck single healthy result changes failed to healthy", func() { - SkipIfRootless() session := podmanTest.Podman([]string{"run", "-dt", "--name", "hc", "--healthcheck-retries", "2", "--healthcheck-command", "\"CMD-SHELL\" \"ls\" \"/foo\" \"||\" \"exit\" \"1\"", ALPINE, "top"}) session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) diff --git a/test/e2e/pod_rm_test.go b/test/e2e/pod_rm_test.go index f9d7abe8f..5da3d563b 100644 --- a/test/e2e/pod_rm_test.go +++ b/test/e2e/pod_rm_test.go @@ -3,6 +3,7 @@ package integration import ( + "fmt" "os" . "github.com/containers/libpod/test/utils" @@ -95,28 +96,41 @@ var _ = Describe("Podman pod rm", func() { }) It("podman pod rm -a doesn't remove a running container", func() { + fmt.Printf("To start, there are %d pods\n", podmanTest.NumberOfPods()) _, ec, podid1 := podmanTest.CreatePod("") Expect(ec).To(Equal(0)) _, ec, _ = podmanTest.CreatePod("") Expect(ec).To(Equal(0)) + fmt.Printf("Started %d pods\n", podmanTest.NumberOfPods()) session := podmanTest.RunTopContainerInPod("", podid1) session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) + podmanTest.WaitForContainer() + Expect(podmanTest.NumberOfContainersRunning()).To(Equal(1)) + fmt.Printf("Started container running in one pod") + num_pods := podmanTest.NumberOfPods() + Expect(num_pods).To(Equal(2)) + ps := podmanTest.Podman([]string{"pod", "ps"}) + ps.WaitWithDefaultTimeout() + fmt.Printf("Current %d pod(s):\n%s\n", num_pods, ps.OutputToString()) + + fmt.Printf("Removing all empty pods\n") result := podmanTest.Podman([]string{"pod", "rm", "-a"}) result.WaitWithDefaultTimeout() Expect(result.ExitCode()).To(Not(Equal(0))) - - result = podmanTest.Podman([]string{"ps", "-q"}) - result.WaitWithDefaultTimeout() - Expect(len(result.OutputToStringArray())).To(Equal(1)) - - // one pod should have been deleted - result = podmanTest.Podman([]string{"pod", "ps", "-q"}) - result.WaitWithDefaultTimeout() - Expect(len(result.OutputToStringArray())).To(Equal(1)) + foundExpectedError, _ := result.ErrorGrepString("contains containers and cannot be removed") + Expect(foundExpectedError).To(Equal(true)) + + num_pods = podmanTest.NumberOfPods() + ps = podmanTest.Podman([]string{"pod", "ps"}) + ps.WaitWithDefaultTimeout() + fmt.Printf("Final %d pod(s):\n%s\n", num_pods, ps.OutputToString()) + Expect(num_pods).To(Equal(1)) + // Confirm top container still running inside remaining pod + Expect(podmanTest.NumberOfContainersRunning()).To(Equal(1)) }) It("podman pod rm -fa removes everything", func() { diff --git a/test/e2e/run_test.go b/test/e2e/run_test.go index 2daf2fe5b..a89ee491b 100644 --- a/test/e2e/run_test.go +++ b/test/e2e/run_test.go @@ -611,7 +611,6 @@ USER mail` session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) Expect(session.OutputToString()).To(ContainSubstring("data")) - }) It("podman run --volumes flag with multiple volumes", func() { diff --git a/test/e2e/volume_rm_test.go b/test/e2e/volume_rm_test.go index 888474670..39628d56f 100644 --- a/test/e2e/volume_rm_test.go +++ b/test/e2e/volume_rm_test.go @@ -32,7 +32,7 @@ var _ = Describe("Podman volume rm", func() { }) - It("podman rm volume", func() { + It("podman volume rm", func() { session := podmanTest.Podman([]string{"volume", "create", "myvol"}) session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) @@ -47,7 +47,7 @@ var _ = Describe("Podman volume rm", func() { Expect(len(session.OutputToStringArray())).To(Equal(0)) }) - It("podman rm with --force flag", func() { + It("podman volume rm with --force flag", func() { SkipIfRemote() session := podmanTest.Podman([]string{"create", "-v", "myvol:/myvol", ALPINE, "ls"}) cid := session.OutputToString() diff --git a/test/framework/framework.go b/test/framework/framework.go new file mode 100644 index 000000000..52401faf8 --- /dev/null +++ b/test/framework/framework.go @@ -0,0 +1,56 @@ +package framework + +import ( + "fmt" + + "github.com/onsi/ginkgo" + "github.com/onsi/gomega" +) + +// TestFramework is used to support commonnly used test features +type TestFramework struct { + setup func(*TestFramework) error + teardown func(*TestFramework) error + TestError error +} + +// NewTestFramework creates a new test framework instance for a given `setup` +// and `teardown` function +func NewTestFramework( + setup func(*TestFramework) error, + teardown func(*TestFramework) error, +) *TestFramework { + return &TestFramework{ + setup, + teardown, + fmt.Errorf("error"), + } +} + +// NilFn is a convenience function which simply does nothing +func NilFunc(f *TestFramework) error { + return nil +} + +// Setup is the global initialization function which runs before each test +// suite +func (t *TestFramework) Setup() { + // Global initialization for the whole framework goes in here + + // Setup the actual test suite + gomega.Expect(t.setup(t)).To(gomega.Succeed()) +} + +// Teardown is the global deinitialization function which runs after each test +// suite +func (t *TestFramework) Teardown() { + // Global deinitialization for the whole framework goes in here + + // Teardown the actual test suite + gomega.Expect(t.teardown(t)).To(gomega.Succeed()) +} + +// Describe is a convenience wrapper around the `ginkgo.Describe` function +func (t *TestFramework) Describe(text string, body func()) bool { + return ginkgo.Describe("libpod: "+text, body) +} diff --git a/version/version.go b/version/version.go index 89b5fbd8b..29a576317 100644 --- a/version/version.go +++ b/version/version.go @@ -4,7 +4,7 @@ package version // NOTE: remember to bump the version at the top // of the top-level README.md file when this is // bumped. -const Version = "1.2.0-dev" +const Version = "1.3.0-dev" // RemoteAPIVersion is the version for the remote // client API. It is used to determine compatibility |