diff options
208 files changed, 3488 insertions, 1085 deletions
diff --git a/.cirrus.yml b/.cirrus.yml index 199c2a533..0efe73802 100644 --- a/.cirrus.yml +++ b/.cirrus.yml @@ -25,6 +25,16 @@ env: CIRRUS_CLONE_DEPTH: 200 #### + #### Cache-image names to test with + ### + FEDORA_CACHE_IMAGE_NAME: "fedora-29-libpod-7f4cd1f7" + PRIOR_FEDORA_CACHE_IMAGE_NAME: "fedora-28-libpod-7f4cd1f7" + UBUNTU_CACHE_IMAGE_NAME: "ubuntu-18-libpod-7f4cd1f7" + # RHEL_CACHE_IMAGE_NAME: "rhel-8-notready" + PRIOR_RHEL_CACHE_IMAGE_NAME: "rhel-7-libpod-7f4cd1f7" + # CENTOS_CACHE_IMAGE_NAME: "centos-7-notready" + + #### #### Variables for composing new cache-images (used in PR testing) from #### base-images (pre-existing in GCE) #### @@ -35,9 +45,11 @@ env: CRIU_COMMIT: "c74b83cd49c00589c0c0468ba5fe685b67fdbd0a" RUNC_COMMIT: "25f3f893c86d07426df93b7aa172f33fdf093fbd" # CSV of cache-image names to build (see $PACKER_BASE/libpod_images.json) - PACKER_BUILDS: "ubuntu-18,fedora-29,fedora-28" # TODO: fah-29,rhel-7,centos-7 + PACKER_BUILDS: "ubuntu-18,fedora-29,fedora-28,rhel-7" # TODO: rhel-8,centos-7 # Version of packer to use - PACKER_VER: "1.3.1" + PACKER_VER: "1.3.2" + # Special image w/ nested-libvirt + tools for creating new cache and base images + IMAGE_BUILDER_CACHE_IMAGE_NAME: "image-builder-image-1541772081" # Google-maintained base-image names UBUNTU_BASE_IMAGE: "ubuntu-1804-bionic-v20181203a" CENTOS_BASE_IMAGE: "centos-7-v20181113" @@ -46,7 +58,7 @@ env: PRIOR_FEDORA_BASE_IMAGE: "fedora-cloud-base-28-1-1-1544474897" FAH_BASE_IMAGE: "fedora-atomichost-29-20181025-1-1541787861" # RHEL image must be imported, google bills extra for their native image. - RHEL_BASE_IMAGE: "rhel-guest-image-7-6-210-x86-64-qcow2-1541783972" + RHEL_BASE_IMAGE: "rhel-guest-image-7-6-210-x86-64-qcow2-1548099756" #### #### Credentials and other secret-sauces, decrypted at runtime when authorized. @@ -113,7 +125,7 @@ build_each_commit_task: memory: "4Gb" disk: 40 matrix: - image_name: "fedora-29-libpod-9afa57a9" + image_name: "${FEDORA_CACHE_IMAGE_NAME}" timeout_in: 30m @@ -122,6 +134,7 @@ build_each_commit_task: - git fetch --depth $CIRRUS_CLONE_DEPTH origin $CIRRUS_BASE_BRANCH - env GOPATH=/var/tmp/go/ make build-all-new-commits GIT_BASE_BRANCH=origin/$CIRRUS_BASE_BRANCH + # This task does the unit and integration testing for every platform testing_task: @@ -139,14 +152,14 @@ testing_task: # 'matrix' combinations. matrix: # Images are generated separately, from build_images_task (below) - image_name: fedora-29-libpod-9afa57a9 - image_name: fedora-28-libpod-9afa57a9 - image_name: ubuntu-18-libpod-9afa57a9 + image_name: "${FEDORA_CACHE_IMAGE_NAME}" + image_name: "${PRIOR_FEDORA_CACHE_IMAGE_NAME}" + image_name: "${UBUNTU_CACHE_IMAGE_NAME}" + image_name: "${PRIOR_RHEL_CACHE_IMAGE_NAME}" # TODO: tests fail - # image_name: "rhel-7-something-something" - # image_name: "centos-7-something-something" - # image_name: "fah-29-libpod-5070733157859328" + # image_name: "${RHEL_CACHE_IMAGE_NAME}" + # image_name: "${CENTOS_CACHE_IMAGE_NAME}" timeout_in: 120m @@ -176,12 +189,13 @@ optional_testing_task: gce_instance: image_project: "libpod-218412" matrix: - image_name: fedora-29-libpod-9afa57a9 - image_name: fedora-28-libpod-9afa57a9 - image_name: ubuntu-18-libpod-9afa57a9 + image_name: "${FEDORA_CACHE_IMAGE_NAME}" + image_name: "${PRIOR_FEDORA_CACHE_IMAGE_NAME}" + image_name: "${UBUNTU_CACHE_IMAGE_NAME}" + image_name: "${PRIOR_RHEL_CACHE_IMAGE_NAME}" # TODO: Make these work (also build_images_task below) - #image_name: "rhel-server-ec2-7-5-165-1-libpod-fce09afe" - #image_name: "centos-7-v20180911-libpod-fce09afe" + # image_name: "${RHEL_CACHE_IMAGE_NAME}" + # image_name: "${CENTOS_CACHE_IMAGE_NAME}" timeout_in: 60m @@ -214,7 +228,7 @@ cache_images_task: cpu: 4 memory: "4Gb" disk: 200 - image_name: "image-builder-image-1541772081" # Simply CentOS 7 + packer dependencies + image_name: "${IMAGE_BUILDER_CACHE_IMAGE_NAME}" # Additional permissions for building GCE images, within a GCE VM scopes: - compute @@ -110,6 +110,7 @@ fi if [ $build -eq 1 ]; then make_install_tools make TAGS="${TAGS}" GOPATH=$GOPATH + make podman-remote TAGS="${TAGS}" GOPATH=$GOPATH fi # Install Podman @@ -126,4 +127,5 @@ if [ $integrationtest -eq 1 ]; then make TAGS="${TAGS}" test-binaries make varlink_generate GOPATH=/go make ginkgo GOPATH=/go $INTEGRATION_TEST_ENVS + make ginkgo-remote GOPATH=/go $INTEGRATION_TEST_ENVS fi @@ -9,14 +9,22 @@ in the [API.md](https://github.com/containers/libpod/blob/master/API.md) file in [func Commit(name: string, image_name: string, changes: []string, author: string, message: string, pause: bool, manifestType: string) string](#Commit) +[func ContainerArtifacts(name: string, artifactName: string) string](#ContainerArtifacts) + [func ContainerCheckpoint(name: string, keep: bool, leaveRunning: bool, tcpEstablished: bool) string](#ContainerCheckpoint) +[func ContainerConfig(name: string) string](#ContainerConfig) + [func ContainerExists(name: string) int](#ContainerExists) +[func ContainerInspectData(name: string) string](#ContainerInspectData) + [func ContainerRestore(name: string, keep: bool, tcpEstablished: bool) string](#ContainerRestore) [func ContainerRunlabel(runlabel: Runlabel) ](#ContainerRunlabel) +[func ContainerStateData(name: string) string](#ContainerStateData) + [func CreateContainer(create: Create) string](#CreateContainer) [func CreateImage() NotImplemented](#CreateImage) @@ -57,7 +65,7 @@ in the [API.md](https://github.com/containers/libpod/blob/master/API.md) file in [func ImageExists(name: string) int](#ImageExists) -[func ImagesPrune() []string](#ImagesPrune) +[func ImagesPrune(all: bool) []string](#ImagesPrune) [func ImportImage(source: string, reference: string, message: string, changes: []string) string](#ImportImage) @@ -73,7 +81,7 @@ in the [API.md](https://github.com/containers/libpod/blob/master/API.md) file in [func ListContainerChanges(name: string) ContainerChanges](#ListContainerChanges) -[func ListContainerMounts() []string](#ListContainerMounts) +[func ListContainerMounts() map[string]](#ListContainerMounts) [func ListContainerPorts(name: string) NotImplemented](#ListContainerPorts) @@ -239,12 +247,24 @@ attributes: _CMD, ENTRYPOINT, ENV, EXPOSE, LABEL, ONBUILD, STOPSIGNAL, USER, VOL container while it is being committed, pass a _true_ bool for the pause argument. If the container cannot be found by the ID or name provided, a (ContainerNotFound)[#ContainerNotFound] error will be returned; otherwise, the resulting image's ID will be returned as a string. +### <a name="ContainerArtifacts"></a>func ContainerArtifacts +<div style="background-color: #E8E8E8; padding: 15px; margin: 10px; border-radius: 10px;"> + +method ContainerArtifacts(name: [string](https://godoc.org/builtin#string), artifactName: [string](https://godoc.org/builtin#string)) [string](https://godoc.org/builtin#string)</div> +ContainerArtifacts returns a container's artifacts in string form. This call is for +development of Podman only and generally should not be used. ### <a name="ContainerCheckpoint"></a>func ContainerCheckpoint <div style="background-color: #E8E8E8; padding: 15px; margin: 10px; border-radius: 10px;"> method ContainerCheckpoint(name: [string](https://godoc.org/builtin#string), keep: [bool](https://godoc.org/builtin#bool), leaveRunning: [bool](https://godoc.org/builtin#bool), tcpEstablished: [bool](https://godoc.org/builtin#bool)) [string](https://godoc.org/builtin#string)</div> ContainerCheckPoint performs a checkpopint on a container by its name or full/partial container ID. On successful checkpoint, the id of the checkpointed container is returned. +### <a name="ContainerConfig"></a>func ContainerConfig +<div style="background-color: #E8E8E8; padding: 15px; margin: 10px; border-radius: 10px;"> + +method ContainerConfig(name: [string](https://godoc.org/builtin#string)) [string](https://godoc.org/builtin#string)</div> +ContainerConfig returns a container's config in string form. This call is for +development of Podman only and generally should not be used. ### <a name="ContainerExists"></a>func ContainerExists <div style="background-color: #E8E8E8; padding: 15px; margin: 10px; border-radius: 10px;"> @@ -258,6 +278,12 @@ $ varlink call -m unix:/run/podman/io.podman/io.podman.ContainerExists '{"name": "exists": 0 } ~~~ +### <a name="ContainerInspectData"></a>func ContainerInspectData +<div style="background-color: #E8E8E8; padding: 15px; margin: 10px; border-radius: 10px;"> + +method ContainerInspectData(name: [string](https://godoc.org/builtin#string)) [string](https://godoc.org/builtin#string)</div> +ContainerInspectData returns a container's inspect data in string form. This call is for +development of Podman only and generally should not be used. ### <a name="ContainerRestore"></a>func ContainerRestore <div style="background-color: #E8E8E8; padding: 15px; margin: 10px; border-radius: 10px;"> @@ -270,6 +296,12 @@ of the container's ID. method ContainerRunlabel(runlabel: [Runlabel](#Runlabel)) </div> ContainerRunlabel runs executes a command as described by a given container image label. +### <a name="ContainerStateData"></a>func ContainerStateData +<div style="background-color: #E8E8E8; padding: 15px; margin: 10px; border-radius: 10px;"> + +method ContainerStateData(name: [string](https://godoc.org/builtin#string)) [string](https://godoc.org/builtin#string)</div> +ContainerStateData returns a container's state config in string form. This call is for +development of Podman only and generally should not be used. ### <a name="CreateContainer"></a>func CreateContainer <div style="background-color: #E8E8E8; padding: 15px; margin: 10px; border-radius: 10px;"> @@ -548,7 +580,7 @@ $ varlink call -m unix:/run/podman/io.podman/io.podman.ImageExists '{"name": "im ### <a name="ImagesPrune"></a>func ImagesPrune <div style="background-color: #E8E8E8; padding: 15px; margin: 10px; border-radius: 10px;"> -method ImagesPrune() [[]string](#[]string)</div> +method ImagesPrune(all: [bool](https://godoc.org/builtin#bool)) [[]string](#[]string)</div> ImagesPrune removes all unused images from the local store. Upon successful pruning, the IDs of the removed images are returned. ### <a name="ImportImage"></a>func ImportImage @@ -611,19 +643,17 @@ its base image. It returns a struct of changed, deleted, and added path names. ### <a name="ListContainerMounts"></a>func ListContainerMounts <div style="background-color: #E8E8E8; padding: 15px; margin: 10px; border-radius: 10px;"> -method ListContainerMounts() [[]string](#[]string)</div> +method ListContainerMounts() [map[string]](#map[string])</div> ListContainerMounts gathers all the mounted container mount points and returns them as an array of strings #### Example ~~~ -$ varlink call -m unix:/run/podman/io.podman/io.podman.ListContainerMounts +$ varlink call unix:/run/podman/io.podman/io.podman.ListContainerMounts { - "mounts": [ - "/var/lib/containers/storage/overlay/b215fb622c65ba3b06c6d2341be80b76a9de7ae415ce419e65228873d4f0dcc8/merged", - "/var/lib/containers/storage/overlay/5eaf806073f79c0ed9a695180ad598e34f963f7407da1d2ccf3560bdab49b26f/merged", - "/var/lib/containers/storage/overlay/1ecb6b1dbb251737c7a24a31869096839c3719d8b250bf075f75172ddcc701e1/merged", - "/var/lib/containers/storage/overlay/7137b28a3c422165fe920cba851f2f8da271c6b5908672c451ebda03ad3919e2/merged" - ] + "mounts": { + "04e4c255269ed2545e7f8bd1395a75f7949c50c223415c00c1d54bfa20f3b3d9": "/var/lib/containers/storage/overlay/a078925828f57e20467ca31cfca8a849210d21ec7e5757332b72b6924f441c17/merged", + "1d58c319f9e881a644a5122ff84419dccf6d138f744469281446ab243ef38924": "/var/lib/containers/storage/overlay/948fcf93f8cb932f0f03fd52e3180a58627d547192ffe3b88e0013b98ddcd0d2/merged" + } } ~~~ ### <a name="ListContainerPorts"></a>func ListContainerPorts @@ -1373,6 +1403,8 @@ virtualSize [int](https://godoc.org/builtin#int) containers [int](https://godoc.org/builtin#int) labels [map[string]](#map[string]) + +isParent [bool](https://godoc.org/builtin#bool) ### <a name="ImageSearch"></a>type ImageSearch ImageSearch is the returned structure for SearchImage. It is returned @@ -1460,7 +1492,7 @@ graph_status [InfoGraphStatus](#InfoGraphStatus) run_root [string](https://godoc.org/builtin#string) ### <a name="ListContainerData"></a>type ListContainerData -ListContainer is the returned struct for an individual container +ListContainerData is the returned struct for an individual container id [string](https://godoc.org/builtin#string) @@ -1620,6 +1652,8 @@ git_commit [string](https://godoc.org/builtin#string) built [int](https://godoc.org/builtin#int) os_arch [string](https://godoc.org/builtin#string) + +remote_api_version [int](https://godoc.org/builtin#int) ## Errors ### <a name="ContainerNotFound"></a>type ContainerNotFound @@ -1,6 +1,6 @@ GO ?= go DESTDIR ?= / -EPOCH_TEST_COMMIT ?= bd40dcfc2bc7c9014ea1f33482fb63aacbcdfe87 +EPOCH_TEST_COMMIT ?= 4406e1cfeed18fe89c0ad4e20a3c3b2f4b9ffcae HEAD ?= HEAD CHANGELOG_BASE ?= HEAD~ CHANGELOG_TARGET ?= HEAD @@ -171,7 +171,10 @@ localunit: test/goecho/goecho varlink_generate ginkgo: ginkgo -v -tags "$(BUILDTAGS)" -cover -flakeAttempts 3 -progress -trace -noColor test/e2e/. -localintegration: varlink_generate test-binaries ginkgo +ginkgo-remote: + ginkgo -v -tags "$(BUILDTAGS) remoteclient" -cover -flakeAttempts 3 -progress -trace -noColor test/e2e/. + +localintegration: varlink_generate test-binaries ginkgo ginkgo-remote localsystem: .install.ginkgo .install.gomega ginkgo -v -noColor test/system/ @@ -188,7 +191,7 @@ run-perftest: perftest vagrant-check: BOX=$(BOX) sh ./vagrant.sh -binaries: varlink_generate podman +binaries: varlink_generate podman podman-remote install.catatonit: ./hack/install_catatonit.sh @@ -221,6 +224,7 @@ install: .gopathok install.bin install.man install.cni install.systemd 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.man: docs install ${SELINUXOPT} -d -m 755 $(MANDIR)/man1 @@ -3,10 +3,9 @@ # Library and tool for running OCI-based containers in Pods Libpod provides a library for applications looking to use the Container Pod concept, -popularized by Kubernetes. libpod also contains the `podman` tool, for managing -Pods, Containers, and Container Images. +popularized by Kubernetes. Libpod also contains the Pod Manager tool `(Podman)`. Podman manages pods, containers, container images, and container volumes. -* [Latest Version: 0.12.1](https://github.com/containers/libpod/releases/latest) +* [Latest Version: 1.0.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 2d7ca6cbf..b8b475362 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -1,5 +1,43 @@ # Release Notes +## 1.0.0 +### Features +- The `podman exec` command now includes a `--workdir` option to set working directory for the executed command +- The `podman create` and `podman run` commands now support the `--init` flag to use a minimal init process in the container +- Added the `podman image sign` command to GPG sign images +- The `podman run --device` flag now accepts directories, and will added any device nodes in the directory to the container +- Added the `podman play kube` command to create pods and containers from Kubernetes pod YAML + +### Bugfixes +- Fixed a bug where passing `podman create` or `podman run` volumes with an empty host or container path could cause a segfault +- Fixed a bug where `storage.conf` was sometimes ignored for rootless containers +- Fixed a bug where Podman run as root would error if CAP_SYS_RESOURCE was not available +- Fixed a bug where Podman would fail to start containers after a system restart due to an out-of-date default Apparmor profile +- Fixed a bug where Podman's bash completions were not working +- Fixed a bug where `podman login` would use existing login credentials even if new credentials were provided +- Fixed a bug where Podman could create some directories with the wrong permissions, breaking containers with user namespaces +- Fixed a bug where `podman runlabel` was not properly setting container names when the `--name` was specified +- Fixed a bug where `podman runlabel` sometimes included extra spaces in command output +- Fixed a bug where `podman commit` was including invalid port numbers in created images when committing containers with published ports +- Fixed a bug where `podman exec` was not honoring the container's environment variables +- Fixed a bug where `podman run --device` would fail when a symlink to a device was specified +- Fixed a bug where `podman build` was not properly picking up OCI runtime paths specified in `libpod.conf` +- Fixed a bug where Podman would mount `/dev/shm` into the container read-only for read-only containers (`/dev/shm` should always be read-write) +- Fixed a bug where Podman would ignore any mount whose container mountpoint was `/dev/shm` +- Fixed a bug where `podman export` did not work with the default `fuse-overlayfs` storage driver +- Fixed a bug where `podman inspect -f '{{ json .Config }}'` on images would not output anything (it now prints the image's config) +- Fixed a bug where `podman rmi -fa` displayed the wrong error message when trying to remove images used by pod infra containers + +### Misc +- Rootless containers now unconditionally use postrun cleanup processes, ensuring resources are freed when the container stops +- A new version of Buildah is included for `podman build`, featuring improved build speed and numerous bugfixes +- Pulling images has been parallelized, allowing individual layers to be pulled in parallel +- The `podman start --attach` command now defaults the `sig-proxy` option to `true`, matching `podman create` and `podman run` +- The `podman info` command now prints the path of the configuration file controlling container storage +- Added `podman list` and `podman ls` as aliases for `podman ps`, and `podman container ps` and `podman container list` as aliases for `podman container ls` +- Changed `podman generate kube` to generate Kubernetes service YAML in the same file as pod YAML, generating a single file instead of two +- To improve compatability with the Docker command line, `podman inspect -f '{{ json .ContainerConfig }}'` on images is no longer valid; please use `podman inspect -f '{{ json .Config }}'` instead + ## 0.12.1.2 ### Bugfixes - Fixed a bug where an empty path for named volumes could make it impossible to create containers diff --git a/changelog.txt b/changelog.txt index b0680a02c..8ee11cdc4 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,3 +1,100 @@ +- Changelog for v1.0.0 (2018-1-11) + * Update release notes for v1.0 + * Remove clientintegration from Makefile + * Regenerate EasyJSON to fix JSON issues + * Update gitvalidation to avoid reverts w/o signoffs + * Cirrus: Post-Merge Testing for v1.0 Branch + * Move python code from contrib to it's own repo python-podman + * Use defaults if paths are not specified in storage.conf + * (Minor) Cirrus: Print timestamp at start + * fix up sigstore path + * Trivial readme updates + * podman: bump RLIMIT_NOFILE also without CAP_SYS_RESOURCE + * Fix handling of nil volumes + * sign: make all error messages lowercase + * sign: use filepath.Join instead of fmt.Sprintf + * createconfig: always cleanup a rootless container + * Fix 'image trust' from PR1899 + * libpod/image: Use ParseNormalizedNamed in RepoDigests + * apparmor: apply default profile at container initialization + * Fix up image sign and trust + * List the long variant of each option before its shorter counterpart + * Use existing interface to request IP address during restore + * Added checkpoint/restore test for same IP + * Enable checkpoint test with established TCP connections + * .github/ISSUE_TEMPLATE: Suggest '/kind bug' and '/kind feature' + * pkg/hooks/exec: Include failed command in hook errors + * hooks/exec/runtimeconfigfilter: Log config changes + * hooks: Add pre-create hooks for runtime-config manipulation + * Add Validate completions + * Add a --workdir option to 'podman exec' + * Default --sig-proxy to true for 'podman start --attach' + * Test that 'podman start --sig-proxy' does not work without --attach + * [WIP]Support podman image sign + * vendor latest buildah + * Honor image environment variables with exec + * Minor: Remove redundant basename command in ooe.sh + * Rename libpod.Config back to ContainerConfig + * Add ability to build golang remote client + * vendor latest buildah + * Add the configuration file used to setup storage to podman info + * podman: set umask to 022 + * podman-login: adhere to user input + * Vendor in latest containers/buildah code + * Readd Python testing + * Update vendor of runc + * [skip ci] Docs: Add Bot Interactions section + * container runlabel NAME implementation + * Bump time for build_each_commit step + * add container-init support + * If local storage file exists, then use it rather then defaults. + * vendor in new containers/storage + * Fix completions + * Touch up some troubleshooting nits + * Log container command before starting the container + * Use sprintf to generate port numbers while committing + * Add troubleshooting for sparse files + * Fix handling of symbolic links + * podman build is not using the default oci-runtime + * Re-enable checkpoint/restore CI tests on Fedora + * Fixes to handle /dev/shm correctly. + * rootless tests using stop is more reliable + * Allow alias for list, ls, ps to work + * Refactor: use idtools.ParseIDMap instead of bundling own version + * cirrus: Use updated images including new crui + * Switch all referencs to image.ContainerConfig to image.Config + * Allow users to specify a directory for additonal devices + * Change all 'can not' to 'cannot' for proper usage + * Invalid index for array + * Vendor in latest psgo code to fix race conditions + * test: add test for rootless export + * export: fix usage with rootless containers + * rootless: add function to join user and mount namespace + * libpod: always store the conmon pid file + * Use existing CRIU packages in CI setup + * skip test for blkio.weight when kernel does not support it + * Add Play + * Cirrus: Skip build all commits test on master + * prepare for move to validate on 1.11 only + * [skip ci] Gate: Update docs w/ safer local command + * Support podman image trust command + * Makefile: validate that each commit can at least build + * perf test a stress test to profile CPU load of podman + * all flakes must die + * Add information on --restart + * generate service object inline + * Cirrus: One IRC notice only + * docs/tutorials: add a basic network config + * display proper error when rmi -fa with infra containers + * add --get-login command to podman-login. + * Show image only once with images -q + * Add script to create CI VMs for debugging + * Cirrus: Migrate PAPR testing of F28 to Cirrus + * Skip checkpoint tests on Fedora <30 + * Cirrus: Add text editors to cache-images + * Clean up some existing varlink endpoints + * mount: allow mount only when using vfs + - Changelog for v0.12.1.2 (2018-12-13) * Add release notes for 0.12.1.2 * runlabel should sub podman for docker|/usr/bin/docker diff --git a/cmd/podman/commands.go b/cmd/podman/commands.go new file mode 100644 index 000000000..718717009 --- /dev/null +++ b/cmd/podman/commands.go @@ -0,0 +1,150 @@ +// +build !remoteclient + +package main + +import "github.com/urfave/cli" + +func getAppCommands() []cli.Command { + return []cli.Command{ + attachCommand, + commitCommand, + buildCommand, + createCommand, + diffCommand, + execCommand, + exportCommand, + importCommand, + killCommand, + kubeCommand, + loadCommand, + loginCommand, + logoutCommand, + logsCommand, + mountCommand, + pauseCommand, + psCommand, + podCommand, + portCommand, + pushCommand, + playCommand, + restartCommand, + rmCommand, + runCommand, + saveCommand, + searchCommand, + startCommand, + statsCommand, + stopCommand, + topCommand, + umountCommand, + unpauseCommand, + volumeCommand, + waitCommand, + } +} + +func getImageSubCommands() []cli.Command { + return []cli.Command{ + buildCommand, + importCommand, + loadCommand, + pullCommand, + saveCommand, + trustCommand, + signCommand, + } +} + +func getContainerSubCommands() []cli.Command { + return []cli.Command{ + attachCommand, + checkpointCommand, + cleanupCommand, + containerExistsCommand, + commitCommand, + createCommand, + diffCommand, + execCommand, + exportCommand, + killCommand, + logsCommand, + psCommand, + mountCommand, + pauseCommand, + portCommand, + pruneContainersCommand, + refreshCommand, + restartCommand, + restoreCommand, + rmCommand, + runCommand, + runlabelCommand, + startCommand, + statsCommand, + stopCommand, + topCommand, + umountCommand, + unpauseCommand, + // updateCommand, + waitCommand, + } +} +func getMainAppFlags() []cli.Flag { + return []cli.Flag{ + cli.StringFlag{ + Name: "cgroup-manager", + Usage: "cgroup manager to use (cgroupfs or systemd, default systemd)", + }, + cli.StringFlag{ + Name: "cni-config-dir", + Usage: "path of the configuration directory for CNI networks", + }, + cli.StringFlag{ + Name: "conmon", + Usage: "path of the conmon binary", + }, + cli.StringFlag{ + Name: "default-mounts-file", + Usage: "path to default mounts file", + Hidden: true, + }, + cli.StringSliceFlag{ + Name: "hooks-dir", + Usage: "set the OCI hooks directory path (may be set multiple times)", + }, + cli.IntFlag{ + Name: "max-workers", + Usage: "the maximum number of workers for parallel operations", + Hidden: true, + }, + cli.StringFlag{ + Name: "namespace", + Usage: "set the libpod namespace, used to create separate views of the containers and pods on the system", + Value: "", + }, + cli.StringFlag{ + Name: "root", + Usage: "path to the root directory in which data, including images, is stored", + }, + cli.StringFlag{ + Name: "runroot", + Usage: "path to the 'run directory' where all state information is stored", + }, + cli.StringFlag{ + Name: "runtime", + Usage: "path to the OCI-compatible binary used to run containers, default is /usr/bin/runc", + }, + cli.StringFlag{ + Name: "storage-driver, s", + Usage: "select which storage driver is used to manage storage of images and containers (default is overlay)", + }, + cli.StringSliceFlag{ + Name: "storage-opt", + Usage: "used to pass an option to the storage driver", + }, + cli.BoolFlag{ + Name: "syslog", + Usage: "output logging information to syslog as well as the console", + }, + } +} diff --git a/cmd/podman/commands_remoteclient.go b/cmd/podman/commands_remoteclient.go new file mode 100644 index 000000000..6701e14a1 --- /dev/null +++ b/cmd/podman/commands_remoteclient.go @@ -0,0 +1,21 @@ +// +build remoteclient + +package main + +import "github.com/urfave/cli" + +func getAppCommands() []cli.Command { + return []cli.Command{} +} + +func getImageSubCommands() []cli.Command { + return []cli.Command{} +} + +func getContainerSubCommands() []cli.Command { + return []cli.Command{} +} + +func getMainAppFlags() []cli.Flag { + return []cli.Flag{} +} diff --git a/cmd/podman/container.go b/cmd/podman/container.go index 4bb6f287a..acbcbb644 100644 --- a/cmd/podman/container.go +++ b/cmd/podman/container.go @@ -1,52 +1,29 @@ package main import ( + "sort" + "github.com/urfave/cli" ) var ( - subCommands = []cli.Command{ - attachCommand, - checkpointCommand, - cleanupCommand, - containerExistsCommand, - commitCommand, - createCommand, - diffCommand, - execCommand, - exportCommand, + containerSubCommands = []cli.Command{ inspectCommand, - killCommand, - logsCommand, - psCommand, - mountCommand, - pauseCommand, - portCommand, - pruneContainersCommand, - refreshCommand, - restartCommand, - restoreCommand, - rmCommand, - runCommand, - runlabelCommand, - startCommand, - statsCommand, - stopCommand, - topCommand, - umountCommand, - unpauseCommand, - // updateCommand, - waitCommand, } - containerDescription = "Manage containers" containerCommand = cli.Command{ Name: "container", Usage: "Manage Containers", Description: containerDescription, ArgsUsage: "", - Subcommands: subCommands, + Subcommands: getContainerSubCommandsSorted(), UseShortOptionHandling: true, OnUsageError: usageErrorHandler, } ) + +func getContainerSubCommandsSorted() []cli.Command { + containerSubCommands = append(containerSubCommands, getContainerSubCommands()...) + sort.Sort(commandSortedAlpha{containerSubCommands}) + return containerSubCommands +} diff --git a/cmd/podman/create.go b/cmd/podman/create.go index 1aa3425a5..c56efa153 100644 --- a/cmd/podman/create.go +++ b/cmd/podman/create.go @@ -4,6 +4,7 @@ import ( "context" "encoding/json" "fmt" + "io" "io/ioutil" "os" "path/filepath" @@ -128,7 +129,12 @@ func createContainer(c *cli.Context, runtime *libpod.Runtime) (*libpod.Container var data *inspect.ImageData = nil if rootfs == "" && !rootless.SkipStorageSetup() { - newImage, err := runtime.ImageRuntime().New(ctx, c.Args()[0], rtc.SignaturePolicyPath, "", os.Stderr, nil, image.SigningOptions{}, false) + var writer io.Writer + if !c.Bool("quiet") { + writer = os.Stderr + } + + newImage, err := runtime.ImageRuntime().New(ctx, c.Args()[0], rtc.SignaturePolicyPath, "", writer, nil, image.SigningOptions{}, false) if err != nil { return nil, nil, err } @@ -173,7 +179,11 @@ func parseSecurityOpt(config *cc.CreateConfig, securityOpts []string) error { if err != nil { return errors.Wrapf(err, "container %q not found", config.PidMode.Container()) } - labelOpts = append(labelOpts, label.DupSecOpt(ctr.ProcessLabel())...) + secopts, err := label.DupSecOpt(ctr.ProcessLabel()) + if err != nil { + return errors.Wrapf(err, "failed to duplicate label %q ", ctr.ProcessLabel()) + } + labelOpts = append(labelOpts, secopts...) } if config.IpcMode.IsHost() { @@ -183,7 +193,11 @@ func parseSecurityOpt(config *cc.CreateConfig, securityOpts []string) error { if err != nil { return errors.Wrapf(err, "container %q not found", config.IpcMode.Container()) } - labelOpts = append(labelOpts, label.DupSecOpt(ctr.ProcessLabel())...) + secopts, err := label.DupSecOpt(ctr.ProcessLabel()) + if err != nil { + return errors.Wrapf(err, "failed to duplicate label %q ", ctr.ProcessLabel()) + } + labelOpts = append(labelOpts, secopts...) } for _, opt := range securityOpts { diff --git a/cmd/podman/exists.go b/cmd/podman/exists.go index bd1bc24ec..a7601aaa2 100644 --- a/cmd/podman/exists.go +++ b/cmd/podman/exists.go @@ -67,12 +67,12 @@ func imageExistsCmd(c *cli.Context) error { if len(args) > 1 || len(args) < 1 { return errors.New("you may only check for the existence of one image at a time") } - localRuntime, err := adapter.GetRuntime(c) + runtime, err := adapter.GetRuntime(c) if err != nil { return errors.Wrapf(err, "could not get runtime") } - defer localRuntime.Runtime.Shutdown(false) - if _, err := localRuntime.NewImageFromLocal(args[0]); err != nil { + defer runtime.Shutdown(false) + if _, err := runtime.NewImageFromLocal(args[0]); err != nil { //TODO we need to ask about having varlink defined errors exposed //so we can reuse them if errors.Cause(err) == image.ErrNoSuchImage || err.Error() == "io.podman.ImageNotFound" { @@ -88,13 +88,13 @@ func containerExistsCmd(c *cli.Context) error { if len(args) > 1 || len(args) < 1 { return errors.New("you may only check for the existence of one container at a time") } - runtime, err := libpodruntime.GetRuntime(c) + runtime, err := adapter.GetRuntime(c) if err != nil { return errors.Wrapf(err, "could not get runtime") } defer runtime.Shutdown(false) if _, err := runtime.LookupContainer(args[0]); err != nil { - if errors.Cause(err) == libpod.ErrNoSuchCtr { + if errors.Cause(err) == libpod.ErrNoSuchCtr || err.Error() == "io.podman.ContainerNotFound" { os.Exit(1) } return err diff --git a/cmd/podman/formats/formats.go b/cmd/podman/formats/formats.go index 3da0ea385..c454c39bd 100644 --- a/cmd/podman/formats/formats.go +++ b/cmd/podman/formats/formats.go @@ -20,6 +20,8 @@ const ( JSONString = "json" // IDString const to save on duplicates for Go templates IDString = "{{.ID}}" + + parsingErrorStr = "Template parsing error" ) // Writer interface for outputs @@ -96,7 +98,7 @@ func (t StdoutTemplateArray) Out() error { t.Template = strings.Replace(strings.TrimSpace(t.Template[5:]), " ", "\t", -1) headerTmpl, err := template.New("header").Funcs(headerFunctions).Parse(t.Template) if err != nil { - return errors.Wrapf(err, "Template parsing error") + return errors.Wrapf(err, parsingErrorStr) } err = headerTmpl.Execute(w, t.Fields) if err != nil { @@ -107,13 +109,12 @@ func (t StdoutTemplateArray) Out() error { t.Template = strings.Replace(t.Template, " ", "\t", -1) tmpl, err := template.New("image").Funcs(basicFunctions).Parse(t.Template) if err != nil { - return errors.Wrapf(err, "Template parsing error") + return errors.Wrapf(err, parsingErrorStr) } - for i, img := range t.Output { + for i, raw := range t.Output { basicTmpl := tmpl.Funcs(basicFunctions) - err = basicTmpl.Execute(w, img) - if err != nil { - return err + if err := basicTmpl.Execute(w, raw); err != nil { + return errors.Wrapf(err, parsingErrorStr) } if i != len(t.Output)-1 { fmt.Fprintln(w, "") diff --git a/cmd/podman/history.go b/cmd/podman/history.go index 7c8c619c8..8a9b6cd94 100644 --- a/cmd/podman/history.go +++ b/cmd/podman/history.go @@ -7,9 +7,9 @@ import ( "time" "github.com/containers/libpod/cmd/podman/formats" - "github.com/containers/libpod/cmd/podman/libpodruntime" + "github.com/containers/libpod/libpod/adapter" "github.com/containers/libpod/libpod/image" - units "github.com/docker/go-units" + "github.com/docker/go-units" "github.com/pkg/errors" "github.com/urfave/cli" ) @@ -72,7 +72,7 @@ func historyCmd(c *cli.Context) error { return err } - runtime, err := libpodruntime.GetRuntime(c) + runtime, err := adapter.GetRuntime(c) if err != nil { return errors.Wrapf(err, "could not get runtime") } @@ -88,7 +88,7 @@ func historyCmd(c *cli.Context) error { return errors.Errorf("podman history takes at most 1 argument") } - image, err := runtime.ImageRuntime().NewFromLocal(args[0]) + image, err := runtime.NewImageFromLocal(args[0]) if err != nil { return err } diff --git a/cmd/podman/image.go b/cmd/podman/image.go index 557fc1056..a51a90b0e 100644 --- a/cmd/podman/image.go +++ b/cmd/podman/image.go @@ -1,36 +1,36 @@ package main import ( + "sort" + "github.com/urfave/cli" ) var ( imageSubCommands = []cli.Command{ - buildCommand, historyCommand, - importCommand, imageExistsCommand, inspectCommand, - loadCommand, lsImagesCommand, pruneImagesCommand, pullCommand, - pushCommand, rmImageCommand, - saveCommand, tagCommand, - trustCommand, - signCommand, } - imageDescription = "Manage images" imageCommand = cli.Command{ Name: "image", Usage: "Manage images", Description: imageDescription, ArgsUsage: "", - Subcommands: imageSubCommands, + Subcommands: getImageSubCommandsSorted(), UseShortOptionHandling: true, OnUsageError: usageErrorHandler, } ) + +func getImageSubCommandsSorted() []cli.Command { + imageSubCommands = append(imageSubCommands, getImageSubCommands()...) + sort.Sort(commandSortedAlpha{imageSubCommands}) + return imageSubCommands +} diff --git a/cmd/podman/images.go b/cmd/podman/images.go index 8b8ce78bd..d4f405975 100644 --- a/cmd/podman/images.go +++ b/cmd/podman/images.go @@ -2,8 +2,6 @@ package main import ( "context" - "github.com/containers/libpod/cmd/podman/imagefilters" - "github.com/containers/libpod/libpod/adapter" "reflect" "sort" "strings" @@ -11,6 +9,8 @@ import ( "unicode" "github.com/containers/libpod/cmd/podman/formats" + "github.com/containers/libpod/cmd/podman/imagefilters" + "github.com/containers/libpod/libpod/adapter" "github.com/containers/libpod/libpod/image" "github.com/docker/go-units" "github.com/opencontainers/go-digest" @@ -152,13 +152,13 @@ func imagesCmd(c *cli.Context) error { return err } - localRuntime, err := adapter.GetRuntime(c) + runtime, err := adapter.GetRuntime(c) if err != nil { return errors.Wrapf(err, "Could not get runtime") } - defer localRuntime.Runtime.Shutdown(false) + defer runtime.Shutdown(false) if len(c.Args()) == 1 { - newImage, err = localRuntime.NewImageFromLocal(c.Args().Get(0)) + newImage, err = runtime.NewImageFromLocal(c.Args().Get(0)) if err != nil { return err } @@ -171,7 +171,7 @@ func imagesCmd(c *cli.Context) error { ctx := getContext() if len(c.StringSlice("filter")) > 0 || newImage != nil { - filterFuncs, err = CreateFilterFuncs(ctx, localRuntime, c, newImage) + filterFuncs, err = CreateFilterFuncs(ctx, runtime, c, newImage) if err != nil { return err } @@ -188,14 +188,7 @@ func imagesCmd(c *cli.Context) error { } opts.outputformat = opts.setOutputFormat() - /* - podman does not implement --all for images - - intermediate images are only generated during the build process. they are - children to the image once built. until buildah supports caching builds, - it will not generate these intermediate images. - */ - images, err := localRuntime.GetImages() + images, err := runtime.GetImages() if err != nil { return errors.Wrapf(err, "unable to get images") } diff --git a/cmd/podman/images_prune.go b/cmd/podman/images_prune.go index 06879e02d..aef387732 100644 --- a/cmd/podman/images_prune.go +++ b/cmd/podman/images_prune.go @@ -2,7 +2,7 @@ package main import ( "fmt" - "github.com/containers/libpod/cmd/podman/libpodruntime" + "github.com/containers/libpod/libpod/adapter" "github.com/pkg/errors" "github.com/urfave/cli" ) @@ -13,33 +13,36 @@ var ( Removes all unnamed images from local storage ` - + pruneImageFlags = []cli.Flag{ + cli.BoolFlag{ + Name: "all, a", + Usage: "remove all unused images, not just dangling ones", + }, + } pruneImagesCommand = cli.Command{ Name: "prune", Usage: "Remove unused images", Description: pruneImagesDescription, Action: pruneImagesCmd, OnUsageError: usageErrorHandler, + Flags: pruneImageFlags, } ) func pruneImagesCmd(c *cli.Context) error { - runtime, err := libpodruntime.GetRuntime(c) + runtime, err := adapter.GetRuntime(c) if err != nil { return errors.Wrapf(err, "could not get runtime") } defer runtime.Shutdown(false) - pruneImages, err := runtime.ImageRuntime().GetPruneImages() - if err != nil { - return err - } - - for _, i := range pruneImages { - if err := i.Remove(true); err != nil { - return errors.Wrapf(err, "failed to remove %s", i.ID()) + // Call prune; if any cids are returned, print them and then + // return err in case an error also came up + pruneCids, err := runtime.PruneImages(c.Bool("all")) + if len(pruneCids) > 0 { + for _, cid := range pruneCids { + fmt.Println(cid) } - fmt.Println(i.ID()) } - return nil + return err } diff --git a/cmd/podman/info.go b/cmd/podman/info.go index 4b80f94db..f5f91b603 100644 --- a/cmd/podman/info.go +++ b/cmd/podman/info.go @@ -1,11 +1,13 @@ package main import ( - "github.com/containers/libpod/libpod/adapter" - "runtime" + "fmt" + rt "runtime" "github.com/containers/libpod/cmd/podman/formats" "github.com/containers/libpod/libpod" + "github.com/containers/libpod/libpod/adapter" + "github.com/containers/libpod/version" "github.com/pkg/errors" "github.com/urfave/cli" ) @@ -27,7 +29,7 @@ var ( Usage: "display additional debug information", }, cli.StringFlag{ - Name: "format", + Name: "format, f", Usage: "Change the output format to JSON or a Go template", }, } @@ -38,21 +40,26 @@ func infoCmd(c *cli.Context) error { return err } info := map[string]interface{}{} + remoteClientInfo := map[string]interface{}{} - localRuntime, err := adapter.GetRuntime(c) + runtime, err := adapter.GetRuntime(c) if err != nil { return errors.Wrapf(err, "could not get runtime") } - defer localRuntime.Runtime.Shutdown(false) + defer runtime.Shutdown(false) - infoArr, err := localRuntime.Runtime.Info() + infoArr, err := runtime.Info() if err != nil { return errors.Wrapf(err, "error getting info") } + if runtime.Remote { + remoteClientInfo["RemoteAPI Version"] = version.RemoteAPIVersion + remoteClientInfo["Podman Version"] = version.Version + remoteClientInfo["OS Arch"] = fmt.Sprintf("%s/%s", rt.GOOS, rt.GOARCH) + infoArr = append(infoArr, libpod.InfoData{Type: "client", Data: remoteClientInfo}) + } - // TODO This is no a problem child because we don't know if we should add information - // TODO about the client or the backend. Only do for traditional podman for now. - if !localRuntime.Remote && c.Bool("debug") { + if !runtime.Remote && c.Bool("debug") { debugInfo := debugInfo(c) infoArr = append(infoArr, libpod.InfoData{Type: "debug", Data: debugInfo}) } @@ -80,8 +87,8 @@ func infoCmd(c *cli.Context) error { // top-level "debug" info func debugInfo(c *cli.Context) map[string]interface{} { info := map[string]interface{}{} - info["compiler"] = runtime.Compiler - info["go version"] = runtime.Version() + info["compiler"] = rt.Compiler + info["go version"] = rt.Version() info["podman version"] = c.App.Version version, _ := libpod.GetVersion() info["git commit"] = version.GitCommit diff --git a/cmd/podman/inspect.go b/cmd/podman/inspect.go index 6ffcde55f..1346da9fb 100644 --- a/cmd/podman/inspect.go +++ b/cmd/podman/inspect.go @@ -2,12 +2,13 @@ package main import ( "context" + "encoding/json" "strings" "github.com/containers/libpod/cmd/podman/formats" - "github.com/containers/libpod/cmd/podman/libpodruntime" "github.com/containers/libpod/cmd/podman/shared" - "github.com/containers/libpod/libpod" + "github.com/containers/libpod/libpod/adapter" + cc "github.com/containers/libpod/pkg/spec" "github.com/containers/libpod/pkg/util" "github.com/pkg/errors" "github.com/urfave/cli" @@ -31,7 +32,7 @@ var ( Usage: "Change the output format to a Go template", }, cli.BoolFlag{ - Name: "size", + Name: "size, s", Usage: "Display total file size if the type is container", }, LatestFlag, @@ -39,7 +40,7 @@ var ( inspectDescription = "This displays the low-level information on containers and images identified by name or ID. By default, this will render all results in a JSON array. If the container and image have the same name, this will return container JSON for unspecified type." inspectCommand = cli.Command{ Name: "inspect", - Usage: "Displays the configuration of a container or image", + Usage: "Display the configuration of a container or image", Description: inspectDescription, Flags: sortFlags(inspectFlags), Action: inspectCmd, @@ -63,7 +64,7 @@ func inspectCmd(c *cli.Context) error { return err } - runtime, err := libpodruntime.GetRuntime(c) + runtime, err := adapter.GetRuntime(c) if err != nil { return errors.Wrapf(err, "error creating libpod runtime") } @@ -87,6 +88,9 @@ func inspectCmd(c *cli.Context) error { } inspectedObjects, iterateErr := iterateInput(getContext(), c, args, runtime, inspectType) + if iterateErr != nil { + return iterateErr + } var out formats.Writer if outputFormat != "" && outputFormat != formats.JSONString { @@ -97,12 +101,11 @@ func inspectCmd(c *cli.Context) error { out = formats.JSONStructArray{Output: inspectedObjects} } - formats.Writer(out).Out() - return iterateErr + return formats.Writer(out).Out() } // func iterateInput iterates the images|containers the user has requested and returns the inspect data and error -func iterateInput(ctx context.Context, c *cli.Context, args []string, runtime *libpod.Runtime, inspectType string) ([]interface{}, error) { +func iterateInput(ctx context.Context, c *cli.Context, args []string, runtime *adapter.LocalRuntime, inspectType string) ([]interface{}, error) { var ( data interface{} inspectedItems []interface{} @@ -122,13 +125,18 @@ func iterateInput(ctx context.Context, c *cli.Context, args []string, runtime *l inspectError = errors.Wrapf(err, "error getting libpod container inspect data %s", ctr.ID()) break } - data, err = shared.GetCtrInspectInfo(ctr, libpodInspectData) + artifact, err := getArtifact(ctr) + if inspectError != nil { + inspectError = err + break + } + data, err = shared.GetCtrInspectInfo(ctr.Config(), libpodInspectData, artifact) if err != nil { inspectError = errors.Wrapf(err, "error parsing container data %q", ctr.ID()) break } case inspectTypeImage: - image, err := runtime.ImageRuntime().NewFromLocal(input) + image, err := runtime.NewImageFromLocal(input) if err != nil { inspectError = errors.Wrapf(err, "error getting image %q", input) break @@ -141,7 +149,7 @@ func iterateInput(ctx context.Context, c *cli.Context, args []string, runtime *l case inspectAll: ctr, err := runtime.LookupContainer(input) if err != nil { - image, err := runtime.ImageRuntime().NewFromLocal(input) + image, err := runtime.NewImageFromLocal(input) if err != nil { inspectError = errors.Wrapf(err, "error getting image %q", input) break @@ -157,7 +165,12 @@ func iterateInput(ctx context.Context, c *cli.Context, args []string, runtime *l inspectError = errors.Wrapf(err, "error getting libpod container inspect data %s", ctr.ID()) break } - data, err = shared.GetCtrInspectInfo(ctr, libpodInspectData) + artifact, inspectError := getArtifact(ctr) + if inspectError != nil { + inspectError = err + break + } + data, err = shared.GetCtrInspectInfo(ctr.Config(), libpodInspectData, artifact) if err != nil { inspectError = errors.Wrapf(err, "error parsing container data %s", ctr.ID()) break @@ -170,3 +183,15 @@ func iterateInput(ctx context.Context, c *cli.Context, args []string, runtime *l } return inspectedItems, inspectError } + +func getArtifact(ctr *adapter.Container) (*cc.CreateConfig, error) { + var createArtifact cc.CreateConfig + artifact, err := ctr.GetArtifact("create-config") + if err != nil { + return nil, err + } + if err := json.Unmarshal(artifact, &createArtifact); err != nil { + return nil, err + } + return &createArtifact, nil +} diff --git a/cmd/podman/main.go b/cmd/podman/main.go index ce60bbfb7..c10590006 100644 --- a/cmd/podman/main.go +++ b/cmd/podman/main.go @@ -6,6 +6,7 @@ import ( "os" "os/exec" "runtime/pprof" + "sort" "syscall" "github.com/containers/libpod/libpod" @@ -47,6 +48,28 @@ var cmdsNotRequiringRootless = map[string]bool{ "top": true, } +type commandSorted []cli.Command + +func (a commandSorted) Len() int { return len(a) } +func (a commandSorted) Swap(i, j int) { a[i], a[j] = a[j], a[i] } + +type commandSortedAlpha struct{ commandSorted } + +func (a commandSortedAlpha) Less(i, j int) bool { + return a.commandSorted[i].Name < a.commandSorted[j].Name +} + +type flagSorted []cli.Flag + +func (a flagSorted) Len() int { return len(a) } +func (a flagSorted) Swap(i, j int) { a[i], a[j] = a[j], a[i] } + +type flagSortedAlpha struct{ flagSorted } + +func (a flagSortedAlpha) Less(i, j int) bool { + return a.flagSorted[i].GetName() < a.flagSorted[j].GetName() +} + func main() { debug := false cpuProfile := false @@ -64,52 +87,21 @@ func main() { app.Version = version.Version app.Commands = []cli.Command{ - attachCommand, - commitCommand, containerCommand, - buildCommand, - createCommand, - diffCommand, - execCommand, - exportCommand, historyCommand, imageCommand, imagesCommand, - importCommand, infoCommand, inspectCommand, - killCommand, - kubeCommand, - loadCommand, - loginCommand, - logoutCommand, - logsCommand, - mountCommand, - pauseCommand, - psCommand, - podCommand, - portCommand, pullCommand, - pushCommand, - playCommand, - restartCommand, - rmCommand, rmiCommand, - runCommand, - saveCommand, - searchCommand, - startCommand, - statsCommand, - stopCommand, tagCommand, - topCommand, - umountCommand, - unpauseCommand, versionCommand, - volumeCommand, - waitCommand, } + app.Commands = append(app.Commands, getAppCommands()...) + sort.Sort(commandSortedAlpha{app.Commands}) + if varlinkCommand != nil { app.Commands = append(app.Commands, *varlinkCommand) } @@ -192,79 +184,28 @@ func main() { } app.Flags = []cli.Flag{ cli.StringFlag{ - Name: "cgroup-manager", - Usage: "cgroup manager to use (cgroupfs or systemd, default systemd)", - }, - cli.StringFlag{ - Name: "cni-config-dir", - Usage: "path of the configuration directory for CNI networks", - }, - cli.StringFlag{ Name: "config, c", Usage: "path of a libpod config file detailing container server configuration options", Hidden: true, }, cli.StringFlag{ - Name: "conmon", - Usage: "path of the conmon binary", - }, - cli.StringFlag{ Name: "cpu-profile", Usage: "path for the cpu profiling results", }, cli.StringFlag{ - Name: "default-mounts-file", - Usage: "path to default mounts file", - Hidden: true, - }, - cli.StringSliceFlag{ - Name: "hooks-dir", - Usage: "set the OCI hooks directory path (may be set multiple times)", - }, - cli.IntFlag{ - Name: "max-workers", - Usage: "the maximum number of workers for parallel operations", - Hidden: true, - }, - cli.StringFlag{ Name: "log-level", Usage: "log messages above specified level: debug, info, warn, error (default), fatal or panic", Value: "error", }, cli.StringFlag{ - Name: "namespace", - Usage: "set the libpod namespace, used to create separate views of the containers and pods on the system", - Value: "", - }, - cli.StringFlag{ - Name: "root", - Usage: "path to the root directory in which data, including images, is stored", - }, - cli.StringFlag{ Name: "tmpdir", Usage: "path to the tmp directory", }, - cli.StringFlag{ - Name: "runroot", - Usage: "path to the 'run directory' where all state information is stored", - }, - cli.StringFlag{ - Name: "runtime", - Usage: "path to the OCI-compatible binary used to run containers, default is /usr/bin/runc", - }, - cli.StringFlag{ - Name: "storage-driver, s", - Usage: "select which storage driver is used to manage storage of images and containers (default is overlay)", - }, - cli.StringSliceFlag{ - Name: "storage-opt", - Usage: "used to pass an option to the storage driver", - }, - cli.BoolFlag{ - Name: "syslog", - Usage: "output logging information to syslog as well as the console", - }, } + + app.Flags = append(app.Flags, getMainAppFlags()...) + sort.Sort(flagSortedAlpha{app.Flags}) + // Check if /etc/containers/registries.conf exists when running in // in a local environment. CheckForRegistries() diff --git a/cmd/podman/mount.go b/cmd/podman/mount.go index c91115597..86a6b2ad1 100644 --- a/cmd/podman/mount.go +++ b/cmd/podman/mount.go @@ -24,13 +24,18 @@ var ( mountFlags = []cli.Flag{ cli.BoolFlag{ - Name: "notruncate", - Usage: "do not truncate output", + Name: "all, a", + Usage: "Mount all containers", }, cli.StringFlag{ Name: "format", Usage: "Change the output format to Go template", }, + cli.BoolFlag{ + Name: "notruncate", + Usage: "do not truncate output", + }, + LatestFlag, } mountCommand = cli.Command{ Name: "mount", @@ -80,20 +85,31 @@ func mountCmd(c *cli.Context) error { } } + if c.Bool("all") && c.Bool("latest") { + return errors.Errorf("--all and --latest cannot be used together") + } + + mountContainers, err := getAllOrLatestContainers(c, runtime, -1, "all") + if err != nil { + if len(mountContainers) == 0 { + return err + } + fmt.Println(err.Error()) + } + formats := map[string]bool{ "": true, of.JSONString: true, } - args := c.Args() json := c.String("format") == of.JSONString if !formats[c.String("format")] { return errors.Errorf("%q is not a supported format", c.String("format")) } var lastError error - if len(args) > 0 { - for _, name := range args { + if len(mountContainers) > 0 { + for _, ctr := range mountContainers { if json { if lastError != nil { logrus.Error(lastError) @@ -101,14 +117,6 @@ func mountCmd(c *cli.Context) error { lastError = errors.Wrapf(err, "json option cannot be used with a container id") continue } - ctr, err := runtime.LookupContainer(name) - if err != nil { - if lastError != nil { - logrus.Error(lastError) - } - lastError = errors.Wrapf(err, "error looking up container %q", name) - continue - } mountPoint, err := ctr.Mount() if err != nil { if lastError != nil { diff --git a/cmd/podman/pause.go b/cmd/podman/pause.go index fcb2f3cb8..9da6abf4b 100644 --- a/cmd/podman/pause.go +++ b/cmd/podman/pause.go @@ -25,7 +25,7 @@ var ( ` pauseCommand = cli.Command{ Name: "pause", - Usage: "Pauses all the processes in one or more containers", + Usage: "Pause all the processes in one or more containers", Description: pauseDescription, Flags: pauseFlags, Action: pauseCmd, diff --git a/cmd/podman/ps.go b/cmd/podman/ps.go index 0ad3f4c73..1708c671c 100644 --- a/cmd/podman/ps.go +++ b/cmd/podman/ps.go @@ -606,19 +606,50 @@ func portsToString(ports []ocicni.PortMapping) string { } func printFormat(format string, containers []shared.PsContainerOutput) error { - out := template.New("output") - out, err := out.Parse(format + "\n") + // return immediately if no containers are present + if len(containers) == 0 { + return nil + } + + // Use a tabwriter to align column format + w := tabwriter.NewWriter(os.Stdout, 0, 0, 3, ' ', 0) + + // Make a map of the field names for the headers + headerNames := make(map[string]string) + v := reflect.ValueOf(containers[0]) + t := v.Type() + for i := 0; i < t.NumField(); i++ { + headerNames[t.Field(i).Name] = t.Field(i).Name + } + + // Spit out the header if "table" is present in the format + if strings.HasPrefix(format, "table") { + hformat := strings.Replace(strings.TrimSpace(format[5:]), " ", "\t", -1) + format = hformat + headerTmpl, err := template.New("header").Parse(hformat) + if err != nil { + return err + } + if err := headerTmpl.Execute(w, headerNames); err != nil { + return err + } + fmt.Fprintln(w, "") + } + // Spit out the data rows now + dataTmpl, err := template.New("data").Parse(format) if err != nil { return err } + for _, container := range containers { - if err := out.Execute(os.Stdout, container); err != nil { + if err := dataTmpl.Execute(w, container); err != nil { return err } - + fmt.Fprintln(w, "") } - return nil + // Flush the writer + return w.Flush() } func dumpJSON(containers []shared.PsContainerOutput) error { diff --git a/cmd/podman/pull.go b/cmd/podman/pull.go index 47130805e..2a78d0c54 100644 --- a/cmd/podman/pull.go +++ b/cmd/podman/pull.go @@ -9,7 +9,7 @@ import ( dockerarchive "github.com/containers/image/docker/archive" "github.com/containers/image/transports/alltransports" "github.com/containers/image/types" - "github.com/containers/libpod/cmd/podman/libpodruntime" + "github.com/containers/libpod/libpod/adapter" image2 "github.com/containers/libpod/libpod/image" "github.com/containers/libpod/pkg/util" "github.com/pkg/errors" @@ -64,7 +64,7 @@ specified, the image with the 'latest' tag (if it exists) is pulled // pullCmd gets the data from the command line and calls pullImage // to copy an image from a registry to a local machine func pullCmd(c *cli.Context) error { - runtime, err := libpodruntime.GetRuntime(c) + runtime, err := adapter.GetRuntime(c) if err != nil { return errors.Wrapf(err, "could not get runtime") } @@ -116,14 +116,14 @@ func pullCmd(c *cli.Context) error { if err != nil { return errors.Wrapf(err, "error parsing %q", image) } - newImage, err := runtime.ImageRuntime().LoadFromArchiveReference(getContext(), srcRef, c.String("signature-policy"), writer) + newImage, err := runtime.LoadFromArchiveReference(getContext(), srcRef, c.String("signature-policy"), writer) if err != nil { return errors.Wrapf(err, "error pulling image from %q", image) } imgID = newImage[0].ID() } else { authfile := getAuthFile(c.String("authfile")) - newImage, err := runtime.ImageRuntime().New(getContext(), image, c.String("signature-policy"), authfile, writer, &dockerRegistryOptions, image2.SigningOptions{}, true) + newImage, err := runtime.New(getContext(), image, c.String("signature-policy"), authfile, writer, &dockerRegistryOptions, image2.SigningOptions{}, true) if err != nil { return errors.Wrapf(err, "error pulling image %q", image) } diff --git a/cmd/podman/rmi.go b/cmd/podman/rmi.go index 5e8ac81a2..c904f2f92 100644 --- a/cmd/podman/rmi.go +++ b/cmd/podman/rmi.go @@ -4,8 +4,7 @@ import ( "fmt" "os" - "github.com/containers/libpod/cmd/podman/libpodruntime" - "github.com/containers/libpod/libpod/image" + "github.com/containers/libpod/libpod/adapter" "github.com/containers/storage" "github.com/pkg/errors" "github.com/urfave/cli" @@ -25,7 +24,7 @@ var ( } rmiCommand = cli.Command{ Name: "rmi", - Usage: "Removes one or more images from local storage", + Usage: "Remove one or more images from local storage", Description: rmiDescription, Action: rmiCmd, ArgsUsage: "IMAGE-NAME-OR-ID [...]", @@ -58,7 +57,7 @@ func rmiCmd(c *cli.Context) error { return err } removeAll := c.Bool("all") - runtime, err := libpodruntime.GetRuntime(c) + runtime, err := adapter.GetRuntime(c) if err != nil { return errors.Wrapf(err, "could not get runtime") } @@ -74,7 +73,7 @@ func rmiCmd(c *cli.Context) error { images := args[:] - removeImage := func(img *image.Image) { + removeImage := func(img *adapter.ContainerImage) { deleted = true msg, deleteErr = runtime.RemoveImage(ctx, img, c.Bool("force")) if deleteErr != nil { @@ -91,8 +90,8 @@ func rmiCmd(c *cli.Context) error { } if removeAll { - var imagesToDelete []*image.Image - imagesToDelete, err = runtime.ImageRuntime().GetImages() + var imagesToDelete []*adapter.ContainerImage + imagesToDelete, err = runtime.GetImages() if err != nil { return errors.Wrapf(err, "unable to query local images") } @@ -112,7 +111,7 @@ func rmiCmd(c *cli.Context) error { removeImage(i) } lastNumberofImages = len(imagesToDelete) - imagesToDelete, err = runtime.ImageRuntime().GetImages() + imagesToDelete, err = runtime.GetImages() if err != nil { return err } @@ -130,7 +129,7 @@ func rmiCmd(c *cli.Context) error { // See https://github.com/containers/libpod/issues/930 as // an exemplary inconsistency issue. for _, i := range images { - newImage, err := runtime.ImageRuntime().NewFromLocal(i) + newImage, err := runtime.NewImageFromLocal(i) if err != nil { fmt.Fprintln(os.Stderr, err) continue diff --git a/cmd/podman/shared/container.go b/cmd/podman/shared/container.go index a904ef75a..9040c4a5c 100644 --- a/cmd/podman/shared/container.go +++ b/cmd/podman/shared/container.go @@ -2,7 +2,6 @@ package shared import ( "context" - "encoding/json" "fmt" "github.com/google/shlex" "io" @@ -446,8 +445,7 @@ func getStrFromSquareBrackets(cmd string) string { // GetCtrInspectInfo takes container inspect data and collects all its info into a ContainerData // structure for inspection related methods -func GetCtrInspectInfo(ctr *libpod.Container, ctrInspectData *inspect.ContainerInspectData) (*inspect.ContainerData, error) { - config := ctr.Config() +func GetCtrInspectInfo(config *libpod.ContainerConfig, ctrInspectData *inspect.ContainerInspectData, createArtifact *cc.CreateConfig) (*inspect.ContainerData, error) { spec := config.Spec cpus, mems, period, quota, realtimePeriod, realtimeRuntime, shares := getCPUInfo(spec) @@ -456,16 +454,6 @@ func GetCtrInspectInfo(ctr *libpod.Container, ctrInspectData *inspect.ContainerI pidsLimit := getPidsInfo(spec) cgroup := getCgroup(spec) - var createArtifact cc.CreateConfig - artifact, err := ctr.GetArtifact("create-config") - if err == nil { - if err := json.Unmarshal(artifact, &createArtifact); err != nil { - return nil, err - } - } else { - logrus.Errorf("couldn't get some inspect information, error getting artifact %q: %v", ctr.ID(), err) - } - data := &inspect.ContainerData{ ctrInspectData, &inspect.HostConfig{ @@ -493,7 +481,7 @@ func GetCtrInspectInfo(ctr *libpod.Container, ctrInspectData *inspect.ContainerI PidsLimit: pidsLimit, Privileged: config.Privileged, ReadonlyRootfs: spec.Root.Readonly, - Runtime: ctr.RuntimeName(), + Runtime: config.OCIRuntime, NetworkMode: string(createArtifact.NetMode), IpcMode: string(createArtifact.IpcMode), Cgroup: cgroup, diff --git a/cmd/podman/tag.go b/cmd/podman/tag.go index c99e5d173..d19cf69a2 100644 --- a/cmd/podman/tag.go +++ b/cmd/podman/tag.go @@ -23,13 +23,13 @@ func tagCmd(c *cli.Context) error { if len(args) < 2 { return errors.Errorf("image name and at least one new name must be specified") } - localRuntime, err := adapter.GetRuntime(c) + runtime, err := adapter.GetRuntime(c) if err != nil { return errors.Wrapf(err, "could not create runtime") } - defer localRuntime.Runtime.Shutdown(false) + defer runtime.Shutdown(false) - newImage, err := localRuntime.NewImageFromLocal(args[0]) + newImage, err := runtime.NewImageFromLocal(args[0]) if err != nil { return err } diff --git a/cmd/podman/umount.go b/cmd/podman/umount.go index 24f0f178b..42f169228 100644 --- a/cmd/podman/umount.go +++ b/cmd/podman/umount.go @@ -21,6 +21,7 @@ var ( Name: "force, f", Usage: "force the complete umount all of the currently mounted containers", }, + LatestFlag, } description = ` @@ -33,7 +34,7 @@ An unmount can be forced with the --force flag. umountCommand = cli.Command{ Name: "umount", Aliases: []string{"unmount"}, - Usage: "Unmounts working container's root filesystem", + Usage: "Unmount working container's root filesystem", Description: description, Flags: sortFlags(umountFlags), Action: umountCmd, @@ -51,59 +52,37 @@ func umountCmd(c *cli.Context) error { force := c.Bool("force") umountAll := c.Bool("all") - args := c.Args() - if len(args) == 0 && !umountAll { - return errors.Errorf("container ID must be specified") + if err := checkAllAndLatest(c); err != nil { + return err } - if len(args) > 0 && umountAll { - return errors.Errorf("when using the --all switch, you may not pass any container IDs") + + containers, err := getAllOrLatestContainers(c, runtime, -1, "all") + if err != nil { + if len(containers) == 0 { + return err + } + fmt.Println(err.Error()) } umountContainerErrStr := "error unmounting container" var lastError error - if len(args) > 0 { - for _, name := range args { - ctr, err := runtime.LookupContainer(name) - if err != nil { - if lastError != nil { - logrus.Error(lastError) - } - lastError = errors.Wrapf(err, "%s %s", umountContainerErrStr, name) - continue - } - - if err = ctr.Unmount(force); err != nil { - if lastError != nil { - logrus.Error(lastError) - } - lastError = errors.Wrapf(err, "%s %s", umountContainerErrStr, name) - continue - } - fmt.Printf("%s\n", ctr.ID()) - } - } else { - containers, err := runtime.GetContainers() - if err != nil { - return errors.Wrapf(err, "error reading Containers") + for _, ctr := range containers { + ctrState, err := ctr.State() + if ctrState == libpod.ContainerStateRunning || err != nil { + continue } - for _, ctr := range containers { - ctrState, err := ctr.State() - if ctrState == libpod.ContainerStateRunning || err != nil { - continue - } - if err = ctr.Unmount(force); err != nil { - if umountAll && errors.Cause(err) == storage.ErrLayerNotMounted { - continue - } - if lastError != nil { - logrus.Error(lastError) - } - lastError = errors.Wrapf(err, "%s %s", umountContainerErrStr, ctr.ID()) + if err = ctr.Unmount(force); err != nil { + if umountAll && errors.Cause(err) == storage.ErrLayerNotMounted { continue } - fmt.Printf("%s\n", ctr.ID()) + if lastError != nil { + logrus.Error(lastError) + } + lastError = errors.Wrapf(err, "%s %s", umountContainerErrStr, ctr.ID()) + continue } + fmt.Printf("%s\n", ctr.ID()) } return lastError } diff --git a/cmd/podman/varlink/io.podman.varlink b/cmd/podman/varlink/io.podman.varlink index a3e8c050e..8b02057a1 100644 --- a/cmd/podman/varlink/io.podman.varlink +++ b/cmd/podman/varlink/io.podman.varlink @@ -9,7 +9,8 @@ type Version ( go_version: string, git_commit: string, built: int, - os_arch: string + os_arch: string, + remote_api_version: int ) type NotImplemented ( @@ -61,7 +62,7 @@ type ImageSearch ( star_count: int ) -# ListContainer is the returned struct for an individual container +# ListContainerData is the returned struct for an individual container type ListContainerData ( id: string, image: string, @@ -986,17 +987,15 @@ method ContainerRunlabel(runlabel: Runlabel) -> () # of strings # #### Example # ~~~ -# $ varlink call -m unix:/run/podman/io.podman/io.podman.ListContainerMounts +# $ varlink call unix:/run/podman/io.podman/io.podman.ListContainerMounts # { -# "mounts": [ -# "/var/lib/containers/storage/overlay/b215fb622c65ba3b06c6d2341be80b76a9de7ae415ce419e65228873d4f0dcc8/merged", -# "/var/lib/containers/storage/overlay/5eaf806073f79c0ed9a695180ad598e34f963f7407da1d2ccf3560bdab49b26f/merged", -# "/var/lib/containers/storage/overlay/1ecb6b1dbb251737c7a24a31869096839c3719d8b250bf075f75172ddcc701e1/merged", -# "/var/lib/containers/storage/overlay/7137b28a3c422165fe920cba851f2f8da271c6b5908672c451ebda03ad3919e2/merged" -# ] +# "mounts": { +# "04e4c255269ed2545e7f8bd1395a75f7949c50c223415c00c1d54bfa20f3b3d9": "/var/lib/containers/storage/overlay/a078925828f57e20467ca31cfca8a849210d21ec7e5757332b72b6924f441c17/merged", +# "1d58c319f9e881a644a5122ff84419dccf6d138f744469281446ab243ef38924": "/var/lib/containers/storage/overlay/948fcf93f8cb932f0f03fd52e3180a58627d547192ffe3b88e0013b98ddcd0d2/merged" +# } # } # ~~~ -method ListContainerMounts() -> (mounts: []string) +method ListContainerMounts() -> (mounts: [string]string) # MountContainer mounts a container by name or full/partial ID. Upon a successful mount, the destination # mount is returned as a string. @@ -1018,7 +1017,7 @@ method UnmountContainer(name: string, force: bool) -> () # ImagesPrune removes all unused images from the local store. Upon successful pruning, # the IDs of the removed images are returned. -method ImagesPrune() -> (pruned: []string) +method ImagesPrune(all: bool) -> (pruned: []string) # This function is not implemented yet. method ListContainerPorts(name: string) -> (notimplemented: NotImplemented) @@ -1035,6 +1034,22 @@ method GenerateKubeService() -> (notimplemented: NotImplemented) # like that created by GenerateKube. See also [GenerateKube](GenerateKube). method ReplayKube() -> (notimplemented: NotImplemented) +# ContainerConfig returns a container's config in string form. This call is for +# development of Podman only and generally should not be used. +method ContainerConfig(name: string) -> (config: string) + +# ContainerArtifacts returns a container's artifacts in string form. This call is for +# development of Podman only and generally should not be used. +method ContainerArtifacts(name: string, artifactName: string) -> (config: string) + +# ContainerInspectData returns a container's inspect data in string form. This call is for +# development of Podman only and generally should not be used. +method ContainerInspectData(name: string) -> (config: string) + +# ContainerStateData returns a container's state config in string form. This call is for +# development of Podman only and generally should not be used. +method ContainerStateData(name: string) -> (config: string) + # ImageNotFound means the image could not be found by the provided name or ID in local storage. error ImageNotFound (name: string) diff --git a/cmd/podman/version.go b/cmd/podman/version.go index d81deb696..ce773ee2e 100644 --- a/cmd/podman/version.go +++ b/cmd/podman/version.go @@ -2,6 +2,8 @@ package main import ( "fmt" + "os" + "text/tabwriter" "time" "github.com/containers/libpod/cmd/podman/formats" @@ -26,20 +28,22 @@ func versionCmd(c *cli.Context) error { default: out = formats.StdoutTemplate{Output: output, Template: versionOutputFormat} } - formats.Writer(out).Out() - return nil + return formats.Writer(out).Out() } - fmt.Println("Version: ", output.Version) - fmt.Println("Go Version: ", output.GoVersion) + w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0) + defer w.Flush() + fmt.Fprintf(w, "Version:\t%s\n", output.Version) + fmt.Fprintf(w, "RemoteAPI Version:\t%d\n", output.RemoteAPIVersion) + fmt.Fprintf(w, "Go Version:\t%s\n", output.GoVersion) if output.GitCommit != "" { - fmt.Println("Git Commit: ", output.GitCommit) + fmt.Fprintf(w, "Git Commit:\t%s\n", output.GitCommit) } // Prints out the build time in readable format if output.Built != 0 { - fmt.Println("Built: ", time.Unix(output.Built, 0).Format(time.ANSIC)) + fmt.Fprintf(w, "Built:\t%s\n", time.Unix(output.Built, 0).Format(time.ANSIC)) } - fmt.Println("OS/Arch: ", output.OsArch) + fmt.Fprintf(w, "OS/Arch:\t%s\n", output.OsArch) return nil } @@ -53,7 +57,7 @@ var ( } versionFlags = []cli.Flag{ cli.StringFlag{ - Name: "format", + Name: "format, f", Usage: "Change the output format to JSON or a Go template", }, } diff --git a/commands.md b/commands.md index 43796722f..00be4f1bd 100644 --- a/commands.md +++ b/commands.md @@ -8,9 +8,14 @@ | [podman-attach(1)](/docs/podman-attach.1.md) | Attach to a running container |[![...](/docs/play.png)](https://asciinema.org/a/XDlocUrHVETFECg4zlO9nBbLf)| | [podman-build(1)](/docs/podman-build.1.md) | Build an image using instructions from Dockerfiles || | [podman-commit(1)](/docs/podman-commit.1.md) | Create new image based on the changed container || -| [podman-container(1)](/docs/podman-container.1.md) | Manage Containers || +| [podman-container(1)](/docs/podman-container.1.md) | Manage Containers || +| [podman-container-checkpoint(1)](/docs/podman-container-checkpoint.1.md) | Checkpoints one or more running containers || | [podman-container-cleanup(1)](/docs/podman-container-cleanup.1.md) | Cleanup Container storage and networks || +| [podman-container-exists(1)](/docs/podman-container-exists.1.md) | Check if an container exists in local storage || +| [podman-container-prune(1)](/docs/podman-container-prune.1.md) | Remove all stopped containers || | [podman-container-refresh(1)](/docs/podman-container-refresh.1.md) | Refresh all containers state in database || +| [podman-container-restore(1)](/docs/podman-container-restore.1.md) | Restores one or more running containers || +| [podman-container-runlabel(1)](/docs/podman-container-runlabel.1.md) | Execute Image Label Method || | [podman-cp(1)](/docs/podman-cp.1.md) | Instead of providing a `podman cp` command, the man page `podman-cp` describes how to use the `podman mount` command to have even more flexibility and functionality|| | [podman-create(1)](/docs/podman-create.1.md) | Create a new container || | [podman-diff(1)](/docs/podman-diff.1.md) | Inspect changes on a container or image's filesystem |[![...](/docs/play.png)](https://asciinema.org/a/FXfWB9CKYFwYM4EfqW3NSZy1G)| @@ -18,7 +23,11 @@ | [podman-export(1)](/docs/podman-export.1.md) | Export container's filesystem contents as a tar archive |[![...](/docs/play.png)](https://asciinema.org/a/913lBIRAg5hK8asyIhhkQVLtV)| | [podman-generate(1)](/docs/podman-generate.1.md) | Generate structured output based on Podman containers and pods | | | [podman-history(1)](/docs/podman-history.1.md) | Shows the history of an image |[![...](/docs/play.png)](https://asciinema.org/a/bCvUQJ6DkxInMELZdc5DinNSx)| -| [podman-image(1)](/docs/podman-image.1.md) | Manage Images|| +| [podman-image(1)](/docs/podman-image.1.md) | Manage Images|| +| [podman-image-exists(1)](/docs/podman-image-exists.1.md) | Check if an image exists in local storage|| +| [podman-image-prune(1)](/docs/podman-image-prune.1.md) | Remove all unused images|| +| [podman-image-sign(1)](/docs/podman-image-sign.1.md) | Create a signature for an image|| +| [podman-image-trust(1)](/docs/podman-image-trust.1.md) | Manage container registry image trust policy|| | [podman-images(1)](/docs/podman-images.1.md) | List images in local storage |[![...](/docs/play.png)](https://asciinema.org/a/133649)| | [podman-import(1)](/docs/podman-import.1.md) | Import a tarball and save it as a filesystem image || | [podman-info(1)](/docs/podman-info.1.md) | Display system information |[![...](/docs/play.png)](https://asciinema.org/a/yKbi5fQ89y5TJ8e1RfJd4ivTD)| diff --git a/completions/bash/podman b/completions/bash/podman index 6333dfdf2..e0de4586c 100644 --- a/completions/bash/podman +++ b/completions/bash/podman @@ -1219,6 +1219,7 @@ _podman_info() { --debug " local options_with_args=" + -f --format " @@ -1366,6 +1367,7 @@ _podman_inspect() { --type -t --size + -s " local all_options="$options_with_args $boolean_options" @@ -1514,6 +1516,8 @@ _podman_umount() { -h --force -f + --latest + -l " local options_with_args=" " @@ -1531,8 +1535,12 @@ _podman_umount() { _podman_mount() { local boolean_options=" + --all + -a --help -h + -l + --latest --notruncate " @@ -2030,6 +2038,7 @@ _podman_version() { " local options_with_args=" --format + -f " local all_options="$options_with_args $boolean_options" @@ -2453,6 +2462,8 @@ _podman_images_prune() { " local boolean_options=" + -a + --all -h --help " diff --git a/contrib/cirrus/build_vm_images.sh b/contrib/cirrus/build_vm_images.sh index ecdf1d877..6b86aa4d4 100755 --- a/contrib/cirrus/build_vm_images.sh +++ b/contrib/cirrus/build_vm_images.sh @@ -23,6 +23,8 @@ SCRIPT_BASE $SCRIPT_BASE PACKER_BASE $PACKER_BASE " +record_timestamp "cache-image build start" + show_env_vars # Everything here is running on the 'image-builder-image' GCE image @@ -40,13 +42,33 @@ then fi fi -set -x - cd "$GOSRC/$PACKER_BASE" + +# Separate PR-produced images from those produced on master. +if [[ "${CIRRUS_BRANCH:-}" == "master" ]] +then + POST_MERGE_BUCKET_SUFFIX="-master" +else + POST_MERGE_BUCKET_SUFFIX="" +fi + make libpod_images \ PACKER_BUILDS=$PACKER_BUILDS \ PACKER_VER=$PACKER_VER \ GOSRC=$GOSRC \ SCRIPT_BASE=$SCRIPT_BASE \ PACKER_BASE=$PACKER_BASE \ + POST_MERGE_BUCKET_SUFFIX=$POST_MERGE_BUCKET_SUFFIX \ BUILT_IMAGE_SUFFIX=$BUILT_IMAGE_SUFFIX + +record_timestamp "cache-image build end" + +# When successful, upload manifest of produced images using a filename unique +# to this build. +URI="gs://packer-import${POST_MERGE_BUCKET_SUFFIX}/manifest${BUILT_IMAGE_SUFFIX}.json" +gsutil cp packer-manifest.json "$URI" + +echo "Finished." +echo "Any tarball URI's referenced above at at $URI" +echo "may be used to create VM images suitable for use in" +echo ".cirrus.yml as values for the 'image_name' keys." diff --git a/contrib/cirrus/integration_test.sh b/contrib/cirrus/integration_test.sh index a50bd448f..58c8af289 100755 --- a/contrib/cirrus/integration_test.sh +++ b/contrib/cirrus/integration_test.sh @@ -9,23 +9,29 @@ OS_RELEASE_ID $OS_RELEASE_ID OS_RELEASE_VER $OS_RELEASE_VER " +record_timestamp "integration test start" + clean_env set -x cd "$GOSRC" case "${OS_RELEASE_ID}-${OS_RELEASE_VER}" in ubuntu-18) - make install PREFIX=/usr ETCDIR=/etc "BUILDTAGS=$BUILDTAGS" - make test-binaries "BUILDTAGS=$BUILDTAGS" - SKIP_USERNS=1 make localintegration "BUILDTAGS=$BUILDTAGS" + make install PREFIX=/usr ETCDIR=/etc + make test-binaries + SKIP_USERNS=1 make localintegration ;; fedora-29) ;& # Continue to the next item fedora-28) ;& centos-7) ;& rhel-7) make install PREFIX=/usr ETCDIR=/etc + make podman-remote + install bin/podman-remote /usr/bin make test-binaries make localintegration ;; *) bad_os_id_ver ;; esac + +record_timestamp "integration test end" diff --git a/contrib/cirrus/lib.sh b/contrib/cirrus/lib.sh index d50c1e1a3..39e6c7699 100644 --- a/contrib/cirrus/lib.sh +++ b/contrib/cirrus/lib.sh @@ -17,7 +17,7 @@ PACKER_BASE=${PACKER_BASE:-./contrib/cirrus/packer} CIRRUS_BUILD_ID=${CIRRUS_BUILD_ID:-DEADBEEF} # a human CIRRUS_BASE_SHA=${CIRRUS_BASE_SHA:-HEAD} CIRRUS_CHANGE_IN_REPO=${CIRRUS_CHANGE_IN_REPO:-FETCH_HEAD} -START_STAMP_FILEPATH="${START_STAMP_FILEPATH:-/var/tmp/start.timestamp}" +TIMESTAMPS_FILEPATH="${TIMESTAMPS_FILEPATH:-/var/tmp/timestamps}" if ! [[ "$PATH" =~ "/usr/local/bin" ]] then @@ -136,11 +136,14 @@ ircmsg() { set -e } -start_timestamp() { - req_env_var "START_STAMP_FILEPATH $START_STAMP_FILEPATH" - [[ -r "$START_STAMP_FILEPATH" ]] || \ - echo -e ".\nThe time at the tone will be:\n$(date --iso-8601=seconds | \ - tee $START_STAMP_FILEPATH)\nBLEEEEEEEEEEP!\n.\n" # Cirrus strips blank lines from output +record_timestamp() { + set +x # sometimes it's turned on + req_env_var "TIMESTAMPS_FILEPATH $TIMESTAMPS_FILEPATH" + echo "." # cirrus webui strips blank-lines + STAMPMSG="The $1 time at the tone will be:" + echo -e "$STAMPMSG\t$(date --iso-8601=seconds)" | \ + tee -a $TIMESTAMPS_FILEPATH + echo -e "BLEEEEEEEEEEP!\n." } # Run sudo in directory with GOPATH set @@ -196,7 +199,7 @@ install_runc_from_git(){ cd "$DEST" ooe.sh git fetch origin --tags ooe.sh git checkout -q "$RUNC_COMMIT" - ooe.sh make static BUILDTAGS="seccomp selinux" + ooe.sh make static BUILDTAGS="seccomp apparmor selinux" sudo install -m 755 runc /usr/bin/runc cd $wd } diff --git a/contrib/cirrus/packer/Makefile b/contrib/cirrus/packer/Makefile index 9bf27373e..0a783e979 100644 --- a/contrib/cirrus/packer/Makefile +++ b/contrib/cirrus/packer/Makefile @@ -3,7 +3,7 @@ # builder name(s) from applicable YAML file, # e.g for names see libpod_images.yml -PACKER_VER ?= 1.3.1 +PACKER_VER ?= 1.3.2 PACKER_DIST_FILENAME := packer_${PACKER_VER}_linux_amd64.zip # Only needed for libpod_base_images target @@ -11,6 +11,7 @@ TIMESTAMP := $(shell date +%s) GOSRC ?= $(shell realpath "./../../../") PACKER_BASE ?= contrib/cirrus/packer SCRIPT_BASE ?= contrib/cirrus +POST_MERGE_BUCKET_SUFFIX ?= # For debugging nested-virt, use #TTYDEV := $(shell tty) @@ -50,10 +51,6 @@ endif -var PACKER_BASE=$(PACKER_BASE) \ -var SCRIPT_BASE=$(SCRIPT_BASE) \ libpod_images.json - @echo "" - @echo "Finished. The images mentioned above, and in packer-manifest.json" - @echo "can be used in .cirrus.yml as values for the 'image_name' keys" - @echo "" cidata.ssh: ssh-keygen -f $@ -P "" -q @@ -100,9 +97,6 @@ endif -var RHEL_IMAGE_FILE=$(RHEL_IMAGE_FILE) \ -var RHEL_CSUM_FILE=$(RHEL_CSUM_FILE) \ -var 'RHSM_COMMAND=$(RHSM_COMMAND)' \ + -var POST_MERGE_BUCKET_SUFFIX=$(POST_MERGE_BUCKET_SUFFIX) \ -only $(PACKER_BUILDS) \ libpod_base_images.json - @echo "" - @echo "Finished. The images mentioned above, and in packer-manifest.json" - @echo "can be used in .cirrus.yml as values for the *_BASE_IMAGE keys." - @echo "" diff --git a/contrib/cirrus/packer/centos_setup.sh b/contrib/cirrus/packer/centos_setup.sh index a13050569..923f2563b 100644 --- a/contrib/cirrus/packer/centos_setup.sh +++ b/contrib/cirrus/packer/centos_setup.sh @@ -25,6 +25,7 @@ ooe.sh sudo yum -y update ooe.sh sudo yum -y install centos-release-scl epel-release ooe.sh sudo yum -y install \ + PyYAML \ atomic-registries \ btrfs-progs-devel \ bzip2 \ diff --git a/contrib/cirrus/packer/image-builder-image_base-setup.sh b/contrib/cirrus/packer/image-builder-image_base-setup.sh index b8e2824a7..8cf9fd8ab 100644 --- a/contrib/cirrus/packer/image-builder-image_base-setup.sh +++ b/contrib/cirrus/packer/image-builder-image_base-setup.sh @@ -45,10 +45,13 @@ ooe.sh sudo yum -y install \ qemu-kvm-tools \ qemu-user \ rsync \ + rng-tools \ unzip \ util-linux \ vim +sudo systemctl enable rngd + sudo ln -s /usr/libexec/qemu-kvm /usr/bin/ sudo tee /etc/modprobe.d/kvm-nested.conf <<EOF diff --git a/contrib/cirrus/packer/libpod_base_images.yml b/contrib/cirrus/packer/libpod_base_images.yml index 109b9b8d5..bf568b40e 100644 --- a/contrib/cirrus/packer/libpod_base_images.yml +++ b/contrib/cirrus/packer/libpod_base_images.yml @@ -105,7 +105,7 @@ builders: ssh_username: 'root' - <<: *nested_virt - name: 'prior_fedora' + name: 'prior-fedora' iso_url: '{{user `PRIOR_FEDORA_IMAGE_URL`}}' iso_checksum_url: '{{user `PRIOR_FEDORA_CSUM_URL`}}' @@ -161,7 +161,7 @@ provisioners: post-processors: - - type: "compress" - only: ['fedora', 'prior_fedora', 'fah', 'rhel'] + only: ['fedora', 'prior-fedora', 'fah', 'rhel'] output: '/tmp/{{build_name}}/disk.raw.tar.gz' format: '.tar.gz' compression_level: 9 @@ -171,12 +171,12 @@ post-processors: project_id: '{{user `GCP_PROJECT_ID`}}' account_file: '{{user `GOOGLE_APPLICATION_CREDENTIALS`}}' bucket: '{{user `XFERBUCKET`}}' - gcs_object_name: '{{build_name}}-{{user `TIMESTAMP`}}-{{uuid}}.tar.gz' + gcs_object_name: '{{build_name}}-{{user `TIMESTAMP`}}.tar.gz' image_name: "{{user `FEDORA_BASE_IMAGE_NAME`}}-{{user `TIMESTAMP`}}" image_description: 'Based on {{user `FEDORA_IMAGE_URL`}}' image_family: '{{user `FEDORA_BASE_IMAGE_NAME`}}' - <<: *gcp_import - only: ['prior_fedora'] + only: ['prior-fedora'] image_name: "{{user `PRIOR_FEDORA_BASE_IMAGE_NAME`}}-{{user `TIMESTAMP`}}" image_description: 'Based on {{user `PRIOR_FEDORA_IMAGE_URL`}}' image_family: '{{user `PRIOR_FEDORA_BASE_IMAGE_NAME`}}' diff --git a/contrib/cirrus/packer/libpod_images.yml b/contrib/cirrus/packer/libpod_images.yml index d31c11a8d..30ad0723a 100644 --- a/contrib/cirrus/packer/libpod_images.yml +++ b/contrib/cirrus/packer/libpod_images.yml @@ -29,6 +29,10 @@ variables: SERVICE_ACCOUNT: '{{env `SERVICE_ACCOUNT`}}' GOOGLE_APPLICATION_CREDENTIALS: '{{env `GOOGLE_APPLICATION_CREDENTIALS`}}' + # Used to separate images produced during PR testing from those + # produced from post-merge testing. Must be empty for PR testing. + POST_MERGE_BUCKET_SUFFIX: '' + # Don't leak sensitive values in error messages / output sensitive-variables: - 'GCE_SSH_USERNAME' @@ -84,13 +88,18 @@ provisioners: - type: 'shell' script: '{{user `GOSRC`}}/{{user `PACKER_BASE`}}/{{split build_name "-" 0}}_setup.sh' environment_vars: - - 'SCRIPT_BASE={{user `SCRIPT_BASE`}}' + - 'GOSRC=/tmp/libpod' - 'CNI_COMMIT={{user `CNI_COMMIT`}}' - 'FEDORA_CNI_COMMIT={{user `FEDORA_CNI_COMMIT`}}' - 'CRIO_COMMIT={{user `CRIO_COMMIT`}}' - 'CRIU_COMMIT={{user `CRIU_COMMIT`}}' - 'RUNC_COMMIT={{user `RUNC_COMMIT`}}' + - 'SCRIPT_BASE={{user `SCRIPT_BASE`}}' - 'RHSM_COMMAND={{user `RHSM_COMMAND`}}' post-processors: - - - type: 'manifest' + # Store VM disk in GCP storage, where it will expire based on a defined + # lifecycle. This prevents GCE from filling with disused images. + - - type: 'googlecompute-export' + paths: ['gs://packer-import{{user `POST_MERGE_BUCKET_SUFFIX`}}/{{build_name}}{{user `BUILT_IMAGE_SUFFIX`}}.tar.gz'] + - type: 'manifest' # writes packer-manifest.json diff --git a/contrib/cirrus/packer/rhel_base-setup.sh b/contrib/cirrus/packer/rhel_base-setup.sh index 8b2073d4f..fbf9f61af 100644 --- a/contrib/cirrus/packer/rhel_base-setup.sh +++ b/contrib/cirrus/packer/rhel_base-setup.sh @@ -16,6 +16,8 @@ req_env_var " install_ooe +rhsm_enable + echo "Setting up repos" # Frequently needed ooe.sh sudo yum -y install https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm @@ -32,12 +34,15 @@ gpgkey=https://packages.cloud.google.com/yum/doc/yum-key.gpg https://packages.cloud.google.com/yum/doc/rpm-package-key.gpg EOM -rhsm_enable +echo "Updating all packages" +ooe.sh sudo yum -y update echo "Installing/removing packages" -ooe.sh sudo yum -y install google-compute-engine google-compute-engine-oslogin -ooe.sh sudo yum -y erase "cloud-init" "rh-amazon-rhui-client*" || true +ooe.sh sudo yum -y install rng-tools google-compute-engine google-compute-engine-oslogin + +echo "Enabling critical services" ooe.sh sudo systemctl enable \ + rngd \ google-accounts-daemon \ google-clock-skew-daemon \ google-instance-setup \ @@ -47,6 +52,29 @@ ooe.sh sudo systemctl enable \ rhel_exit_handler # release subscription! +echo "Configuring boot" +cat << "EOF" | sudo tee /etc/default/grub +GRUB_TIMEOUT=0 +GRUB_DISTRIBUTOR="$(sed 's, release .*$,,g' /etc/system-release)" +GRUB_DEFAULT=saved +GRUB_DISABLE_SUBMENU=true +GRUB_TERMINAL="serial console" +GRUB_SERIAL_COMMAND="serial --speed=38400" +GRUB_CMDLINE_LINUX="crashkernel=auto console=ttyS0,38400n8" +GRUB_DISABLE_RECOVERY="true" +EOF +sudo grub2-mkconfig -o /boot/grub2/grub.cfg + +echo "Configuring networking" +ooe.sh sudo nmcli connection modify 'System eth0' 802-3-ethernet.mtu 1460 +ooe.sh sudo nmcli connection modify 'System eth0' connection.autoconnect yes +ooe.sh sudo nmcli connection modify 'System eth0' connection.autoconnect-priority +ooe.sh sudo nmcli connection modify 'System eth0' ipv4.method auto +ooe.sh sudo nmcli connection modify 'System eth0' ipv4.dhcp-send-hostname yes +ooe.sh sudo nmcli connection modify 'System eth0' ipv4.dhcp-timeout 0 +ooe.sh sudo nmcli connection modify 'System eth0' ipv4.never-default no +ooe.sh /usr/bin/google_instance_setup + rh_finalize echo "SUCCESS!" diff --git a/contrib/cirrus/packer/rhel_setup.sh b/contrib/cirrus/packer/rhel_setup.sh index 99376fd65..ac6866a57 100644 --- a/contrib/cirrus/packer/rhel_setup.sh +++ b/contrib/cirrus/packer/rhel_setup.sh @@ -31,6 +31,7 @@ ooe.sh sudo subscription-manager repos \ ooe.sh sudo yum -y update ooe.sh sudo yum -y install \ + PyYAML \ atomic-registries \ btrfs-progs-devel \ bzip2 \ @@ -64,9 +65,11 @@ ooe.sh sudo yum -y install \ protobuf-python \ python \ python2-future \ + python2-pyyaml \ python34-dateutil \ python34-psutil \ python34-pytoml \ + python34-PyYAML \ runc \ skopeo-containers \ unzip \ diff --git a/contrib/cirrus/setup_environment.sh b/contrib/cirrus/setup_environment.sh index 5899dca2d..838f3c3f3 100755 --- a/contrib/cirrus/setup_environment.sh +++ b/contrib/cirrus/setup_environment.sh @@ -4,7 +4,7 @@ set -e source $(dirname $0)/lib.sh -start_timestamp +record_timestamp "env. setup start" req_env_var " USER $USER @@ -57,7 +57,6 @@ then ubuntu-18) # Always install runc on Ubuntu install_runc_from_git - envstr='export BUILDTAGS="seccomp $($GOSRC/hack/btrfs_tag.sh) $($GOSRC/hack/btrfs_installed_tag.sh) $($GOSRC/hack/ostree_tag.sh) varlink exclude_graphdriver_devicemapper"' ;; fedora-29) ;& # Continue to the next item fedora-28) @@ -67,11 +66,9 @@ then ;& # Continue to the next item centos-7) ;& rhel-7) - envstr='unset BUILDTAGS' # Use default from Makefile ;; *) bad_os_id_ver ;; esac - X=$(echo "$envstr" | tee -a "$HOME/$ENVLIB") && eval "$X" && echo "$X" # Do the same for golang env. vars go env | while read envline @@ -85,3 +82,5 @@ then # Only testing-VMs need deps installed [[ -n "$PACKER_BUILDS" ]] || install_testing_dependencies # must exist in $GOPATH fi + +record_timestamp "env. setup end" diff --git a/contrib/cirrus/system_test.sh b/contrib/cirrus/system_test.sh index 66974f8c6..cb179407a 100755 --- a/contrib/cirrus/system_test.sh +++ b/contrib/cirrus/system_test.sh @@ -15,12 +15,9 @@ set -x cd "$GOSRC" case "${OS_RELEASE_ID}-${OS_RELEASE_VER}" in - ubuntu-18) - make install.tools "BUILDTAGS=$BUILDTAGS" - make "BUILDTAGS=$BUILDTAGS" - make test-binaries "BUILDTAGS=$BUILDTAGS" - ;; + ubuntu-18) ;& # Continue to the next item fedora-28) ;& + fedora-29) ;& centos-7) ;& rhel-7) make install.tools diff --git a/contrib/cirrus/unit_test.sh b/contrib/cirrus/unit_test.sh index 61d9dc73d..fd9e82509 100755 --- a/contrib/cirrus/unit_test.sh +++ b/contrib/cirrus/unit_test.sh @@ -9,17 +9,15 @@ OS_RELEASE_ID $OS_RELEASE_ID OS_RELEASE_VER $OS_RELEASE_VER " +record_timestamp "unit test start" + clean_env set -x cd "$GOSRC" case "${OS_RELEASE_ID}-${OS_RELEASE_VER}" in - ubuntu-18) - make install.tools "BUILDTAGS=$BUILDTAGS" - make localunit "BUILDTAGS=$BUILDTAGS" - make "BUILDTAGS=$BUILDTAGS" - ;; - fedora-29) ;& # Continue to the next item + ubuntu-18) ;& # Continue to the next item + fedora-29) ;& fedora-28) ;& centos-7) ;& rhel-7) @@ -29,3 +27,5 @@ case "${OS_RELEASE_ID}-${OS_RELEASE_VER}" in ;; *) bad_os_id_ver ;; esac + +record_timestamp "unit test end" diff --git a/contrib/spec/podman.spec.in b/contrib/spec/podman.spec.in index 16cf01976..bf75522dc 100644 --- a/contrib/spec/podman.spec.in +++ b/contrib/spec/podman.spec.in @@ -39,7 +39,7 @@ %global shortcommit_conmon %(c=%{commit_conmon}; echo ${c:0:7}) Name: podman -Version: 0.12.2 +Version: 1.0.1 Release: #COMMITDATE#.git%{shortcommit0}%{?dist} Summary: Manage Pods, Containers and Container Images License: ASL 2.0 diff --git a/docs/podman-image-prune.1.md b/docs/podman-image-prune.1.md index db76b26e0..df912c380 100644 --- a/docs/podman-image-prune.1.md +++ b/docs/podman-image-prune.1.md @@ -6,23 +6,38 @@ podman-image-prune - Remove all unused images # SYNOPSIS **podman image prune** +[**-a**|**--all**] [**-h**|**--help**] # DESCRIPTION -**podman image prune** removes all unused images from local storage. An unused image -is defined as an image that does not have any containers based on it. +**podman image prune** removes all dangling images from local storage. With the `all` option, +you can delete all unused images. Unused images are dangling images as well as any image that +does not have any containers based on it. + +## OPTIONS +**--all, -a** + +Remove dangling images and images that have no associated containers. ## Examples ## -Remove all unused images from local storage +Remove all dangling images from local storage ``` $ sudo podman image prune f3e20dc537fb04cb51672a5cb6fdf2292e61d411315549391a0d1f64e4e3097e 324a7a3b2e0135f4226ffdd473e4099fd9e477a74230cdc35de69e84c0f9d907 +``` + +Remove all unused images from local storage +``` +$ sudo podman image prune -a +f3e20dc537fb04cb51672a5cb6fdf2292e61d411315549391a0d1f64e4e3097e +324a7a3b2e0135f4226ffdd473e4099fd9e477a74230cdc35de69e84c0f9d907 6125002719feb1ddf3030acab1df6156da7ce0e78e571e9b6e9c250424d6220c 91e732da5657264c6f4641b8d0c4001c218ae6c1adb9dcef33ad00cafd37d8b6 e4e5109420323221f170627c138817770fb64832da7d8fe2babd863148287fca 77a57fa8285e9656dbb7b23d9efa837a106957409ddd702f995605af27a45ebe + ``` ## SEE ALSO diff --git a/docs/podman-image-trust.1.md b/docs/podman-image-trust.1.md index 24209698c..3b6564315 100644 --- a/docs/podman-image-trust.1.md +++ b/docs/podman-image-trust.1.md @@ -1,7 +1,7 @@ % podman-image-trust "1" # NAME -podman\-trust - Manage container image trust policy +podman\-trust - Manage container registry image trust policy # SYNOPSIS diff --git a/docs/podman-info.1.md b/docs/podman-info.1.md index 836a2c420..d3a0658c9 100644 --- a/docs/podman-info.1.md +++ b/docs/podman-info.1.md @@ -19,7 +19,7 @@ Displays information pertinent to the host, current storage stats, configured co Show additional information -**--format** +**--format, -f** Change output format to "json" or a Go template. diff --git a/docs/podman-inspect.1.md b/docs/podman-inspect.1.md index 7bdbcc662..b01bc0f4e 100644 --- a/docs/podman-inspect.1.md +++ b/docs/podman-inspect.1.md @@ -27,7 +27,7 @@ The keys of the returned JSON can be used as the values for the --format flag (s Instead of providing the container name or ID, use the last created container. If you use methods other than Podman to run containers such as CRI-O, the last started container could be from either of those methods. -**--size** +**--size, -s** Display the total file size if the type is a container diff --git a/docs/podman-mount.1.md b/docs/podman-mount.1.md index ccc2d386d..2cccf5ee0 100644 --- a/docs/podman-mount.1.md +++ b/docs/podman-mount.1.md @@ -19,10 +19,20 @@ returned. ## OPTIONS +**--all, a** + +Mount all containers. + **--format** Print the mounted containers in specified format (json) +**--latest, -l** + +Instead of providing the container name or ID, use the last created container. +If you use methods other than Podman to run containers such as CRI-O, the last +started container could be from either of those methods. + **--notruncate** Do not truncate IDs in output. diff --git a/docs/podman-rm.1.md b/docs/podman-rm.1.md index 56664a8c1..4fcb0b6c5 100644 --- a/docs/podman-rm.1.md +++ b/docs/podman-rm.1.md @@ -11,14 +11,14 @@ podman\-rm - Remove one or more containers ## OPTIONS -**--force, f** - -Force the removal of a running and paused containers - **--all, a** Remove all containers. Can be used in conjunction with -f as well. +**--force, f** + +Force the removal of a running and paused containers + **--latest, -l** Instead of providing the container name or ID, use the last created container. If you use methods other than Podman diff --git a/docs/podman-umount.1.md b/docs/podman-umount.1.md index 70f30869a..cceb63019 100644 --- a/docs/podman-umount.1.md +++ b/docs/podman-umount.1.md @@ -29,6 +29,12 @@ processes have mounted it. Note: This could cause other processes that are using the file system to fail, as the mount point could be removed without their knowledge. +**--latest, -l** + +Instead of providing the container name or ID, use the last created container. +If you use methods other than Podman to run containers such as CRI-O, the last +started container could be from either of those methods. + ## EXAMPLE podman umount containerID diff --git a/docs/podman-version.1.md b/docs/podman-version.1.md index 749a33afd..171096587 100644 --- a/docs/podman-version.1.md +++ b/docs/podman-version.1.md @@ -16,7 +16,7 @@ OS, and Architecture. Print usage statement -**--format** +**--format**, **-f** Change output format to "json" or a Go template. diff --git a/docs/podman.1.md b/docs/podman.1.md index a73ebb55e..74e700fac 100644 --- a/docs/podman.1.md +++ b/docs/podman.1.md @@ -68,7 +68,7 @@ Default state dir is configured in /etc/containers/storage.conf. **--runtime**=**value** -Path to the OCI compatible binary used to run containers +Name of the OCI runtime as specified in libpod.conf or absolute path to the OCI compatible binary used to run containers. **--storage-driver, -s**=**value** diff --git a/docs/tutorials/podman_tutorial.md b/docs/tutorials/podman_tutorial.md index f8332c820..5017e61cd 100644 --- a/docs/tutorials/podman_tutorial.md +++ b/docs/tutorials/podman_tutorial.md @@ -14,6 +14,17 @@ Fedora 27 and later provide Podman via the package manager. sudo dnf install -y podman ``` +*Optional*: If you've already installed podman on Fedora and you're feeling +adventerous, you can test the very latest podman in Fedora's `updates-testing` +repository before it goes out to all Fedora users. +```console +sudo yum distro-sync --enablerepo=updates-testing podman +``` + +If you use a newer podman package from Fedora's `updates-testing`, we would +appreciate your `+1` feedback in [Bodhi, Fedora's update management +system](https://bodhi.fedoraproject.org/updates/?packages=podman). + ## Install Podman on Fedora from Source Many of the basic components to run Podman are readily available from the Fedora RPM repositories. In this section, we will help you install all the runtime and build dependencies for Podman, @@ -112,7 +123,7 @@ sudo make install PREFIX=/usr This sample container will run a very basic httpd server that serves only its index page. ```console -sudo podman run -dt -e HTTPD_VAR_RUN=/var/run/httpd -e HTTPD_MAIN_CONF_D_PATH=/etc/httpd/conf.d \ +podman run -dt -e HTTPD_VAR_RUN=/var/run/httpd -e HTTPD_MAIN_CONF_D_PATH=/etc/httpd/conf.d \ -e HTTPD_MAIN_CONF_PATH=/etc/httpd/conf \ -e HTTPD_CONTAINER_SCRIPTS_PATH=/usr/share/container-scripts/httpd/ \ registry.fedoraproject.org/f27/httpd /usr/bin/run-httpd @@ -123,7 +134,7 @@ will print the container ID after it has run. ### Listing running containers The Podman *ps* command is used to list creating and running containers. ```console -sudo podman ps +podman ps ``` Note: If you add *-a* to the *ps* command, Podman will show all containers. diff --git a/hack/get_ci_vm.sh b/hack/get_ci_vm.sh index e9a755dd4..b058b4273 100755 --- a/hack/get_ci_vm.sh +++ b/hack/get_ci_vm.sh @@ -2,9 +2,14 @@ set -e -cd $(dirname $0)/../ - -VMNAME="${USER}-twidling-$1" +RED="\e[1;36;41m" +YEL="\e[1;33;44m" +NOR="\e[0m" +USAGE_WARNING=" +${YEL}WARNING: This will not work without local sudo access to run podman,${NOR} + ${YEL}and prior authorization to use the libpod GCP project. Also,${NOR} + ${YEL}possession of the proper ssh private key is required.${NOR} +" # TODO: Many/most of these values should come from .cirrus.yml ZONE="us-central1-a" CPUS="2" @@ -12,74 +17,187 @@ MEMORY="4Gb" DISK="200" PROJECT="libpod-218412" GOSRC="/var/tmp/go/src/github.com/containers/libpod" +# Command shortcuts save some typing +PGCLOUD="sudo podman run -it --rm -e AS_ID=$UID -e AS_USER=$USER --security-opt label=disable -v /home/$USER:$HOME -v /tmp:/tmp:ro quay.io/cevich/gcloud_centos:latest --configuration=libpod --project=$PROJECT" +SCP_CMD="$PGCLOUD compute scp" + +LIBPODROOT=$(realpath "$(dirname $0)/../") +# else: Assume $PWD is the root of the libpod repository +[[ "$LIBPODROOT" != "/" ]] || LIBPODROOT=$PWD + +showrun() { + if [[ "$1" == "--background" ]] + then + shift + # Properly escape any nested spaces, so command can be copy-pasted + echo '+ '$(printf " %q" "$@")' &' > /dev/stderr + "$@" & + echo -e "${RED}<backgrounded>${NOR}" + else + echo '+ '$(printf " %q" "$@") > /dev/stderr + "$@" + fi +} + +TEMPFILE=$(mktemp -p '' $(basename $0)_XXXXX.tar.bz2) +cleanup() { + set +e + wait + rm -f "$TEMPFILE" +} +trap cleanup EXIT -PGCLOUD="sudo podman run -it --rm -e AS_ID=$UID -e AS_USER=$USER -v /home/$USER:$HOME:z quay.io/cevich/gcloud_centos:latest" -CREATE_CMD="$PGCLOUD compute instances create --zone=$ZONE --image=$1 --custom-cpu=$CPUS --custom-memory=$MEMORY --boot-disk-size=$DISK --labels=in-use-by=$USER $VMNAME" -SSH_CMD="ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -o CheckHostIP=no -F /dev/null" -CLEANUP_CMD="$PGCLOUD compute instances delete --zone $ZONE --delete-disks=all $VMNAME" +delvm() { + cleanup + echo -e "\n" + echo -e "\n${YEL}Offering to Delete $VMNAME ${RED}(Might take a minute or two)${NOR}" + showrun $CLEANUP_CMD # prompts for Yes/No +} -# COLOR! -RED="\e[1;36;41m" -YEL="\e[1;33;44m" -NOR="\e[0m" +image_hints() { + egrep '[[:space:]]+[[:alnum:]].+_CACHE_IMAGE_NAME:[[:space:]+"[[:print:]]+"' \ + "$LIBPODROOT/.cirrus.yml" | cut -d: -f 2 | tr -d '"[:blank:]' | \ + grep -v 'notready' | grep -v 'image-builder' | sort -u +} -if [[ -z "$1" ]] -then - echo -e "\n${RED}Error: No image-name specified. Some possible values (from .cirrus.yml).${NOR}" - egrep 'image_name' ".cirrus.yml" | grep -v '#' | cut -d: -f 2 | tr -d [:blank:] +show_usage() { + echo -e "\n${RED}ERROR: $1${NOR}" + echo -e "${YEL}Usage: $(basename $0) [-s | -p] <image_name>${NOR}\n" + if [[ -r ".cirrus.yml" ]] + then + echo -e "${YEL}Some possible image_name values (from .cirrus.yml):${NOR}" + image_hints + echo "" + fi exit 1 -fi +} + +get_env_vars() { + python -c ' +import yaml +env=yaml.load(open(".cirrus.yml"))["env"] +keys=[k for k in env if "ENCRYPTED" not in str(env[k])] +for k,v in env.items(): + v=str(v) + if "ENCRYPTED" not in v: + print "{0}=\"{1}\"".format(k, v), + ' +} + +parse_args(){ + if [[ -z "$1" ]] + then + show_usage "Must specify at least one command-line parameter." + elif [[ "$1" == "-p" ]] + then + DEPS="PACKAGE_DEPS=true SOURCE_DEPS=false" + IMAGE_NAME="$2" + + elif [[ "$1" == "-s" ]] + then + DEPS="PACKAGE_DEPS=false SOURCE_DEPS=true" + IMAGE_NAME="$2" + else # no -s or -p + DEPS="$(get_env_vars)" + IMAGE_NAME="$1" + fi + + if [[ -z "$IMAGE_NAME" ]] + then + show_usage "No image-name specified." + fi + + if [[ "$USER" =~ "root" ]] + then + show_usage "This script must be run as a regular user." + fi + + echo -e "$USAGE_WARNING" + + SETUP_CMD="env $DEPS $GOSRC/contrib/cirrus/setup_environment.sh" + VMNAME="${VMNAME:-${USER}-${IMAGE_NAME}}" + CREATE_CMD="$PGCLOUD compute instances create --zone=$ZONE --image=${IMAGE_NAME} --custom-cpu=$CPUS --custom-memory=$MEMORY --boot-disk-size=$DISK --labels=in-use-by=$USER $VMNAME" + SSH_CMD="$PGCLOUD compute ssh root@$VMNAME" + CLEANUP_CMD="$PGCLOUD compute instances delete --zone $ZONE --delete-disks=all $VMNAME" +} + +##### main + +parse_args $@ -echo -e "\n${YEL}WARNING: This will not work without local sudo access to run podman,${NOR}" -echo -e " ${YEL}and prior authorization to use the libpod GCP project. Also,${NOR}" -echo -e " ${YEL}possession of the proper ssh private key is required.${NOR}" +cd $LIBPODROOT -if [[ "$USER" =~ "root" ]] +# Attempt to determine if named 'libpod' gcloud configuration exists +showrun $PGCLOUD info > $TEMPFILE +if egrep -q "Account:.*None" "$TEMPFILE" then - echo -e "\n${RED}ERROR: This script must be run as a regular user${NOR}" - exit 2 + echo -e "\n${YEL}WARNING: Can't find gcloud configuration for libpod, running init.${NOR}" + echo -e " ${RED}Please choose "#1: Re-initialize" and "login" if asked.${NOR}" + showrun $PGCLOUD init --project=$PROJECT --console-only --skip-diagnostics + + # Verify it worked (account name == someone@example.com) + $PGCLOUD info > $TEMPFILE + if egrep -q "Account:.*None" "$TEMPFILE" + then + echo -e "${RED}ERROR: Could not initialize libpod configuration in gcloud.${NOR}" + exit 5 + fi + + # If this is the only config, make it the default to avoid persistent warnings from gcloud + [[ -r "$HOME/.config/gcloud/configurations/config_default" ]] || \ + ln "$HOME/.config/gcloud/configurations/config_libpod" \ + "$HOME/.config/gcloud/configurations/config_default" fi -if [[ ! -r "$HOME/.config/gcloud/active_config" ]] +# Couldn't make rsync work with gcloud's ssh wrapper :( +echo -e "\n${YEL}Packing up repository into a tarball $VMNAME.${NOR}" +showrun --background tar cjf $TEMPFILE --warning=no-file-changed -C $LIBPODROOT . + +trap delvm INT # Allow deleting VM if CTRL-C during create +# This fails if VM already exists: permit this usage to re-init +echo -e "\n${YEL}Trying to creating a VM named $VMNAME ${RED}(might take a minute/two. Errors ignored).${NOR}" +showrun $CREATE_CMD || true # allow re-running commands below when "delete: N" + +# Any subsequent failure should prompt for VM deletion +trap delvm EXIT + +echo -e "\n${YEL}Waiting up to 30s for ssh port to open${NOR}" +ATTEMPTS=10 +for (( COUNT=1 ; COUNT <= $ATTEMPTS ; COUNT++ )) +do + if $SSH_CMD --command "true"; then break; else sleep 3s; fi +done +if (( COUNT > $ATTEMPTS )) then - echo -e "\n${RED}ERROR: Can't find gcloud configuration, attempting to run init.${NOR}" - $PGCLOUD init --project=$PROJECT + echo -e "\n${RED}Failed${NOR}" + exit 7 fi +echo -e "${YEL}Got it${NOR}" -cleanup() { - echo -e "\n${YEL}Deleting $VMNAME ${RED}(Might take a minute or two)${NOR} -+ $CLEANUP_CMD -" - $CLEANUP_CMD # prompts for Yes/No -} - -trap cleanup EXIT +if $SSH_CMD --command "test -r /root/.bash_profile_original" +then + echo -e "\n${YEL}Resetting environment configuration${NOR}" + showrun $SSH_CMD --command "cp /root/.bash_profile_original /root/.bash_profile" +fi -echo -e "\n${YEL}Trying to creating a VM named $VMNAME (not fatal if already exists).${NOR}" -echo "+ $CREATE_CMD" -$CREATE_CMD || true # allow re-running commands below when "delete: N" +echo -e "\n${YEL}Removing and re-creating $GOSRC on $VMNAME.${NOR}" +showrun $SSH_CMD --command "rm -rf $GOSRC" +showrun $SSH_CMD --command "mkdir -p $GOSRC" -echo -e "\n${YEL}Attempting to retrieve IP address of existing ${VMNAME}${NOR}." -IP=`$PGCLOUD compute instances list --filter=name=$VMNAME --limit=1 '--format=csv(networkInterfaces.accessConfigs.natIP)' | tr --complement --delete .[:digit:]` +echo -e "\n${YEL}Transfering tarball to $VMNAME.${NOR}" +wait +showrun $SCP_CMD $TEMPFILE root@$VMNAME:$TEMPFILE -echo -e "\n${YEL}Creating $GOSRC directory.${NOR}" -SSH_MKDIR="$SSH_CMD root@$IP mkdir -vp $GOSRC" -echo "+ $SSH_MKDIR" -$SSH_MKDIR +echo -e "\n${YEL}Unpacking tarball into $GOSRC on $VMNAME.${NOR}" +showrun $SSH_CMD --command "tar xjf $TEMPFILE -C $GOSRC" -echo -e "\n${YEL}Synchronizing local repository to $IP:${GOSRC}${NOR} ." -export RSYNC_RSH="$SSH_CMD" -RSYNC_CMD="rsync --quiet --recursive --update --links --safe-links --perms --sparse $PWD/ root@$IP:$GOSRC/" -echo "+ export RSYNC_RSH=\"$SSH_CMD\"" -echo "+ $RSYNC_CMD" -$RSYNC_CMD +echo -e "\n${YEL}Removing tarball on $VMNAME.${NOR}" +showrun $SSH_CMD --command "rm -f $TEMPFILE" echo -e "\n${YEL}Executing environment setup${NOR}" -ENV_CMD="$SSH_CMD root@$IP env CI=true $GOSRC/contrib/cirrus/setup_environment.sh" -echo "+ $ENV_CMD" -$SSH_CMD root@$IP $GOSRC/contrib/cirrus/setup_environment.sh - -echo -e "\n${YEL}Connecting to $VMNAME ${RED}(option to delete VM upon logout).${NOR}" -SSH_CMD="$SSH_CMD -t root@$IP" -echo "+ $SSH_CMD" -$SSH_CMD "cd $GOSRC ; bash -il" +[[ "$1" == "-p" ]] && echo -e "${RED}Using package-based dependencies.${NOR}" +[[ "$1" == "-s" ]] && echo -e "${RED}Using source-based dependencies.${NOR}" +showrun $SSH_CMD --command "$SETUP_CMD" + +echo -e "\n${YEL}Connecting to $VMNAME ${RED}(option to delete VM upon logout).${NOR}\n" +showrun $SSH_CMD -- -t "cd $GOSRC && exec env $DEPS bash -il" diff --git a/install.md b/install.md index efb568b66..4f075332d 100644 --- a/install.md +++ b/install.md @@ -1,5 +1,67 @@ # libpod Installation Instructions +## Installing packaged versions of Podman + +#### [Arch Linux](https://www.archlinux.org) + +```bash +sudo pacman -S podman +``` + +#### [Fedora](https://www.fedoraproject.org), [CentOS](https://www.centos.org) + +```bash +sudo yum -y install podman +``` + + +#### [Fedora-CoreOS](https://coreos.fedoraproject.org), [Fedora SilverBlue](https://silverblue.fedoraproject.org) + +Built-in, no need to install + +#### [Gentoo](https://www.gentoo.org) + +```bash +sudo emerge app-emulation/libpod +``` + +#### [openSUSE](https://www.opensuse.org) + +```bash +sudo zypper install podman +``` + +#### [openSUSE Kubic](https://kubic.opensuse.org) + +Built-in, no need to install + +#### [RHEL7](https://www.redhat.com/en/technologies/linux-platforms/enterprise-linux) + +Subscribe, then enable Extras channel and install podman. + +```bash +sudo subscription-manager repos --enable=rhel-7-server-extras-rpms +sudo yum -y install podman +``` + +#### [RHEL8 Beta](https://www.redhat.com/en/blog/powering-its-future-while-preserving-present-introducing-red-hat-enterprise-linux-8-beta?intcmp=701f2000001Cz6OAAS) + +```bash +sudo yum module enable -y container-tools:1.0 +sudo yum module install -y container-tools:1.0 +``` + +#### [Ubuntu](https://www.ubuntu.com) + +```bash +sudo apt-get update -qq +sudo apt-get install -qq -y software-properties-common +sudo add-apt-repository -y ppa:projectatomic/ppa +sudo apt-get -qq -y install podman +``` + +## Building from scratch + ### Prerequisites #### runc installed diff --git a/libpod.conf b/libpod.conf index cfdf83775..acd6c8982 100644 --- a/libpod.conf +++ b/libpod.conf @@ -4,17 +4,6 @@ # Default transport method for pulling and pushing for images image_default_transport = "docker://" -# Paths to look for a valid OCI runtime (runc, runv, etc) -runtime_path = [ - "/usr/bin/runc", - "/usr/sbin/runc", - "/usr/local/bin/runc", - "/usr/local/sbin/runc", - "/sbin/runc", - "/bin/runc", - "/usr/lib/cri-o-runc/sbin/runc" -] - # Paths to look for the Conmon container manager binary conmon_path = [ "/usr/libexec/podman/conmon", @@ -98,3 +87,15 @@ pause_command = "/pause" # Default libpod support for container labeling # label=true + +# Paths to look for a valid OCI runtime (runc, runv, etc) +[runtimes] +runc = [ + "/usr/bin/runc", + "/usr/sbin/runc", + "/usr/local/bin/runc", + "/usr/local/sbin/runc", + "/sbin/runc", + "/bin/runc", + "/usr/lib/cri-o-runc/sbin/runc" +] diff --git a/libpod/adapter/client.go b/libpod/adapter/client.go index 383c242c9..b3bb9acae 100644 --- a/libpod/adapter/client.go +++ b/libpod/adapter/client.go @@ -3,12 +3,32 @@ package adapter import ( + "os" + + "github.com/sirupsen/logrus" "github.com/varlink/go/varlink" ) +// DefaultAddress is the default address of the varlink socket +const DefaultAddress = "unix:/run/podman/io.podman" + // Connect provides a varlink connection func (r RemoteRuntime) Connect() (*varlink.Connection, error) { - connection, err := varlink.NewConnection("unix:/run/podman/io.podman") + var err error + var connection *varlink.Connection + if bridge := os.Getenv("PODMAN_VARLINK_BRIDGE"); bridge != "" { + logrus.Infof("Connecting with varlink bridge") + logrus.Debugf("%s", bridge) + connection, err = varlink.NewBridge(bridge) + } else { + address := os.Getenv("PODMAN_VARLINK_ADDRESS") + if address == "" { + address = DefaultAddress + } + logrus.Infof("Connecting with varlink address") + logrus.Debugf("%s", address) + connection, err = varlink.NewConnection(address) + } if err != nil { return nil, err } diff --git a/libpod/adapter/containers_remote.go b/libpod/adapter/containers_remote.go new file mode 100644 index 000000000..9623304e5 --- /dev/null +++ b/libpod/adapter/containers_remote.go @@ -0,0 +1,50 @@ +// +build remoteclient + +package adapter + +import ( + "encoding/json" + + iopodman "github.com/containers/libpod/cmd/podman/varlink" + "github.com/containers/libpod/libpod" + "github.com/containers/libpod/pkg/inspect" +) + +// Inspect returns an inspect struct from varlink +func (c *Container) Inspect(size bool) (*inspect.ContainerInspectData, error) { + reply, err := iopodman.ContainerInspectData().Call(c.Runtime.Conn, c.ID()) + if err != nil { + return nil, err + } + data := inspect.ContainerInspectData{} + if err := json.Unmarshal([]byte(reply), &data); err != nil { + return nil, err + } + return &data, err +} + +// ID returns the ID of the container +func (c *Container) ID() string { + return c.config.ID +} + +// GetArtifact returns a container's artifacts +func (c *Container) GetArtifact(name string) ([]byte, error) { + var data []byte + reply, err := iopodman.ContainerArtifacts().Call(c.Runtime.Conn, c.ID(), name) + if err != nil { + return nil, err + } + if err := json.Unmarshal([]byte(reply), &data); err != nil { + return nil, err + } + return data, err +} + +// Config returns a container's Config ... same as ctr.Config() +func (c *Container) Config() *libpod.ContainerConfig { + if c.config != nil { + return c.config + } + return c.Runtime.Config(c.ID()) +} diff --git a/libpod/adapter/images_remote.go b/libpod/adapter/images_remote.go index 77b0629a7..e7b38dccc 100644 --- a/libpod/adapter/images_remote.go +++ b/libpod/adapter/images_remote.go @@ -3,15 +3,22 @@ package adapter import ( - "github.com/containers/libpod/libpod" + "context" + "encoding/json" + + iopodman "github.com/containers/libpod/cmd/podman/varlink" + "github.com/containers/libpod/pkg/inspect" ) -// Images returns information for the host system and its components -func (r RemoteRuntime) Images() ([]libpod.InfoData, error) { - conn, err := r.Connect() +// Inspect returns returns an ImageData struct from over a varlink connection +func (i *ContainerImage) Inspect(ctx context.Context) (*inspect.ImageData, error) { + reply, err := iopodman.InspectImage().Call(i.Runtime.Conn, i.ID()) if err != nil { return nil, err } - _ = conn - return nil, nil + data := inspect.ImageData{} + if err := json.Unmarshal([]byte(reply), &data); err != nil { + return nil, err + } + return &data, nil } diff --git a/libpod/adapter/runtime.go b/libpod/adapter/runtime.go index 13141f886..f4961437e 100644 --- a/libpod/adapter/runtime.go +++ b/libpod/adapter/runtime.go @@ -3,6 +3,10 @@ package adapter import ( + "context" + "io" + + "github.com/containers/image/types" "github.com/containers/libpod/cmd/podman/libpodruntime" "github.com/containers/libpod/libpod" "github.com/containers/libpod/libpod/image" @@ -11,8 +15,8 @@ import ( // LocalRuntime describes a typical libpod runtime type LocalRuntime struct { - Runtime *libpod.Runtime - Remote bool + *libpod.Runtime + Remote bool } // ContainerImage ... @@ -20,6 +24,11 @@ type ContainerImage struct { *image.Image } +// Container ... +type Container struct { + *libpod.Container +} + // GetRuntime returns a LocalRuntime struct with the actual runtime embedded in it func GetRuntime(c *cli.Context) (*LocalRuntime, error) { runtime, err := libpodruntime.GetRuntime(c) @@ -53,3 +62,45 @@ func (r *LocalRuntime) NewImageFromLocal(name string) (*ContainerImage, error) { } return &ContainerImage{img}, nil } + +// LoadFromArchiveReference calls into local storage to load an image from an archive +func (r *LocalRuntime) LoadFromArchiveReference(ctx context.Context, srcRef types.ImageReference, signaturePolicyPath string, writer io.Writer) ([]*ContainerImage, error) { + var containerImages []*ContainerImage + imgs, err := r.Runtime.ImageRuntime().LoadFromArchiveReference(ctx, srcRef, signaturePolicyPath, writer) + if err != nil { + return nil, err + } + for _, i := range imgs { + ci := ContainerImage{i} + containerImages = append(containerImages, &ci) + } + return containerImages, nil +} + +// New calls into local storage to look for an image in local storage or to pull it +func (r *LocalRuntime) New(ctx context.Context, name, signaturePolicyPath, authfile string, writer io.Writer, dockeroptions *image.DockerRegistryOptions, signingoptions image.SigningOptions, forcePull bool) (*ContainerImage, error) { + img, err := r.Runtime.ImageRuntime().New(ctx, name, signaturePolicyPath, authfile, writer, dockeroptions, signingoptions, forcePull) + if err != nil { + return nil, err + } + return &ContainerImage{img}, nil +} + +// RemoveImage calls into local storage and removes an image +func (r *LocalRuntime) RemoveImage(ctx context.Context, img *ContainerImage, force bool) (string, error) { + return r.Runtime.RemoveImage(ctx, img.Image, force) +} + +// LookupContainer ... +func (r *LocalRuntime) LookupContainer(idOrName string) (*Container, error) { + ctr, err := r.Runtime.LookupContainer(idOrName) + if err != nil { + return nil, err + } + return &Container{ctr}, nil +} + +// PruneImages is wrapper into PruneImages within the image pkg +func (r *LocalRuntime) PruneImages(all bool) ([]string, error) { + return r.ImageRuntime().PruneImages(all) +} diff --git a/libpod/adapter/runtime_remote.go b/libpod/adapter/runtime_remote.go index 27301d90b..f184ce0a9 100644 --- a/libpod/adapter/runtime_remote.go +++ b/libpod/adapter/runtime_remote.go @@ -4,12 +4,18 @@ package adapter import ( "context" + "encoding/json" "fmt" + "io" "strings" "time" - iopodman "github.com/containers/libpod/cmd/podman/varlink" - digest "github.com/opencontainers/go-digest" + "github.com/containers/image/types" + "github.com/containers/libpod/cmd/podman/varlink" + "github.com/containers/libpod/libpod" + "github.com/containers/libpod/libpod/image" + "github.com/opencontainers/go-digest" + "github.com/sirupsen/logrus" "github.com/urfave/cli" "github.com/varlink/go/varlink" ) @@ -19,13 +25,13 @@ type RemoteImageRuntime struct{} // RemoteRuntime describes a wrapper runtime struct type RemoteRuntime struct { + Conn *varlink.Connection + Remote bool } // LocalRuntime describes a typical libpod runtime type LocalRuntime struct { - Runtime *RemoteRuntime - Remote bool - Conn *varlink.Connection + *RemoteRuntime } // GetRuntime returns a LocalRuntime struct with the actual runtime embedded in it @@ -35,11 +41,14 @@ func GetRuntime(c *cli.Context) (*LocalRuntime, error) { if err != nil { return nil, err } - return &LocalRuntime{ - Runtime: &runtime, - Remote: true, - Conn: conn, - }, nil + rr := RemoteRuntime{ + Conn: conn, + Remote: true, + } + foo := LocalRuntime{ + &rr, + } + return &foo, nil } // Shutdown is a bogus wrapper for compat with the libpod runtime @@ -67,6 +76,18 @@ type remoteImage struct { Runtime *LocalRuntime } +// Container ... +type Container struct { + remoteContainer +} + +// remoteContainer .... +type remoteContainer struct { + Runtime *LocalRuntime + config *libpod.ContainerConfig + state *libpod.ContainerState +} + // GetImages returns a slice of containerimages over a varlink connection func (r *LocalRuntime) GetImages() ([]*ContainerImage, error) { var newImages []*ContainerImage @@ -119,6 +140,42 @@ func (r *LocalRuntime) NewImageFromLocal(name string) (*ContainerImage, error) { } +// LoadFromArchiveReference creates an image from a local archive +func (r *LocalRuntime) LoadFromArchiveReference(ctx context.Context, srcRef types.ImageReference, signaturePolicyPath string, writer io.Writer) ([]*ContainerImage, error) { + // TODO We need to find a way to leak certDir, creds, and the tlsverify into this function, normally this would + // come from cli options but we don't want want those in here either. + imageID, err := iopodman.PullImage().Call(r.Conn, srcRef.DockerReference().String(), "", "", signaturePolicyPath, true) + if err != nil { + return nil, err + } + newImage, err := r.NewImageFromLocal(imageID) + if err != nil { + return nil, err + } + return []*ContainerImage{newImage}, nil +} + +// New calls into local storage to look for an image in local storage or to pull it +func (r *LocalRuntime) New(ctx context.Context, name, signaturePolicyPath, authfile string, writer io.Writer, dockeroptions *image.DockerRegistryOptions, signingoptions image.SigningOptions, forcePull bool) (*ContainerImage, error) { + // TODO Creds needs to be figured out here too, like above + tlsBool := dockeroptions.DockerInsecureSkipTLSVerify + // Remember SkipTlsVerify is the opposite of tlsverify + // If tlsBook is true or undefined, we do not skip + SkipTlsVerify := false + if tlsBool == types.OptionalBoolFalse { + SkipTlsVerify = true + } + imageID, err := iopodman.PullImage().Call(r.Conn, name, dockeroptions.DockerCertPath, "", signaturePolicyPath, SkipTlsVerify) + if err != nil { + return nil, err + } + newImage, err := r.NewImageFromLocal(imageID) + if err != nil { + return nil, err + } + return newImage, nil +} + func splitStringDate(d string) (time.Time, error) { fields := strings.Fields(d) t := fmt.Sprintf("%sT%sZ", fields[0], fields[1]) @@ -174,6 +231,95 @@ func (ci *ContainerImage) TagImage(tag string) error { return err } -func (r RemoteRuntime) RemoveImage(force bool) error { - return nil +// RemoveImage calls varlink to remove an image +func (r *LocalRuntime) RemoveImage(ctx context.Context, img *ContainerImage, force bool) (string, error) { + return iopodman.RemoveImage().Call(r.Conn, img.InputName, force) +} + +// History returns the history of an image and its layers +func (ci *ContainerImage) History(ctx context.Context) ([]*image.History, error) { + var imageHistories []*image.History + + reply, err := iopodman.HistoryImage().Call(ci.Runtime.Conn, ci.InputName) + if err != nil { + return nil, err + } + for _, h := range reply { + created, err := splitStringDate(h.Created) + if err != nil { + return nil, err + } + ih := image.History{ + ID: h.Id, + Created: &created, + CreatedBy: h.CreatedBy, + Size: h.Size, + Comment: h.Comment, + } + imageHistories = append(imageHistories, &ih) + } + return imageHistories, nil +} + +// LookupContainer gets basic information about container over a varlink +// connection and then translates it to a *Container +func (r *LocalRuntime) LookupContainer(idOrName string) (*Container, error) { + state, err := r.ContainerState(idOrName) + if err != nil { + return nil, err + } + config := r.Config(idOrName) + if err != nil { + return nil, err + } + + rc := remoteContainer{ + r, + config, + state, + } + + c := Container{ + rc, + } + return &c, nil +} + +func (r *LocalRuntime) GetLatestContainer() (*Container, error) { + return nil, libpod.ErrNotImplemented +} + +// ContainerState returns the "state" of the container. +func (r *LocalRuntime) ContainerState(name string) (*libpod.ContainerState, error) { //no-lint + reply, err := iopodman.ContainerStateData().Call(r.Conn, name) + if err != nil { + return nil, err + } + data := libpod.ContainerState{} + if err := json.Unmarshal([]byte(reply), &data); err != nil { + return nil, err + } + return &data, err + +} + +// Config returns a container config +func (r *LocalRuntime) Config(name string) *libpod.ContainerConfig { + // TODO the Spec being returned is not populated. Matt and I could not figure out why. Will defer + // further looking into it for after devconf. + // The libpod function for this has no errors so we are kind of in a tough + // spot here. Logging the errors for now. + reply, err := iopodman.ContainerConfig().Call(r.Conn, name) + if err != nil { + logrus.Error("call to container.config failed") + } + data := libpod.ContainerConfig{} + if err := json.Unmarshal([]byte(reply), &data); err != nil { + logrus.Error("failed to unmarshal container inspect data") + } + return &data + +} +func (r *LocalRuntime) PruneImages(all bool) ([]string, error) { + return iopodman.ImagesPrune().Call(r.Conn, all) } diff --git a/libpod/boltdb_state.go b/libpod/boltdb_state.go index e7a07a9a8..5bc15dd7f 100644 --- a/libpod/boltdb_state.go +++ b/libpod/boltdb_state.go @@ -205,7 +205,7 @@ func (s *BoltState) Refresh() error { return errors.Wrapf(ErrInternal, "container %s missing state in DB", string(id)) } - state := new(containerState) + state := new(ContainerState) if err := json.Unmarshal(stateBytes, state); err != nil { return errors.Wrapf(err, "error unmarshalling state for container %s", string(id)) @@ -325,7 +325,7 @@ func (s *BoltState) Container(id string) (*Container, error) { ctr := new(Container) ctr.config = new(ContainerConfig) - ctr.state = new(containerState) + ctr.state = new(ContainerState) db, err := s.getDBCon() if err != nil { @@ -361,7 +361,7 @@ func (s *BoltState) LookupContainer(idOrName string) (*Container, error) { ctr := new(Container) ctr.config = new(ContainerConfig) - ctr.state = new(containerState) + ctr.state = new(ContainerState) db, err := s.getDBCon() if err != nil { @@ -542,7 +542,7 @@ func (s *BoltState) UpdateContainer(ctr *Container) error { return errors.Wrapf(ErrNSMismatch, "container %s is in namespace %q, does not match our namespace %q", ctr.ID(), ctr.config.Namespace, s.namespace) } - newState := new(containerState) + newState := new(ContainerState) netNSPath := "" ctrID := []byte(ctr.ID()) @@ -754,7 +754,7 @@ func (s *BoltState) AllContainers() ([]*Container, error) { ctr := new(Container) ctr.config = new(ContainerConfig) - ctr.state = new(containerState) + ctr.state = new(ContainerState) if err := s.getContainerFromDB(id, ctr, ctrBucket); err != nil { // If the error is a namespace mismatch, we can @@ -1140,7 +1140,7 @@ func (s *BoltState) PodContainers(pod *Pod) ([]*Container, error) { err = podCtrs.ForEach(func(id, val []byte) error { newCtr := new(Container) newCtr.config = new(ContainerConfig) - newCtr.state = new(containerState) + newCtr.state = new(ContainerState) ctrs = append(ctrs, newCtr) return s.getContainerFromDB(id, newCtr, ctrBkt) diff --git a/libpod/boltdb_state_linux.go b/libpod/boltdb_state_linux.go index d91f311e5..09a9be606 100644 --- a/libpod/boltdb_state_linux.go +++ b/libpod/boltdb_state_linux.go @@ -8,7 +8,7 @@ import ( // replaceNetNS handle network namespace transitions after updating a // container's state. -func replaceNetNS(netNSPath string, ctr *Container, newState *containerState) error { +func replaceNetNS(netNSPath string, ctr *Container, newState *ContainerState) error { if netNSPath != "" { // Check if the container's old state has a good netns if ctr.state.NetNS != nil && netNSPath == ctr.state.NetNS.Path() { diff --git a/libpod/boltdb_state_unsupported.go b/libpod/boltdb_state_unsupported.go index 64610d304..244dc51a0 100644 --- a/libpod/boltdb_state_unsupported.go +++ b/libpod/boltdb_state_unsupported.go @@ -3,7 +3,7 @@ package libpod // replaceNetNS is exclusive to the Linux platform and is a no-op elsewhere -func replaceNetNS(netNSPath string, ctr *Container, newState *containerState) error { +func replaceNetNS(netNSPath string, ctr *Container, newState *ContainerState) error { return nil } diff --git a/libpod/common_test.go b/libpod/common_test.go index 4af68a040..df730098e 100644 --- a/libpod/common_test.go +++ b/libpod/common_test.go @@ -48,7 +48,7 @@ func getTestContainer(id, name string, manager lock.Manager) (*Container, error) }, }, }, - state: &containerState{ + state: &ContainerState{ State: ContainerStateRunning, ConfigPath: "/does/not/exist/specs/" + id, RunDir: "/does/not/exist/tmp/", @@ -166,10 +166,10 @@ func testContainersEqual(t *testing.T, a, b *Container, allowedEmpty bool) { aConfig := new(ContainerConfig) bConfig := new(ContainerConfig) - aState := new(containerState) - bState := new(containerState) + aState := new(ContainerState) + bState := new(ContainerState) - blankState := new(containerState) + blankState := new(ContainerState) assert.Equal(t, a.valid, b.valid) diff --git a/libpod/container.go b/libpod/container.go index ca83bbffe..b0589be3b 100644 --- a/libpod/container.go +++ b/libpod/container.go @@ -116,7 +116,7 @@ func (ns LinuxNS) String() string { type Container struct { config *ContainerConfig - state *containerState + state *ContainerState // Batched indicates that a container has been locked as part of a // Batch() operation @@ -136,10 +136,10 @@ type Container struct { requestedIP net.IP } -// containerState contains the current state of the container +// ContainerState contains the current state of the container // It is stored on disk in a tmpfs and recreated on reboot // easyjson:json -type containerState struct { +type ContainerState struct { // The current state of the running container State ContainerStatus `json:"state"` // The path to the JSON OCI runtime spec for this container @@ -350,6 +350,9 @@ type ContainerConfig struct { PostConfigureNetNS bool `json:"postConfigureNetNS"` + // OCIRuntime used to create the container + OCIRuntime string `json:"runtime,omitempty"` + // ExitCommand is the container's exit command. // This Command will be executed when the container exits ExitCommand []string `json:"exitCommand,omitempty"` @@ -412,14 +415,15 @@ func (c *Container) Spec() *spec.Spec { // config does not exist (e.g., because the container was never started) return // the spec from the config. func (c *Container) specFromState() (*spec.Spec, error) { - spec := c.config.Spec + returnSpec := c.config.Spec if f, err := os.Open(c.state.ConfigPath); err == nil { + returnSpec = new(spec.Spec) content, err := ioutil.ReadAll(f) if err != nil { return nil, errors.Wrapf(err, "error reading container config") } - if err := json.Unmarshal([]byte(content), &spec); err != nil { + if err := json.Unmarshal([]byte(content), &returnSpec); err != nil { return nil, errors.Wrapf(err, "error unmarshalling container config") } } else { @@ -429,7 +433,7 @@ func (c *Container) specFromState() (*spec.Spec, error) { } } - return spec, nil + return returnSpec, nil } // ID returns the container's ID @@ -1059,3 +1063,18 @@ func networkDisabled(c *Container) (bool, error) { } return false, nil } + +// ContainerState returns containerstate struct +func (c *Container) ContainerState() (*ContainerState, error) { + if !c.batched { + c.lock.Lock() + defer c.lock.Unlock() + + if err := c.syncContainer(); err != nil { + return nil, err + } + } + returnConfig := new(ContainerState) + deepcopier.Copy(c.state).To(returnConfig) + return c.state, nil +} diff --git a/libpod/container_internal.go b/libpod/container_internal.go index 90f4659da..39c1501da 100644 --- a/libpod/container_internal.go +++ b/libpod/container_internal.go @@ -388,7 +388,7 @@ func (c *Container) teardownStorage() error { // Reset resets state fields to default values // It is performed before a refresh and clears the state after a reboot // It does not save the results - assumes the database will do that for us -func resetState(state *containerState) error { +func resetState(state *ContainerState) error { state.PID = 0 state.Mountpoint = "" state.Mounted = false @@ -540,7 +540,7 @@ func (c *Container) isStopped() (bool, error) { if err != nil { return true, err } - return (c.state.State == ContainerStateStopped || c.state.State == ContainerStateExited), nil + return (c.state.State != ContainerStateRunning && c.state.State != ContainerStatePaused), nil } // save container state to the database diff --git a/libpod/container_internal_test.go b/libpod/container_internal_test.go index 124f1d20e..f1e2b70a7 100644 --- a/libpod/container_internal_test.go +++ b/libpod/container_internal_test.go @@ -37,7 +37,7 @@ func TestPostDeleteHooks(t *testing.T) { }, StaticDir: dir, // not the bundle, but good enough for this test }, - state: &containerState{ + state: &ContainerState{ ExtensionStageHooks: map[string][]rspec.Hook{ "poststop": { rspec.Hook{ diff --git a/libpod/image/prune.go b/libpod/image/prune.go index 6a1f160d5..8602c222c 100644 --- a/libpod/image/prune.go +++ b/libpod/image/prune.go @@ -1,9 +1,11 @@ package image +import "github.com/pkg/errors" + // GetPruneImages returns a slice of images that have no names/unused -func (ir *Runtime) GetPruneImages() ([]*Image, error) { +func (ir *Runtime) GetPruneImages(all bool) ([]*Image, error) { var ( - unamedImages []*Image + pruneImages []*Image ) allImages, err := ir.GetImages() if err != nil { @@ -11,16 +13,35 @@ func (ir *Runtime) GetPruneImages() ([]*Image, error) { } for _, i := range allImages { if len(i.Names()) == 0 { - unamedImages = append(unamedImages, i) + pruneImages = append(pruneImages, i) continue } - containers, err := i.Containers() - if err != nil { - return nil, err + if all { + containers, err := i.Containers() + if err != nil { + return nil, err + } + if len(containers) < 1 { + pruneImages = append(pruneImages, i) + } } - if len(containers) < 1 { - unamedImages = append(unamedImages, i) + } + return pruneImages, nil +} + +// PruneImages prunes dangling and optionally all unused images from the local +// image store +func (ir *Runtime) PruneImages(all bool) ([]string, error) { + var prunedCids []string + pruneImages, err := ir.GetPruneImages(all) + if err != nil { + return nil, errors.Wrap(err, "unable to get images to prune") + } + for _, p := range pruneImages { + if err := p.Remove(true); err != nil { + return nil, errors.Wrap(err, "failed to prune image") } + prunedCids = append(prunedCids, p.ID()) } - return unamedImages, nil + return prunedCids, nil } diff --git a/libpod/info.go b/libpod/info.go index a98f93897..191ce6810 100644 --- a/libpod/info.go +++ b/libpod/info.go @@ -4,7 +4,6 @@ import ( "bufio" "bytes" "fmt" - "github.com/containers/buildah" "io/ioutil" "os" "runtime" @@ -12,6 +11,7 @@ import ( "strings" "time" + "github.com/containers/buildah" "github.com/containers/libpod/pkg/rootless" "github.com/containers/libpod/pkg/util" "github.com/containers/libpod/utils" @@ -184,12 +184,12 @@ func (r *Runtime) GetConmonVersion() (string, error) { // GetOCIRuntimePath returns the path to the OCI Runtime Path the runtime is using func (r *Runtime) GetOCIRuntimePath() string { - return r.ociRuntimePath + return r.ociRuntimePath.Paths[0] } // GetOCIRuntimeVersion returns a string representation of the oci runtimes version func (r *Runtime) GetOCIRuntimeVersion() (string, error) { - output, err := utils.ExecCmd(r.ociRuntimePath, "--version") + output, err := utils.ExecCmd(r.ociRuntimePath.Paths[0], "--version") if err != nil { return "", err } diff --git a/libpod/networking_linux.go b/libpod/networking_linux.go index a343bee6a..f9caf26d1 100644 --- a/libpod/networking_linux.go +++ b/libpod/networking_linux.go @@ -121,6 +121,19 @@ func (r *Runtime) createNetNS(ctr *Container) (n ns.NetNS, q []*cnitypes.Result, return ctrNS, networkStatus, err } +type slirp4netnsCmdArg struct { + Proto string `json:"proto,omitempty"` + HostAddr string `json:"host_addr"` + HostPort int32 `json:"host_port"` + GuestAddr string `json:"guest_addr"` + GuestPort int32 `json:"guest_port"` +} + +type slirp4netnsCmd struct { + Execute string `json:"execute"` + Args slirp4netnsCmdArg `json:"arguments"` +} + // Configure the network namespace for a rootless container func (r *Runtime) setupRootlessNetNS(ctr *Container) (err error) { defer ctr.rootlessSlirpSyncR.Close() @@ -139,7 +152,15 @@ func (r *Runtime) setupRootlessNetNS(ctr *Container) (err error) { defer syncR.Close() defer syncW.Close() - cmd := exec.Command(path, "-c", "-e", "3", "-r", "4", fmt.Sprintf("%d", ctr.state.PID), "tap0") + havePortMapping := len(ctr.Config().PortMappings) > 0 + apiSocket := filepath.Join(r.ociRuntime.tmpDir, fmt.Sprintf("%s.net", ctr.config.ID)) + var cmd *exec.Cmd + if havePortMapping { + // if we need ports to be mapped from the host, create a API socket to use for communicating with slirp4netns. + cmd = exec.Command(path, "-c", "-e", "3", "-r", "4", "--api-socket", apiSocket, fmt.Sprintf("%d", ctr.state.PID), "tap0") + } else { + cmd = exec.Command(path, "-c", "-e", "3", "-r", "4", fmt.Sprintf("%d", ctr.state.PID), "tap0") + } cmd.SysProcAttr = &syscall.SysProcAttr{ Setpgid: true, @@ -162,19 +183,99 @@ func (r *Runtime) setupRootlessNetNS(ctr *Container) (err error) { if os.IsTimeout(err) { // Check if the process is still running. var status syscall.WaitStatus - _, err := syscall.Wait4(cmd.Process.Pid, &status, syscall.WNOHANG, nil) + pid, err := syscall.Wait4(cmd.Process.Pid, &status, syscall.WNOHANG, nil) if err != nil { return errors.Wrapf(err, "failed to read slirp4netns process status") } + if pid != cmd.Process.Pid { + continue + } if status.Exited() || status.Signaled() { return errors.New("slirp4netns failed") } - continue } return errors.Wrapf(err, "failed to read from slirp4netns sync pipe") } } + + if havePortMapping { + const pidWaitTimeout = 60 * time.Second + chWait := make(chan error) + go func() { + interval := 25 * time.Millisecond + for i := time.Duration(0); i < pidWaitTimeout; i += interval { + // Check if the process is still running. + var status syscall.WaitStatus + pid, err := syscall.Wait4(cmd.Process.Pid, &status, syscall.WNOHANG, nil) + if err != nil { + break + } + if pid != cmd.Process.Pid { + continue + } + if status.Exited() || status.Signaled() { + chWait <- fmt.Errorf("slirp4netns exited with status %d", status.ExitStatus()) + } + time.Sleep(interval) + } + }() + defer close(chWait) + + // wait that API socket file appears before trying to use it. + if _, err := WaitForFile(apiSocket, chWait, pidWaitTimeout*time.Millisecond); err != nil { + return errors.Wrapf(err, "waiting for slirp4nets to create the api socket file %s", apiSocket) + } + + // for each port we want to add we need to open a connection to the slirp4netns control socket + // and send the add_hostfwd command. + for _, i := range ctr.config.PortMappings { + conn, err := net.Dial("unix", apiSocket) + if err != nil { + return errors.Wrapf(err, "cannot open connection to %s", apiSocket) + } + defer conn.Close() + hostIP := i.HostIP + if hostIP == "" { + hostIP = "0.0.0.0" + } + cmd := slirp4netnsCmd{ + Execute: "add_hostfwd", + Args: slirp4netnsCmdArg{ + Proto: i.Protocol, + HostAddr: hostIP, + HostPort: i.HostPort, + GuestPort: i.ContainerPort, + }, + } + // create the JSON payload and send it. Mark the end of request shutting down writes + // to the socket, as requested by slirp4netns. + data, err := json.Marshal(&cmd) + if err != nil { + return errors.Wrapf(err, "cannot marshal JSON for slirp4netns") + } + if _, err := conn.Write([]byte(fmt.Sprintf("%s\n", data))); err != nil { + return errors.Wrapf(err, "cannot write to control socket %s", apiSocket) + } + if err := conn.(*net.UnixConn).CloseWrite(); err != nil { + return errors.Wrapf(err, "cannot shutdown the socket %s", apiSocket) + } + buf := make([]byte, 2048) + len, err := conn.Read(buf) + if err != nil { + return errors.Wrapf(err, "cannot read from control socket %s", apiSocket) + } + // if there is no 'error' key in the received JSON data, then the operation was + // successful. + var y map[string]interface{} + if err := json.Unmarshal(buf[0:len], &y); err != nil { + return errors.Wrapf(err, "error parsing error status from slirp4netns") + } + if e, found := y["error"]; found { + return errors.Errorf("error from slirp4netns while setting up port redirection: %v", e) + } + } + } return nil } diff --git a/libpod/oci.go b/libpod/oci.go index 7a908db2e..e55bd57dc 100644 --- a/libpod/oci.go +++ b/libpod/oci.go @@ -75,10 +75,10 @@ type syncInfo struct { } // Make a new OCI runtime with provided options -func newOCIRuntime(name string, path string, 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) (*OCIRuntime, error) { runtime := new(OCIRuntime) - runtime.name = name - runtime.path = path + runtime.name = oruntime.Name + runtime.path = oruntime.Paths[0] runtime.conmonPath = conmonPath runtime.conmonEnv = conmonEnv runtime.cgroupManager = cgroupManager @@ -323,7 +323,7 @@ func (r *OCIRuntime) createOCIContainer(ctr *Container, cgroupParent string, res cmd.Env = append(cmd.Env, fmt.Sprintf("HOME=%s", os.Getenv("HOME"))) cmd.Env = append(cmd.Env, fmt.Sprintf("XDG_RUNTIME_DIR=%s", runtimeDir)) - if r.reservePorts { + if r.reservePorts && !ctr.config.NetMode.IsSlirp4netns() { ports, err := bindPorts(ctr.config.PortMappings) if err != nil { return err @@ -356,18 +356,25 @@ func (r *OCIRuntime) createOCIContainer(ctr *Container, cgroupParent string, res // Set the label of the conmon process to be level :s0 // This will allow the container processes to talk to fifo-files // passed into the container by conmon - var plabel string + var ( + plabel string + con selinux.Context + ) plabel, err = selinux.CurrentLabel() if err != nil { childPipe.Close() return errors.Wrapf(err, "Failed to get current SELinux label") } - c := selinux.NewContext(plabel) + con, err = selinux.NewContext(plabel) + if err != nil { + return errors.Wrapf(err, "Failed to get new context from SELinux label") + } + runtime.LockOSThread() - if c["level"] != "s0" && c["level"] != "" { - c["level"] = "s0" - if err = label.SetProcessLabel(c.Get()); err != nil { + if con["level"] != "s0" && con["level"] != "" { + con["level"] = "s0" + if err = label.SetProcessLabel(con.Get()); err != nil { runtime.UnlockOSThread() return err } diff --git a/libpod/options.go b/libpod/options.go index 319e1f6c6..d965c058e 100644 --- a/libpod/options.go +++ b/libpod/options.go @@ -137,17 +137,17 @@ func WithStateType(storeType RuntimeStateStore) RuntimeOption { } // WithOCIRuntime specifies an OCI runtime to use for running containers. -func WithOCIRuntime(runtimePath string) RuntimeOption { +func WithOCIRuntime(runtime string) RuntimeOption { return func(rt *Runtime) error { if rt.valid { return ErrRuntimeFinalized } - if runtimePath == "" { + if runtime == "" { return errors.Wrapf(ErrInvalidArg, "must provide a valid path") } - rt.config.RuntimePath = []string{runtimePath} + rt.config.OCIRuntime = runtime return nil } diff --git a/libpod/runtime.go b/libpod/runtime.go index c9471247c..c7000d84a 100644 --- a/libpod/runtime.go +++ b/libpod/runtime.go @@ -86,7 +86,7 @@ type Runtime struct { imageContext *types.SystemContext ociRuntime *OCIRuntime netPlugin ocicni.CNIPlugin - ociRuntimePath string + ociRuntimePath OCIRuntimePath conmonPath string valid bool lock sync.RWMutex @@ -96,6 +96,14 @@ type Runtime struct { configuredFrom *runtimeConfiguredFrom } +// OCIRuntimePath contains information about an OCI runtime. +type OCIRuntimePath struct { + // Name of the runtime to refer to by the --runtime flag + Name string `toml:"name"` + // Paths to check for this executable + Paths []string `toml:"paths"` +} + // RuntimeConfig contains configuration options used to set up the runtime type RuntimeConfig struct { // StorageConfig is the configuration used by containers/storage @@ -118,10 +126,10 @@ type RuntimeConfig struct { // cause conflicts in containers/storage // As such this is not exposed via the config file StateType RuntimeStateStore `toml:"-"` - // RuntimePath is the path to OCI runtime binary for launching - // containers - // The first path pointing to a valid file will be used - RuntimePath []string `toml:"runtime_path"` + // OCIRuntime is the OCI runtime to use. + OCIRuntime string `toml:"runtime"` + // OCIRuntimes are the set of configured OCI runtimes (default is runc) + OCIRuntimes map[string][]string `toml:"runtimes"` // ConmonPath is the path to the Conmon binary used for managing // containers // The first path pointing to a valid file will be used @@ -213,14 +221,17 @@ var ( StorageConfig: storage.StoreOptions{}, ImageDefaultTransport: DefaultTransport, StateType: BoltDBStateStore, - RuntimePath: []string{ - "/usr/bin/runc", - "/usr/sbin/runc", - "/usr/local/bin/runc", - "/usr/local/sbin/runc", - "/sbin/runc", - "/bin/runc", - "/usr/lib/cri-o-runc/sbin/runc", + OCIRuntime: "runc", + OCIRuntimes: map[string][]string{ + "runc": { + "/usr/bin/runc", + "/usr/sbin/runc", + "/usr/local/bin/runc", + "/usr/local/sbin/runc", + "/sbin/runc", + "/bin/runc", + "/usr/lib/cri-o-runc/sbin/runc", + }, }, ConmonPath: []string{ "/usr/libexec/podman/conmon", @@ -414,8 +425,9 @@ func NewRuntimeFromConfig(configPath string, options ...RuntimeOption) (runtime runtime.config = new(RuntimeConfig) runtime.configuredFrom = new(runtimeConfiguredFrom) - // Set two fields not in the TOML config + // Set three fields not in the TOML config runtime.config.StateType = defaultRuntimeConfig.StateType + runtime.config.OCIRuntime = defaultRuntimeConfig.OCIRuntime runtime.config.StorageConfig = storage.StoreOptions{} // Check to see if the given configuration file exists @@ -453,22 +465,35 @@ func NewRuntimeFromConfig(configPath string, options ...RuntimeOption) (runtime func makeRuntime(runtime *Runtime) (err error) { // Find a working OCI runtime binary foundRuntime := false - for _, path := range runtime.config.RuntimePath { - stat, err := os.Stat(path) - if err != nil { - continue - } - if stat.IsDir() { - continue - } + // If runtime is an absolute path, then use it as it is. + if runtime.config.OCIRuntime[0] == '/' { foundRuntime = true - runtime.ociRuntimePath = path - break + runtime.ociRuntimePath = OCIRuntimePath{Name: filepath.Base(runtime.config.OCIRuntime), Paths: []string{runtime.config.OCIRuntime}} + } else { + // If not, look it up in the configuration. + paths := runtime.config.OCIRuntimes[runtime.config.OCIRuntime] + if paths != nil { + for _, path := range paths { + stat, err := os.Stat(path) + if err != nil { + if os.IsNotExist(err) { + continue + } + return errors.Wrapf(err, "cannot stat %s", path) + } + if !stat.Mode().IsRegular() { + continue + } + foundRuntime = true + runtime.ociRuntimePath = OCIRuntimePath{Name: runtime.config.OCIRuntime, Paths: []string{path}} + break + } + } } if !foundRuntime { return errors.Wrapf(ErrInvalidArg, "could not find a working binary (configured options: %v)", - runtime.config.RuntimePath) + runtime.config.OCIRuntimes) } // Find a working conmon binary @@ -530,6 +555,11 @@ func makeRuntime(runtime *Runtime) (err error) { // Reset defaults if they were not explicitly set if !runtime.configuredFrom.storageGraphDriverSet && dbConfig.GraphDriver != "" { + if runtime.config.StorageConfig.GraphDriverName != dbConfig.GraphDriver && + runtime.config.StorageConfig.GraphDriverName != "" { + logrus.Errorf("User-selected graph driver %s overwritten by graph driver %s from database - delete libpod local files to resolve", + runtime.config.StorageConfig.GraphDriverName, dbConfig.GraphDriver) + } runtime.config.StorageConfig.GraphDriverName = dbConfig.GraphDriver } if !runtime.configuredFrom.storageGraphRootSet && dbConfig.StorageRoot != "" { @@ -619,7 +649,7 @@ func makeRuntime(runtime *Runtime) (err error) { } // Make an OCI runtime to perform container operations - ociRuntime, err := newOCIRuntime("runc", runtime.ociRuntimePath, + ociRuntime, err := newOCIRuntime(runtime.ociRuntimePath, runtime.conmonPath, runtime.config.ConmonEnvVars, runtime.config.CgroupManager, runtime.config.TmpDir, runtime.config.MaxLogSize, runtime.config.NoPivotRoot, @@ -799,7 +829,11 @@ func (r *Runtime) refreshRootless() error { // Take advantage of a command that requires a new userns // so that we are running as the root user and able to use refresh() cmd := exec.Command(os.Args[0], "info") - return cmd.Run() + err := cmd.Run() + if err != nil { + return errors.Wrapf(err, "Error running %s info while refreshing state", os.Args[0]) + } + return nil } // Reconfigures the runtime after a reboot diff --git a/libpod/runtime_ctr.go b/libpod/runtime_ctr.go index ab79fe5fb..6d5ce5a7e 100644 --- a/libpod/runtime_ctr.go +++ b/libpod/runtime_ctr.go @@ -48,7 +48,7 @@ func (r *Runtime) newContainer(ctx context.Context, rSpec *spec.Spec, options .. ctr := new(Container) ctr.config = new(ContainerConfig) - ctr.state = new(containerState) + ctr.state = new(ContainerState) ctr.config.ID = stringid.GenerateNonCryptoID() @@ -62,6 +62,8 @@ func (r *Runtime) newContainer(ctx context.Context, rSpec *spec.Spec, options .. ctr.config.StopTimeout = CtrRemoveTimeout + ctr.config.OCIRuntime = r.config.OCIRuntime + // Set namespace based on current runtime namespace // Do so before options run so they can override it if r.config.Namespace != "" { diff --git a/libpod/version.go b/libpod/version.go index 966588ae9..d2b99a275 100644 --- a/libpod/version.go +++ b/libpod/version.go @@ -19,11 +19,12 @@ var ( //Version is an output struct for varlink type Version struct { - Version string - GoVersion string - GitCommit string - Built int64 - OsArch string + RemoteAPIVersion int64 + Version string + GoVersion string + GitCommit string + Built int64 + OsArch string } // GetVersion returns a VersionOutput struct for varlink and podman @@ -39,10 +40,11 @@ func GetVersion() (Version, error) { } } return Version{ - Version: podmanVersion.Version, - GoVersion: runtime.Version(), - GitCommit: gitCommit, - Built: buildTime, - OsArch: runtime.GOOS + "/" + runtime.GOARCH, + RemoteAPIVersion: podmanVersion.RemoteAPIVersion, + Version: podmanVersion.Version, + GoVersion: runtime.Version(), + GitCommit: gitCommit, + Built: buildTime, + OsArch: runtime.GOOS + "/" + runtime.GOARCH, }, nil } diff --git a/pkg/apparmor/apparmor_linux.go b/pkg/apparmor/apparmor_linux.go index 0787b3fa5..2c5022c1f 100644 --- a/pkg/apparmor/apparmor_linux.go +++ b/pkg/apparmor/apparmor_linux.go @@ -214,8 +214,15 @@ func CheckProfileAndLoadDefault(name string) (string, error) { return name, nil } - if name != "" && rootless.IsRootless() { - return "", errors.Wrapf(ErrApparmorRootless, "cannot load AppArmor profile %q", name) + // AppArmor is not supported in rootless mode as it requires root + // privileges. Return an error in case a specific profile is specified. + if rootless.IsRootless() { + if name != "" { + return "", errors.Wrapf(ErrApparmorRootless, "cannot load AppArmor profile %q", name) + } else { + logrus.Debug("skipping loading default AppArmor profile (rootless mode)") + return "", nil + } } if name != "" && !runcaa.IsEnabled() { @@ -230,7 +237,7 @@ func CheckProfileAndLoadDefault(name string) (string, error) { return "", err } if !isLoaded { - return "", fmt.Errorf("AppArmor profile %q specified but not loaded") + return "", fmt.Errorf("AppArmor profile %q specified but not loaded", name) } return name, nil } diff --git a/pkg/rootless/rootless_linux.c b/pkg/rootless/rootless_linux.c index 1d28ff68d..279a03d3f 100644 --- a/pkg/rootless/rootless_linux.c +++ b/pkg/rootless/rootless_linux.c @@ -70,9 +70,12 @@ get_cmd_line_args (pid_t pid) if (allocated == used) { allocated += 512; - buffer = realloc (buffer, allocated); - if (buffer == NULL) - return NULL; + char *tmp = realloc (buffer, allocated); + if (buffer == NULL) { + free(buffer); + return NULL; + } + buffer=tmp; } } close (fd); diff --git a/pkg/spec/createconfig.go b/pkg/spec/createconfig.go index 632d60b55..344f4afb9 100644 --- a/pkg/spec/createconfig.go +++ b/pkg/spec/createconfig.go @@ -422,11 +422,7 @@ func (c *CreateConfig) GetContainerCreateOptions(runtime *libpod.Runtime, pod *l } options = append(options, libpod.WithNetNSFrom(connectedCtr)) } else if !c.NetMode.IsHost() && !c.NetMode.IsNone() { - isRootless := rootless.IsRootless() postConfigureNetNS := c.NetMode.IsSlirp4netns() || (len(c.IDMappings.UIDMap) > 0 || len(c.IDMappings.GIDMap) > 0) && !c.UsernsMode.IsHost() - if isRootless && len(portBindings) > 0 { - return nil, errors.New("port bindings are not yet supported by rootless containers") - } options = append(options, libpod.WithNetNS(portBindings, postConfigureNetNS, string(c.NetMode), networks)) } diff --git a/pkg/spec/spec.go b/pkg/spec/spec.go index 9ef0223f2..46105af4a 100644 --- a/pkg/spec/spec.go +++ b/pkg/spec/spec.go @@ -376,6 +376,10 @@ func CreateConfigToOCISpec(config *CreateConfig) (*spec.Spec, error) { //nolint } func blockAccessToKernelFilesystems(config *CreateConfig, g *generate.Generator) { + if config.PidMode.IsHost() && rootless.IsRootless() { + return + } + if !config.Privileged { for _, mp := range []string{ "/proc/acpi", diff --git a/pkg/varlinkapi/containers.go b/pkg/varlinkapi/containers.go index 07d981786..a01e3cc2b 100644 --- a/pkg/varlinkapi/containers.go +++ b/pkg/varlinkapi/containers.go @@ -12,6 +12,7 @@ import ( "github.com/containers/libpod/cmd/podman/shared" "github.com/containers/libpod/cmd/podman/varlink" "github.com/containers/libpod/libpod" + cc "github.com/containers/libpod/pkg/spec" "github.com/containers/storage/pkg/archive" "github.com/pkg/errors" ) @@ -68,7 +69,12 @@ func (i *LibpodAPI) InspectContainer(call iopodman.VarlinkCall, name string) err if err != nil { return call.ReplyErrorOccurred(err.Error()) } - data, err := shared.GetCtrInspectInfo(ctr, inspectInfo) + artifact, err := getArtifact(ctr) + if err != nil { + return call.ReplyErrorOccurred(err.Error()) + } + + data, err := shared.GetCtrInspectInfo(ctr.Config(), inspectInfo, artifact) if err != nil { return call.ReplyErrorOccurred(err.Error()) } @@ -462,3 +468,81 @@ func (i *LibpodAPI) ContainerRestore(call iopodman.VarlinkCall, name string, kee } return call.ReplyContainerRestore(ctr.ID()) } + +func getArtifact(ctr *libpod.Container) (*cc.CreateConfig, error) { + var createArtifact cc.CreateConfig + artifact, err := ctr.GetArtifact("create-config") + if err != nil { + return nil, err + } + if err := json.Unmarshal(artifact, &createArtifact); err != nil { + return nil, err + } + return &createArtifact, nil +} + +// ContainerConfig returns just the container.config struct +func (i *LibpodAPI) ContainerConfig(call iopodman.VarlinkCall, name string) error { + ctr, err := i.Runtime.LookupContainer(name) + if err != nil { + return call.ReplyErrorOccurred(err.Error()) + } + config := ctr.Config() + b, err := json.Marshal(config) + if err != nil { + return call.ReplyErrorOccurred("unable to serialize container config") + } + return call.ReplyContainerConfig(string(b)) +} + +// ContainerArtifacts returns an untouched container's artifact in string format +func (i *LibpodAPI) ContainerArtifacts(call iopodman.VarlinkCall, name, artifactName string) error { + ctr, err := i.Runtime.LookupContainer(name) + if err != nil { + return call.ReplyErrorOccurred(err.Error()) + } + artifacts, err := ctr.GetArtifact(artifactName) + if err != nil { + return call.ReplyErrorOccurred("unable to get container artifacts") + } + b, err := json.Marshal(artifacts) + if err != nil { + return call.ReplyErrorOccurred("unable to serialize container artifacts") + } + return call.ReplyContainerArtifacts(string(b)) +} + +// ContainerInspectData returns the inspect data of a container in string format +func (i *LibpodAPI) ContainerInspectData(call iopodman.VarlinkCall, name string) error { + ctr, err := i.Runtime.LookupContainer(name) + if err != nil { + return call.ReplyErrorOccurred(err.Error()) + } + data, err := ctr.Inspect(true) + if err != nil { + return call.ReplyErrorOccurred("unable to inspect container") + } + b, err := json.Marshal(data) + if err != nil { + return call.ReplyErrorOccurred("unable to serialize container inspect data") + } + return call.ReplyContainerInspectData(string(b)) + +} + +// ContainerStateData returns a container's state data in string format +func (i *LibpodAPI) ContainerStateData(call iopodman.VarlinkCall, name string) error { + ctr, err := i.Runtime.LookupContainer(name) + if err != nil { + return call.ReplyErrorOccurred(err.Error()) + } + data, err := ctr.ContainerState() + if err != nil { + return call.ReplyErrorOccurred("unable to obtain container state") + } + b, err := json.Marshal(data) + if err != nil { + return call.ReplyErrorOccurred("unable to serialize container inspect data") + } + return call.ReplyContainerStateData(string(b)) +} diff --git a/pkg/varlinkapi/images.go b/pkg/varlinkapi/images.go index 744f031c0..d6a9b7301 100644 --- a/pkg/varlinkapi/images.go +++ b/pkg/varlinkapi/images.go @@ -627,19 +627,10 @@ func (i *LibpodAPI) ContainerRunlabel(call iopodman.VarlinkCall, input iopodman. } // ImagesPrune .... -func (i *LibpodAPI) ImagesPrune(call iopodman.VarlinkCall) error { - var ( - pruned []string - ) - pruneImages, err := i.Runtime.ImageRuntime().GetPruneImages() +func (i *LibpodAPI) ImagesPrune(call iopodman.VarlinkCall, all bool) error { + prunedImages, err := i.Runtime.ImageRuntime().PruneImages(all) if err != nil { - return err - } - for _, i := range pruneImages { - if err := i.Remove(true); err != nil { - return call.ReplyErrorOccurred(err.Error()) - } - pruned = append(pruned, i.ID()) + return call.ReplyErrorOccurred(err.Error()) } - return call.ReplyImagesPrune(pruned) + return call.ReplyImagesPrune(prunedImages) } diff --git a/pkg/varlinkapi/mount.go b/pkg/varlinkapi/mount.go index 84e6b2709..3b4fe87e3 100644 --- a/pkg/varlinkapi/mount.go +++ b/pkg/varlinkapi/mount.go @@ -6,7 +6,7 @@ import ( // ListContainerMounts ... func (i *LibpodAPI) ListContainerMounts(call iopodman.VarlinkCall) error { - var mounts []string + mounts := make(map[string]string) allContainers, err := i.Runtime.GetAllContainers() if err != nil { return call.ReplyErrorOccurred(err.Error()) @@ -17,7 +17,7 @@ func (i *LibpodAPI) ListContainerMounts(call iopodman.VarlinkCall) error { return call.ReplyErrorOccurred(err.Error()) } if mounted { - mounts = append(mounts, mountPoint) + mounts[container.ID()] = mountPoint } } return call.ReplyListContainerMounts(mounts) diff --git a/pkg/varlinkapi/system.go b/pkg/varlinkapi/system.go index e50643dd0..376502f21 100644 --- a/pkg/varlinkapi/system.go +++ b/pkg/varlinkapi/system.go @@ -16,11 +16,12 @@ func (i *LibpodAPI) GetVersion(call iopodman.VarlinkCall) error { } return call.ReplyGetVersion(iopodman.Version{ - Version: versionInfo.Version, - Go_version: versionInfo.GoVersion, - Git_commit: versionInfo.GitCommit, - Built: versionInfo.Built, - Os_arch: versionInfo.OsArch, + Remote_api_version: versionInfo.RemoteAPIVersion, + Version: versionInfo.Version, + Go_version: versionInfo.GoVersion, + Git_commit: versionInfo.GitCommit, + Built: versionInfo.Built, + Os_arch: versionInfo.OsArch, }) } diff --git a/test/e2e/attach_test.go b/test/e2e/attach_test.go index 6bc576461..42866d5a1 100644 --- a/test/e2e/attach_test.go +++ b/test/e2e/attach_test.go @@ -1,3 +1,5 @@ +// +build !remoteclient + package integration import ( diff --git a/test/e2e/checkpoint_test.go b/test/e2e/checkpoint_test.go index ca2f35fc9..fda6eb085 100644 --- a/test/e2e/checkpoint_test.go +++ b/test/e2e/checkpoint_test.go @@ -1,3 +1,5 @@ +// +build !remoteclient + package integration import ( diff --git a/test/e2e/commit_test.go b/test/e2e/commit_test.go index 18771c09e..34b218621 100644 --- a/test/e2e/commit_test.go +++ b/test/e2e/commit_test.go @@ -1,3 +1,5 @@ +// +build !remoteclient + package integration import ( diff --git a/test/e2e/common_test.go b/test/e2e/common_test.go new file mode 100644 index 000000000..308a6bf29 --- /dev/null +++ b/test/e2e/common_test.go @@ -0,0 +1,218 @@ +package integration + +import ( + "encoding/json" + "fmt" + "os" + "os/exec" + "path/filepath" + "strings" + "testing" + + "github.com/containers/libpod/pkg/inspect" + . "github.com/containers/libpod/test/utils" + "github.com/containers/storage/pkg/reexec" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +var ( + PODMAN_BINARY string + CONMON_BINARY string + CNI_CONFIG_DIR string + RUNC_BINARY string + INTEGRATION_ROOT string + CGROUP_MANAGER = "systemd" + ARTIFACT_DIR = "/tmp/.artifacts" + RESTORE_IMAGES = []string{ALPINE, BB} + defaultWaitTimeout = 90 +) + +// PodmanTestIntegration struct for command line options +type PodmanTestIntegration struct { + PodmanTest + ConmonBinary string + CrioRoot string + CNIConfigDir string + RunCBinary string + RunRoot string + StorageOptions string + SignaturePolicyPath string + CgroupManager string + Host HostOS +} + +// PodmanSessionIntegration sturct for command line session +type PodmanSessionIntegration struct { + *PodmanSession +} + +// TestLibpod ginkgo master function +func TestLibpod(t *testing.T) { + if reexec.Init() { + os.Exit(1) + } + if os.Getenv("NOCACHE") == "1" { + CACHE_IMAGES = []string{} + RESTORE_IMAGES = []string{} + } + RegisterFailHandler(Fail) + RunSpecs(t, "Libpod Suite") +} + +var _ = BeforeSuite(func() { + //Cache images + cwd, _ := os.Getwd() + INTEGRATION_ROOT = filepath.Join(cwd, "../../") + podman := PodmanTestCreate("/tmp") + podman.ArtifactPath = ARTIFACT_DIR + if _, err := os.Stat(ARTIFACT_DIR); os.IsNotExist(err) { + if err = os.Mkdir(ARTIFACT_DIR, 0777); err != nil { + fmt.Printf("%q\n", err) + os.Exit(1) + } + } + for _, image := range CACHE_IMAGES { + if err := podman.CreateArtifact(image); err != nil { + fmt.Printf("%q\n", err) + os.Exit(1) + } + } + host := GetHostDistributionInfo() + if host.Distribution == "rhel" && strings.HasPrefix(host.Version, "7") { + f, err := os.OpenFile("/proc/sys/user/max_user_namespaces", os.O_WRONLY, 0644) + if err != nil { + fmt.Println("Unable to enable userspace on RHEL 7") + os.Exit(1) + } + _, err = f.WriteString("15000") + if err != nil { + fmt.Println("Unable to enable userspace on RHEL 7") + os.Exit(1) + } + f.Close() + } +}) + +// PodmanTestCreate creates a PodmanTestIntegration instance for the tests +func PodmanTestCreateUtil(tempDir string, remote bool) *PodmanTestIntegration { + var ( + podmanRemoteBinary string + ) + + host := GetHostDistributionInfo() + cwd, _ := os.Getwd() + + podmanBinary := filepath.Join(cwd, "../../bin/podman") + if os.Getenv("PODMAN_BINARY") != "" { + podmanBinary = os.Getenv("PODMAN_BINARY") + } + + if remote { + podmanRemoteBinary = filepath.Join(cwd, "../../bin/podman-remote") + if os.Getenv("PODMAN_REMOTE_BINARY") != "" { + podmanRemoteBinary = os.Getenv("PODMAN_REMOTE_BINARY") + } + } + conmonBinary := filepath.Join("/usr/libexec/podman/conmon") + altConmonBinary := "/usr/libexec/crio/conmon" + if _, err := os.Stat(conmonBinary); os.IsNotExist(err) { + conmonBinary = altConmonBinary + } + if os.Getenv("CONMON_BINARY") != "" { + conmonBinary = os.Getenv("CONMON_BINARY") + } + storageOptions := STORAGE_OPTIONS + if os.Getenv("STORAGE_OPTIONS") != "" { + storageOptions = os.Getenv("STORAGE_OPTIONS") + } + cgroupManager := CGROUP_MANAGER + if os.Getenv("CGROUP_MANAGER") != "" { + cgroupManager = os.Getenv("CGROUP_MANAGER") + } + + // Ubuntu doesn't use systemd cgroups + if host.Distribution == "ubuntu" { + cgroupManager = "cgroupfs" + } + + runCBinary, err := exec.LookPath("runc") + // If we cannot find the runc binary, setting to something static as we have no way + // to return an error. The tests will fail and point out that the runc binary could + // not be found nicely. + if err != nil { + runCBinary = "/usr/bin/runc" + } + + CNIConfigDir := "/etc/cni/net.d" + + p := &PodmanTestIntegration{ + PodmanTest: PodmanTest{ + PodmanBinary: podmanBinary, + ArtifactPath: ARTIFACT_DIR, + TempDir: tempDir, + RemoteTest: remote, + }, + ConmonBinary: conmonBinary, + CrioRoot: filepath.Join(tempDir, "crio"), + CNIConfigDir: CNIConfigDir, + RunCBinary: runCBinary, + RunRoot: filepath.Join(tempDir, "crio-run"), + StorageOptions: storageOptions, + SignaturePolicyPath: filepath.Join(INTEGRATION_ROOT, "test/policy.json"), + CgroupManager: cgroupManager, + Host: host, + } + if remote { + p.PodmanTest.RemotePodmanBinary = podmanRemoteBinary + } + + // Setup registries.conf ENV variable + p.setDefaultRegistriesConfigEnv() + // Rewrite the PodmanAsUser function + p.PodmanMakeOptions = p.makeOptions + return p +} + +// RestoreAllArtifacts unpacks all cached images +func (p *PodmanTestIntegration) RestoreAllArtifacts() error { + if os.Getenv("NO_TEST_CACHE") != "" { + return nil + } + for _, image := range RESTORE_IMAGES { + if err := p.RestoreArtifact(image); err != nil { + return err + } + } + return nil +} + +// CreateArtifact creates a cached image in the artifact dir +func (p *PodmanTestIntegration) CreateArtifact(image string) error { + if os.Getenv("NO_TEST_CACHE") != "" { + return nil + } + fmt.Printf("Caching %s...", image) + dest := strings.Split(image, "/") + destName := fmt.Sprintf("/tmp/%s.tar", strings.Replace(strings.Join(strings.Split(dest[len(dest)-1], "/"), ""), ":", "-", -1)) + if _, err := os.Stat(destName); os.IsNotExist(err) { + pull := p.Podman([]string{"pull", image}) + pull.Wait(90) + + save := p.Podman([]string{"save", "-o", destName, image}) + save.Wait(90) + fmt.Printf("\n") + } else { + fmt.Printf(" already exists.\n") + } + return nil +} + +// InspectImageJSON takes the session output of an inspect +// image and returns json +func (s *PodmanSessionIntegration) InspectImageJSON() []inspect.ImageData { + var i []inspect.ImageData + err := json.Unmarshal(s.Out.Contents(), &i) + Expect(err).To(BeNil()) + return i +} diff --git a/test/e2e/create_staticip_test.go b/test/e2e/create_staticip_test.go index 17ac5cb40..9bdc30342 100644 --- a/test/e2e/create_staticip_test.go +++ b/test/e2e/create_staticip_test.go @@ -1,3 +1,5 @@ +// +build !remoteclient + package integration import ( diff --git a/test/e2e/create_test.go b/test/e2e/create_test.go index 684a7cd88..b28a0c428 100644 --- a/test/e2e/create_test.go +++ b/test/e2e/create_test.go @@ -1,3 +1,5 @@ +// +build !remoteclient + package integration import ( diff --git a/test/e2e/diff_test.go b/test/e2e/diff_test.go index 2c0060dd5..94e150467 100644 --- a/test/e2e/diff_test.go +++ b/test/e2e/diff_test.go @@ -1,3 +1,5 @@ +// +build !remoteclient + package integration import ( diff --git a/test/e2e/exec_test.go b/test/e2e/exec_test.go index a181501a5..5839b364d 100644 --- a/test/e2e/exec_test.go +++ b/test/e2e/exec_test.go @@ -1,3 +1,5 @@ +// +build !remoteclient + package integration import ( diff --git a/test/e2e/exists_test.go b/test/e2e/exists_test.go index d9652de4b..c4b5e4968 100644 --- a/test/e2e/exists_test.go +++ b/test/e2e/exists_test.go @@ -32,6 +32,7 @@ var _ = Describe("Podman image|container exists", func() { GinkgoWriter.Write([]byte(timedResult)) }) + It("podman image exists in local storage by fq name", func() { session := podmanTest.Podman([]string{"image", "exists", ALPINE}) session.WaitWithDefaultTimeout() @@ -48,6 +49,7 @@ var _ = Describe("Podman image|container exists", func() { Expect(session.ExitCode()).To(Equal(1)) }) It("podman container exists in local storage by name", func() { + SkipIfRemote() setup := podmanTest.RunTopContainer("foobar") setup.WaitWithDefaultTimeout() Expect(setup.ExitCode()).To(Equal(0)) @@ -57,6 +59,7 @@ var _ = Describe("Podman image|container exists", func() { Expect(session.ExitCode()).To(Equal(0)) }) It("podman container exists in local storage by container ID", func() { + SkipIfRemote() setup := podmanTest.RunTopContainer("") setup.WaitWithDefaultTimeout() Expect(setup.ExitCode()).To(Equal(0)) @@ -67,6 +70,7 @@ var _ = Describe("Podman image|container exists", func() { Expect(session.ExitCode()).To(Equal(0)) }) It("podman container exists in local storage by short container ID", func() { + SkipIfRemote() setup := podmanTest.RunTopContainer("") setup.WaitWithDefaultTimeout() Expect(setup.ExitCode()).To(Equal(0)) @@ -77,12 +81,14 @@ var _ = Describe("Podman image|container exists", func() { Expect(session.ExitCode()).To(Equal(0)) }) It("podman container does not exist in local storage", func() { + SkipIfRemote() session := podmanTest.Podman([]string{"container", "exists", "foobar"}) session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(1)) }) It("podman pod exists in local storage by name", func() { + SkipIfRemote() setup, rc, _ := podmanTest.CreatePod("foobar") setup.WaitWithDefaultTimeout() Expect(rc).To(Equal(0)) @@ -92,6 +98,7 @@ var _ = Describe("Podman image|container exists", func() { Expect(session.ExitCode()).To(Equal(0)) }) It("podman pod exists in local storage by container ID", func() { + SkipIfRemote() setup, rc, podID := podmanTest.CreatePod("") setup.WaitWithDefaultTimeout() Expect(rc).To(Equal(0)) @@ -101,6 +108,7 @@ var _ = Describe("Podman image|container exists", func() { Expect(session.ExitCode()).To(Equal(0)) }) It("podman pod exists in local storage by short container ID", func() { + SkipIfRemote() setup, rc, podID := podmanTest.CreatePod("") setup.WaitWithDefaultTimeout() Expect(rc).To(Equal(0)) @@ -110,6 +118,7 @@ var _ = Describe("Podman image|container exists", func() { Expect(session.ExitCode()).To(Equal(0)) }) It("podman pod does not exist in local storage", func() { + SkipIfRemote() session := podmanTest.Podman([]string{"pod", "exists", "foobar"}) session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(1)) diff --git a/test/e2e/export_test.go b/test/e2e/export_test.go index 42ea45041..de3f23667 100644 --- a/test/e2e/export_test.go +++ b/test/e2e/export_test.go @@ -1,3 +1,5 @@ +// +build !remoteclient + package integration import ( diff --git a/test/e2e/generate_kube_test.go b/test/e2e/generate_kube_test.go index 0ee078455..94e02dc55 100644 --- a/test/e2e/generate_kube_test.go +++ b/test/e2e/generate_kube_test.go @@ -1,3 +1,5 @@ +// +build !remoteclient + package integration import ( diff --git a/test/e2e/images_test.go b/test/e2e/images_test.go index af32c032b..595084403 100644 --- a/test/e2e/images_test.go +++ b/test/e2e/images_test.go @@ -106,6 +106,9 @@ var _ = Describe("Podman images", func() { }) It("podman images filter before image", func() { + if podmanTest.RemoteTest { + Skip("Does not work on remote client") + } dockerfile := `FROM docker.io/library/alpine:latest ` podmanTest.BuildImage(dockerfile, "foobar.com/before:latest", "false") @@ -116,6 +119,9 @@ var _ = Describe("Podman images", func() { }) It("podman images filter after image", func() { + if podmanTest.RemoteTest { + Skip("Does not work on remote client") + } rmi := podmanTest.Podman([]string{"rmi", "busybox"}) rmi.WaitWithDefaultTimeout() Expect(rmi.ExitCode()).To(Equal(0)) @@ -130,6 +136,9 @@ var _ = Describe("Podman images", func() { }) It("podman images filter dangling", func() { + if podmanTest.RemoteTest { + Skip("Does not work on remote client") + } dockerfile := `FROM docker.io/library/alpine:latest ` podmanTest.BuildImage(dockerfile, "foobar.com/before:latest", "false") @@ -141,6 +150,9 @@ var _ = Describe("Podman images", func() { }) It("podman check for image with sha256: prefix", func() { + if podmanTest.RemoteTest { + Skip("Does not work on remote client") + } session := podmanTest.Podman([]string{"inspect", "--format=json", ALPINE}) session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) @@ -175,6 +187,9 @@ var _ = Describe("Podman images", func() { }) It("podman images --all flag", func() { + if podmanTest.RemoteTest { + Skip("Does not work on remote client") + } dockerfile := `FROM docker.io/library/alpine:latest RUN mkdir hello RUN touch test.txt diff --git a/test/e2e/import_test.go b/test/e2e/import_test.go index 6f132fd93..dc7451f7b 100644 --- a/test/e2e/import_test.go +++ b/test/e2e/import_test.go @@ -1,3 +1,5 @@ +// +build !remoteclient + package integration import ( diff --git a/test/e2e/info_test.go b/test/e2e/info_test.go index e972c86c8..2022dff1b 100644 --- a/test/e2e/info_test.go +++ b/test/e2e/info_test.go @@ -1,3 +1,5 @@ +// +build !remoteclient + package integration import ( diff --git a/test/e2e/inspect_test.go b/test/e2e/inspect_test.go index 87c4db935..e5c471bf9 100644 --- a/test/e2e/inspect_test.go +++ b/test/e2e/inspect_test.go @@ -43,6 +43,7 @@ var _ = Describe("Podman inspect", func() { }) It("podman inspect bogus container", func() { + SkipIfRemote() session := podmanTest.Podman([]string{"inspect", "foobar4321"}) session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Not(Equal(0))) @@ -66,6 +67,7 @@ var _ = Describe("Podman inspect", func() { }) It("podman inspect container with size", func() { + SkipIfRemote() _, ec, _ := podmanTest.RunLsContainer("") Expect(ec).To(Equal(0)) @@ -77,6 +79,7 @@ var _ = Describe("Podman inspect", func() { }) It("podman inspect container and image", func() { + SkipIfRemote() ls, ec, _ := podmanTest.RunLsContainer("") Expect(ec).To(Equal(0)) cid := ls.OutputToString() @@ -88,6 +91,7 @@ var _ = Describe("Podman inspect", func() { }) It("podman inspect -l with additional input should fail", func() { + SkipIfRemote() result := podmanTest.Podman([]string{"inspect", "-l", "1234foobar"}) result.WaitWithDefaultTimeout() Expect(result.ExitCode()).To(Equal(125)) diff --git a/test/e2e/kill_test.go b/test/e2e/kill_test.go index 913a843cb..5f1f5f4c1 100644 --- a/test/e2e/kill_test.go +++ b/test/e2e/kill_test.go @@ -1,3 +1,5 @@ +// +build !remoteclient + package integration import ( diff --git a/test/e2e/libpod_suite_remoteclient_test.go b/test/e2e/libpod_suite_remoteclient_test.go new file mode 100644 index 000000000..4b769a574 --- /dev/null +++ b/test/e2e/libpod_suite_remoteclient_test.go @@ -0,0 +1,188 @@ +// +build remoteclient + +package integration + +import ( + "fmt" + "github.com/containers/libpod/libpod" + "github.com/containers/libpod/pkg/inspect" + "github.com/onsi/ginkgo" + "io/ioutil" + "os" + "os/exec" + "path/filepath" + "strings" +) + +func SkipIfRemote() { + ginkgo.Skip("This function is not enabled for remote podman") +} + +// Cleanup cleans up the temporary store +func (p *PodmanTestIntegration) Cleanup() { + p.StopVarlink() + // TODO + // Stop all containers + // Rm all containers + + if err := os.RemoveAll(p.TempDir); err != nil { + fmt.Printf("%q\n", err) + } + + // Clean up the registries configuration file ENV variable set in Create + resetRegistriesConfigEnv() +} + +// Podman is the exec call to podman on the filesystem +func (p *PodmanTestIntegration) Podman(args []string) *PodmanSessionIntegration { + podmanSession := p.PodmanBase(args) + return &PodmanSessionIntegration{podmanSession} +} + +//RunTopContainer runs a simple container in the background that +// runs top. If the name passed != "", it will have a name +func (p *PodmanTestIntegration) RunTopContainer(name string) *PodmanSessionIntegration { + // TODO + return nil +} + +//RunLsContainer runs a simple container in the background that +// simply runs ls. If the name passed != "", it will have a name +func (p *PodmanTestIntegration) RunLsContainer(name string) (*PodmanSessionIntegration, int, string) { + // TODO + return nil, 0, "" +} + +// InspectImageJSON takes the session output of an inspect +// image and returns json +//func (s *PodmanSessionIntegration) InspectImageJSON() []inspect.ImageData { +// // TODO +// return nil +//} + +func (p *PodmanTestIntegration) setDefaultRegistriesConfigEnv() { + defaultFile := filepath.Join(INTEGRATION_ROOT, "test/registries.conf") + os.Setenv("REGISTRIES_CONFIG_PATH", defaultFile) +} + +func (p *PodmanTestIntegration) setRegistriesConfigEnv(b []byte) { + outfile := filepath.Join(p.TempDir, "registries.conf") + os.Setenv("REGISTRIES_CONFIG_PATH", outfile) + ioutil.WriteFile(outfile, b, 0644) +} + +func resetRegistriesConfigEnv() { + os.Setenv("REGISTRIES_CONFIG_PATH", "") +} + +// InspectContainerToJSON takes the session output of an inspect +// container and returns json +func (s *PodmanSessionIntegration) InspectContainerToJSON() []inspect.ContainerData { + // TODO + return nil +} + +// CreatePod creates a pod with no infra container +// it optionally takes a pod name +func (p *PodmanTestIntegration) CreatePod(name string) (*PodmanSessionIntegration, int, string) { + // TODO + return nil, 0, "" +} + +func (p *PodmanTestIntegration) RunTopContainerInPod(name, pod string) *PodmanSessionIntegration { + // TODO + return nil +} + +// BuildImage uses podman build and buildah to build an image +// called imageName based on a string dockerfile +func (p *PodmanTestIntegration) BuildImage(dockerfile, imageName string, layers string) { + // TODO +} + +// CleanupPod cleans up the temporary store +func (p *PodmanTestIntegration) CleanupPod() { + // TODO +} + +// InspectPodToJSON takes the sessions output from a pod inspect and returns json +func (s *PodmanSessionIntegration) InspectPodToJSON() libpod.PodInspect { + // TODO + return libpod.PodInspect{} +} +func (p *PodmanTestIntegration) RunLsContainerInPod(name, pod string) (*PodmanSessionIntegration, int, string) { + // TODO + return nil, 0, "" +} + +// PullImages pulls multiple images +func (p *PodmanTestIntegration) PullImages(images []string) error { + // TODO + return libpod.ErrNotImplemented +} + +// PodmanPID execs podman and returns its PID +func (p *PodmanTestIntegration) PodmanPID(args []string) (*PodmanSessionIntegration, int) { + // TODO + return nil, 0 +} + +// CleanupVolume cleans up the temporary store +func (p *PodmanTestIntegration) CleanupVolume() { + // TODO +} + +func PodmanTestCreate(tempDir string) *PodmanTestIntegration { + pti := PodmanTestCreateUtil(tempDir, true) + pti.StartVarlink() + return pti +} + +func (p *PodmanTestIntegration) StartVarlink() { + if _, err := os.Stat("/path/to/whatever"); os.IsNotExist(err) { + os.MkdirAll("/run/podman", 0755) + } + args := []string{"varlink", "--timeout", "0", "unix:/run/podman/io.podman"} + podmanOptions := getVarlinkOptions(p, args) + command := exec.Command(p.PodmanBinary, podmanOptions...) + fmt.Printf("Running: %s %s\n", p.PodmanBinary, strings.Join(podmanOptions, " ")) + command.Start() + p.VarlinkSession = command.Process +} + +func (p *PodmanTestIntegration) StopVarlink() { + varlinkSession := p.VarlinkSession + varlinkSession.Kill() + varlinkSession.Wait() +} + +//MakeOptions assembles all the podman main options +func (p *PodmanTestIntegration) makeOptions(args []string) []string { + return args +} + +//MakeOptions assembles all the podman main options +func getVarlinkOptions(p *PodmanTestIntegration, args []string) []string { + podmanOptions := strings.Split(fmt.Sprintf("--root %s --runroot %s --runtime %s --conmon %s --cni-config-dir %s --cgroup-manager %s", + p.CrioRoot, p.RunRoot, p.RunCBinary, p.ConmonBinary, p.CNIConfigDir, p.CgroupManager), " ") + if os.Getenv("HOOK_OPTION") != "" { + podmanOptions = append(podmanOptions, os.Getenv("HOOK_OPTION")) + } + podmanOptions = append(podmanOptions, strings.Split(p.StorageOptions, " ")...) + podmanOptions = append(podmanOptions, args...) + return podmanOptions +} + +// RestoreArtifact puts the cached image into our test store +func (p *PodmanTestIntegration) RestoreArtifact(image string) error { + fmt.Printf("Restoring %s...\n", image) + dest := strings.Split(image, "/") + destName := fmt.Sprintf("/tmp/%s.tar", strings.Replace(strings.Join(strings.Split(dest[len(dest)-1], "/"), ""), ":", "-", -1)) + args := []string{"load", "-q", "-i", destName} + podmanOptions := getVarlinkOptions(p, args) + command := exec.Command(p.PodmanBinary, podmanOptions...) + fmt.Printf("Running: %s %s\n", p.PodmanBinary, strings.Join(podmanOptions, " ")) + command.Start() + command.Wait() + return nil +} diff --git a/test/e2e/libpod_suite_test.go b/test/e2e/libpod_suite_test.go index d312124ab..1f218cbdf 100644 --- a/test/e2e/libpod_suite_test.go +++ b/test/e2e/libpod_suite_test.go @@ -1,3 +1,5 @@ +// +build !remoteclient + package integration import ( @@ -8,172 +10,16 @@ import ( "os/exec" "path/filepath" "strings" - "testing" "github.com/containers/libpod/libpod" "github.com/containers/libpod/pkg/inspect" . "github.com/containers/libpod/test/utils" - "github.com/containers/storage/pkg/reexec" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" "github.com/onsi/gomega/gexec" ) -var ( - PODMAN_BINARY string - CONMON_BINARY string - CNI_CONFIG_DIR string - RUNC_BINARY string - INTEGRATION_ROOT string - CGROUP_MANAGER = "systemd" - ARTIFACT_DIR = "/tmp/.artifacts" - RESTORE_IMAGES = []string{ALPINE, BB} - defaultWaitTimeout = 90 -) - -// PodmanTestIntegration struct for command line options -type PodmanTestIntegration struct { - PodmanTest - ConmonBinary string - CrioRoot string - CNIConfigDir string - RunCBinary string - RunRoot string - StorageOptions string - SignaturePolicyPath string - CgroupManager string - Host HostOS -} - -// PodmanSessionIntegration sturct for command line session -type PodmanSessionIntegration struct { - *PodmanSession -} - -// TestLibpod ginkgo master function -func TestLibpod(t *testing.T) { - if reexec.Init() { - os.Exit(1) - } - if os.Getenv("NOCACHE") == "1" { - CACHE_IMAGES = []string{} - RESTORE_IMAGES = []string{} - } - RegisterFailHandler(Fail) - RunSpecs(t, "Libpod Suite") -} - -var _ = BeforeSuite(func() { - //Cache images - cwd, _ := os.Getwd() - INTEGRATION_ROOT = filepath.Join(cwd, "../../") - podman := PodmanTestCreate("/tmp") - podman.ArtifactPath = ARTIFACT_DIR - if _, err := os.Stat(ARTIFACT_DIR); os.IsNotExist(err) { - if err = os.Mkdir(ARTIFACT_DIR, 0777); err != nil { - fmt.Printf("%q\n", err) - os.Exit(1) - } - } - for _, image := range CACHE_IMAGES { - if err := podman.CreateArtifact(image); err != nil { - fmt.Printf("%q\n", err) - os.Exit(1) - } - } - host := GetHostDistributionInfo() - if host.Distribution == "rhel" && strings.HasPrefix(host.Version, "7") { - f, err := os.OpenFile("/proc/sys/user/max_user_namespaces", os.O_WRONLY, 0644) - if err != nil { - fmt.Println("Unable to enable userspace on RHEL 7") - os.Exit(1) - } - _, err = f.WriteString("15000") - if err != nil { - fmt.Println("Unable to enable userspace on RHEL 7") - os.Exit(1) - } - f.Close() - } -}) - -// PodmanTestCreate creates a PodmanTestIntegration instance for the tests -func PodmanTestCreate(tempDir string) *PodmanTestIntegration { - - host := GetHostDistributionInfo() - cwd, _ := os.Getwd() - - podmanBinary := filepath.Join(cwd, "../../bin/podman") - if os.Getenv("PODMAN_BINARY") != "" { - podmanBinary = os.Getenv("PODMAN_BINARY") - } - conmonBinary := filepath.Join("/usr/libexec/podman/conmon") - altConmonBinary := "/usr/libexec/crio/conmon" - if _, err := os.Stat(conmonBinary); os.IsNotExist(err) { - conmonBinary = altConmonBinary - } - if os.Getenv("CONMON_BINARY") != "" { - conmonBinary = os.Getenv("CONMON_BINARY") - } - storageOptions := STORAGE_OPTIONS - if os.Getenv("STORAGE_OPTIONS") != "" { - storageOptions = os.Getenv("STORAGE_OPTIONS") - } - cgroupManager := CGROUP_MANAGER - if os.Getenv("CGROUP_MANAGER") != "" { - cgroupManager = os.Getenv("CGROUP_MANAGER") - } - - // Ubuntu doesn't use systemd cgroups - if host.Distribution == "ubuntu" { - cgroupManager = "cgroupfs" - } - - runCBinary, err := exec.LookPath("runc") - // If we cannot find the runc binary, setting to something static as we have no way - // to return an error. The tests will fail and point out that the runc binary could - // not be found nicely. - if err != nil { - runCBinary = "/usr/bin/runc" - } - - CNIConfigDir := "/etc/cni/net.d" - - p := &PodmanTestIntegration{ - PodmanTest: PodmanTest{ - PodmanBinary: podmanBinary, - ArtifactPath: ARTIFACT_DIR, - TempDir: tempDir, - }, - ConmonBinary: conmonBinary, - CrioRoot: filepath.Join(tempDir, "crio"), - CNIConfigDir: CNIConfigDir, - RunCBinary: runCBinary, - RunRoot: filepath.Join(tempDir, "crio-run"), - StorageOptions: storageOptions, - SignaturePolicyPath: filepath.Join(INTEGRATION_ROOT, "test/policy.json"), - CgroupManager: cgroupManager, - Host: host, - } - - // Setup registries.conf ENV variable - p.setDefaultRegistriesConfigEnv() - // Rewrite the PodmanAsUser function - p.PodmanMakeOptions = p.makeOptions - return p -} - -//MakeOptions assembles all the podman main options -func (p *PodmanTestIntegration) makeOptions(args []string) []string { - podmanOptions := strings.Split(fmt.Sprintf("--root %s --runroot %s --runtime %s --conmon %s --cni-config-dir %s --cgroup-manager %s", - p.CrioRoot, p.RunRoot, p.RunCBinary, p.ConmonBinary, p.CNIConfigDir, p.CgroupManager), " ") - if os.Getenv("HOOK_OPTION") != "" { - podmanOptions = append(podmanOptions, os.Getenv("HOOK_OPTION")) - } - podmanOptions = append(podmanOptions, strings.Split(p.StorageOptions, " ")...) - podmanOptions = append(podmanOptions, args...) - return podmanOptions -} +func SkipIfRemote() {} // Podman is the exec call to podman on the filesystem func (p *PodmanTestIntegration) Podman(args []string) *PodmanSessionIntegration { @@ -272,59 +118,6 @@ func (s *PodmanSessionIntegration) InspectPodToJSON() libpod.PodInspect { return i } -// InspectImageJSON takes the session output of an inspect -// image and returns json -func (s *PodmanSessionIntegration) InspectImageJSON() []inspect.ImageData { - var i []inspect.ImageData - err := json.Unmarshal(s.Out.Contents(), &i) - Expect(err).To(BeNil()) - return i -} - -// CreateArtifact creates a cached image in the artifact dir -func (p *PodmanTestIntegration) CreateArtifact(image string) error { - if os.Getenv("NO_TEST_CACHE") != "" { - return nil - } - fmt.Printf("Caching %s...", image) - dest := strings.Split(image, "/") - destName := fmt.Sprintf("/tmp/%s.tar", strings.Replace(strings.Join(strings.Split(dest[len(dest)-1], "/"), ""), ":", "-", -1)) - if _, err := os.Stat(destName); os.IsNotExist(err) { - pull := p.Podman([]string{"pull", image}) - pull.Wait(90) - - save := p.Podman([]string{"save", "-o", destName, image}) - save.Wait(90) - fmt.Printf("\n") - } else { - fmt.Printf(" already exists.\n") - } - return nil -} - -// RestoreArtifact puts the cached image into our test store -func (p *PodmanTestIntegration) RestoreArtifact(image string) error { - fmt.Printf("Restoring %s...\n", image) - dest := strings.Split(image, "/") - destName := fmt.Sprintf("/tmp/%s.tar", strings.Replace(strings.Join(strings.Split(dest[len(dest)-1], "/"), ""), ":", "-", -1)) - restore := p.Podman([]string{"load", "-q", "-i", destName}) - restore.Wait(90) - return nil -} - -// RestoreAllArtifacts unpacks all cached images -func (p *PodmanTestIntegration) RestoreAllArtifacts() error { - if os.Getenv("NO_TEST_CACHE") != "" { - return nil - } - for _, image := range RESTORE_IMAGES { - if err := p.RestoreArtifact(image); err != nil { - return err - } - } - return nil -} - // CreatePod creates a pod with no infra container // it optionally takes a pod name func (p *PodmanTestIntegration) CreatePod(name string) (*PodmanSessionIntegration, int, string) { @@ -406,3 +199,29 @@ func (p *PodmanTestIntegration) setRegistriesConfigEnv(b []byte) { func resetRegistriesConfigEnv() { os.Setenv("REGISTRIES_CONFIG_PATH", "") } + +func PodmanTestCreate(tempDir string) *PodmanTestIntegration { + return PodmanTestCreateUtil(tempDir, false) +} + +//MakeOptions assembles all the podman main options +func (p *PodmanTestIntegration) makeOptions(args []string) []string { + podmanOptions := strings.Split(fmt.Sprintf("--root %s --runroot %s --runtime %s --conmon %s --cni-config-dir %s --cgroup-manager %s", + p.CrioRoot, p.RunRoot, p.RunCBinary, p.ConmonBinary, p.CNIConfigDir, p.CgroupManager), " ") + if os.Getenv("HOOK_OPTION") != "" { + podmanOptions = append(podmanOptions, os.Getenv("HOOK_OPTION")) + } + podmanOptions = append(podmanOptions, strings.Split(p.StorageOptions, " ")...) + podmanOptions = append(podmanOptions, args...) + return podmanOptions +} + +// RestoreArtifact puts the cached image into our test store +func (p *PodmanTestIntegration) RestoreArtifact(image string) error { + fmt.Printf("Restoring %s...\n", image) + dest := strings.Split(image, "/") + destName := fmt.Sprintf("/tmp/%s.tar", strings.Replace(strings.Join(strings.Split(dest[len(dest)-1], "/"), ""), ":", "-", -1)) + restore := p.Podman([]string{"load", "-q", "-i", destName}) + restore.Wait(90) + return nil +} diff --git a/test/e2e/load_test.go b/test/e2e/load_test.go index 4d7007191..423f99ac8 100644 --- a/test/e2e/load_test.go +++ b/test/e2e/load_test.go @@ -1,3 +1,5 @@ +// +build !remoteclient + package integration import ( diff --git a/test/e2e/logs_test.go b/test/e2e/logs_test.go index 236ddb221..d3c4fb802 100644 --- a/test/e2e/logs_test.go +++ b/test/e2e/logs_test.go @@ -1,3 +1,5 @@ +// +build !remoteclient + package integration import ( diff --git a/test/e2e/mount_test.go b/test/e2e/mount_test.go index a93a0aa4a..94218e6a9 100644 --- a/test/e2e/mount_test.go +++ b/test/e2e/mount_test.go @@ -1,3 +1,5 @@ +// +build !remoteclient + package integration import ( diff --git a/test/e2e/namespace_test.go b/test/e2e/namespace_test.go index ebce09f54..a0b6e6187 100644 --- a/test/e2e/namespace_test.go +++ b/test/e2e/namespace_test.go @@ -1,3 +1,5 @@ +// +build !remoteclient + package integration import ( diff --git a/test/e2e/pause_test.go b/test/e2e/pause_test.go index e109bc077..f1ea17ead 100644 --- a/test/e2e/pause_test.go +++ b/test/e2e/pause_test.go @@ -1,3 +1,5 @@ +// +build !remoteclient + package integration import ( diff --git a/test/e2e/pod_create_test.go b/test/e2e/pod_create_test.go index 5abf9613b..cb2b0e7b0 100644 --- a/test/e2e/pod_create_test.go +++ b/test/e2e/pod_create_test.go @@ -1,3 +1,5 @@ +// +build !remoteclient + package integration import ( diff --git a/test/e2e/pod_infra_container_test.go b/test/e2e/pod_infra_container_test.go index 8c7c09c97..161bf7f9c 100644 --- a/test/e2e/pod_infra_container_test.go +++ b/test/e2e/pod_infra_container_test.go @@ -1,3 +1,5 @@ +// +build !remoteclient + package integration import ( diff --git a/test/e2e/pod_inspect_test.go b/test/e2e/pod_inspect_test.go index 51e95f788..457acb373 100644 --- a/test/e2e/pod_inspect_test.go +++ b/test/e2e/pod_inspect_test.go @@ -1,3 +1,5 @@ +// +build !remoteclient + package integration import ( diff --git a/test/e2e/pod_kill_test.go b/test/e2e/pod_kill_test.go index d9cec2cad..419a3a777 100644 --- a/test/e2e/pod_kill_test.go +++ b/test/e2e/pod_kill_test.go @@ -1,3 +1,5 @@ +// +build !remoteclient + package integration import ( diff --git a/test/e2e/pod_pause_test.go b/test/e2e/pod_pause_test.go index 8f766d3db..a5192f84b 100644 --- a/test/e2e/pod_pause_test.go +++ b/test/e2e/pod_pause_test.go @@ -1,3 +1,5 @@ +// +build !remoteclient + package integration import ( diff --git a/test/e2e/pod_pod_namespaces.go b/test/e2e/pod_pod_namespaces.go index b1d5abb1c..9815e37ef 100644 --- a/test/e2e/pod_pod_namespaces.go +++ b/test/e2e/pod_pod_namespaces.go @@ -1,3 +1,5 @@ +// +build !remoteclient + package integration import ( diff --git a/test/e2e/pod_ps_test.go b/test/e2e/pod_ps_test.go index 9e816bcfa..3b7198861 100644 --- a/test/e2e/pod_ps_test.go +++ b/test/e2e/pod_ps_test.go @@ -1,3 +1,5 @@ +// +build !remoteclient + package integration import ( diff --git a/test/e2e/pod_restart_test.go b/test/e2e/pod_restart_test.go index d0964e8de..e8acfd2ec 100644 --- a/test/e2e/pod_restart_test.go +++ b/test/e2e/pod_restart_test.go @@ -1,3 +1,5 @@ +// +build !remoteclient + package integration import ( diff --git a/test/e2e/pod_rm_test.go b/test/e2e/pod_rm_test.go index 48767b33f..f63d2c8aa 100644 --- a/test/e2e/pod_rm_test.go +++ b/test/e2e/pod_rm_test.go @@ -1,3 +1,5 @@ +// +build !remoteclient + package integration import ( diff --git a/test/e2e/pod_start_test.go b/test/e2e/pod_start_test.go index 346346425..77e8b586d 100644 --- a/test/e2e/pod_start_test.go +++ b/test/e2e/pod_start_test.go @@ -1,3 +1,5 @@ +// +build !remoteclient + package integration import ( diff --git a/test/e2e/pod_stats_test.go b/test/e2e/pod_stats_test.go index d7b9a8f48..43d089a24 100644 --- a/test/e2e/pod_stats_test.go +++ b/test/e2e/pod_stats_test.go @@ -1,3 +1,5 @@ +// +build !remoteclient + package integration import ( diff --git a/test/e2e/pod_stop_test.go b/test/e2e/pod_stop_test.go index 6c5319a3d..b3d7df252 100644 --- a/test/e2e/pod_stop_test.go +++ b/test/e2e/pod_stop_test.go @@ -1,3 +1,5 @@ +// +build !remoteclient + package integration import ( diff --git a/test/e2e/pod_top_test.go b/test/e2e/pod_top_test.go index 3dc80ddfb..507d723b4 100644 --- a/test/e2e/pod_top_test.go +++ b/test/e2e/pod_top_test.go @@ -1,3 +1,5 @@ +// +build !remoteclient + package integration import ( diff --git a/test/e2e/port_test.go b/test/e2e/port_test.go index 09f3ab53a..fa633c379 100644 --- a/test/e2e/port_test.go +++ b/test/e2e/port_test.go @@ -1,3 +1,5 @@ +// +build !remoteclient + package integration import ( diff --git a/test/e2e/prune_test.go b/test/e2e/prune_test.go index 6679a676c..81fb82b20 100644 --- a/test/e2e/prune_test.go +++ b/test/e2e/prune_test.go @@ -39,6 +39,7 @@ var _ = Describe("Podman rm", func() { }) It("podman container prune containers", func() { + SkipIfRemote() top := podmanTest.RunTopContainer("") top.WaitWithDefaultTimeout() Expect(top.ExitCode()).To(Equal(0)) @@ -55,6 +56,7 @@ var _ = Describe("Podman rm", func() { }) It("podman image prune none images", func() { + SkipIfRemote() podmanTest.BuildImage(pruneImage, "alpine_bash:latest", "true") none := podmanTest.Podman([]string{"images", "-a"}) @@ -72,10 +74,11 @@ var _ = Describe("Podman rm", func() { Expect(none.ExitCode()).To(Equal(0)) hasNoneAfter, _ := after.GrepString("<none>") Expect(hasNoneAfter).To(BeFalse()) + Expect(len(after.OutputToStringArray()) > 1).To(BeTrue()) }) It("podman image prune unused images", func() { - prune := podmanTest.Podman([]string{"image", "prune"}) + prune := podmanTest.Podman([]string{"image", "prune", "-a"}) prune.WaitWithDefaultTimeout() Expect(prune.ExitCode()).To(Equal(0)) diff --git a/test/e2e/ps_test.go b/test/e2e/ps_test.go index 9caa6e7f1..9b1c55bb4 100644 --- a/test/e2e/ps_test.go +++ b/test/e2e/ps_test.go @@ -1,3 +1,5 @@ +// +build !remoteclient + package integration import ( @@ -5,6 +7,7 @@ import ( "os" "regexp" "sort" + "strings" . "github.com/containers/libpod/test/utils" "github.com/docker/go-units" @@ -146,10 +149,12 @@ var _ = Describe("Podman ps", func() { _, ec, _ := podmanTest.RunLsContainer("test1") Expect(ec).To(Equal(0)) - result := podmanTest.Podman([]string{"ps", "-a", "--format", "\"table {{.ID}} {{.Image}} {{.Labels}}\""}) + result := podmanTest.Podman([]string{"ps", "-a", "--format", "table {{.ID}} {{.Image}} {{.Labels}}"}) result.WaitWithDefaultTimeout() + Expect(strings.Contains(result.OutputToStringArray()[0], "table")).To(BeFalse()) + Expect(strings.Contains(result.OutputToStringArray()[0], "ID")).To(BeTrue()) + Expect(strings.Contains(result.OutputToStringArray()[1], "alpine:latest")).To(BeTrue()) Expect(result.ExitCode()).To(Equal(0)) - Expect(result.IsJSONOutputValid()).To(BeTrue()) }) It("podman ps ancestor filter flag", func() { diff --git a/test/e2e/pull_test.go b/test/e2e/pull_test.go index ad8742984..bfae15152 100644 --- a/test/e2e/pull_test.go +++ b/test/e2e/pull_test.go @@ -1,3 +1,5 @@ +// +build !remoteclient + package integration import ( diff --git a/test/e2e/push_test.go b/test/e2e/push_test.go index 3447cd57e..42aefd1f7 100644 --- a/test/e2e/push_test.go +++ b/test/e2e/push_test.go @@ -1,3 +1,5 @@ +// +build !remoteclient + package integration import ( diff --git a/test/e2e/refresh_test.go b/test/e2e/refresh_test.go index bf8fff105..de331bf88 100644 --- a/test/e2e/refresh_test.go +++ b/test/e2e/refresh_test.go @@ -1,3 +1,5 @@ +// +build !remoteclient + package integration import ( diff --git a/test/e2e/restart_test.go b/test/e2e/restart_test.go index 30801c272..3c77444d8 100644 --- a/test/e2e/restart_test.go +++ b/test/e2e/restart_test.go @@ -1,3 +1,5 @@ +// +build !remoteclient + package integration import ( diff --git a/test/e2e/rm_test.go b/test/e2e/rm_test.go index c6a2b61ee..bc1431bce 100644 --- a/test/e2e/rm_test.go +++ b/test/e2e/rm_test.go @@ -1,3 +1,5 @@ +// +build !remoteclient + package integration import ( diff --git a/test/e2e/rmi_test.go b/test/e2e/rmi_test.go index 22bfbbe8c..c160e1bc5 100644 --- a/test/e2e/rmi_test.go +++ b/test/e2e/rmi_test.go @@ -111,6 +111,7 @@ var _ = Describe("Podman rmi", func() { }) It("podman rmi image that is a parent of another image", func() { + SkipIfRemote() session := podmanTest.Podman([]string{"rmi", "-fa"}) session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) @@ -148,6 +149,7 @@ var _ = Describe("Podman rmi", func() { }) It("podman rmi image that is created from another named imaged", func() { + SkipIfRemote() session := podmanTest.Podman([]string{"rmi", "-fa"}) session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) @@ -183,6 +185,7 @@ var _ = Describe("Podman rmi", func() { }) It("podman rmi with cached images", func() { + SkipIfRemote() session := podmanTest.Podman([]string{"rmi", "-fa"}) session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) @@ -252,6 +255,7 @@ var _ = Describe("Podman rmi", func() { }) It("podman rmi -a with parent|child images", func() { + SkipIfRemote() dockerfile := `FROM docker.io/library/alpine:latest AS base RUN touch /1 ENV LOCAL=/1 diff --git a/test/e2e/rootless_test.go b/test/e2e/rootless_test.go index 8e9f9fc8d..2b84d34c9 100644 --- a/test/e2e/rootless_test.go +++ b/test/e2e/rootless_test.go @@ -1,3 +1,5 @@ +// +build !remoteclient + package integration import ( @@ -274,6 +276,10 @@ var _ = Describe("Podman rootless", func() { runRootlessHelper([]string{"--net", "host"}) }) + It("podman rootless rootfs --pid host", func() { + runRootlessHelper([]string{"--pid", "host"}) + }) + It("podman rootless rootfs --privileged", func() { runRootlessHelper([]string{"--privileged"}) }) diff --git a/test/e2e/run_cgroup_parent_test.go b/test/e2e/run_cgroup_parent_test.go index 57b3aa6b1..efc9a7009 100644 --- a/test/e2e/run_cgroup_parent_test.go +++ b/test/e2e/run_cgroup_parent_test.go @@ -1,3 +1,5 @@ +// +build !remoteclient + package integration import ( diff --git a/test/e2e/run_cleanup_test.go b/test/e2e/run_cleanup_test.go index 5b60efa86..aa823b4e6 100644 --- a/test/e2e/run_cleanup_test.go +++ b/test/e2e/run_cleanup_test.go @@ -1,3 +1,5 @@ +// +build !remoteclient + package integration import ( diff --git a/test/e2e/run_cpu_test.go b/test/e2e/run_cpu_test.go index 343fe656c..f74d3ed84 100644 --- a/test/e2e/run_cpu_test.go +++ b/test/e2e/run_cpu_test.go @@ -1,3 +1,5 @@ +// +build !remoteclient + package integration import ( diff --git a/test/e2e/run_device_test.go b/test/e2e/run_device_test.go index 7f1f7b2d0..4f26ac8ee 100644 --- a/test/e2e/run_device_test.go +++ b/test/e2e/run_device_test.go @@ -1,3 +1,5 @@ +// +build !remoteclient + package integration import ( diff --git a/test/e2e/run_dns_test.go b/test/e2e/run_dns_test.go index 444c568e0..6c649cdbc 100644 --- a/test/e2e/run_dns_test.go +++ b/test/e2e/run_dns_test.go @@ -1,3 +1,5 @@ +// +build !remoteclient + package integration import ( diff --git a/test/e2e/run_entrypoint_test.go b/test/e2e/run_entrypoint_test.go index 227037f92..a33e16b63 100644 --- a/test/e2e/run_entrypoint_test.go +++ b/test/e2e/run_entrypoint_test.go @@ -1,3 +1,5 @@ +// +build !remoteclient + package integration import ( diff --git a/test/e2e/run_exit_test.go b/test/e2e/run_exit_test.go index 788cbd8dd..03072f598 100644 --- a/test/e2e/run_exit_test.go +++ b/test/e2e/run_exit_test.go @@ -1,3 +1,5 @@ +// +build !remoteclient + package integration import ( diff --git a/test/e2e/run_memory_test.go b/test/e2e/run_memory_test.go index 91a311e85..e9262d4f0 100644 --- a/test/e2e/run_memory_test.go +++ b/test/e2e/run_memory_test.go @@ -1,3 +1,5 @@ +// +build !remoteclient + package integration import ( diff --git a/test/e2e/run_networking_test.go b/test/e2e/run_networking_test.go index 68b1f06de..1c09a4d0b 100644 --- a/test/e2e/run_networking_test.go +++ b/test/e2e/run_networking_test.go @@ -1,3 +1,5 @@ +// +build !remoteclient + package integration import ( diff --git a/test/e2e/run_ns_test.go b/test/e2e/run_ns_test.go index e4dcc5adc..9962185f2 100644 --- a/test/e2e/run_ns_test.go +++ b/test/e2e/run_ns_test.go @@ -1,3 +1,5 @@ +// +build !remoteclient + package integration import ( diff --git a/test/e2e/run_passwd_test.go b/test/e2e/run_passwd_test.go index 891f4fbd8..fcb81fb77 100644 --- a/test/e2e/run_passwd_test.go +++ b/test/e2e/run_passwd_test.go @@ -1,3 +1,5 @@ +// +build !remoteclient + package integration import ( diff --git a/test/e2e/run_privileged_test.go b/test/e2e/run_privileged_test.go index 770ea3e6b..0c0de30c5 100644 --- a/test/e2e/run_privileged_test.go +++ b/test/e2e/run_privileged_test.go @@ -1,3 +1,5 @@ +// +build !remoteclient + package integration import ( diff --git a/test/e2e/run_restart_test.go b/test/e2e/run_restart_test.go index 018c66b45..2659d2b11 100644 --- a/test/e2e/run_restart_test.go +++ b/test/e2e/run_restart_test.go @@ -1,3 +1,5 @@ +// +build !remoteclient + package integration import ( diff --git a/test/e2e/run_selinux_test.go b/test/e2e/run_selinux_test.go index 418382e16..57e488abc 100644 --- a/test/e2e/run_selinux_test.go +++ b/test/e2e/run_selinux_test.go @@ -1,3 +1,5 @@ +// +build !remoteclient + package integration import ( diff --git a/test/e2e/run_signal_test.go b/test/e2e/run_signal_test.go index 8f7894db8..9be8e7810 100644 --- a/test/e2e/run_signal_test.go +++ b/test/e2e/run_signal_test.go @@ -1,3 +1,5 @@ +// +build !remoteclient + package integration import ( diff --git a/test/e2e/run_staticip_test.go b/test/e2e/run_staticip_test.go index 749835b47..bf50e5eb7 100644 --- a/test/e2e/run_staticip_test.go +++ b/test/e2e/run_staticip_test.go @@ -1,3 +1,5 @@ +// +build !remoteclient + package integration import ( diff --git a/test/e2e/run_test.go b/test/e2e/run_test.go index 1e7f4f0f4..22a36bb6c 100644 --- a/test/e2e/run_test.go +++ b/test/e2e/run_test.go @@ -1,3 +1,5 @@ +// +build !remoteclient + package integration import ( diff --git a/test/e2e/run_userns_test.go b/test/e2e/run_userns_test.go index b1f3d08b4..254897e70 100644 --- a/test/e2e/run_userns_test.go +++ b/test/e2e/run_userns_test.go @@ -1,3 +1,5 @@ +// +build !remoteclient + package integration import ( diff --git a/test/e2e/runlabel_test.go b/test/e2e/runlabel_test.go index 93a19ba30..9b4f584b0 100644 --- a/test/e2e/runlabel_test.go +++ b/test/e2e/runlabel_test.go @@ -1,3 +1,5 @@ +// +build !remoteclient + package integration import ( diff --git a/test/e2e/save_test.go b/test/e2e/save_test.go index 9f64e49a7..b354492b8 100644 --- a/test/e2e/save_test.go +++ b/test/e2e/save_test.go @@ -1,3 +1,5 @@ +// +build !remoteclient + package integration import ( diff --git a/test/e2e/search_test.go b/test/e2e/search_test.go index 0167e9062..1438fd97b 100644 --- a/test/e2e/search_test.go +++ b/test/e2e/search_test.go @@ -1,3 +1,5 @@ +// +build !remoteclient + package integration import ( diff --git a/test/e2e/start_test.go b/test/e2e/start_test.go index 9d7ac145c..c4ed6f545 100644 --- a/test/e2e/start_test.go +++ b/test/e2e/start_test.go @@ -1,3 +1,5 @@ +// +build !remoteclient + package integration import ( diff --git a/test/e2e/stats_test.go b/test/e2e/stats_test.go index be00d68b2..e7b0b5f6e 100644 --- a/test/e2e/stats_test.go +++ b/test/e2e/stats_test.go @@ -1,3 +1,5 @@ +// +build !remoteclient + package integration import ( diff --git a/test/e2e/stop_test.go b/test/e2e/stop_test.go index 5c229b9b4..8fffedbb9 100644 --- a/test/e2e/stop_test.go +++ b/test/e2e/stop_test.go @@ -1,3 +1,5 @@ +// +build !remoteclient + package integration import ( diff --git a/test/e2e/systemd_test.go b/test/e2e/systemd_test.go index ce67bb469..a7e7a1500 100644 --- a/test/e2e/systemd_test.go +++ b/test/e2e/systemd_test.go @@ -1,3 +1,5 @@ +// +build !remoteclient + package integration import ( diff --git a/test/e2e/tag_test.go b/test/e2e/tag_test.go index 53896d1a2..9f67eaf80 100644 --- a/test/e2e/tag_test.go +++ b/test/e2e/tag_test.go @@ -1,3 +1,5 @@ +// +build !remoteclient + package integration import ( diff --git a/test/e2e/top_test.go b/test/e2e/top_test.go index cfcf2a959..067358468 100644 --- a/test/e2e/top_test.go +++ b/test/e2e/top_test.go @@ -1,3 +1,5 @@ +// +build !remoteclient + package integration import ( diff --git a/test/e2e/trust_test.go b/test/e2e/trust_test.go index bbf09eca4..0d36266f6 100644 --- a/test/e2e/trust_test.go +++ b/test/e2e/trust_test.go @@ -1,3 +1,5 @@ +// +build !remoteclient + package integration import ( diff --git a/test/e2e/volume_create_test.go b/test/e2e/volume_create_test.go index 50ee63f2a..9e525786e 100644 --- a/test/e2e/volume_create_test.go +++ b/test/e2e/volume_create_test.go @@ -1,3 +1,5 @@ +// +build !remoteclient + package integration import ( diff --git a/test/e2e/volume_inspect_test.go b/test/e2e/volume_inspect_test.go index d0d5a601e..aacdbe8be 100644 --- a/test/e2e/volume_inspect_test.go +++ b/test/e2e/volume_inspect_test.go @@ -1,3 +1,5 @@ +// +build !remoteclient + package integration import ( diff --git a/test/e2e/volume_ls_test.go b/test/e2e/volume_ls_test.go index 119d29d9b..d2ee558c1 100644 --- a/test/e2e/volume_ls_test.go +++ b/test/e2e/volume_ls_test.go @@ -1,3 +1,5 @@ +// +build !remoteclient + package integration import ( diff --git a/test/e2e/volume_prune_test.go b/test/e2e/volume_prune_test.go index 8c0a10e77..008acc2a2 100644 --- a/test/e2e/volume_prune_test.go +++ b/test/e2e/volume_prune_test.go @@ -1,3 +1,5 @@ +// +build !remoteclient + package integration import ( diff --git a/test/e2e/volume_rm_test.go b/test/e2e/volume_rm_test.go index cebb09467..295b290e4 100644 --- a/test/e2e/volume_rm_test.go +++ b/test/e2e/volume_rm_test.go @@ -1,3 +1,5 @@ +// +build !remoteclient + package integration import ( diff --git a/test/e2e/wait_test.go b/test/e2e/wait_test.go index a7e9b4c06..08da97aa0 100644 --- a/test/e2e/wait_test.go +++ b/test/e2e/wait_test.go @@ -1,3 +1,5 @@ +// +build !remoteclient + package integration import ( diff --git a/test/utils/utils.go b/test/utils/utils.go index 288c768d4..23dcb95e3 100644 --- a/test/utils/utils.go +++ b/test/utils/utils.go @@ -33,10 +33,13 @@ type PodmanTestCommon interface { // PodmanTest struct for command line options type PodmanTest struct { - PodmanMakeOptions func(args []string) []string - PodmanBinary string - ArtifactPath string - TempDir string + PodmanMakeOptions func(args []string) []string + PodmanBinary string + ArtifactPath string + TempDir string + RemoteTest bool + RemotePodmanBinary string + VarlinkSession *os.Process } // PodmanSession wraps the gexec.session so we can extend it @@ -61,17 +64,20 @@ func (p *PodmanTest) MakeOptions(args []string) []string { func (p *PodmanTest) PodmanAsUserBase(args []string, uid, gid uint32, env []string) *PodmanSession { var command *exec.Cmd podmanOptions := p.MakeOptions(args) - + podmanBinary := p.PodmanBinary + if p.RemoteTest { + podmanBinary = p.RemotePodmanBinary + } if env == nil { - fmt.Printf("Running: %s %s\n", p.PodmanBinary, strings.Join(podmanOptions, " ")) + fmt.Printf("Running: %s %s\n", podmanBinary, strings.Join(podmanOptions, " ")) } else { - fmt.Printf("Running: (env: %v) %s %s\n", env, p.PodmanBinary, strings.Join(podmanOptions, " ")) + fmt.Printf("Running: (env: %v) %s %s\n", env, podmanBinary, strings.Join(podmanOptions, " ")) } if uid != 0 || gid != 0 { - nsEnterOpts := append([]string{"--userspec", fmt.Sprintf("%d:%d", uid, gid), "/", p.PodmanBinary}, podmanOptions...) + nsEnterOpts := append([]string{"--userspec", fmt.Sprintf("%d:%d", uid, gid), "/", podmanBinary}, podmanOptions...) command = exec.Command("chroot", nsEnterOpts...) } else { - command = exec.Command(p.PodmanBinary, podmanOptions...) + command = exec.Command(podmanBinary, podmanOptions...) } if env != nil { command.Env = env diff --git a/transfer.md b/transfer.md index 00e732265..c0d309575 100644 --- a/transfer.md +++ b/transfer.md @@ -40,7 +40,7 @@ There are other equivalents for these tools | `docker attach` | [`podman exec`](./docs/podman-attach.1.md) | | `docker build` | [`podman build`](./docs/podman-build.1.md) | | `docker commit` | [`podman commit`](./docs/podman-commit.1.md) | -| `docker container`|[`podman container`](./docs/podman-container.1.md) | +| `docker container`|[`podman container`](./docs/podman-container.1.md) | | `docker cp` | [`podman mount`](./docs/podman-cp.1.md) **** | | `docker create` | [`podman create`](./docs/podman-create.1.md) | | `docker diff` | [`podman diff`](./docs/podman-diff.1.md) | @@ -70,7 +70,13 @@ There are other equivalents for these tools | `docker top` | [`podman top`](./docs/podman-top.1.md) | | `docker unpause` | [`podman unpause`](./docs/podman-unpause.1.md) | | `docker version` | [`podman version`](./docs/podman-version.1.md) | -| `docker wait` | [`podman wait`](./docs/podman-wait.1.md) | +| `docker volume` | [`podman volume`](./docs/podman-volume.1.md) | +| `docker volume create` | [`podman volume create`](./docs/podman-volume-create.1.md) | +| `docker volume inspect`| [`podman volume inspect`](./docs/podman-volume-inspect.1.md)| +| `docker volume ls` | [`podman volume ls`](./docs/podman-volume-ls.1.md) | +| `docker volume prune` | [`podman volume prune`](./docs/podman-volume-prune.1.md) | +| `docker volume rm` | [`podman volume rm`](./docs/podman-volume-rm.1.md) | +| `docker wait` | [`podman wait`](./docs/podman-wait.1.md) | **** Use mount to take advantage of the entire linux tool chain rather then just cp. Read [`here`](./docs/podman-cp.1.md) for more information. @@ -83,18 +89,45 @@ Those Docker commands currently do not have equivalents in `podman`: | `docker events` || | `docker network` || | `docker node` || -| `docker plugin` |podman does not support plugins. We recommend you use alternative OCI Runtimes or OCI Runtime Hooks to alter behavior of podman.| +| `docker plugin` | podman does not support plugins. We recommend you use alternative OCI Runtimes or OCI Runtime Hooks to alter behavior of podman.| | `docker rename` | podman does not support rename, you need to use `podman rm` and `podman create` to rename a container.| | `docker secret` || | `docker service` || | `docker stack` || | `docker swarm` | podman does not support swarm. We support Kubernetes for orchestration using [CRI-O](https://github.com/kubernetes-sigs/cri-o).| | `docker system` || -| `docker volume` | podman does not support volumes. Volumes should be built on the host operating system and then volume mounted into the containers.| +| `docker volume` | podman currently supports file volumes. Future enhancement planned to support Docker Volumes Plugins ## Missing commands in Docker The following podman commands do not have a Docker equivalent: +* [`podman generate`](./docs/podman-generate.1.md) +* [`podman generate kube`](./docs/podman-generate-kube.1.md) +* [`podman container checkpoint`](/docs/podman-container-checkpoint.1.md) +* [`podman container cleanup`](/docs/podman-container-cleanup.1.md) +* [`podman container exists`](/docs/podman-container-exists.1.md) +* [`podman container refresh`](/docs/podman-container-refresh.1.md) +* [`podman container runlabel`](/docs/podman-container-runlabel.1.md) +* [`podman container restore`](/docs/podman-container-restore.1.md) +* [`podman image exists`](./docs/podman-image-exists.1.md) +* [`podman image sign`](./docs/podman-image-sign.1.md) +* [`podman image trust`](./docs/podman-image-trust.1.md) * [`podman mount`](./docs/podman-mount.1.md) +* [`podman play`](./docs/podman-play.1.md) +* [`podman play kube`](./docs/podman-play-kube.1.md) +* [`podman pod`](./docs/podman-pod.1.md) +* [`podman pod create`](./docs/podman-pod-create.1.md) +* [`podman pod exists`](./docs/podman-pod-exists.1.md) +* [`podman pod inspect`](./docs/podman-pod-inspect.1.md) +* [`podman pod kill`](./docs/podman-pod-kill.1.md) +* [`podman pod pause`](./docs/podman-pod-pause.1.md) +* [`podman pod ps`](./docs/podman-pod-ps.1.md) +* [`podman pod restart`](./docs/podman-pod-restart.1.md) +* [`podman pod rm`](./docs/podman-pod-rm.1.md) +* [`podman pod start`](./docs/podman-pod-start.1.md) +* [`podman pod stop`](./docs/podman-pod-stop.1.md) +* [`podman pod top`](./docs/podman-pod-top.1.md) +* [`podman pod unpause`](./docs/podman-pod-unpause.1.md) +* [`podman varlink`](./docs/podman-varlink.1.md) * [`podman umount`](./docs/podman-umount.1.md) diff --git a/troubleshooting.md b/troubleshooting.md index ac3335da0..d210d85df 100644 --- a/troubleshooting.md +++ b/troubleshooting.md @@ -161,3 +161,15 @@ standard_init_linux.go:203: exec user process caused "permission denied" #### Solution Since the administrator of the system setup your home directory to be noexec, you will not be allowed to execute containers from storage in your home directory. It is possible to work around this by manually specifying a container storage path that is not on a noexec mount. Simply copy the file /etc/containers/storage.conf to ~/.config/containers/ (creating the directory if necessary). Specify a graphroot directory which is not on a noexec mount point and to which you have read/write privileges. You will need to modify other fields to writable directories as well. + +For example + +``` +cat ~/.config/containers/storage.conf +[storage] + driver = "overlay" + runroot = "/run/user/1000" + graphroot = "/execdir/myuser/storage" + [storage.options] + mount_program = "/bin/fuse-overlayfs" +``` diff --git a/vendor.conf b/vendor.conf index b6c58165c..76c2b8810 100644 --- a/vendor.conf +++ b/vendor.conf @@ -16,7 +16,7 @@ github.com/containerd/continuity 004b46473808b3e7a4a3049c20e4376c91eb966d github.com/containernetworking/cni v0.7.0-alpha1 github.com/containernetworking/plugins v0.7.4 github.com/containers/image v1.3 -github.com/containers/storage v1.4 +github.com/containers/storage v1.8 github.com/containers/psgo v1.1 github.com/coreos/go-systemd v14 github.com/cri-o/ocicni 2d2983e40c242322a56c22a903785e7f83eb378c @@ -54,7 +54,7 @@ github.com/opencontainers/image-spec v1.0.0 github.com/opencontainers/runc v1.0.0-rc6 github.com/opencontainers/runtime-spec 1722abf79c2f8f2675f47367f827c6491472cf27 github.com/opencontainers/runtime-tools v0.8.0 -github.com/opencontainers/selinux v1.0.0 +github.com/opencontainers/selinux v1.1 github.com/ostreedev/ostree-go d0388bd827cfac6fa8eec760246eb8375674b2a0 github.com/pkg/errors v0.8.1 github.com/pmezard/go-difflib v1.0.0 @@ -90,7 +90,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/varlink/go e9fdc57f40123518ac513eb3443e50625ad6b434 +github.com/varlink/go 92687ab4eb68d99e43b1f5b93477ad76bb54f811 github.com/containers/buildah e7ca330f923701dba8859f5c014d0a9a3f7f0a49 # TODO: Gotty has not been updated since 2012. Can we find replacement? github.com/Nvveen/Gotty cd527374f1e5bff4938207604a14f2e38a9cf512 diff --git a/vendor/github.com/containers/storage/containers_ffjson.go b/vendor/github.com/containers/storage/containers_ffjson.go index aef6becfe..40b912bb3 100644 --- a/vendor/github.com/containers/storage/containers_ffjson.go +++ b/vendor/github.com/containers/storage/containers_ffjson.go @@ -1,5 +1,5 @@ // Code generated by ffjson <https://github.com/pquerna/ffjson>. DO NOT EDIT. -// source: containers.go +// source: ./containers.go package storage diff --git a/vendor/github.com/containers/storage/drivers/aufs/aufs.go b/vendor/github.com/containers/storage/drivers/aufs/aufs.go index ca69816be..e821bc0c5 100644 --- a/vendor/github.com/containers/storage/drivers/aufs/aufs.go +++ b/vendor/github.com/containers/storage/drivers/aufs/aufs.go @@ -253,6 +253,11 @@ func (a *Driver) AdditionalImageStores() []string { return nil } +// CreateFromTemplate creates a layer with the same contents and parent as another layer. +func (a *Driver) CreateFromTemplate(id, template string, templateIDMappings *idtools.IDMappings, parent string, parentIDMappings *idtools.IDMappings, opts *graphdriver.CreateOpts, readWrite bool) error { + return graphdriver.NaiveCreateFromTemplate(a, id, template, templateIDMappings, parent, parentIDMappings, opts, readWrite) +} + // CreateReadWrite creates a layer that is writable for use as a container // file system. func (a *Driver) CreateReadWrite(id, parent string, opts *graphdriver.CreateOpts) error { diff --git a/vendor/github.com/containers/storage/drivers/btrfs/btrfs.go b/vendor/github.com/containers/storage/drivers/btrfs/btrfs.go index 567cda9d3..30254d9fb 100644 --- a/vendor/github.com/containers/storage/drivers/btrfs/btrfs.go +++ b/vendor/github.com/containers/storage/drivers/btrfs/btrfs.go @@ -490,6 +490,11 @@ func (d *Driver) quotasDirID(id string) string { return path.Join(d.quotasDir(), id) } +// CreateFromTemplate creates a layer with the same contents and parent as another layer. +func (d *Driver) CreateFromTemplate(id, template string, templateIDMappings *idtools.IDMappings, parent string, parentIDMappings *idtools.IDMappings, opts *graphdriver.CreateOpts, readWrite bool) error { + return d.Create(id, template, opts) +} + // CreateReadWrite creates a layer that is writable for use as a container // file system. func (d *Driver) CreateReadWrite(id, parent string, opts *graphdriver.CreateOpts) error { diff --git a/vendor/github.com/containers/storage/drivers/devmapper/driver.go b/vendor/github.com/containers/storage/drivers/devmapper/driver.go index 39a4fbe2c..13677c93a 100644 --- a/vendor/github.com/containers/storage/drivers/devmapper/driver.go +++ b/vendor/github.com/containers/storage/drivers/devmapper/driver.go @@ -123,6 +123,11 @@ func (d *Driver) Cleanup() error { return err } +// CreateFromTemplate creates a layer with the same contents and parent as another layer. +func (d *Driver) CreateFromTemplate(id, template string, templateIDMappings *idtools.IDMappings, parent string, parentIDMappings *idtools.IDMappings, opts *graphdriver.CreateOpts, readWrite bool) error { + return d.Create(id, template, opts) +} + // CreateReadWrite creates a layer that is writable for use as a container // file system. func (d *Driver) CreateReadWrite(id, parent string, opts *graphdriver.CreateOpts) error { diff --git a/vendor/github.com/containers/storage/drivers/driver.go b/vendor/github.com/containers/storage/drivers/driver.go index 476b55160..dda172574 100644 --- a/vendor/github.com/containers/storage/drivers/driver.go +++ b/vendor/github.com/containers/storage/drivers/driver.go @@ -72,6 +72,9 @@ type ProtoDriver interface { // specified id and parent and options passed in opts. Parent // may be "" and opts may be nil. Create(id, parent string, opts *CreateOpts) error + // CreateFromTemplate creates a new filesystem layer with the specified id + // and parent, with contents identical to the specified template layer. + CreateFromTemplate(id, template string, templateIDMappings *idtools.IDMappings, parent string, parentIDMappings *idtools.IDMappings, opts *CreateOpts, readWrite bool) error // Remove attempts to remove the filesystem layer with this id. Remove(id string) error // Get returns the mountpoint for the layered filesystem referred diff --git a/vendor/github.com/containers/storage/drivers/overlay/check.go b/vendor/github.com/containers/storage/drivers/overlay/check.go index 590d517fa..a566e4afd 100644 --- a/vendor/github.com/containers/storage/drivers/overlay/check.go +++ b/vendor/github.com/containers/storage/drivers/overlay/check.go @@ -10,6 +10,8 @@ import ( "path/filepath" "syscall" + "github.com/containers/storage/pkg/ioutils" + "github.com/containers/storage/pkg/mount" "github.com/containers/storage/pkg/system" "github.com/pkg/errors" "github.com/sirupsen/logrus" @@ -57,10 +59,11 @@ func doesSupportNativeDiff(d, mountOpts string) error { } opts := fmt.Sprintf("lowerdir=%s:%s,upperdir=%s,workdir=%s", path.Join(td, "l2"), path.Join(td, "l1"), path.Join(td, "l3"), path.Join(td, "work")) - if mountOpts != "" { - opts = fmt.Sprintf("%s,%s", opts, mountOpts) + flags, data := mount.ParseOptions(mountOpts) + if data != "" { + opts = fmt.Sprintf("%s,%s", opts, data) } - if err := unix.Mount("overlay", filepath.Join(td, "merged"), "overlay", 0, opts); err != nil { + if err := unix.Mount("overlay", filepath.Join(td, "merged"), "overlay", uintptr(flags), opts); err != nil { return errors.Wrap(err, "failed to mount overlay") } defer func() { @@ -103,3 +106,60 @@ func doesSupportNativeDiff(d, mountOpts string) error { return nil } + +// doesMetacopy checks if the filesystem is going to optimize changes to +// metadata by using nodes marked with an "overlay.metacopy" attribute to avoid +// copying up a file from a lower layer unless/until its contents are being +// modified +func doesMetacopy(d, mountOpts string) (bool, error) { + td, err := ioutil.TempDir(d, "metacopy-check") + if err != nil { + return false, err + } + defer func() { + if err := os.RemoveAll(td); err != nil { + logrus.Warnf("Failed to remove check directory %v: %v", td, err) + } + }() + + // Make directories l1, l2, work, merged + if err := os.MkdirAll(filepath.Join(td, "l1"), 0755); err != nil { + return false, err + } + if err := ioutils.AtomicWriteFile(filepath.Join(td, "l1", "f"), []byte{0xff}, 0700); err != nil { + return false, err + } + if err := os.MkdirAll(filepath.Join(td, "l2"), 0755); err != nil { + return false, err + } + if err := os.Mkdir(filepath.Join(td, "work"), 0755); err != nil { + return false, err + } + if err := os.Mkdir(filepath.Join(td, "merged"), 0755); err != nil { + return false, err + } + // Mount using the mandatory options and configured options + opts := fmt.Sprintf("lowerdir=%s,upperdir=%s,workdir=%s", path.Join(td, "l1"), path.Join(td, "l2"), path.Join(td, "work")) + flags, data := mount.ParseOptions(mountOpts) + if data != "" { + opts = fmt.Sprintf("%s,%s", opts, data) + } + if err := unix.Mount("overlay", filepath.Join(td, "merged"), "overlay", uintptr(flags), opts); err != nil { + return false, errors.Wrap(err, "failed to mount overlay for metacopy check") + } + defer func() { + if err := unix.Unmount(filepath.Join(td, "merged"), 0); err != nil { + logrus.Warnf("Failed to unmount check directory %v: %v", filepath.Join(td, "merged"), err) + } + }() + // Make a change that only impacts the inode, and check if the pulled-up copy is marked + // as a metadata-only copy + if err := os.Chmod(filepath.Join(td, "merged", "f"), 0600); err != nil { + return false, errors.Wrap(err, "error changing permissions on file for metacopy check") + } + metacopy, err := system.Lgetxattr(filepath.Join(td, "l2", "f"), "trusted.overlay.metacopy") + if err != nil { + return false, errors.Wrap(err, "metacopy flag was not set on file in upper layer") + } + return metacopy != nil, nil +} diff --git a/vendor/github.com/containers/storage/drivers/overlay/overlay.go b/vendor/github.com/containers/storage/drivers/overlay/overlay.go index 2455b6736..57d6dd63a 100644 --- a/vendor/github.com/containers/storage/drivers/overlay/overlay.go +++ b/vendor/github.com/containers/storage/drivers/overlay/overlay.go @@ -85,13 +85,12 @@ const ( ) type overlayOptions struct { - overrideKernelCheck bool - imageStores []string - quota quota.Quota - mountProgram string - ostreeRepo string - skipMountHome bool - mountOptions string + imageStores []string + quota quota.Quota + mountProgram string + ostreeRepo string + skipMountHome bool + mountOptions string } // Driver contains information about the home directory and the list of active mounts that are created using this driver. @@ -105,6 +104,7 @@ type Driver struct { options overlayOptions naiveDiff graphdriver.DiffDriver supportsDType bool + usingMetacopy bool locker *locker.Locker convert map[string]bool } @@ -158,6 +158,7 @@ func Init(home string, options []string, uidMaps, gidMaps []idtools.IDMap) (grap return nil, err } + var usingMetacopy bool var supportsDType bool if opts.mountProgram != "" { supportsDType = true @@ -172,6 +173,17 @@ func Init(home string, options []string, uidMaps, gidMaps []idtools.IDMap) (grap } return nil, errors.Wrap(err, "kernel does not support overlay fs") } + usingMetacopy, err = doesMetacopy(home, opts.mountOptions) + if err == nil { + if usingMetacopy { + logrus.Debugf("overlay test mount indicated that metacopy is being used") + } else { + logrus.Debugf("overlay test mount indicated that metacopy is not being used") + } + } else { + logrus.Warnf("overlay test mount did not indicate whether or not metacopy is being used: %v", err) + return nil, err + } } if !opts.skipMountHome { @@ -193,6 +205,7 @@ func Init(home string, options []string, uidMaps, gidMaps []idtools.IDMap) (grap gidMaps: gidMaps, ctr: graphdriver.NewRefCounter(graphdriver.NewFsChecker(graphdriver.FsMagicOverlay)), supportsDType: supportsDType, + usingMetacopy: usingMetacopy, locker: locker.New(), options: *opts, convert: make(map[string]bool), @@ -212,7 +225,7 @@ func Init(home string, options []string, uidMaps, gidMaps []idtools.IDMap) (grap return nil, fmt.Errorf("Storage option overlay.size only supported for backingFS XFS. Found %v", backingFs) } - logrus.Debugf("backingFs=%s, projectQuotaSupported=%v, useNativeDiff=%v", backingFs, projectQuotaSupported, !d.useNaiveDiff()) + logrus.Debugf("backingFs=%s, projectQuotaSupported=%v, useNativeDiff=%v, usingMetacopy=%v", backingFs, projectQuotaSupported, !d.useNaiveDiff(), d.usingMetacopy) return d, nil } @@ -226,12 +239,6 @@ func parseOptions(options []string) (*overlayOptions, error) { } key = strings.ToLower(key) switch key { - case ".override_kernel_check", "overlay.override_kernel_check", "overlay2.override_kernel_check": - logrus.Debugf("overlay: override_kernelcheck=%s", val) - o.overrideKernelCheck, err = strconv.ParseBool(val) - if err != nil { - return nil, err - } case ".mountopt", "overlay.mountopt", "overlay2.mountopt": o.mountOptions = val case ".size", "overlay.size", "overlay2.size": @@ -375,6 +382,7 @@ func (d *Driver) Status() [][2]string { {"Backing Filesystem", backingFs}, {"Supports d_type", strconv.FormatBool(d.supportsDType)}, {"Native Overlay Diff", strconv.FormatBool(!d.useNaiveDiff())}, + {"Using metacopy", strconv.FormatBool(d.usingMetacopy)}, } } @@ -410,6 +418,14 @@ func (d *Driver) Cleanup() error { return mount.Unmount(d.home) } +// CreateFromTemplate creates a layer with the same contents and parent as another layer. +func (d *Driver) CreateFromTemplate(id, template string, templateIDMappings *idtools.IDMappings, parent string, parentIDMappings *idtools.IDMappings, opts *graphdriver.CreateOpts, readWrite bool) error { + if readWrite { + return d.CreateReadWrite(id, template, opts) + } + return d.Create(id, template, opts) +} + // CreateReadWrite creates a layer that is writable for use as a container // file system. func (d *Driver) CreateReadWrite(id, parent string, opts *graphdriver.CreateOpts) error { @@ -793,6 +809,7 @@ func (d *Driver) get(id string, disableShifting bool, options graphdriver.MountO mountTarget = path.Join(id, "merged") } flags, data := mount.ParseOptions(mountData) + logrus.Debugf("overlay: mount_data=%s", mountData) if err := mountFunc("overlay", mountTarget, "overlay", uintptr(flags), data); err != nil { return "", fmt.Errorf("error creating overlay mount to %s: %v", mountTarget, err) } @@ -986,6 +1003,7 @@ func (d *Driver) UpdateLayerIDMap(id string, toContainer, toHost *idtools.IDMapp // Mount the new layer and handle ownership changes and possible copy_ups in it. options := graphdriver.MountOpts{ MountLabel: mountLabel, + Options: strings.Split(d.options.mountOptions, ","), } layerFs, err := d.get(id, true, options) if err != nil { diff --git a/vendor/github.com/containers/storage/drivers/template.go b/vendor/github.com/containers/storage/drivers/template.go new file mode 100644 index 000000000..dfcbffb83 --- /dev/null +++ b/vendor/github.com/containers/storage/drivers/template.go @@ -0,0 +1,45 @@ +package graphdriver + +import ( + "github.com/sirupsen/logrus" + + "github.com/containers/storage/pkg/idtools" +) + +// TemplateDriver is just barely enough of a driver that we can implement a +// naive version of CreateFromTemplate on top of it. +type TemplateDriver interface { + DiffDriver + CreateReadWrite(id, parent string, opts *CreateOpts) error + Create(id, parent string, opts *CreateOpts) error + Remove(id string) error +} + +// CreateFromTemplate creates a layer with the same contents and parent as +// another layer. Internally, it may even depend on that other layer +// continuing to exist, as if it were actually a child of the child layer. +func NaiveCreateFromTemplate(d TemplateDriver, id, template string, templateIDMappings *idtools.IDMappings, parent string, parentIDMappings *idtools.IDMappings, opts *CreateOpts, readWrite bool) error { + var err error + if readWrite { + err = d.CreateReadWrite(id, parent, opts) + } else { + err = d.Create(id, parent, opts) + } + if err != nil { + return err + } + diff, err := d.Diff(template, templateIDMappings, parent, parentIDMappings, opts.MountLabel) + if err != nil { + if err2 := d.Remove(id); err2 != nil { + logrus.Errorf("error removing layer %q: %v", id, err2) + } + return err + } + if _, err = d.ApplyDiff(id, templateIDMappings, parent, opts.MountLabel, diff); err != nil { + if err2 := d.Remove(id); err2 != nil { + logrus.Errorf("error removing layer %q: %v", id, err2) + } + return err + } + return nil +} diff --git a/vendor/github.com/containers/storage/drivers/vfs/driver.go b/vendor/github.com/containers/storage/drivers/vfs/driver.go index f7f3c75ba..5941ccc17 100644 --- a/vendor/github.com/containers/storage/drivers/vfs/driver.go +++ b/vendor/github.com/containers/storage/drivers/vfs/driver.go @@ -99,6 +99,14 @@ func (d *Driver) Cleanup() error { return nil } +// CreateFromTemplate creates a layer with the same contents and parent as another layer. +func (d *Driver) CreateFromTemplate(id, template string, templateIDMappings *idtools.IDMappings, parent string, parentIDMappings *idtools.IDMappings, opts *graphdriver.CreateOpts, readWrite bool) error { + if readWrite { + return d.CreateReadWrite(id, template, opts) + } + return d.Create(id, template, opts) +} + // CreateReadWrite creates a layer that is writable for use as a container // file system. func (d *Driver) CreateReadWrite(id, parent string, opts *graphdriver.CreateOpts) error { diff --git a/vendor/github.com/containers/storage/drivers/windows/windows.go b/vendor/github.com/containers/storage/drivers/windows/windows.go index c6d86a4ab..c7df1c1fe 100644 --- a/vendor/github.com/containers/storage/drivers/windows/windows.go +++ b/vendor/github.com/containers/storage/drivers/windows/windows.go @@ -185,6 +185,11 @@ func (d *Driver) Exists(id string) bool { return result } +// CreateFromTemplate creates a layer with the same contents and parent as another layer. +func (d *Driver) CreateFromTemplate(id, template string, templateIDMappings *idtools.IDMappings, parent string, parentIDMappings *idtools.IDMappings, opts *graphdriver.CreateOpts, readWrite bool) error { + return graphdriver.NaiveCreateFromTemplate(d, id, template, templateIDMappings, parent, parentIDMappings, opts, readWrite) +} + // CreateReadWrite creates a layer that is writable for use as a container // file system. func (d *Driver) CreateReadWrite(id, parent string, opts *graphdriver.CreateOpts) error { diff --git a/vendor/github.com/containers/storage/drivers/zfs/zfs.go b/vendor/github.com/containers/storage/drivers/zfs/zfs.go index c3ce6e869..eaa9e8bc5 100644 --- a/vendor/github.com/containers/storage/drivers/zfs/zfs.go +++ b/vendor/github.com/containers/storage/drivers/zfs/zfs.go @@ -1,4 +1,4 @@ -// +build linux freebsd solaris +// +build linux freebsd package zfs @@ -16,7 +16,7 @@ import ( "github.com/containers/storage/pkg/idtools" "github.com/containers/storage/pkg/mount" "github.com/containers/storage/pkg/parsers" - zfs "github.com/mistifyio/go-zfs" + "github.com/mistifyio/go-zfs" "github.com/opencontainers/selinux/go-selinux/label" "github.com/pkg/errors" "github.com/sirupsen/logrus" @@ -38,7 +38,7 @@ type Logger struct{} // Log wraps log message from ZFS driver with a prefix '[zfs]'. func (*Logger) Log(cmd []string) { - logrus.Debugf("[zfs] %s", strings.Join(cmd, " ")) + logrus.WithField("storage-driver", "zfs").Debugf("%s", strings.Join(cmd, " ")) } // Init returns a new ZFS driver. @@ -47,14 +47,16 @@ func (*Logger) Log(cmd []string) { func Init(base string, opt []string, uidMaps, gidMaps []idtools.IDMap) (graphdriver.Driver, error) { var err error + logger := logrus.WithField("storage-driver", "zfs") + if _, err := exec.LookPath("zfs"); err != nil { - logrus.Debugf("[zfs] zfs command is not available: %v", err) + logger.Debugf("zfs command is not available: %v", err) return nil, errors.Wrap(graphdriver.ErrPrerequisites, "the 'zfs' command is not available") } file, err := os.OpenFile("/dev/zfs", os.O_RDWR, 0600) if err != nil { - logrus.Debugf("[zfs] cannot open /dev/zfs: %v", err) + logger.Debugf("cannot open /dev/zfs: %v", err) return nil, errors.Wrapf(graphdriver.ErrPrerequisites, "could not open /dev/zfs: %v", err) } defer file.Close() @@ -109,9 +111,6 @@ func Init(base string, opt []string, uidMaps, gidMaps []idtools.IDMap) (graphdri return nil, fmt.Errorf("Failed to create '%s': %v", base, err) } - if err := mount.MakePrivate(base); err != nil { - return nil, err - } d := &Driver{ dataset: rootDataset, options: options, @@ -157,7 +156,7 @@ func lookupZfsDataset(rootdir string) (string, error) { } for _, m := range mounts { if err := unix.Stat(m.Mountpoint, &stat); err != nil { - logrus.Debugf("[zfs] failed to stat '%s' while scanning for zfs mount: %v", m.Mountpoint, err) + logrus.WithField("storage-driver", "zfs").Debugf("failed to stat '%s' while scanning for zfs mount: %v", m.Mountpoint, err) continue // may fail on fuse file systems } @@ -184,7 +183,7 @@ func (d *Driver) String() string { return "zfs" } -// Cleanup is used to implement graphdriver.ProtoDriver. There is no cleanup required for this driver. +// Cleanup is called on when program exits, it is a no-op for ZFS. func (d *Driver) Cleanup() error { return nil } @@ -260,6 +259,11 @@ func (d *Driver) mountPath(id string) string { return path.Join(d.options.mountPath, "graph", getMountpoint(id)) } +// CreateFromTemplate creates a layer with the same contents and parent as another layer. +func (d *Driver) CreateFromTemplate(id, template string, templateIDMappings *idtools.IDMappings, parent string, parentIDMappings *idtools.IDMappings, opts *graphdriver.CreateOpts, readWrite bool) error { + return d.Create(id, template, opts) +} + // CreateReadWrite creates a layer that is writable for use as a container // file system. func (d *Driver) CreateReadWrite(id, parent string, opts *graphdriver.CreateOpts) error { @@ -360,11 +364,25 @@ func (d *Driver) Remove(id string) error { } // Get returns the mountpoint for the given id after creating the target directories if necessary. -func (d *Driver) Get(id string, options graphdriver.MountOpts) (string, error) { +func (d *Driver) Get(id string, options graphdriver.MountOpts) (_ string, retErr error) { + mountpoint := d.mountPath(id) if count := d.ctr.Increment(mountpoint); count > 1 { return mountpoint, nil } + defer func() { + if retErr != nil { + if c := d.ctr.Decrement(mountpoint); c <= 0 { + if mntErr := unix.Unmount(mountpoint, 0); mntErr != nil { + logrus.WithField("storage-driver", "zfs").Errorf("Error unmounting %v: %v", mountpoint, mntErr) + } + if rmErr := unix.Rmdir(mountpoint); rmErr != nil && !os.IsNotExist(rmErr) { + logrus.WithField("storage-driver", "zfs").Debugf("Failed to remove %s: %v", id, rmErr) + } + + } + } + }() mountOptions := d.options.mountOptions if len(options.Options) > 0 { @@ -373,29 +391,24 @@ func (d *Driver) Get(id string, options graphdriver.MountOpts) (string, error) { filesystem := d.zfsPath(id) opts := label.FormatMountLabel(mountOptions, options.MountLabel) - logrus.Debugf(`[zfs] mount("%s", "%s", "%s")`, filesystem, mountpoint, opts) + logrus.WithField("storage-driver", "zfs").Debugf(`mount("%s", "%s", "%s")`, filesystem, mountpoint, opts) rootUID, rootGID, err := idtools.GetRootUIDGID(d.uidMaps, d.gidMaps) if err != nil { - d.ctr.Decrement(mountpoint) return "", err } // Create the target directories if they don't exist if err := idtools.MkdirAllAs(mountpoint, 0755, rootUID, rootGID); err != nil { - d.ctr.Decrement(mountpoint) return "", err } if err := mount.Mount(filesystem, mountpoint, "zfs", opts); err != nil { - d.ctr.Decrement(mountpoint) - return "", fmt.Errorf("error creating zfs mount of %s to %s: %v", filesystem, mountpoint, err) + return "", errors.Wrap(err, "error creating zfs mount") } // this could be our first mount after creation of the filesystem, and the root dir may still have root // permissions instead of the remapped root uid:gid (if user namespaces are enabled): if err := os.Chown(mountpoint, rootUID, rootGID); err != nil { - mount.Unmount(mountpoint) - d.ctr.Decrement(mountpoint) return "", fmt.Errorf("error modifying zfs mountpoint (%s) directory ownership: %v", mountpoint, err) } @@ -408,16 +421,18 @@ func (d *Driver) Put(id string) error { if count := d.ctr.Decrement(mountpoint); count > 0 { return nil } - mounted, err := graphdriver.Mounted(graphdriver.FsMagicZfs, mountpoint) - if err != nil || !mounted { - return err - } - logrus.Debugf(`[zfs] unmount("%s")`, mountpoint) + logger := logrus.WithField("storage-driver", "zfs") - if err := mount.Unmount(mountpoint); err != nil { - return fmt.Errorf("error unmounting to %s: %v", mountpoint, err) + logger.Debugf(`unmount("%s")`, mountpoint) + + if err := unix.Unmount(mountpoint, unix.MNT_DETACH); err != nil { + logger.Warnf("Failed to unmount %s mount %s: %v", id, mountpoint, err) + } + if err := unix.Rmdir(mountpoint); err != nil && !os.IsNotExist(err) { + logger.Debugf("Failed to remove %s mount point %s: %v", id, mountpoint, err) } + return nil } diff --git a/vendor/github.com/containers/storage/drivers/zfs/zfs_freebsd.go b/vendor/github.com/containers/storage/drivers/zfs/zfs_freebsd.go index 69c0448d3..bf6905159 100644 --- a/vendor/github.com/containers/storage/drivers/zfs/zfs_freebsd.go +++ b/vendor/github.com/containers/storage/drivers/zfs/zfs_freebsd.go @@ -18,7 +18,7 @@ func checkRootdirFs(rootdir string) error { // on FreeBSD buf.Fstypename contains ['z', 'f', 's', 0 ... ] if (buf.Fstypename[0] != 122) || (buf.Fstypename[1] != 102) || (buf.Fstypename[2] != 115) || (buf.Fstypename[3] != 0) { - logrus.Debugf("[zfs] no zfs dataset found for rootdir '%s'", rootdir) + logrus.WithField("storage-driver", "zfs").Debugf("no zfs dataset found for rootdir '%s'", rootdir) return errors.Wrapf(graphdriver.ErrPrerequisites, "no zfs dataset found for rootdir '%s'", rootdir) } diff --git a/vendor/github.com/containers/storage/drivers/zfs/zfs_linux.go b/vendor/github.com/containers/storage/drivers/zfs/zfs_linux.go index da298047d..fb1ef3a3d 100644 --- a/vendor/github.com/containers/storage/drivers/zfs/zfs_linux.go +++ b/vendor/github.com/containers/storage/drivers/zfs/zfs_linux.go @@ -1,23 +1,24 @@ package zfs import ( - "fmt" - "github.com/containers/storage/drivers" "github.com/pkg/errors" "github.com/sirupsen/logrus" - "golang.org/x/sys/unix" ) -func checkRootdirFs(rootdir string) error { - var buf unix.Statfs_t - if err := unix.Statfs(rootdir, &buf); err != nil { - return fmt.Errorf("Failed to access '%s': %s", rootdir, err) +func checkRootdirFs(rootDir string) error { + fsMagic, err := graphdriver.GetFSMagic(rootDir) + if err != nil { + return err + } + backingFS := "unknown" + if fsName, ok := graphdriver.FsNames[fsMagic]; ok { + backingFS = fsName } - if graphdriver.FsMagic(buf.Type) != graphdriver.FsMagicZfs { - logrus.Debugf("[zfs] no zfs dataset found for rootdir '%s'", rootdir) - return errors.Wrapf(graphdriver.ErrPrerequisites, "no zfs dataset found for rootdir '%s'", rootdir) + if fsMagic != graphdriver.FsMagicZfs { + logrus.WithField("root", rootDir).WithField("backingFS", backingFS).WithField("storage-driver", "zfs").Error("No zfs dataset found for root") + return errors.Wrapf(graphdriver.ErrPrerequisites, "no zfs dataset found for rootdir '%s'", rootDir) } return nil diff --git a/vendor/github.com/containers/storage/drivers/zfs/zfs_solaris.go b/vendor/github.com/containers/storage/drivers/zfs/zfs_solaris.go deleted file mode 100644 index 2383bf3bf..000000000 --- a/vendor/github.com/containers/storage/drivers/zfs/zfs_solaris.go +++ /dev/null @@ -1,59 +0,0 @@ -// +build solaris,cgo - -package zfs - -/* -#include <sys/statvfs.h> -#include <stdlib.h> - -static inline struct statvfs *getstatfs(char *s) { - struct statvfs *buf; - int err; - buf = (struct statvfs *)malloc(sizeof(struct statvfs)); - err = statvfs(s, buf); - return buf; -} -*/ -import "C" -import ( - "path/filepath" - "strings" - "unsafe" - - "github.com/containers/storage/drivers" - "github.com/pkg/errors" - "github.com/sirupsen/logrus" -) - -func checkRootdirFs(rootdir string) error { - - cs := C.CString(filepath.Dir(rootdir)) - defer C.free(unsafe.Pointer(cs)) - buf := C.getstatfs(cs) - defer C.free(unsafe.Pointer(buf)) - - // on Solaris buf.f_basetype contains ['z', 'f', 's', 0 ... ] - if (buf.f_basetype[0] != 122) || (buf.f_basetype[1] != 102) || (buf.f_basetype[2] != 115) || - (buf.f_basetype[3] != 0) { - logrus.Debugf("[zfs] no zfs dataset found for rootdir '%s'", rootdir) - return errors.Wrapf(graphdriver.ErrPrerequisites, "no zfs dataset found for rootdir '%s'", rootdir) - } - - return nil -} - -/* rootfs is introduced to comply with the OCI spec -which states that root filesystem must be mounted at <CID>/rootfs/ instead of <CID>/ -*/ -func getMountpoint(id string) string { - maxlen := 12 - - // we need to preserve filesystem suffix - suffix := strings.SplitN(id, "-", 2) - - if len(suffix) > 1 { - return filepath.Join(id[:maxlen]+"-"+suffix[1], "rootfs", "root") - } - - return filepath.Join(id[:maxlen], "rootfs", "root") -} diff --git a/vendor/github.com/containers/storage/drivers/zfs/zfs_unsupported.go b/vendor/github.com/containers/storage/drivers/zfs/zfs_unsupported.go index ce8daadaf..643b169bc 100644 --- a/vendor/github.com/containers/storage/drivers/zfs/zfs_unsupported.go +++ b/vendor/github.com/containers/storage/drivers/zfs/zfs_unsupported.go @@ -1,4 +1,4 @@ -// +build !linux,!freebsd,!solaris +// +build !linux,!freebsd package zfs diff --git a/vendor/github.com/containers/storage/images.go b/vendor/github.com/containers/storage/images.go index b10501b08..d99842534 100644 --- a/vendor/github.com/containers/storage/images.go +++ b/vendor/github.com/containers/storage/images.go @@ -42,7 +42,9 @@ type Image struct { // MappedTopLayers are the IDs of alternate versions of the top layer // which have the same contents and parent, and which differ from - // TopLayer only in which ID mappings they use. + // TopLayer only in which ID mappings they use. When the image is + // to be removed, they should be removed before the TopLayer, as the + // graph driver may depend on that. MappedTopLayers []string `json:"mapped-layers,omitempty"` // Metadata is data we keep for the convenience of the caller. It is not diff --git a/vendor/github.com/containers/storage/images_ffjson.go b/vendor/github.com/containers/storage/images_ffjson.go index 6b40ebd59..539acfe93 100644 --- a/vendor/github.com/containers/storage/images_ffjson.go +++ b/vendor/github.com/containers/storage/images_ffjson.go @@ -1,5 +1,5 @@ // Code generated by ffjson <https://github.com/pquerna/ffjson>. DO NOT EDIT. -// source: images.go +// source: ./images.go package storage diff --git a/vendor/github.com/containers/storage/layers.go b/vendor/github.com/containers/storage/layers.go index 299d2f818..cdc3cbba9 100644 --- a/vendor/github.com/containers/storage/layers.go +++ b/vendor/github.com/containers/storage/layers.go @@ -551,9 +551,20 @@ func (r *layerStore) Put(id string, parentLayer *Layer, names []string, mountLab } } parent := "" - var parentMappings *idtools.IDMappings if parentLayer != nil { parent = parentLayer.ID + } + var parentMappings, templateIDMappings, oldMappings *idtools.IDMappings + if moreOptions.TemplateLayer != "" { + templateLayer, ok := r.lookup(moreOptions.TemplateLayer) + if !ok { + return nil, -1, ErrLayerUnknown + } + templateIDMappings = idtools.NewIDMappingsFromMaps(templateLayer.UIDMap, templateLayer.GIDMap) + } else { + templateIDMappings = &idtools.IDMappings{} + } + if parentLayer != nil { parentMappings = idtools.NewIDMappingsFromMaps(parentLayer.UIDMap, parentLayer.GIDMap) } else { parentMappings = &idtools.IDMappings{} @@ -566,23 +577,34 @@ func (r *layerStore) Put(id string, parentLayer *Layer, names []string, mountLab MountLabel: mountLabel, StorageOpt: options, } - if writeable { - if err = r.driver.CreateReadWrite(id, parent, &opts); err != nil { + if moreOptions.TemplateLayer != "" { + if err = r.driver.CreateFromTemplate(id, moreOptions.TemplateLayer, templateIDMappings, parent, parentMappings, &opts, writeable); err != nil { if id != "" { - return nil, -1, errors.Wrapf(err, "error creating read-write layer with ID %q", id) + return nil, -1, errors.Wrapf(err, "error creating copy of template layer %q with ID %q", moreOptions.TemplateLayer, id) } - return nil, -1, errors.Wrapf(err, "error creating read-write layer") + return nil, -1, errors.Wrapf(err, "error creating copy of template layer %q", moreOptions.TemplateLayer) } + oldMappings = templateIDMappings } else { - if err = r.driver.Create(id, parent, &opts); err != nil { - if id != "" { - return nil, -1, errors.Wrapf(err, "error creating layer with ID %q", id) + if writeable { + if err = r.driver.CreateReadWrite(id, parent, &opts); err != nil { + if id != "" { + return nil, -1, errors.Wrapf(err, "error creating read-write layer with ID %q", id) + } + return nil, -1, errors.Wrapf(err, "error creating read-write layer") + } + } else { + if err = r.driver.Create(id, parent, &opts); err != nil { + if id != "" { + return nil, -1, errors.Wrapf(err, "error creating layer with ID %q", id) + } + return nil, -1, errors.Wrapf(err, "error creating layer") } - return nil, -1, errors.Wrapf(err, "error creating layer") } + oldMappings = parentMappings } - if !reflect.DeepEqual(parentMappings.UIDs(), idMappings.UIDs()) || !reflect.DeepEqual(parentMappings.GIDs(), idMappings.GIDs()) { - if err = r.driver.UpdateLayerIDMap(id, parentMappings, idMappings, mountLabel); err != nil { + if !reflect.DeepEqual(oldMappings.UIDs(), idMappings.UIDs()) || !reflect.DeepEqual(oldMappings.GIDs(), idMappings.GIDs()) { + if err = r.driver.UpdateLayerIDMap(id, oldMappings, idMappings, mountLabel); err != nil { // We don't have a record of this layer, but at least // try to clean it up underneath us. r.driver.Remove(id) diff --git a/vendor/github.com/containers/storage/layers_ffjson.go b/vendor/github.com/containers/storage/layers_ffjson.go index 125b5d8c9..09b5d0f33 100644 --- a/vendor/github.com/containers/storage/layers_ffjson.go +++ b/vendor/github.com/containers/storage/layers_ffjson.go @@ -1,5 +1,5 @@ // Code generated by ffjson <https://github.com/pquerna/ffjson>. DO NOT EDIT. -// source: layers.go +// source: ./layers.go package storage diff --git a/vendor/github.com/containers/storage/pkg/archive/example_changes.go b/vendor/github.com/containers/storage/pkg/archive/example_changes.go new file mode 100644 index 000000000..70f9c5564 --- /dev/null +++ b/vendor/github.com/containers/storage/pkg/archive/example_changes.go @@ -0,0 +1,97 @@ +// +build ignore + +// Simple tool to create an archive stream from an old and new directory +// +// By default it will stream the comparison of two temporary directories with junk files +package main + +import ( + "flag" + "fmt" + "io" + "io/ioutil" + "os" + "path" + + "github.com/containers/storage/pkg/archive" + "github.com/sirupsen/logrus" +) + +var ( + flDebug = flag.Bool("D", false, "debugging output") + flNewDir = flag.String("newdir", "", "") + flOldDir = flag.String("olddir", "", "") + log = logrus.New() +) + +func main() { + flag.Usage = func() { + fmt.Println("Produce a tar from comparing two directory paths. By default a demo tar is created of around 200 files (including hardlinks)") + fmt.Printf("%s [OPTIONS]\n", os.Args[0]) + flag.PrintDefaults() + } + flag.Parse() + log.Out = os.Stderr + if (len(os.Getenv("DEBUG")) > 0) || *flDebug { + logrus.SetLevel(logrus.DebugLevel) + } + var newDir, oldDir string + + if len(*flNewDir) == 0 { + var err error + newDir, err = ioutil.TempDir("", "storage-test-newDir") + if err != nil { + log.Fatal(err) + } + defer os.RemoveAll(newDir) + if _, err := prepareUntarSourceDirectory(100, newDir, true); err != nil { + log.Fatal(err) + } + } else { + newDir = *flNewDir + } + + if len(*flOldDir) == 0 { + oldDir, err := ioutil.TempDir("", "storage-test-oldDir") + if err != nil { + log.Fatal(err) + } + defer os.RemoveAll(oldDir) + } else { + oldDir = *flOldDir + } + + changes, err := archive.ChangesDirs(newDir, oldDir) + if err != nil { + log.Fatal(err) + } + + a, err := archive.ExportChanges(newDir, changes) + if err != nil { + log.Fatal(err) + } + defer a.Close() + + i, err := io.Copy(os.Stdout, a) + if err != nil && err != io.EOF { + log.Fatal(err) + } + fmt.Fprintf(os.Stderr, "wrote archive of %d bytes", i) +} + +func prepareUntarSourceDirectory(numberOfFiles int, targetPath string, makeLinks bool) (int, error) { + fileData := []byte("fooo") + for n := 0; n < numberOfFiles; n++ { + fileName := fmt.Sprintf("file-%d", n) + if err := ioutil.WriteFile(path.Join(targetPath, fileName), fileData, 0700); err != nil { + return 0, err + } + if makeLinks { + if err := os.Link(path.Join(targetPath, fileName), path.Join(targetPath, fileName+"-link")); err != nil { + return 0, err + } + } + } + totalSize := numberOfFiles * len(fileData) + return totalSize, nil +} diff --git a/vendor/github.com/containers/storage/store.go b/vendor/github.com/containers/storage/store.go index 5877c3b06..3fe305cc1 100644 --- a/vendor/github.com/containers/storage/store.go +++ b/vendor/github.com/containers/storage/store.go @@ -482,6 +482,10 @@ type LayerOptions struct { // inherit settings from its parent layer or, if it has no parent // layer, the Store object. IDMappingOptions + // TemplateLayer is the ID of a layer whose contents will be used to + // initialize this layer. If set, it should be a child of the layer + // which we want to use as the parent of the new layer. + TemplateLayer string } // ImageOptions is used for passing options to a Store's CreateImage() method. @@ -973,7 +977,7 @@ func (s *store) CreateImage(id string, names []string, layer, metadata string, o return ristore.Create(id, names, layer, metadata, creationDate, options.Digest) } -func (s *store) imageTopLayerForMapping(image *Image, ristore ROImageStore, readWrite bool, rlstore LayerStore, lstores []ROLayerStore, options IDMappingOptions) (*Layer, error) { +func (s *store) imageTopLayerForMapping(image *Image, ristore ROImageStore, createMappedLayer bool, rlstore LayerStore, lstores []ROLayerStore, options IDMappingOptions) (*Layer, error) { layerMatchesMappingOptions := func(layer *Layer, options IDMappingOptions) bool { // If the driver supports shifting and the layer has no mappings, we can use it. if s.graphDriver.SupportsShifting() && len(layer.UIDMap) == 0 && len(layer.GIDMap) == 0 { @@ -994,7 +998,6 @@ func (s *store) imageTopLayerForMapping(image *Image, ristore ROImageStore, read return reflect.DeepEqual(layer.UIDMap, options.UIDMap) && reflect.DeepEqual(layer.GIDMap, options.GIDMap) } var layer, parentLayer *Layer - var layerHomeStore ROLayerStore // Locate the image's top layer and its parent, if it has one. for _, store := range append([]ROLayerStore{rlstore}, lstores...) { if store != rlstore { @@ -1027,7 +1030,6 @@ func (s *store) imageTopLayerForMapping(image *Image, ristore ROImageStore, read if layer == nil { layer = cLayer parentLayer = cParentLayer - layerHomeStore = store } } } @@ -1037,27 +1039,25 @@ func (s *store) imageTopLayerForMapping(image *Image, ristore ROImageStore, read } // The top layer's mappings don't match the ones we want, but it's in a read-only // image store, so we can't create and add a mapped copy of the layer to the image. - if !readWrite { + // We'll have to do the mapping for the container itself, elsewhere. + if !createMappedLayer { return layer, nil } // The top layer's mappings don't match the ones we want, and it's in an image store // that lets us edit image metadata... if istore, ok := ristore.(*imageStore); ok { - // ... so extract the layer's contents, create a new copy of it with the - // desired mappings, and register it as an alternate top layer in the image. - noCompression := archive.Uncompressed - diffOptions := DiffOptions{ - Compression: &noCompression, - } - rc, err := layerHomeStore.Diff("", layer.ID, &diffOptions) - if err != nil { - return nil, errors.Wrapf(err, "error reading layer %q to create an ID-mapped version of it", layer.ID) - } - defer rc.Close() - + // ... so create a duplicate of the layer with the desired mappings, and + // register it as an alternate top layer in the image. var layerOptions LayerOptions if s.graphDriver.SupportsShifting() { - layerOptions = LayerOptions{IDMappingOptions: IDMappingOptions{HostUIDMapping: true, HostGIDMapping: true, UIDMap: nil, GIDMap: nil}} + layerOptions = LayerOptions{ + IDMappingOptions: IDMappingOptions{ + HostUIDMapping: true, + HostGIDMapping: true, + UIDMap: nil, + GIDMap: nil, + }, + } } else { layerOptions = LayerOptions{ IDMappingOptions: IDMappingOptions{ @@ -1068,9 +1068,10 @@ func (s *store) imageTopLayerForMapping(image *Image, ristore ROImageStore, read }, } } - mappedLayer, _, err := rlstore.Put("", parentLayer, nil, layer.MountLabel, nil, &layerOptions, false, nil, rc) + layerOptions.TemplateLayer = layer.ID + mappedLayer, _, err := rlstore.Put("", parentLayer, nil, layer.MountLabel, nil, &layerOptions, false, nil, nil) if err != nil { - return nil, errors.Wrapf(err, "error creating ID-mapped copy of layer %q", layer.ID) + return nil, errors.Wrapf(err, "error creating an ID-mapped copy of layer %q", layer.ID) } if err = istore.addMappedTopLayer(image.ID, mappedLayer.ID); err != nil { if err2 := rlstore.Delete(mappedLayer.ID); err2 != nil { @@ -1144,7 +1145,9 @@ func (s *store) CreateContainer(id string, names []string, image, layer, metadat } imageID = cimage.ID - ilayer, err := s.imageTopLayerForMapping(cimage, imageHomeStore, imageHomeStore == istore, rlstore, lstores, idMappingsOptions) + createMappedLayer := imageHomeStore == istore + + ilayer, err := s.imageTopLayerForMapping(cimage, imageHomeStore, createMappedLayer, rlstore, lstores, idMappingsOptions) if err != nil { return nil, err } @@ -1170,7 +1173,14 @@ func (s *store) CreateContainer(id string, names []string, image, layer, metadat } var layerOptions *LayerOptions if s.graphDriver.SupportsShifting() { - layerOptions = &LayerOptions{IDMappingOptions: IDMappingOptions{HostUIDMapping: true, HostGIDMapping: true, UIDMap: nil, GIDMap: nil}} + layerOptions = &LayerOptions{ + IDMappingOptions: IDMappingOptions{ + HostUIDMapping: true, + HostGIDMapping: true, + UIDMap: nil, + GIDMap: nil, + }, + } } else { layerOptions = &LayerOptions{ IDMappingOptions: IDMappingOptions{ @@ -2091,10 +2101,10 @@ func (s *store) DeleteImage(id string, commit bool) (layers []string, err error) break } lastRemoved = layer - layersToRemove = append(layersToRemove, lastRemoved) if layer == image.TopLayer { layersToRemove = append(layersToRemove, image.MappedTopLayers...) } + layersToRemove = append(layersToRemove, lastRemoved) layer = parent } } else { @@ -3064,9 +3074,6 @@ type OptionsConfig struct { // Size Size string `toml:"size"` - // OverrideKernelCheck - OverrideKernelCheck string `toml:"override_kernel_check"` - // RemapUIDs is a list of default UID mappings to use for layers. RemapUIDs string `toml:"remap-uids"` // RemapGIDs is a list of default GID mappings to use for layers. @@ -3191,9 +3198,6 @@ func ReloadConfigurationFile(configFile string, storeOptions *StoreOptions) { if config.Storage.Options.MountOpt != "" { storeOptions.GraphDriverOptions = append(storeOptions.GraphDriverOptions, fmt.Sprintf("%s.mountopt=%s", config.Storage.Driver, config.Storage.Options.MountOpt)) } - if config.Storage.Options.OverrideKernelCheck != "" { - storeOptions.GraphDriverOptions = append(storeOptions.GraphDriverOptions, fmt.Sprintf("%s.override_kernel_check=%s", config.Storage.Driver, config.Storage.Options.OverrideKernelCheck)) - } if config.Storage.Options.RemapUser != "" && config.Storage.Options.RemapGroup == "" { config.Storage.Options.RemapGroup = config.Storage.Options.RemapUser } diff --git a/vendor/github.com/containers/storage/vendor.conf b/vendor/github.com/containers/storage/vendor.conf index 936321b12..04af9010b 100644 --- a/vendor/github.com/containers/storage/vendor.conf +++ b/vendor/github.com/containers/storage/vendor.conf @@ -8,7 +8,7 @@ github.com/mattn/go-shellwords 753a2322a99f87c0eff284980e77f53041555bc6 github.com/mistifyio/go-zfs c0224de804d438efd11ea6e52ada8014537d6062 github.com/opencontainers/go-digest master github.com/opencontainers/runc 6c22e77604689db8725fa866f0f2ec0b3e8c3a07 -github.com/opencontainers/selinux 36a9bc45a08c85f2c52bd9eb32e20267876773bd +github.com/opencontainers/selinux v1.1 github.com/ostreedev/ostree-go master github.com/pborman/uuid 1b00554d822231195d1babd97ff4a781231955c9 github.com/pkg/errors master diff --git a/vendor/github.com/opencontainers/selinux/go-selinux/label/label.go b/vendor/github.com/opencontainers/selinux/go-selinux/label/label.go index bb27ac936..4e9a8c54f 100644 --- a/vendor/github.com/opencontainers/selinux/go-selinux/label/label.go +++ b/vendor/github.com/opencontainers/selinux/go-selinux/label/label.go @@ -75,8 +75,8 @@ func ReleaseLabel(label string) error { // DupSecOpt takes a process label and returns security options that // can be used to set duplicate labels on future container processes -func DupSecOpt(src string) []string { - return nil +func DupSecOpt(src string) ([]string, error) { + return nil, nil } // DisableSecOpt returns a security opt that can disable labeling diff --git a/vendor/github.com/opencontainers/selinux/go-selinux/label/label_selinux.go b/vendor/github.com/opencontainers/selinux/go-selinux/label/label_selinux.go index de214b2d5..d4e26909d 100644 --- a/vendor/github.com/opencontainers/selinux/go-selinux/label/label_selinux.go +++ b/vendor/github.com/opencontainers/selinux/go-selinux/label/label_selinux.go @@ -4,6 +4,8 @@ package label import ( "fmt" + "os" + "os/user" "strings" "github.com/opencontainers/selinux/go-selinux" @@ -35,8 +37,15 @@ func InitLabels(options []string) (plabel string, mlabel string, Err error) { ReleaseLabel(mountLabel) } }() - pcon := selinux.NewContext(processLabel) - mcon := selinux.NewContext(mountLabel) + pcon, err := selinux.NewContext(processLabel) + if err != nil { + return "", "", err + } + + mcon, err := selinux.NewContext(mountLabel) + if err != nil { + return "", "", err + } for _, opt := range options { if opt == "disable" { return "", mountLabel, nil @@ -146,13 +155,56 @@ func Relabel(path string, fileLabel string, shared bool) error { return nil } - exclude_paths := map[string]bool{"/": true, "/usr": true, "/etc": true, "/tmp": true, "/home": true, "/run": true, "/var": true, "/root": true} + exclude_paths := map[string]bool{ + "/": true, + "/bin": true, + "/boot": true, + "/dev": true, + "/etc": true, + "/etc/passwd": true, + "/etc/pki": true, + "/etc/shadow": true, + "/home": true, + "/lib": true, + "/lib64": true, + "/media": true, + "/opt": true, + "/proc": true, + "/root": true, + "/run": true, + "/sbin": true, + "/srv": true, + "/sys": true, + "/tmp": true, + "/usr": true, + "/var": true, + "/var/lib": true, + "/var/log": true, + } + + if home := os.Getenv("HOME"); home != "" { + exclude_paths[home] = true + } + + if sudoUser := os.Getenv("SUDO_USER"); sudoUser != "" { + if usr, err := user.Lookup(sudoUser); err == nil { + exclude_paths[usr.HomeDir] = true + } + } + + if path != "/" { + path = strings.TrimSuffix(path, "/") + } if exclude_paths[path] { return fmt.Errorf("SELinux relabeling of %s is not allowed", path) } if shared { - c := selinux.NewContext(fileLabel) + c, err := selinux.NewContext(fileLabel) + if err != nil { + return err + } + c["level"] = "s0" fileLabel = c.Get() } @@ -195,7 +247,7 @@ func ReleaseLabel(label string) error { // DupSecOpt takes a process label and returns security options that // can be used to set duplicate labels on future container processes -func DupSecOpt(src string) []string { +func DupSecOpt(src string) ([]string, error) { return selinux.DupSecOpt(src) } diff --git a/vendor/github.com/opencontainers/selinux/go-selinux/selinux_linux.go b/vendor/github.com/opencontainers/selinux/go-selinux/selinux_linux.go index 7832f7497..5adafd317 100644 --- a/vendor/github.com/opencontainers/selinux/go-selinux/selinux_linux.go +++ b/vendor/github.com/opencontainers/selinux/go-selinux/selinux_linux.go @@ -52,6 +52,8 @@ var ( ErrMCSAlreadyExists = errors.New("MCS label already exists") // ErrEmptyPath is returned when an empty path has been specified. ErrEmptyPath = errors.New("empty path") + // InvalidLabel is returned when an invalid label is specified. + InvalidLabel = errors.New("Invalid Label") assignRegex = regexp.MustCompile(`^([^=]+)=(.*)$`) roFileLabel string @@ -405,11 +407,14 @@ func (c Context) Get() string { } // NewContext creates a new Context struct from the specified label -func NewContext(label string) Context { +func NewContext(label string) (Context, error) { c := make(Context) if len(label) != 0 { con := strings.SplitN(label, ":", 4) + if len(con) < 3 { + return c, InvalidLabel + } c["user"] = con[0] c["role"] = con[1] c["type"] = con[2] @@ -417,7 +422,7 @@ func NewContext(label string) Context { c["level"] = con[3] } } - return c + return c, nil } // ClearLabels clears all reserved labels @@ -630,12 +635,12 @@ func ContainerLabels() (processLabel string, fileLabel string) { roFileLabel = fileLabel } exit: - scon := NewContext(processLabel) + scon, _ := NewContext(processLabel) if scon["level"] != "" { mcs := uniqMcs(1024) scon["level"] = mcs processLabel = scon.Get() - scon = NewContext(fileLabel) + scon, _ = NewContext(fileLabel) scon["level"] = mcs fileLabel = scon.Get() } @@ -661,8 +666,14 @@ func CopyLevel(src, dest string) (string, error) { if err := SecurityCheckContext(dest); err != nil { return "", err } - scon := NewContext(src) - tcon := NewContext(dest) + scon, err := NewContext(src) + if err != nil { + return "", err + } + tcon, err := NewContext(dest) + if err != nil { + return "", err + } mcsDelete(tcon["level"]) mcsAdd(scon["level"]) tcon["level"] = scon["level"] @@ -714,15 +725,18 @@ func Chcon(fpath string, label string, recurse bool) error { // DupSecOpt takes an SELinux process label and returns security options that // can be used to set the SELinux Type and Level for future container processes. -func DupSecOpt(src string) []string { +func DupSecOpt(src string) ([]string, error) { if src == "" { - return nil + return nil, nil + } + con, err := NewContext(src) + if err != nil { + return nil, err } - con := NewContext(src) if con["user"] == "" || con["role"] == "" || con["type"] == "" { - return nil + return nil, nil } dup := []string{"user:" + con["user"], "role:" + con["role"], @@ -733,7 +747,7 @@ func DupSecOpt(src string) []string { dup = append(dup, "level:"+con["level"]) } - return dup + return dup, nil } // DisableSecOpt returns a security opt that can be used to disable SELinux diff --git a/vendor/github.com/opencontainers/selinux/go-selinux/selinux_stub.go b/vendor/github.com/opencontainers/selinux/go-selinux/selinux_stub.go index 99efa155a..9497acbd0 100644 --- a/vendor/github.com/opencontainers/selinux/go-selinux/selinux_stub.go +++ b/vendor/github.com/opencontainers/selinux/go-selinux/selinux_stub.go @@ -115,9 +115,9 @@ func (c Context) Get() string { } // NewContext creates a new Context struct from the specified label -func NewContext(label string) Context { +func NewContext(label string) (Context, error) { c := make(Context) - return c + return c, nil } // ClearLabels clears all reserved MLS/MCS levels @@ -195,8 +195,8 @@ func Chcon(fpath string, label string, recurse bool) error { // DupSecOpt takes an SELinux process label and returns security options that // can be used to set the SELinux Type and Level for future container processes. -func DupSecOpt(src string) []string { - return nil +func DupSecOpt(src string) ([]string, error) { + return nil, nil } // DisableSecOpt returns a security opt that can be used to disable SELinux diff --git a/vendor/github.com/urfave/cli/help.go b/vendor/github.com/urfave/cli/help.go index 65874fa2f..a0e949f72 100644 --- a/vendor/github.com/urfave/cli/help.go +++ b/vendor/github.com/urfave/cli/help.go @@ -82,7 +82,7 @@ OPTIONS: var helpCommand = Command{ Name: "help", Aliases: []string{"h"}, - Usage: "Shows a list of commands or help for one command", + Usage: "Show a list of commands or help for one command", ArgsUsage: "[command]", Action: func(c *Context) error { args := c.Args() diff --git a/vendor/github.com/varlink/go/.gitignore b/vendor/github.com/varlink/go/.gitignore index 69e30cff1..a43b0e2d3 100644 --- a/vendor/github.com/varlink/go/.gitignore +++ b/vendor/github.com/varlink/go/.gitignore @@ -1 +1,2 @@ /cmd/varlink-go-certification/orgvarlinkcertification/orgvarlinkcertification.go +/.idea diff --git a/vendor/github.com/varlink/go/.travis.yml b/vendor/github.com/varlink/go/.travis.yml index c090a924f..fa9963500 100644 --- a/vendor/github.com/varlink/go/.travis.yml +++ b/vendor/github.com/varlink/go/.travis.yml @@ -1,11 +1,13 @@ language: go sudo: false go: -- '1.9' -- 1.10.x +- '1.10' +- 1.11.x install: - go get golang.org/x/tools/cmd/cover - go get github.com/mattn/goveralls +- go get github.com/TylerBrock/colorjson +- go get github.com/fatih/color script: - go generate ./... - '"$HOME/gopath/bin/goveralls" -v -show -service=travis-ci -repotoken "$COVERALLS_TOKEN"' diff --git a/vendor/github.com/varlink/go/cmd/varlink/main.go b/vendor/github.com/varlink/go/cmd/varlink/main.go new file mode 100644 index 000000000..1de4e1a45 --- /dev/null +++ b/vendor/github.com/varlink/go/cmd/varlink/main.go @@ -0,0 +1,295 @@ +package main + +import ( + "encoding/json" + "flag" + "fmt" + "github.com/TylerBrock/colorjson" + "github.com/fatih/color" + "github.com/varlink/go/varlink" + "os" + "strings" +) + +var bold = color.New(color.Bold) +var errorBoldRed = bold.Sprint(color.New(color.FgRed).Sprint("Error:")) +var bridge string + +func ErrPrintf(format string, a ...interface{}) { + fmt.Fprintf(os.Stderr, "%s ", errorBoldRed) + fmt.Fprintf(os.Stderr, format, a...) +} + +func print_usage(set *flag.FlagSet, arg_help string) { + if set == nil { + fmt.Fprintf(os.Stderr, "Usage: %s [GLOBAL OPTIONS] COMMAND ...\n", os.Args[0]) + } else { + fmt.Fprintf(os.Stderr, "Usage: %s [GLOBAL OPTIONS] %s [OPTIONS] %s\n", os.Args[0], set.Name(), arg_help) + } + + fmt.Fprintln(os.Stderr, "\nGlobal Options:") + flag.PrintDefaults() + + if set == nil { + fmt.Fprintln(os.Stderr, "\nCommands:") + fmt.Fprintln(os.Stderr, " info\tPrint information about a service") + fmt.Fprintln(os.Stderr, " help\tPrint interface description or service information") + fmt.Fprintln(os.Stderr, " call\tCall a method") + } else { + fmt.Fprintln(os.Stderr, "\nOptions:") + set.PrintDefaults() + } + os.Exit(1) +} + +func varlink_call(args []string) { + var err error + var oneway bool + + callFlags := flag.NewFlagSet("help", flag.ExitOnError) + callFlags.BoolVar(&oneway, "-oneway", false, "Use bridge for connection") + var help bool + callFlags.BoolVar(&help, "help", false, "Prints help information") + var usage = func() { print_usage(callFlags, "<[ADDRESS/]INTERFACE.METHOD> [ARGUMENTS]") } + callFlags.Usage = usage + + _ = callFlags.Parse(args) + + if help { + usage() + } + + var con *varlink.Connection + var address string + var methodName string + + if len(bridge) != 0 { + con, err = varlink.NewBridge(bridge) + + if err != nil { + ErrPrintf("Cannot connect with bridge '%s': %v\n", bridge, err) + os.Exit(2) + } + address = "bridge:" + bridge + methodName = callFlags.Arg(0) + } else { + uri := callFlags.Arg(0) + if uri == "" { + usage() + } + + li := strings.LastIndex(uri, "/") + + if li == -1 { + ErrPrintf("Invalid address '%s'\n", uri) + os.Exit(2) + } + + address = uri[:li] + methodName = uri[li+1:] + + con, err = varlink.NewConnection(address) + + if err != nil { + ErrPrintf("Cannot connect to '%s': %v\n", address, err) + os.Exit(2) + } + } + var parameters string + var params json.RawMessage + + parameters = callFlags.Arg(1) + if parameters == "" { + params = nil + } else { + json.Unmarshal([]byte(parameters), ¶ms) + } + + var flags uint64 + flags = 0 + if oneway { + flags |= varlink.Oneway + } + recv, err := con.Send(methodName, params, flags) + + var retval map[string]interface{} + + // FIXME: Use cont + _, err = recv(&retval) + + f := colorjson.NewFormatter() + f.Indent = 2 + f.KeyColor = color.New(color.FgCyan) + f.StringColor = color.New(color.FgMagenta) + f.NumberColor = color.New(color.FgMagenta) + f.BoolColor = color.New(color.FgMagenta) + f.NullColor = color.New(color.FgMagenta) + + if err != nil { + ErrPrintf("Error calling '%s': %v\n", methodName, err) + switch e := err.(type) { + case *varlink.Error: + println(e.Name) + errorRawParameters := e.Parameters.(*json.RawMessage) + + if errorRawParameters == nil { + break + } + var param map[string]interface{} + _ = json.Unmarshal(*errorRawParameters, ¶m) + c, _ := f.Marshal(param) + ErrPrintf("%v\n", string(c)) + } + os.Exit(2) + } + c, _ := f.Marshal(retval) + fmt.Println(string(c)) +} + +func varlink_help(args []string) { + var err error + + helpFlags := flag.NewFlagSet("help", flag.ExitOnError) + var help bool + helpFlags.BoolVar(&help, "help", false, "Prints help information") + var usage = func() { print_usage(helpFlags, "<[ADDRESS/]INTERFACE>") } + helpFlags.Usage = usage + + _ = helpFlags.Parse(args) + + if help { + usage() + } + + var con *varlink.Connection + var address string + var interfaceName string + + if len(bridge) != 0 { + con, err = varlink.NewBridge(bridge) + + if err != nil { + ErrPrintf("Cannot connect with bridge '%s': %v\n", bridge, err) + os.Exit(2) + } + address = "bridge:" + bridge + interfaceName = helpFlags.Arg(0) + } else { + uri := helpFlags.Arg(0) + if uri == "" && bridge == "" { + ErrPrintf("No ADDRESS or activation or bridge\n\n") + usage() + } + + li := strings.LastIndex(uri, "/") + + if li == -1 { + ErrPrintf("Invalid address '%s'\n", uri) + os.Exit(2) + } + + address = uri[:li] + + con, err = varlink.NewConnection(address) + + if err != nil { + ErrPrintf("Cannot connect to '%s': %v\n", address, err) + os.Exit(2) + } + + interfaceName = uri[li+1:] + } + description, err := con.GetInterfaceDescription(interfaceName) + + if err != nil { + ErrPrintf("Cannot get interface description for '%s': %v\n", interfaceName, err) + os.Exit(2) + } + + fmt.Println(description) +} + +func varlink_info(args []string) { + var err error + infoFlags := flag.NewFlagSet("info", flag.ExitOnError) + var help bool + infoFlags.BoolVar(&help, "help", false, "Prints help information") + var usage = func() { print_usage(infoFlags, "[ADDRESS]") } + infoFlags.Usage = usage + + _ = infoFlags.Parse(args) + + if help { + usage() + } + + var con *varlink.Connection + var address string + + if len(bridge) != 0 { + con, err = varlink.NewBridge(bridge) + + if err != nil { + ErrPrintf("Cannot connect with bridge '%s': %v\n", bridge, err) + os.Exit(2) + } + address = "bridge:" + bridge + } else { + address = infoFlags.Arg(0) + + if address == "" && bridge == "" { + ErrPrintf("No ADDRESS or activation or bridge\n\n") + usage() + } + + con, err = varlink.NewConnection(address) + + if err != nil { + ErrPrintf("Cannot connect to '%s': %v\n", address, err) + os.Exit(2) + } + } + + var vendor, product, version, url string + var interfaces []string + + err = con.GetInfo(&vendor, &product, &version, &url, &interfaces) + + if err != nil { + ErrPrintf("Cannot get info for '%s': %v\n", address, err) + os.Exit(2) + } + + fmt.Printf("%s %s\n", bold.Sprint("Vendor:"), vendor) + fmt.Printf("%s %s\n", bold.Sprint("Product:"), product) + fmt.Printf("%s %s\n", bold.Sprint("Version:"), version) + fmt.Printf("%s %s\n", bold.Sprint("URL:"), url) + fmt.Printf("%s\n %s\n\n", bold.Sprint("Interfaces:"), strings.Join(interfaces[:], "\n ")) +} + +func main() { + var debug bool + var colorMode string + + flag.CommandLine.Usage = func() { print_usage(nil, "") } + flag.BoolVar(&debug, "debug", false, "Enable debug output") + flag.StringVar(&bridge, "bridge", "", "Use bridge for connection") + flag.StringVar(&colorMode, "color", "auto", "colorize output [default: auto] [possible values: on, off, auto]") + + flag.Parse() + + if colorMode != "on" && (os.Getenv("TERM") == "" || colorMode == "off") { + color.NoColor = true // disables colorized output + } + + switch flag.Arg(0) { + case "info": + varlink_info(flag.Args()[1:]) + case "help": + varlink_help(flag.Args()[1:]) + case "call": + varlink_call(flag.Args()[1:]) + default: + print_usage(nil, "") + } +} diff --git a/vendor/github.com/varlink/go/varlink/bridge.go b/vendor/github.com/varlink/go/varlink/bridge.go new file mode 100644 index 000000000..b20c0925a --- /dev/null +++ b/vendor/github.com/varlink/go/varlink/bridge.go @@ -0,0 +1,59 @@ +// +build !windows + +package varlink + +import ( + "bufio" + "io" + "log" + "net" + "os/exec" +) + +type PipeCon struct { + net.Conn + reader *io.ReadCloser + writer *io.WriteCloser +} + +func (p PipeCon) Close() error { + err1 := (*p.reader).Close() + err2 := (*p.writer).Close() + if err1 != nil { + return err1 + } + if err2 != nil { + return err2 + } + return nil +} + +// NewConnection returns a new connection to the given address. +func NewBridge(bridge string) (*Connection, error) { + //var err error + + c := Connection{} + cmd := exec.Command("sh", "-c", bridge) + r, err := cmd.StdoutPipe() + if err != nil { + return nil, err + } + w, err := cmd.StdinPipe() + if err != nil { + return nil, err + } + c.conn = PipeCon{nil, &r, &w} + c.address = "" + c.reader = bufio.NewReader(r) + c.writer = bufio.NewWriter(w) + + go func() { + err := cmd.Run() + if err != nil { + log.Fatal(err) + } + }() + + return &c, nil +} + diff --git a/vendor/github.com/varlink/go/varlink/bridge_windows.go b/vendor/github.com/varlink/go/varlink/bridge_windows.go new file mode 100644 index 000000000..692367a1a --- /dev/null +++ b/vendor/github.com/varlink/go/varlink/bridge_windows.go @@ -0,0 +1,57 @@ +package varlink + +import ( + "bufio" + "io" + "log" + "net" + "os/exec" +) + +type PipeCon struct { + net.Conn + reader *io.ReadCloser + writer *io.WriteCloser +} + +func (p PipeCon) Close() error { + err1 := (*p.reader).Close() + err2 := (*p.writer).Close() + if err1 != nil { + return err1 + } + if err2 != nil { + return err2 + } + return nil +} + +// NewConnection returns a new connection to the given address. +func NewBridge(bridge string) (*Connection, error) { + //var err error + + c := Connection{} + cmd := exec.Command("cmd", "/C", bridge) + r, err := cmd.StdoutPipe() + if err != nil { + return nil, err + } + w, err := cmd.StdinPipe() + if err != nil { + return nil, err + } + c.conn = PipeCon{nil, &r, &w} + c.address = "" + c.reader = bufio.NewReader(r) + c.writer = bufio.NewWriter(w) + + go func() { + err := cmd.Run() + if err != nil { + log.Fatal(err) + } + }() + + return &c, nil +} + diff --git a/vendor/github.com/varlink/go/varlink/connection.go b/vendor/github.com/varlink/go/varlink/connection.go index 43bec6393..ac9542408 100644 --- a/vendor/github.com/varlink/go/varlink/connection.go +++ b/vendor/github.com/varlink/go/varlink/connection.go @@ -3,6 +3,7 @@ package varlink import ( "bufio" "encoding/json" + "fmt" "net" "strings" ) @@ -18,6 +19,7 @@ const ( // Error is a varlink error returned from a method call. type Error struct { + error Name string Parameters interface{} } @@ -189,6 +191,11 @@ func NewConnection(address string) (*Connection, error) { var err error words := strings.SplitN(address, ":", 2) + + if len(words) != 2 { + return nil, fmt.Errorf("Protocol missing") + } + protocol := words[0] addr := words[1] diff --git a/vendor/github.com/varlink/go/varlink/service.go b/vendor/github.com/varlink/go/varlink/service.go index 551ba4e53..cb461f917 100644 --- a/vendor/github.com/varlink/go/varlink/service.go +++ b/vendor/github.com/varlink/go/varlink/service.go @@ -6,10 +6,8 @@ import ( "fmt" "net" "os" - "strconv" "strings" "sync" - "syscall" "time" ) @@ -110,58 +108,6 @@ func (s *Service) handleMessage(writer *bufio.Writer, request []byte) error { return iface.VarlinkDispatch(c, methodname) } -func activationListener() net.Listener { - pid, err := strconv.Atoi(os.Getenv("LISTEN_PID")) - if err != nil || pid != os.Getpid() { - return nil - } - - nfds, err := strconv.Atoi(os.Getenv("LISTEN_FDS")) - if err != nil || nfds < 1 { - return nil - } - - fd := -1 - - // If more than one file descriptor is passed, find the - // "varlink" tag. The first file descriptor is always 3. - if nfds > 1 { - fdnames, set := os.LookupEnv("LISTEN_FDNAMES") - if !set { - return nil - } - - names := strings.Split(fdnames, ":") - if len(names) != nfds { - return nil - } - - for i, name := range names { - if name == "varlink" { - fd = 3 + i - break - } - } - - if fd < 0 { - return nil - } - - } else { - fd = 3 - } - - syscall.CloseOnExec(fd) - - file := os.NewFile(uintptr(fd), "varlink") - listener, err := net.FileListener(file) - if err != nil { - return nil - } - - return listener -} - // Shutdown shuts down the listener of a running service. func (s *Service) Shutdown() { s.running = false @@ -253,18 +199,17 @@ func getListener(protocol string, address string) (net.Listener, error) { } func (s *Service) refreshTimeout(timeout time.Duration) error { - switch s.protocol { - case "unix": - if err := s.listener.(*net.UnixListener).SetDeadline(time.Now().Add(timeout)); err != nil { + switch l := s.listener.(type) { + case *net.UnixListener: + if err:= l.SetDeadline(time.Now().Add(timeout)); err != nil { return err } - - case "tcp": - if err := s.listener.(*net.TCPListener).SetDeadline(time.Now().Add(timeout)); err != nil { + case *net.TCPListener: + if err:= l.SetDeadline(time.Now().Add(timeout)); err != nil { return err } - } + } return nil } diff --git a/vendor/github.com/varlink/go/varlink/socketactivation.go b/vendor/github.com/varlink/go/varlink/socketactivation.go new file mode 100644 index 000000000..a64c0dc8e --- /dev/null +++ b/vendor/github.com/varlink/go/varlink/socketactivation.go @@ -0,0 +1,63 @@ +// +build !windows + +package varlink + +import ( + "net" + "os" + "strconv" + "strings" + "syscall" +) + +func activationListener() net.Listener { + pid, err := strconv.Atoi(os.Getenv("LISTEN_PID")) + if err != nil || pid != os.Getpid() { + return nil + } + + nfds, err := strconv.Atoi(os.Getenv("LISTEN_FDS")) + if err != nil || nfds < 1 { + return nil + } + + fd := -1 + + // If more than one file descriptor is passed, find the + // "varlink" tag. The first file descriptor is always 3. + if nfds > 1 { + fdnames, set := os.LookupEnv("LISTEN_FDNAMES") + if !set { + return nil + } + + names := strings.Split(fdnames, ":") + if len(names) != nfds { + return nil + } + + for i, name := range names { + if name == "varlink" { + fd = 3 + i + break + } + } + + if fd < 0 { + return nil + } + + } else { + fd = 3 + } + + syscall.CloseOnExec(fd) + + file := os.NewFile(uintptr(fd), "varlink") + listener, err := net.FileListener(file) + if err != nil { + return nil + } + + return listener +} diff --git a/vendor/github.com/varlink/go/varlink/socketactivation_windows.go b/vendor/github.com/varlink/go/varlink/socketactivation_windows.go new file mode 100644 index 000000000..fb0894531 --- /dev/null +++ b/vendor/github.com/varlink/go/varlink/socketactivation_windows.go @@ -0,0 +1,7 @@ +package varlink + +import "net" + +func activationListener() net.Listener { + return nil +} diff --git a/version/version.go b/version/version.go index 45dc93d91..24daf707c 100644 --- a/version/version.go +++ b/version/version.go @@ -4,4 +4,9 @@ package version // NOTE: remember to bump the version at the top // of the top-level README.md file when this is // bumped. -const Version = "0.12.2-dev" +const Version = "1.0.1-dev" + +// RemoteAPIVersion is the version for the remote +// client API. It is used to determine compatibility +// between a remote podman client and its backend +const RemoteAPIVersion = 1 |