diff options
143 files changed, 1769 insertions, 763 deletions
@@ -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 @@ -1,6 +1,6 @@ GO ?= go DESTDIR ?= / -EPOCH_TEST_COMMIT ?= bd40dcfc2bc7c9014ea1f33482fb63aacbcdfe87 +EPOCH_TEST_COMMIT ?= 4406e1cfeed18fe89c0ad4e20a3c3b2f4b9ffcae HEAD ?= HEAD CHANGELOG_BASE ?= HEAD~ CHANGELOG_TARGET ?= HEAD @@ -109,6 +109,9 @@ podman: .gopathok $(PODMAN_VARLINK_DEPENDENCIES) podman-remote: .gopathok $(PODMAN_VARLINK_DEPENDENCIES) $(GO) build -ldflags '$(LDFLAGS_PODMAN)' -tags "$(BUILDTAGS) remoteclient" -o bin/$@ $(PROJECT)/cmd/podman +podman-remote-darwin: .gopathok $(PODMAN_VARLINK_DEPENDENCIES) + GOOS=darwin $(GO) build -ldflags '$(LDFLAGS_PODMAN)' -tags "remoteclient containers_image_openpgp exclude_graphdriver_devicemapper" -o bin/$@ $(PROJECT)/cmd/podman + local-cross: $(CROSS_BUILD_TARGETS) bin/podman.cross.%: .gopathok @@ -168,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/ @@ -185,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 2e2559ec7..a7601aaa2 100644 --- a/cmd/podman/exists.go +++ b/cmd/podman/exists.go @@ -5,6 +5,7 @@ import ( "github.com/containers/libpod/cmd/podman/libpodruntime" "github.com/containers/libpod/libpod" + "github.com/containers/libpod/libpod/adapter" "github.com/containers/libpod/libpod/image" "github.com/pkg/errors" "github.com/urfave/cli" @@ -66,13 +67,15 @@ 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") } - 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.ImageRuntime().NewFromLocal(args[0]); err != nil { - if errors.Cause(err) == image.ErrNoSuchImage { + 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" { os.Exit(1) } return err @@ -85,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/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/main.go b/cmd/podman/main.go index 604404827..ce60bbfb7 100644 --- a/cmd/podman/main.go +++ b/cmd/podman/main.go @@ -120,7 +120,7 @@ func main() { os.Exit(1) } args := c.Args() - if args.Present() { + if args.Present() && rootless.IsRootless() { if _, notRequireRootless := cmdsNotRequiringRootless[args.First()]; !notRequireRootless { became, ret, err := rootless.BecomeRootInUserNS() if err != nil { @@ -265,11 +265,10 @@ func main() { Usage: "output logging information to syslog as well as the console", }, } - if _, err := os.Stat("/etc/containers/registries.conf"); err != nil { - if os.IsNotExist(err) { - logrus.Warn("unable to find /etc/containers/registries.conf. some podman (image shortnames) commands may be limited") - } - } + // Check if /etc/containers/registries.conf exists when running in + // in a local environment. + CheckForRegistries() + if err := app.Run(os.Args); err != nil { if debug { logrus.Errorf(err.Error()) 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/platform_linux.go b/cmd/podman/platform_linux.go new file mode 100644 index 000000000..2127923ae --- /dev/null +++ b/cmd/podman/platform_linux.go @@ -0,0 +1,17 @@ +// +build linux + +package main + +import ( + "os" + + "github.com/sirupsen/logrus" +) + +func CheckForRegistries() { + if _, err := os.Stat("/etc/containers/registries.conf"); err != nil { + if os.IsNotExist(err) { + logrus.Warn("unable to find /etc/containers/registries.conf. some podman (image shortnames) commands may be limited") + } + } +} diff --git a/cmd/podman/platform_unsupported.go b/cmd/podman/platform_unsupported.go new file mode 100644 index 000000000..f39eeaf63 --- /dev/null +++ b/cmd/podman/platform_unsupported.go @@ -0,0 +1,6 @@ +// +build !linux + +package main + +func CheckForRegistries() { +} 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/tag.go b/cmd/podman/tag.go index 29f66d41e..d19cf69a2 100644 --- a/cmd/podman/tag.go +++ b/cmd/podman/tag.go @@ -1,7 +1,7 @@ package main import ( - "github.com/containers/libpod/cmd/podman/libpodruntime" + "github.com/containers/libpod/libpod/adapter" "github.com/pkg/errors" "github.com/urfave/cli" ) @@ -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") } - runtime, err := libpodruntime.GetRuntime(c) + runtime, err := adapter.GetRuntime(c) if err != nil { return errors.Wrapf(err, "could not create runtime") } defer runtime.Shutdown(false) - newImage, err := runtime.ImageRuntime().NewFromLocal(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..c6f1d3f1b 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, 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..e1d8e42b9 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,183 @@ 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 +} -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" +TEMPFILE=$(mktemp -p '' $(basename $0)_XXXXX.tar.bz2) +cleanup() { + set +e + wait + rm -f "$TEMPFILE" +} +trap cleanup EXIT -# COLOR! -RED="\e[1;36;41m" -YEL="\e[1;33;44m" -NOR="\e[0m" +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 +} -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 + egrep 'image_name' ".cirrus.yml" | grep -v '#' | cut -d: -f 2 | \ + tr -d [:blank:] | sort -u > "$TEMPFILE" + echo -e "${YEL}Some possible image_name values (from .cirrus.yml):${NOR}" + cat $TEMPFILE + 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 -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}" +parse_args $@ -if [[ "$USER" =~ "root" ]] +cd $LIBPODROOT + +# 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/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 2f22dd36b..8ef8fe167 100644 --- a/libpod/adapter/runtime_remote.go +++ b/libpod/adapter/runtime_remote.go @@ -5,13 +5,16 @@ package adapter import ( "context" "fmt" - "github.com/containers/libpod/cmd/podman/varlink" + "io" + "strings" + "time" + + "github.com/containers/image/types" + iopodman "github.com/containers/libpod/cmd/podman/varlink" "github.com/containers/libpod/libpod/image" - "github.com/opencontainers/go-digest" + digest "github.com/opencontainers/go-digest" "github.com/urfave/cli" "github.com/varlink/go/varlink" - "strings" - "time" ) // ImageRuntime is wrapper for image runtime @@ -19,13 +22,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 +38,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 @@ -59,13 +65,36 @@ type remoteImage struct { RepoDigests []string Parent string Size int64 - Tag string - Repository string Created time.Time InputName string Names []string Digest digest.Digest isParent bool + Runtime *LocalRuntime +} + +// Container ... +type Container struct { + remoteContainer +} + +// remoteContainer .... +type remoteContainer struct { + ID string + Image string + ImageID string + Command []string + Created time.Time + RunningFor string + Status string + //Ports []ocicni.PortMapping + RootFsSize int64 + RWSize int64 + Names string + Labels []map[string]string + // Mounts []string + // ContainerRunning bool + //Namespaces []LinuxNameSpace } // GetImages returns a slice of containerimages over a varlink connection @@ -80,7 +109,7 @@ func (r *LocalRuntime) GetImages() ([]*ContainerImage, error) { if len(i.RepoTags) > 1 { name = i.RepoTags[0] } - newImage, err := imageInListToContainerImage(i, name) + newImage, err := imageInListToContainerImage(i, name, r) if err != nil { return nil, err } @@ -89,11 +118,7 @@ func (r *LocalRuntime) GetImages() ([]*ContainerImage, error) { return newImages, nil } -func imageInListToContainerImage(i iopodman.ImageInList, name string) (*ContainerImage, error) { - imageParts, err := image.DecomposeString(name) - if err != nil { - return nil, err - } +func imageInListToContainerImage(i iopodman.ImageInList, name string, runtime *LocalRuntime) (*ContainerImage, error) { created, err := splitStringDate(i.Created) if err != nil { return nil, err @@ -107,10 +132,9 @@ func imageInListToContainerImage(i iopodman.ImageInList, name string) (*Containe Parent: i.ParentId, Size: i.Size, Created: created, - Tag: imageParts.Tag, - Repository: imageParts.Registry, Names: i.RepoTags, isParent: i.IsParent, + Runtime: runtime, } return &ContainerImage{ri}, nil } @@ -121,10 +145,46 @@ func (r *LocalRuntime) NewImageFromLocal(name string) (*ContainerImage, error) { if err != nil { return nil, err } - return imageInListToContainerImage(img, name) + return imageInListToContainerImage(img, name, r) } +// 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]) @@ -173,3 +233,78 @@ func (ci *ContainerImage) Labels(ctx context.Context) (map[string]string, error) func (ci *ContainerImage) Dangling() bool { return len(ci.Names()) == 0 } + +// TagImage ... +func (ci *ContainerImage) TagImage(tag string) error { + _, err := iopodman.TagImage().Call(ci.Runtime.Conn, ci.ID(), tag) + return err +} + +// 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 *RemoteRuntime) LookupContainer(idOrName string) (*Container, error) { + container, err := iopodman.GetContainer().Call(r.Conn, idOrName) + if err != nil { + return nil, err + } + return listContainerDataToContainer(container) +} + +// listContainerDataToContainer takes a varlink listcontainerData struct and makes +// an "adapted" Container +func listContainerDataToContainer(listData iopodman.ListContainerData) (*Container, error) { + created, err := splitStringDate(listData.Createdat) + if err != nil { + return nil, err + } + rc := remoteContainer{ + // TODO commented out attributes will be populated when podman-remote ps + // is implemented. They are not needed yet for basic container operations. + ID: listData.Id, + Image: listData.Image, + ImageID: listData.Imageid, + Command: listData.Command, + Created: created, + RunningFor: listData.Runningfor, + Status: listData.Status, + //ports: + RootFsSize: listData.Rootfssize, + RWSize: listData.Rwsize, + Names: listData.Names, + //Labels: + //Mounts + //ContainerRunning: + //namespaces: + } + return &Container{rc}, nil +} diff --git a/libpod/container.go b/libpod/container.go index ca83bbffe..95f7a2972 100644 --- a/libpod/container.go +++ b/libpod/container.go @@ -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"` diff --git a/libpod/container_api.go b/libpod/container_api.go index 4eaf737b0..149867759 100644 --- a/libpod/container_api.go +++ b/libpod/container_api.go @@ -3,6 +3,7 @@ package libpod import ( "context" "fmt" + "io" "io/ioutil" "os" "strconv" @@ -413,6 +414,25 @@ func (c *Container) Exec(tty, privileged bool, env, cmd []string, user, workDir return waitErr } +// AttachStreams contains streams that will be attached to the container +type AttachStreams struct { + // OutputStream will be attached to container's STDOUT + OutputStream io.WriteCloser + // ErrorStream will be attached to container's STDERR + ErrorStream io.WriteCloser + // InputStream will be attached to container's STDIN + InputStream io.Reader + // AttachOutput is whether to attach to STDOUT + // If false, stdout will not be attached + AttachOutput bool + // AttachError is whether to attach to STDERR + // If false, stdout will not be attached + AttachError bool + // AttachInput is whether to attach to STDIN + // If false, stdout will not be attached + AttachInput bool +} + // Attach attaches to a container func (c *Container) Attach(streams *AttachStreams, keys string, resize <-chan remotecommand.TerminalSize) error { if !c.batched { diff --git a/libpod/container_attach.go b/libpod/container_attach_linux.go index f925c3897..1d6f0bd96 100644 --- a/libpod/container_attach.go +++ b/libpod/container_attach_linux.go @@ -1,3 +1,5 @@ +//+build linux + package libpod import ( @@ -27,25 +29,6 @@ const ( AttachPipeStderr = 3 ) -// AttachStreams contains streams that will be attached to the container -type AttachStreams struct { - // OutputStream will be attached to container's STDOUT - OutputStream io.WriteCloser - // ErrorStream will be attached to container's STDERR - ErrorStream io.WriteCloser - // InputStream will be attached to container's STDIN - InputStream io.Reader - // AttachOutput is whether to attach to STDOUT - // If false, stdout will not be attached - AttachOutput bool - // AttachError is whether to attach to STDERR - // If false, stdout will not be attached - AttachError bool - // AttachInput is whether to attach to STDIN - // If false, stdout will not be attached - AttachInput bool -} - // Attach to the given container // Does not check if state is appropriate func (c *Container) attach(streams *AttachStreams, keys string, resize <-chan remotecommand.TerminalSize, startContainer bool) error { diff --git a/libpod/container_attach_unsupported.go b/libpod/container_attach_unsupported.go new file mode 100644 index 000000000..068652b29 --- /dev/null +++ b/libpod/container_attach_unsupported.go @@ -0,0 +1,11 @@ +//+build !linux + +package libpod + +import ( + "k8s.io/client-go/tools/remotecommand" +) + +func (c *Container) attach(streams *AttachStreams, keys string, resize <-chan remotecommand.TerminalSize, startContainer bool) error { + return ErrNotImplemented +} diff --git a/libpod/container_internal.go b/libpod/container_internal.go index 90f4659da..ce8791f08 100644 --- a/libpod/container_internal.go +++ b/libpod/container_internal.go @@ -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/image/image.go b/libpod/image/image.go index dda753385..ea326d820 100644 --- a/libpod/image/image.go +++ b/libpod/image/image.go @@ -25,7 +25,7 @@ import ( "github.com/containers/libpod/pkg/util" "github.com/containers/storage" "github.com/containers/storage/pkg/reexec" - "github.com/opencontainers/go-digest" + digest "github.com/opencontainers/go-digest" ociv1 "github.com/opencontainers/image-spec/specs-go/v1" "github.com/pkg/errors" "github.com/sirupsen/logrus" @@ -226,7 +226,6 @@ func (i *Image) getLocalImage() (*storage.Image, error) { i.InputName = dest.DockerReference().String() } - var taggedName string img, err := i.imageruntime.getImage(stripSha256(i.InputName)) if err == nil { return img.image, err @@ -240,25 +239,18 @@ func (i *Image) getLocalImage() (*storage.Image, error) { return nil, err } - // the inputname isn't tagged, so we assume latest and try again - if !decomposedImage.isTagged { - taggedName = fmt.Sprintf("%s:latest", i.InputName) - img, err = i.imageruntime.getImage(taggedName) - if err == nil { - return img.image, nil - } - } - // The image has a registry name in it and we made sure we looked for it locally // with a tag. It cannot be local. if decomposedImage.hasRegistry { return nil, errors.Wrapf(ErrNoSuchImage, imageError) - } - // if the image is saved with the repository localhost, searching with localhost prepended is necessary // We don't need to strip the sha because we have already determined it is not an ID - img, err = i.imageruntime.getImage(fmt.Sprintf("%s/%s", DefaultLocalRegistry, i.InputName)) + ref, err := decomposedImage.referenceWithRegistry(DefaultLocalRegistry) + if err != nil { + return nil, err + } + img, err = i.imageruntime.getImage(ref.String()) if err == nil { return img.image, err } @@ -452,35 +444,42 @@ func getImageDigest(ctx context.Context, src types.ImageReference, sc *types.Sys return "@" + digest.Hex(), nil } -// normalizeTag returns the canonical version of tag for use in Image.Names() -func normalizeTag(tag string) (string, error) { +// normalizedTag returns the canonical version of tag for use in Image.Names() +func normalizedTag(tag string) (reference.Named, error) { decomposedTag, err := decompose(tag) if err != nil { - return "", err - } - // If the input does not have a tag, we need to add one (latest) - if !decomposedTag.isTagged { - tag = fmt.Sprintf("%s:%s", tag, decomposedTag.Tag) + return nil, err } // If the input doesn't specify a registry, set the registry to localhost + var ref reference.Named if !decomposedTag.hasRegistry { - tag = fmt.Sprintf("%s/%s", DefaultLocalRegistry, tag) + ref, err = decomposedTag.referenceWithRegistry(DefaultLocalRegistry) + if err != nil { + return nil, err + } + } else { + ref, err = decomposedTag.normalizedReference() + if err != nil { + return nil, err + } } - return tag, nil + // If the input does not have a tag, we need to add one (latest) + ref = reference.TagNameOnly(ref) + return ref, nil } // TagImage adds a tag to the given image func (i *Image) TagImage(tag string) error { i.reloadImage() - tag, err := normalizeTag(tag) + ref, err := normalizedTag(tag) if err != nil { return err } tags := i.Names() - if util.StringInSlice(tag, tags) { + if util.StringInSlice(ref.String(), tags) { return nil } - tags = append(tags, tag) + tags = append(tags, ref.String()) if err := i.imageruntime.store.SetNames(i.ID(), tags); err != nil { return err } @@ -931,21 +930,23 @@ func (i *Image) MatchRepoTag(input string) (string, error) { if err != nil { return "", err } + imageRegistry, imageName, imageSuspiciousTagValueForSearch := dcImage.suspiciousRefNameTagValuesForSearch() for _, repoName := range i.Names() { count := 0 dcRepoName, err := decompose(repoName) if err != nil { return "", err } - if dcRepoName.Registry == dcImage.Registry && dcImage.Registry != "" { + repoNameRegistry, repoNameName, repoNameSuspiciousTagValueForSearch := dcRepoName.suspiciousRefNameTagValuesForSearch() + if repoNameRegistry == imageRegistry && imageRegistry != "" { count++ } - if dcRepoName.name == dcImage.name && dcImage.name != "" { + if repoNameName == imageName && imageName != "" { count++ - } else if splitString(dcRepoName.name) == splitString(dcImage.name) { + } else if splitString(repoNameName) == splitString(imageName) { count++ } - if dcRepoName.Tag == dcImage.Tag { + if repoNameSuspiciousTagValueForSearch == imageSuspiciousTagValueForSearch { count++ } results[count] = append(results[count], repoName) diff --git a/libpod/image/image_test.go b/libpod/image/image_test.go index 2a68fd273..077ae460e 100644 --- a/libpod/image/image_test.go +++ b/libpod/image/image_test.go @@ -257,24 +257,25 @@ func Test_stripSha256(t *testing.T) { assert.Equal(t, stripSha256("sha256:a"), "a") } -func TestNormalizeTag(t *testing.T) { +func TestNormalizedTag(t *testing.T) { const digestSuffix = "@sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" for _, c := range []struct{ input, expected string }{ {"#", ""}, // Clearly invalid {"example.com/busybox", "example.com/busybox:latest"}, // Qualified name-only {"example.com/busybox:notlatest", "example.com/busybox:notlatest"}, // Qualified name:tag - {"example.com/busybox" + digestSuffix, "example.com/busybox" + digestSuffix + ":none"}, // Qualified name@digest; FIXME: The result is not even syntactically valid! + {"example.com/busybox" + digestSuffix, "example.com/busybox" + digestSuffix}, // Qualified name@digest; FIXME? Should we allow tagging with a digest at all? {"example.com/busybox:notlatest" + digestSuffix, "example.com/busybox:notlatest" + digestSuffix}, // Qualified name:tag@digest {"busybox:latest", "localhost/busybox:latest"}, // Unqualified name-only {"ns/busybox:latest", "localhost/ns/busybox:latest"}, // Unqualified with a dot-less namespace + {"docker.io/busybox:latest", "docker.io/library/busybox:latest"}, // docker.io without /library/ } { - res, err := normalizeTag(c.input) + res, err := normalizedTag(c.input) if c.expected == "" { assert.Error(t, err, c.input) } else { assert.NoError(t, err, c.input) - assert.Equal(t, c.expected, res) + assert.Equal(t, c.expected, res.String()) } } } diff --git a/libpod/image/parts.go b/libpod/image/parts.go index b2a69f26c..dfdf0b08a 100644 --- a/libpod/image/parts.go +++ b/libpod/image/parts.go @@ -1,23 +1,20 @@ package image import ( - "fmt" "strings" "github.com/containers/image/docker/reference" + "github.com/pkg/errors" ) -// Parts describes the parts of an image's name -type Parts struct { - transport string - Registry string - name string - Tag string - isTagged bool - hasRegistry bool +// imageParts describes the parts of an image's name +type imageParts struct { + unnormalizedRef reference.Named // WARNING: Did not go through docker.io[/library] normalization + hasRegistry bool } -// Registries must contain a ":" or a "." or be localhost +// Registries must contain a ":" or a "." or be localhost; this helper exists for users of reference.Parse. +// For inputs that should use the docker.io[/library] normalization, use reference.ParseNormalizedNamed instead. func isRegistry(name string) bool { return strings.ContainsAny(name, ".:") || name == "localhost" } @@ -30,68 +27,78 @@ func GetImageBaseName(input string) (string, error) { if err != nil { return "", err } - splitImageName := strings.Split(decomposedImage.name, "/") + splitImageName := strings.Split(decomposedImage.unnormalizedRef.Name(), "/") return splitImageName[len(splitImageName)-1], nil } -// DecomposeString decomposes a string name into imageParts description. This -// is a wrapper for decompose -func DecomposeString(input string) (Parts, error) { - return decompose(input) -} - // decompose breaks an input name into an imageParts description -func decompose(input string) (Parts, error) { - var ( - parts Parts - hasRegistry bool - tag string - ) +func decompose(input string) (imageParts, error) { imgRef, err := reference.Parse(input) if err != nil { - return parts, err + return imageParts{}, err } - ntag, isTagged := imgRef.(reference.NamedTagged) - if !isTagged { - tag = "latest" - if _, hasDigest := imgRef.(reference.Digested); hasDigest { - tag = "none" - } - } else { - tag = ntag.Tag() + unnormalizedNamed := imgRef.(reference.Named) + // ip.unnormalizedRef, because it uses reference.Parse and not reference.ParseNormalizedNamed, + // does not use the standard heuristics for domains vs. namespaces/repos, so we need to check + // explicitly. + hasRegistry := isRegistry(reference.Domain(unnormalizedNamed)) + return imageParts{ + unnormalizedRef: unnormalizedNamed, + hasRegistry: hasRegistry, + }, nil +} + +// suspiciousRefNameTagValuesForSearch returns a "tag" value used in a previous implementation. +// This exists only to preserve existing behavior in heuristic code; it’s dubious that that behavior is correct, +// gespecially for the tag value. +func (ip *imageParts) suspiciousRefNameTagValuesForSearch() (string, string, string) { + registry := reference.Domain(ip.unnormalizedRef) + imageName := reference.Path(ip.unnormalizedRef) + // ip.unnormalizedRef, because it uses reference.Parse and not reference.ParseNormalizedNamed, + // does not use the standard heuristics for domains vs. namespaces/repos. + if registry != "" && !isRegistry(registry) { + imageName = registry + "/" + imageName + registry = "" } - registry := reference.Domain(imgRef.(reference.Named)) - imageName := reference.Path(imgRef.(reference.Named)) - // Is this a Registry or a repo? - if isRegistry(registry) { - hasRegistry = true + + var tag string + if tagged, isTagged := ip.unnormalizedRef.(reference.NamedTagged); isTagged { + tag = tagged.Tag() + } else if _, hasDigest := ip.unnormalizedRef.(reference.Digested); hasDigest { + tag = "none" } else { - if registry != "" { - imageName = registry + "/" + imageName - registry = "" - } + tag = "latest" } - return Parts{ - Registry: registry, - hasRegistry: hasRegistry, - name: imageName, - Tag: tag, - isTagged: isTagged, - transport: DefaultTransport, - }, nil + return registry, imageName, tag } -// assemble concatenates an image's parts into a string -func (ip *Parts) assemble() string { - spec := fmt.Sprintf("%s:%s", ip.name, ip.Tag) - - if ip.Registry != "" { - spec = fmt.Sprintf("%s/%s", ip.Registry, spec) +// referenceWithRegistry returns a (normalized) reference.Named composed of ip (with !ip.hasRegistry) +// qualified with registry. +func (ip *imageParts) referenceWithRegistry(registry string) (reference.Named, error) { + if ip.hasRegistry { + return nil, errors.Errorf("internal error: referenceWithRegistry called on imageParts with a registry (%#v)", *ip) } - return spec + // We could build a reference.WithName+WithTag/WithDigest here, but we need to round-trip via a string + // and a ParseNormalizedNamed anyway to get the right normalization of docker.io/library, so + // just use a string directly. + qualified := registry + "/" + ip.unnormalizedRef.String() + ref, err := reference.ParseNormalizedNamed(qualified) + if err != nil { + return nil, errors.Wrapf(err, "error normalizing registry+unqualified reference %#v", qualified) + } + return ref, nil } -// assemble concatenates an image's parts with transport into a string -func (ip *Parts) assembleWithTransport() string { - return fmt.Sprintf("%s%s", ip.transport, ip.assemble()) +// normalizedReference returns a (normalized) reference for ip (with ip.hasRegistry) +func (ip *imageParts) normalizedReference() (reference.Named, error) { + if !ip.hasRegistry { + return nil, errors.Errorf("internal error: normalizedReference called on imageParts without a registry (%#v)", *ip) + } + // We need to round-trip via a string to get the right normalization of docker.io/library + s := ip.unnormalizedRef.String() + ref, err := reference.ParseNormalizedNamed(s) + if err != nil { // Should never happen + return nil, errors.Wrapf(err, "error normalizing qualified reference %#v", s) + } + return ref, nil } diff --git a/libpod/image/parts_test.go b/libpod/image/parts_test.go index 1e01c85d3..726e55e86 100644 --- a/libpod/image/parts_test.go +++ b/libpod/image/parts_test.go @@ -4,64 +4,120 @@ import ( "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestDecompose(t *testing.T) { const digestSuffix = "@sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" for _, c := range []struct { - input string - transport, registry, name, tag string - isTagged, hasRegistry bool - assembled string - assembledWithTransport string + input string + registry, name, suspiciousTagValueForSearch string + hasRegistry bool }{ - {"#", "", "", "", "", false, false, "", ""}, // Entirely invalid input + {"#", "", "", "", false}, // Entirely invalid input { // Fully qualified docker.io, name-only input - "docker.io/library/busybox", "docker://", "docker.io", "library/busybox", "latest", false, true, - "docker.io/library/busybox:latest", "docker://docker.io/library/busybox:latest", + "docker.io/library/busybox", "docker.io", "library/busybox", "latest", true, }, { // Fully qualified example.com, name-only input - "example.com/ns/busybox", "docker://", "example.com", "ns/busybox", "latest", false, true, - "example.com/ns/busybox:latest", "docker://example.com/ns/busybox:latest", + "example.com/ns/busybox", "example.com", "ns/busybox", "latest", true, }, { // Unqualified single-name input - "busybox", "docker://", "", "busybox", "latest", false, false, - "busybox:latest", "docker://busybox:latest", + "busybox", "", "busybox", "latest", false, }, { // Unqualified namespaced input - "ns/busybox", "docker://", "", "ns/busybox", "latest", false, false, - "ns/busybox:latest", "docker://ns/busybox:latest", + "ns/busybox", "", "ns/busybox", "latest", false, }, { // name:tag - "example.com/ns/busybox:notlatest", "docker://", "example.com", "ns/busybox", "notlatest", true, true, - "example.com/ns/busybox:notlatest", "docker://example.com/ns/busybox:notlatest", + "example.com/ns/busybox:notlatest", "example.com", "ns/busybox", "notlatest", true, }, { // name@digest - // FIXME? .tag == "none" - "example.com/ns/busybox" + digestSuffix, "docker://", "example.com", "ns/busybox", "none", false, true, - // FIXME: this drops the digest and replaces it with an incorrect tag. - "example.com/ns/busybox:none", "docker://example.com/ns/busybox:none", + // FIXME? .suspiciousTagValueForSearch == "none" + "example.com/ns/busybox" + digestSuffix, "example.com", "ns/busybox", "none", true, }, { // name:tag@digest - "example.com/ns/busybox:notlatest" + digestSuffix, "docker://", "example.com", "ns/busybox", "notlatest", true, true, - // FIXME: This drops the digest - "example.com/ns/busybox:notlatest", "docker://example.com/ns/busybox:notlatest", + "example.com/ns/busybox:notlatest" + digestSuffix, "example.com", "ns/busybox", "notlatest", true, }, } { parts, err := decompose(c.input) - if c.transport == "" { + if c.name == "" { assert.Error(t, err, c.input) } else { assert.NoError(t, err, c.input) - assert.Equal(t, c.transport, parts.transport, c.input) - assert.Equal(t, c.registry, parts.Registry, c.input) - assert.Equal(t, c.name, parts.name, c.input) - assert.Equal(t, c.tag, parts.Tag, c.input) - assert.Equal(t, c.isTagged, parts.isTagged, c.input) + registry, name, suspiciousTagValueForSearch := parts.suspiciousRefNameTagValuesForSearch() + assert.Equal(t, c.registry, registry, c.input) + assert.Equal(t, c.name, name, c.input) + assert.Equal(t, c.suspiciousTagValueForSearch, suspiciousTagValueForSearch, c.input) assert.Equal(t, c.hasRegistry, parts.hasRegistry, c.input) - assert.Equal(t, c.assembled, parts.assemble(), c.input) - assert.Equal(t, c.assembledWithTransport, parts.assembleWithTransport(), c.input) + } + } +} + +func TestImagePartsReferenceWithRegistry(t *testing.T) { + const digestSuffix = "@sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" + + for _, c := range []struct { + input string + withDocker, withNonDocker string + }{ + {"example.com/ns/busybox", "", ""}, // Fully-qualified input is invalid. + {"busybox", "docker.io/library/busybox", "example.com/busybox"}, // Single-name input + {"ns/busybox", "docker.io/ns/busybox", "example.com/ns/busybox"}, // Namespaced input + {"ns/busybox:notlatest", "docker.io/ns/busybox:notlatest", "example.com/ns/busybox:notlatest"}, // name:tag + {"ns/busybox" + digestSuffix, "docker.io/ns/busybox" + digestSuffix, "example.com/ns/busybox" + digestSuffix}, // name@digest + { // name:tag@digest + "ns/busybox:notlatest" + digestSuffix, + "docker.io/ns/busybox:notlatest" + digestSuffix, "example.com/ns/busybox:notlatest" + digestSuffix, + }, + } { + parts, err := decompose(c.input) + require.NoError(t, err) + if c.withDocker == "" { + _, err := parts.referenceWithRegistry("docker.io") + assert.Error(t, err, c.input) + _, err = parts.referenceWithRegistry("example.com") + assert.Error(t, err, c.input) + } else { + ref, err := parts.referenceWithRegistry("docker.io") + require.NoError(t, err, c.input) + assert.Equal(t, c.withDocker, ref.String()) + ref, err = parts.referenceWithRegistry("example.com") + require.NoError(t, err, c.input) + assert.Equal(t, c.withNonDocker, ref.String()) + } + } + + // Invalid registry value + parts, err := decompose("busybox") + require.NoError(t, err) + _, err = parts.referenceWithRegistry("invalid@domain") + assert.Error(t, err) +} + +func TestImagePartsNormalizedReference(t *testing.T) { + const digestSuffix = "@sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" + + for _, c := range []struct{ input, expected string }{ + {"busybox", ""}, // Unqualified input is invalid + {"docker.io/busybox", "docker.io/library/busybox"}, // docker.io single-name + {"example.com/busybox", "example.com/busybox"}, // example.com single-name + {"docker.io/ns/busybox", "docker.io/ns/busybox"}, // docker.io namespaced + {"example.com/ns/busybox", "example.com/ns/busybox"}, // example.com namespaced + {"example.com/ns/busybox:notlatest", "example.com/ns/busybox:notlatest"}, // name:tag + {"example.com/ns/busybox" + digestSuffix, "example.com/ns/busybox" + digestSuffix}, // name@digest + { // name:tag@digest + "example.com/ns/busybox:notlatest" + digestSuffix, "example.com/ns/busybox:notlatest" + digestSuffix, + }, + } { + parts, err := decompose(c.input) + require.NoError(t, err) + if c.expected == "" { + _, err := parts.normalizedReference() + assert.Error(t, err, c.input) + } else { + ref, err := parts.normalizedReference() + require.NoError(t, err, c.input) + assert.Equal(t, c.expected, ref.String()) } } } diff --git a/libpod/image/pull.go b/libpod/image/pull.go index 203e94310..434b83520 100644 --- a/libpod/image/pull.go +++ b/libpod/image/pull.go @@ -4,7 +4,6 @@ import ( "context" "fmt" "io" - "strings" cp "github.com/containers/image/copy" "github.com/containers/image/directory" @@ -76,9 +75,11 @@ func (ir *Runtime) getPullRefPair(srcRef types.ImageReference, destName string) decomposedDest, err := decompose(destName) if err == nil && !decomposedDest.hasRegistry { // If the image doesn't have a registry, set it as the default repo - decomposedDest.Registry = DefaultLocalRegistry - decomposedDest.hasRegistry = true - destName = decomposedDest.assemble() + ref, err := decomposedDest.referenceWithRegistry(DefaultLocalRegistry) + if err != nil { + return pullRefPair{}, err + } + destName = ref.String() } reference := destName @@ -270,12 +271,6 @@ func (ir *Runtime) doPullImage(ctx context.Context, sc *types.SystemContext, goa return images, nil } -// hasShaInInputName returns a bool as to whether the user provided an image name that includes -// a reference to a specific sha -func hasShaInInputName(inputName string) bool { - return strings.Contains(inputName, "@sha256:") -} - // pullGoalFromPossiblyUnqualifiedName looks at inputName and determines the possible // image references to try pulling in combination with the registries.conf file as well func (ir *Runtime) pullGoalFromPossiblyUnqualifiedName(inputName string) (*pullGoal, error) { @@ -284,31 +279,11 @@ func (ir *Runtime) pullGoalFromPossiblyUnqualifiedName(inputName string) (*pullG return nil, err } if decomposedImage.hasRegistry { - var imageName, destName string - if hasShaInInputName(inputName) { - imageName = fmt.Sprintf("%s%s", decomposedImage.transport, inputName) - } else { - imageName = decomposedImage.assembleWithTransport() - } - srcRef, err := alltransports.ParseImageName(imageName) + srcRef, err := docker.ParseReference("//" + inputName) if err != nil { return nil, errors.Wrapf(err, "unable to parse '%s'", inputName) } - if hasShaInInputName(inputName) { - destName = decomposedImage.assemble() - } else { - destName = inputName - } - destRef, err := is.Transport.ParseStoreReference(ir.store, destName) - if err != nil { - return nil, errors.Wrapf(err, "error parsing dest reference name %#v", destName) - } - ps := pullRefPair{ - image: inputName, - srcRef: srcRef, - dstRef: destRef, - } - return singlePullRefPairGoal(ps), nil + return ir.getSinglePullRefPairGoal(srcRef, inputName) } searchRegistries, err := registries.GetRegistries() @@ -317,22 +292,18 @@ func (ir *Runtime) pullGoalFromPossiblyUnqualifiedName(inputName string) (*pullG } var refPairs []pullRefPair for _, registry := range searchRegistries { - decomposedImage.Registry = registry - imageName := decomposedImage.assembleWithTransport() - if hasShaInInputName(inputName) { - imageName = fmt.Sprintf("%s%s/%s", decomposedImage.transport, registry, inputName) - } - srcRef, err := alltransports.ParseImageName(imageName) + ref, err := decomposedImage.referenceWithRegistry(registry) if err != nil { - return nil, errors.Wrapf(err, "unable to parse '%s'", inputName) + return nil, err } - ps := pullRefPair{ - image: decomposedImage.assemble(), - srcRef: srcRef, + imageName := ref.String() + srcRef, err := docker.ParseReference("//" + imageName) + if err != nil { + return nil, errors.Wrapf(err, "unable to parse '%s'", imageName) } - ps.dstRef, err = is.Transport.ParseStoreReference(ir.store, ps.image) + ps, err := ir.getPullRefPair(srcRef, imageName) if err != nil { - return nil, errors.Wrapf(err, "error parsing dest reference name %#v", ps.image) + return nil, err } refPairs = append(refPairs, ps) } diff --git a/libpod/image/pull_test.go b/libpod/image/pull_test.go index 37b45dc83..3890c5e6c 100644 --- a/libpod/image/pull_test.go +++ b/libpod/image/pull_test.go @@ -76,9 +76,7 @@ func TestGetPullRefPair(t *testing.T) { }, { // name, no registry, no tag: "dir:/dev/this-does-not-exist", "from-directory", - // FIXME(?) Adding a registry also adds a :latest tag. OTOH that actually matches the used destination. - // Either way it is surprising that the localhost/ addition changes this. (mitr hoping to remove the "image" member). - "localhost/from-directory:latest", "localhost/from-directory:latest", + "localhost/from-directory", "localhost/from-directory:latest", }, { // registry/name:tag : "dir:/dev/this-does-not-exist", "example.com/from-directory:notlatest", @@ -90,8 +88,7 @@ func TestGetPullRefPair(t *testing.T) { }, { // name@digest, no registry: "dir:/dev/this-does-not-exist", "from-directory" + digestSuffix, - // FIXME?! Why is this dropping the digest, and adding :none?! - "localhost/from-directory:none", "localhost/from-directory:none", + "localhost/from-directory" + digestSuffix, "localhost/from-directory" + digestSuffix, }, { // registry/name@digest: "dir:/dev/this-does-not-exist", "example.com/from-directory" + digestSuffix, @@ -211,14 +208,13 @@ func TestPullGoalFromImageReference(t *testing.T) { false, }, { // Relative path, single element. - // FIXME? Note the :latest difference in .image. "dir:this-does-not-exist", - []expected{{"localhost/this-does-not-exist:latest", "localhost/this-does-not-exist:latest"}}, + []expected{{"localhost/this-does-not-exist", "localhost/this-does-not-exist:latest"}}, false, }, { // Relative path, multiple elements. "dir:testdata/this-does-not-exist", - []expected{{"localhost/testdata/this-does-not-exist:latest", "localhost/testdata/this-does-not-exist:latest"}}, + []expected{{"localhost/testdata/this-does-not-exist", "localhost/testdata/this-does-not-exist:latest"}}, false, }, @@ -324,8 +320,7 @@ func TestPullGoalFromPossiblyUnqualifiedName(t *testing.T) { { // Qualified example.com, name@digest. "example.com/ns/busybox" + digestSuffix, []pullRefStrings{{"example.com/ns/busybox" + digestSuffix, "docker://example.com/ns/busybox" + digestSuffix, - // FIXME?! Why is .dstName dropping the digest, and adding :none?! - "example.com/ns/busybox:none"}}, + "example.com/ns/busybox" + digestSuffix}}, false, }, // Qualified example.com, name:tag@digest. This code is happy to try, but .srcRef parsing currently rejects such input. @@ -333,16 +328,16 @@ func TestPullGoalFromPossiblyUnqualifiedName(t *testing.T) { { // Unqualified, single-name, name-only "busybox", []pullRefStrings{ - {"example.com/busybox:latest", "docker://example.com/busybox:latest", "example.com/busybox:latest"}, + {"example.com/busybox", "docker://example.com/busybox:latest", "example.com/busybox:latest"}, // (The docker:// representation is shortened by c/image/docker.Reference but it refers to "docker.io/library".) - {"docker.io/busybox:latest", "docker://busybox:latest", "docker.io/library/busybox:latest"}, + {"docker.io/library/busybox", "docker://busybox:latest", "docker.io/library/busybox:latest"}, }, true, }, { // Unqualified, namespaced, name-only "ns/busybox", []pullRefStrings{ - {"example.com/ns/busybox:latest", "docker://example.com/ns/busybox:latest", "example.com/ns/busybox:latest"}, + {"example.com/ns/busybox", "docker://example.com/ns/busybox:latest", "example.com/ns/busybox:latest"}, }, true, }, @@ -351,17 +346,16 @@ func TestPullGoalFromPossiblyUnqualifiedName(t *testing.T) { []pullRefStrings{ {"example.com/busybox:notlatest", "docker://example.com/busybox:notlatest", "example.com/busybox:notlatest"}, // (The docker:// representation is shortened by c/image/docker.Reference but it refers to "docker.io/library".) - {"docker.io/busybox:notlatest", "docker://busybox:notlatest", "docker.io/library/busybox:notlatest"}, + {"docker.io/library/busybox:notlatest", "docker://busybox:notlatest", "docker.io/library/busybox:notlatest"}, }, true, }, { // Unqualified, name@digest "busybox" + digestSuffix, []pullRefStrings{ - // FIXME?! Why is .input and .dstName dropping the digest, and adding :none?! - {"example.com/busybox:none", "docker://example.com/busybox" + digestSuffix, "example.com/busybox:none"}, + {"example.com/busybox" + digestSuffix, "docker://example.com/busybox" + digestSuffix, "example.com/busybox" + digestSuffix}, // (The docker:// representation is shortened by c/image/docker.Reference but it refers to "docker.io/library".) - {"docker.io/busybox:none", "docker://busybox" + digestSuffix, "docker.io/library/busybox:none"}, + {"docker.io/library/busybox" + digestSuffix, "docker://busybox" + digestSuffix, "docker.io/library/busybox" + digestSuffix}, }, true, }, diff --git a/libpod/image/utils.go b/libpod/image/utils.go index 135b47008..ad027f32a 100644 --- a/libpod/image/utils.go +++ b/libpod/image/utils.go @@ -16,7 +16,8 @@ import ( // findImageInRepotags takes an imageParts struct and searches images' repotags for // a match on name:tag -func findImageInRepotags(search Parts, images []*Image) (*storage.Image, error) { +func findImageInRepotags(search imageParts, images []*Image) (*storage.Image, error) { + _, searchName, searchSuspiciousTagValueForSearch := search.suspiciousRefNameTagValuesForSearch() var results []*storage.Image for _, image := range images { for _, name := range image.Names() { @@ -25,21 +26,22 @@ func findImageInRepotags(search Parts, images []*Image) (*storage.Image, error) if err != nil { continue } - if d.name == search.name && d.Tag == search.Tag { + _, dName, dSuspiciousTagValueForSearch := d.suspiciousRefNameTagValuesForSearch() + if dName == searchName && dSuspiciousTagValueForSearch == searchSuspiciousTagValueForSearch { results = append(results, image.image) continue } // account for registry:/somedir/image - if strings.HasSuffix(d.name, search.name) && d.Tag == search.Tag { + if strings.HasSuffix(dName, searchName) && dSuspiciousTagValueForSearch == searchSuspiciousTagValueForSearch { results = append(results, image.image) continue } } } if len(results) == 0 { - return &storage.Image{}, errors.Errorf("unable to find a name and tag match for %s in repotags", search.name) + return &storage.Image{}, errors.Errorf("unable to find a name and tag match for %s in repotags", searchName) } else if len(results) > 1 { - return &storage.Image{}, errors.Errorf("found multiple name and tag matches for %s in repotags", search.name) + return &storage.Image{}, errors.Errorf("found multiple name and tag matches for %s in repotags", searchName) } return results[0], nil } diff --git a/libpod/info.go b/libpod/info.go index a98f93897..191ce6810 100644 --- a/libpod/info.go +++ b/libpod/info.go @@ -4,7 +4,6 @@ import ( "bufio" "bytes" "fmt" - "github.com/containers/buildah" "io/ioutil" "os" "runtime" @@ -12,6 +11,7 @@ import ( "strings" "time" + "github.com/containers/buildah" "github.com/containers/libpod/pkg/rootless" "github.com/containers/libpod/pkg/util" "github.com/containers/libpod/utils" @@ -184,12 +184,12 @@ func (r *Runtime) GetConmonVersion() (string, error) { // GetOCIRuntimePath returns the path to the OCI Runtime Path the runtime is using func (r *Runtime) GetOCIRuntimePath() string { - return r.ociRuntimePath + return r.ociRuntimePath.Paths[0] } // GetOCIRuntimeVersion returns a string representation of the oci runtimes version func (r *Runtime) GetOCIRuntimeVersion() (string, error) { - output, err := utils.ExecCmd(r.ociRuntimePath, "--version") + output, err := utils.ExecCmd(r.ociRuntimePath.Paths[0], "--version") if err != nil { return "", err } diff --git a/libpod/lock/shm_lock_manager_unsupported.go b/libpod/lock/shm_lock_manager_unsupported.go index a1340fcd1..cbdb2f7bc 100644 --- a/libpod/lock/shm_lock_manager_unsupported.go +++ b/libpod/lock/shm_lock_manager_unsupported.go @@ -9,12 +9,12 @@ import "fmt" type SHMLockManager struct{} // NewSHMLockManager is not supported on this platform -func NewSHMLockManager(numLocks uint32) (Manager, error) { +func NewSHMLockManager(path string, numLocks uint32) (Manager, error) { return nil, fmt.Errorf("not supported") } // OpenSHMLockManager is not supported on this platform -func OpenSHMLockManager(numLocks uint32) (Manager, error) { +func OpenSHMLockManager(path string, numLocks uint32) (Manager, error) { return nil, fmt.Errorf("not supported") } diff --git a/libpod/oci.go b/libpod/oci.go index 7a908db2e..4092657f8 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..68599fe6d 100644 --- a/libpod/runtime_ctr.go +++ b/libpod/runtime_ctr.go @@ -62,6 +62,8 @@ func (r *Runtime) newContainer(ctx context.Context, rSpec *spec.Spec, options .. ctr.config.StopTimeout = CtrRemoveTimeout + ctr.config.OCIRuntime = r.config.OCIRuntime + // Set namespace based on current runtime namespace // Do so before options run so they can override it if r.config.Namespace != "" { diff --git a/libpod/runtime_volume_unsupported.go b/libpod/runtime_volume_unsupported.go new file mode 100644 index 000000000..d87459759 --- /dev/null +++ b/libpod/runtime_volume_unsupported.go @@ -0,0 +1,19 @@ +// +build !linux + +package libpod + +import ( + "context" +) + +func (r *Runtime) removeVolume(ctx context.Context, v *Volume, force, prune bool) error { + return ErrNotImplemented +} + +func (r *Runtime) newVolume(ctx context.Context, options ...VolumeCreateOption) (*Volume, error) { + return nil, ErrNotImplemented +} + +func (r *Runtime) NewVolume(ctx context.Context, options ...VolumeCreateOption) (*Volume, error) { + return nil, ErrNotImplemented +} 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/rootless/rootless_unsupported.go b/pkg/rootless/rootless_unsupported.go index d72402c9f..1823c023e 100644 --- a/pkg/rootless/rootless_unsupported.go +++ b/pkg/rootless/rootless_unsupported.go @@ -14,7 +14,7 @@ func IsRootless() bool { // BecomeRootInUserNS is a stub function that always returns false and an // error on unsupported OS's func BecomeRootInUserNS() (bool, int, error) { - return false, -1, errors.New("this function is not supported on this os") + return false, -1, errors.New("this function is not supported on this os1") } // GetRootlessUID returns the UID of the user in the parent userNS @@ -34,11 +34,18 @@ func SkipStorageSetup() bool { // JoinNS re-exec podman in a new userNS and join the user namespace of the specified // PID. func JoinNS(pid uint) (bool, int, error) { - return false, -1, errors.New("this function is not supported on this os") + return false, -1, errors.New("this function is not supported on this os2") } // JoinNSPath re-exec podman in a new userNS and join the owner user namespace of the // specified path. func JoinNSPath(path string) (bool, int, error) { - return false, -1, errors.New("this function is not supported on this os") + return false, -1, errors.New("this function is not supported on this os3") +} + +// JoinDirectUserAndMountNS re-exec podman in a new userNS and join the user and mount +// namespace of the specified PID without looking up its parent. Useful to join directly +// the conmon process. +func JoinDirectUserAndMountNS(pid uint) (bool, int, error) { + return false, -1, errors.New("this function is not supported on this os4") } diff --git a/pkg/spec/config_unsupported.go b/pkg/spec/config_unsupported.go index c2a58696d..160414878 100644 --- a/pkg/spec/config_unsupported.go +++ b/pkg/spec/config_unsupported.go @@ -26,3 +26,7 @@ func (c *CreateConfig) createBlockIO() (*spec.LinuxBlockIO, error) { func makeThrottleArray(throttleInput []string, rateType int) ([]spec.LinuxThrottleDevice, error) { return nil, errors.New("function not implemented") } + +func devicesFromPath(g *generate.Generator, devicePath string) error { + return errors.New("function not implemented") +} 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..e00a59b7f --- /dev/null +++ b/test/e2e/common_test.go @@ -0,0 +1,229 @@ +package integration + +import ( + "fmt" + . "github.com/containers/libpod/test/utils" + "os" + "os/exec" + + "github.com/containers/storage/pkg/reexec" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + "path/filepath" + "strings" + "testing" +) + +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 +} 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..38c50da14 100644 --- a/test/e2e/inspect_test.go +++ b/test/e2e/inspect_test.go @@ -1,3 +1,5 @@ +// +build !remoteclient + package integration import ( 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..cad4401f8 --- /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..a7ce14239 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 { @@ -281,50 +127,6 @@ func (s *PodmanSessionIntegration) InspectImageJSON() []inspect.ImageData { 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 +208,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..daf8b8c32 100644 --- a/test/e2e/rootless_test.go +++ b/test/e2e/rootless_test.go @@ -1,3 +1,5 @@ +// +build !remoteclient + package integration import ( 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/troubleshooting.md b/troubleshooting.md index 561562193..d210d85df 100644 --- a/troubleshooting.md +++ b/troubleshooting.md @@ -9,7 +9,7 @@ A large number of issues reported against Podman are often found to already be fixed in more current versions of the project. Before reporting an issue, please verify the -version you are running with `podman version` and compare it to the lastest release +version you are running with `podman version` and compare it to the latest release documented on the top of Podman's [README.md](README.md). If they differ, please update your version of PODMAN to the latest possible @@ -143,3 +143,33 @@ If you are using a useradd command within a Dockerfile with a large UID/GID, it #### Solution If the entry in the Dockerfile looked like: RUN useradd -u 99999000 -g users newuser then add the `--log-no-init` parameter to change it to: `RUN useradd --log-no-init -u 99999000 -g users newuser`. This option tells useradd to stop creating the lastlog file. + +### 7) Permission denied when running Podman commands + +When rootless podman attempts to execute a container on a non exec home directory a permission error will be raised. + +#### Symptom + +If you are running podman or buildah on a home directory that is mounted noexec, +then they will fail. With a message like: + +``` +podman run centos:7 +standard_init_linux.go:203: exec user process caused "permission denied" +``` + +#### Solution + +Since the administrator of the system setup your home directory to be noexec, you will not be allowed to execute containers from storage in your home directory. It is possible to work around this by manually specifying a container storage path that is not on a noexec mount. Simply copy the file /etc/containers/storage.conf to ~/.config/containers/ (creating the directory if necessary). Specify a graphroot directory which is not on a noexec mount point and to which you have read/write privileges. You will need to modify other fields to writable directories as well. + +For example + +``` +cat ~/.config/containers/storage.conf +[storage] + driver = "overlay" + runroot = "/run/user/1000" + graphroot = "/execdir/myuser/storage" + [storage.options] + mount_program = "/bin/fuse-overlayfs" +``` diff --git a/vendor.conf b/vendor.conf index 604d2f685..209c22a14 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.6 github.com/containers/psgo v1.1 github.com/coreos/go-systemd v14 github.com/cri-o/ocicni 2d2983e40c242322a56c22a903785e7f83eb378c diff --git a/vendor/github.com/containers/storage/drivers/overlay/overlay.go b/vendor/github.com/containers/storage/drivers/overlay/overlay.go index 2455b6736..06d58db38 100644 --- a/vendor/github.com/containers/storage/drivers/overlay/overlay.go +++ b/vendor/github.com/containers/storage/drivers/overlay/overlay.go @@ -793,6 +793,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 +987,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/zfs/zfs.go b/vendor/github.com/containers/storage/drivers/zfs/zfs.go index c3ce6e869..69b143c54 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 } @@ -360,11 +359,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 +386,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 +416,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/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" |