diff options
164 files changed, 2605 insertions, 737 deletions
diff --git a/.cirrus.yml b/.cirrus.yml index 199c2a533..3192d15ae 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-9afa57a9" + PRIOR_FEDORA_CACHE_IMAGE_NAME: "fedora-28-libpod-9afa57a9" + UBUNTU_CACHE_IMAGE_NAME: "ubuntu-18-libpod-9afa57a9" + RHEL_CACHE_IMAGE_NAME: "rhel-8-notready" + PRIOR_RHEL_CACHE_IMAGE_NAME: "rhel-7-notready" + CENTOS_CACHE_IMAGE_NAME: "centos-7-notready" + + #### #### Variables for composing new cache-images (used in PR testing) from #### base-images (pre-existing in GCE) #### @@ -38,6 +48,8 @@ env: PACKER_BUILDS: "ubuntu-18,fedora-29,fedora-28" # TODO: fah-29,rhel-7,centos-7 # Version of packer to use PACKER_VER: "1.3.1" + # 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" @@ -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 @@ -139,14 +151,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}" # 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: "${PRIOR_RHEL_CACHE_IMAGE_NAME} + # image_name: "${CENTOS_CACHE_IMAGE_NAME}" timeout_in: 120m @@ -176,12 +188,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}" # 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: "${PRIOR_RHEL_CACHE_IMAGE_NAME} + # image_name: "${CENTOS_CACHE_IMAGE_NAME}" timeout_in: 60m @@ -214,7 +227,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) @@ -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;"> @@ -1373,6 +1405,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 +1494,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) @@ -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 @@ -6,7 +6,7 @@ Libpod provides a library for applications looking to use the Container Pod conc popularized by Kubernetes. libpod also contains the `podman` tool, for managing Pods, Containers, and Container Images. -* [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/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/images.go b/cmd/podman/images.go index 8b8ce78bd..031f06618 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 } @@ -195,7 +195,7 @@ func imagesCmd(c *cli.Context) error { 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/info.go b/cmd/podman/info.go index 4b80f94db..c33ede548 100644 --- a/cmd/podman/info.go +++ b/cmd/podman/info.go @@ -1,11 +1,11 @@ package main import ( - "github.com/containers/libpod/libpod/adapter" "runtime" "github.com/containers/libpod/cmd/podman/formats" "github.com/containers/libpod/libpod" + "github.com/containers/libpod/libpod/adapter" "github.com/pkg/errors" "github.com/urfave/cli" ) @@ -39,20 +39,20 @@ func infoCmd(c *cli.Context) error { } info := 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") } // 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}) } diff --git a/cmd/podman/inspect.go b/cmd/podman/inspect.go index 6ffcde55f..3ef740463 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" @@ -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/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/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..fbf860eb2 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" @@ -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..7c9b5897b 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 = ` @@ -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..79300f9bc 100644 --- a/cmd/podman/varlink/io.podman.varlink +++ b/cmd/podman/varlink/io.podman.varlink @@ -61,7 +61,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, @@ -1035,6 +1035,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/completions/bash/podman b/completions/bash/podman index 6333dfdf2..08891563c 100644 --- a/completions/bash/podman +++ b/completions/bash/podman @@ -1514,6 +1514,8 @@ _podman_umount() { -h --force -f + --latest + -l " local options_with_args=" " @@ -1531,8 +1533,12 @@ _podman_umount() { _podman_mount() { local boolean_options=" + --all + -a --help -h + -l + --latest --notruncate " diff --git a/contrib/cirrus/integration_test.sh b/contrib/cirrus/integration_test.sh index a50bd448f..371c2c914 100755 --- a/contrib/cirrus/integration_test.sh +++ b/contrib/cirrus/integration_test.sh @@ -24,6 +24,8 @@ case "${OS_RELEASE_ID}-${OS_RELEASE_VER}" in centos-7) ;& rhel-7) make install PREFIX=/usr ETCDIR=/etc + make podman-remote + install bin/podman-remote /usr/bin make test-binaries make localintegration ;; 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-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.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/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..c5268c04d 100644 --- a/install.md +++ b/install.md @@ -1,5 +1,65 @@ # libpod Installation Instructions +## Installing packaged versions of Podman + +#### [Arch Linux](https://www.archlinux.org) + +Podman is available in the AUR through the [libpod package](https://aur.archlinux.org/packages/libpod/) + +#### [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..1f3599082 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,40 @@ 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 +} diff --git a/libpod/adapter/runtime_remote.go b/libpod/adapter/runtime_remote.go index 27301d90b..7189348bc 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,92 @@ 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 + } 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/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/oci.go b/libpod/oci.go index 702f82491..3451ddaa8 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 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..5ff8b30f6 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 @@ -619,7 +644,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, 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/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/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/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..a75601b65 --- /dev/null +++ b/test/e2e/common_test.go @@ -0,0 +1,240 @@ +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 +} + +//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 +} + +// 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..660c2f639 --- /dev/null +++ b/test/e2e/libpod_suite_remoteclient_test.go @@ -0,0 +1,157 @@ +// +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 := p.MakeOptions(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() +} diff --git a/test/e2e/libpod_suite_test.go b/test/e2e/libpod_suite_test.go index d312124ab..48304af7f 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,10 @@ func (p *PodmanTestIntegration) setRegistriesConfigEnv(b []byte) { func resetRegistriesConfigEnv() { os.Setenv("REGISTRIES_CONFIG_PATH", "") } + +func PodmanTestCreate(tempDir string) *PodmanTestIntegration { + return PodmanTestCreateUtil(tempDir, false) +} + +//func (p *PodmanTestIntegration) StartVarlink() {} +//func (p *PodmanTestIntegration) StopVarlink() {} 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..50a279232 100644 --- a/test/e2e/prune_test.go +++ b/test/e2e/prune_test.go @@ -1,3 +1,5 @@ +// +build !remoteclient + package integration import ( diff --git a/test/e2e/ps_test.go b/test/e2e/ps_test.go index 9caa6e7f1..bff2427d5 100644 --- a/test/e2e/ps_test.go +++ b/test/e2e/ps_test.go @@ -1,3 +1,5 @@ +// +build !remoteclient + package integration import ( 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/version_test.go b/test/e2e/version_test.go index 68a462bdb..8ae2eb9ca 100644 --- a/test/e2e/version_test.go +++ b/test/e2e/version_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/vendor.conf b/vendor.conf index b6c58165c..9c9d41a0d 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.7 github.com/containers/psgo v1.1 github.com/coreos/go-systemd v14 github.com/cri-o/ocicni 2d2983e40c242322a56c22a903785e7f83eb378c @@ -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..3e8daf23c 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 } @@ -174,6 +174,18 @@ func Init(home string, options []string, uidMaps, gidMaps []idtools.IDMap) (grap } } + 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 { if err := mount.MakePrivate(home); err != nil { return nil, err @@ -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/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..ea5a92286 100644 --- a/version/version.go +++ b/version/version.go @@ -4,4 +4,4 @@ package version // NOTE: remember to bump the version at the top // of the top-level README.md file when this is // bumped. -const Version = "0.12.2-dev" +const Version = "1.0.1-dev" |