diff options
85 files changed, 1448 insertions, 468 deletions
diff --git a/.cirrus.yml b/.cirrus.yml index cc645e601..848dc2b6d 100644 --- a/.cirrus.yml +++ b/.cirrus.yml @@ -31,9 +31,9 @@ env: #### #### Cache-image names to test with ### - FEDORA_CACHE_IMAGE_NAME: "fedora-29-libpod-5171433328607232" - PRIOR_FEDORA_CACHE_IMAGE_NAME: "fedora-28-libpod-5171433328607232" - UBUNTU_CACHE_IMAGE_NAME: "ubuntu-18-libpod-5171433328607232" + FEDORA_CACHE_IMAGE_NAME: "fedora-29-libpod-4844850202017792" + PRIOR_FEDORA_CACHE_IMAGE_NAME: "fedora-28-libpod-4844850202017792" + UBUNTU_CACHE_IMAGE_NAME: "ubuntu-18-libpod-4844850202017792" #### #### Variables for composing new cache-images (used in PR testing) from @@ -43,7 +43,7 @@ env: # Git commits to use while building dependencies into cache-images FEDORA_CNI_COMMIT: "412b6d31280682bb4fab4446f113c22ff1886554" CNI_COMMIT: "7480240de9749f9a0a5c8614b17f1f03e0c06ab9" - CONMON_COMMIT: "f02c053eb37010fc76d1e2966de7f2cb9f969ef2" + CONMON_COMMIT: "8455ce1ef385120deb827d0f0588c04357bad4c4" CRIU_COMMIT: "c74b83cd49c00589c0c0468ba5fe685b67fdbd0a" # Special image w/ nested-libvirt + tools for creating new cache and base images IMAGE_BUILDER_CACHE_IMAGE_NAME: "image-builder-image-1541772081" diff --git a/.copr/prepare.sh b/.copr/prepare.sh index 57c380b02..d7c5083ca 100644 --- a/.copr/prepare.sh +++ b/.copr/prepare.sh @@ -29,4 +29,4 @@ fi mkdir build/ git archive --prefix "libpod-${COMMIT_SHORT}/" --format "tar.gz" HEAD -o "build/libpod-${COMMIT_SHORT}.tar.gz" git clone https://github.com/containers/conmon -cd conmon && git checkout 59952292a3b07ac125575024ae21956efe0ecdfb && git archive --prefix "conmon/" --format "tar.gz" HEAD -o "../build/conmon.tar.gz" +cd conmon && git checkout 8455ce1ef385120deb827d0f0588c04357bad4c4 && git archive --prefix "conmon/" --format "tar.gz" HEAD -o "../build/conmon.tar.gz" diff --git a/.tool/lint b/.tool/lint index 01f44311d..67dfd4b28 100755 --- a/.tool/lint +++ b/.tool/lint @@ -39,7 +39,6 @@ ${LINTER} \ --exclude='.*_test\.go:.*error return value not checked.*\(errcheck\)$'\ --exclude='duplicate of.*_test.go.*\(dupl\)$'\ --exclude='cmd\/client\/.*\.go.*\(dupl\)$'\ - --exclude='libpod\/.*_easyjson.go:.*'\ --exclude='vendor\/.*'\ --exclude='podman\/.*'\ --exclude='server\/seccomp\/.*\.go.*$'\ @@ -286,7 +286,7 @@ in the [API.md](https://github.com/containers/libpod/blob/master/API.md) file in <div style="background-color: #E8E8E8; padding: 15px; margin: 10px; border-radius: 10px;"> method Attach(name: [string](https://godoc.org/builtin#string), detachKeys: [string](https://godoc.org/builtin#string), start: [bool](https://godoc.org/builtin#bool)) </div> -Attach takes the name or ID of a container and sets up a the ability to remotely attach to its console. The start +Attach takes the name or ID of a container and sets up the ability to remotely attach to its console. The start bool is whether you wish to start the container in question first. ### <a name="AttachControl"></a>func AttachControl <div style="background-color: #E8E8E8; padding: 15px; margin: 10px; border-radius: 10px;"> @@ -459,7 +459,7 @@ $ varlink call -m unix:/run/podman/io.podman/io.podman.ExportContainer '{"name": method ExportImage(name: [string](https://godoc.org/builtin#string), destination: [string](https://godoc.org/builtin#string), compress: [bool](https://godoc.org/builtin#bool), tags: [[]string](#[]string)) [string](https://godoc.org/builtin#string)</div> ExportImage takes the name or ID of an image and exports it to a destination like a tarball. There is also -a booleon option to force compression. It also takes in a string array of tags to be able to save multiple +a boolean option to force compression. It also takes in a string array of tags to be able to save multiple tags of the same image to a tarball (each tag should be of the form <image>:<tag>). Upon completion, the ID of the image is returned. If the image cannot be found in local storage, an [ImageNotFound](#ImageNotFound) error will be returned. See also [ImportImage](ImportImage). @@ -729,14 +729,14 @@ error will be returned if the container cannot be found. See also [InspectImage] <div style="background-color: #E8E8E8; padding: 15px; margin: 10px; border-radius: 10px;"> method InspectImage(name: [string](https://godoc.org/builtin#string)) [string](https://godoc.org/builtin#string)</div> -InspectImage takes the name or ID of an image and returns a string respresentation of data associated with the +InspectImage takes the name or ID of an image and returns a string representation of data associated with the mage. You must serialize the string into JSON to use it further. An [ImageNotFound](#ImageNotFound) error will be returned if the image cannot be found. ### <a name="InspectPod"></a>func InspectPod <div style="background-color: #E8E8E8; padding: 15px; margin: 10px; border-radius: 10px;"> method InspectPod(name: [string](https://godoc.org/builtin#string)) [string](https://godoc.org/builtin#string)</div> -InspectPod takes the name or ID of an image and returns a string respresentation of data associated with the +InspectPod takes the name or ID of an image and returns a string representation of data associated with the pod. You must serialize the string into JSON to use it further. A [PodNotFound](#PodNotFound) error will be returned if the pod cannot be found. ### <a name="KillContainer"></a>func KillContainer @@ -1650,7 +1650,7 @@ name [string](https://godoc.org/builtin#string) star_count [int](https://godoc.org/builtin#int) ### <a name="InfoDistribution"></a>type InfoDistribution -InfoDistribution describes the the host's distribution +InfoDistribution describes the host's distribution distribution [string](https://godoc.org/builtin#string) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 87efdeb8f..b86f3e345 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -315,7 +315,7 @@ branches, which you may occasionally see [red bars on the status graph .](https://cirrus-ci.com/github/containers/libpod/master) When the graph shows mostly green bars on the right, it's a good indication -the master branch is currently stable. Alternating red/green bars is indicitave +the master branch is currently stable. Alternating red/green bars is indicative of a testing "flake", and should be examined (anybody can do this): * *One or a small handful of tests, on a single task, (i.e. specific distro/version) @@ -350,6 +350,9 @@ and [PRs](https://github.com/containers/libpod/pulls) tracking system. +There is also a [mailing list](https://lists.podman.io/archives/) at `lists.podman.io`. +You can subscribe by sending a message to `podman@lists.podman.io` with the subject `subscribe`. + [owners]: https://github.com/kubernetes/community/blob/master/contributors/guide/owners.md#owners diff --git a/Dockerfile b/Dockerfile index 4fc85e959..214fbeb34 100644 --- a/Dockerfile +++ b/Dockerfile @@ -56,7 +56,7 @@ RUN set -x \ && rm -rf "$GOPATH" # Install conmon -ENV CONMON_COMMIT 59952292a3b07ac125575024ae21956efe0ecdfb +ENV CONMON_COMMIT 8455ce1ef385120deb827d0f0588c04357bad4c4 RUN set -x \ && export GOPATH="$(mktemp -d)" \ && git clone https://github.com/containers/conmon.git "$GOPATH/src/github.com/containers/conmon.git" \ @@ -79,14 +79,6 @@ RUN set -x \ && cp bin/* /usr/libexec/cni \ && rm -rf "$GOPATH" -# Install buildah -RUN set -x \ - && export GOPATH=/go \ - && git clone https://github.com/containers/buildah "$GOPATH/src/github.com/containers/buildah" \ - && cd "$GOPATH/src/github.com/containers/buildah" \ - && make \ - && make install - # Install ginkgo RUN set -x \ && export GOPATH=/go \ @@ -98,12 +90,6 @@ RUN set -x \ && export GOPATH=/go \ && go get github.com/onsi/gomega/... -# Install easyjson -RUN set -x \ - && export GOPATH=/go \ - && go get -u github.com/mailru/easyjson/... \ - && install -D -m 755 "$GOPATH"/bin/easyjson /usr/bin/ - # Install latest stable criu version RUN set -x \ && cd /tmp \ diff --git a/Dockerfile.centos b/Dockerfile.centos index 159449c63..72b926bff 100644 --- a/Dockerfile.centos +++ b/Dockerfile.centos @@ -38,14 +38,6 @@ RUN set -x \ && cp bin/* /usr/libexec/cni \ && rm -rf "$GOPATH" -# Install buildah -RUN set -x \ - && export GOPATH=/go \ - && git clone https://github.com/containers/buildah "$GOPATH/src/github.com/containers/buildah" \ - && cd "$GOPATH/src/github.com/containers/buildah" \ - && make \ - && make install - # Install ginkgo RUN set -x \ && export GOPATH=/go \ @@ -57,14 +49,8 @@ RUN set -x \ && export GOPATH=/go \ && go get github.com/onsi/gomega/... -# Install easyjson -RUN set -x \ - && export GOPATH=/go \ - && go get -u github.com/mailru/easyjson/... \ - && install -D -m 755 "$GOPATH"/bin/easyjson /usr/bin/ - # Install conmon -ENV CONMON_COMMIT 59952292a3b07ac125575024ae21956efe0ecdfb +ENV CONMON_COMMIT 8455ce1ef385120deb827d0f0588c04357bad4c4 RUN set -x \ && export GOPATH="$(mktemp -d)" \ && git clone https://github.com/containers/conmon.git "$GOPATH/src/github.com/containers/conmon.git" \ diff --git a/Dockerfile.fedora b/Dockerfile.fedora index 74a770a90..c34d4bb16 100644 --- a/Dockerfile.fedora +++ b/Dockerfile.fedora @@ -42,14 +42,6 @@ RUN set -x \ && cp bin/* /usr/libexec/cni \ && rm -rf "$GOPATH" -# Install buildah -RUN set -x \ - && export GOPATH=/go \ - && git clone https://github.com/containers/buildah "$GOPATH/src/github.com/containers/buildah" \ - && cd "$GOPATH/src/github.com/containers/buildah" \ - && make \ - && make install - # Install ginkgo RUN set -x \ && export GOPATH=/go \ @@ -61,14 +53,8 @@ RUN set -x \ && export GOPATH=/go \ && go get github.com/onsi/gomega/... -# Install easyjson -RUN set -x \ - && export GOPATH=/go \ - && go get -u github.com/mailru/easyjson/... \ - && install -D -m 755 "$GOPATH"/bin/easyjson /usr/bin/ - # Install conmon -ENV CONMON_COMMIT 59952292a3b07ac125575024ae21956efe0ecdfb +ENV CONMON_COMMIT 8455ce1ef385120deb827d0f0588c04357bad4c4 RUN set -x \ && export GOPATH="$(mktemp -d)" \ && git clone https://github.com/containers/conmon.git "$GOPATH/src/github.com/containers/conmon.git" \ @@ -1,6 +1,6 @@ GO ?= go -DESTDIR ?= / -EPOCH_TEST_COMMIT ?= 1f31892a9fd8573d4b25274b208e6b9f860cdf81 +DESTDIR ?= +EPOCH_TEST_COMMIT ?= 90e3c9002b2293569e0cec168a30ecb962b00034 HEAD ?= HEAD CHANGELOG_BASE ?= HEAD~ CHANGELOG_TARGET ?= HEAD @@ -10,12 +10,12 @@ GIT_BRANCH ?= $(shell git rev-parse --abbrev-ref HEAD 2>/dev/null) GIT_BRANCH_CLEAN ?= $(shell echo $(GIT_BRANCH) | sed -e "s/[^[:alnum:]]/-/g") LIBPOD_IMAGE ?= libpod_dev$(if $(GIT_BRANCH_CLEAN),:$(GIT_BRANCH_CLEAN)) LIBPOD_INSTANCE := libpod_dev -PREFIX ?= ${DESTDIR}/usr/local +PREFIX ?= /usr/local BINDIR ?= ${PREFIX}/bin LIBEXECDIR ?= ${PREFIX}/libexec MANDIR ?= ${PREFIX}/share/man SHAREDIR_CONTAINERS ?= ${PREFIX}/share/containers -ETCDIR ?= ${DESTDIR}/etc +ETCDIR ?= /etc TMPFILESDIR ?= ${PREFIX}/lib/tmpfiles.d SYSTEMDDIR ?= ${PREFIX}/lib/systemd/system BUILDTAGS ?= \ @@ -51,7 +51,11 @@ COMMIT_NO ?= $(shell git rev-parse HEAD 2> /dev/null || true) GIT_COMMIT ?= $(if $(shell git status --porcelain --untracked-files=no),${COMMIT_NO}-dirty,${COMMIT_NO}) BUILD_INFO ?= $(shell date +%s) LIBPOD := ${PROJECT}/libpod -LDFLAGS_PODMAN ?= $(LDFLAGS) -X $(LIBPOD).gitCommit=$(GIT_COMMIT) -X $(LIBPOD).buildInfo=$(BUILD_INFO) +LDFLAGS_PODMAN ?= $(LDFLAGS) \ + -X $(LIBPOD).gitCommit=$(GIT_COMMIT) \ + -X $(LIBPOD).buildInfo=$(BUILD_INFO) \ + -X $(LIBPOD).installPrefix=$(PREFIX) \ + -X $(LIBPOD).etcDir=$(ETCDIR) ISODATE ?= $(shell date --iso-8601) #Update to LIBSECCOMP_COMMIT should reflect in Dockerfile too. LIBSECCOMP_COMMIT := release-2.3 @@ -262,54 +266,54 @@ changelog: ## Generate changelog install: .gopathok install.bin install.remote install.man install.cni install.systemd ## Install binaries to system locations install.remote: - install ${SELINUXOPT} -d -m 755 $(BINDIR) - install ${SELINUXOPT} -m 755 bin/podman-remote $(BINDIR)/podman-remote - test -z "${SELINUXOPT}" || chcon --verbose --reference=$(BINDIR)/podman bin/podman-remote + install ${SELINUXOPT} -d -m 755 $(DESTDIR)$(BINDIR) + install ${SELINUXOPT} -m 755 bin/podman-remote $(DESTDIR)$(BINDIR)/podman-remote + test -z "${SELINUXOPT}" || chcon --verbose --reference=$(DESTDIR)$(BINDIR)/podman bin/podman-remote install.bin: - install ${SELINUXOPT} -d -m 755 $(BINDIR) - install ${SELINUXOPT} -m 755 bin/podman $(BINDIR)/podman - test -z "${SELINUXOPT}" || chcon --verbose --reference=$(BINDIR)/podman bin/podman + install ${SELINUXOPT} -d -m 755 $(DESTDIR)$(BINDIR) + install ${SELINUXOPT} -m 755 bin/podman $(DESTDIR)$(BINDIR)/podman + test -z "${SELINUXOPT}" || chcon --verbose --reference=$(DESTDIR)$(BINDIR)/podman bin/podman install.man: docs - install ${SELINUXOPT} -d -m 755 $(MANDIR)/man1 - install ${SELINUXOPT} -d -m 755 $(MANDIR)/man5 - install ${SELINUXOPT} -m 644 $(filter %.1,$(MANPAGES)) -t $(MANDIR)/man1 - install ${SELINUXOPT} -m 644 $(filter %.5,$(MANPAGES)) -t $(MANDIR)/man5 - install ${SELINUXOPT} -m 644 docs/links/*1 -t $(MANDIR)/man1 + install ${SELINUXOPT} -d -m 755 $(DESTDIR)$(MANDIR)/man1 + install ${SELINUXOPT} -d -m 755 $(DESTDIR)$(MANDIR)/man5 + install ${SELINUXOPT} -m 644 $(filter %.1,$(MANPAGES)) -t $(DESTDIR)$(MANDIR)/man1 + install ${SELINUXOPT} -m 644 $(filter %.5,$(MANPAGES)) -t $(DESTDIR)$(MANDIR)/man5 + install ${SELINUXOPT} -m 644 docs/links/*1 -t $(DESTDIR)$(MANDIR)/man1 install.config: - install ${SELINUXOPT} -d -m 755 $(SHAREDIR_CONTAINERS) - install ${SELINUXOPT} -m 644 libpod.conf $(SHAREDIR_CONTAINERS)/libpod.conf - install ${SELINUXOPT} -m 644 seccomp.json $(SHAREDIR_CONTAINERS)/seccomp.json + install ${SELINUXOPT} -d -m 755 $(DESTDIR)$(SHAREDIR_CONTAINERS) + install ${SELINUXOPT} -m 644 libpod.conf $(DESTDIR)$(SHAREDIR_CONTAINERS)/libpod.conf + install ${SELINUXOPT} -m 644 seccomp.json $(DESTDIR)$(SHAREDIR_CONTAINERS)/seccomp.json install.completions: - install ${SELINUXOPT} -d -m 755 ${BASHINSTALLDIR} - install ${SELINUXOPT} -m 644 completions/bash/podman ${BASHINSTALLDIR} - install ${SELINUXOPT} -d -m 755 ${ZSHINSTALLDIR} - install ${SELINUXOPT} -m 644 completions/zsh/_podman ${ZSHINSTALLDIR} + install ${SELINUXOPT} -d -m 755 ${DESTDIR}${BASHINSTALLDIR} + install ${SELINUXOPT} -m 644 completions/bash/podman ${DESTDIR}${BASHINSTALLDIR} + install ${SELINUXOPT} -d -m 755 ${DESTDIR}${ZSHINSTALLDIR} + install ${SELINUXOPT} -m 644 completions/zsh/_podman ${DESTDIR}${ZSHINSTALLDIR} install.cni: - install ${SELINUXOPT} -d -m 755 ${ETCDIR}/cni/net.d/ - install ${SELINUXOPT} -m 644 cni/87-podman-bridge.conflist ${ETCDIR}/cni/net.d/87-podman-bridge.conflist + install ${SELINUXOPT} -d -m 755 ${DESTDIR}${ETCDIR}/cni/net.d/ + install ${SELINUXOPT} -m 644 cni/87-podman-bridge.conflist ${DESTDIR}${ETCDIR}/cni/net.d/87-podman-bridge.conflist install.docker: docker-docs - install ${SELINUXOPT} -d -m 755 $(BINDIR) $(MANDIR)/man1 - install ${SELINUXOPT} -m 755 docker $(BINDIR)/docker - install ${SELINUXOPT} -m 644 docs/docker*.1 -t $(MANDIR)/man1 + install ${SELINUXOPT} -d -m 755 $(DESTDIR)$(BINDIR) $(DESTDIR)$(MANDIR)/man1 + install ${SELINUXOPT} -m 755 docker $(DESTDIR)$(BINDIR)/docker + install ${SELINUXOPT} -m 644 docs/docker*.1 -t $(DESTDIR)$(MANDIR)/man1 install.systemd: - install ${SELINUXOPT} -m 755 -d ${SYSTEMDDIR} ${TMPFILESDIR} - install ${SELINUXOPT} -m 644 contrib/varlink/io.podman.socket ${SYSTEMDDIR}/io.podman.socket - install ${SELINUXOPT} -m 644 contrib/varlink/io.podman.service ${SYSTEMDDIR}/io.podman.service - install ${SELINUXOPT} -m 644 contrib/varlink/podman.conf ${TMPFILESDIR}/podman.conf + install ${SELINUXOPT} -m 755 -d ${DESTDIR}${SYSTEMDDIR} ${DESTDIR}${TMPFILESDIR} + install ${SELINUXOPT} -m 644 contrib/varlink/io.podman.socket ${DESTDIR}${SYSTEMDDIR}/io.podman.socket + install ${SELINUXOPT} -m 644 contrib/varlink/io.podman.service ${DESTDIR}${SYSTEMDDIR}/io.podman.service + install ${SELINUXOPT} -m 644 contrib/varlink/podman.conf ${DESTDIR}${TMPFILESDIR}/podman.conf uninstall: for i in $(filter %.1,$(MANPAGES)); do \ - rm -f $(MANDIR)/man1/$$(basename $${i}); \ + rm -f $(DESTDIR)$(MANDIR)/man1/$$(basename $${i}); \ done; \ for i in $(filter %.5,$(MANPAGES)); do \ - rm -f $(MANDIR)/man5/$$(basename $${i}); \ + rm -f $(DESTDIR)$(MANDIR)/man5/$$(basename $${i}); \ done .PHONY: .gitvalidation @@ -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.3.1](https://github.com/containers/libpod/releases/latest) +* [Latest Version: 1.4.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/RELEASE_NOTES.md b/RELEASE_NOTES.md index e36757edc..d5717f7db 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -1,5 +1,25 @@ # Release Notes +## 1.4.1 +### Features +- The `podman exec` command now sets its error code differently based on whether the container does not exist, and the command in the container does not exist +- The `podman inspect` command on containers now outputs Mounts JSON that matches that of `docker inspect`, only including user-specified volumes and differentiating bind mounts and named volumes +- The `podman inspect` command now reports the path to a container's OCI spec with the `OCIConfigPath` key (only included when the container is initialized or running) +- The `podman run --mount` command now supports the `bind-nonrecursive` option for bind mounts ([#3314](https://github.com/containers/libpod/issues/3314)) + +### Bugfixes +- Fixed a bug where `podman play kube` would fail to create containers due to an unspecified log driver +- Fixed a bug where Podman would fail to build with [musl libc](https://www.musl-libc.org/) ([#3284](https://github.com/containers/libpod/issues/3284)) +- Fixed a bug where rootless Podman using `slirp4netns` networking in an environment with no nameservers on the host other than localhost would result in nonfunctional networking ([#3277](https://github.com/containers/libpod/issues/3277)) +- Fixed a bug where `podman import` would not properly set environment variables, discarding their values and retaining only keys +- Fixed a bug where Podman would fail to run when built with Apparmor support but run on systems without the Apparmor kernel module loaded ([#3331](https://github.com/containers/libpod/issues/3331)) + +### Misc +- Remote Podman will now default the username it uses to log in to remote systems to the username of the current user +- Podman now uses JSON logging with OCI runtimes that support it, allowing for better error reporting +- Updated vendored Buildah to v1.8.4 +- Updated vendored containers/image to v2.0 + ## 1.4.0 ### Features - The `podman checkpoint` and `podman restore` commands can now be used to migrate containers between Podman installations on different systems ([#1618](https://github.com/containers/libpod/issues/1618)) @@ -22,6 +42,7 @@ - Fixed a bug where `podman exec` would fail on older kernels ([#2968](https://github.com/containers/libpod/issues/2968)) ### Misc +- The `podman inspect` command on containers now uses the `Id` key (instead of `ID`) for the container's ID, for better compatability with the output of `docker inspect` - The `podman commit` command is now usable with the Podman remote client - The `--signature-policy` flag (used with several image-related commands) has been deprecated - The `podman unshare` command now defines two environment variables in the spawned shell: `CONTAINERS_RUNROOT` and `CONTAINERS_GRAPHROOT`, pointing to temporary and permanent storage for rootless containers diff --git a/changelog.txt b/changelog.txt index ece82e15b..3b1260c90 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,3 +1,53 @@ +- Changelog for v1.4.1 (2019-06-14) + * Completely disable global options test + * Update release notes for 1.4.1 + * Skip runlabel global options test for podman-in-podman + * pkg/apparmor: fix when AA is disabled + * Fix ENV parsing on `podman import` + * Fix storage-opts type in Cobra + * Use the logical registry location instead of the physical one in (podman info) + * Update containers/image to v2.0.0, and buildah to v1.8.4 + * Document exit codes for podman exec + * Add --storage flag to 'podman rm' (local only) + * When creating exit command, pass storage options on + * Bump cirrus images + * Mention the new Podman mailing list in contributing.md + * Update 1.4.0 release notes with ID -> Id in inspect + * Bump conmon to 0.3.0 + * Cirrus: Guarantee ssh is running for rootless + * Purge all use of easyjson and ffjson in libpod + * Split mount options in inspect further + * storage: support --mount type=bind,bind-nonrecursive + * oci: allow to specify what runtimes support JSON + * storage: fix typo + * oci: use json formatted errors from the runtime + * Make Inspect's mounts struct accurate to Docker + * Provide OCI spec path in `podman inspect` output + * If container is not in correct state podman exec should exit with 126 + * rootless: use the slirp4netns builtin DNS first + * Add --filename option to generate kube + * Fix podman-remote to user default username + * Prohibit use of positional args with --import + * BATS tests - get working again + * Add a test for 'podman play kube' to prevent regression + * Cirrus: New images w/o buildah + * Remove source-built buildah from CI + * standardize documentation formatting + * Touchup upstream Dockerfile + * only set log driver if it isn't empty + * Fix cgo includes for musl + * When you change the storage driver we ignore the storage-options + * Update vendor on containers/storage to v1.12.10 + * Bump gitvalidation epoch + * Bump to v1.4.1-dev + * Default 'pause' to false for 'podman cp' + * Update c/storage to 9b10041d7b2ef767ce9c42b5862b6c51eeb82214 + * Fix spelling + * fix tutorial link to install.md + * Cirrus: Minor cleanup of dependencies and docs + * Begin to break up pkg/inspect + * docs: Add CI section and links + - Changelog for v1.4.0 (2019-06-07) * Update release notes for v1.4.0 * Update release notes for v1.4.0 diff --git a/cmd/podman/cliconfig/config.go b/cmd/podman/cliconfig/config.go index b8b1648b8..4a4c839cc 100644 --- a/cmd/podman/cliconfig/config.go +++ b/cmd/podman/cliconfig/config.go @@ -145,7 +145,8 @@ type ExportValues struct { } type GenerateKubeValues struct { PodmanCommand - Service bool + Service bool + Filename string } type GenerateSystemdValues struct { @@ -438,6 +439,7 @@ type RmValues struct { All bool Force bool Latest bool + Storage bool Volumes bool } diff --git a/cmd/podman/cp.go b/cmd/podman/cp.go index 7679ebcf1..a9418e6e0 100644 --- a/cmd/podman/cp.go +++ b/cmd/podman/cp.go @@ -145,7 +145,19 @@ func copyBetweenHostAndContainer(runtime *libpod.Runtime, src string, dest strin var glob []string if isFromHostToCtr { - if filepath.IsAbs(destPath) { + if isVol, volDestName, volName := isVolumeDestName(destPath, ctr); isVol { + path, err := pathWithVolumeMount(ctr, runtime, volDestName, volName, destPath) + if err != nil { + return errors.Wrapf(err, "error getting destination path from volume %s", volDestName) + } + destPath = path + } else if isBindMount, mount := isBindMountDestName(destPath, ctr); isBindMount { + path, err := pathWithBindMountSource(mount, destPath) + if err != nil { + return errors.Wrapf(err, "error getting destination path from bind mount %s", mount.Destination) + } + destPath = path + } else if filepath.IsAbs(destPath) { cleanedPath, err := securejoin.SecureJoin(mountPoint, destPath) if err != nil { return err @@ -166,7 +178,19 @@ func copyBetweenHostAndContainer(runtime *libpod.Runtime, src string, dest strin destPath = cleanedPath } } else { - if filepath.IsAbs(srcPath) { + if isVol, volDestName, volName := isVolumeDestName(srcPath, ctr); isVol { + path, err := pathWithVolumeMount(ctr, runtime, volDestName, volName, srcPath) + if err != nil { + return errors.Wrapf(err, "error getting source path from volume %s", volDestName) + } + srcPath = path + } else if isBindMount, mount := isBindMountDestName(srcPath, ctr); isBindMount { + path, err := pathWithBindMountSource(mount, srcPath) + if err != nil { + return errors.Wrapf(err, "error getting source path from bind moutn %s", mount.Destination) + } + srcPath = path + } else if filepath.IsAbs(srcPath) { cleanedPath, err := securejoin.SecureJoin(mountPoint, srcPath) if err != nil { return err @@ -407,3 +431,72 @@ func streamFileToStdout(srcPath string, srcfi os.FileInfo) error { } return nil } + +func isVolumeDestName(path string, ctr *libpod.Container) (bool, string, string) { + separator := string(os.PathSeparator) + if filepath.IsAbs(path) { + path = strings.TrimPrefix(path, separator) + } + if path == "" { + return false, "", "" + } + for _, vol := range ctr.Config().NamedVolumes { + volNamePath := strings.TrimPrefix(vol.Dest, separator) + if matchVolumePath(path, volNamePath) { + return true, vol.Dest, vol.Name + } + } + return false, "", "" +} + +// if SRCPATH or DESTPATH is from volume mount's destination -v or --mount type=volume, generates the path with volume mount point +func pathWithVolumeMount(ctr *libpod.Container, runtime *libpod.Runtime, volDestName, volName, path string) (string, error) { + destVolume, err := runtime.GetVolume(volName) + if err != nil { + return "", errors.Wrapf(err, "error getting volume destination %s", volName) + } + if !filepath.IsAbs(path) { + path = filepath.Join(string(os.PathSeparator), path) + } + path, err = securejoin.SecureJoin(destVolume.MountPoint(), strings.TrimPrefix(path, volDestName)) + return path, err +} + +func isBindMountDestName(path string, ctr *libpod.Container) (bool, specs.Mount) { + separator := string(os.PathSeparator) + if filepath.IsAbs(path) { + path = strings.TrimPrefix(path, string(os.PathSeparator)) + } + if path == "" { + return false, specs.Mount{} + } + for _, m := range ctr.Config().Spec.Mounts { + if m.Type != "bind" { + continue + } + mDest := strings.TrimPrefix(m.Destination, separator) + if matchVolumePath(path, mDest) { + return true, m + } + } + return false, specs.Mount{} +} + +func matchVolumePath(path, target string) bool { + pathStr := filepath.Clean(path) + target = filepath.Clean(target) + for len(pathStr) > len(target) && strings.Contains(pathStr, string(os.PathSeparator)) { + pathStr = pathStr[:strings.LastIndex(pathStr, string(os.PathSeparator))] + } + if pathStr == target { + return true + } + return false +} + +func pathWithBindMountSource(m specs.Mount, path string) (string, error) { + if !filepath.IsAbs(path) { + path = filepath.Join(string(os.PathSeparator), path) + } + return securejoin.SecureJoin(m.Source, strings.TrimPrefix(path, m.Destination)) +} diff --git a/cmd/podman/exec.go b/cmd/podman/exec.go index deff44a92..0684da842 100644 --- a/cmd/podman/exec.go +++ b/cmd/podman/exec.go @@ -125,5 +125,10 @@ func execCmd(c *cliconfig.ExecValues) error { streams.AttachError = true streams.AttachInput = true - return ctr.Exec(c.Tty, c.Privileged, envs, cmd, c.User, c.Workdir, streams, c.PreserveFDs) + err = ctr.Exec(c.Tty, c.Privileged, envs, cmd, c.User, c.Workdir, streams, c.PreserveFDs) + if errors.Cause(err) == libpod.ErrCtrStateInvalid { + exitCode = 126 + } + + return err } diff --git a/cmd/podman/generate_kube.go b/cmd/podman/generate_kube.go index 318dd0771..3969e3132 100644 --- a/cmd/podman/generate_kube.go +++ b/cmd/podman/generate_kube.go @@ -2,6 +2,9 @@ package main import ( "fmt" + "io/ioutil" + "os" + "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/pkg/adapter" podmanVersion "github.com/containers/libpod/version" @@ -37,6 +40,7 @@ func init() { containerKubeCommand.SetUsageTemplate(UsageTemplate()) flags := containerKubeCommand.Flags() flags.BoolVarP(&containerKubeCommand.Service, "service", "s", false, "Generate YAML for kubernetes service object") + flags.StringVarP(&containerKubeCommand.Filename, "filename", "f", "", "Filename to output to") } func generateKubeYAMLCmd(c *cliconfig.GenerateKubeValues) error { @@ -88,8 +92,19 @@ func generateKubeYAMLCmd(c *cliconfig.GenerateKubeValues) error { output = append(output, []byte("---\n")...) output = append(output, marshalledService...) } - // Output the v1.Pod with the v1.Container - fmt.Println(string(output)) + + if c.Filename != "" { + if _, err := os.Stat(c.Filename); err == nil { + return errors.Errorf("cannot write to %q - file exists", c.Filename) + } + + if err := ioutil.WriteFile(c.Filename, output, 0644); err != nil { + return err + } + } else { + // Output the v1.Pod with the v1.Container + fmt.Println(string(output)) + } return nil } diff --git a/cmd/podman/main.go b/cmd/podman/main.go index a149a47f9..cbca32cc8 100644 --- a/cmd/podman/main.go +++ b/cmd/podman/main.go @@ -104,6 +104,9 @@ func before(cmd *cobra.Command, args []string) error { logrus.Errorf(err.Error()) os.Exit(1) } + if err := setSyslog(); err != nil { + return err + } if err := setupRootless(cmd, args); err != nil { return err } diff --git a/cmd/podman/main_local.go b/cmd/podman/main_local.go index b4f21bd0c..132f35ab5 100644 --- a/cmd/podman/main_local.go +++ b/cmd/podman/main_local.go @@ -48,7 +48,7 @@ func init() { rootCmd.PersistentFlags().StringVar(&MainGlobalOpts.Runtime, "runtime", "", "Path to the OCI-compatible binary used to run containers, default is /usr/bin/runc") // -s is depracated due to conflict with -s on subcommands rootCmd.PersistentFlags().StringVar(&MainGlobalOpts.StorageDriver, "storage-driver", "", "Select which storage driver is used to manage storage of images and containers (default is overlay)") - rootCmd.PersistentFlags().StringSliceVar(&MainGlobalOpts.StorageOpts, "storage-opt", []string{}, "Used to pass an option to the storage driver") + rootCmd.PersistentFlags().StringArrayVar(&MainGlobalOpts.StorageOpts, "storage-opt", []string{}, "Used to pass an option to the storage driver") rootCmd.PersistentFlags().BoolVar(&MainGlobalOpts.Syslog, "syslog", false, "Output logging information to syslog as well as the console") rootCmd.PersistentFlags().StringVar(&MainGlobalOpts.TmpDir, "tmpdir", "", "Path to the tmp directory") diff --git a/cmd/podman/main_remote.go b/cmd/podman/main_remote.go index de6c2c760..1b9430e92 100644 --- a/cmd/podman/main_remote.go +++ b/cmd/podman/main_remote.go @@ -3,15 +3,21 @@ package main import ( + "os/user" + "github.com/spf13/cobra" ) const remote = true func init() { + var username string + if curruser, err := user.Current(); err == nil { + username = curruser.Username + } rootCmd.PersistentFlags().StringVar(&MainGlobalOpts.ConnectionName, "connection", "", "remote connection name") rootCmd.PersistentFlags().StringVar(&MainGlobalOpts.RemoteConfigFilePath, "remote-config-path", "", "alternate path for configuration file") - rootCmd.PersistentFlags().StringVar(&MainGlobalOpts.RemoteUserName, "username", "", "username on the remote host") + rootCmd.PersistentFlags().StringVar(&MainGlobalOpts.RemoteUserName, "username", username, "username on the remote host") rootCmd.PersistentFlags().StringVar(&MainGlobalOpts.RemoteHost, "remote-host", "", "remote host") // TODO maybe we allow the altering of this for bridge connections? //rootCmd.PersistentFlags().StringVar(&MainGlobalOpts.VarlinkAddress, "varlink-address", adapter.DefaultAddress, "address of the varlink socket") diff --git a/cmd/podman/restore.go b/cmd/podman/restore.go index 9c77d4a5e..6e445e5df 100644 --- a/cmd/podman/restore.go +++ b/cmd/podman/restore.go @@ -76,8 +76,22 @@ func restoreCmd(c *cliconfig.RestoreValues, cmd *cobra.Command) error { return errors.Errorf("--tcp-established cannot be used with --name") } - if (c.Import != "") && (c.All || c.Latest) { - return errors.Errorf("Cannot use --import and --all or --latest at the same time") + argLen := len(c.InputArgs) + if c.Import != "" { + if c.All || c.Latest { + return errors.Errorf("Cannot use --import with --all or --latest") + } + if argLen > 0 { + return errors.Errorf("Cannot use --import with positional arguments") + } } + + if (c.All || c.Latest) && argLen > 0 { + return errors.Errorf("no arguments are needed with --all or --latest") + } + if argLen < 1 && !c.All && !c.Latest && c.Import == "" { + return errors.Errorf("you must provide at least one name or id") + } + return runtime.Restore(getContext(), c, options) } diff --git a/cmd/podman/rm.go b/cmd/podman/rm.go index 1bf56b782..2710a8194 100644 --- a/cmd/podman/rm.go +++ b/cmd/podman/rm.go @@ -42,7 +42,9 @@ func init() { flags.BoolVarP(&rmCommand.All, "all", "a", false, "Remove all containers") flags.BoolVarP(&rmCommand.Force, "force", "f", false, "Force removal of a running container. The default is false") flags.BoolVarP(&rmCommand.Latest, "latest", "l", false, "Act on the latest container podman is aware of") + flags.BoolVar(&rmCommand.Storage, "storage", false, "Remove container from storage library") flags.BoolVarP(&rmCommand.Volumes, "volumes", "v", false, "Remove the volumes associated with the container") + markFlagHiddenForRemoteClient("storage", flags) markFlagHiddenForRemoteClient("latest", flags) } @@ -54,6 +56,13 @@ func rmCmd(c *cliconfig.RmValues) error { } defer runtime.Shutdown(false) + // Storage conflicts with --all/--latest/--volumes + if c.Storage { + if c.All || c.Latest || c.Volumes { + return errors.Errorf("--storage conflicts with --volumes, --all, and --latest") + } + } + ok, failures, err := runtime.RemoveContainers(getContext(), c) if err != nil { if errors.Cause(err) == libpod.ErrNoSuchCtr { diff --git a/cmd/podman/varlink/io.podman.varlink b/cmd/podman/varlink/io.podman.varlink index 5b3d5ae4c..9410b9459 100644 --- a/cmd/podman/varlink/io.podman.varlink +++ b/cmd/podman/varlink/io.podman.varlink @@ -207,7 +207,7 @@ type ContainerNameSpace ( ipc: string ) -# InfoDistribution describes the the host's distribution +# InfoDistribution describes the host's distribution type InfoDistribution ( distribution: string, version: string @@ -671,7 +671,7 @@ method PauseContainer(name: string) -> (container: string) # See also [PauseContainer](#PauseContainer). method UnpauseContainer(name: string) -> (container: string) -# Attach takes the name or ID of a container and sets up a the ability to remotely attach to its console. The start +# Attach takes the name or ID of a container and sets up the ability to remotely attach to its console. The start # bool is whether you wish to start the container in question first. method Attach(name: string, detachKeys: string, start: bool) -> () @@ -744,7 +744,7 @@ method BuildImage(build: BuildInfo) -> (image: MoreResponse) # This function is not implemented yet. # method CreateImage() -> (notimplemented: NotImplemented) -# InspectImage takes the name or ID of an image and returns a string respresentation of data associated with the +# InspectImage takes the name or ID of an image and returns a string representation of data associated with the #image. You must serialize the string into JSON to use it further. An [ImageNotFound](#ImageNotFound) error will # be returned if the image cannot be found. method InspectImage(name: string) -> (image: string) @@ -810,7 +810,7 @@ method Commit(name: string, image_name: string, changes: []string, author: strin method ImportImage(source: string, reference: string, message: string, changes: []string, delete: bool) -> (image: string) # ExportImage takes the name or ID of an image and exports it to a destination like a tarball. There is also -# a booleon option to force compression. It also takes in a string array of tags to be able to save multiple +# a boolean option to force compression. It also takes in a string array of tags to be able to save multiple # tags of the same image to a tarball (each tag should be of the form <image>:<tag>). Upon completion, the ID # of the image is returned. If the image cannot be found in local storage, an [ImageNotFound](#ImageNotFound) # error will be returned. See also [ImportImage](ImportImage). @@ -915,7 +915,7 @@ method ListPods() -> (pods: []ListPodData) # ~~~ method GetPod(name: string) -> (pod: ListPodData) -# InspectPod takes the name or ID of an image and returns a string respresentation of data associated with the +# InspectPod takes the name or ID of an image and returns a string representation of data associated with the # pod. You must serialize the string into JSON to use it further. A [PodNotFound](#PodNotFound) error will # be returned if the pod cannot be found. method InspectPod(name: string) -> (pod: string) diff --git a/completions/bash/podman b/completions/bash/podman index efb8a6a9b..65c6308cc 100644 --- a/completions/bash/podman +++ b/completions/bash/podman @@ -2041,6 +2041,7 @@ _podman_rm() { -h --latest -l + --storage --volumes -v " @@ -2467,7 +2468,9 @@ _podman_healthcheck_run() { } _podman_generate_kube() { - local options_with_args="" + local options_with_args=" + --filename -f + " local boolean_options=" -h diff --git a/contrib/cirrus/lib.sh b/contrib/cirrus/lib.sh index 462fa332a..97901cfc7 100644 --- a/contrib/cirrus/lib.sh +++ b/contrib/cirrus/lib.sh @@ -211,6 +211,8 @@ setup_rootless() { # Works with older versions of bash printf "${_env_var_name}=%q\n" "$(printenv $_env_var_name)" >> "/home/$ROOTLESS_USER/.bashrc" done + echo "Ensure the systems ssh process is up and running" + systemctl --wait restart sshd # a regular 'start' could hang forever } # Helper/wrapper script to only show stderr/stdout on non-zero exit diff --git a/contrib/cirrus/packer/fedora_setup.sh b/contrib/cirrus/packer/fedora_setup.sh index 33e240895..4388dc992 100644 --- a/contrib/cirrus/packer/fedora_setup.sh +++ b/contrib/cirrus/packer/fedora_setup.sh @@ -76,8 +76,6 @@ install_conmon CNI_COMMIT=$FEDORA_CNI_COMMIT install_cni_plugins -install_buildah - sudo /tmp/libpod/hack/install_catatonit.sh rh_finalize # N/B: Halts system! diff --git a/contrib/cirrus/packer/ubuntu_setup.sh b/contrib/cirrus/packer/ubuntu_setup.sh index 17e274d97..f183932c1 100644 --- a/contrib/cirrus/packer/ubuntu_setup.sh +++ b/contrib/cirrus/packer/ubuntu_setup.sh @@ -99,8 +99,6 @@ install_conmon install_cni_plugins -install_buildah - sudo /tmp/libpod/hack/install_catatonit.sh install_varlink diff --git a/contrib/gate/Dockerfile b/contrib/gate/Dockerfile index 9a6f5dc8d..630371c76 100644 --- a/contrib/gate/Dockerfile +++ b/contrib/gate/Dockerfile @@ -2,7 +2,6 @@ FROM fedora:29 RUN dnf -y install \ atomic-registries \ btrfs-progs-devel \ - buildah \ bzip2 \ conmon \ container-selinux \ diff --git a/contrib/spec/podman.spec.in b/contrib/spec/podman.spec.in index 8c2ccd4b0..d0ad07044 100644 --- a/contrib/spec/podman.spec.in +++ b/contrib/spec/podman.spec.in @@ -35,11 +35,11 @@ # People want conmon packaged with the copr rpm %global import_path_conmon github.com/containers/conmon %global git_conmon https://%{import_path_conmon} -%global commit_conmon 59952292a3b07ac125575024ae21956efe0ecdfb +%global commit_conmon 8455ce1ef385120deb827d0f0588c04357bad4c4 %global shortcommit_conmon %(c=%{commit_conmon}; echo ${c:0:7}) Name: podman -Version: 1.4.1 +Version: 1.4.2 Release: #COMMITDATE#.git%{shortcommit0}%{?dist} Summary: Manage Pods, Containers and Container Images License: ASL 2.0 diff --git a/docs/podman-container-restore.1.md b/docs/podman-container-restore.1.md index 5b94cd2fa..c96a37f80 100644 --- a/docs/podman-container-restore.1.md +++ b/docs/podman-container-restore.1.md @@ -45,8 +45,8 @@ connections. **--import, -i** Import a checkpoint tar.gz file, which was exported by Podman. This can be used -to import a checkpointed container from another host. It is not necessary to specify -a container when restoring from an exported checkpoint. +to import a checkpointed container from another host. Do not specify a *container* +argument when using this option. **--name, -n** diff --git a/docs/podman-create.1.md b/docs/podman-create.1.md index 88f8fe8c7..a4eebef4c 100644 --- a/docs/podman-create.1.md +++ b/docs/podman-create.1.md @@ -458,6 +458,7 @@ Current supported mount TYPES are bind, and tmpfs. Options specific to bind: · bind-propagation: shared, slave, private, rshared, rslave, or rprivate(default). See also mount(2). + . bind-nonrecursive: do not setup a recursive bind mount. By default it is recursive. Options specific to tmpfs: diff --git a/docs/podman-exec.1.md b/docs/podman-exec.1.md index a6a58df01..07d24e295 100644 --- a/docs/podman-exec.1.md +++ b/docs/podman-exec.1.md @@ -52,6 +52,34 @@ The default working directory for running binaries within a container is the roo The image developer can set a different default with the WORKDIR instruction, which can be overridden when creating the container. +## Exit Status + +The exit code from `podman exec` gives information about why the command within the container failed to run or why it exited. When `podman exec` exits with a +non-zero code, the exit codes follow the `chroot` standard, see below: + +**_125_** if the error is with podman **_itself_** + + $ podman exec --foo ctrID /bin/sh; echo $? + Error: unknown flag: --foo + 125 + +**_126_** if the **_contained command_** cannot be invoked + + $ podman exec ctrID /etc; echo $? + Error: container_linux.go:346: starting container process caused "exec: \"/etc\": permission denied": OCI runtime error + 126 + +**_127_** if the **_contained command_** cannot be found + + $ podman exec ctrID foo; echo $? + Error: container_linux.go:346: starting container process caused "exec: \"foo\": executable file not found in $PATH": OCI runtime error + 127 + +**_Exit code_** of **_contained command_** otherwise + + $ podman exec ctrID /bin/sh -c 'exit 3' + # 3 + ## EXAMPLES $ podman exec -it ctrID ls diff --git a/docs/podman-generate-kube.1.md b/docs/podman-generate-kube.1.md index dd9068ef1..88d8e9627 100644 --- a/docs/podman-generate-kube.1.md +++ b/docs/podman-generate-kube.1.md @@ -14,6 +14,10 @@ Note that the generated Kubernetes YAML file can be used to re-run the deploymen ## OPTIONS: +**--filename**, **-f**=**filename** + +Output to the given file, instead of STDOUT. If the file already exists, `generate kube` will refuse to replace it and return an error. + **--service**, **-s** Generate a Kubernetes service object in addition to the Pods. Used to generate a Service specification for the corresponding Pod ouput. In particular, if the object has portmap bindings, the service specification will include a NodePort declaration to expose the service. A diff --git a/docs/podman-rm.1.md b/docs/podman-rm.1.md index c7ff23bdf..32a8c2943 100644 --- a/docs/podman-rm.1.md +++ b/docs/podman-rm.1.md @@ -30,6 +30,13 @@ to run containers such as CRI-O, the last started container could be from either The latest option is not supported on the remote client. +**--storage** + +Remove the container from the storage library only. +This is only possible with containers that are not present in libpod (cannot be seen by `podman ps`). +It is used to remove containers from `podman build` and `buildah`, and orphan containers which were only partially removed by `podman rm`. +The storage option conflicts with the **--all**, **--latest**, and **--volumes** options. + **--volumes**, **-v** Remove the volumes associated with the container. diff --git a/docs/podman-run.1.md b/docs/podman-run.1.md index 5bf7aeee8..123f7ee5f 100644 --- a/docs/podman-run.1.md +++ b/docs/podman-run.1.md @@ -471,6 +471,7 @@ Current supported mount TYPES are bind, and tmpfs. Options specific to bind: · bind-propagation: Z, z, shared, slave, private, rshared, rslave, or rprivate(default). See also mount(2). + . bind-nonrecursive: do not setup a recursive bind mount. By default it is recursive. Options specific to tmpfs: @@ -668,7 +669,7 @@ Signal to stop a container. Default is SIGTERM. Timeout (in seconds) to stop a container. Default is 10. -**--subuidname**=*name* +**--subgidname**=*name* Run the container in a new user namespace using the map with 'name' in the `/etc/subgid` file. If calling podman run as an unprivileged user, the user needs to have the right to use the mapping. See `subgid(5)`. @@ -902,28 +903,25 @@ the exit codes follow the `chroot` standard, see below: **_125_** if the error is with podman **_itself_** $ podman run --foo busybox; echo $? - # flag provided but not defined: --foo - See 'podman run --help'. - 125 + Error: unknown flag: --foo + 125 **_126_** if the **_contained command_** cannot be invoked $ podman run busybox /etc; echo $? - # exec: "/etc": permission denied - podman: Error response from daemon: Contained command could not be invoked - 126 + Error: container_linux.go:346: starting container process caused "exec: \"/etc\": permission denied": OCI runtime error + 126 **_127_** if the **_contained command_** cannot be found $ podman run busybox foo; echo $? - # exec: "foo": executable file not found in $PATH - podman: Error response from daemon: Contained command not found or does not exist - 127 + Error: container_linux.go:346: starting container process caused "exec: \"foo\": executable file not found in $PATH": OCI runtime error + 127 **_Exit code_** of **_contained command_** otherwise $ podman run busybox /bin/sh -c 'exit 3' - # 3 + 3 ## EXAMPLES diff --git a/docs/podman.1.md b/docs/podman.1.md index b6c0628ed..e1dc1d1f1 100644 --- a/docs/podman.1.md +++ b/docs/podman.1.md @@ -103,24 +103,21 @@ the exit codes follow the `chroot` standard, see below: **_125_** if the error is with podman **_itself_** $ podman run --foo busybox; echo $? - # flag provided but not defined: --foo - See 'podman run --help'. + Error: unknown flag: --foo 125 -**_126_** if executing a **_container command_** and the the **_command_** cannot be invoked +**_126_** if executing a **_contained command_** and the **_command_** cannot be invoked $ podman run busybox /etc; echo $? - # exec: "/etc": permission denied - podman: Error response from daemon: Contained command could not be invoked + Error: container_linux.go:346: starting container process caused "exec: \"/etc\": permission denied": OCI runtime error 126 -**_127_** if executing a **_container command_** and the the **_command_** cannot be found +**_127_** if executing a **_contained command_** and the **_command_** cannot be found $ podman run busybox foo; echo $? - # exec: "foo": executable file not found in $PATH - podman: Error response from daemon: Contained command not found or does not exist + Error: container_linux.go:346: starting container process caused "exec: \"foo\": executable file not found in $PATH": OCI runtime error 127 -**_Exit code_** of **_container command_** otherwise +**_Exit code_** of **_contained command_** otherwise $ podman run busybox /bin/sh -c 'exit 3' # 3 diff --git a/libpod.conf b/libpod.conf index ce6b95cda..45e955c36 100644 --- a/libpod.conf +++ b/libpod.conf @@ -106,6 +106,10 @@ num_locks = 2048 # Default OCI runtime runtime = "runc" +# List of the OCI runtimes that support --format=json. When json is supported +# libpod will use it for reporting nicer errors. +runtime_supports_json = ["runc"] + # Paths to look for a valid OCI runtime (runc, runv, etc) [runtimes] runc = [ diff --git a/libpod/container.go b/libpod/container.go index c8ab42fc3..68c4cd6b0 100644 --- a/libpod/container.go +++ b/libpod/container.go @@ -135,7 +135,6 @@ const ( // assume that their callers handled this requirement. Generally speaking, if a // function takes the container lock and accesses any part of state, it should // syncContainer() immediately after locking. -// ffjson: skip type Container struct { config *ContainerConfig @@ -161,7 +160,6 @@ type Container struct { // ContainerState contains the current state of the container // It is stored on disk in a tmpfs and recreated on reboot -// easyjson:json type ContainerState struct { // The current state of the running container State ContainerStatus `json:"state"` @@ -222,7 +220,6 @@ type ContainerState struct { } // ExecSession contains information on an active exec session -// easyjson:json type ExecSession struct { ID string `json:"id"` Command []string `json:"command"` @@ -232,7 +229,6 @@ type ExecSession struct { // ContainerConfig contains all information that was used to create the // container. It may not be changed once created. // It is stored, read-only, on disk -// easyjson:json type ContainerConfig struct { Spec *spec.Spec `json:"spec"` ID string `json:"id"` diff --git a/libpod/container_api.go b/libpod/container_api.go index 1894780de..0e877d04e 100644 --- a/libpod/container_api.go +++ b/libpod/container_api.go @@ -180,7 +180,7 @@ func (c *Container) StopWithTimeout(timeout uint) error { if c.state.State == ContainerStateConfigured || c.state.State == ContainerStateUnknown || c.state.State == ContainerStatePaused { - return errors.Wrapf(ErrCtrStateInvalid, "can only stop created, running, or stopped containers. %s in state %s", c.ID(), c.state.State.String()) + return errors.Wrapf(ErrCtrStateInvalid, "can only stop created, running, or stopped containers. %s is in state %s", c.ID(), c.state.State.String()) } if c.state.State == ContainerStateStopped || @@ -203,7 +203,7 @@ func (c *Container) Kill(signal uint) error { } if c.state.State != ContainerStateRunning { - return errors.Wrapf(ErrCtrStateInvalid, "can only kill running containers") + return errors.Wrapf(ErrCtrStateInvalid, "can only kill running containers. %s is in state %s", c.ID(), c.state.State.String()) } defer c.newContainerEvent(events.Kill) @@ -241,7 +241,7 @@ func (c *Container) Exec(tty, privileged bool, env, cmd []string, user, workDir // TODO can probably relax this once we track exec sessions if conState != ContainerStateRunning { - return errors.Errorf("cannot exec into container that is not running") + return errors.Wrapf(ErrCtrStateInvalid, "cannot exec into container that is not running") } if privileged || c.config.Privileged { capList = caps.GetAllCapabilities() diff --git a/libpod/container_inspect.go b/libpod/container_inspect.go index 8e34e7088..0a62ceb7c 100644 --- a/libpod/container_inspect.go +++ b/libpod/container_inspect.go @@ -1,12 +1,11 @@ package libpod import ( - "strings" "time" "github.com/containers/libpod/libpod/driver" "github.com/cri-o/ocicni/pkg/ocicni" - specs "github.com/opencontainers/runtime-spec/specs-go" + spec "github.com/opencontainers/runtime-spec/specs-go" "github.com/pkg/errors" "github.com/sirupsen/logrus" ) @@ -29,6 +28,7 @@ type InspectContainerData struct { HostnamePath string `json:"HostnamePath"` HostsPath string `json:"HostsPath"` StaticDir string `json:"StaticDir"` + OCIConfigPath string `json:"OCIConfigPath,omitempty"` LogPath string `json:"LogPath"` ConmonPidFile string `json:"ConmonPidFile"` Name string `json:"Name"` @@ -43,7 +43,7 @@ type InspectContainerData struct { GraphDriver *driver.Data `json:"GraphDriver"` SizeRw int64 `json:"SizeRw,omitempty"` SizeRootFs int64 `json:"SizeRootFs,omitempty"` - Mounts []specs.Mount `json:"Mounts"` + Mounts []*InspectMount `json:"Mounts"` Dependencies []string `json:"Dependencies"` NetworkSettings *InspectNetworkSettings `json:"NetworkSettings"` //TODO ExitCommand []string `json:"ExitCommand"` @@ -51,6 +51,35 @@ type InspectContainerData struct { IsInfra bool `json:"IsInfra"` } +// InspectMount provides a record of a single mount in a container. It contains +// fields for both named and normal volumes. Only user-specified volumes will be +// included, and tmpfs volumes are not included even if the user specified them. +type InspectMount struct { + // Whether the mount is a volume or bind mount. Allowed values are + // "volume" and "bind". + Type string `json:"Type"` + // The name of the volume. Empty for bind mounts. + Name string `json:"Name,omptempty"` + // The source directory for the volume. + Src string `json:"Source"` + // The destination directory for the volume. Specified as a path within + // the container, as it would be passed into the OCI runtime. + Dst string `json:"Destination"` + // The driver used for the named volume. Empty for bind mounts. + Driver string `json:"Driver"` + // Contains SELinux :z/:Z mount options. Unclear what, if anything, else + // goes in here. + Mode string `json:"Mode"` + // All remaining mount options. Additional data, not present in the + // original output. + Options []string `json:"Options"` + // Whether the volume is read-write + RW bool `json:"RW"` + // Mount propagation for the mount. Can be empty if not specified, but + // is always printed - no omitempty. + Propagation string `json:"Propagation"` +} + // InspectContainerState provides a detailed record of a container's current // state. It is returned as part of InspectContainerData. // As with InspectContainerData, many portions of this struct are matched to @@ -148,34 +177,24 @@ func (c *Container) getContainerInspectData(size bool, driverData *driver.Data) execIDs = append(execIDs, id) } - if c.state.BindMounts == nil { - c.state.BindMounts = make(map[string]string) - } - resolvPath := "" - if getPath, ok := c.state.BindMounts["/etc/resolv.conf"]; ok { - resolvPath = getPath - } - hostsPath := "" - if getPath, ok := c.state.BindMounts["/etc/hosts"]; ok { - hostsPath = getPath - } - hostnamePath := "" - if getPath, ok := c.state.BindMounts["/etc/hostname"]; ok { - hostnamePath = getPath + if c.state.BindMounts != nil { + if getPath, ok := c.state.BindMounts["/etc/resolv.conf"]; ok { + resolvPath = getPath + } + if getPath, ok := c.state.BindMounts["/etc/hosts"]; ok { + hostsPath = getPath + } + if getPath, ok := c.state.BindMounts["/etc/hostname"]; ok { + hostnamePath = getPath + } } - var mounts []specs.Mount - for i, mnt := range spec.Mounts { - mounts = append(mounts, mnt) - // We only want to show the name of the named volume in the inspect - // output, so split the path and get the name out of it. - if strings.Contains(mnt.Source, c.runtime.config.VolumePath) { - split := strings.Split(mnt.Source[len(c.runtime.config.VolumePath)+1:], "/") - mounts[i].Source = split[0] - } + mounts, err := c.getInspectMounts() + if err != nil { + return nil, err } data := &InspectContainerData{ @@ -242,8 +261,12 @@ func (c *Container) getContainerInspectData(size bool, driverData *driver.Data) IsInfra: c.IsInfra(), } + if c.state.ConfigPath != "" { + data.OCIConfigPath = c.state.ConfigPath + } + if c.config.HealthCheckConfig != nil { - // This container has a healthcheck defined in it; we need to add it's state + // This container has a healthcheck defined in it; we need to add it's state healthCheckState, err := c.GetHealthCheckLog() if err != nil { // An error here is not considered fatal; no health state will be displayed @@ -275,3 +298,106 @@ func (c *Container) getContainerInspectData(size bool, driverData *driver.Data) } return data, nil } + +// Get inspect-formatted mounts list. +// Only includes user-specified mounts. Only includes bind mounts and named +// volumes, not tmpfs volumes. +func (c *Container) getInspectMounts() ([]*InspectMount, error) { + inspectMounts := []*InspectMount{} + + // No mounts, return early + if len(c.config.UserVolumes) == 0 { + return inspectMounts, nil + } + + // We need to parse all named volumes and mounts into maps, so we don't + // end up with repeated lookups for each user volume. + // Map destination to struct, as destination is what is stored in + // UserVolumes. + namedVolumes := make(map[string]*ContainerNamedVolume) + mounts := make(map[string]spec.Mount) + for _, namedVol := range c.config.NamedVolumes { + namedVolumes[namedVol.Dest] = namedVol + } + for _, mount := range c.config.Spec.Mounts { + mounts[mount.Destination] = mount + } + + for _, vol := range c.config.UserVolumes { + // We need to look up the volumes. + // First: is it a named volume? + if volume, ok := namedVolumes[vol]; ok { + mountStruct := new(InspectMount) + mountStruct.Type = "volume" + mountStruct.Dst = volume.Dest + mountStruct.Name = volume.Name + + // For src and driver, we need to look up the named + // volume. + volFromDB, err := c.runtime.state.Volume(volume.Name) + if err != nil { + return nil, errors.Wrapf(err, "error looking up volume %s in container %s config", volume.Name, c.ID()) + } + mountStruct.Driver = volFromDB.Driver() + mountStruct.Src = volFromDB.MountPoint() + + parseMountOptionsForInspect(volume.Options, mountStruct) + + inspectMounts = append(inspectMounts, mountStruct) + } else if mount, ok := mounts[vol]; ok { + // It's a mount. + // Is it a tmpfs? If so, discard. + if mount.Type == "tmpfs" { + continue + } + + mountStruct := new(InspectMount) + mountStruct.Type = "bind" + mountStruct.Src = mount.Source + mountStruct.Dst = mount.Destination + + parseMountOptionsForInspect(mount.Options, mountStruct) + + inspectMounts = append(inspectMounts, mountStruct) + } + // We couldn't find a mount. Log a warning. + logrus.Warnf("Could not find mount at destination %q when building inspect output for container %s", vol, c.ID()) + } + + return inspectMounts, nil +} + +// Parse mount options so we can populate them in the mount structure. +// The mount passed in will be modified. +func parseMountOptionsForInspect(options []string, mount *InspectMount) { + isRW := true + mountProp := "" + zZ := "" + otherOpts := []string{} + + // Some of these may be overwritten if the user passes us garbage opts + // (for example, [ro,rw]) + // We catch these on the Podman side, so not a problem there, but other + // users of libpod who do not properly validate mount options may see + // this. + // Not really worth dealing with on our end - garbage in, garbage out. + for _, opt := range options { + switch opt { + case "ro": + isRW = false + case "rw": + // Do nothing, silently discard + case "shared", "slave", "private", "rshared", "rslave", "rprivate": + mountProp = opt + case "z", "Z": + zZ = opt + default: + otherOpts = append(otherOpts, opt) + } + } + + mount.RW = isRW + mount.Propagation = mountProp + mount.Mode = zZ + mount.Options = otherOpts +} diff --git a/libpod/container_internal_linux.go b/libpod/container_internal_linux.go index a88b0a6d5..55cc5089b 100644 --- a/libpod/container_internal_linux.go +++ b/libpod/container_internal_linux.go @@ -1000,7 +1000,7 @@ func (c *Container) generateResolvConf() (string, error) { nameservers := resolvconf.GetNameservers(resolv.Content) // slirp4netns has a built in DNS server. if c.config.NetMode.IsSlirp4netns() { - nameservers = append(nameservers, "10.0.2.3") + nameservers = append([]string{"10.0.2.3"}, nameservers...) } if len(c.config.DNSServer) > 0 { // We store DNS servers as net.IP, so need to convert to string diff --git a/libpod/errors.go b/libpod/errors.go index dd82d0796..cca0935ec 100644 --- a/libpod/errors.go +++ b/libpod/errors.go @@ -96,4 +96,7 @@ var ( // ErrOSNotSupported indicates the function is not available on the particular // OS. ErrOSNotSupported = errors.New("No support for this OS yet") + + // ErrOCIRuntime indicates an error from the OCI runtime + ErrOCIRuntime = errors.New("OCI runtime error") ) diff --git a/libpod/oci.go b/libpod/oci.go index 7138108c5..dcb72fc1b 100644 --- a/libpod/oci.go +++ b/libpod/oci.go @@ -58,6 +58,7 @@ type OCIRuntime struct { logSizeMax int64 noPivot bool reservePorts bool + supportsJSON bool } // syncInfo is used to return data from monitor process to daemon @@ -66,8 +67,16 @@ type syncInfo struct { Message string `json:"message,omitempty"` } +// ociError is used to parse the OCI runtime JSON log. It is not part of the +// OCI runtime specifications, it follows what runc does +type ociError struct { + Level string `json:"level,omitempty"` + Time string `json:"time,omitempty"` + Msg string `json:"msg,omitempty"` +} + // Make a new OCI runtime with provided options -func newOCIRuntime(oruntime OCIRuntimePath, conmonPath string, conmonEnv []string, cgroupManager string, tmpDir string, logSizeMax int64, noPivotRoot bool, reservePorts bool) (*OCIRuntime, error) { +func newOCIRuntime(oruntime OCIRuntimePath, conmonPath string, conmonEnv []string, cgroupManager string, tmpDir string, logSizeMax int64, noPivotRoot bool, reservePorts bool, supportsJSON bool) (*OCIRuntime, error) { runtime := new(OCIRuntime) runtime.name = oruntime.Name runtime.path = oruntime.Paths[0] @@ -78,6 +87,7 @@ func newOCIRuntime(oruntime OCIRuntimePath, conmonPath string, conmonEnv []strin runtime.logSizeMax = logSizeMax runtime.noPivot = noPivotRoot runtime.reservePorts = reservePorts + runtime.supportsJSON = supportsJSON runtime.exitsDir = filepath.Join(runtime.tmpDir, "exits") runtime.socketsDir = filepath.Join(runtime.tmpDir, "socket") diff --git a/libpod/oci_linux.go b/libpod/oci_linux.go index 7c1c18052..9bbefdb06 100644 --- a/libpod/oci_linux.go +++ b/libpod/oci_linux.go @@ -6,6 +6,7 @@ import ( "bufio" "bytes" "fmt" + "io/ioutil" "os" "os/exec" "path/filepath" @@ -208,6 +209,9 @@ func (r *OCIRuntime) createOCIContainer(ctr *Container, cgroupParent string, res defer parentPipe.Close() defer parentStartPipe.Close() + ociLog := filepath.Join(ctr.state.RunDir, "oci-log") + logLevel := logrus.GetLevel() + args := []string{} if r.cgroupManager == SystemdCgroupsManager { args = append(args, "-s") @@ -219,6 +223,9 @@ func (r *OCIRuntime) createOCIContainer(ctr *Container, cgroupParent string, res args = append(args, "-b", ctr.bundlePath()) args = append(args, "-p", filepath.Join(ctr.state.RunDir, "pidfile")) args = append(args, "--exit-dir", r.exitsDir) + if logLevel != logrus.DebugLevel && r.supportsJSON { + args = append(args, "--runtime-arg", "--log-format=json", "--runtime-arg", "--log", fmt.Sprintf("--runtime-arg=%s", ociLog)) + } if ctr.config.ConmonPidFile != "" { args = append(args, "--conmon-pidfile", ctr.config.ConmonPidFile) } @@ -248,7 +255,6 @@ func (r *OCIRuntime) createOCIContainer(ctr *Container, cgroupParent string, res args = append(args, "--no-pivot") } - logLevel := logrus.GetLevel() args = append(args, "--log-level", logLevel.String()) if logLevel == logrus.DebugLevel { @@ -417,8 +423,18 @@ func (r *OCIRuntime) createOCIContainer(ctr *Container, cgroupParent string, res } logrus.Debugf("Received container pid: %d", ss.si.Pid) if ss.si.Pid == -1 { + if r.supportsJSON { + data, err := ioutil.ReadFile(ociLog) + if err == nil { + var ociErr ociError + if err := json.Unmarshal(data, &ociErr); err == nil { + return errors.Wrapf(ErrOCIRuntime, "%s", strings.Trim(ociErr.Msg, "\n")) + } + } + } + // If we failed to parse the JSON errors, then print the output as it is if ss.si.Message != "" { - return errors.Wrapf(ErrInternal, "container create failed: %s", ss.si.Message) + return errors.Wrapf(ErrOCIRuntime, "%s", ss.si.Message) } return errors.Wrapf(ErrInternal, "container create failed") } diff --git a/libpod/options.go b/libpod/options.go index 20aa51981..cdac09654 100644 --- a/libpod/options.go +++ b/libpod/options.go @@ -1127,6 +1127,8 @@ func WithGroups(groups []string) CtrCreateOption { // These are not added to the container's spec, but will instead be used during // commit to populate the volumes of the new image, and to trigger some OCI // hooks that are only added if volume mounts are present. +// Furthermore, they are used in the output of inspect, to filter volumes - +// only volumes included in this list will be included in the output. // Unless explicitly set, committed images will have no volumes. // The given volumes slice must not be nil. func WithUserVolumes(volumes []string) CtrCreateOption { diff --git a/libpod/pod.go b/libpod/pod.go index 4ce697402..c319c449f 100644 --- a/libpod/pod.go +++ b/libpod/pod.go @@ -18,7 +18,6 @@ import ( // assume their callers handled this requirement. Generally speaking, if a // function takes the pod lock and accesses any part of state, it should // updatePod() immediately after locking. -// ffjson: skip // Pod represents a group of containers that may share namespaces type Pod struct { config *PodConfig @@ -30,7 +29,6 @@ type Pod struct { } // PodConfig represents a pod's static configuration -// easyjson:json type PodConfig struct { ID string `json:"id"` Name string `json:"name"` @@ -66,7 +64,6 @@ type PodConfig struct { } // podState represents a pod's state -// easyjson:json type podState struct { // CgroupPath is the path to the pod's CGroup CgroupPath string `json:"cgroupPath"` @@ -77,7 +74,6 @@ type podState struct { // PodInspect represents the data we want to display for // podman pod inspect -// easyjson:json type PodInspect struct { Config *PodConfig State *PodInspectState @@ -85,14 +81,12 @@ type PodInspect struct { } // PodInspectState contains inspect data on the pod's state -// easyjson:json type PodInspectState struct { CgroupPath string `json:"cgroupPath"` InfraContainerID string `json:"infraContainerID"` } // PodContainerInfo keeps information on a container in a pod -// easyjson:json type PodContainerInfo struct { ID string `json:"id"` State string `json:"state"` diff --git a/libpod/runtime.go b/libpod/runtime.go index 098607b63..2c50fce85 100644 --- a/libpod/runtime.go +++ b/libpod/runtime.go @@ -42,11 +42,20 @@ const ( SQLiteStateStore RuntimeStateStore = iota // BoltDBStateStore is a state backed by a BoltDB database BoltDBStateStore RuntimeStateStore = iota +) + +var ( + // InstallPrefix is the prefix where podman will be installed. + // It can be overridden at build time. + installPrefix = "/usr/local" + // EtcDir is the sysconfdir where podman should look for system config files. + // It can be overridden at build time. + etcDir = "/etc" // SeccompDefaultPath defines the default seccomp path - SeccompDefaultPath = "/usr/share/containers/seccomp.json" + SeccompDefaultPath = installPrefix + "/share/containers/seccomp.json" // SeccompOverridePath if this exists it overrides the default seccomp path - SeccompOverridePath = "/etc/crio/seccomp.json" + SeccompOverridePath = etcDir + "/crio/seccomp.json" // ConfigPath is the path to the libpod configuration file // This file is loaded to replace the builtin default config before @@ -54,11 +63,11 @@ const ( // If it is not present, the builtin default config is used instead // This path can be overridden when the runtime is created by using // NewRuntimeFromConfig() instead of NewRuntime() - ConfigPath = "/usr/share/containers/libpod.conf" + ConfigPath = installPrefix + "/share/containers/libpod.conf" // OverrideConfigPath is the path to an override for the default libpod // configuration file. If OverrideConfigPath exists, it will be used in // place of the configuration file pointed to by ConfigPath. - OverrideConfigPath = "/etc/containers/libpod.conf" + OverrideConfigPath = etcDir + "/containers/libpod.conf" // DefaultInfraImage to use for infra container DefaultInfraImage = "k8s.gcr.io/pause:3.1" @@ -151,6 +160,8 @@ type RuntimeConfig struct { OCIRuntime string `toml:"runtime"` // OCIRuntimes are the set of configured OCI runtimes (default is runc) OCIRuntimes map[string][]string `toml:"runtimes"` + // RuntimeSupportsJSON is the list of the OCI runtimes that support --format=json + RuntimeSupportsJSON []string `toml:"runtime_supports_json"` // RuntimePath is the path to OCI runtime binary for launching // containers. // The first path pointing to a valid file will be used @@ -298,7 +309,7 @@ func defaultRuntimeConfig() (RuntimeConfig, error) { TmpDir: "", MaxLogSize: -1, NoPivotRoot: false, - CNIConfigDir: "/etc/cni/net.d/", + CNIConfigDir: etcDir + "/cni/net.d/", CNIPluginDir: []string{"/usr/libexec/cni", "/usr/lib/cni", "/usr/local/lib/cni", "/opt/cni/bin"}, InfraCommand: DefaultInfraCommand, InfraImage: DefaultInfraImage, @@ -830,12 +841,21 @@ func makeRuntime(ctx context.Context, runtime *Runtime) (err error) { } } + supportsJSON := false + for _, r := range runtime.config.RuntimeSupportsJSON { + if r == runtime.config.OCIRuntime { + supportsJSON = true + break + } + } + // Make an OCI runtime to perform container operations ociRuntime, err := newOCIRuntime(runtime.ociRuntimePath, runtime.conmonPath, runtime.config.ConmonEnvVars, runtime.config.CgroupManager, runtime.config.TmpDir, runtime.config.MaxLogSize, runtime.config.NoPivotRoot, - runtime.config.EnablePortReservation) + runtime.config.EnablePortReservation, + supportsJSON) if err != nil { return err } diff --git a/libpod/runtime_cstorage.go b/libpod/runtime_cstorage.go new file mode 100644 index 000000000..569f63322 --- /dev/null +++ b/libpod/runtime_cstorage.go @@ -0,0 +1,118 @@ +package libpod + +import ( + "github.com/containers/storage" + "github.com/pkg/errors" + "github.com/sirupsen/logrus" +) + +// StorageContainer represents a container present in c/storage but not in +// libpod. +type StorageContainer struct { + ID string + Names []string + PresentInLibpod bool +} + +// ListStorageContainers lists all containers visible to c/storage. +func (r *Runtime) ListStorageContainers() ([]*StorageContainer, error) { + r.lock.RLock() + defer r.lock.RUnlock() + + finalCtrs := []*StorageContainer{} + + ctrs, err := r.store.Containers() + if err != nil { + return nil, err + } + + for _, ctr := range ctrs { + storageCtr := new(StorageContainer) + storageCtr.ID = ctr.ID + storageCtr.Names = ctr.Names + + // Look up if container is in state + hasCtr, err := r.state.HasContainer(ctr.ID) + if err != nil { + return nil, errors.Wrapf(err, "error looking up container %s in state", ctr.ID) + } + + storageCtr.PresentInLibpod = hasCtr + + finalCtrs = append(finalCtrs, storageCtr) + } + + return finalCtrs, nil +} + +// RemoveStorageContainer removes a container from c/storage. +// The container WILL NOT be removed if it exists in libpod. +// Accepts ID or full name of container. +// If force is set, the container will be unmounted first to ensure removal. +func (r *Runtime) RemoveStorageContainer(idOrName string, force bool) error { + r.lock.Lock() + defer r.lock.Unlock() + + targetID, err := r.store.Lookup(idOrName) + if err != nil { + if err == storage.ErrLayerUnknown { + return errors.Wrapf(ErrNoSuchCtr, "no container with ID or name %q found", idOrName) + } + return errors.Wrapf(err, "error looking up container %q", idOrName) + } + + // Lookup returns an ID but it's not guaranteed to be a container ID. + // So we can still error here. + ctr, err := r.store.Container(targetID) + if err != nil { + if err == storage.ErrContainerUnknown { + return errors.Wrapf(ErrNoSuchCtr, "%q does not refer to a container", idOrName) + } + return errors.Wrapf(err, "error retrieving container %q", idOrName) + } + + // Error out if the container exists in libpod + exists, err := r.state.HasContainer(ctr.ID) + if err != nil { + return err + } + if exists { + return errors.Wrapf(ErrCtrExists, "refusing to remove %q as it exists in libpod as container %s", idOrName, ctr.ID) + } + + if !force { + timesMounted, err := r.store.Mounted(ctr.ID) + if err != nil { + if err == storage.ErrContainerUnknown { + // Container was removed from under us. + // It's gone, so don't bother erroring. + logrus.Warnf("Storage for container %s already removed", ctr.ID) + return nil + } + return errors.Wrapf(err, "error looking up container %q mounts", idOrName) + } + if timesMounted > 0 { + return errors.Wrapf(ErrCtrStateInvalid, "container %q is mounted and cannot be removed without using force", idOrName) + } + } else { + if _, err := r.store.Unmount(ctr.ID, true); err != nil { + if err == storage.ErrContainerUnknown { + // Container again gone, no error + logrus.Warnf("Storage for container %s already removed", ctr.ID) + return nil + } + return errors.Wrapf(err, "error unmounting container %q", idOrName) + } + } + + if err := r.store.DeleteContainer(ctr.ID); err != nil { + if err == storage.ErrContainerUnknown { + // Container again gone, no error + logrus.Warnf("Storage for container %s already removed", ctr.ID) + return nil + } + return errors.Wrapf(err, "error removing storage for container %q", idOrName) + } + + return nil +} diff --git a/libpod/runtime_ctr.go b/libpod/runtime_ctr.go index cf1f5701d..0871b83a7 100644 --- a/libpod/runtime_ctr.go +++ b/libpod/runtime_ctr.go @@ -9,9 +9,7 @@ import ( "time" "github.com/containers/libpod/libpod/events" - "github.com/containers/libpod/libpod/image" "github.com/containers/libpod/pkg/rootless" - "github.com/containers/storage" "github.com/containers/storage/pkg/stringid" spec "github.com/opencontainers/runtime-spec/specs-go" "github.com/opencontainers/runtime-tools/generate" @@ -614,16 +612,3 @@ func (r *Runtime) GetLatestContainer() (*Container, error) { } return ctrs[lastCreatedIndex], nil } - -// RemoveContainersFromStorage attempt to remove containers from storage that do not exist in libpod database -func (r *Runtime) RemoveContainersFromStorage(ctrs []string) { - for _, i := range ctrs { - // if the container does not exist in database, attempt to remove it from storage - if _, err := r.LookupContainer(i); err != nil && errors.Cause(err) == image.ErrNoSuchCtr { - r.storageService.UnmountContainerImage(i, true) - if err := r.storageService.DeleteContainer(i); err != nil && errors.Cause(err) != storage.ErrContainerUnknown { - logrus.Errorf("Failed to remove container %q from storage: %s", i, err) - } - } - } -} diff --git a/libpod/volume.go b/libpod/volume.go index 0b37d44ef..9ed2ff087 100644 --- a/libpod/volume.go +++ b/libpod/volume.go @@ -10,7 +10,6 @@ type Volume struct { } // VolumeConfig holds the volume's config information -//easyjson:json type VolumeConfig struct { // Name of the volume Name string `json:"name"` diff --git a/pkg/adapter/containers.go b/pkg/adapter/containers.go index 29297fbd5..40b1e6b43 100644 --- a/pkg/adapter/containers.go +++ b/pkg/adapter/containers.go @@ -190,12 +190,18 @@ func (r *LocalRuntime) RemoveContainers(ctx context.Context, cli *cliconfig.RmVa } logrus.Debugf("Setting maximum rm workers to %d", maxWorkers) + if cli.Storage { + for _, ctr := range cli.InputArgs { + if err := r.RemoveStorageContainer(ctr, cli.Force); err != nil { + failures[ctr] = err + } + ok = append(ok, ctr) + } + return ok, failures, nil + } + ctrs, err := shortcuts.GetContainersByContext(cli.All, cli.Latest, cli.InputArgs, r.Runtime) if err != nil { - // Force may be used to remove containers no longer found in the database - if cli.Force && len(cli.InputArgs) > 0 && errors.Cause(err) == libpod.ErrNoSuchCtr { - r.RemoveContainersFromStorage(cli.InputArgs) - } return ok, failures, err } diff --git a/pkg/apparmor/apparmor_linux.go b/pkg/apparmor/apparmor_linux.go index 2c5022c1f..0d01f41e9 100644 --- a/pkg/apparmor/apparmor_linux.go +++ b/pkg/apparmor/apparmor_linux.go @@ -225,8 +225,13 @@ func CheckProfileAndLoadDefault(name string) (string, error) { } } - if name != "" && !runcaa.IsEnabled() { - return "", fmt.Errorf("profile %q specified but AppArmor is disabled on the host", name) + // Check if AppArmor is disabled and error out if a profile is to be set. + if !runcaa.IsEnabled() { + if name == "" { + return "", nil + } else { + return "", fmt.Errorf("profile %q specified but AppArmor is disabled on the host", name) + } } // If the specified name is not empty or is not a default libpod one, diff --git a/pkg/registries/registries.go b/pkg/registries/registries.go index 5c4ecd020..de63dcbf1 100644 --- a/pkg/registries/registries.go +++ b/pkg/registries/registries.go @@ -44,17 +44,7 @@ func getRegistries() ([]sysregistriesv2.Registry, error) { // GetRegistries obtains the list of search registries defined in the global registries file. func GetRegistries() ([]string, error) { - var searchRegistries []string - registries, err := getRegistries() - if err != nil { - return nil, err - } - for _, reg := range registries { - if reg.Search { - searchRegistries = append(searchRegistries, reg.Location) - } - } - return searchRegistries, nil + return sysregistriesv2.UnqualifiedSearchRegistries(&types.SystemContext{SystemRegistriesConfPath: SystemRegistriesConfPath()}) } // GetBlockedRegistries obtains the list of blocked registries defined in the global registries file. @@ -66,7 +56,7 @@ func GetBlockedRegistries() ([]string, error) { } for _, reg := range registries { if reg.Blocked { - blockedRegistries = append(blockedRegistries, reg.Location) + blockedRegistries = append(blockedRegistries, reg.Prefix) } } return blockedRegistries, nil @@ -81,7 +71,7 @@ func GetInsecureRegistries() ([]string, error) { } for _, reg := range registries { if reg.Insecure { - insecureRegistries = append(insecureRegistries, reg.Location) + insecureRegistries = append(insecureRegistries, reg.Prefix) } } return insecureRegistries, nil diff --git a/pkg/spec/createconfig.go b/pkg/spec/createconfig.go index ed8036a54..a8413d6c7 100644 --- a/pkg/spec/createconfig.go +++ b/pkg/spec/createconfig.go @@ -162,6 +162,10 @@ func (c *CreateConfig) createExitCommand(runtime *libpod.Runtime) ([]string, err if config.StorageConfig.GraphDriverName != "" { command = append(command, []string{"--storage-driver", config.StorageConfig.GraphDriverName}...) } + for _, opt := range config.StorageConfig.GraphDriverOptions { + command = append(command, []string{"--storage-opt", opt}...) + } + if c.Syslog { command = append(command, "--syslog", "true") } diff --git a/pkg/spec/storage.go b/pkg/spec/storage.go index e221b5cb5..283585ef8 100644 --- a/pkg/spec/storage.go +++ b/pkg/spec/storage.go @@ -384,7 +384,7 @@ func (config *CreateConfig) getMounts() (map[string]spec.Mount, map[string]*libp } finalNamedVolumes[volume.Dest] = volume default: - return nil, nil, errors.Errorf("invalid fylesystem type %q", kv[1]) + return nil, nil, errors.Errorf("invalid filesystem type %q", kv[1]) } } @@ -403,6 +403,8 @@ func getBindMount(args []string) (spec.Mount, error) { for _, val := range args { kv := strings.Split(val, "=") switch kv[0] { + case "bind-nonrecursive": + newMount.Options = append(newMount.Options, "bind") case "ro", "nosuid", "nodev", "noexec": // TODO: detect duplication of these options. // (Is this necessary?) @@ -574,7 +576,7 @@ func ValidateVolumeCtrDir(ctrDir string) error { // ValidateVolumeOpts validates a volume's options func ValidateVolumeOpts(options []string) error { - var foundRootPropagation, foundRWRO, foundLabelChange int + var foundRootPropagation, foundRWRO, foundLabelChange, bindType int for _, opt := range options { switch opt { case "rw", "ro": @@ -592,6 +594,11 @@ func ValidateVolumeOpts(options []string) error { if foundRootPropagation > 1 { return errors.Errorf("invalid options %q, can only specify 1 '[r]shared', '[r]private' or '[r]slave' option", strings.Join(options, ", ")) } + case "bind", "rbind": + bindType++ + if bindType > 1 { + return errors.Errorf("invalid options %q, can only specify 1 '[r]bind' option", strings.Join(options, ", ")) + } default: return errors.Errorf("invalid option type %q", opt) } diff --git a/pkg/util/mountOpts.go b/pkg/util/mountOpts.go index 59459807c..489e7eeef 100644 --- a/pkg/util/mountOpts.go +++ b/pkg/util/mountOpts.go @@ -17,10 +17,19 @@ var ( // sensible and follow convention. func ProcessOptions(options []string) []string { var ( - foundrw, foundro bool - rootProp string + foundbind, foundrw, foundro bool + rootProp string ) - options = append(options, "rbind") + for _, opt := range options { + switch opt { + case "bind", "rbind": + foundbind = true + break + } + } + if !foundbind { + options = append(options, "rbind") + } for _, opt := range options { switch opt { case "rw": diff --git a/pkg/util/utils.go b/pkg/util/utils.go index a074f276c..61cdbbf38 100644 --- a/pkg/util/utils.go +++ b/pkg/util/utils.go @@ -99,7 +99,10 @@ func GetImageConfig(changes []string) (v1.ImageConfig, error) { var st struct{} exposedPorts[pair[1]] = st case "ENV": - env = append(env, pair[1]) + if len(pair) < 3 { + return v1.ImageConfig{}, errors.Errorf("no value given for environment variable %q", pair[1]) + } + env = append(env, strings.Join(pair[1:], "=")) case "ENTRYPOINT": entrypoint = append(entrypoint, pair[1]) case "CMD": diff --git a/test/e2e/cp_test.go b/test/e2e/cp_test.go index 597d0aef7..f7596d77d 100644 --- a/test/e2e/cp_test.go +++ b/test/e2e/cp_test.go @@ -184,4 +184,25 @@ var _ = Describe("Podman cp", func() { _, err = os.Stat("/tmp/cp_test.txt") Expect(err).To(Not(BeNil())) }) + It("podman cp volume", func() { + session := podmanTest.Podman([]string{"volume", "create", "data"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + + session = podmanTest.Podman([]string{"create", "-v", "data:/data", "--name", "container1", ALPINE}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + + err = ioutil.WriteFile("cp_vol", []byte("copy to the volume"), 0644) + if err != nil { + os.Exit(1) + } + session = podmanTest.Podman([]string{"cp", "cp_vol", "container1" + ":/data/cp_vol1"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + + session = podmanTest.Podman([]string{"cp", "container1" + ":/data/cp_vol1", "cp_vol2"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + }) }) diff --git a/test/e2e/generate_kube_test.go b/test/e2e/generate_kube_test.go index 95d46476d..40cc534c2 100644 --- a/test/e2e/generate_kube_test.go +++ b/test/e2e/generate_kube_test.go @@ -4,6 +4,7 @@ package integration import ( "os" + "path/filepath" . "github.com/containers/libpod/test/utils" "github.com/ghodss/yaml" @@ -104,4 +105,43 @@ var _ = Describe("Podman generate kube", func() { _, err := yaml.Marshal(kube.OutputToString()) Expect(err).To(BeNil()) }) + + It("podman generate and reimport kube on pod", func() { + podName := "toppod" + _, rc, _ := podmanTest.CreatePod(podName) + Expect(rc).To(Equal(0)) + + session := podmanTest.Podman([]string{"create", "--pod", podName, "--name", "test1", ALPINE, "top"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + + session2 := podmanTest.Podman([]string{"create", "--pod", podName, "--name", "test2", ALPINE, "top"}) + session2.WaitWithDefaultTimeout() + Expect(session2.ExitCode()).To(Equal(0)) + + outputFile := filepath.Join(podmanTest.RunRoot, "pod.yaml") + kube := podmanTest.Podman([]string{"generate", "kube", "-f", outputFile, podName}) + kube.WaitWithDefaultTimeout() + Expect(kube.ExitCode()).To(Equal(0)) + + session3 := podmanTest.Podman([]string{"pod", "rm", "-af"}) + session3.WaitWithDefaultTimeout() + Expect(session3.ExitCode()).To(Equal(0)) + + session4 := podmanTest.Podman([]string{"play", "kube", outputFile}) + session4.WaitWithDefaultTimeout() + Expect(session4.ExitCode()).To(Equal(0)) + + session5 := podmanTest.Podman([]string{"pod", "ps"}) + session5.WaitWithDefaultTimeout() + Expect(session5.ExitCode()).To(Equal(0)) + Expect(session5.OutputToString()).To(ContainSubstring(podName)) + + session6 := podmanTest.Podman([]string{"ps", "-a"}) + session6.WaitWithDefaultTimeout() + Expect(session6.ExitCode()).To(Equal(0)) + psOut := session6.OutputToString() + Expect(psOut).To(ContainSubstring("test1")) + Expect(psOut).To(ContainSubstring("test2")) + }) }) diff --git a/test/e2e/logs_test.go b/test/e2e/logs_test.go index cc50c4d95..d17f60a5d 100644 --- a/test/e2e/logs_test.go +++ b/test/e2e/logs_test.go @@ -218,4 +218,21 @@ var _ = Describe("Podman logs", func() { Expect(results.ExitCode()).To(Equal(0)) Expect(len(results.OutputToStringArray())).To(Equal(3)) }) + + It("podman logs -f two lines", func() { + containerName := "logs-f-rm" + + logc := podmanTest.Podman([]string{"run", "--rm", "--name", containerName, "-dt", ALPINE, "sh", "-c", "echo podman; sleep 1; echo podman"}) + logc.WaitWithDefaultTimeout() + Expect(logc.ExitCode()).To(Equal(0)) + + results := podmanTest.Podman([]string{"logs", "-f", containerName}) + results.WaitWithDefaultTimeout() + Expect(results.ExitCode()).To(Equal(0)) + + // Verify that the cleanup process worked correctly and we can recreate a container with the same name + logc = podmanTest.Podman([]string{"run", "--rm", "--name", containerName, "-dt", ALPINE, "true"}) + logc.WaitWithDefaultTimeout() + Expect(logc.ExitCode()).To(Equal(0)) + }) }) diff --git a/test/e2e/run_test.go b/test/e2e/run_test.go index 31720ea04..3ba3c2bb3 100644 --- a/test/e2e/run_test.go +++ b/test/e2e/run_test.go @@ -659,6 +659,14 @@ USER mail` Expect(isSharedOnly).Should(BeTrue()) }) + It("podman run --mount type=bind,bind-nonrecursive", func() { + SkipIfRootless() + session := podmanTest.Podman([]string{"run", "--mount", "type=bind,bind-nonrecursive,slave,src=/,target=/host", fedoraMinimal, "findmnt", "-nR", "/host"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + Expect(len(session.OutputToStringArray())).To(Equal(1)) + }) + It("podman run --pod automatically", func() { session := podmanTest.Podman([]string{"run", "--pod", "new:foobar", ALPINE, "ls"}) session.WaitWithDefaultTimeout() diff --git a/test/e2e/runlabel_test.go b/test/e2e/runlabel_test.go index 5ef68603e..4e2cb501e 100644 --- a/test/e2e/runlabel_test.go +++ b/test/e2e/runlabel_test.go @@ -85,6 +85,7 @@ var _ = Describe("podman container runlabel", func() { }) It("podman container runlabel global options", func() { + Skip("Test nonfunctional for podman-in-podman testing") image := "podman-global-test:ls" podmanTest.BuildImage(GlobalDockerfile, image, "false") result := podmanTest.Podman([]string{"--syslog", "--log-level", "debug", "container", "runlabel", "RUN", image}) diff --git a/test/system/005-info.bats b/test/system/005-info.bats index 47c7a52fc..0068e35a9 100644 --- a/test/system/005-info.bats +++ b/test/system/005-info.bats @@ -14,7 +14,7 @@ Distribution: OCIRuntime:\\\s\\\+package: os: rootless: -insecure registries: +registries: store: GraphDriverName: GraphRoot: @@ -37,9 +37,7 @@ RunRoot: tests=" host.BuildahVersion | [0-9.] -host.Conmon.package | $expr_nvr host.Conmon.path | $expr_path -host.OCIRuntime.package | $expr_nvr host.OCIRuntime.path | $expr_path store.ConfigFile | $expr_path store.GraphDriverName | [a-z0-9]\\\+\\\$ diff --git a/test/system/120-load.bats b/test/system/120-load.bats index dedfe6172..f2dedb73f 100644 --- a/test/system/120-load.bats +++ b/test/system/120-load.bats @@ -87,6 +87,10 @@ verify_iid_and_name() { @test "podman load - will not read from tty" { + if [ ! -t 0 ]; then + skip "STDIN is not a tty" + fi + run_podman 125 load is "$output" \ "Error: cannot read from terminal. Use command-line redirection" \ diff --git a/vendor.conf b/vendor.conf index b7cb584f0..9b9044b66 100644 --- a/vendor.conf +++ b/vendor.conf @@ -15,7 +15,7 @@ github.com/containerd/cgroups 4994991857f9b0ae8dc439551e8bebdbb4bf66c1 github.com/containerd/continuity 004b46473808b3e7a4a3049c20e4376c91eb966d github.com/containernetworking/cni v0.7.0-rc2 github.com/containernetworking/plugins v0.7.4 -github.com/containers/image 2c0349c99af7d90694b3faa0e9bde404d407b145 +github.com/containers/image v2.0.0 github.com/vbauerster/mpb v3.3.4 github.com/mattn/go-isatty v0.0.4 github.com/VividCortex/ewma v1.1.1 @@ -93,7 +93,7 @@ k8s.io/api kubernetes-1.10.13-beta.0 https://github.com/kubernetes/api k8s.io/apimachinery kubernetes-1.10.13-beta.0 https://github.com/kubernetes/apimachinery k8s.io/client-go kubernetes-1.10.13-beta.0 https://github.com/kubernetes/client-go github.com/mrunalp/fileutils 7d4729fb36185a7c1719923406c9d40e54fb93c7 -github.com/containers/buildah v1.8.3 +github.com/containers/buildah v1.8.4 github.com/varlink/go 0f1d566d194b9d6d48e0d47c5e4d822628919066 # TODO: Gotty has not been updated since 2012. Can we find replacement? github.com/Nvveen/Gotty cd527374f1e5bff4938207604a14f2e38a9cf512 diff --git a/vendor/github.com/containers/buildah/add.go b/vendor/github.com/containers/buildah/add.go index 11bfb6a12..b03aa65b2 100644 --- a/vendor/github.com/containers/buildah/add.go +++ b/vendor/github.com/containers/buildah/add.go @@ -34,10 +34,14 @@ type AddAndCopyOptions struct { // If the sources include directory trees, Hasher will be passed // tar-format archives of the directory trees. Hasher io.Writer - // Exludes contents in the .dockerignore file + // Excludes is the contents of the .dockerignore file Excludes []string - // current directory on host + // The base directory for Excludes and data to copy in ContextDir string + // ID mapping options to use when contents to be copied are part of + // another container, and need ownerships to be mapped from the host to + // that container's values before copying them into the container. + IDMappingOptions *IDMappingOptions } // addURL copies the contents of the source URL to the destination. This is @@ -116,6 +120,12 @@ func (b *Builder) Add(destination string, extract bool, options AddAndCopyOption hostOwner := idtools.IDPair{UID: int(hostUID), GID: int(hostGID)} dest := mountPoint if destination != "" && filepath.IsAbs(destination) { + dir := filepath.Dir(destination) + if dir != "." && dir != "/" { + if err = idtools.MkdirAllAndChownNew(filepath.Join(dest, dir), 0755, hostOwner); err != nil { + return errors.Wrapf(err, "error creating directory %q", filepath.Join(dest, dir)) + } + } dest = filepath.Join(dest, destination) } else { if err = idtools.MkdirAllAndChownNew(filepath.Join(dest, b.WorkDir()), 0755, hostOwner); err != nil { @@ -146,8 +156,8 @@ func (b *Builder) Add(destination string, extract bool, options AddAndCopyOption if len(source) > 1 && (destfi == nil || !destfi.IsDir()) { return errors.Errorf("destination %q is not a directory", dest) } - copyFileWithTar := b.copyFileWithTar(&containerOwner, options.Hasher) - copyWithTar := b.copyWithTar(&containerOwner, options.Hasher) + copyFileWithTar := b.copyFileWithTar(options.IDMappingOptions, &containerOwner, options.Hasher) + copyWithTar := b.copyWithTar(options.IDMappingOptions, &containerOwner, options.Hasher) untarPath := b.untarPath(nil, options.Hasher) err = addHelper(excludes, extract, dest, destfi, hostOwner, options, copyFileWithTar, copyWithTar, untarPath, source...) if err != nil { diff --git a/vendor/github.com/containers/buildah/buildah.go b/vendor/github.com/containers/buildah/buildah.go index 56c8f088f..b97e048cc 100644 --- a/vendor/github.com/containers/buildah/buildah.go +++ b/vendor/github.com/containers/buildah/buildah.go @@ -26,7 +26,7 @@ const ( Package = "buildah" // Version for the Package. Bump version in contrib/rpm/buildah.spec // too. - Version = "1.8.3" + Version = "1.8.4" // The value we use to identify what type of information, currently a // serialized Builder structure, we are using as per-container state. // This should only be changed when we make incompatible changes to diff --git a/vendor/github.com/containers/buildah/chroot/run.go b/vendor/github.com/containers/buildah/chroot/run.go index d6e5a61ea..ae60d9bbe 100644 --- a/vendor/github.com/containers/buildah/chroot/run.go +++ b/vendor/github.com/containers/buildah/chroot/run.go @@ -220,7 +220,6 @@ func runUsingChrootMain() { var stdout io.Writer var stderr io.Writer fdDesc := make(map[int]string) - deferred := func() {} if options.Spec.Process.Terminal { // Create a pseudo-terminal -- open a copy of the master side. ptyMasterFd, err := unix.Open("/dev/ptmx", os.O_RDWR, 0600) @@ -370,12 +369,16 @@ func runUsingChrootMain() { return } } + if err := unix.SetNonblock(relays[unix.Stdin], true); err != nil { + logrus.Errorf("error setting %d to nonblocking: %v", relays[unix.Stdin], err) + } go func() { buffers := make(map[int]*bytes.Buffer) for _, writeFd := range relays { buffers[writeFd] = new(bytes.Buffer) } pollTimeout := -1 + stdinClose := false for len(relays) > 0 { fds := make([]unix.PollFd, 0, len(relays)) for fd := range relays { @@ -395,6 +398,9 @@ func runUsingChrootMain() { removeFds[int(rfd.Fd)] = struct{}{} } if rfd.Revents&unix.POLLIN == 0 { + if stdinClose && stdinCopy == nil { + continue + } continue } b := make([]byte, 8192) @@ -449,8 +455,19 @@ func runUsingChrootMain() { if buffer.Len() > 0 { pollTimeout = 100 } + if wfd == relays[unix.Stdin] && stdinClose && buffer.Len() == 0 { + stdinCopy.Close() + delete(relays, unix.Stdin) + } } for rfd := range removeFds { + if rfd == unix.Stdin { + buffer, found := buffers[relays[unix.Stdin]] + if found && buffer.Len() > 0 { + stdinClose = true + continue + } + } if !options.Spec.Process.Terminal && rfd == unix.Stdin { stdinCopy.Close() } @@ -461,7 +478,6 @@ func runUsingChrootMain() { // Set up mounts and namespaces, and run the parent subprocess. status, err := runUsingChroot(options.Spec, options.BundlePath, ctty, stdin, stdout, stderr, closeOnceRunning) - deferred() if err != nil { fmt.Fprintf(os.Stderr, "error running subprocess: %v\n", err) os.Exit(1) diff --git a/vendor/github.com/containers/buildah/image.go b/vendor/github.com/containers/buildah/image.go index 215920cc3..dc2d323d4 100644 --- a/vendor/github.com/containers/buildah/image.go +++ b/vendor/github.com/containers/buildah/image.go @@ -707,7 +707,7 @@ func (b *Builder) makeImageRef(options CommitOptions, exporting bool) (types.Ima exporting: exporting, squash: options.Squash, emptyLayer: options.EmptyLayer, - tarPath: b.tarPath(), + tarPath: b.tarPath(&b.IDMappingOptions), parent: parent, blobDirectory: options.BlobDirectory, preEmptyLayers: b.PrependedEmptyLayers, diff --git a/vendor/github.com/containers/buildah/imagebuildah/build.go b/vendor/github.com/containers/buildah/imagebuildah/build.go index 3665251cd..20d6715f5 100644 --- a/vendor/github.com/containers/buildah/imagebuildah/build.go +++ b/vendor/github.com/containers/buildah/imagebuildah/build.go @@ -27,7 +27,7 @@ import ( "github.com/containers/image/types" "github.com/containers/storage" "github.com/containers/storage/pkg/archive" - "github.com/cyphar/filepath-securejoin" + securejoin "github.com/cyphar/filepath-securejoin" docker "github.com/fsouza/go-dockerclient" v1 "github.com/opencontainers/image-spec/specs-go/v1" "github.com/opencontainers/runtime-spec/specs-go" @@ -210,7 +210,6 @@ type Executor struct { annotations []string onbuild []string layers bool - topLayers []string useCache bool removeIntermediateCtrs bool forceRmIntermediateCtrs bool @@ -515,26 +514,55 @@ func (s *StageExecutor) Copy(excludes []string, copies ...imagebuilder.Copy) err for _, src := range copy.Src { contextDir := s.executor.contextDir copyExcludes := excludes + var idMappingOptions *buildah.IDMappingOptions if strings.HasPrefix(src, "http://") || strings.HasPrefix(src, "https://") { sources = append(sources, src) } else if len(copy.From) > 0 { + var srcRoot string if other, ok := s.executor.stages[copy.From]; ok && other.index < s.index { - sources = append(sources, filepath.Join(other.mountPoint, src)) + srcRoot = other.mountPoint contextDir = other.mountPoint + idMappingOptions = &other.builder.IDMappingOptions } else if builder, ok := s.executor.containerMap[copy.From]; ok { - sources = append(sources, filepath.Join(builder.MountPoint, src)) + srcRoot = builder.MountPoint contextDir = builder.MountPoint + idMappingOptions = &builder.IDMappingOptions } else { return errors.Errorf("the stage %q has not been built", copy.From) } + srcSecure, err := securejoin.SecureJoin(srcRoot, src) + if err != nil { + return err + } + // If destination is a folder, we need to take extra care to + // ensure that files are copied with correct names (since + // resolving a symlink may result in a different name). + if hadFinalPathSeparator { + _, srcName := filepath.Split(src) + _, srcNameSecure := filepath.Split(srcSecure) + if srcName != srcNameSecure { + options := buildah.AddAndCopyOptions{ + Chown: copy.Chown, + ContextDir: contextDir, + Excludes: copyExcludes, + } + if err := s.builder.Add(filepath.Join(copy.Dest, srcName), copy.Download, options, srcSecure); err != nil { + return err + } + continue + } + } + sources = append(sources, srcSecure) + } else { sources = append(sources, filepath.Join(s.executor.contextDir, src)) copyExcludes = append(s.executor.excludes, excludes...) } options := buildah.AddAndCopyOptions{ - Chown: copy.Chown, - ContextDir: contextDir, - Excludes: copyExcludes, + Chown: copy.Chown, + ContextDir: contextDir, + Excludes: copyExcludes, + IDMappingOptions: idMappingOptions, } if err := s.builder.Add(copy.Dest, copy.Download, options, sources...); err != nil { return err @@ -860,9 +888,6 @@ func (s *StageExecutor) prepare(ctx context.Context, stage imagebuilder.Stage, f // Make this our "current" working container. s.mountPoint = mountPoint s.builder = builder - // Add the top layer of this image to b.topLayers so we can - // keep track of them when building with cached images. - s.executor.topLayers = append(s.executor.topLayers, builder.TopLayer) } logrus.Debugln("Container ID:", builder.ContainerID) return builder, nil @@ -967,7 +992,7 @@ func (s *StageExecutor) Execute(ctx context.Context, stage imagebuilder.Stage, b } logImageID := func(imgID string) { if s.executor.iidfile == "" { - fmt.Fprintf(s.executor.out, "--> %s\n", imgID) + fmt.Fprintf(s.executor.out, "%s\n", imgID) } } @@ -985,7 +1010,7 @@ func (s *StageExecutor) Execute(ctx context.Context, stage imagebuilder.Stage, b // We don't need to squash the base image, so just // reuse the base image. logCommit(s.output, -1) - if imgID, ref, err = s.copyExistingImage(ctx, s.builder.FromImageID, s.output); err != nil { + if imgID, ref, err = s.tagExistingImage(ctx, s.builder.FromImageID, s.output); err != nil { return "", nil, err } } @@ -1110,7 +1135,7 @@ func (s *StageExecutor) Execute(ctx context.Context, stage imagebuilder.Stage, b imgID = cacheID if commitName != "" { logCommit(commitName, i) - if imgID, ref, err = s.copyExistingImage(ctx, cacheID, commitName); err != nil { + if imgID, ref, err = s.tagExistingImage(ctx, cacheID, commitName); err != nil { return "", nil, err } logImageID(imgID) @@ -1179,8 +1204,8 @@ func (s *StageExecutor) Execute(ctx context.Context, stage imagebuilder.Stage, b return imgID, ref, nil } -// copyExistingImage creates a copy of an image already in the store -func (s *StageExecutor) copyExistingImage(ctx context.Context, cacheID, output string) (string, reference.Canonical, error) { +// tagExistingImage adds names to an image already in the store +func (s *StageExecutor) tagExistingImage(ctx context.Context, cacheID, output string) (string, reference.Canonical, error) { // If we don't need to attach a name to the image, just return the cache ID. if output == "" { return cacheID, nil, nil @@ -1247,11 +1272,11 @@ func (s *StageExecutor) layerExists(ctx context.Context, currNode *parser.Node, return "", errors.Wrapf(err, "error getting top layer info") } } - // If the parent of the top layer of an image is equal to the last entry in b.topLayers + // If the parent of the top layer of an image is equal to the current build image's top layer, // it means that this image is potentially a cached intermediate image from a previous // build. Next we double check that the history of this image is equivalent to the previous // lines in the Dockerfile up till the point we are at in the build. - if imageTopLayer == nil || imageTopLayer.Parent == s.executor.topLayers[len(s.executor.topLayers)-1] || imageTopLayer.ID == s.executor.topLayers[len(s.executor.topLayers)-1] { + if imageTopLayer == nil || (s.builder.TopLayer != "" && (imageTopLayer.Parent == s.builder.TopLayer || imageTopLayer.ID == s.builder.TopLayer)) { history, err := s.executor.getImageHistory(ctx, image.ID) if err != nil { return "", errors.Wrapf(err, "error getting history of %q", image.ID) @@ -1340,26 +1365,8 @@ func (b *Executor) historyMatches(baseHistory []v1.History, child *parser.Node, return false } } - instruction := child.Original - switch strings.ToUpper(child.Value) { - case "RUN": - instruction = instruction[4:] - buildArgs := b.getBuildArgs() - // If a previous image was built with some build-args but the new build process doesn't have any build-args - // specified, the command might be expanded differently, so compare the lengths of the old instruction with - // the current one. 11 is the length of "/bin/sh -c " that is used to run the run commands. - if buildArgs == "" && len(history[len(baseHistory)].CreatedBy) > len(instruction)+11 { - return false - } - // There are build-args, so check if anything with the build-args has changed - if buildArgs != "" && !strings.Contains(history[len(baseHistory)].CreatedBy, buildArgs) { - return false - } - fallthrough - default: - if !strings.Contains(history[len(baseHistory)].CreatedBy, instruction) { - return false - } + if history[len(baseHistory)].CreatedBy != b.getCreatedBy(child) { + return false } return true } @@ -1373,6 +1380,7 @@ func (b *Executor) getBuildArgs() string { buildArgs = append(buildArgs, k+"="+v) } } + sort.Strings(buildArgs) return strings.Join(buildArgs, " ") } @@ -1545,7 +1553,6 @@ func (s *StageExecutor) commit(ctx context.Context, ib *imagebuilder.Builder, cr options := buildah.CommitOptions{ Compression: s.executor.compression, SignaturePolicyPath: s.executor.signaturePolicyPath, - AdditionalTags: s.executor.additionalTags, ReportWriter: writer, PreferredManifestType: s.executor.outputFormat, SystemContext: s.executor.systemContext, @@ -1731,6 +1738,24 @@ func (b *Executor) Build(ctx context.Context, stages imagebuilder.Stages) (image fmt.Fprintf(b.out, "[Warning] one or more build args were not consumed: %v\n", unusedList) } + if len(b.additionalTags) > 0 { + if dest, err := b.resolveNameToImageRef(b.output); err == nil { + switch dest.Transport().Name() { + case is.Transport.Name(): + img, err := is.Transport.GetStoreImage(b.store, dest) + if err != nil { + return imageID, ref, errors.Wrapf(err, "error locating just-written image %q", transports.ImageName(dest)) + } + if err = util.AddImageNames(b.store, "", b.systemContext, img, b.additionalTags); err != nil { + return imageID, ref, errors.Wrapf(err, "error setting image names to %v", append(img.Names, b.additionalTags...)) + } + logrus.Debugf("assigned names %v to image %q", img.Names, img.ID) + default: + logrus.Warnf("don't know how to add tags to images stored in %q transport", dest.Transport().Name()) + } + } + } + if err := cleanup(); err != nil { return "", nil, err } diff --git a/vendor/github.com/containers/buildah/pkg/overlay/overlay.go b/vendor/github.com/containers/buildah/pkg/overlay/overlay.go index 31f0c2cec..14d29a25b 100644 --- a/vendor/github.com/containers/buildah/pkg/overlay/overlay.go +++ b/vendor/github.com/containers/buildah/pkg/overlay/overlay.go @@ -2,6 +2,7 @@ package overlay import ( "fmt" + "io/ioutil" "os" "path/filepath" "strings" @@ -15,13 +16,27 @@ import ( // MountTemp creates a subdir of the contentDir based on the source directory // from the source system. It then mounds up the source directory on to the // generated mount point and returns the mount point to the caller. -func MountTemp(store storage.Store, containerId, source, dest string, rootUID, rootGID int) (specs.Mount, string, error) { - mount := specs.Mount{} +func MountTemp(store storage.Store, containerId, source, dest string, rootUID, rootGID int) (mount specs.Mount, contentDir string, Err error) { - contentDir, err := store.ContainerDirectory(containerId) + containerDir, err := store.ContainerDirectory(containerId) if err != nil { return mount, "", err } + contentDir = filepath.Join(containerDir, "overlay") + if err := idtools.MkdirAllAs(contentDir, 0700, rootUID, rootGID); err != nil { + return mount, "", errors.Wrapf(err, "failed to create the overlay %s directory", contentDir) + } + + contentDir, err = ioutil.TempDir(contentDir, "") + if err != nil { + return mount, "", errors.Wrapf(err, "failed to create TempDir in the overlay %s directory", contentDir) + } + defer func() { + if Err != nil { + os.RemoveAll(contentDir) + } + }() + upperDir := filepath.Join(contentDir, "upper") workDir := filepath.Join(contentDir, "work") if err := idtools.MkdirAllAs(upperDir, 0700, rootUID, rootGID); err != nil { @@ -44,3 +59,13 @@ func MountTemp(store storage.Store, containerId, source, dest string, rootUID, r func RemoveTemp(contentDir string) error { return os.RemoveAll(contentDir) } + +// CleanupContent removes all temporary mountpoint and all content from +// directory +func CleanupContent(containerDir string) (Err error) { + contentDir := filepath.Join(containerDir, "overlay") + if err := os.RemoveAll(contentDir); err != nil && !os.IsNotExist(err) { + return errors.Wrapf(err, "failed to cleanup overlay %s directory", contentDir) + } + return nil +} diff --git a/vendor/github.com/containers/buildah/pkg/parse/parse.go b/vendor/github.com/containers/buildah/pkg/parse/parse.go index 6c58f1194..61e70cdd3 100644 --- a/vendor/github.com/containers/buildah/pkg/parse/parse.go +++ b/vendor/github.com/containers/buildah/pkg/parse/parse.go @@ -37,6 +37,7 @@ func CommonBuildOptions(c *cobra.Command) (*buildah.CommonBuildOptions, error) { var ( memoryLimit int64 memorySwap int64 + noDNS bool err error ) @@ -67,9 +68,26 @@ func CommonBuildOptions(c *cobra.Command) (*buildah.CommonBuildOptions, error) { } } + noDNS = false dnsServers, _ := c.Flags().GetStringSlice("dns") + for _, server := range dnsServers { + if strings.ToLower(server) == "none" { + noDNS = true + } + } + if noDNS && len(dnsServers) > 1 { + return nil, errors.Errorf("invalid --dns, --dns=none may not be used with any other --dns options") + } + dnsSearch, _ := c.Flags().GetStringSlice("dns-search") + if noDNS && len(dnsSearch) > 0 { + return nil, errors.Errorf("invalid --dns-search, --dns-search may not be used with --dns=none") + } + dnsOptions, _ := c.Flags().GetStringSlice("dns-option") + if noDNS && len(dnsOptions) > 0 { + return nil, errors.Errorf("invalid --dns-option, --dns-option may not be used with --dns=none") + } if _, err := units.FromHumanSize(c.Flag("shm-size").Value.String()); err != nil { return nil, errors.Wrapf(err, "invalid --shm-size") @@ -80,7 +98,7 @@ func CommonBuildOptions(c *cobra.Command) (*buildah.CommonBuildOptions, error) { } cpuPeriod, _ := c.Flags().GetUint64("cpu-period") cpuQuota, _ := c.Flags().GetInt64("cpu-quota") - cpuShares, _ := c.Flags().GetUint64("cpu-shared") + cpuShares, _ := c.Flags().GetUint64("cpu-shares") httpProxy, _ := c.Flags().GetBool("http-proxy") ulimit, _ := c.Flags().GetStringSlice("ulimit") commonOpts := &buildah.CommonBuildOptions{ diff --git a/vendor/github.com/containers/buildah/pkg/unshare/unshare.c b/vendor/github.com/containers/buildah/pkg/unshare/unshare.c index 67a3e0e4d..fd0d48d43 100644 --- a/vendor/github.com/containers/buildah/pkg/unshare/unshare.c +++ b/vendor/github.com/containers/buildah/pkg/unshare/unshare.c @@ -3,7 +3,7 @@ #include <sys/ioctl.h> #include <sys/stat.h> #include <sys/syscall.h> -#include <linux/memfd.h> +#include <sys/mman.h> #include <fcntl.h> #include <grp.h> #include <sched.h> @@ -14,6 +14,17 @@ #include <errno.h> #include <unistd.h> +/* Open Source projects like conda-forge, want to package podman and are based + off of centos:6, Conda-force has minimal libc requirements and is lacking + the memfd.h file, so we use mmam.h +*/ +#ifndef MFD_ALLOW_SEALING +#define MFD_ALLOW_SEALING 2U +#endif +#ifndef MFD_CLOEXEC +#define MFD_CLOEXEC 1U +#endif + #ifndef F_LINUX_SPECIFIC_BASE #define F_LINUX_SPECIFIC_BASE 1024 #endif diff --git a/vendor/github.com/containers/buildah/pkg/unshare/unshare.go b/vendor/github.com/containers/buildah/pkg/unshare/unshare.go index 5a68d6b8d..21b102cf5 100644 --- a/vendor/github.com/containers/buildah/pkg/unshare/unshare.go +++ b/vendor/github.com/containers/buildah/pkg/unshare/unshare.go @@ -195,13 +195,25 @@ func (c *Cmd) Start() error { if err == nil { gidmapSet = true } else { - fmt.Fprintf(continueWrite, "error running newgidmap: %v: %s", err, g.String()) - fmt.Fprintf(continueWrite, "falling back to single mapping\n") + logrus.Warnf("error running newgidmap: %v: %s", err, g.String()) + logrus.Warnf("falling back to single mapping") g.Reset() g.Write([]byte(fmt.Sprintf("0 %d 1\n", os.Getegid()))) } } if !gidmapSet { + if c.UseNewgidmap { + setgroups, err := os.OpenFile(fmt.Sprintf("/proc/%s/setgroups", pidString), os.O_TRUNC|os.O_WRONLY, 0) + if err != nil { + fmt.Fprintf(continueWrite, "error opening /proc/%s/setgroups: %v", pidString, err) + return errors.Wrapf(err, "error opening /proc/%s/setgroups", pidString) + } + defer setgroups.Close() + if _, err := fmt.Fprintf(setgroups, "deny"); err != nil { + fmt.Fprintf(continueWrite, "error writing 'deny' to /proc/%s/setgroups: %v", pidString, err) + return errors.Wrapf(err, "error writing 'deny' to /proc/%s/setgroups", pidString) + } + } gidmap, err := os.OpenFile(fmt.Sprintf("/proc/%s/gid_map", pidString), os.O_TRUNC|os.O_WRONLY, 0) if err != nil { fmt.Fprintf(continueWrite, "error opening /proc/%s/gid_map: %v", pidString, err) @@ -232,8 +244,8 @@ func (c *Cmd) Start() error { if err == nil { uidmapSet = true } else { - fmt.Fprintf(continueWrite, "error running newuidmap: %v: %s", err, u.String()) - fmt.Fprintf(continueWrite, "falling back to single mapping\n") + logrus.Warnf("error running newuidmap: %v: %s", err, u.String()) + logrus.Warnf("falling back to single mapping") u.Reset() u.Write([]byte(fmt.Sprintf("0 %d 1\n", os.Geteuid()))) } diff --git a/vendor/github.com/containers/buildah/run_linux.go b/vendor/github.com/containers/buildah/run_linux.go index 16c0550aa..55f9502b2 100644 --- a/vendor/github.com/containers/buildah/run_linux.go +++ b/vendor/github.com/containers/buildah/run_linux.go @@ -174,7 +174,7 @@ func (b *Builder) Run(command []string, options RunOptions) error { bindFiles["/etc/hosts"] = hostFile } - if !contains(volumes, "/etc/resolv.conf") { + if !(contains(volumes, "/etc/resolv.conf") || (len(b.CommonBuildOpts.DNSServers) == 1 && strings.ToLower(b.CommonBuildOpts.DNSServers[0]) == "none")) { resolvFile, err := b.addNetworkConfig(path, "/etc/resolv.conf", rootIDPair, b.CommonBuildOpts.DNSServers, b.CommonBuildOpts.DNSSearch, b.CommonBuildOpts.DNSOptions) if err != nil { return err @@ -434,7 +434,7 @@ func (b *Builder) setupMounts(mountPoint string, spec *specs.Spec, bundlePath st // Add temporary copies of the contents of volume locations at the // volume locations, unless we already have something there. - copyWithTar := b.copyWithTar(nil, nil) + copyWithTar := b.copyWithTar(nil, nil, nil) builtins, err := runSetupBuiltinVolumes(b.MountLabel, mountPoint, cdir, copyWithTar, builtinVolumes, int(rootUID), int(rootGID)) if err != nil { return err @@ -1049,6 +1049,18 @@ func runConfigureNetwork(isolation Isolation, options RunOptions, configureNetwo return teardown, nil } +func setNonblock(fd int, description string, nonblocking bool) error { + err := unix.SetNonblock(fd, nonblocking) + if err != nil { + if nonblocking { + logrus.Errorf("error setting %s to nonblocking: %v", description, err) + } else { + logrus.Errorf("error setting descriptor %s blocking: %v", description, err) + } + } + return err +} + func runCopyStdio(stdio *sync.WaitGroup, copyPipes bool, stdioPipe [][]int, copyConsole bool, consoleListener *net.UnixListener, finishCopy []int, finishedCopy chan struct{}, spec *specs.Spec) { defer func() { unix.Close(finishCopy[0]) @@ -1116,14 +1128,16 @@ func runCopyStdio(stdio *sync.WaitGroup, copyPipes bool, stdioPipe [][]int, copy } // Set our reading descriptors to non-blocking. for rfd, wfd := range relayMap { - if err := unix.SetNonblock(rfd, true); err != nil { - logrus.Errorf("error setting %s to nonblocking: %v", readDesc[rfd], err) + if err := setNonblock(rfd, readDesc[rfd], true); err != nil { return } - if err := unix.SetNonblock(wfd, false); err != nil { - logrus.Errorf("error setting descriptor %d (%s) blocking: %v", wfd, writeDesc[wfd], err) - } + setNonblock(wfd, writeDesc[wfd], false) } + + setNonblock(stdioPipe[unix.Stdin][1], writeDesc[stdioPipe[unix.Stdin][1]], true) + + closeStdin := false + // Pass data back and forth. pollTimeout := -1 for len(relayMap) > 0 { @@ -1155,12 +1169,6 @@ func runCopyStdio(stdio *sync.WaitGroup, copyPipes bool, stdioPipe [][]int, copy } // If the POLLIN flag isn't set, then there's no data to be read from this descriptor. if pollFd.Revents&unix.POLLIN == 0 { - // If we're using pipes and it's our stdin and it's closed, close the writing - // end of the corresponding pipe. - if copyPipes && int(pollFd.Fd) == unix.Stdin && pollFd.Revents&unix.POLLHUP != 0 { - unix.Close(stdioPipe[unix.Stdin][1]) - stdioPipe[unix.Stdin][1] = -1 - } continue } // Read whatever there is to be read. @@ -1175,10 +1183,8 @@ func runCopyStdio(stdio *sync.WaitGroup, copyPipes bool, stdioPipe [][]int, copy // using pipes, it's an EOF, so close the stdin // pipe's writing end. if n == 0 && copyPipes && int(pollFd.Fd) == unix.Stdin { - unix.Close(stdioPipe[unix.Stdin][1]) - stdioPipe[unix.Stdin][1] = -1 - } - if n > 0 { + removes[int(pollFd.Fd)] = struct{}{} + } else if n > 0 { // Buffer the data in case we get blocked on where they need to go. nwritten, err := relayBuffer[writeFD].Write(buf[:n]) if err != nil { @@ -1222,6 +1228,11 @@ func runCopyStdio(stdio *sync.WaitGroup, copyPipes bool, stdioPipe [][]int, copy if n > 0 { relayBuffer[writeFD].Next(n) } + if closeStdin && writeFD == stdioPipe[unix.Stdin][1] && stdioPipe[unix.Stdin][1] >= 0 && relayBuffer[stdioPipe[unix.Stdin][1]].Len() == 0 { + logrus.Debugf("closing stdin") + unix.Close(stdioPipe[unix.Stdin][1]) + stdioPipe[unix.Stdin][1] = -1 + } } if relayBuffer[writeFD].Len() > 0 { pollTimeout = 100 @@ -1229,6 +1240,14 @@ func runCopyStdio(stdio *sync.WaitGroup, copyPipes bool, stdioPipe [][]int, copy } // Remove any descriptors which we don't need to poll any more from the poll descriptor list. for remove := range removes { + if copyPipes && remove == unix.Stdin { + closeStdin = true + if relayBuffer[stdioPipe[unix.Stdin][1]].Len() == 0 { + logrus.Debugf("closing stdin") + unix.Close(stdioPipe[unix.Stdin][1]) + stdioPipe[unix.Stdin][1] = -1 + } + } delete(relayMap, remove) } // If the we-can-return pipe had anything for us, we're done. @@ -1453,7 +1472,7 @@ func setupNamespaces(g *generate.Generator, namespaceOptions NamespaceOptions, i } } } - if configureNetwork { + if configureNetwork && !unshare.IsRootless() { for name, val := range util.DefaultNetworkSysctl { // Check that the sysctl we are adding is actually supported // by the kernel @@ -1564,6 +1583,15 @@ func (b *Builder) cleanupTempVolumes() { func (b *Builder) runSetupVolumeMounts(mountLabel string, volumeMounts []string, optionMounts []specs.Mount, rootUID, rootGID int) (mounts []specs.Mount, Err error) { + // Make sure the overlay directory is clean before running + containerDir, err := b.store.ContainerDirectory(b.ContainerID) + if err != nil { + return nil, errors.Wrapf(err, "error looking up container directory for %s", b.ContainerID) + } + if err := overlay.CleanupContent(containerDir); err != nil { + return nil, errors.Wrapf(err, "error cleaning up overlay content for %s", b.ContainerID) + } + parseMount := func(host, container string, options []string) (specs.Mount, error) { var foundrw, foundro, foundz, foundZ, foundO bool var rootProp string diff --git a/vendor/github.com/containers/buildah/util.go b/vendor/github.com/containers/buildah/util.go index 08fb99706..ce21d2651 100644 --- a/vendor/github.com/containers/buildah/util.go +++ b/vendor/github.com/containers/buildah/util.go @@ -1,9 +1,12 @@ package buildah import ( + "archive/tar" "io" "os" + "path/filepath" + "github.com/containers/buildah/util" "github.com/containers/image/docker/reference" "github.com/containers/image/pkg/sysregistries" "github.com/containers/image/pkg/sysregistriesv2" @@ -12,7 +15,9 @@ import ( "github.com/containers/storage/pkg/archive" "github.com/containers/storage/pkg/chrootarchive" "github.com/containers/storage/pkg/idtools" + "github.com/containers/storage/pkg/pools" "github.com/containers/storage/pkg/reexec" + "github.com/containers/storage/pkg/system" "github.com/opencontainers/image-spec/specs-go/v1" rspec "github.com/opencontainers/runtime-spec/specs-go" "github.com/opencontainers/selinux/go-selinux" @@ -105,19 +110,108 @@ func convertRuntimeIDMaps(UIDMap, GIDMap []rspec.LinuxIDMapping) ([]idtools.IDMa } // copyFileWithTar returns a function which copies a single file from outside -// of any container into our working container, mapping permissions using the -// container's ID maps, possibly overridden using the passed-in chownOpts -func (b *Builder) copyFileWithTar(chownOpts *idtools.IDPair, hasher io.Writer) func(src, dest string) error { - convertedUIDMap, convertedGIDMap := convertRuntimeIDMaps(b.IDMappingOptions.UIDMap, b.IDMappingOptions.GIDMap) - return chrootarchive.CopyFileWithTarAndChown(chownOpts, hasher, convertedUIDMap, convertedGIDMap) +// of any container, or another container, into our working container, mapping +// read permissions using the passed-in ID maps, writing using the container's +// ID mappings, possibly overridden using the passed-in chownOpts +func (b *Builder) copyFileWithTar(tarIDMappingOptions *IDMappingOptions, chownOpts *idtools.IDPair, hasher io.Writer) func(src, dest string) error { + if tarIDMappingOptions == nil { + tarIDMappingOptions = &IDMappingOptions{ + HostUIDMapping: true, + HostGIDMapping: true, + } + } + return func(src, dest string) error { + logrus.Debugf("copyFileWithTar(%s, %s)", src, dest) + f, err := os.Open(src) + if err != nil { + return errors.Wrapf(err, "error opening %q to copy its contents", src) + } + defer func() { + if f != nil { + f.Close() + } + }() + + sysfi, err := system.Lstat(src) + if err != nil { + return errors.Wrapf(err, "error reading attributes of %q", src) + } + + hostUID := sysfi.UID() + hostGID := sysfi.GID() + containerUID, containerGID, err := util.GetContainerIDs(tarIDMappingOptions.UIDMap, tarIDMappingOptions.GIDMap, hostUID, hostGID) + if err != nil { + return errors.Wrapf(err, "error mapping owner IDs of %q: %d/%d", src, hostUID, hostGID) + } + + fi, err := os.Lstat(src) + if err != nil { + return errors.Wrapf(err, "error reading attributes of %q", src) + } + + hdr, err := tar.FileInfoHeader(fi, filepath.Base(src)) + if err != nil { + return errors.Wrapf(err, "error generating tar header for: %q", src) + } + hdr.Name = filepath.Base(dest) + hdr.Uid = int(containerUID) + hdr.Gid = int(containerGID) + + pipeReader, pipeWriter := io.Pipe() + writer := tar.NewWriter(pipeWriter) + var copyErr error + go func(srcFile *os.File) { + err := writer.WriteHeader(hdr) + if err != nil { + logrus.Debugf("error writing header for %s: %v", srcFile.Name(), err) + copyErr = err + } + n, err := pools.Copy(writer, srcFile) + if n != hdr.Size { + logrus.Debugf("expected to write %d bytes for %s, wrote %d instead", hdr.Size, srcFile.Name(), n) + } + if err != nil { + logrus.Debugf("error reading %s: %v", srcFile.Name(), err) + copyErr = err + } + if err = writer.Close(); err != nil { + logrus.Debugf("error closing write pipe for %s: %v", srcFile.Name(), err) + } + if err = srcFile.Close(); err != nil { + logrus.Debugf("error closing %s: %v", srcFile.Name(), err) + } + pipeWriter.Close() + pipeWriter = nil + return + }(f) + + untar := b.untar(chownOpts, hasher) + err = untar(pipeReader, filepath.Dir(dest)) + if err == nil { + err = copyErr + } + f = nil + if pipeWriter != nil { + pipeWriter.Close() + } + return err + } } // copyWithTar returns a function which copies a directory tree from outside of -// any container into our working container, mapping permissions using the -// container's ID maps, possibly overridden using the passed-in chownOpts -func (b *Builder) copyWithTar(chownOpts *idtools.IDPair, hasher io.Writer) func(src, dest string) error { - convertedUIDMap, convertedGIDMap := convertRuntimeIDMaps(b.IDMappingOptions.UIDMap, b.IDMappingOptions.GIDMap) - return chrootarchive.CopyWithTarAndChown(chownOpts, hasher, convertedUIDMap, convertedGIDMap) +// our container or from another container, into our working container, mapping +// permissions at read-time using the container's ID maps, with ownership at +// write-time possibly overridden using the passed-in chownOpts +func (b *Builder) copyWithTar(tarIDMappingOptions *IDMappingOptions, chownOpts *idtools.IDPair, hasher io.Writer) func(src, dest string) error { + tar := b.tarPath(tarIDMappingOptions) + untar := b.untar(chownOpts, hasher) + return func(src, dest string) error { + rc, err := tar(src) + if err != nil { + return errors.Wrapf(err, "error archiving %q for copy", src) + } + return untar(rc, dest) + } } // untarPath returns a function which extracts an archive in a specified @@ -128,12 +222,58 @@ func (b *Builder) untarPath(chownOpts *idtools.IDPair, hasher io.Writer) func(sr return chrootarchive.UntarPathAndChown(chownOpts, hasher, convertedUIDMap, convertedGIDMap) } -// tarPath returns a function which creates an archive of a specified +// tarPath returns a function which creates an archive of a specified location, +// which is often somewhere in the container's filesystem, mapping permissions +// using the container's ID maps, or the passed-in maps if specified +func (b *Builder) tarPath(idMappingOptions *IDMappingOptions) func(path string) (io.ReadCloser, error) { + var uidmap, gidmap []idtools.IDMap + if idMappingOptions == nil { + idMappingOptions = &IDMappingOptions{ + HostUIDMapping: true, + HostGIDMapping: true, + } + } + convertedUIDMap, convertedGIDMap := convertRuntimeIDMaps(idMappingOptions.UIDMap, idMappingOptions.GIDMap) + tarMappings := idtools.NewIDMappingsFromMaps(convertedUIDMap, convertedGIDMap) + uidmap = tarMappings.UIDs() + gidmap = tarMappings.GIDs() + options := &archive.TarOptions{ + Compression: archive.Uncompressed, + UIDMaps: uidmap, + GIDMaps: gidmap, + } + return func(path string) (io.ReadCloser, error) { + return archive.TarWithOptions(path, options) + } +} + +// untar returns a function which extracts an archive stream to a specified // location in the container's filesystem, mapping permissions using the -// container's ID maps -func (b *Builder) tarPath() func(path string) (io.ReadCloser, error) { +// container's ID maps, possibly overridden using the passed-in chownOpts +func (b *Builder) untar(chownOpts *idtools.IDPair, hasher io.Writer) func(tarArchive io.ReadCloser, dest string) error { convertedUIDMap, convertedGIDMap := convertRuntimeIDMaps(b.IDMappingOptions.UIDMap, b.IDMappingOptions.GIDMap) - return archive.TarPath(convertedUIDMap, convertedGIDMap) + untarMappings := idtools.NewIDMappingsFromMaps(convertedUIDMap, convertedGIDMap) + options := &archive.TarOptions{ + UIDMaps: untarMappings.UIDs(), + GIDMaps: untarMappings.GIDs(), + ChownOpts: chownOpts, + } + untar := chrootarchive.Untar + if hasher != nil { + originalUntar := untar + untar = func(tarArchive io.Reader, dest string, options *archive.TarOptions) error { + return originalUntar(io.TeeReader(tarArchive, hasher), dest, options) + } + } + return func(tarArchive io.ReadCloser, dest string) error { + err := untar(tarArchive, dest, options) + if err2 := tarArchive.Close(); err2 != nil { + if err == nil { + err = err2 + } + } + return err + } } // isRegistryBlocked checks if the named registry is marked as blocked diff --git a/vendor/github.com/containers/buildah/util/util.go b/vendor/github.com/containers/buildah/util/util.go index 30afe8313..4736d7b77 100644 --- a/vendor/github.com/containers/buildah/util/util.go +++ b/vendor/github.com/containers/buildah/util/util.go @@ -106,13 +106,19 @@ func ResolveName(name string, firstRegistry string, sc *types.SystemContext, sto // Figure out the list of registries. var registries []string - searchRegistries, err := sysregistriesv2.FindUnqualifiedSearchRegistries(sc) + searchRegistries, err := sysregistriesv2.UnqualifiedSearchRegistries(sc) if err != nil { logrus.Debugf("unable to read configured registries to complete %q: %v", name, err) + searchRegistries = nil } for _, registry := range searchRegistries { - if !registry.Blocked { - registries = append(registries, registry.Location) + reg, err := sysregistriesv2.FindRegistry(sc, registry) + if err != nil { + logrus.Debugf("unable to read registry configuraitno for %#v: %v", registry, err) + continue + } + if reg == nil || !reg.Blocked { + registries = append(registries, registry) } } searchRegistriesAreEmpty := len(registries) == 0 @@ -257,6 +263,36 @@ func StringInSlice(s string, slice []string) bool { return false } +// GetContainerIDs uses ID mappings to compute the container-level IDs that will +// correspond to a UID/GID pair on the host. +func GetContainerIDs(uidmap, gidmap []specs.LinuxIDMapping, uid, gid uint32) (uint32, uint32, error) { + uidMapped := true + for _, m := range uidmap { + uidMapped = false + if uid >= m.HostID && uid < m.HostID+m.Size { + uid = (uid - m.HostID) + m.ContainerID + uidMapped = true + break + } + } + if !uidMapped { + return 0, 0, errors.Errorf("container uses ID mappings (%#v), but doesn't map UID %d", uidmap, uid) + } + gidMapped := true + for _, m := range gidmap { + gidMapped = false + if gid >= m.HostID && gid < m.HostID+m.Size { + gid = (gid - m.HostID) + m.ContainerID + gidMapped = true + break + } + } + if !gidMapped { + return 0, 0, errors.Errorf("container uses ID mappings (%#v), but doesn't map GID %d", gidmap, gid) + } + return uid, gid, nil +} + // GetHostIDs uses ID mappings to compute the host-level IDs that will // correspond to a UID/GID pair in the container. func GetHostIDs(uidmap, gidmap []specs.LinuxIDMapping, uid, gid uint32) (uint32, uint32, error) { @@ -270,7 +306,7 @@ func GetHostIDs(uidmap, gidmap []specs.LinuxIDMapping, uid, gid uint32) (uint32, } } if !uidMapped { - return 0, 0, errors.Errorf("container uses ID mappings, but doesn't map UID %d", uid) + return 0, 0, errors.Errorf("container uses ID mappings (%#v), but doesn't map UID %d", uidmap, uid) } gidMapped := true for _, m := range gidmap { @@ -282,7 +318,7 @@ func GetHostIDs(uidmap, gidmap []specs.LinuxIDMapping, uid, gid uint32) (uint32, } } if !gidMapped { - return 0, 0, errors.Errorf("container uses ID mappings, but doesn't map GID %d", gid) + return 0, 0, errors.Errorf("container uses ID mappings (%#v), but doesn't map GID %d", gidmap, gid) } return uid, gid, nil } diff --git a/vendor/github.com/containers/buildah/vendor.conf b/vendor/github.com/containers/buildah/vendor.conf index 0c982626a..88148947a 100644 --- a/vendor/github.com/containers/buildah/vendor.conf +++ b/vendor/github.com/containers/buildah/vendor.conf @@ -3,12 +3,12 @@ github.com/blang/semver v3.5.0 github.com/BurntSushi/toml v0.2.0 github.com/containerd/continuity 004b46473808b3e7a4a3049c20e4376c91eb966d github.com/containernetworking/cni v0.7.0-rc2 -github.com/containers/image 9467ac9cfd92c545aa389f22f27e552de053c0f2 +github.com/containers/image v2.0.0 github.com/cyphar/filepath-securejoin v0.2.1 github.com/vbauerster/mpb v3.3.4 github.com/mattn/go-isatty v0.0.4 github.com/VividCortex/ewma v1.1.1 -github.com/containers/storage v1.12.7 +github.com/containers/storage v1.12.10 github.com/docker/distribution 5f6282db7d65e6d72ad7c2cc66310724a57be716 github.com/docker/docker 54dddadc7d5d89fe0be88f76979f6f6ab0dede83 github.com/docker/docker-credential-helpers v0.6.1 diff --git a/vendor/github.com/containers/image/docker/docker_image_src.go b/vendor/github.com/containers/image/docker/docker_image_src.go index c8fdb407c..c43e6e7ca 100644 --- a/vendor/github.com/containers/image/docker/docker_image_src.go +++ b/vendor/github.com/containers/image/docker/docker_image_src.go @@ -29,44 +29,16 @@ type dockerImageSource struct { cachedManifestMIMEType string // Only valid if cachedManifest != nil } -// newImageSource creates a new `ImageSource` for the specified image reference -// `ref`. -// -// The following steps will be done during the instance creation: -// -// - Lookup the registry within the configured location in -// `sys.SystemRegistriesConfPath`. If there is no configured registry available, -// we fallback to the provided docker reference `ref`. -// -// - References which contain a configured prefix will be automatically rewritten -// to the correct target reference. For example, if the configured -// `prefix = "example.com/foo"`, `location = "example.com"` and the image will be -// pulled from the ref `example.com/foo/image`, then the resulting pull will -// effectively point to `example.com/image`. -// -// - If the rewritten reference succeeds, it will be used as the `dockerRef` -// in the client. If the rewrite fails, the function immediately returns an error. -// -// - Each mirror will be used (in the configured order) to test the -// availability of the image manifest on the remote location. For example, -// if the manifest is not reachable due to connectivity issues, then the next -// mirror will be tested instead. If no mirror is configured or contains the -// target manifest, then the initial `ref` will be tested as fallback. The -// creation of the new `dockerImageSource` only succeeds if a remote -// location with the available manifest was found. -// -// A cleanup call to `.Close()` is needed if the caller is done using the returned -// `ImageSource`. +// newImageSource creates a new ImageSource for the specified image reference. +// The caller must call .Close() on the returned ImageSource. func newImageSource(ctx context.Context, sys *types.SystemContext, ref dockerReference) (*dockerImageSource, error) { registry, err := sysregistriesv2.FindRegistry(sys, ref.ref.Name()) if err != nil { return nil, errors.Wrapf(err, "error loading registries configuration") } - if registry == nil { - // No configuration was found for the provided reference, so we create - // a fallback registry by hand to make the client creation below work - // as intended. + // No configuration was found for the provided reference, so use the + // equivalent of a default configuration. registry = &sysregistriesv2.Registry{ Endpoint: sysregistriesv2.Endpoint{ Location: ref.ref.String(), @@ -76,18 +48,19 @@ func newImageSource(ctx context.Context, sys *types.SystemContext, ref dockerRef } primaryDomain := reference.Domain(ref.ref) - // Found the registry within the sysregistriesv2 configuration. Now we test - // all endpoints for the manifest availability. If a working image source - // was found, it will be used for all future pull actions. + // Check all endpoints for the manifest availability. If we find one that does + // contain the image, it will be used for all future pull actions. Always try the + // non-mirror original location last; this both transparently handles the case + // of no mirrors configured, and ensures we return the error encountered when + // acessing the upstream location if all endpoints fail. manifestLoadErr := errors.New("Internal error: newImageSource returned without trying any endpoint") - for _, endpoint := range append(registry.Mirrors, registry.Endpoint) { - logrus.Debugf("Trying to pull %q from endpoint %q", ref.ref, endpoint.Location) - - newRef, err := endpoint.RewriteReference(ref.ref, registry.Prefix) - if err != nil { - return nil, err - } - dockerRef, err := newReference(newRef) + pullSources, err := registry.PullSourcesFromReference(ref.ref) + if err != nil { + return nil, err + } + for _, pullSource := range pullSources { + logrus.Debugf("Trying to pull %q", pullSource.Reference) + dockerRef, err := newReference(pullSource.Reference) if err != nil { return nil, err } @@ -104,7 +77,7 @@ func newImageSource(ctx context.Context, sys *types.SystemContext, ref dockerRef if err != nil { return nil, err } - client.tlsClientConfig.InsecureSkipVerify = endpoint.Insecure + client.tlsClientConfig.InsecureSkipVerify = pullSource.Endpoint.Insecure testImageSource := &dockerImageSource{ ref: dockerRef, diff --git a/vendor/github.com/containers/image/docker/reference/README.md b/vendor/github.com/containers/image/docker/reference/README.md index 53a88de82..3c4d74eb4 100644 --- a/vendor/github.com/containers/image/docker/reference/README.md +++ b/vendor/github.com/containers/image/docker/reference/README.md @@ -1,2 +1,2 @@ -This is a copy of github.com/docker/distribution/reference as of commit fb0bebc4b64e3881cc52a2478d749845ed76d2a8, +This is a copy of github.com/docker/distribution/reference as of commit 3226863cbcba6dbc2f6c83a37b28126c934af3f8, except that ParseAnyReferenceWithSet has been removed to drop the dependency on github.com/docker/distribution/digestset.
\ No newline at end of file diff --git a/vendor/github.com/containers/image/docker/reference/normalize.go b/vendor/github.com/containers/image/docker/reference/normalize.go index fcc436a39..6a86ec64f 100644 --- a/vendor/github.com/containers/image/docker/reference/normalize.go +++ b/vendor/github.com/containers/image/docker/reference/normalize.go @@ -55,6 +55,35 @@ func ParseNormalizedNamed(s string) (Named, error) { return named, nil } +// ParseDockerRef normalizes the image reference following the docker convention. This is added +// mainly for backward compatibility. +// The reference returned can only be either tagged or digested. For reference contains both tag +// and digest, the function returns digested reference, e.g. docker.io/library/busybox:latest@ +// sha256:7cc4b5aefd1d0cadf8d97d4350462ba51c694ebca145b08d7d41b41acc8db5aa will be returned as +// docker.io/library/busybox@sha256:7cc4b5aefd1d0cadf8d97d4350462ba51c694ebca145b08d7d41b41acc8db5aa. +func ParseDockerRef(ref string) (Named, error) { + named, err := ParseNormalizedNamed(ref) + if err != nil { + return nil, err + } + if _, ok := named.(NamedTagged); ok { + if canonical, ok := named.(Canonical); ok { + // The reference is both tagged and digested, only + // return digested. + newNamed, err := WithName(canonical.Name()) + if err != nil { + return nil, err + } + newCanonical, err := WithDigest(newNamed, canonical.Digest()) + if err != nil { + return nil, err + } + return newCanonical, nil + } + } + return TagNameOnly(named), nil +} + // splitDockerDomain splits a repository name to domain and remotename string. // If no valid domain is found, the default domain is used. Repository name // needs to be already validated before. diff --git a/vendor/github.com/containers/image/docker/reference/reference.go b/vendor/github.com/containers/image/docker/reference/reference.go index fd3510e9e..8c0c23b2f 100644 --- a/vendor/github.com/containers/image/docker/reference/reference.go +++ b/vendor/github.com/containers/image/docker/reference/reference.go @@ -15,7 +15,7 @@ // tag := /[\w][\w.-]{0,127}/ // // digest := digest-algorithm ":" digest-hex -// digest-algorithm := digest-algorithm-component [ digest-algorithm-separator digest-algorithm-component ] +// digest-algorithm := digest-algorithm-component [ digest-algorithm-separator digest-algorithm-component ]* // digest-algorithm-separator := /[+.-_]/ // digest-algorithm-component := /[A-Za-z][A-Za-z0-9]*/ // digest-hex := /[0-9a-fA-F]{32,}/ ; At least 128 bit digest value @@ -205,7 +205,7 @@ func Parse(s string) (Reference, error) { var repo repository nameMatch := anchoredNameRegexp.FindStringSubmatch(matches[1]) - if nameMatch != nil && len(nameMatch) == 3 { + if len(nameMatch) == 3 { repo.domain = nameMatch[1] repo.path = nameMatch[2] } else { diff --git a/vendor/github.com/containers/image/docker/reference/regexp.go b/vendor/github.com/containers/image/docker/reference/regexp.go index 405e995db..786034932 100644 --- a/vendor/github.com/containers/image/docker/reference/regexp.go +++ b/vendor/github.com/containers/image/docker/reference/regexp.go @@ -20,15 +20,15 @@ var ( optional(repeated(separatorRegexp, alphaNumericRegexp))) // domainComponentRegexp restricts the registry domain component of a - // repository name to start with a component as defined by domainRegexp + // repository name to start with a component as defined by DomainRegexp // and followed by an optional port. domainComponentRegexp = match(`(?:[a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])`) - // domainRegexp defines the structure of potential domain components + // DomainRegexp defines the structure of potential domain components // that may be part of image names. This is purposely a subset of what is // allowed by DNS to ensure backwards compatibility with Docker image // names. - domainRegexp = expression( + DomainRegexp = expression( domainComponentRegexp, optional(repeated(literal(`.`), domainComponentRegexp)), optional(literal(`:`), match(`[0-9]+`))) @@ -51,14 +51,14 @@ var ( // regexp has capturing groups for the domain and name part omitting // the separating forward slash from either. NameRegexp = expression( - optional(domainRegexp, literal(`/`)), + optional(DomainRegexp, literal(`/`)), nameComponentRegexp, optional(repeated(literal(`/`), nameComponentRegexp))) // anchoredNameRegexp is used to parse a name value, capturing the // domain and trailing components. anchoredNameRegexp = anchored( - optional(capture(domainRegexp), literal(`/`)), + optional(capture(DomainRegexp), literal(`/`)), capture(nameComponentRegexp, optional(repeated(literal(`/`), nameComponentRegexp)))) diff --git a/vendor/github.com/containers/image/pkg/sysregistriesv2/system_registries_v2.go b/vendor/github.com/containers/image/pkg/sysregistriesv2/system_registries_v2.go index 99ae65774..361e6fc60 100644 --- a/vendor/github.com/containers/image/pkg/sysregistriesv2/system_registries_v2.go +++ b/vendor/github.com/containers/image/pkg/sysregistriesv2/system_registries_v2.go @@ -5,6 +5,7 @@ import ( "io/ioutil" "os" "path/filepath" + "regexp" "strings" "sync" @@ -35,10 +36,10 @@ type Endpoint struct { Insecure bool `toml:"insecure"` } -// RewriteReference will substitute the provided reference `prefix` to the +// rewriteReference will substitute the provided reference `prefix` to the // endpoints `location` from the `ref` and creates a new named reference from it. // The function errors if the newly created reference is not parsable. -func (e *Endpoint) RewriteReference(ref reference.Named, prefix string) (reference.Named, error) { +func (e *Endpoint) rewriteReference(ref reference.Named, prefix string) (reference.Named, error) { refString := ref.String() if !refMatchesPrefix(refString, prefix) { return nil, fmt.Errorf("invalid prefix '%v' for reference '%v'", prefix, refString) @@ -61,8 +62,10 @@ type Registry struct { Mirrors []Endpoint `toml:"mirror"` // If true, pulling from the registry will be blocked. Blocked bool `toml:"blocked"` - // If true, the registry can be used when pulling an unqualified image. - Search bool `toml:"unqualified-search"` + // If true, mirrors will only be used for digest pulls. Pulling images by + // tag can potentially yield different images, depending on which endpoint + // we pull from. Forcing digest-pulls for mirrors avoids that issue. + MirrorByDigestOnly bool `toml:"mirror-by-digest-only"` // Prefix is used for matching images, and to translate one namespace to // another. If `Prefix="example.com/bar"`, `location="example.com/foo/bar"` // and we pull from "example.com/bar/myimage:latest", the image will @@ -71,6 +74,41 @@ type Registry struct { Prefix string `toml:"prefix"` } +// PullSource consists of an Endpoint and a Reference. Note that the reference is +// rewritten according to the registries prefix and the Endpoint's location. +type PullSource struct { + Endpoint Endpoint + Reference reference.Named +} + +// PullSourcesFromReference returns a slice of PullSource's based on the passed +// reference. +func (r *Registry) PullSourcesFromReference(ref reference.Named) ([]PullSource, error) { + var endpoints []Endpoint + + if r.MirrorByDigestOnly { + // Only use mirrors when the reference is a digest one. + if _, isDigested := ref.(reference.Canonical); isDigested { + endpoints = append(r.Mirrors, r.Endpoint) + } else { + endpoints = []Endpoint{r.Endpoint} + } + } else { + endpoints = append(r.Mirrors, r.Endpoint) + } + + sources := []PullSource{} + for _, ep := range endpoints { + rewritten, err := ep.rewriteReference(ref, r.Prefix) + if err != nil { + return nil, err + } + sources = append(sources, PullSource{Endpoint: ep, Reference: rewritten}) + } + + return sources, nil +} + // V1TOMLregistries is for backwards compatibility to sysregistries v1 type V1TOMLregistries struct { Registries []string `toml:"registries"` @@ -83,11 +121,35 @@ type V1TOMLConfig struct { Block V1TOMLregistries `toml:"block"` } +// V1RegistriesConf is the sysregistries v1 configuration format. +type V1RegistriesConf struct { + V1TOMLConfig `toml:"registries"` +} + +// Nonempty returns true if config contains at least one configuration entry. +func (config *V1RegistriesConf) Nonempty() bool { + return (len(config.V1TOMLConfig.Search.Registries) != 0 || + len(config.V1TOMLConfig.Insecure.Registries) != 0 || + len(config.V1TOMLConfig.Block.Registries) != 0) +} + +// V2RegistriesConf is the sysregistries v2 configuration format. +type V2RegistriesConf struct { + Registries []Registry `toml:"registry"` + // An array of host[:port] (not prefix!) entries to use for resolving unqualified image references + UnqualifiedSearchRegistries []string `toml:"unqualified-search-registries"` +} + +// Nonempty returns true if config contains at least one configuration entry. +func (config *V2RegistriesConf) Nonempty() bool { + return (len(config.Registries) != 0 || + len(config.UnqualifiedSearchRegistries) != 0) +} + // tomlConfig is the data type used to unmarshal the toml config. type tomlConfig struct { - Registries []Registry `toml:"registry"` - // backwards compatability to sysregistries v1 - V1TOMLConfig `toml:"registries"` + V2RegistriesConf + V1RegistriesConf // for backwards compatibility with sysregistries v1 } // InvalidRegistries represents an invalid registry configurations. An example @@ -120,12 +182,10 @@ func parseLocation(input string) (string, error) { return trimmed, nil } -// getV1Registries transforms v1 registries in the config into an array of v2 -// registries of type Registry. -func getV1Registries(config *tomlConfig) ([]Registry, error) { +// ConvertToV2 returns a v2 config corresponding to a v1 one. +func (config *V1RegistriesConf) ConvertToV2() (*V2RegistriesConf, error) { regMap := make(map[string]*Registry) - // We must preserve the order of config.V1Registries.Search.Registries at least. The order of the - // other registries is not really important, but make it deterministic (the same for the same config file) + // The order of the registries is not really important, but make it deterministic (the same for the same config file) // to minimize behavior inconsistency and not contribute to difficult-to-reproduce situations. registryOrder := []string{} @@ -148,15 +208,6 @@ func getV1Registries(config *tomlConfig) ([]Registry, error) { return reg, nil } - // Note: config.V1Registries.Search needs to be processed first to ensure registryOrder is populated in the right order - // if one of the search registries is also in one of the other lists. - for _, search := range config.V1TOMLConfig.Search.Registries { - reg, err := getRegistry(search) - if err != nil { - return nil, err - } - reg.Search = true - } for _, blocked := range config.V1TOMLConfig.Block.Registries { reg, err := getRegistry(blocked) if err != nil { @@ -172,28 +223,31 @@ func getV1Registries(config *tomlConfig) ([]Registry, error) { reg.Insecure = true } - registries := []Registry{} + res := &V2RegistriesConf{ + UnqualifiedSearchRegistries: config.V1TOMLConfig.Search.Registries, + } for _, location := range registryOrder { reg := regMap[location] - registries = append(registries, *reg) + res.Registries = append(res.Registries, *reg) } - return registries, nil + return res, nil } -// postProcessRegistries checks the consistency of all registries (e.g., set -// the Prefix to Location if not set) and applies conflict checks. It returns an -// array of cleaned registries and error in case of conflicts. -func postProcessRegistries(regs []Registry) ([]Registry, error) { - var registries []Registry - regMap := make(map[string][]Registry) +// anchoredDomainRegexp is an internal implementation detail of postProcess, defining the valid values of elements of UnqualifiedSearchRegistries. +var anchoredDomainRegexp = regexp.MustCompile("^" + reference.DomainRegexp.String() + "$") - for _, reg := range regs { - var err error +// postProcess checks the consistency of all the configuration, looks for conflicts, +// and normalizes the configuration (e.g., sets the Prefix to Location if not set). +func (config *V2RegistriesConf) postProcess() error { + regMap := make(map[string][]*Registry) + for i := range config.Registries { + reg := &config.Registries[i] // make sure Location and Prefix are valid + var err error reg.Location, err = parseLocation(reg.Location) if err != nil { - return nil, err + return err } if reg.Prefix == "" { @@ -201,7 +255,7 @@ func postProcessRegistries(regs []Registry) ([]Registry, error) { } else { reg.Prefix, err = parseLocation(reg.Prefix) if err != nil { - return nil, err + return err } } @@ -209,10 +263,9 @@ func postProcessRegistries(regs []Registry) ([]Registry, error) { for _, mir := range reg.Mirrors { mir.Location, err = parseLocation(mir.Location) if err != nil { - return nil, err + return err } } - registries = append(registries, reg) regMap[reg.Location] = append(regMap[reg.Location], reg) } @@ -222,22 +275,32 @@ func postProcessRegistries(regs []Registry) ([]Registry, error) { // // Note: we need to iterate over the registries array to ensure a // deterministic behavior which is not guaranteed by maps. - for _, reg := range registries { + for _, reg := range config.Registries { others, _ := regMap[reg.Location] for _, other := range others { if reg.Insecure != other.Insecure { msg := fmt.Sprintf("registry '%s' is defined multiple times with conflicting 'insecure' setting", reg.Location) - - return nil, &InvalidRegistries{s: msg} + return &InvalidRegistries{s: msg} } if reg.Blocked != other.Blocked { msg := fmt.Sprintf("registry '%s' is defined multiple times with conflicting 'blocked' setting", reg.Location) - return nil, &InvalidRegistries{s: msg} + return &InvalidRegistries{s: msg} } } } - return registries, nil + for i := range config.UnqualifiedSearchRegistries { + registry, err := parseLocation(config.UnqualifiedSearchRegistries[i]) + if err != nil { + return err + } + if !anchoredDomainRegexp.MatchString(registry) { + return &InvalidRegistries{fmt.Sprintf("Invalid unqualified-search-registries entry %#v", registry)} + } + config.UnqualifiedSearchRegistries[i] = registry + } + + return nil } // getConfigPath returns the system-registries config path if specified. @@ -260,7 +323,7 @@ var configMutex = sync.Mutex{} // configCache caches already loaded configs with config paths as keys and is // used to avoid redudantly parsing configs. Concurrent accesses to the cache // are synchronized via configMutex. -var configCache = make(map[string][]Registry) +var configCache = make(map[string]*V2RegistriesConf) // InvalidateCache invalidates the registry cache. This function is meant to be // used for long-running processes that need to reload potential changes made to @@ -268,20 +331,18 @@ var configCache = make(map[string][]Registry) func InvalidateCache() { configMutex.Lock() defer configMutex.Unlock() - configCache = make(map[string][]Registry) + configCache = make(map[string]*V2RegistriesConf) } -// GetRegistries loads and returns the registries specified in the config. -// Note the parsed content of registry config files is cached. For reloading, -// use `InvalidateCache` and re-call `GetRegistries`. -func GetRegistries(ctx *types.SystemContext) ([]Registry, error) { +// getConfig returns the config object corresponding to ctx, loading it if it is not yet cached. +func getConfig(ctx *types.SystemContext) (*V2RegistriesConf, error) { configPath := getConfigPath(ctx) configMutex.Lock() defer configMutex.Unlock() // if the config has already been loaded, return the cached registries - if registries, inCache := configCache[configPath]; inCache { - return registries, nil + if config, inCache := configCache[configPath]; inCache { + return config, nil } // load the config @@ -292,51 +353,53 @@ func GetRegistries(ctx *types.SystemContext) ([]Registry, error) { // isn't set. Note: if ctx.SystemRegistriesConfPath points to // the default config, we will still return an error. if os.IsNotExist(err) && (ctx == nil || ctx.SystemRegistriesConfPath == "") { - return []Registry{}, nil + return &V2RegistriesConf{Registries: []Registry{}}, nil } return nil, err } - registries := config.Registries + v2Config := &config.V2RegistriesConf // backwards compatibility for v1 configs - v1Registries, err := getV1Registries(config) - if err != nil { - return nil, err - } - if len(v1Registries) > 0 { - if len(registries) > 0 { + if config.V1RegistriesConf.Nonempty() { + if config.V2RegistriesConf.Nonempty() { return nil, &InvalidRegistries{s: "mixing sysregistry v1/v2 is not supported"} } - registries = v1Registries + v2, err := config.V1RegistriesConf.ConvertToV2() + if err != nil { + return nil, err + } + v2Config = v2 } - registries, err = postProcessRegistries(registries) - if err != nil { + if err := v2Config.postProcess(); err != nil { return nil, err } // populate the cache - configCache[configPath] = registries - - return registries, err + configCache[configPath] = v2Config + return v2Config, nil } -// FindUnqualifiedSearchRegistries returns all registries that are configured -// for unqualified image search (i.e., with Registry.Search == true). -func FindUnqualifiedSearchRegistries(ctx *types.SystemContext) ([]Registry, error) { - registries, err := GetRegistries(ctx) +// GetRegistries loads and returns the registries specified in the config. +// Note the parsed content of registry config files is cached. For reloading, +// use `InvalidateCache` and re-call `GetRegistries`. +func GetRegistries(ctx *types.SystemContext) ([]Registry, error) { + config, err := getConfig(ctx) if err != nil { return nil, err } + return config.Registries, nil +} - unqualified := []Registry{} - for _, reg := range registries { - if reg.Search { - unqualified = append(unqualified, reg) - } +// UnqualifiedSearchRegistries returns a list of host[:port] entries to try +// for unqualified image search, in the returned order) +func UnqualifiedSearchRegistries(ctx *types.SystemContext) ([]string, error) { + config, err := getConfig(ctx) + if err != nil { + return nil, err } - return unqualified, nil + return config.UnqualifiedSearchRegistries, nil } // refMatchesPrefix returns true iff ref, @@ -371,14 +434,14 @@ func refMatchesPrefix(ref, prefix string) bool { // — note that this requires the name to start with an explicit hostname!). // If no Registry prefixes the image, nil is returned. func FindRegistry(ctx *types.SystemContext, ref string) (*Registry, error) { - registries, err := GetRegistries(ctx) + config, err := getConfig(ctx) if err != nil { return nil, err } reg := Registry{} prefixLen := 0 - for _, r := range registries { + for _, r := range config.Registries { if refMatchesPrefix(ref, r.Prefix) { length := len(r.Prefix) if length > prefixLen { @@ -393,21 +456,12 @@ func FindRegistry(ctx *types.SystemContext, ref string) (*Registry, error) { return nil, nil } -// Reads the global registry file from the filesystem. Returns a byte array. -func readRegistryConf(configPath string) ([]byte, error) { - configBytes, err := ioutil.ReadFile(configPath) - return configBytes, err -} - -// Used in unittests to parse custom configs without a types.SystemContext. -var readConf = readRegistryConf - // Loads the registry configuration file from the filesystem and then unmarshals // it. Returns the unmarshalled object. func loadRegistryConf(configPath string) (*tomlConfig, error) { config := &tomlConfig{} - configBytes, err := readConf(configPath) + configBytes, err := ioutil.ReadFile(configPath) if err != nil { return nil, err } diff --git a/vendor/github.com/containers/image/version/version.go b/vendor/github.com/containers/image/version/version.go index 184274736..62b2c8bc5 100644 --- a/vendor/github.com/containers/image/version/version.go +++ b/vendor/github.com/containers/image/version/version.go @@ -4,14 +4,14 @@ import "fmt" const ( // VersionMajor is for an API incompatible changes - VersionMajor = 1 + VersionMajor = 2 // VersionMinor is for functionality in a backwards-compatible manner - VersionMinor = 7 + VersionMinor = 0 // VersionPatch is for backwards-compatible bug fixes VersionPatch = 0 // VersionDev indicates development branch. Releases will be empty string. - VersionDev = "-dev" + VersionDev = "" ) // Version is the specification version that the package types support. diff --git a/version/version.go b/version/version.go index c3917c016..2ef7a9c65 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.4.1-dev" +const Version = "1.4.2-dev" // RemoteAPIVersion is the version for the remote // client API. It is used to determine compatibility |