diff options
188 files changed, 4466 insertions, 2671 deletions
diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml new file mode 100644 index 000000000..44cb82ff0 --- /dev/null +++ b/.github/workflows/stale.yml @@ -0,0 +1,25 @@ +name: Mark stale issues and pull requests + +# Please refer to https://github.com/actions/stale/blob/master/action.yml +# to see all config knobs of the stale action. + +on: + schedule: + - cron: "0 0 * * *" + +jobs: + stale: + + runs-on: ubuntu-latest + + steps: + - uses: actions/stale@v1 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + stale-issue-message: 'This issue had no activity for 30 days. In the absence of activity or the "do-not-close" label, the issue will be automatically closed within 7 days.' + stale-pr-message: 'This pull request had no activity for 30 days. In the absence of activity or the "do-not-close" label, the pull request will be automatically closed within 7 days.' + stale-issue-label: 'stale-issue' + stale-pr-label: 'stale-pr' + days-before-stale: 30 + days-before-close: 7 + exempt-pr-label: 'do-not-close' @@ -107,6 +107,8 @@ in the [API.md](https://github.com/containers/libpod/blob/master/API.md) file in [func InspectPod(name: string) string](#InspectPod) +[func InspectVolume(name: string) string](#InspectVolume) + [func KillContainer(name: string, signal: int) string](#KillContainer) [func KillPod(name: string, signal: int) string](#KillPod) @@ -804,6 +806,12 @@ method InspectPod(name: [string](https://godoc.org/builtin#string)) [string](htt InspectPod takes the name or ID of an image and returns a string representation of data associated with the pod. You must serialize the string into JSON to use it further. A [PodNotFound](#PodNotFound) error will be returned if the pod cannot be found. +### <a name="InspectVolume"></a>func InspectVolume +<div style="background-color: #E8E8E8; padding: 15px; margin: 10px; border-radius: 10px;"> + +method InspectVolume(name: [string](https://godoc.org/builtin#string)) [string](https://godoc.org/builtin#string)</div> +InspectVolume inspects a single volume. Returns inspect JSON in the form of a +string. ### <a name="KillContainer"></a>func KillContainer <div style="background-color: #E8E8E8; padding: 15px; margin: 10px; border-radius: 10px;"> @@ -1591,7 +1599,7 @@ subgidname [?string](#?string) sysctl [?[]string](#?[]string) -systemd [?bool](#?bool) +systemd [?string](#?string) tmpfs [?[]string](#?[]string) @@ -3,7 +3,7 @@ export GOPROXY=https://proxy.golang.org GO ?= go DESTDIR ?= -EPOCH_TEST_COMMIT ?= 960f07b0f79e6d6f94842fd4892e775c319f0a39 +EPOCH_TEST_COMMIT ?= 2b0892e757c878cdb087dd22b8986bccef0276ed HEAD ?= HEAD CHANGELOG_BASE ?= HEAD~ CHANGELOG_TARGET ?= HEAD @@ -438,7 +438,10 @@ install.systemd: install ${SELINUXOPT} -m 644 contrib/varlink/io.podman.socket ${DESTDIR}${SYSTEMDDIR}/io.podman.socket install ${SELINUXOPT} -m 644 contrib/varlink/io.podman.socket ${DESTDIR}${USERSYSTEMDDIR}/io.podman.socket install ${SELINUXOPT} -m 644 contrib/varlink/io.podman.service ${DESTDIR}${SYSTEMDDIR}/io.podman.service - install ${SELINUXOPT} -m 644 contrib/varlink/io.podman.service ${DESTDIR}${USERSYSTEMDDIR}/io.podman.service + install ${SELINUXOPT} -d ${DESTDIR}${USERSYSTEMDDIR} + # User units are ordered differently, we can't make the *system* multi-user.target depend on a user unit. + # For user units the default.target that's the default is fine. + sed -e 's,^WantedBy=.*,WantedBy=default.target,' < contrib/varlink/io.podman.service > ${DESTDIR}${USERSYSTEMDDIR}/io.podman.service install ${SELINUXOPT} -m 644 contrib/varlink/podman.conf ${DESTDIR}${TMPFILESDIR}/podman.conf uninstall: @@ -451,7 +454,7 @@ uninstall: .PHONY: .gitvalidation .gitvalidation: .gopathok - GIT_CHECK_EXCLUDE="./vendor" $(GOBIN)/git-validation -v -run DCO,short-subject,dangling-whitespace -range $(EPOCH_TEST_COMMIT)..$(HEAD) + GIT_CHECK_EXCLUDE="./vendor:docs/rtd/make.bat" $(GOBIN)/git-validation -v -run DCO,short-subject,dangling-whitespace -range $(EPOCH_TEST_COMMIT)..$(HEAD) .PHONY: install.tools install.tools: .install.gitvalidation .install.gometalinter .install.md2man .install.ginkgo .install.golangci-lint ## Install needed tools @@ -5,7 +5,7 @@ Libpod provides a library for applications looking to use the Container Pod concept, popularized by Kubernetes. Libpod also contains the Pod Manager tool `(Podman)`. Podman manages pods, containers, container images, and container volumes. -* [Latest Version: 1.6.0](https://github.com/containers/libpod/releases/latest) +* [Latest Version: 1.6.2](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) * [GoDoc: ![GoDoc](https://godoc.org/github.com/containers/libpod/libpod?status.svg)](https://godoc.org/github.com/containers/libpod/libpod) * Automated continuous release downloads (including remote-client): diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index bff9a5f14..235871273 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -1,5 +1,38 @@ # Release Notes +## 1.6.2 +### Features +- Added a `--runtime` flag to `podman system migrate` to allow the OCI runtime for all containers to be reset, to ease transition to the `crun` runtime on CGroups V2 systems until `runc` gains full support +- The `podman rm` command can now remove containers in broken states which previously could not be removed +- The `podman info` command, when run without root, now shows information on UID and GID mappings in the rootless user namespace +- Added `podman build --squash-all` flag, which squashes all layers (including those of the base image) into one layer +- The `--systemd` flag to `podman run` and `podman create` now accepts a string argument and allows a new value, `always`, which forces systemd support without checking if the the container entrypoint is systemd + +### Bugfixes +- Fixed a bug where the `podman top` command did not work on systems using CGroups V2 ([#4192](https://github.com/containers/libpod/issues/4192)) +- Fixed a bug where rootless Podman could double-close a file, leading to a panic +- Fixed a bug where rootless Podman could fail to retrieve some containers while refreshing the state +- Fixed a bug where `podman start --attach --sig-proxy=false` would still proxy signals into the container +- Fixed a bug where Podman would unconditionally use a non-default path for authentication credentials (`auth.json`), breaking `podman login` integration with `skopeo` and other tools using the containers/image library +- Fixed a bug where `podman ps --format=json` and `podman images --format=json` would display `null` when no results were returned, instead of valid JSON +- Fixed a bug where `podman build --squash` was incorrectly squashing all layers into one, instead of only new layers +- Fixed a bug where rootless Podman would allow volumes with options to be mounted (mounting volumes requires root), creating an inconsistent state where volumes reported as mounted but were not ([#4248](https://github.com/containers/libpod/issues/4248)) +- Fixed a bug where volumes which failed to unmount could not be removed ([#4247](https://github.com/containers/libpod/issues/4247)) +- Fixed a bug where Podman incorrectly handled some errors relating to unmounted or missing containers in containers/storage +- Fixed a bug where `podman stats` was broken on systems running CGroups V2 when run rootless ([#4268](https://github.com/containers/libpod/issues/4268)) +- Fixed a bug where the `podman start` command would print the short container ID, instead of the full ID +- Fixed a bug where containers created with an OCI runtime that is no longer available (uninstalled or removed from the config file) would not appear in `podman ps` and could not be removed via `podman rm` +- Fixed a bug where containers restored via `podman container restore --import` would retain the CGroup path of the original container, even if their container ID changed; thus, multiple containers created from the same checkpoint would all share the same CGroup + +### Misc +- The default PID limit for containers is now set to 4096. It can be adjusted back to the old default (unlimited) by passing `--pids-limit 0` to `podman create` and `podman run` +- The `podman start --attach` command now automatically attaches `STDIN` if the container was created with `-i` +- The `podman network create` command now validates network names using the same regular expression as container and pod names +- The `--systemd` flag to `podman run` and `podman create` will now only enable systemd mode when the binary being run inside the container is `/sbin/init`, `/usr/sbin/init`, or ends in `systemd` (previously detected any path ending in `init` or `systemd`) +- Updated vendored Buildah to 1.11.3 +- Updated vendored containers/storage to 1.13.5 +- Updated vendored containers/image to 4.0.1 + ## 1.6.1 ### Bugfixes - Fixed a bug where rootless Podman on systems using CGroups V2 would not function with the `cgroupfs` CGroups manager diff --git a/changelog.txt b/changelog.txt index 8508d0d1c..615e2a135 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,3 +1,73 @@ +- Changelog for v1.6.2 (2019-10-17) + * Finalize release notes for v1.6.2 + * rootless: drop dependency on docker + * Bump gitvalidation epoch + * Bump to v1.6.2-dev + * Refactor tests when checking for error exit codes + * Attach stdin to container at start if it was created with --interactive + +- Changelog for v1.6.2-rc1 (2019-10-16) + * Add release notes for Podman 1.6.2 + * start: print full container ID + * Add a MissingRuntime implementation + * rootless v2 cannot collect network stats + * inspect: rename ImageID go field to Image + * systemd: accept also /sbin/init + * Unwrap errors before comparing them + * vendor github.com/containers/storage@v1.13.5 + * Ensure volumes can be removed when they fail to unmount + * Fix sample's JSON syntax error in oci-hooks.5.md + * change error wording when conmon fails without logs + * images: empty list is valid json with --format=json + * Allow giving path to Podman for cleanup command + * Touch up bad math in run man page + * Add squash-all, fix squash option in build + * tests: enable ps --size tests for rootless + * container: initialize results list + * Make user io.podman.service unit WantedBy=default.target + * rootless: do not set PIDs limit if --cgroup-manager=cgroupfs + * Update build man page with latest Buildah changes + * Fix default path for auth.json + * When restoring containers, reset cgroup path + * Migrate can move containers to a new runtime + * Move OCI runtime implementation behind an interface + * show uid_map in podman info + * cli: support --systemd=always + * systemd: expect full path /usr/sbin/init + * catch runc v2 error + * Respect --sig-proxy flag with podman start --attach + * rootless: automatically recreate the pause.pid file + * rootless: do not close files twice + * refresh: do not access network ns if not in the namespace + * Cirrus: Produce and collect varlink output + * io.podman.socket: drop Also=multi-user.target + * Cirrus: Remove broken/failing testing_crun task + * Cirrus: Use new VM cache images + * Cirrus: Install conmon in Fedora VMs + * vendor c/psgo@v1.3.2 + * troubleshooting: fix useradd no-log-init argument + * Setup a reasonable default for pids-limit 4096 + * Update c/image to v4.0.1 and buildah to 1.11.3 + * When evicting containers, perform a normal remove first + * Bump gopkg.in/yaml.v2 from 2.2.3 to 2.2.4 + * podman network create: validate user input + * Cirrus: Simplify package NVR logging + * Docs: Update links, add links to latest + * Cirrus: Fix log URIs & add optional $ALSO_FILENAME + * Raise start_test polling interval + * system tests: info: deal with hyphen in username + * Bump gitvalidation epoch + * Bump to v1.6.2-dev + * Apply changes also to the windows implementation + * System-tests: Use bash explicitly + * Podman 1.6.0 has been released, update the README + * Add api link to tutorials + * Bump gopkg.in/yaml.v2 from 2.2.2 to 2.2.3 + * Allow setting default parameters with env vars + * Avoid hard-coding path to varlink and podman + * Allow changing IdentityFile and to IgnoreHosts + * rm: add containers eviction with `rm --force` + - Changelog for v1.6.1 (2019-10-02) * Update release notes for v1.6.1 * Bump gitvalidation epoch diff --git a/cmd/podman/build.go b/cmd/podman/build.go index 8eb12cacd..f4efea544 100644 --- a/cmd/podman/build.go +++ b/cmd/podman/build.go @@ -17,20 +17,22 @@ import ( "github.com/pkg/errors" "github.com/sirupsen/logrus" "github.com/spf13/cobra" + "github.com/spf13/pflag" ) var ( buildCommand cliconfig.BuildValues - buildDescription = "Builds an OCI or Docker image using instructions from one or more Dockerfiles and a specified build context directory." + buildDescription = "Builds an OCI or Docker image using instructions from one or more Containerfiles and a specified build context directory." layerValues buildahcli.LayerResults budFlagsValues buildahcli.BudResults fromAndBudValues buildahcli.FromAndBudResults userNSValues buildahcli.UserNSResults namespaceValues buildahcli.NameSpaceResults + podBuildValues cliconfig.PodmanBuildResults _buildCommand = &cobra.Command{ Use: "build [flags] CONTEXT", - Short: "Build an image using instructions from Dockerfiles", + Short: "Build an image using instructions from Containerfiles", Long: buildDescription, RunE: func(cmd *cobra.Command, args []string) error { buildCommand.InputArgs = args @@ -40,11 +42,12 @@ var ( buildCommand.FromAndBudResults = &fromAndBudValues buildCommand.LayerResults = &layerValues buildCommand.NameSpaceResults = &namespaceValues + buildCommand.PodmanBuildResults = &podBuildValues buildCommand.Remote = remoteclient return buildCmd(&buildCommand) }, Example: `podman build . - podman build --creds=username:password -t imageName -f Dockerfile.simple . + podman build --creds=username:password -t imageName -f Containerfile.simple . podman build --layers --force-rm --tag imageName .`, } ) @@ -73,25 +76,40 @@ func init() { logrus.Error("unable to set force-rm flag to true") } flag.DefValue = "true" + podmanBuildFlags := GetPodmanBuildFlags(&podBuildValues) + flag = podmanBuildFlags.Lookup("squash-all") + if err := flag.Value.Set("false"); err != nil { + logrus.Error("unable to set squash-all flag to false") + } + flag.DefValue = "true" fromAndBugFlags := buildahcli.GetFromAndBudFlags(&fromAndBudValues, &userNSValues, &namespaceValues) flags.AddFlagSet(&budFlags) - flags.AddFlagSet(&layerFlags) flags.AddFlagSet(&fromAndBugFlags) + flags.AddFlagSet(&layerFlags) + flags.AddFlagSet(&podmanBuildFlags) markFlagHidden(flags, "signature-policy") } -func getDockerfiles(files []string) []string { - var dockerfiles []string +// GetPodmanBuildFlags flags used only by `podman build` and not by +// `buildah bud`. +func GetPodmanBuildFlags(flags *cliconfig.PodmanBuildResults) pflag.FlagSet { + fs := pflag.FlagSet{} + fs.BoolVar(&flags.SquashAll, "squash-all", false, "Squash all layers into a single layer.") + return fs +} + +func getContainerfiles(files []string) []string { + var containerfiles []string for _, f := range files { if f == "-" { - dockerfiles = append(dockerfiles, "/dev/stdin") + containerfiles = append(containerfiles, "/dev/stdin") } else { - dockerfiles = append(dockerfiles, f) + containerfiles = append(containerfiles, f) } } - return dockerfiles + return containerfiles } func getNsValues(c *cliconfig.BuildValues) ([]buildah.NamespaceOption, error) { @@ -119,6 +137,12 @@ func getNsValues(c *cliconfig.BuildValues) ([]buildah.NamespaceOption, error) { } func buildCmd(c *cliconfig.BuildValues) error { + if (c.Flags().Changed("squash") && c.Flags().Changed("layers")) || + (c.Flags().Changed("squash-all") && c.Flags().Changed("layers")) || + (c.Flags().Changed("squash-all") && c.Flags().Changed("squash")) { + return fmt.Errorf("cannot specify squash, squash-all and layers options together") + } + // The following was taken directly from containers/buildah/cmd/bud.go // TODO Find a away to vendor more of this in rather than copy from bud output := "" @@ -151,7 +175,7 @@ func buildCmd(c *cliconfig.BuildValues) error { } } - dockerfiles := getDockerfiles(c.File) + containerfiles := getContainerfiles(c.File) format, err := getFormat(&c.PodmanCommand) if err != nil { return nil @@ -190,31 +214,35 @@ func buildCmd(c *cliconfig.BuildValues) error { } } else { // No context directory or URL was specified. Try to use the - // home of the first locally-available Dockerfile. - for i := range dockerfiles { - if strings.HasPrefix(dockerfiles[i], "http://") || - strings.HasPrefix(dockerfiles[i], "https://") || - strings.HasPrefix(dockerfiles[i], "git://") || - strings.HasPrefix(dockerfiles[i], "github.com/") { + // home of the first locally-available Containerfile. + for i := range containerfiles { + if strings.HasPrefix(containerfiles[i], "http://") || + strings.HasPrefix(containerfiles[i], "https://") || + strings.HasPrefix(containerfiles[i], "git://") || + strings.HasPrefix(containerfiles[i], "github.com/") { continue } - absFile, err := filepath.Abs(dockerfiles[i]) + absFile, err := filepath.Abs(containerfiles[i]) if err != nil { - return errors.Wrapf(err, "error determining path to file %q", dockerfiles[i]) + return errors.Wrapf(err, "error determining path to file %q", containerfiles[i]) } contextDir = filepath.Dir(absFile) - dockerfiles[i], err = filepath.Rel(contextDir, absFile) + containerfiles[i], err = filepath.Rel(contextDir, absFile) if err != nil { - return errors.Wrapf(err, "error determining path to file %q", dockerfiles[i]) + return errors.Wrapf(err, "error determining path to file %q", containerfiles[i]) } break } } if contextDir == "" { - return errors.Errorf("no context directory specified, and no dockerfile specified") + return errors.Errorf("no context directory specified, and no containerfile specified") } - if len(dockerfiles) == 0 { - dockerfiles = append(dockerfiles, filepath.Join(contextDir, "Dockerfile")) + if len(containerfiles) == 0 { + if checkIfFileExists(filepath.Join(contextDir, "Containerfile")) { + containerfiles = append(containerfiles, filepath.Join(contextDir, "Containerfile")) + } else { + containerfiles = append(containerfiles, filepath.Join(contextDir, "Dockerfile")) + } } runtime, err := adapter.GetRuntime(getContext(), &c.PodmanCommand) @@ -289,6 +317,22 @@ func buildCmd(c *cliconfig.BuildValues) error { Volumes: c.Volumes, } + // `buildah bud --layers=false` acts like `docker build --squash` does. + // That is all of the new layers created during the build process are + // condensed into one, any layers present prior to this build are retained + // without condensing. `buildah bud --squash` squashes both new and old + // layers down into one. Translate Podman commands into Buildah. + // Squash invoked, retain old layers, squash new layers into one. + if c.Flags().Changed("squash") && c.Squash { + c.Squash = false + layers = false + } + // Squash-all invoked, squash both new and old layers into one. + if c.Flags().Changed("squash-all") { + c.Squash = true + layers = false + } + options := imagebuildah.BuildOptions{ CommonBuildOpts: &buildOpts, AdditionalTags: tags, @@ -318,7 +362,7 @@ func buildCmd(c *cliconfig.BuildValues) error { Squash: c.Squash, Target: c.Target, } - return runtime.Build(getContext(), c, options, dockerfiles) + return runtime.Build(getContext(), c, options, containerfiles) } // useLayers returns false if BUILDAH_LAYERS is set to "0" or "false" diff --git a/cmd/podman/cliconfig/config.go b/cmd/podman/cliconfig/config.go index 5b5225f02..86258a543 100644 --- a/cmd/podman/cliconfig/config.go +++ b/cmd/podman/cliconfig/config.go @@ -267,6 +267,7 @@ type MountValues struct { type NetworkCreateValues struct { PodmanCommand Driver string + DisableDNS bool Gateway net.IP Internal bool IPamDriver string @@ -651,6 +652,7 @@ type SystemRenumberValues struct { type SystemMigrateValues struct { PodmanCommand + NewRuntime string } type SystemDfValues struct { diff --git a/cmd/podman/cliconfig/create.go b/cmd/podman/cliconfig/create.go index 5fb2eed10..c27dfbbee 100644 --- a/cmd/podman/cliconfig/create.go +++ b/cmd/podman/cliconfig/create.go @@ -12,13 +12,20 @@ type RunValues struct { PodmanCommand } +// PodmanBuildResults represents the results for Podman Build flags +// that are unique to Podman. +type PodmanBuildResults struct { + SquashAll bool +} + type BuildValues struct { PodmanCommand *buildahcli.BudResults *buildahcli.UserNSResults *buildahcli.FromAndBudResults - *buildahcli.NameSpaceResults *buildahcli.LayerResults + *buildahcli.NameSpaceResults + *PodmanBuildResults } type CpValues struct { diff --git a/cmd/podman/cliconfig/defaults.go b/cmd/podman/cliconfig/defaults.go index d5dae0874..ce695d153 100644 --- a/cmd/podman/cliconfig/defaults.go +++ b/cmd/podman/cliconfig/defaults.go @@ -1,10 +1,5 @@ package cliconfig -const ( - // DefaultSystemD value - DefaultSystemD bool = true -) - var ( // DefaultHealthCheckInterval default value DefaultHealthCheckInterval = "30s" diff --git a/cmd/podman/common.go b/cmd/podman/common.go index 2a3f8f3ad..e93586b62 100644 --- a/cmd/podman/common.go +++ b/cmd/podman/common.go @@ -455,9 +455,9 @@ func getCreateFlags(c *cliconfig.PodmanCommand) { "sysctl", []string{}, "Sysctl options (default [])", ) - createFlags.Bool( - "systemd", cliconfig.DefaultSystemD, - "Run container in systemd mode if the command executable is systemd or init", + createFlags.String( + "systemd", "true", + `Run container in systemd mode ("true"|"false"|"always" (default "true")`, ) createFlags.StringArray( "tmpfs", []string{}, diff --git a/cmd/podman/images.go b/cmd/podman/images.go index fe7c89b5c..e363fa3bb 100644 --- a/cmd/podman/images.go +++ b/cmd/podman/images.go @@ -216,17 +216,18 @@ func (i imagesOptions) setOutputFormat() string { } // imagesToGeneric creates an empty array of interfaces for output -func imagesToGeneric(templParams []imagesTemplateParams, JSONParams []imagesJSONParams) (genericParams []interface{}) { +func imagesToGeneric(templParams []imagesTemplateParams, JSONParams []imagesJSONParams) []interface{} { + genericParams := []interface{}{} if len(templParams) > 0 { for _, v := range templParams { genericParams = append(genericParams, interface{}(v)) } - return + return genericParams } for _, v := range JSONParams { genericParams = append(genericParams, interface{}(v)) } - return + return genericParams } func sortImagesOutput(sortBy string, imagesOutput imagesSorted) imagesSorted { @@ -309,7 +310,8 @@ func getImagesTemplateOutput(ctx context.Context, images []*adapter.ContainerIma } // getImagesJSONOutput returns the images information in its raw form -func getImagesJSONOutput(ctx context.Context, images []*adapter.ContainerImage) (imagesOutput []imagesJSONParams) { +func getImagesJSONOutput(ctx context.Context, images []*adapter.ContainerImage) []imagesJSONParams { + imagesOutput := []imagesJSONParams{} for _, img := range images { size, err := img.Size(ctx) if err != nil { @@ -325,7 +327,7 @@ func getImagesJSONOutput(ctx context.Context, images []*adapter.ContainerImage) } imagesOutput = append(imagesOutput, params) } - return + return imagesOutput } // generateImagesOutput generates the images based on the format provided @@ -336,10 +338,6 @@ func generateImagesOutput(ctx context.Context, images []*adapter.ContainerImage, switch opts.format { case formats.JSONString: - // If 0 images are present, print nothing for JSON - if len(images) == 0 { - return nil - } imagesOutput := getImagesJSONOutput(ctx, images) out = formats.JSONStructArray{Output: imagesToGeneric([]imagesTemplateParams{}, imagesOutput)} default: diff --git a/cmd/podman/inspect.go b/cmd/podman/inspect.go index cff221cb0..872b59561 100644 --- a/cmd/podman/inspect.go +++ b/cmd/podman/inspect.go @@ -104,6 +104,9 @@ func inspectCmd(c *cliconfig.InspectValues) error { if strings.Contains(outputFormat, ".Dst") { outputFormat = strings.Replace(outputFormat, ".Dst", ".Destination", -1) } + if strings.Contains(outputFormat, ".ImageID") { + outputFormat = strings.Replace(outputFormat, ".ImageID", ".Image", -1) + } if latestContainer { lc, err := runtime.GetLatestContainer() if err != nil { diff --git a/cmd/podman/libpodruntime/runtime.go b/cmd/podman/libpodruntime/runtime.go index 6dafeb0b0..dd8c3f173 100644 --- a/cmd/podman/libpodruntime/runtime.go +++ b/cmd/podman/libpodruntime/runtime.go @@ -14,31 +14,31 @@ import ( ) // GetRuntimeMigrate gets a libpod runtime that will perform a migration of existing containers -func GetRuntimeMigrate(ctx context.Context, c *cliconfig.PodmanCommand) (*libpod.Runtime, error) { - return getRuntime(ctx, c, false, true, false, true) +func GetRuntimeMigrate(ctx context.Context, c *cliconfig.PodmanCommand, newRuntime string) (*libpod.Runtime, error) { + return getRuntime(ctx, c, false, true, false, true, newRuntime) } // GetRuntimeDisableFDs gets a libpod runtime that will disable sd notify func GetRuntimeDisableFDs(ctx context.Context, c *cliconfig.PodmanCommand) (*libpod.Runtime, error) { - return getRuntime(ctx, c, false, false, false, false) + return getRuntime(ctx, c, false, false, false, false, "") } // GetRuntimeRenumber gets a libpod runtime that will perform a lock renumber func GetRuntimeRenumber(ctx context.Context, c *cliconfig.PodmanCommand) (*libpod.Runtime, error) { - return getRuntime(ctx, c, true, false, false, true) + return getRuntime(ctx, c, true, false, false, true, "") } // GetRuntime generates a new libpod runtime configured by command line options func GetRuntime(ctx context.Context, c *cliconfig.PodmanCommand) (*libpod.Runtime, error) { - return getRuntime(ctx, c, false, false, false, true) + return getRuntime(ctx, c, false, false, false, true, "") } // GetRuntimeNoStore generates a new libpod runtime configured by command line options func GetRuntimeNoStore(ctx context.Context, c *cliconfig.PodmanCommand) (*libpod.Runtime, error) { - return getRuntime(ctx, c, false, false, true, true) + return getRuntime(ctx, c, false, false, true, true, "") } -func getRuntime(ctx context.Context, c *cliconfig.PodmanCommand, renumber, migrate, noStore, withFDS bool) (*libpod.Runtime, error) { +func getRuntime(ctx context.Context, c *cliconfig.PodmanCommand, renumber, migrate, noStore, withFDS bool, newRuntime string) (*libpod.Runtime, error) { options := []libpod.RuntimeOption{} storageOpts := storage.StoreOptions{} storageSet := false @@ -88,6 +88,9 @@ func getRuntime(ctx context.Context, c *cliconfig.PodmanCommand, renumber, migra } if migrate { options = append(options, libpod.WithMigrate()) + if newRuntime != "" { + options = append(options, libpod.WithMigrateRuntime(newRuntime)) + } } if renumber { diff --git a/cmd/podman/main_local.go b/cmd/podman/main_local.go index bdffb6b1e..202d93b35 100644 --- a/cmd/podman/main_local.go +++ b/cmd/podman/main_local.go @@ -174,14 +174,13 @@ func setupRootless(cmd *cobra.Command, args []string) error { if err != nil { return err } - + conf, err := runtime.GetConfig() + if err != nil { + return err + } if !ownsCgroup { unitName := fmt.Sprintf("podman-%d.scope", os.Getpid()) if err := utils.RunUnderSystemdScope(os.Getpid(), "user.slice", unitName); err != nil { - conf, err2 := runtime.GetConfig() - if err2 != nil { - return err2 - } if conf.CgroupManager == libpod.SystemdCgroupsManager { logrus.Warnf("Failed to add podman to systemd sandbox cgroup: %v", err) } else { diff --git a/cmd/podman/network_create.go b/cmd/podman/network_create.go index 11f13faad..6710883ae 100644 --- a/cmd/podman/network_create.go +++ b/cmd/podman/network_create.go @@ -46,7 +46,7 @@ func init() { // TODO enable when IPv6 is working //flags.BoolVar(&networkCreateCommand.IPV6, "IPv6", false, "enable IPv6 networking") flags.IPNetVar(&networkCreateCommand.Network, "subnet", net.IPNet{}, "subnet in CIDR format") - + flags.BoolVar(&networkCreateCommand.DisableDNS, "disable-dns", false, "disable dns plugin") } func networkcreateCmd(c *cliconfig.NetworkCreateValues) error { diff --git a/cmd/podman/run.go b/cmd/podman/run.go index 4836c99dc..7aa4cb3c4 100644 --- a/cmd/podman/run.go +++ b/cmd/podman/run.go @@ -45,7 +45,6 @@ func runCmd(c *cliconfig.RunValues) error { span, _ := opentracing.StartSpanFromContext(Ctx, "runCmd") defer span.Finish() } - if err := createInit(&c.PodmanCommand); err != nil { return err } diff --git a/cmd/podman/shared/container.go b/cmd/podman/shared/container.go index 022377b1f..15bbb46d2 100644 --- a/cmd/podman/shared/container.go +++ b/cmd/podman/shared/container.go @@ -449,10 +449,8 @@ func GetPsContainerOutput(r *libpod.Runtime, opts PsOptions, filters []string, m // PBatch performs batch operations on a container in parallel. It spawns the // number of workers relative to the number of parallel operations desired. func PBatch(containers []*libpod.Container, workers int, opts PsOptions) []PsContainerOutput { - var ( - wg sync.WaitGroup - psResults []PsContainerOutput - ) + var wg sync.WaitGroup + psResults := []PsContainerOutput{} // If the number of containers in question is less than the number of // proposed parallel operations, we shouldnt spawn so many workers. diff --git a/cmd/podman/shared/create.go b/cmd/podman/shared/create.go index 9020613c5..bf9410b72 100644 --- a/cmd/podman/shared/create.go +++ b/cmd/podman/shared/create.go @@ -662,9 +662,17 @@ func ParseCreateOpts(ctx context.Context, c *GenericCLIResults, runtime *libpod. return nil, errors.Errorf("invalid image-volume type %q. Pick one of bind, tmpfs, or ignore", c.String("image-volume")) } - var systemd bool - if command != nil && c.Bool("systemd") && ((filepath.Base(command[0]) == "init") || (filepath.Base(command[0]) == "systemd")) { - systemd = true + systemd := c.String("systemd") == "always" + if !systemd && command != nil { + x, err := strconv.ParseBool(c.String("systemd")) + if err != nil { + return nil, errors.Wrapf(err, "cannot parse bool %s", c.String("systemd")) + } + if x && (command[0] == "/usr/sbin/init" || command[0] == "/sbin/init" || (filepath.Base(command[0]) == "systemd")) { + systemd = true + } + } + if systemd { if signalString == "" { stopSignal, err = signal.ParseSignal("RTMIN+3") if err != nil { diff --git a/cmd/podman/shared/funcs.go b/cmd/podman/shared/funcs.go index bb4eed1e3..9362e8e9b 100644 --- a/cmd/podman/shared/funcs.go +++ b/cmd/podman/shared/funcs.go @@ -21,7 +21,7 @@ func GetAuthFile(authfile string) string { } if runtimeDir, err := util.GetRuntimeDir(); err == nil { - return filepath.Join(runtimeDir, "auth.json") + return filepath.Join(runtimeDir, "containers/auth.json") } return "" } diff --git a/cmd/podman/shared/intermediate.go b/cmd/podman/shared/intermediate.go index cccdd1bea..0f71dc087 100644 --- a/cmd/podman/shared/intermediate.go +++ b/cmd/podman/shared/intermediate.go @@ -449,7 +449,7 @@ func NewIntermediateLayer(c *cliconfig.PodmanCommand, remote bool) GenericCLIRes m["subgidname"] = newCRString(c, "subgidname") m["subuidname"] = newCRString(c, "subuidname") m["sysctl"] = newCRStringSlice(c, "sysctl") - m["systemd"] = newCRBool(c, "systemd") + m["systemd"] = newCRString(c, "systemd") m["tmpfs"] = newCRStringArray(c, "tmpfs") m["tty"] = newCRBool(c, "tty") m["uidmap"] = newCRStringSlice(c, "uidmap") diff --git a/cmd/podman/shared/intermediate_varlink.go b/cmd/podman/shared/intermediate_varlink.go index 9dbf83950..c95470a72 100644 --- a/cmd/podman/shared/intermediate_varlink.go +++ b/cmd/podman/shared/intermediate_varlink.go @@ -152,7 +152,7 @@ func (g GenericCLIResults) MakeVarlink() iopodman.Create { Subuidname: StringToPtr(g.Find("subuidname")), Subgidname: StringToPtr(g.Find("subgidname")), Sysctl: StringSliceToPtr(g.Find("sysctl")), - Systemd: BoolToPtr(g.Find("systemd")), + Systemd: StringToPtr(g.Find("systemd")), Tmpfs: StringSliceToPtr(g.Find("tmpfs")), Tty: BoolToPtr(g.Find("tty")), Uidmap: StringSliceToPtr(g.Find("uidmap")), @@ -321,6 +321,7 @@ func VarlinkCreateToGeneric(opts iopodman.Create) GenericCLIResults { var memSwapDefault int64 = -1 netModeDefault := "bridge" + systemdDefault := "true" if rootless.IsRootless() { netModeDefault = "slirp4netns" } @@ -409,7 +410,7 @@ func VarlinkCreateToGeneric(opts iopodman.Create) GenericCLIResults { m["subgidname"] = stringFromVarlink(opts.Subgidname, "subgidname", nil) m["subuidname"] = stringFromVarlink(opts.Subuidname, "subuidname", nil) m["sysctl"] = stringSliceFromVarlink(opts.Sysctl, "sysctl", nil) - m["systemd"] = boolFromVarlink(opts.Systemd, "systemd", cliconfig.DefaultSystemD) + m["systemd"] = stringFromVarlink(opts.Systemd, "systemd", &systemdDefault) m["tmpfs"] = stringSliceFromVarlink(opts.Tmpfs, "tmpfs", nil) m["tty"] = boolFromVarlink(opts.Tty, "tty", false) m["uidmap"] = stringSliceFromVarlink(opts.Uidmap, "uidmap", nil) diff --git a/cmd/podman/shared/volumes_shared.go b/cmd/podman/shared/volumes_shared.go index 912615cad..74c0ce011 100644 --- a/cmd/podman/shared/volumes_shared.go +++ b/cmd/podman/shared/volumes_shared.go @@ -2,8 +2,13 @@ package shared import ( "context" + "strconv" + "strings" "github.com/containers/libpod/libpod" + "github.com/containers/libpod/libpod/define" + "github.com/pkg/errors" + "github.com/sirupsen/logrus" ) // Remove given set of volumes @@ -45,3 +50,60 @@ func SharedRemoveVolumes(ctx context.Context, runtime *libpod.Runtime, vols []st return success, failed, nil } + +// Handle volume options from CLI. +// Parse "o" option to find UID, GID. +func ParseVolumeOptions(opts map[string]string) ([]libpod.VolumeCreateOption, error) { + libpodOptions := []libpod.VolumeCreateOption{} + volumeOptions := make(map[string]string) + + for key, value := range opts { + switch key { + case "o": + // o has special handling to parse out UID, GID. + // These are separate Libpod options. + splitVal := strings.Split(value, ",") + finalVal := []string{} + for _, o := range splitVal { + // Options will be formatted as either "opt" or + // "opt=value" + splitO := strings.SplitN(o, "=", 2) + switch strings.ToLower(splitO[0]) { + case "uid": + if len(splitO) != 2 { + return nil, errors.Wrapf(define.ErrInvalidArg, "uid option must provide a UID") + } + intUID, err := strconv.Atoi(splitO[1]) + if err != nil { + return nil, errors.Wrapf(err, "cannot convert UID %s to integer", splitO[1]) + } + logrus.Debugf("Removing uid= from options and adding WithVolumeUID for UID %d", intUID) + libpodOptions = append(libpodOptions, libpod.WithVolumeUID(intUID)) + case "gid": + if len(splitO) != 2 { + return nil, errors.Wrapf(define.ErrInvalidArg, "gid option must provide a GID") + } + intGID, err := strconv.Atoi(splitO[1]) + if err != nil { + return nil, errors.Wrapf(err, "cannot convert GID %s to integer", splitO[1]) + } + logrus.Debugf("Removing gid= from options and adding WithVolumeGID for GID %d", intGID) + libpodOptions = append(libpodOptions, libpod.WithVolumeGID(intGID)) + default: + finalVal = append(finalVal, o) + } + } + if len(finalVal) > 0 { + volumeOptions[key] = strings.Join(finalVal, ",") + } + default: + volumeOptions[key] = value + } + } + + if len(volumeOptions) > 0 { + libpodOptions = append(libpodOptions, libpod.WithVolumeOptions(volumeOptions)) + } + + return libpodOptions, nil +} diff --git a/cmd/podman/stats.go b/cmd/podman/stats.go index 25514ec75..f8c476386 100644 --- a/cmd/podman/stats.go +++ b/cmd/podman/stats.go @@ -35,7 +35,7 @@ var ( statsDescription = "Display percentage of CPU, memory, network I/O, block I/O and PIDs for one or more containers." _statsCommand = &cobra.Command{ - Use: "stats [flags] CONTAINER [CONTAINER...]", + Use: "stats [flags] [CONTAINER...]", Short: "Display a live stream of container resource usage statistics", Long: statsDescription, RunE: func(cmd *cobra.Command, args []string) error { @@ -44,9 +44,6 @@ var ( statsCommand.Remote = remoteclient return statsCmd(&statsCommand) }, - Args: func(cmd *cobra.Command, args []string) error { - return checkAllAndLatest(cmd, args, false) - }, Example: `podman stats --all --no-stream podman stats ctrID podman stats --no-stream --format "table {{.ID}} {{.Name}} {{.MemUsage}}" ctrID`, @@ -92,8 +89,6 @@ func statsCmd(c *cliconfig.StatsValues) error { if ctr > 1 { return errors.Errorf("--all, --latest and containers cannot be used together") - } else if ctr == 0 { - return errors.Errorf("you must specify --all, --latest, or at least one container") } runtime, err := libpodruntime.GetRuntime(getContext(), &c.PodmanCommand) diff --git a/cmd/podman/system_migrate.go b/cmd/podman/system_migrate.go index 4a0afcfad..9c90aeb52 100644 --- a/cmd/podman/system_migrate.go +++ b/cmd/podman/system_migrate.go @@ -32,13 +32,15 @@ func init() { migrateCommand.Command = _migrateCommand migrateCommand.SetHelpTemplate(HelpTemplate()) migrateCommand.SetUsageTemplate(UsageTemplate()) + flags := migrateCommand.Flags() + flags.StringVar(&migrateCommand.NewRuntime, "new-runtime", "", "Specify a new runtime for all containers") } func migrateCmd(c *cliconfig.SystemMigrateValues) error { // We need to pass one extra option to NewRuntime. // This will inform the OCI runtime to start a migrate. // That's controlled by the last argument to GetRuntime. - r, err := libpodruntime.GetRuntimeMigrate(getContext(), &c.PodmanCommand) + r, err := libpodruntime.GetRuntimeMigrate(getContext(), &c.PodmanCommand, c.NewRuntime) if err != nil { return errors.Wrapf(err, "error migrating containers") } diff --git a/cmd/podman/utils.go b/cmd/podman/utils.go index c0ddaba4e..592d7a1d1 100644 --- a/cmd/podman/utils.go +++ b/cmd/podman/utils.go @@ -2,6 +2,7 @@ package main import ( "fmt" + "os" "reflect" "runtime/debug" @@ -63,3 +64,12 @@ func aliasFlags(f *pflag.FlagSet, name string) pflag.NormalizedName { } return pflag.NormalizedName(name) } + +// Check if a file exists and is not a directory +func checkIfFileExists(name string) bool { + file, err := os.Stat(name) + if os.IsNotExist(err) { + return false + } + return !file.IsDir() +} diff --git a/cmd/podman/varlink/io.podman.varlink b/cmd/podman/varlink/io.podman.varlink index 2408dc80c..dca366bc5 100644 --- a/cmd/podman/varlink/io.podman.varlink +++ b/cmd/podman/varlink/io.podman.varlink @@ -363,7 +363,7 @@ type Create ( subuidname: ?string, subgidname: ?string, sysctl: ?[]string, - systemd: ?bool, + systemd: ?string, tmpfs: ?[]string, tty: ?bool, uidmap: ?[]string, @@ -1268,6 +1268,10 @@ method VolumeRemove(options: VolumeRemoveOpts) -> (successes: []string, failures # GetVolumes gets slice of the volumes on a remote host method GetVolumes(args: []string, all: bool) -> (volumes: []Volume) +# InspectVolume inspects a single volume. Returns inspect JSON in the form of a +# string. +method InspectVolume(name: string) -> (volume: string) + # VolumesPrune removes unused volumes on the host method VolumesPrune() -> (prunedNames: []string, prunedErrors: []string) diff --git a/cmd/podman/volume_create.go b/cmd/podman/volume_create.go index 617f701a4..e5a576749 100644 --- a/cmd/podman/volume_create.go +++ b/cmd/podman/volume_create.go @@ -37,7 +37,7 @@ func init() { flags := volumeCreateCommand.Flags() flags.StringVar(&volumeCreateCommand.Driver, "driver", "", "Specify volume driver name (default local)") flags.StringSliceVarP(&volumeCreateCommand.Label, "label", "l", []string{}, "Set metadata for a volume (default [])") - flags.StringSliceVarP(&volumeCreateCommand.Opt, "opt", "o", []string{}, "Set driver specific options (default [])") + flags.StringArrayVarP(&volumeCreateCommand.Opt, "opt", "o", []string{}, "Set driver specific options (default [])") } func volumeCreateCmd(c *cliconfig.VolumeCreateValues) error { diff --git a/completions/bash/podman b/completions/bash/podman index 4bc387871..0abf9e738 100644 --- a/completions/bash/podman +++ b/completions/bash/podman @@ -982,6 +982,7 @@ _podman_network_create() { --subnet " local boolean_options=" + --disable-dns --help -h --internal @@ -1246,6 +1247,7 @@ _podman_build() { -q --rm --squash + --squash-all --tls-verify " diff --git a/contrib/spec/podman.spec.in b/contrib/spec/podman.spec.in index bd2cff3f6..d5247f689 100644 --- a/contrib/spec/podman.spec.in +++ b/contrib/spec/podman.spec.in @@ -39,7 +39,7 @@ %global shortcommit_conmon %(c=%{commit_conmon}; echo ${c:0:7}) Name: podman -Version: 1.6.2 +Version: 1.6.3 Release: #COMMITDATE#.git%{shortcommit0}%{?dist} Summary: Manage Pods, Containers and Container Images License: ASL 2.0 diff --git a/docs/podman-build.1.md b/docs/podman-build.1.md index 1a04f8224..567d0ead3 100644 --- a/docs/podman-build.1.md +++ b/docs/podman-build.1.md @@ -1,23 +1,25 @@ % podman-build(1) ## NAME -podman\-build - Build a container image using a Dockerfile +podman\-build - Build a container image using a Containerfile ## SYNOPSIS -**podman build** [*options*] *context* +**podman build** [*options*] [*context*] -**podman image build** [*options*] *context* +**podman image build** [*options*] [*context*] ## DESCRIPTION -**podman build** Builds an image using instructions from one or more Dockerfiles and a specified build context directory. +**podman build** Builds an image using instructions from one or more Containerfiles or Dockerfiles and a specified build context directory. A Containerfile uses the same syntax as a Dockerfile internally. For this document, a file referred to as a Containerfile can be a file named either 'Containerfile' or 'Dockerfile'. -The build context directory can be specified as the http(s) URL of an archive, git repository or Dockerfile. +The build context directory can be specified as the http(s) URL of an archive, git repository or Containerfile. -Dockerfiles ending with a ".in" suffix will be preprocessed via CPP(1). This can be useful to decompose Dockerfiles into several reusable parts that can be used via CPP's **#include** directive. Notice, a Dockerfile.in file can still be used by other tools when manually preprocessing them via `cpp -E`. +If no context directory is specified, then Podman will assume the current working directory as the build context, which should contain the Containerfile. + +Containerfiles ending with a ".in" suffix will be preprocessed via CPP(1). This can be useful to decompose Containerfiles into several reusable parts that can be used via CPP's **#include** directive. Notice, a Containerfile.in file can still be used by other tools when manually preprocessing them via `cpp -E`. When the URL is an archive, the contents of the URL is downloaded to a temporary location and extracted before execution. -When the URL is an Dockerfile, the Dockerfile is downloaded to a temporary location. +When the URL is an Containerfile, the Containerfile is downloaded to a temporary location. When a Git repository is set as the URL, the repository is cloned locally and then set as the context. @@ -46,7 +48,7 @@ environment variable. `export REGISTRY_AUTH_FILE=path` **--build-arg**=*arg=value* Specifies a build argument and its value, which will be interpolated in -instructions read from the Dockerfiles in the same way that environment +instructions read from the Containerfiles in the same way that environment variables are, but which will not be added to environment variable list in the resulting image's configuration. @@ -170,6 +172,10 @@ The [username[:password]] to use to authenticate with the registry if required. If one or both values are not supplied, a command line prompt will appear and the value can be entered. The password is entered without echo. +**--device**=*device* + +Add a host device to the container. The format is `<device-on-host>[:<device-on-container>][:<permissions>]` (e.g. --device=/dev/sdc:/dev/xvdc:rwm) + **--disable-compression, -D** Don't compress filesystem layers when building the image unless it is required @@ -201,22 +207,22 @@ Set custom DNS options Set custom DNS search domains -**--file**, **-f**=*Dockerfile* +**--file**, **-f**=*Containerfile* -Specifies a Dockerfile which contains instructions for building the image, +Specifies a Containerfile which contains instructions for building the image, either a local file or an **http** or **https** URL. If more than one -Dockerfile is specified, *FROM* instructions will only be accepted from the +Containerfile is specified, *FROM* instructions will only be accepted from the first specified file. -If a build context is not specified, and at least one Dockerfile is a +If a build context is not specified, and at least one Containerfile is a local file, the directory in which it resides will be used as the build context. -If you specify `-f -`, the Dockerfile contents will be read from stdin. +If you specify `-f -`, the Containerfile contents will be read from stdin. **--force-rm**=*true|false* -Always remove intermediate containers after a build, even if the build is unsuccessful. +Always remove intermediate containers after a build, even if the build fails (default false). **--format** @@ -368,7 +374,8 @@ environment variable. `export BUILDAH_RUNTIME=/usr/local/bin/runc` Adds global flags for the container runtime. To list the supported flags, please consult the manpages of the selected container runtime (`runc` is the default -runtime, the manpage to consult is `runc(8)`). +runtime, the manpage to consult is `runc(8)`. When the machine is configured +for cgroup V2, the default runtime is `crun`, the manpage to consult is `crun(8)`.). Note: Do not pass the leading `--` to the flag. To pass the runc flag `--log-format json` to podman build, the option given would be `--runtime-flag log-format=json`. @@ -398,6 +405,11 @@ If you omit the unit, the system uses bytes. If you omit the size entirely, the **--squash** +Squash all of the image's new layers into a single new layer; any preexisting layers +are not squashed. + +**--squash-all** + Squash all of the new image's layers (including those inherited from a base image) into a single new layer. **--tag**, **-t**=*imageName* @@ -408,7 +420,7 @@ If _imageName_ does not include a registry name, the registry name *localhost* w **--target**=*stageName* -Set the target build stage to build. When building a Dockerfile with multiple build stages, --target +Set the target build stage to build. When building a Containerfile with multiple build stages, --target can be used to specify an intermediate build stage by name as the final stage for the resulting image. Commands after the target stage will be skipped. @@ -526,7 +538,7 @@ process. container. The `OPTIONS` are a comma delimited list and can be: * [rw|ro] - * [z|Z] + * [z|Z|O] * [`[r]shared`|`[r]slave`|`[r]private`] The `CONTAINER-DIR` must be an absolute path such as `/src/docs`. The `HOST-DIR` @@ -559,7 +571,7 @@ Only the current container can use a private volume. `Overlay Volume Mounts` - The `:O` flag tells Buildah to mount the directory from the host as a temporary storage using the Overlay file system. The `RUN` command containers are allowed to modify contents within the mountpoint and are stored in the container storage in a separate directory. In Overlay FS terms the source directory will be the lower, and the container storage directory will be the upper. Modifications to the mount point are destroyed when the `RUN` command finishes executing, similar to a tmpfs mount point. + The `:O` flag tells Podman to mount the directory from the host as a temporary storage using the Overlay file system. The `RUN` command containers are allowed to modify contents within the mountpoint and are stored in the container storage in a separate directory. In Overlay FS terms the source directory will be the lower, and the container storage directory will be the upper. Modifications to the mount point are destroyed when the `RUN` command finishes executing, similar to a tmpfs mount point. Any subsequent execution of `RUN` commands sees the original source directory content, any changes from previous RUN commands no longer exists. @@ -605,16 +617,16 @@ mount can be changed directly. For instance if `/` is the source mount for ## EXAMPLES -### Build an image using local Dockerfiles +### Build an image using local Containerfiles ``` $ podman build . -$ podman build -f Dockerfile.simple . +$ podman build -f Containerfile.simple . $ cat ~/Dockerfile | podman build -f - . -$ podman build -f Dockerfile.simple -f Dockerfile.notsosimple . +$ podman build -f Dockerfile.simple -f Containerfile.notsosimple . $ podman build -f Dockerfile.in ~ @@ -649,19 +661,19 @@ $ podman build --no-cache --rm=false -t imageName . ### Building an image using a URL, Git repo, or archive - The build context directory can be specified as a URL to a Dockerfile, a Git repository, or URL to an archive. If the URL is a Dockerfile, it is downloaded to a temporary location and used as the context. When a Git repository is set as the URL, the repository is cloned locally to a temporary location and then used as the context. Lastly, if the URL is an archive, it is downloaded to a temporary location and extracted before being used as the context. + The build context directory can be specified as a URL to a Containerfile, a Git repository, or URL to an archive. If the URL is a Containerfile, it is downloaded to a temporary location and used as the context. When a Git repository is set as the URL, the repository is cloned locally to a temporary location and then used as the context. Lastly, if the URL is an archive, it is downloaded to a temporary location and extracted before being used as the context. -#### Building an image using a URL to a Dockerfile +#### Building an image using a URL to a Containerfile - Podman will download the Dockerfile to a temporary location and then use it as the build context. + Podman will download the Containerfile to a temporary location and then use it as the build context. ``` -$ podman build https://10.10.10.1/podman/Dockerfile +$ podman build https://10.10.10.1/podman/Containerfile ``` #### Building an image using a Git repository - Podman will clone the specified GitHub repository to a temporary location and use it as the context. The Dockerfile at the root of the repository will be used and it only works if the GitHub repository is a dedicated repository. + Podman will clone the specified GitHub repository to a temporary location and use it as the context. The Containerfile at the root of the repository will be used and it only works if the GitHub repository is a dedicated repository. ``` $ podman build git://github.com/scollier/purpletest @@ -669,10 +681,10 @@ $ podman build git://github.com/scollier/purpletest #### Building an image using a URL to an archive - Podman will fetch the archive file, decompress it, and use its contents as the build context. The Dockerfile at the root of the archive and the rest of the archive will get used as the context of the build. If you pass `-f PATH/Dockerfile` option as well, the system will look for that file inside the contents of the archive. + Podman will fetch the archive file, decompress it, and use its contents as the build context. The Containerfile at the root of the archive and the rest of the archive will get used as the context of the build. If you pass `-f PATH/Containerfile` option as well, the system will look for that file inside the contents of the archive. ``` -$ podman build -f dev/Dockerfile https://10.10.10.1/podman/context.tar.gz +$ podman build -f dev/Containerfile https://10.10.10.1/podman/context.tar.gz ``` Note: supported compression formats are 'xz', 'bzip2', 'gzip' and 'identity' (no compression). @@ -685,14 +697,14 @@ registries.conf is the configuration file which specifies which container regist ## Troubleshooting -If you are using a useradd command within a Dockerfile with a large UID/GID, it will create a large sparse file `/var/log/lastlog`. This can cause the build to hang forever. Go language does not support sparse files correctly, which can lead to some huge files being created in your container image. +If you are using a useradd command within a Containerfile with a large UID/GID, it will create a large sparse file `/var/log/lastlog`. This can cause the build to hang forever. Go language does not support sparse files correctly, which can lead to some huge files being created in your container image. ### Solution If you are using `useradd` within your build script, you should pass the `--no-log-init or -l` option to the `useradd` command. This option tells useradd to stop creating the lastlog file. ## SEE ALSO -podman(1), buildah(1), containers-registries.conf(5), useradd(8) +podman(1), buildah(1), containers-registries.conf(5), crun(8), runc(8), useradd(8) ## HISTORY May 2018, Minor revisions added by Joe Doss <joe@solidadmin.com> diff --git a/docs/podman-create.1.md b/docs/podman-create.1.md index 46fa4fcd4..6617850fd 100644 --- a/docs/podman-create.1.md +++ b/docs/podman-create.1.md @@ -515,13 +515,16 @@ This works for both background and foreground containers. **--network**, **--net**="*bridge*" Set the Network mode for the container. Invalid if using **--dns**, **--dns-option**, or **--dns-search** with **--network** that is set to 'none' or 'container:<name|id>'. - 'bridge': create a network stack on the default bridge - 'none': no networking - 'container:<name|id>': reuse another container's network stack - 'host': use the Podman host network stack. Note: the host mode gives the container full access to local system services such as D-bus and is therefore considered insecure. - '<network-name>|<network-id>': connect to a user-defined network - 'ns:<path>': path to a network namespace to join - 'slirp4netns': use slirp4netns to create a user network stack. This is the default for rootless containers + +Valid values are: + +- `bridge`: create a network stack on the default bridge +- `none`: no networking +- `container:<name|id>`: reuse another container's network stack +- `host`: use the Podman host network stack. Note: the host mode gives the container full access to local system services such as D-bus and is therefore considered insecure. +- `<network-name>|<network-id>`: connect to a user-defined network, multiple networks should be comma separated +- `ns:<path>`: path to a network namespace to join +- `slirp4netns`: use slirp4netns to create a user network stack. This is the default for rootless containers **--network-alias**=*alias* @@ -626,6 +629,7 @@ If container is running in --read-only mode, then mount a read-write tmpfs on /r Restart policy to follow when containers exit. Restart policy will not take effect if a container is stopped via the `podman kill` or `podman stop` commands. + Valid values are: - `no` : Do not restart containers on exit @@ -710,12 +714,17 @@ Network Namespace - current sysctls allowed: Note: if you use the --network=host option these sysctls will not be allowed. -**--systemd**=*true|false* +**--systemd**=*true|false|always* Run container in systemd mode. The default is *true*. -If the command you running inside of the container is systemd or init, podman -will setup tmpfs mount points in the following directories: +The value *always* enforces the systemd mode is enforced without +looking at the executable name. Otherwise, if set to true and the +command you are running inside the container is systemd, /usr/sbin/init +or /sbin/init. + +If the command you are running inside of the container is systemd, +Podman will setup tmpfs mount points in the following directories: /run, /run/lock, /tmp, /sys/fs/cgroup/systemd, /var/lib/journal @@ -795,7 +804,7 @@ Set the UTS mode for the container **ns**: specify the user namespace to use. Note: the host mode gives the container access to changing the host's hostname and is therefore considered insecure. -**--volume**, **-v**[=*[HOST-DIR:CONTAINER-DIR[:OPTIONS]]*] +**--volume**, **-v**[=*[[SOURCE-VOLUME|HOST-DIR:]CONTAINER-DIR[:OPTIONS]]*] Create a bind mount. If you specify, ` -v /HOST-DIR:/CONTAINER-DIR`, podman bind mounts `/HOST-DIR` in the host to `/CONTAINER-DIR` in the podman @@ -805,11 +814,23 @@ container. The `OPTIONS` are a comma delimited list and can be: * [z|Z] * [`[r]shared`|`[r]slave`|`[r]private`] -The `CONTAINER-DIR` must be an absolute path such as `/src/docs`. The `HOST-DIR` -must be an absolute path as well. Podman bind-mounts the `HOST-DIR` to the -path you specify. For example, if you supply the `/foo` value, Podman creates a bind-mount. +The `CONTAINER-DIR` must be an absolute path such as `/src/docs`. The volume +will be mounted into the container at this directory. + +Volumes may specify a source as well, as either a directory on the host or the +name of a named volume. If no source is given, the volume will be created as an +anonymous named volume with a randomly generated name, and will be removed when +the container is removed via the `--rm` flag or `podman rm --volumes`. + +If a volume source is specified, it must be a path on the host or the name of a +named volume. Host paths are allowed to be absolute or relative; relative paths +are resolved relative to the directory Podman is run in. Any source that does +not begin with a `.` or `/` it will be treated as the name of a named volume. +If a volume with that name does not exist, it will be created. Volumes created +with names are not anonymous and are not removed by `--rm` and +`podman rm --volumes`. -You can specify multiple **-v** options to mount one or more mounts to a +You can specify multiple **-v** options to mount one or more volumes into a container. You can add `:ro` or `:rw` suffix to a volume to mount it read-only or diff --git a/docs/podman-derivative-api b/docs/podman-derivative-api new file mode 100644 index 000000000..1b6153df5 --- /dev/null +++ b/docs/podman-derivative-api @@ -0,0 +1,64 @@ +.TH How to use libpod for custom/derivative projects +.PP +libpod today is a Golang library and a CLI. The choice of interface you make has advantages and disadvantages. + +.SH Running as a subprocess +.PP +Advantages: + +.RS +.IP \(bu 2 +Many commands output JSON +.IP \(bu 2 +Works with languages other than Golang +.IP \(bu 2 +Easy to get started + +.RE + +.PP +Disadvantages: + +.RS +.IP \(bu 2 +Error handling is harder +.IP \(bu 2 +May be slower +.IP \(bu 2 +Can't hook into or control low\-level things like how images are pulled + +.RE + +.SH Vendoring into a Go project +.PP +Advantages: + +.RS +.IP \(bu 2 +Significant power and control + +.RE + +.PP +Disadvantages: + +.RS +.IP \(bu 2 +You are now on the hook for container runtime security updates (partially, \fB\fCrunc\fR/\fB\fCcrun\fR are separate) +.IP \(bu 2 +Binary size +.IP \(bu 2 +Potential skew between multiple libpod versions operating on the same storage can cause problems + +.RE + +.SH Varlink +.PP +Some code exists for this; splits the difference. Future uncertain. + +.SH Making the choice +.PP +A good question to ask first is: Do you want users to be able to use \fB\fCpodman\fR to manipulate the containers created by your project? +If so, that makes it more likely that you want to run \fB\fCpodman\fR as a subprocess. If you want a separate image store and a fundamentally +different experience; if what you're doing with containers is quite different from those created by the \fB\fCpodman\fR CLI, +that may drive you towards vendoring. diff --git a/docs/podman-network-create.1.md b/docs/podman-network-create.1.md index 0679d8ee2..c281d50d9 100644 --- a/docs/podman-network-create.1.md +++ b/docs/podman-network-create.1.md @@ -15,6 +15,11 @@ If no options are provided, Podman will assign a free subnet and name for your n Upon completion of creating the network, Podman will display the path to the newly added network file. ## OPTIONS +**--disable-dns** + +Disables the DNS plugin for this network which if enabled, can perform container to container name +resolution. + **-d**, , **--driver** Driver to manage the network (default "bridge"). Currently on `bridge` is supported. diff --git a/docs/podman-run.1.md b/docs/podman-run.1.md index dfc634288..d6d8f4c1e 100644 --- a/docs/podman-run.1.md +++ b/docs/podman-run.1.md @@ -289,7 +289,7 @@ on the host system. Run the container in a new user namespace using the supplied mapping. This option conflicts with the --userns and --subgidname flags. This option can be passed several times to map different ranges. If calling Podman run as an unprivileged user, the user needs to have the right to use the mapping. See `subuid(5)`. -The example maps gids 0-2000 in the container to the gids 30000-31999 on the host. `--gidmap=0:30000:2000` +The example maps gids 0-1999 in the container to the gids 30000-31999 on the host. `--gidmap=0:30000:2000` **--group-add**=*group* @@ -526,11 +526,14 @@ This works for both background and foreground containers. **--network**, **--net**=*node* Set the Network mode for the container. Invalid if using **--dns**, **--dns-option**, or **--dns-search** with **--network** that is set to 'none' or 'container:<name|id>'. + +Valid values are: + - `bridge`: create a network stack on the default bridge - `none`: no networking - `container:<name|id>`: reuse another container's network stack - `host`: use the Podman host network stack. Note: the host mode gives the container full access to local system services such as D-bus and is therefore considered insecure. -- `<network-name>|<network-id>`: connect to a user-defined network +- `<network-name>|<network-id>`: connect to a user-defined network, multiple networks should be comma separated - `ns:<path>`: path to a network namespace to join - `slirp4netns`: use slirp4netns to create a user network stack. This is the default for rootless containers @@ -645,6 +648,7 @@ If container is running in --read-only mode, then mount a read-write tmpfs on /r Restart policy to follow when containers exit. Restart policy will not take effect if a container is stopped via the `podman kill` or `podman stop` commands. + Valid values are: - `no` : Do not restart containers on exit @@ -747,12 +751,17 @@ Network Namespace - current sysctls allowed: Note: if you use the `--network=host` option these sysctls will not be allowed. -**--systemd**=*true|false* +**--systemd**=*true|false|always* Run container in systemd mode. The default is *true*. -If the command you are running inside of the container is systemd or init, Podman -will setup tmpfs mount points in the following directories: +The value *always* enforces the systemd mode is enforced without +looking at the executable name. Otherwise, if set to true and the +command you are running inside the container is systemd, /usr/sbin/init +or /sbin/init. + +If the command you are running inside of the container is systemd +Podman will setup tmpfs mount points in the following directories: /run, /run/lock, /tmp, /sys/fs/cgroup/systemd, /var/lib/journal @@ -794,7 +803,7 @@ standard input. Run the container in a new user namespace using the supplied mapping. This option conflicts with the --userns and --subuidname flags. This option can be passed several times to map different ranges. If calling Podman run as an unprivileged user, the user needs to have the right to use the mapping. See `subuid(5)`. -The example maps uids 0-2000 in the container to the uids 30000-31999 on the host. `--uidmap=0:30000:2000` +The example maps uids 0-1999 in the container to the uids 30000-31999 on the host. `--uidmap=0:30000:2000` **--ulimit**=*option* @@ -834,7 +843,7 @@ Set the UTS mode for the container **NOTE**: the host mode gives the container access to changing the host's hostname and is therefore considered insecure. -**--volume**, **-v**[=*[HOST-DIR-OR-VOUME-NAME:CONTAINER-DIR[:OPTIONS]]*] +**--volume**, **-v**[=*[[SOURCE-VOLUME|HOST-DIR:]CONTAINER-DIR[:OPTIONS]]*] Create a bind mount. If you specify, ` -v /HOST-DIR:/CONTAINER-DIR`, Podman bind mounts `/HOST-DIR` in the host to `/CONTAINER-DIR` in the Podman @@ -848,11 +857,23 @@ create one. * [`z`|`Z`] * [`[r]shared`|`[r]slave`|`[r]private`] -The `/CONTAINER-DIR` must be an absolute path such as `/src/docs`. The `/HOST-DIR` -must be an absolute path as well. Podman bind-mounts the `HOST-DIR` to the -path you specify. For example, if you supply the `/foo` value, Podman creates a bind-mount. +The `CONTAINER-DIR` must be an absolute path such as `/src/docs`. The volume +will be mounted into the container at this directory. + +Volumes may specify a source as well, as either a directory on the host or the +name of a named volume. If no source is given, the volume will be created as an +anonymous named volume with a randomly generated name, and will be removed when +the container is removed via the `--rm` flag or `podman rm --volumes`. + +If a volume source is specified, it must be a path on the host or the name of a +named volume. Host paths are allowed to be absolute or relative; relative paths +are resolved relative to the directory Podman is run in. Any source that does +not begin with a `.` or `/` it will be treated as the name of a named volume. +If a volume with that name does not exist, it will be created. Volumes created +with names are not anonymous and are not removed by `--rm` and +`podman rm --volumes`. -You can specify multiple **-v** options to mount one or more mounts to a +You can specify multiple **-v** options to mount one or more volumes into a container. You can add `:ro` or `:rw` suffix to a volume to mount it read-only or diff --git a/docs/podman-stats.1.md b/docs/podman-stats.1.md index e0cff0dc2..741873c3f 100644 --- a/docs/podman-stats.1.md +++ b/docs/podman-stats.1.md @@ -15,6 +15,9 @@ Note: Podman stats will not work in rootless environments that use CGroups V1. Podman stats relies on CGroup information for statistics, and CGroup v1 is not supported for rootless use cases. +Note: Rootless environments that use CGroups V2 are not able to report statistics +about their networking usage. + ## OPTIONS **--all**, **-a** diff --git a/docs/podman-system-migrate.1.md b/docs/podman-system-migrate.1.md index d175d0344..d5e3bcb95 100644 --- a/docs/podman-system-migrate.1.md +++ b/docs/podman-system-migrate.1.md @@ -24,6 +24,14 @@ pause process. The `/etc/subuid` and `/etc/subgid` files can then be edited or changed with usermod to recreate the user namespace with the newly configured mappings. +## OPTIONS + +**--new-runtime**=*runtime* + +Set a new OCI runtime for all containers. +This can be used after a system upgrade which changes the default OCI runtime to move all containers to the new runtime. +There are no guarantees that the containers will continue to work under the new runtime, as some runtimes support differing options and configurations. + ## SYNOPSIS **podman system migrate** diff --git a/docs/podman-volume-create.1.md b/docs/podman-volume-create.1.md index 6612b0ad2..b354f396f 100644 --- a/docs/podman-volume-create.1.md +++ b/docs/podman-volume-create.1.md @@ -30,6 +30,13 @@ Set metadata for a volume (e.g., --label mykey=value). **-o**, **--opt**=*option* Set driver specific options. +For the default driver, `local`, this allows a volume to be configured to mount a filesystem on the host. +For the `local` driver the following options are supported: `type`, `device`, and `o`. +The `type` option sets the type of the filesystem to be mounted, and is equivalent to the `-t` flag to **mount(8)**. +The `device` option sets the device to be mounted, and is equivalent to the `device` argument to **mount(8)**. +The `o` option sets options for the mount, and is equivalent to the `-o` flag to **mount(8)** with two exceptions. +The `o` option supports `uid` and `gid` options to set the UID and GID of the created volume that are not normally supported by **mount(8)**. +Using volume options with the `local` driver requires root privileges. ## EXAMPLES @@ -40,11 +47,13 @@ $ podman volume create $ podman volume create --label foo=bar myvol -$ podman volume create --opt device=tmpfs --opt type=tmpfs --opt o=nodev,noexec myvol +# podman volume create --opt device=tmpfs --opt type=tmpfs --opt o=nodev,noexec myvol + +# podman volume create --opt device=tmpfs --opt type=tmpfs --opt o=uid=1000,gid=1000 testvol ``` ## SEE ALSO -podman-volume(1) +podman-volume(1), mount(8) ## HISTORY November 2018, Originally compiled by Urvashi Mohnani <umohnani@redhat.com> diff --git a/docs/podman.1.md b/docs/podman.1.md index 742d94bd5..f6fa1a457 100644 --- a/docs/podman.1.md +++ b/docs/podman.1.md @@ -137,7 +137,7 @@ the exit codes follow the `chroot` standard, see below: | Command | Description | | ------------------------------------------------ | --------------------------------------------------------------------------- | | [podman-attach(1)](podman-attach.1.md) | Attach to a running container. | -| [podman-build(1)](podman-build.1.md) | Build a container image using a Dockerfile. | +| [podman-build(1)](podman-build.1.md) | Build a container image using a Containerfile. | | [podman-commit(1)](podman-commit.1.md) | Create new image based on the changed container. | | [podman-container(1)](podman-container.1.md) | Manage containers. | | [podman-cp(1)](podman-cp.1.md) | Copy files/folders between a container and the local filesystem. | diff --git a/docs/rtd/Makefile b/docs/rtd/Makefile new file mode 100644 index 000000000..50af6490a --- /dev/null +++ b/docs/rtd/Makefile @@ -0,0 +1,123 @@ +# Minimal makefile for Sphinx documentation +# + +# You can set these variables from the command line, and also +# from the environment for the first two. +SPHINXOPTS ?= +SPHINXBUILD ?= sphinx-build +SOURCEDIR = source +BUILDDIR = build + +# Put it first so that "make" without argument is like "make help". +help: + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +clean: + rm -fr build/ + rm -f source/man/podman-*.1.md + +copy: + cp -v ../podman-attach.1.md source/man/ + cp -v ../podman-build.1.md source/man/ + cp -v ../podman-commit.1.md source/man/ + # container + cp -v ../podman-container-checkpoint.1.md source/man/ + cp -v ../podman-container-exists.1.md source/man/ + cp -v ../podman-container-restore.1.md source/man/ + cp -v ../podman-container-cleanup.1.md source/man/ + cp -v ../podman-container-prune.1.md source/man/ + cp -v ../podman-container-runlabel.1.md source/man/ + cp -v ../podman-cp.1.md source/man/ + cp -v ../podman-create.1.md source/man/ + cp -v ../podman-diff.1.md source/man/ + cp -v ../podman-events.1.md source/man/ + cp -v ../podman-exec.1.md source/man/ + cp -v ../podman-export.1.md source/man/ + # generate + cp -v ../podman-generate-systemd.1.md source/man/ + cp -v ../podman-generate-kube.1.md source/man/ + # healthcheck + cp -v ../podman-healthcheck-run.1.md source/man/ + #cp -v ../podman-help.1.md source/ + cp -v ../podman-history.1.md source/man/ + # image + cp -v ../podman-image-prune.1.md source/man/ + cp -v ../podman-image-tree.1.md source/man/ + cp -v ../podman-image-trust.1.md source/man/ + cp -v ../podman-image-exists.1.md source/man/ + cp -v ../podman-image-sign.1.md source/man/ + cp -v ../podman-images.1.md source/man/ + cp -v ../podman-import.1.md source/man/ + cp -v ../podman-info.1.md source/man/ + cp -v ../podman-init.1.md source/man/ + cp -v ../podman-inspect.1.md source/man/ + cp -v ../podman-kill.1.md source/man/ + cp -v ../podman-load.1.md source/man/ + cp -v ../podman-login.1.md source/man/ + cp -v ../podman-logout.1.md source/man/ + cp -v ../podman-logs.1.md source/man/ + cp -v ../podman-mount.1.md source/man/ + # network + cp -v ../podman-network-create.1.md source/man/ + cp -v ../podman-network-ls.1.md source/man/ + cp -v ../podman-network-inspect.1.md source/man/ + cp -v ../podman-network-rm.1.md source/man/ + cp -v ../podman-pause.1.md source/man/ + # play + cp -v ../podman-play-kube.1.md source/man/ + # pod + cp -v ../podman-pod-create.1.md source/man/ + cp -v ../podman-pod-pause.1.md source/man/ + cp -v ../podman-pod-rm.1.md source/man/ + cp -v ../podman-pod-top.1.md source/man/ + cp -v ../podman-pod-exists.1.md source/man/ + cp -v ../podman-pod-prune.1.md source/man/ + cp -v ../podman-pod-start.1.md source/man/ + cp -v ../podman-pod-unpause.1.md source/man/ + cp -v ../podman-pod-inspect.1.md source/man/ + cp -v ../podman-pod-ps.1.md source/man/ + cp -v ../podman-pod-stats.1.md source/man/ + cp -v ../podman-pod-kill.1.md source/man/ + cp -v ../podman-pod-restart.1.md source/man/ + cp -v ../podman-pod-stop.1.md source/man/ + cp -v ../podman-port.1.md source/man/ + cp -v ../podman-ps.1.md source/man/ + cp -v ../podman-pull.1.md source/man/ + cp -v ../podman-push.1.md source/man/ + cp -v ../podman-restart.1.md source/man/ + cp -v ../podman-rm.1.md source/man/ + cp -v ../podman-rmi.1.md source/man/ + cp -v ../podman-run.1.md source/man/ + cp -v ../podman-save.1.md source/man/ + cp -v ../podman-search.1.md source/man/ + cp -v ../podman-start.1.md source/man/ + cp -v ../podman-stats.1.md source/man/ + cp -v ../podman-stop.1.md source/man/ + # system + cp -v ../podman-system-migrate.1.md source/man/ + cp -v ../podman-system-renumber.1.md source/man/ + cp -v ../podman-system-df.1.md source/man/ + cp -v ../podman-system-prune.1.md source/man/ + cp -v ../podman-top.1.md source/man/ + cp -v ../podman-umount.1.md source/man/ + cp -v ../podman-unpause.1.md source/man/ + cp -v ../podman-unshare.1.md source/man/ + cp -v ../podman-varlink.1.md source/man/ + cp -v ../podman-version.1.md source/man/ + # volume + cp -v ../podman-volume-inspect.1.md source/man/ + cp -v ../podman-volume-prune.1.md source/man/ + cp -v ../podman-volume-create.1.md source/man/ + cp -v ../podman-volume-ls.1.md source/man/ + cp -v ../podman-volume-rm.1.md source/man/ + cp -v ../podman-wait.1.md source/man/ + +.PHONY: help Makefile copy + +html: copy + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +# Catch-all target: route all unknown targets to Sphinx using the new +# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). +%: Makefile + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/docs/rtd/make.bat b/docs/rtd/make.bat new file mode 100644 index 000000000..6247f7e23 --- /dev/null +++ b/docs/rtd/make.bat @@ -0,0 +1,35 @@ +@ECHO OFF
+
+pushd %~dp0
+
+REM Command file for Sphinx documentation
+
+if "%SPHINXBUILD%" == "" (
+ set SPHINXBUILD=sphinx-build
+)
+set SOURCEDIR=source
+set BUILDDIR=build
+
+if "%1" == "" goto help
+
+%SPHINXBUILD% >NUL 2>NUL
+if errorlevel 9009 (
+ echo.
+ echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
+ echo.installed, then set the SPHINXBUILD environment variable to point
+ echo.to the full path of the 'sphinx-build' executable. Alternatively you
+ echo.may add the Sphinx directory to PATH.
+ echo.
+ echo.If you don't have Sphinx installed, grab it from
+ echo.http://sphinx-doc.org/
+ exit /b 1
+)
+
+%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
+goto end
+
+:help
+%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
+
+:end
+popd
diff --git a/docs/rtd/requirements.txt b/docs/rtd/requirements.txt new file mode 100644 index 000000000..44af373ac --- /dev/null +++ b/docs/rtd/requirements.txt @@ -0,0 +1,4 @@ +# requirements file for readthedocs pip installs + +# use md instead of rst +recommonmark diff --git a/docs/rtd/source/Commands.rst b/docs/rtd/source/Commands.rst new file mode 100644 index 000000000..f6ba5b20d --- /dev/null +++ b/docs/rtd/source/Commands.rst @@ -0,0 +1,107 @@ +Commands +======== + + +:doc:`attach <man/podman-attach.1>` Attach to a running container + +:doc:`build <man/podman-build.1>` Build an image using instructions from Containerfiles + +:doc:`commit <man/podman-commit.1>` Create new image based on the changed container + +:doc:`containers <man/managecontainers>` Manage Containers + +:doc:`cp <man/podman-cp.1>` Copy files/folders between a container and the local filesystem + +:doc:`create <man/podman-create.1>` Create but do not start a container + +:doc:`diff <man/podman-diff.1>` Inspect changes on container's file systems + +:doc:`events <man/podman-events.1>` Show podman events + +:doc:`exec <man/podman-exec.1>` Run a process in a running container + +:doc:`export <man/podman-export.1>` Export container's filesystem contents as a tar archive + +:doc:`generate <man/generate>` Generated structured data + +:doc:`healthcheck <man/healthcheck>` Manage Healthcheck + +:doc:`history <man/podman-history.1>` Show history of a specified image + +:doc:`image <man/image>` Manage images + +:doc:`images <man/podman-images.1>` List images in local storage + +:doc:`import <man/podman-import.1>` Import a tarball to create a filesystem image + +:doc:`info <man/podman-info.1>` Display podman system information + +:doc:`init <man/podman-init.1>` Initialize one or more containers + +:doc:`inspect <man/podman-inspect.1>` Display the configuration of a container or image + +:doc:`kill <man/podman-kill.1>` Kill one or more running containers with a specific signal + +:doc:`load <man/podman-load.1>` Load an image from container archive + +:doc:`login <man/podman-login.1>` Login to a container registry + +:doc:`logout <man/podman-logout.1>` Logout of a container registry + +:doc:`logs <man/podman-logs.1>` Fetch the logs of a container + +:doc:`mount <man/podman-mount.1>` Mount a working container's root filesystem + +:doc:`network <man/network>` Manage Networks + +:doc:`pause <man/podman-pause.1>` Pause all the processes in one or more containers + +:doc:`play <man/play>` Play a pod + +:doc:`pod <man/pod>` Manage pods + +:doc:`port <man/podman-port.1>` List port mappings or a specific mapping for the container + +:doc:`ps <man/podman-ps.1>` List containers + +:doc:`pull <man/podman-pull.1>` Pull an image from a registry + +:doc:`push <man/podman-push.1>` Push an image to a specified destination + +:doc:`restart <man/podman-restart.1>` Restart one or more containers + +:doc:`rm <man/podman-rm.1>` Remove one or more containers + +:doc:`rmi <man/podman-rmi.1>` Removes one or more images from local storage + +:doc:`run <man/podman-run.1>` Run a command in a new container + +:doc:`save <man/podman-save.1>` Save image to an archive + +:doc:`search <man/podman-search.1>` Search registry for image + +:doc:`start <man/podman-start.1>` Start one or more containers + +:doc:`stats <man/podman-stats.1>` Display a live stream of container resource usage statistics + +:doc:`stop <man/podman-stop.1>` Stop one or more containers + +:doc:`system <man/system>` Manage podman + +:doc:`tag <man/podman-tag.1>` Add an additional name to a local image + +:doc:`top <man/podman-top.1>` Display the running processes of a container + +:doc:`umount <man/podman-umount.1>` Unmounts working container's root filesystem + +:doc:`unpause <man/podman-unpause.1>` Unpause the processes in one or more containers + +:doc:`unshare <man/podman-unshare.1>` Run a command in a modified user namespace + +:doc:`varlink <man/podman-varlink.1>` Run varlink interface + +:doc:`version <man/podman-version.1>` Display the Podman Version Information + +:doc:`volume <man/volume>` Manage volumes + +:doc:`wait <man/podman-wait.1>` Block on one or more containers
\ No newline at end of file diff --git a/docs/rtd/source/Introduction.rst b/docs/rtd/source/Introduction.rst new file mode 100644 index 000000000..c516b3317 --- /dev/null +++ b/docs/rtd/source/Introduction.rst @@ -0,0 +1,2 @@ +Introduction +============ diff --git a/docs/rtd/source/Reference.rst b/docs/rtd/source/Reference.rst new file mode 100644 index 000000000..9a771c87f --- /dev/null +++ b/docs/rtd/source/Reference.rst @@ -0,0 +1,2 @@ +Reference +========= diff --git a/docs/rtd/source/Tutorials.rst b/docs/rtd/source/Tutorials.rst new file mode 100644 index 000000000..0c7e28c3b --- /dev/null +++ b/docs/rtd/source/Tutorials.rst @@ -0,0 +1,2 @@ +Tutorials +========= diff --git a/docs/rtd/source/conf.py b/docs/rtd/source/conf.py new file mode 100644 index 000000000..d95290f72 --- /dev/null +++ b/docs/rtd/source/conf.py @@ -0,0 +1,57 @@ +# Configuration file for the Sphinx documentation builder. +# +# This file only contains a selection of the most common options. For a full +# list see the documentation: +# https://www.sphinx-doc.org/en/master/usage/configuration.html + +# -- Path setup -------------------------------------------------------------- + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +# +# import os +# import sys +# sys.path.insert(0, os.path.abspath('.')) + + +# -- Project information ----------------------------------------------------- + +project = 'Podman' +copyright = '2019, team' +author = 'team' + + +# -- General configuration --------------------------------------------------- + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = [ + 'recommonmark', +] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +# This pattern also affects html_static_path and html_extra_path. +exclude_patterns = [] + +master_doc = 'index' + +# -- Options for HTML output ------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +# +html_theme = 'alabaster' + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] + + +# -- Extension configuration ------------------------------------------------- diff --git a/docs/rtd/source/index.rst b/docs/rtd/source/index.rst new file mode 100644 index 000000000..9dd61a6a6 --- /dev/null +++ b/docs/rtd/source/index.rst @@ -0,0 +1,26 @@ +.. Podman documentation master file, created by + sphinx-quickstart on Tue Oct 22 15:20:30 2019. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +Welcome to Podman's documentation! +================================== + +.. toctree:: + :maxdepth: 2 + :caption: Contents: + + Introduction + Commands + Reference + Tutorials + + + + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` diff --git a/docs/rtd/source/man/generate.rst b/docs/rtd/source/man/generate.rst new file mode 100644 index 000000000..e82a15735 --- /dev/null +++ b/docs/rtd/source/man/generate.rst @@ -0,0 +1,6 @@ +Generate +======== + +:doc:`kube <podman-generate-kube.1>` Generate Kubernetes pod YAML from a container or pod + +:doc:`systemd <podman-generate-systemd.1>` Generate a systemd unit file for a Podman container diff --git a/docs/rtd/source/man/healthcheck.rst b/docs/rtd/source/man/healthcheck.rst new file mode 100644 index 000000000..697c1358b --- /dev/null +++ b/docs/rtd/source/man/healthcheck.rst @@ -0,0 +1,4 @@ +HealthCheck +=========== + +:doc:`run <podman-healthcheck-run.1>` run the health check of a container diff --git a/docs/rtd/source/man/image.rst b/docs/rtd/source/man/image.rst new file mode 100644 index 000000000..ad963cd41 --- /dev/null +++ b/docs/rtd/source/man/image.rst @@ -0,0 +1,35 @@ +Image +===== + + +:doc:`build <podman-build.1>` Build an image using instructions from Containerfiles + +:doc:`exists <podman-image-exists.1>` Check if an image exists in local storage + +:doc:`history <podman-history.1>` Show history of a specified image + +:doc:`import <podman-import.1>` Import a tarball to create a filesystem image + +:doc:`inspect <podman-inspect.1>` Display the configuration of an image + +:doc:`list <podman-images.1>` List images in local storage + +:doc:`load <podman-load.1>` Load an image from container archive + +:doc:`prune <podman-image-prune.1>` Remove unused images + +:doc:`pull <podman-pull.1>` Pull an image from a registry + +:doc:`push <podman-push.1>` Push an image to a specified destination + +:doc:`rm <podman-rmi.1>` Removes one or more images from local storage + +:doc:`save <podman-save.1>` Save image to an archive + +:doc:`sign <podman-image-sign.1>` Sign an image + +:doc:`tag <podman-tag.1>` Add an additional name to a local image + +:doc:`tree <podman-image-tree.1>` Prints layer hierarchy of an image in a tree format + +:doc:`trust <podman-image-trust.1>` Manage container image trust policy diff --git a/docs/rtd/source/man/managecontainers.rst b/docs/rtd/source/man/managecontainers.rst new file mode 100644 index 000000000..20e8c0679 --- /dev/null +++ b/docs/rtd/source/man/managecontainers.rst @@ -0,0 +1,64 @@ +Manage Containers +================= + +:doc:`attach <podman-attach.1>` Attach to a running container + +:doc:`checkpoint <podman-container-checkpoint.1>` Checkpoints one or more containers + +:doc:`cleanup <podman-container-cleanup.1>` Cleanup network and mountpoints of one or more containers + +:doc:`commit <podman-commit.1>` Create new image based on the changed container + +:doc:`cp <podman-cp.1>` Copy files/folders between a container and the local filesystem + +:doc:`create <podman-create.1>` Create but do not start a container + +:doc:`diff <podman-diff.1>` Inspect changes on container's file systems + +:doc:`exec <podman-exec.1>` Run a process in a running container + +:doc:`exists <podman-exists.1>` Check if a container exists in local storage + +:doc:`export <podman-export.1>` Export container's filesystem contents as a tar archive + +:doc:`init <podman-init.1>` Initialize one or more containers + +:doc:`inspect <podman-inspect.1>` Display the configuration of a container or image + +:doc:`kill <podman-kill.1>` Kill one or more running containers with a specific signal + +:doc:`list <podman-ps.1>` List containers + +:doc:`logs <podman-logs.1>` Fetch the logs of a container + +:doc:`mount <podman-mount.1>` Mount a working container's root filesystem + +:doc:`pause <podman-pause.1>` Pause all the processes in one or more containers + +:doc:`port <podman-port.1>` List port mappings or a specific mapping for the container + +:doc:`restart <podman-restart.1>` Restart one or more containers + +:doc:`prune <podman-container-prune.1>` Remove all stopped containers + +:doc:`restore <podman-container-restore.1>` Restores one or more containers from a checkpoint + +:doc:`rm <podman-rm.1>` Remove one or more containers + +:doc:`run <podman-run.1>` Run a command in a new container + +:doc:`runlabel <podman-container-runlabel.1>` Execute the command described by an image label + +:doc:`start <podman-start.1>` Start one or more containers + +:doc:`stats <podman-stats.1>` Display a live stream of container resource usage statistics + +:doc:`stop <podman-stop.1>` Stop one or more containers + +:doc:`top <podman-top.1>` Display the running processes of a container + +:doc:`umount <podman-umount.1>` Unmounts working container's root filesystem + +:doc:`unpause <podman-unpause.1>` Unpause the processes in one or more containers + +:doc:`wait <podman-wait.1>` Block on one or more containers diff --git a/docs/rtd/source/man/network.rst b/docs/rtd/source/man/network.rst new file mode 100644 index 000000000..6d6a4c022 --- /dev/null +++ b/docs/rtd/source/man/network.rst @@ -0,0 +1,10 @@ +Network +===== + +:doc:`create <podman-network-create.1>` network create + +:doc:`inspect <podman-network-inspect.1>` network inspect + +:doc:`ls <podman-network-ls.1>` network list + +:doc:`rm <podman-network-rm.1>` network rm
\ No newline at end of file diff --git a/docs/rtd/source/man/play.rst b/docs/rtd/source/man/play.rst new file mode 100644 index 000000000..93e1a9a1e --- /dev/null +++ b/docs/rtd/source/man/play.rst @@ -0,0 +1,4 @@ +Play +==== + +:doc:`kube <podman-play-kube.1>` Play a pod based on Kubernetes YAML diff --git a/docs/rtd/source/man/pod.rst b/docs/rtd/source/man/pod.rst new file mode 100644 index 000000000..13c1740f8 --- /dev/null +++ b/docs/rtd/source/man/pod.rst @@ -0,0 +1,30 @@ +Pod +=== + +:doc:`create <podman-pod-create.1>` Create a new empty pod + +:doc:`exists <podman-pod-exists.1>` Check if a pod exists in local storage + +:doc:`inspect <podman-pod-inspect.1>` Displays a pod configuration + +:doc:`kill <podman-pod-kill.1>` Send the specified signal or SIGKILL to containers in pod + +:doc:`pause <podman-pause.1>` Pause one or more pods + +:doc:`prune <podman-pod-prune.1>` Remove all stopped pods + +:doc:`ps <podman-pod-ps.1>` List pods + +:doc:`restart <podman-pod-restart.1>` Restart one or more pods + +:doc:`rm <podman-pod-rm.1>` Remove one or more pods + +:doc:`start <podman-pod-start.1>` Start one or more pods + +:doc:`stats <podman-pod-stats.1>` Display a live stream of resource usage statistics for the containers in one or more pods + +:doc:`stop <podman-pod-stop.1>` Stop one or more pods + +:doc:`top <podman-pod-top.1>` Display the running processes of containers in a pod + +:doc:`unpause <podman-pod-unpause.1>` Unpause one or more pods diff --git a/docs/rtd/source/man/system.rst b/docs/rtd/source/man/system.rst new file mode 100644 index 000000000..764ec01c1 --- /dev/null +++ b/docs/rtd/source/man/system.rst @@ -0,0 +1,12 @@ +System +====== + +:doc:`df <podman-system-df.1>` Show podman disk usage + +:doc:`info <podman-info.1>` Display podman system information + +:doc:`migrate <podman-system-migrate.1>` Migrate containers + +:doc:`prune <podman-system-prune.1>` Remove unused data + +:doc:`renumber <podman-system-renumber.1>` Migrate lock numbers diff --git a/docs/rtd/source/man/volume.rst b/docs/rtd/source/man/volume.rst new file mode 100644 index 000000000..ee18e4b2e --- /dev/null +++ b/docs/rtd/source/man/volume.rst @@ -0,0 +1,11 @@ +Volume +====== +:doc:`create <podman-volume-create.1>` Create a new volume + +:doc:`inspect <podman-volume-inspect.1>` Display detailed information on one or more volumes + +:doc:`ls <podman-volume-ls.1>` List volumes + +:doc:`prune <podman-volume-prune.1>` Remove all unused volumes + +:doc:`rm <podman-volume-rm.1>` Remove one or more volumes
\ No newline at end of file diff --git a/docs/tutorials/rootless_tutorial.md b/docs/tutorials/rootless_tutorial.md index ed700485a..9a31826bd 100644 --- a/docs/tutorials/rootless_tutorial.md +++ b/docs/tutorials/rootless_tutorial.md @@ -13,7 +13,7 @@ The alternative OCI runtime support for cgroup V2 can be turned on at the comma ``` sudo podman --runtime /usr/bin/crun ``` -or by changing the value for the "Default OCI runtime" in the libpod.conf file either at the system level or at the [#user-configuration-files](user level) from `runtime = "runc"` to `runtime = "crun"`. +or by changing the value for the "Default OCI runtime" in the libpod.conf file either at the system level or at the [user level](#user-configuration-files) from `runtime = "runc"` to `runtime = "crun"`. ## Administrator Actions @@ -59,7 +59,7 @@ The format of this file is USERNAME:UID:RANGE This means the user johndoe is allocated UIDS 100000-165535 as well as their standard UID in the /etc/passwd file. NOTE: this is not currently supported with network installs. These files must be available locally to the host machine. It is not possible to configure this with LDAP or Active Directory. -If you update either the /etc/subuid or the /etc/subgid file, you need to stop all the running containers owned by the user and kill the pause process that is running on the system for that user. This can be done automatically by using the `[podman system migrate](https://github.com/containers/libpod/blob/master/docs/podman-system-migrate.1.md)` command which will stop all the containers for the user and will kill the pause process. +If you update either the /etc/subuid or the /etc/subgid file, you need to stop all the running containers owned by the user and kill the pause process that is running on the system for that user. This can be done automatically by using the [`podman system migrate`](https://github.com/containers/libpod/blob/master/docs/podman-system-migrate.1.md) command which will stop all the containers for the user and will kill the pause process. Rather than updating the files directly, the usermod program can be used to assign UIDs and GIDs to a user. @@ -4,20 +4,17 @@ go 1.12 require ( github.com/BurntSushi/toml v0.3.1 - github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect github.com/blang/semver v3.5.1+incompatible // indirect github.com/buger/goterm v0.0.0-20181115115552-c206103e1f37 github.com/checkpoint-restore/go-criu v0.0.0-20190109184317-bdb7599cd87b github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd // indirect - github.com/containerd/console v0.0.0-20181022165439-0650fd9eeb50 // indirect github.com/containerd/continuity v0.0.0-20190426062206-aaeac12a7ffc // indirect github.com/containernetworking/cni v0.7.1 github.com/containernetworking/plugins v0.8.2 github.com/containers/buildah v1.11.3 - github.com/containers/image v3.0.2+incompatible // indirect github.com/containers/image/v4 v4.0.1 - github.com/containers/psgo v1.3.1 - github.com/containers/storage v1.13.4 + github.com/containers/psgo v1.3.2 + github.com/containers/storage v1.13.5 github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f // indirect github.com/cri-o/ocicni v0.1.1-0.20190702175919-7762645d18ca @@ -42,7 +39,6 @@ require ( github.com/imdario/mergo v0.3.7 // indirect github.com/json-iterator/go v1.1.7 github.com/mattn/go-isatty v0.0.8 // indirect - github.com/moby/moby v0.0.0-20171005181806-f8806b18b4b9 // indirect github.com/mrunalp/fileutils v0.0.0-20171103030105-7d4729fb3618 github.com/onsi/ginkgo v1.10.1 github.com/onsi/gomega v1.7.0 @@ -65,7 +61,6 @@ require ( github.com/uber-go/atomic v1.4.0 // indirect github.com/uber/jaeger-client-go v2.19.0+incompatible github.com/uber/jaeger-lib v0.0.0-20190122222657-d036253de8f5 // indirect - github.com/urfave/cli v1.21.0 // indirect github.com/varlink/go v0.0.0-20190502142041-0f1d566d194b github.com/vishvananda/netlink v1.0.0 go.uber.org/atomic v1.4.0 // indirect @@ -18,8 +18,6 @@ github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jB github.com/Microsoft/hcsshim v0.8.6 h1:ZfF0+zZeYdzMIVMZHKtDKJvLHj76XCuVae/jNkjj0IA= github.com/Microsoft/hcsshim v0.8.6/go.mod h1:Op3hHsoHPAvb6lceZHDtd9OkTew38wNoXnJs8iY7rUg= github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= -github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEVMRuU21PR1EtLVZJmdB18Gu3Rw= -github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk= github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/VividCortex/ewma v1.1.1 h1:MnEK4VOv6n0RSY4vtRe3h11qjxL3+t0B8yOL8iMXdcM= @@ -45,9 +43,7 @@ github.com/checkpoint-restore/go-criu v0.0.0-20190109184317-bdb7599cd87b/go.mod github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd h1:qMd81Ts1T2OTKmB4acZcyKaMtRnY5Y44NuXGX2GFJ1w= github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= -github.com/containerd/console v0.0.0-20181022165439-0650fd9eeb50/go.mod h1:Tj/on1eG8kiEhd0+fhSDzsPAFESxzBBvdyEgyryXffw= github.com/containerd/continuity v0.0.0-20180216233310-d8fb8589b0e8/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= -github.com/containerd/continuity v0.0.0-20180814194400-c7c5070e6f6e/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= github.com/containerd/continuity v0.0.0-20181203112020-004b46473808 h1:4BX8f882bXEDKfWIf0wa8HRvpnBoPszJJXL+TVbBw4M= github.com/containerd/continuity v0.0.0-20181203112020-004b46473808/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= github.com/containerd/continuity v0.0.0-20190426062206-aaeac12a7ffc h1:TP+534wVlf61smEIq1nwLLAjQVEK2EADoW3CX9AuT+8= @@ -57,22 +53,18 @@ github.com/containernetworking/cni v0.7.1 h1:fE3r16wpSEyaqY4Z4oFrLMmIGfBYIKpPrHK github.com/containernetworking/cni v0.7.1/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ61X79hmU3w8FmsY= github.com/containernetworking/plugins v0.8.2 h1:5lnwfsAYO+V7yXhysJKy3E1A2Gy9oVut031zfdOzI9w= github.com/containernetworking/plugins v0.8.2/go.mod h1:TxALKWZpWL79BC3GOYKJzzXr7U8R23PdhwaLp6F3adc= -github.com/containers/buildah v1.11.2 h1:U6Abrp1J7H19vHvhqIran4Xvw+Z3WIqMM86fIt9L7Qk= -github.com/containers/buildah v1.11.2/go.mod h1:CtnP3vsLiU3xgKvkhdb4b0IzYwXNzHRv3ezl4z+RPC0= github.com/containers/buildah v1.11.3 h1:L5vFj+ao58IGq3G30jN94vRQrIgMU/uTOEKduDr3Nyg= github.com/containers/buildah v1.11.3/go.mod h1:jqZmSU/PhFwTHHlOotnw4bbs1JbkRQLh8dut5DF4Qek= -github.com/containers/image v3.0.2+incompatible h1:B1lqAE8MUPCrsBLE86J0gnXleeRq8zJnQryhiiGQNyE= -github.com/containers/image v3.0.2+incompatible/go.mod h1:8Vtij257IWSanUQKe1tAeNOm2sRVkSqQTVQ1IlwI3+M= github.com/containers/image/v4 v4.0.1 h1:idNGHChj0Pyv3vLrxul2oSVMZLeFqpoq3CjLeVgapSQ= github.com/containers/image/v4 v4.0.1/go.mod h1:0ASJH1YgJiX/eqFZObqepgsvIA4XjCgpyfwn9pDGafA= github.com/containers/libtrust v0.0.0-20190913040956-14b96171aa3b h1:Q8ePgVfHDplZ7U33NwHZkrVELsZP5fYj9pM5WBZB2GE= github.com/containers/libtrust v0.0.0-20190913040956-14b96171aa3b/go.mod h1:9rfv8iPl1ZP7aqh9YA68wnZv2NUDbXdcdPHVz0pFbPY= -github.com/containers/psgo v1.3.1 h1:1kE+jJ9Ou5f9zQT/M2IdeSclsKWsXrSFlOcnqc+F2TA= -github.com/containers/psgo v1.3.1/go.mod h1:LLiRMmxZ6FWP4bB/fOUu6kDT+4okk/ZCeeykqh0O5Ns= -github.com/containers/storage v1.13.2 h1:UXZ0Ckmk6+6+4vj2M2ywruVtH97pnRoAhTG8ctd+yQI= -github.com/containers/storage v1.13.2/go.mod h1:6D8nK2sU9V7nEmAraINRs88ZEscM5C5DK+8Npp27GeA= +github.com/containers/psgo v1.3.2 h1:jYfppPih3S/j2Yi5O14AXjd8GfCx1ph9L3YsoK3adko= +github.com/containers/psgo v1.3.2/go.mod h1:ENXXLQ5E1At4K0EUsGogXBJi/C28gwqkONWeLPI9fJ8= github.com/containers/storage v1.13.4 h1:j0bBaJDKbUHtAW1MXPFnwXJtqcH+foWeuXK1YaBV5GA= github.com/containers/storage v1.13.4/go.mod h1:6D8nK2sU9V7nEmAraINRs88ZEscM5C5DK+8Npp27GeA= +github.com/containers/storage v1.13.5 h1:/SUzGeOP2HDijpF7Yur21Ch6WTZC1BNeZF917CWcp5c= +github.com/containers/storage v1.13.5/go.mod h1:HELz8Sn+UVbPaUZMI8RvIG9doD4y4z6Gtg4k7xdd2ZY= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= github.com/coreos/go-iptables v0.4.2 h1:KH0EwId05JwWIfb96gWvkiT2cbuOu8ygqUaB+yPAwIg= @@ -87,8 +79,6 @@ github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfc github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= github.com/cri-o/ocicni v0.1.1-0.20190702175919-7762645d18ca h1:CJstDqYy9ClWuPcDHMTCAiUS+ckekluYetGR2iYYWuo= github.com/cri-o/ocicni v0.1.1-0.20190702175919-7762645d18ca/go.mod h1:BO0al9TKber3XUTucLzKgoG5sq8qiOB41H7zSdfw6r8= -github.com/cyphar/filepath-securejoin v0.2.1 h1:5DPkzz/0MwUpvR4fxASKzgApeq2OMFY5FfYtrX28Coo= -github.com/cyphar/filepath-securejoin v0.2.1/go.mod h1:FpkQEhXnPnOthhzymB7CGsFk2G9VLXONKD9G7QGMM+4= github.com/cyphar/filepath-securejoin v0.2.2 h1:jCwT2GTP+PY5nBz3c/YL5PAIbusElVrPujOBSCj8xRg= github.com/cyphar/filepath-securejoin v0.2.2/go.mod h1:FpkQEhXnPnOthhzymB7CGsFk2G9VLXONKD9G7QGMM+4= github.com/d2g/dhcp4 v0.0.0-20170904100407-a1d1b6c41b1c/go.mod h1:Ct2BUK8SB0YC1SMSibvLzxjeJLnrYEVLULFNiHY9YfQ= @@ -106,10 +96,6 @@ github.com/docker/distribution v2.7.1+incompatible h1:a5mlkVzth6W5A4fOsS3D2EO5BU github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= github.com/docker/docker v0.0.0-20171019062838-86f080cff091/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker v0.0.0-20180522102801-da99009bbb11/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= -github.com/docker/docker v0.7.3-0.20180827131323-0c5f8d2b9b23 h1:mJtkfC9RUrUWHMk0cFDNhVoc9U3k2FRAzEZ+5pqSIHo= -github.com/docker/docker v0.7.3-0.20180827131323-0c5f8d2b9b23/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= -github.com/docker/docker v0.7.3-0.20190309235953-33c3200e0d16 h1:dmUn0SuGx7unKFwxyeQ/oLUHhEfZosEDrpmYM+6MTuc= -github.com/docker/docker v0.7.3-0.20190309235953-33c3200e0d16/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker v1.4.2-0.20190710153559-aa8249ae1b8b h1:+Ga+YpCDpcY1fln6GI0fiiirpqHGcob5/Vk3oKNuGdU= github.com/docker/docker v1.4.2-0.20190710153559-aa8249ae1b8b/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker-credential-helpers v0.6.0/go.mod h1:WRaJzqw3CTB9bk10avuGsjVBZsD05qeibJ1/TYlvc0Y= @@ -120,16 +106,10 @@ github.com/docker/docker-credential-helpers v0.6.3/go.mod h1:WRaJzqw3CTB9bk10avu github.com/docker/go-connections v0.0.0-20180212134524-7beb39f0b969/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= -github.com/docker/go-metrics v0.0.0-20181218153428-b84716841b82 h1:X0fj836zx99zFu83v/M79DuBn84IL/Syx1SY6Y5ZEMA= -github.com/docker/go-metrics v0.0.0-20181218153428-b84716841b82/go.mod h1:/u0gXw0Gay3ceNrsHubL3BtdOL2fHf93USgMTe0W5dI= github.com/docker/go-metrics v0.0.1 h1:AgB/0SvBxihN0X8OR4SjsblXkbMvalQ8cjmtKQ2rQV8= github.com/docker/go-metrics v0.0.1/go.mod h1:cG1hvH2utMXtqgqqYE9plW6lDxS3/5ayHzueweSI3Vw= -github.com/docker/go-units v0.3.3 h1:Xk8S3Xj5sLGlG5g67hJmYMmUgXv5N4PhkjJHHqrwnTk= -github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw= github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= -github.com/docker/libnetwork v0.8.0-dev.2.0.20180608203834-19279f049241 h1:+ebE/hCU02srkeIg8Vp/vlUp182JapYWtXzV+bCeR2I= -github.com/docker/libnetwork v0.8.0-dev.2.0.20180608203834-19279f049241/go.mod h1:93m0aTqz6z+g32wla4l4WxTrdtvBRmVzYRkYvasA5Z8= github.com/docker/libnetwork v0.8.0-dev.2.0.20190625141545-5a177b73e316 h1:moehPjPiGUaWdwgOl92xRyFHJyaqXDHcCyW9M6nmCK4= github.com/docker/libnetwork v0.8.0-dev.2.0.20190625141545-5a177b73e316/go.mod h1:93m0aTqz6z+g32wla4l4WxTrdtvBRmVzYRkYvasA5Z8= github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7 h1:UhxFibDNY/bfvqU5CAUmr9zpesgbU6SWc8/B4mflAE4= @@ -143,8 +123,6 @@ github.com/elazarl/goproxy v0.0.0-20190421051319-9d40249d3c2f/go.mod h1:/Zj4wYkg github.com/elazarl/goproxy/ext v0.0.0-20190911111923-ecfe977594f1 h1:8B7WF1rIoM8H1smfpXFvOawSAzlRDMVzoGu9zE3+OCk= github.com/elazarl/goproxy/ext v0.0.0-20190911111923-ecfe977594f1/go.mod h1:gNh8nYJoAm43RfaxurUnxr+N1PwuFV3ZMl/efxlIlY8= github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= -github.com/etcd-io/bbolt v1.3.2 h1:RLRQ0TKLX7DlBRXAJHvbmXL17Q3KNnTBtZ9B6Qo+/Y0= -github.com/etcd-io/bbolt v1.3.2/go.mod h1:ZF2nL25h33cCyBtcyWeZ2/I3HQOfTP+0PIEvHjkjCrw= github.com/etcd-io/bbolt v1.3.3 h1:gSJmxrs37LgTqR/oyJBWok6k6SvXEUerFTbltIhXkBM= github.com/etcd-io/bbolt v1.3.3/go.mod h1:ZF2nL25h33cCyBtcyWeZ2/I3HQOfTP+0PIEvHjkjCrw= github.com/evanphx/json-patch v0.0.0-20190203023257-5858425f7550/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= @@ -153,10 +131,6 @@ github.com/fatih/camelcase v1.0.0 h1:hxNvNX/xYBp0ovncs8WyWZrOrpBNub/JfaMvbURyft8 github.com/fatih/camelcase v1.0.0/go.mod h1:yN2Sb0lFhZJUdVvtELVWefmrXpuZESvPmqwoZc+/fpc= github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= -github.com/fsouza/go-dockerclient v1.3.0 h1:tOXkq/5++XihrAvH5YNwCTdPeQg3XVcC6WI2FVy4ZS0= -github.com/fsouza/go-dockerclient v1.3.0/go.mod h1:IN9UPc4/w7cXiARH2Yg99XxUHbAM+6rAi9hzBVbkWRU= -github.com/fsouza/go-dockerclient v1.4.1 h1:W7wuJ3IB48WYZv/UBk9dCTIb9oX805+L9KIm65HcUYs= -github.com/fsouza/go-dockerclient v1.4.1/go.mod h1:PUNHxbowDqRXfRgZqMz1OeGtbWC6VKyZvJ99hDjB0qs= github.com/fsouza/go-dockerclient v1.4.4 h1:Sd5nD4wdAgiPxvrbYUzT2ZZNmPk3z+GGnZ+frvw8z04= github.com/fsouza/go-dockerclient v1.4.4/go.mod h1:PrwszSL5fbmsESocROrOGq/NULMXRw+bajY0ltzD6MA= github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= @@ -197,6 +171,7 @@ github.com/google/btree v0.0.0-20160524151835-7d79101e329e/go.mod h1:lNA+9X1NB3Z github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1 h1:Xye71clBPdm5HgqGwUkwhbynsUJZhDbS20FvLhQ2izg= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/gofuzz v0.0.0-20161122191042-44d81051d367/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI= github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf h1:+RRA9JqSOZFfKrOeqr2z77+8R2RKyh8PG66dcu1V0ck= @@ -213,10 +188,6 @@ github.com/gophercloud/gophercloud v0.0.0-20190126172459-c818fa66e4c8/go.mod h1: github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8= github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= github.com/gorilla/mux v0.0.0-20170217192616-94e7d24fd285/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= -github.com/gorilla/mux v1.6.2 h1:Pgr17XVTNXAk3q/r4CpKzC5xBM/qW1uVLV+IhRZpIIk= -github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= -github.com/gorilla/mux v1.7.2 h1:zoNxOV7WjqXptQOVngLmcSQgXmgk4NMz1HibBchjl/I= -github.com/gorilla/mux v1.7.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/mux v1.7.3 h1:gnP5JzjVOuiZD07fKKToCAOjS0yOpj/qPETTXCCS6hw= github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gotestyourself/gotestyourself v2.2.0+incompatible/go.mod h1:zZKM6oeNM8k+FRljX1mnzVYeS8wiGgQyvST1/GafPbY= @@ -288,7 +259,6 @@ github.com/mistifyio/go-zfs v2.1.1+incompatible h1:gAMO1HM9xBRONLHHYnu5iFsOJUiJd github.com/mistifyio/go-zfs v2.1.1+incompatible/go.mod h1:8AuVvqP/mXw1px98n46wfvcGfQ4ci2FwoAjKYxuo3Z4= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/moby/moby v0.0.0-20171005181806-f8806b18b4b9/go.mod h1:fDXVQ6+S340veQPv35CzDahGBmHsiclFwfEygB/TWMc= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -315,7 +285,6 @@ github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+ github.com/onsi/gomega v0.0.0-20151007035656-2152b45fa28a/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v0.0.0-20190113212917-5533ce8a0da3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= -github.com/onsi/gomega v1.4.1/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v1.5.0 h1:izbySO9zDPmjJ8rDjLvkA2zJHIo+HkYXHnf7eN7SSyo= github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.0 h1:XPnZz8VVBHjVsy1vzJmRwIcSwiUO+JFfrv/xGiigmME= @@ -387,22 +356,17 @@ github.com/seccomp/containers-golang v0.0.0-20180629143253-cdfdaa7543f4 h1:rOG9o github.com/seccomp/containers-golang v0.0.0-20180629143253-cdfdaa7543f4/go.mod h1:f/98/SnvAzhAEFQJ3u836FePXvcbE8BS0YGMQNn4mhA= github.com/seccomp/containers-golang v0.0.0-20190312124753-8ca8945ccf5f h1:OtU/w6sBKmXYaw2KEODxjcYi3oPSyyslhgGFgIJVGAI= github.com/seccomp/containers-golang v0.0.0-20190312124753-8ca8945ccf5f/go.mod h1:f/98/SnvAzhAEFQJ3u836FePXvcbE8BS0YGMQNn4mhA= -github.com/seccomp/libseccomp-golang v0.9.0 h1:S1pmhdFh5spQtVojA+4GUdWBqvI8ydYHxrx8iR6xN8o= -github.com/seccomp/libseccomp-golang v0.9.0/go.mod h1:GbW5+tmTXfcxTToHLXlScSlAvWlF4P2Ca7zGrPiEpWo= github.com/seccomp/libseccomp-golang v0.9.1 h1:NJjM5DNFOs0s3kYE1WUOr6G8V97sdt46rlXTMfXGWBo= github.com/seccomp/libseccomp-golang v0.9.1/go.mod h1:GbW5+tmTXfcxTToHLXlScSlAvWlF4P2Ca7zGrPiEpWo= github.com/sirupsen/logrus v0.0.0-20190403091019-9b3cdde74fbe/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= github.com/sirupsen/logrus v1.0.6/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= -github.com/sirupsen/logrus v1.3.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= -github.com/spf13/cobra v0.0.3 h1:ZlrZ4XsMRm04Fr5pSFxBgfND2EBVa1nLpiy1stUsX/8= -github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/cobra v0.0.5 h1:f0B+LkLX6DtmRH1isoNA9VTtNUK9K8xYd28JNNfOv/s= github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= @@ -432,11 +396,8 @@ github.com/uber/jaeger-client-go v2.19.0+incompatible/go.mod h1:WVhlPFC8FDjOFMMW github.com/uber/jaeger-lib v0.0.0-20190122222657-d036253de8f5 h1:CwmGyzHTzCqCdZJkWR0A7ucZXgrCY7spRcpvm7ci//s= github.com/uber/jaeger-lib v0.0.0-20190122222657-d036253de8f5/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U= github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= -github.com/ulikunitz/xz v0.5.5 h1:pFrO0lVpTBXLpYw+pnLj6TbvHuyjXMfjGeCwSqCVwok= -github.com/ulikunitz/xz v0.5.5/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8= github.com/ulikunitz/xz v0.5.6 h1:jGHAfXawEGZQ3blwU5wnWKQJvAraT7Ftq9EXjnXYgt8= github.com/ulikunitz/xz v0.5.6/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8= -github.com/urfave/cli v1.21.0/go.mod h1:lxDj6qX9Q6lWQxIrbrT0nwecwUtRnhVZAJjJZrVUZZQ= github.com/varlink/go v0.0.0-20190502142041-0f1d566d194b h1:hdDRrn9OP/roL8a/e/5Zu85ldrcdndu9IeBj2OEvQm0= github.com/varlink/go v0.0.0-20190502142041-0f1d566d194b/go.mod h1:YHaw8N660ESgMgLOZfLQqT1htFItynAUxMesFBho52s= github.com/vbatts/tar-split v0.11.1 h1:0Odu65rhcZ3JZaPHxl7tCI3V/C/Q9Zf82UFravl02dE= @@ -464,15 +425,12 @@ go.etcd.io/bbolt v1.3.3 h1:MUGmc65QhB3pIlaQ5bB4LwqSj6GIonVJXpZiaKNyaKk= go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.uber.org/atomic v1.4.0 h1:cxzIVoETapQEqDhQu3QfnvXAV4AlzcvUCxkVUFw3+EU= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= -golang.org/x/crypto v0.0.0-20180820150726-614d502a4dac/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181009213950-7c1a557ab941/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181025213731-e84da0312774/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190621222207-cc06ce4a13d4 h1:ydJNl0ENAG67pFbB+9tfhiL2pYqLhfoaZFw/cjLhY4A= -golang.org/x/crypto v0.0.0-20190621222207-cc06ce4a13d4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 h1:HuIa8hRrWRSrqYzx1qI49NNxhdi2PrY7gxVSq1JjLDc= golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -505,7 +463,6 @@ golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180824143301-4910a1d54f87/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -513,7 +470,6 @@ golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190310054646-10058d7d4faa/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -535,8 +491,6 @@ golang.org/x/text v0.3.1-0.20181227161524-e6919f6577db/go.mod h1:bEr9sfX3Q8Zfm5f golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/time v0.0.0-20161028155119-f51c12702a4d/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 h1:SvFZT6jyqRaOeXpc5h/JSfZenJ2O330aBsf7JfSUXmQ= -golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190921001708-c4c64cad1fd0 h1:xQwXv67TxFo9nC1GJFyab5eq/5B590r6RlnL/G8Sz7w= golang.org/x/time v0.0.0-20190921001708-c4c64cad1fd0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -559,8 +513,6 @@ google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoA google.golang.org/genproto v0.0.0-20190620144150-6af8c5fc6601 h1:9VBRTdmgQxbs6HE0sUnMrSWNePppAJU07NYvX5dIB04= google.golang.org/genproto v0.0.0-20190620144150-6af8c5fc6601/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.21.1 h1:j6XxA85m/6txkUCHvzlV5f+HBNl/1r5cZ2A/3IEFOO8= -google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.22.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.24.0 h1:vb/1TCsVn3DcJlQ0Gs1yB1pKI6Do2/QNwxdKqmc/b0s= google.golang.org/grpc v1.24.0/go.mod h1:XDChyiUovWa60DnaeDeZmSW86xtLtjtZbwvSiRnRtcA= @@ -569,6 +521,7 @@ gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLks gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= @@ -585,7 +538,6 @@ gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gotest.tools v0.0.0-20190624233834-05ebafbffc79/go.mod h1:R//lfYlUuTOTfblYI3lGoAAAebUdzjvbmQsuB7Ykd90= -gotest.tools v2.1.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/libpod/boltdb_state_internal.go b/libpod/boltdb_state_internal.go index ed87373e9..3347a3648 100644 --- a/libpod/boltdb_state_internal.go +++ b/libpod/boltdb_state_internal.go @@ -396,7 +396,11 @@ func (s *BoltState) getContainerFromDB(id []byte, ctr *Container, ctrsBkt *bolt. ociRuntime, ok := s.runtime.ociRuntimes[runtimeName] if !ok { - return errors.Wrapf(define.ErrOCIRuntimeUnavailable, "cannot find OCI runtime %q for container %s", ctr.config.OCIRuntime, ctr.ID()) + // Use a MissingRuntime implementation + ociRuntime, err = getMissingRuntime(runtimeName, s.runtime) + if err != nil { + return err + } } ctr.ociRuntime = ociRuntime } diff --git a/libpod/container.go b/libpod/container.go index f36ddbd3f..fc9ef0c86 100644 --- a/libpod/container.go +++ b/libpod/container.go @@ -129,7 +129,7 @@ type Container struct { valid bool lock lock.Locker runtime *Runtime - ociRuntime *OCIRuntime + ociRuntime OCIRuntime rootlessSlirpSyncR *os.File rootlessSlirpSyncW *os.File @@ -1185,3 +1185,12 @@ func (c *Container) HasHealthCheck() bool { func (c *Container) HealthCheckConfig() *manifest.Schema2HealthConfig { return c.config.HealthCheckConfig } + +// AutoRemove indicates whether the container will be removed after it is executed +func (c *Container) AutoRemove() bool { + spec := c.config.Spec + if spec.Annotations == nil { + return false + } + return c.Spec().Annotations[InspectAnnotationAutoremove] == InspectResponseTrue +} diff --git a/libpod/container_api.go b/libpod/container_api.go index 4f0d5301c..1b2d52ce3 100644 --- a/libpod/container_api.go +++ b/libpod/container_api.go @@ -32,9 +32,7 @@ func (c *Container) Init(ctx context.Context) (err error) { } } - if !(c.state.State == define.ContainerStateConfigured || - c.state.State == define.ContainerStateStopped || - c.state.State == define.ContainerStateExited) { + if !c.ensureState(define.ContainerStateConfigured, define.ContainerStateStopped, define.ContainerStateExited) { return errors.Wrapf(define.ErrCtrStateInvalid, "container %s has already been created in runtime", c.ID()) } @@ -176,18 +174,15 @@ func (c *Container) StopWithTimeout(timeout uint) error { } } - if c.state.State == define.ContainerStateConfigured || - c.state.State == define.ContainerStateUnknown || - c.state.State == define.ContainerStatePaused { - return errors.Wrapf(define.ErrCtrStateInvalid, "can only stop created, running, or stopped containers. %s is in state %s", c.ID(), c.state.State.String()) + if c.ensureState(define.ContainerStateStopped, define.ContainerStateExited) { + return define.ErrCtrStopped } - if c.state.State == define.ContainerStateStopped || - c.state.State == define.ContainerStateExited { - return define.ErrCtrStopped + if !c.ensureState(define.ContainerStateCreated, define.ContainerStateRunning) { + return errors.Wrapf(define.ErrCtrStateInvalid, "can only stop created or running containers. %s is in state %s", c.ID(), c.state.State.String()) } - return c.stop(timeout) + return c.stop(timeout, false) } // Kill sends a signal to a container @@ -201,17 +196,20 @@ func (c *Container) Kill(signal uint) error { } } + // TODO: Is killing a paused container OK? if c.state.State != define.ContainerStateRunning { return errors.Wrapf(define.ErrCtrStateInvalid, "can only kill running containers. %s is in state %s", c.ID(), c.state.State.String()) } - defer c.newContainerEvent(events.Kill) - if err := c.ociRuntime.killContainer(c, signal); err != nil { + // Hardcode all = false, we only use all when removing. + if err := c.ociRuntime.KillContainer(c, signal, false); err != nil { return err } c.state.StoppedByUser = true + c.newContainerEvent(events.Kill) + return c.save() } @@ -221,7 +219,7 @@ func (c *Container) Kill(signal uint) error { // Sometimes, the $RUNTIME exec call errors, and if that is the case, the exit code is the exit code of the call. // Otherwise, the exit code will be the exit code of the executed call inside of the container. // TODO investigate allowing exec without attaching -func (c *Container) Exec(tty, privileged bool, env, cmd []string, user, workDir string, streams *AttachStreams, preserveFDs int, resize chan remotecommand.TerminalSize, detachKeys string) (int, error) { +func (c *Container) Exec(tty, privileged bool, env map[string]string, cmd []string, user, workDir string, streams *AttachStreams, preserveFDs uint, resize chan remotecommand.TerminalSize, detachKeys string) (int, error) { var capList []string if !c.batched { c.lock.Lock() @@ -232,10 +230,7 @@ func (c *Container) Exec(tty, privileged bool, env, cmd []string, user, workDir } } - conState := c.state.State - - // TODO can probably relax this once we track exec sessions - if conState != define.ContainerStateRunning { + if c.state.State != define.ContainerStateRunning { return define.ExecErrorCodeCannotInvoke, errors.Wrapf(define.ErrCtrStateInvalid, "cannot exec into container that is not running") } @@ -278,7 +273,19 @@ func (c *Container) Exec(tty, privileged bool, env, cmd []string, user, workDir user = c.config.User } - pid, attachChan, err := c.ociRuntime.execContainer(c, cmd, capList, env, tty, workDir, user, sessionID, streams, preserveFDs, resize, detachKeys) + opts := new(ExecOptions) + opts.Cmd = cmd + opts.CapAdd = capList + opts.Env = env + opts.Terminal = tty + opts.Cwd = workDir + opts.User = user + opts.Streams = streams + opts.PreserveFDs = preserveFDs + opts.Resize = resize + opts.DetachKeys = detachKeys + + pid, attachChan, err := c.ociRuntime.ExecContainer(c, sessionID, opts) if err != nil { ec := define.ExecErrorCodeGeneric // Conmon will pass a non-zero exit code from the runtime as a pid here. @@ -377,11 +384,10 @@ func (c *Container) Attach(streams *AttachStreams, keys string, resize <-chan re c.lock.Unlock() } - if c.state.State != define.ContainerStateCreated && - c.state.State != define.ContainerStateRunning && - c.state.State != define.ContainerStateExited { + if !c.ensureState(define.ContainerStateCreated, define.ContainerStateRunning) { return errors.Wrapf(define.ErrCtrStateInvalid, "can only attach to created or running containers") } + defer c.newContainerEvent(events.Attach) return c.attach(streams, keys, resize, false, nil) } @@ -418,7 +424,7 @@ func (c *Container) Unmount(force bool) error { return errors.Wrapf(err, "can't determine how many times %s is mounted, refusing to unmount", c.ID()) } if mounted == 1 { - if c.state.State == define.ContainerStateRunning || c.state.State == define.ContainerStatePaused { + if c.ensureState(define.ContainerStateRunning, define.ContainerStatePaused) { return errors.Wrapf(define.ErrCtrStateInvalid, "cannot unmount storage for container %s as it is running or paused", c.ID()) } if len(c.state.ExecSessions) != 0 { @@ -524,7 +530,10 @@ func (c *Container) WaitWithInterval(waitTimeout time.Duration) (int32, error) { return -1, define.ErrCtrRemoved } - exitFile := c.exitFilePath() + exitFile, err := c.exitFilePath() + if err != nil { + return -1, err + } chWait := make(chan error, 1) defer close(chWait) @@ -557,7 +566,7 @@ func (c *Container) Cleanup(ctx context.Context) error { } // Check if state is good - if c.state.State == define.ContainerStateRunning || c.state.State == define.ContainerStatePaused { + if !c.ensureState(define.ContainerStateConfigured, define.ContainerStateCreated, define.ContainerStateStopped, define.ContainerStateExited) { return errors.Wrapf(define.ErrCtrStateInvalid, "container %s is running or paused, refusing to clean up", c.ID()) } @@ -635,11 +644,9 @@ func (c *Container) Sync() error { // If runtime knows about the container, update its status in runtime // And then save back to disk - if (c.state.State != define.ContainerStateUnknown) && - (c.state.State != define.ContainerStateConfigured) && - (c.state.State != define.ContainerStateExited) { + if c.ensureState(define.ContainerStateCreated, define.ContainerStateRunning, define.ContainerStatePaused, define.ContainerStateStopped) { oldState := c.state.State - if err := c.ociRuntime.updateContainerStatus(c, true); err != nil { + if err := c.ociRuntime.UpdateContainerStatus(c); err != nil { return err } // Only save back to DB if state changed @@ -649,6 +656,7 @@ func (c *Container) Sync() error { } } } + defer c.newContainerEvent(events.Sync) return nil } @@ -687,7 +695,7 @@ func (c *Container) Refresh(ctx context.Context) error { // Next, if the container is running, stop it if c.state.State == define.ContainerStateRunning { - if err := c.stop(c.config.StopTimeout); err != nil { + if err := c.stop(c.config.StopTimeout, false); err != nil { return err } } @@ -696,8 +704,10 @@ func (c *Container) Refresh(ctx context.Context) error { if len(c.state.ExecSessions) > 0 { logrus.Infof("Killing %d exec sessions in container %s. They will not be restored after refresh.", len(c.state.ExecSessions), c.ID()) - if err := c.ociRuntime.execStopContainer(c, c.config.StopTimeout); err != nil { - return err + } + for _, session := range c.state.ExecSessions { + if err := c.ociRuntime.ExecStopContainer(c, session.ID, c.StopTimeout()); err != nil { + return errors.Wrapf(err, "error stopping exec session %s of container %s", session.ID, c.ID()) } } @@ -821,12 +831,3 @@ func (c *Container) Restore(ctx context.Context, options ContainerCheckpointOpti defer c.newContainerEvent(events.Restore) return c.restore(ctx, options) } - -// AutoRemove indicates whether the container will be removed after it is executed -func (c *Container) AutoRemove() bool { - spec := c.config.Spec - if spec.Annotations == nil { - return false - } - return c.Spec().Annotations[InspectAnnotationAutoremove] == InspectResponseTrue -} diff --git a/libpod/container_commit.go b/libpod/container_commit.go index 570d406b7..d5afe0da7 100644 --- a/libpod/container_commit.go +++ b/libpod/container_commit.go @@ -50,11 +50,11 @@ func (c *Container) Commit(ctx context.Context, destImage string, options Contai } if c.state.State == define.ContainerStateRunning && options.Pause { - if err := c.ociRuntime.pauseContainer(c); err != nil { + if err := c.pause(); err != nil { return nil, errors.Wrapf(err, "error pausing container %q", c.ID()) } defer func() { - if err := c.ociRuntime.unpauseContainer(c); err != nil { + if err := c.unpause(); err != nil { logrus.Errorf("error unpausing container %q: %v", c.ID(), err) } }() diff --git a/libpod/container_inspect.go b/libpod/container_inspect.go index 5a92b3e54..70b51960b 100644 --- a/libpod/container_inspect.go +++ b/libpod/container_inspect.go @@ -96,7 +96,7 @@ type InspectContainerData struct { Path string `json:"Path"` Args []string `json:"Args"` State *InspectContainerState `json:"State"` - ImageID string `json:"Image"` + Image string `json:"Image"` ImageName string `json:"ImageName"` Rootfs string `json:"Rootfs"` Pod string `json:"Pod"` @@ -718,7 +718,7 @@ func (c *Container) getContainerInspectData(size bool, driverData *driver.Data) StartedAt: runtimeInfo.StartedTime, FinishedAt: runtimeInfo.FinishedTime, }, - ImageID: config.RootfsImageID, + Image: config.RootfsImageID, ImageName: config.RootfsImageName, ExitCommand: config.ExitCommand, Namespace: config.Namespace, diff --git a/libpod/container_internal.go b/libpod/container_internal.go index ac921d737..028d7601d 100644 --- a/libpod/container_internal.go +++ b/libpod/container_internal.go @@ -131,13 +131,13 @@ func (c *Container) CheckpointPath() string { } // AttachSocketPath retrieves the path of the container's attach socket -func (c *Container) AttachSocketPath() string { - return filepath.Join(c.ociRuntime.socketsDir, c.ID(), "attach") +func (c *Container) AttachSocketPath() (string, error) { + return c.ociRuntime.AttachSocketPath(c) } // exitFilePath gets the path to the container's exit file -func (c *Container) exitFilePath() string { - return filepath.Join(c.ociRuntime.exitsDir, c.ID()) +func (c *Container) exitFilePath() (string, error) { + return c.ociRuntime.ExitFilePath(c) } // create a bundle path and associated files for an exec session @@ -167,12 +167,8 @@ func (c *Container) cleanupExecBundle(sessionID string) error { if err := os.RemoveAll(c.execBundlePath(sessionID)); err != nil && !os.IsNotExist(err) { return err } - // Clean up the sockets dir. Issue #3962 - // Also ignore if it doesn't exist for some reason; hence the conditional return below - if err := os.RemoveAll(filepath.Join(c.ociRuntime.socketsDir, sessionID)); err != nil && !os.IsNotExist(err) { - return err - } - return nil + + return c.ociRuntime.ExecContainerCleanup(c, sessionID) } // the path to a containers exec session bundle @@ -191,8 +187,8 @@ func (c *Container) execLogPath(sessionID string) string { } // the socket conmon creates for an exec session -func (c *Container) execAttachSocketPath(sessionID string) string { - return filepath.Join(c.ociRuntime.socketsDir, sessionID, "attach") +func (c *Container) execAttachSocketPath(sessionID string) (string, error) { + return c.ociRuntime.ExecAttachSocketPath(c, sessionID) } // execExitFileDir gets the path to the container's exit file @@ -202,7 +198,7 @@ func (c *Container) execExitFileDir(sessionID string) string { // execOCILog returns the file path for the exec sessions oci log func (c *Container) execOCILog(sessionID string) string { - if !c.ociRuntime.supportsJSON { + if !c.ociRuntime.SupportsJSONErrors() { return "" } return filepath.Join(c.execBundlePath(sessionID), "oci-log") @@ -233,12 +229,15 @@ func (c *Container) readExecExitCode(sessionID string) (int, error) { // Wait for the container's exit file to appear. // When it does, update our state based on it. func (c *Container) waitForExitFileAndSync() error { - exitFile := c.exitFilePath() + exitFile, err := c.exitFilePath() + if err != nil { + return err + } chWait := make(chan error) defer close(chWait) - _, err := WaitForFile(exitFile, chWait, time.Second*5) + _, err = WaitForFile(exitFile, chWait, time.Second*5) if err != nil { // Exit file did not appear // Reset our state @@ -253,7 +252,7 @@ func (c *Container) waitForExitFileAndSync() error { return err } - if err := c.ociRuntime.updateContainerStatus(c, false); err != nil { + if err := c.checkExitFile(); err != nil { return err } @@ -329,7 +328,7 @@ func (c *Container) handleRestartPolicy(ctx context.Context) (restarted bool, er // Is the container running again? // If so, we don't have to do anything - if c.state.State == define.ContainerStateRunning || c.state.State == define.ContainerStatePaused { + if c.ensureState(define.ContainerStateRunning, define.ContainerStatePaused) { return false, nil } else if c.state.State == define.ContainerStateUnknown { return false, errors.Wrapf(define.ErrInternal, "invalid container state encountered in restart attempt!") @@ -360,8 +359,7 @@ func (c *Container) handleRestartPolicy(ctx context.Context) (restarted bool, er if err := c.reinit(ctx, true); err != nil { return false, err } - } else if c.state.State == define.ContainerStateConfigured || - c.state.State == define.ContainerStateExited { + } else if c.ensureState(define.ContainerStateConfigured, define.ContainerStateExited) { // Initialize the container if err := c.init(ctx, true); err != nil { return false, err @@ -373,6 +371,18 @@ func (c *Container) handleRestartPolicy(ctx context.Context) (restarted bool, er return true, nil } +// Ensure that the container is in a specific state or state. +// Returns true if the container is in one of the given states, +// or false otherwise. +func (c *Container) ensureState(states ...define.ContainerStatus) bool { + for _, state := range states { + if state == c.state.State { + return true + } + } + return false +} + // Sync this container with on-disk state and runtime status // Should only be called with container lock held // This function should suffice to ensure a container's state is accurate and @@ -383,14 +393,13 @@ func (c *Container) syncContainer() error { } // If runtime knows about the container, update its status in runtime // And then save back to disk - if (c.state.State != define.ContainerStateUnknown) && - (c.state.State != define.ContainerStateConfigured) && - (c.state.State != define.ContainerStateExited) { + if c.ensureState(define.ContainerStateCreated, define.ContainerStateRunning, define.ContainerStateStopped, define.ContainerStatePaused) { oldState := c.state.State - // TODO: optionally replace this with a stat for the exit file - if err := c.ociRuntime.updateContainerStatus(c, false); err != nil { + + if err := c.checkExitFile(); err != nil { return err } + // Only save back to DB if state changed if c.state.State != oldState { // Check for a restart policy match @@ -516,7 +525,7 @@ func (c *Container) setupStorage(ctx context.Context) error { // Tear down a container's storage prior to removal func (c *Container) teardownStorage() error { - if c.state.State == define.ContainerStateRunning || c.state.State == define.ContainerStatePaused { + if c.ensureState(define.ContainerStateRunning, define.ContainerStatePaused) { return errors.Wrapf(define.ErrCtrStateInvalid, "cannot remove storage for container %s as it is running or paused", c.ID()) } @@ -534,7 +543,7 @@ func (c *Container) teardownStorage() error { // error - we wanted it gone, it is already gone. // Potentially another tool using containers/storage already // removed it? - if err == storage.ErrNotAContainer || err == storage.ErrContainerUnknown { + if errors.Cause(err) == storage.ErrNotAContainer || errors.Cause(err) == storage.ErrContainerUnknown { logrus.Warnf("Storage for container %s already removed", c.ID()) return nil } @@ -649,7 +658,10 @@ func (c *Container) removeConmonFiles() error { } // Remove the exit file so we don't leak memory in tmpfs - exitFile := filepath.Join(c.ociRuntime.exitsDir, c.ID()) + exitFile, err := c.exitFilePath() + if err != nil { + return err + } if err := os.Remove(exitFile); err != nil && !os.IsNotExist(err) { return errors.Wrapf(err, "error removing container %s exit file", c.ID()) } @@ -718,10 +730,7 @@ func (c *Container) save() error { // Otherwise, this function will return with error if there are dependencies of this container that aren't running. func (c *Container) prepareToStart(ctx context.Context, recursive bool) (err error) { // Container must be created or stopped to be started - if !(c.state.State == define.ContainerStateConfigured || - c.state.State == define.ContainerStateCreated || - c.state.State == define.ContainerStateStopped || - c.state.State == define.ContainerStateExited) { + if !c.ensureState(define.ContainerStateConfigured, define.ContainerStateCreated, define.ContainerStateStopped, define.ContainerStateExited) { return errors.Wrapf(define.ErrCtrStateInvalid, "container %s must be in Created or Stopped state to be started", c.ID()) } @@ -752,8 +761,7 @@ func (c *Container) prepareToStart(ctx context.Context, recursive bool) (err err if err := c.reinit(ctx, false); err != nil { return err } - } else if c.state.State == define.ContainerStateConfigured || - c.state.State == define.ContainerStateExited { + } else if c.ensureState(define.ContainerStateConfigured, define.ContainerStateExited) { // Or initialize it if necessary if err := c.init(ctx, false); err != nil { return err @@ -938,9 +946,13 @@ func (c *Container) init(ctx context.Context, retainRetries bool) error { } // With the spec complete, do an OCI create - if err := c.ociRuntime.createContainer(c, nil); err != nil { + if err := c.ociRuntime.CreateContainer(c, nil); err != nil { + // Fedora 31 is carrying a patch to display improved error + // messages to better handle the V2 transition. This is NOT + // upstream in any OCI runtime. + // TODO: Remove once runc supports cgroupsv2 if strings.Contains(err.Error(), "this version of runc doesn't work on cgroups v2") { - logrus.Errorf("oci runtime %q does not support CGroups V2: use system migrate to mitigate", c.ociRuntime.name) + logrus.Errorf("oci runtime %q does not support CGroups V2: use system migrate to mitigate", c.ociRuntime.Name()) } return err } @@ -980,7 +992,7 @@ func (c *Container) cleanupRuntime(ctx context.Context) error { // If the container is not ContainerStateStopped or // ContainerStateCreated, do nothing. - if c.state.State != define.ContainerStateStopped && c.state.State != define.ContainerStateCreated { + if !c.ensureState(define.ContainerStateStopped, define.ContainerStateCreated) { return nil } @@ -1071,8 +1083,7 @@ func (c *Container) initAndStart(ctx context.Context) (err error) { if err := c.reinit(ctx, false); err != nil { return err } - } else if c.state.State == define.ContainerStateConfigured || - c.state.State == define.ContainerStateExited { + } else if c.ensureState(define.ContainerStateConfigured, define.ContainerStateExited) { if err := c.init(ctx, false); err != nil { return err } @@ -1088,7 +1099,7 @@ func (c *Container) start() error { logrus.Debugf("Starting container %s with command %v", c.ID(), c.config.Spec.Process.Args) } - if err := c.ociRuntime.startContainer(c); err != nil { + if err := c.ociRuntime.StartContainer(c); err != nil { return err } logrus.Debugf("Started container %s", c.ID()) @@ -1110,10 +1121,28 @@ func (c *Container) start() error { } // Internal, non-locking function to stop container -func (c *Container) stop(timeout uint) error { +func (c *Container) stop(timeout uint, all bool) error { logrus.Debugf("Stopping ctr %s (timeout %d)", c.ID(), timeout) - if err := c.ociRuntime.stopContainer(c, timeout); err != nil { + // We can't use --all if CGroups aren't present. + // Rootless containers with CGroups v1 and NoCgroups are both cases + // where this can happen. + if all { + if c.config.NoCgroups { + all = false + } else if rootless.IsRootless() { + // Only do this check if we need to + unified, err := cgroups.IsCgroup2UnifiedMode() + if err != nil { + return err + } + if !unified { + all = false + } + } + } + + if err := c.ociRuntime.StopContainer(c, timeout, all); err != nil { return err } @@ -1150,7 +1179,7 @@ func (c *Container) pause() error { } } - if err := c.ociRuntime.pauseContainer(c); err != nil { + if err := c.ociRuntime.PauseContainer(c); err != nil { return err } @@ -1167,7 +1196,7 @@ func (c *Container) unpause() error { return errors.Wrapf(define.ErrNoCgroups, "cannot unpause without using CGroups") } - if err := c.ociRuntime.unpauseContainer(c); err != nil { + if err := c.ociRuntime.UnpauseContainer(c); err != nil { return err } @@ -1180,7 +1209,7 @@ func (c *Container) unpause() error { // Internal, non-locking function to restart a container func (c *Container) restartWithTimeout(ctx context.Context, timeout uint) (err error) { - if c.state.State == define.ContainerStateUnknown || c.state.State == define.ContainerStatePaused { + if !c.ensureState(define.ContainerStateConfigured, define.ContainerStateCreated, define.ContainerStateRunning, define.ContainerStateStopped, define.ContainerStateExited) { return errors.Wrapf(define.ErrCtrStateInvalid, "unable to restart a container in a paused or unknown state") } @@ -1188,7 +1217,7 @@ func (c *Container) restartWithTimeout(ctx context.Context, timeout uint) (err e if c.state.State == define.ContainerStateRunning { conmonPID := c.state.ConmonPID - if err := c.stop(timeout); err != nil { + if err := c.stop(timeout, false); err != nil { return err } // Old versions of conmon have a bug where they create the exit file before @@ -1475,7 +1504,7 @@ func (c *Container) delete(ctx context.Context) (err error) { span.SetTag("struct", "container") defer span.Finish() - if err := c.ociRuntime.deleteContainer(c); err != nil { + if err := c.ociRuntime.DeleteContainer(c); err != nil { return errors.Wrapf(err, "error removing container %s from runtime", c.ID()) } @@ -1708,9 +1737,8 @@ func (c *Container) checkReadyForRemoval() error { return errors.Wrapf(define.ErrCtrStateInvalid, "container %s is in invalid state", c.ID()) } - if c.state.State == define.ContainerStateRunning || - c.state.State == define.ContainerStatePaused { - return errors.Wrapf(define.ErrCtrStateInvalid, "cannot remove container %s as it is %s - running or paused containers cannot be removed", c.ID(), c.state.State.String()) + if c.ensureState(define.ContainerStateRunning, define.ContainerStatePaused) { + return errors.Wrapf(define.ErrCtrStateInvalid, "cannot remove container %s as it is %s - running or paused containers cannot be removed without force", c.ID(), c.state.State.String()) } if len(c.state.ExecSessions) != 0 { @@ -1787,3 +1815,35 @@ func (c *Container) sortUserVolumes(ctrSpec *spec.Spec) ([]*ContainerNamedVolume } return namedUserVolumes, userMounts } + +// Check for an exit file, and handle one if present +func (c *Container) checkExitFile() error { + // If the container's not running, nothing to do. + if !c.ensureState(define.ContainerStateRunning, define.ContainerStatePaused) { + return nil + } + + exitFile, err := c.exitFilePath() + if err != nil { + return err + } + + // Check for the exit file + info, err := os.Stat(exitFile) + if err != nil { + if os.IsNotExist(err) { + // Container is still running, no error + return nil + } + + return errors.Wrapf(err, "error running stat on container %s exit file", c.ID()) + } + + // Alright, it exists. Transition to Stopped state. + c.state.State = define.ContainerStateStopped + c.state.PID = 0 + c.state.ConmonPID = 0 + + // Read the exit file to get our stopped time and exit code. + return c.handleExitFile(exitFile, info) +} diff --git a/libpod/container_internal_linux.go b/libpod/container_internal_linux.go index 2636fdb6c..283d38a0f 100644 --- a/libpod/container_internal_linux.go +++ b/libpod/container_internal_linux.go @@ -419,27 +419,11 @@ func (c *Container) generateSpec(ctx context.Context) (*spec.Spec, error) { g.AddProcessEnv("container", "libpod") } - unified, err := cgroups.IsCgroup2UnifiedMode() + cgroupPath, err := c.getOCICgroupPath() if err != nil { return nil, err } - if (rootless.IsRootless() && !unified) || c.config.NoCgroups { - g.SetLinuxCgroupsPath("") - } else if c.runtime.config.CgroupManager == SystemdCgroupsManager { - // When runc is set to use Systemd as a cgroup manager, it - // expects cgroups to be passed as follows: - // slice:prefix:name - systemdCgroups := fmt.Sprintf("%s:libpod:%s", path.Base(c.config.CgroupParent), c.ID()) - logrus.Debugf("Setting CGroups for container %s to %s", c.ID(), systemdCgroups) - g.SetLinuxCgroupsPath(systemdCgroups) - } else { - cgroupPath, err := c.CGroupPath() - if err != nil { - return nil, err - } - logrus.Debugf("Setting CGroup path for container %s to %s", c.ID(), cgroupPath) - g.SetLinuxCgroupsPath(cgroupPath) - } + g.SetLinuxCgroupsPath(cgroupPath) // Mounts need to be sorted so paths will not cover other paths mounts := sortMounts(g.Mounts()) @@ -566,6 +550,7 @@ func (c *Container) setupSystemd(mounts []spec.Mount, g generate.Generator) erro Options: []string{"bind", "nodev", "noexec", "nosuid"}, } g.AddMount(systemdMnt) + g.AddLinuxMaskedPaths("/sys/fs/cgroup/systemd/release_agent") } return nil @@ -659,7 +644,7 @@ func (c *Container) checkpointRestoreSupported() (err error) { if !criu.CheckForCriu() { return errors.Errorf("Checkpoint/Restore requires at least CRIU %d", criu.MinCriuVersion) } - if !c.ociRuntime.featureCheckCheckpointing() { + if !c.ociRuntime.SupportsCheckpoint() { return errors.Errorf("Configured runtime does not support checkpoint/restore") } return nil @@ -695,7 +680,7 @@ func (c *Container) checkpoint(ctx context.Context, options ContainerCheckpointO return err } - if err := c.ociRuntime.checkpointContainer(c, options); err != nil { + if err := c.ociRuntime.CheckpointContainer(c, options); err != nil { return err } @@ -923,7 +908,7 @@ func (c *Container) restore(ctx context.Context, options ContainerCheckpointOpti } } - if err := c.ociRuntime.createContainer(c, &options); err != nil { + if err := c.ociRuntime.CreateContainer(c, &options); err != nil { return err } @@ -1332,3 +1317,30 @@ func (c *Container) refreshCNI() error { podNetwork := c.runtime.getPodNetwork(c.ID(), c.config.Name, "", c.config.Networks, c.config.PortMappings, c.config.StaticIP) return c.runtime.netPlugin.TearDownPod(podNetwork) } + +// Get cgroup path in a format suitable for the OCI spec +func (c *Container) getOCICgroupPath() (string, error) { + unified, err := cgroups.IsCgroup2UnifiedMode() + if err != nil { + return "", err + } + if (rootless.IsRootless() && !unified) || c.config.NoCgroups { + return "", nil + } else if c.runtime.config.CgroupManager == SystemdCgroupsManager { + // When runc is set to use Systemd as a cgroup manager, it + // expects cgroups to be passed as follows: + // slice:prefix:name + systemdCgroups := fmt.Sprintf("%s:libpod:%s", path.Base(c.config.CgroupParent), c.ID()) + logrus.Debugf("Setting CGroups for container %s to %s", c.ID(), systemdCgroups) + return systemdCgroups, nil + } else if c.runtime.config.CgroupManager == CgroupfsCgroupsManager { + cgroupPath, err := c.CGroupPath() + if err != nil { + return "", err + } + logrus.Debugf("Setting CGroup path for container %s to %s", c.ID(), cgroupPath) + return cgroupPath, nil + } else { + return "", errors.Wrapf(define.ErrInvalidArg, "invalid cgroup manager %s requested", c.runtime.config.CgroupManager) + } +} diff --git a/libpod/container_internal_unsupported.go b/libpod/container_internal_unsupported.go index 05a587c59..4abaa6362 100644 --- a/libpod/container_internal_unsupported.go +++ b/libpod/container_internal_unsupported.go @@ -44,3 +44,7 @@ func (c *Container) copyOwnerAndPerms(source, dest string) error { func (c *Container) refreshCNI() error { return define.ErrNotImplemented } + +func (c *Container) getOCICgroupPath() (string, error) { + return "", define.ErrNotImplemented +} diff --git a/libpod/define/errors.go b/libpod/define/errors.go index 5392fbc62..523062866 100644 --- a/libpod/define/errors.go +++ b/libpod/define/errors.go @@ -65,6 +65,10 @@ var ( // CGroup. ErrNoCgroups = errors.New("this container does not have a cgroup") + // ErrRootless indicates that the given command cannot but run without + // root. + ErrRootless = errors.New("operation requires root privileges") + // ErrRuntimeStopped indicates that the runtime has already been shut // down and no further operations can be performed on it ErrRuntimeStopped = errors.New("runtime has already been stopped") diff --git a/libpod/healthcheck.go b/libpod/healthcheck.go index 0338828e4..68ffc2349 100644 --- a/libpod/healthcheck.go +++ b/libpod/healthcheck.go @@ -141,7 +141,7 @@ func (c *Container) runHealthCheck() (HealthCheckStatus, error) { logrus.Debugf("executing health check command %s for %s", strings.Join(newCommand, " "), c.ID()) timeStart := time.Now() hcResult := HealthCheckSuccess - _, hcErr := c.Exec(false, false, []string{}, newCommand, "", "", streams, 0, nil, "") + _, hcErr := c.Exec(false, false, map[string]string{}, newCommand, "", "", streams, 0, nil, "") if hcErr != nil { errCause := errors.Cause(hcErr) hcResult = HealthCheckFailure diff --git a/libpod/info.go b/libpod/info.go index 297086ebb..e5c075d97 100644 --- a/libpod/info.go +++ b/libpod/info.go @@ -15,7 +15,6 @@ import ( "github.com/containers/buildah" "github.com/containers/libpod/pkg/cgroups" "github.com/containers/libpod/pkg/rootless" - "github.com/containers/libpod/utils" "github.com/containers/storage" "github.com/containers/storage/pkg/system" "github.com/pkg/errors" @@ -48,14 +47,7 @@ func (r *Runtime) hostInfo() (map[string]interface{}, error) { info["MemFree"] = mi.MemFree info["SwapTotal"] = mi.SwapTotal info["SwapFree"] = mi.SwapFree - conmonVersion, _ := r.GetConmonVersion() - ociruntimeVersion, _ := r.GetOCIRuntimeVersion() hostDistributionInfo := r.GetHostDistributionInfo() - info["Conmon"] = map[string]interface{}{ - "path": r.conmonPath, - "package": r.defaultOCIRuntime.conmonPackage(), - "version": conmonVersion, - } if rootless.IsRootless() { if path, err := exec.LookPath("slirp4netns"); err == nil { logrus.Warnf("Failed to retrieve program version for %s: %v", path, err) @@ -69,11 +61,18 @@ func (r *Runtime) hostInfo() (map[string]interface{}, error) { program["Package"] = packageVersion(path) info["slirp4netns"] = program } - } - info["OCIRuntime"] = map[string]interface{}{ - "path": r.defaultOCIRuntime.path, - "package": r.defaultOCIRuntime.pathPackage(), - "version": ociruntimeVersion, + uidmappings, err := rootless.ReadMappingsProc("/proc/self/uid_map") + if err != nil { + return nil, errors.Wrapf(err, "error reading uid mappings") + } + gidmappings, err := rootless.ReadMappingsProc("/proc/self/gid_map") + if err != nil { + return nil, errors.Wrapf(err, "error reading gid mappings") + } + idmappings := make(map[string]interface{}) + idmappings["uidmap"] = uidmappings + idmappings["gidmap"] = gidmappings + info["IDMappings"] = idmappings } info["Distribution"] = map[string]interface{}{ "distribution": hostDistributionInfo["Distribution"], @@ -86,6 +85,15 @@ func (r *Runtime) hostInfo() (map[string]interface{}, error) { } info["kernel"] = kv + runtimeInfo, err := r.defaultOCIRuntime.RuntimeInfo() + if err != nil { + logrus.Errorf("Error getting info on OCI runtime %s: %v", r.defaultOCIRuntime.Name(), err) + } else { + for k, v := range runtimeInfo { + info[k] = v + } + } + up, err := readUptime() if err != nil { return nil, errors.Wrapf(err, "error reading up time") @@ -128,6 +136,7 @@ func (r *Runtime) hostInfo() (map[string]interface{}, error) { } info["hostname"] = host info["eventlogger"] = r.eventer.String() + return info, nil } @@ -215,29 +224,6 @@ func readUptime() (string, error) { return string(f[0]), nil } -// GetConmonVersion returns a string representation of the conmon version -func (r *Runtime) GetConmonVersion() (string, error) { - output, err := utils.ExecCmd(r.conmonPath, "--version") - if err != nil { - return "", err - } - return strings.TrimSuffix(strings.Replace(output, "\n", ", ", 1), "\n"), nil -} - -// GetOCIRuntimePath returns the path to the OCI Runtime Path the runtime is using -func (r *Runtime) GetOCIRuntimePath() string { - return r.defaultOCIRuntime.path -} - -// GetOCIRuntimeVersion returns a string representation of the oci runtimes version -func (r *Runtime) GetOCIRuntimeVersion() (string, error) { - output, err := utils.ExecCmd(r.GetOCIRuntimePath(), "--version") - if err != nil { - return "", err - } - return strings.TrimSuffix(output, "\n"), nil -} - // GetHostDistributionInfo returns a map containing the host's distribution and version func (r *Runtime) GetHostDistributionInfo() map[string]string { dist := make(map[string]string) diff --git a/libpod/networking_linux.go b/libpod/networking_linux.go index 61ab57d65..daa0619a2 100644 --- a/libpod/networking_linux.go +++ b/libpod/networking_linux.go @@ -5,6 +5,7 @@ package libpod import ( "crypto/rand" "fmt" + "io/ioutil" "net" "os" "os/exec" @@ -131,7 +132,7 @@ func checkSlirpFlags(path string) (bool, bool, bool, error) { cmd := exec.Command(path, "--help") out, err := cmd.CombinedOutput() if err != nil { - return false, false, false, err + return false, false, false, errors.Wrapf(err, "slirp4netns %q", out) } return strings.Contains(string(out), "--disable-host-loopback"), strings.Contains(string(out), "--mtu"), strings.Contains(string(out), "--enable-sandbox"), nil } @@ -157,7 +158,8 @@ func (r *Runtime) setupRootlessNetNS(ctr *Container) (err error) { defer errorhandling.CloseQuiet(syncW) havePortMapping := len(ctr.Config().PortMappings) > 0 - apiSocket := filepath.Join(ctr.ociRuntime.tmpDir, fmt.Sprintf("%s.net", ctr.config.ID)) + apiSocket := filepath.Join(ctr.runtime.config.TmpDir, fmt.Sprintf("%s.net", ctr.config.ID)) + logPath := filepath.Join(ctr.runtime.config.TmpDir, fmt.Sprintf("slirp4netns-%s.log", ctr.config.ID)) cmdArgs := []string{} if havePortMapping { @@ -165,7 +167,7 @@ func (r *Runtime) setupRootlessNetNS(ctr *Container) (err error) { } dhp, mtu, sandbox, err := checkSlirpFlags(path) if err != nil { - return errors.Wrapf(err, "error checking slirp4netns binary %s", path) + return errors.Wrapf(err, "error checking slirp4netns binary %s: %q", path, err) } if dhp { cmdArgs = append(cmdArgs, "--disable-host-loopback") @@ -210,6 +212,18 @@ func (r *Runtime) setupRootlessNetNS(ctr *Container) (err error) { // Leak one end of the pipe in slirp4netns, the other will be sent to conmon cmd.ExtraFiles = append(cmd.ExtraFiles, ctr.rootlessSlirpSyncR, syncW) + logFile, err := os.Create(logPath) + if err != nil { + return errors.Wrapf(err, "failed to open slirp4netns log file %s", logPath) + } + defer logFile.Close() + // Unlink immediately the file so we won't need to worry about cleaning it up later. + // It is still accessible through the open fd logFile. + if err := os.Remove(logPath); err != nil { + return errors.Wrapf(err, "delete file %s", logPath) + } + cmd.Stdout = logFile + cmd.Stderr = logFile if err := cmd.Start(); err != nil { return errors.Wrapf(err, "failed to start slirp4netns process") } @@ -238,7 +252,15 @@ func (r *Runtime) setupRootlessNetNS(ctr *Container) (err error) { continue } if status.Exited() { - return errors.New("slirp4netns failed") + // Seek at the beginning of the file and read all its content + if _, err := logFile.Seek(0, 0); err != nil { + logrus.Errorf("could not seek log file: %q", err) + } + logContent, err := ioutil.ReadAll(logFile) + if err != nil { + return errors.Wrapf(err, "slirp4netns failed") + } + return errors.Errorf("slirp4netns failed: %q", logContent) } if status.Signaled() { return errors.New("slirp4netns killed by signal") @@ -462,6 +484,12 @@ func getContainerNetNS(ctr *Container) (string, error) { func getContainerNetIO(ctr *Container) (*netlink.LinkStatistics, error) { var netStats *netlink.LinkStatistics + // rootless v2 cannot seem to resolve its network connection to + // collect statistics. For now, we allow stats to at least run + // by returning nil + if rootless.IsRootless() { + return netStats, nil + } netNSPath, netPathErr := getContainerNetNS(ctr) if netPathErr != nil { return nil, netPathErr diff --git a/libpod/oci.go b/libpod/oci.go index 9879fa90e..9e761788e 100644 --- a/libpod/oci.go +++ b/libpod/oci.go @@ -1,441 +1,132 @@ package libpod import ( - "bytes" - "fmt" - "io/ioutil" - "net" - "os" - "os/exec" - "path/filepath" - "strings" - "time" - - "github.com/containers/libpod/libpod/define" - "github.com/containers/libpod/pkg/util" - "github.com/cri-o/ocicni/pkg/ocicni" - spec "github.com/opencontainers/runtime-spec/specs-go" - "github.com/opencontainers/selinux/go-selinux/label" - "github.com/pkg/errors" - "github.com/sirupsen/logrus" - - // TODO import these functions into libpod and remove the import - // Trying to keep libpod from depending on CRI-O code - "github.com/containers/libpod/utils" -) - -// OCI code is undergoing heavy rewrite - -const ( - // CgroupfsCgroupsManager represents cgroupfs native cgroup manager - CgroupfsCgroupsManager = "cgroupfs" - // SystemdCgroupsManager represents systemd native cgroup manager - SystemdCgroupsManager = "systemd" - - // ContainerCreateTimeout represents the value of container creating timeout - ContainerCreateTimeout = 240 * time.Second - - // Timeout before declaring that runtime has failed to kill a given - // container - killContainerTimeout = 5 * time.Second - // DefaultShmSize is the default shm size - DefaultShmSize = 64 * 1024 * 1024 - // NsRunDir is the default directory in which running network namespaces - // are stored - NsRunDir = "/var/run/netns" + "k8s.io/client-go/tools/remotecommand" ) -// OCIRuntime represents an OCI-compatible runtime that libpod can call into -// to perform container operations -type OCIRuntime struct { - name string - path string - conmonPath string - conmonEnv []string - cgroupManager string - tmpDir string - exitsDir string - socketsDir string - logSizeMax int64 - noPivot bool - reservePorts bool - supportsJSON bool - supportsNoCgroups bool - sdNotify bool -} - -// ociError is used to parse the OCI runtime JSON log. It is not part of the -// OCI runtime specifications, it follows what runc does -type ociError struct { - Level string `json:"level,omitempty"` - Time string `json:"time,omitempty"` - Msg string `json:"msg,omitempty"` -} - -// Make a new OCI runtime with provided options. -// The first path that points to a valid executable will be used. -func newOCIRuntime(name string, paths []string, conmonPath string, runtimeCfg *RuntimeConfig, supportsJSON, supportsNoCgroups bool) (*OCIRuntime, error) { - if name == "" { - return nil, errors.Wrapf(define.ErrInvalidArg, "the OCI runtime must be provided a non-empty name") - } - - runtime := new(OCIRuntime) - runtime.name = name - runtime.conmonPath = conmonPath - - runtime.conmonEnv = runtimeCfg.ConmonEnvVars - runtime.cgroupManager = runtimeCfg.CgroupManager - runtime.tmpDir = runtimeCfg.TmpDir - runtime.logSizeMax = runtimeCfg.MaxLogSize - runtime.noPivot = runtimeCfg.NoPivotRoot - runtime.reservePorts = runtimeCfg.EnablePortReservation - runtime.sdNotify = runtimeCfg.SDNotify - - // TODO: probe OCI runtime for feature and enable automatically if - // available. - runtime.supportsJSON = supportsJSON - runtime.supportsNoCgroups = supportsNoCgroups - - foundPath := false - for _, path := range paths { - stat, err := os.Stat(path) - if err != nil { - if os.IsNotExist(err) { - continue - } - return nil, errors.Wrapf(err, "cannot stat %s", path) - } - if !stat.Mode().IsRegular() { - continue - } - foundPath = true - runtime.path = path - logrus.Debugf("using runtime %q", path) - break - } - - // Search the $PATH as last fallback - if !foundPath { - if foundRuntime, err := exec.LookPath(name); err == nil { - foundPath = true - runtime.path = foundRuntime - logrus.Debugf("using runtime %q from $PATH: %q", name, foundRuntime) - } - } - - if !foundPath { - return nil, errors.Wrapf(define.ErrInvalidArg, "no valid executable found for OCI runtime %s", name) - } - - runtime.exitsDir = filepath.Join(runtime.tmpDir, "exits") - runtime.socketsDir = filepath.Join(runtime.tmpDir, "socket") - - if runtime.cgroupManager != CgroupfsCgroupsManager && runtime.cgroupManager != SystemdCgroupsManager { - return nil, errors.Wrapf(define.ErrInvalidArg, "invalid cgroup manager specified: %s", runtime.cgroupManager) - } - - // Create the exit files and attach sockets directories - if err := os.MkdirAll(runtime.exitsDir, 0750); err != nil { - // The directory is allowed to exist - if !os.IsExist(err) { - return nil, errors.Wrapf(err, "error creating OCI runtime exit files directory %s", - runtime.exitsDir) - } - } - if err := os.MkdirAll(runtime.socketsDir, 0750); err != nil { - // The directory is allowed to exist - if !os.IsExist(err) { - return nil, errors.Wrapf(err, "error creating OCI runtime attach sockets directory %s", - runtime.socketsDir) - } - } - - return runtime, nil -} - -// Create systemd unit name for cgroup scopes -func createUnitName(prefix string, name string) string { - return fmt.Sprintf("%s-%s.scope", prefix, name) -} - -func bindPorts(ports []ocicni.PortMapping) ([]*os.File, error) { - var files []*os.File - notifySCTP := false - for _, i := range ports { - switch i.Protocol { - case "udp": - addr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", i.HostIP, i.HostPort)) - if err != nil { - return nil, errors.Wrapf(err, "cannot resolve the UDP address") - } - - server, err := net.ListenUDP("udp", addr) - if err != nil { - return nil, errors.Wrapf(err, "cannot listen on the UDP port") - } - f, err := server.File() - if err != nil { - return nil, errors.Wrapf(err, "cannot get file for UDP socket") - } - files = append(files, f) - - case "tcp": - addr, err := net.ResolveTCPAddr("tcp4", fmt.Sprintf("%s:%d", i.HostIP, i.HostPort)) - if err != nil { - return nil, errors.Wrapf(err, "cannot resolve the TCP address") - } - - server, err := net.ListenTCP("tcp4", addr) - if err != nil { - return nil, errors.Wrapf(err, "cannot listen on the TCP port") - } - f, err := server.File() - if err != nil { - return nil, errors.Wrapf(err, "cannot get file for TCP socket") - } - files = append(files, f) - case "sctp": - if !notifySCTP { - notifySCTP = true - logrus.Warnf("port reservation for SCTP is not supported") - } - default: - return nil, fmt.Errorf("unknown protocol %s", i.Protocol) - - } - } - return files, nil -} - -// updateContainerStatus retrieves the current status of the container from the -// runtime. It updates the container's state but does not save it. -// If useRunc is false, we will not directly hit runc to see the container's -// status, but will instead only check for the existence of the conmon exit file -// and update state to stopped if it exists. -func (r *OCIRuntime) updateContainerStatus(ctr *Container, useRuntime bool) error { - exitFile := ctr.exitFilePath() - - runtimeDir, err := util.GetRuntimeDir() - if err != nil { - return err - } - - // If not using the OCI runtime, we don't need to do most of this. - if !useRuntime { - // If the container's not running, nothing to do. - if ctr.state.State != define.ContainerStateRunning && ctr.state.State != define.ContainerStatePaused { - return nil - } - - // Check for the exit file conmon makes - info, err := os.Stat(exitFile) - if err != nil { - if os.IsNotExist(err) { - // Container is still running, no error - return nil - } - - return errors.Wrapf(err, "error running stat on container %s exit file", ctr.ID()) - } - - // Alright, it exists. Transition to Stopped state. - ctr.state.State = define.ContainerStateStopped - ctr.state.PID = 0 - ctr.state.ConmonPID = 0 - - // Read the exit file to get our stopped time and exit code. - return ctr.handleExitFile(exitFile, info) - } - - // Store old state so we know if we were already stopped - oldState := ctr.state.State - - state := new(spec.State) - - cmd := exec.Command(r.path, "state", ctr.ID()) - cmd.Env = append(cmd.Env, fmt.Sprintf("XDG_RUNTIME_DIR=%s", runtimeDir)) - - outPipe, err := cmd.StdoutPipe() - if err != nil { - return errors.Wrapf(err, "getting stdout pipe") - } - errPipe, err := cmd.StderrPipe() - if err != nil { - return errors.Wrapf(err, "getting stderr pipe") - } - - if err := cmd.Start(); err != nil { - out, err2 := ioutil.ReadAll(errPipe) - if err2 != nil { - return errors.Wrapf(err, "error getting container %s state", ctr.ID()) - } - if strings.Contains(string(out), "does not exist") { - if err := ctr.removeConmonFiles(); err != nil { - logrus.Debugf("unable to remove conmon files for container %s", ctr.ID()) - } - ctr.state.ExitCode = -1 - ctr.state.FinishedTime = time.Now() - ctr.state.State = define.ContainerStateExited - return nil - } - return errors.Wrapf(err, "error getting container %s state. stderr/out: %s", ctr.ID(), out) - } - defer func() { - _ = cmd.Wait() - }() - - if err := errPipe.Close(); err != nil { - return err - } - out, err := ioutil.ReadAll(outPipe) - if err != nil { - return errors.Wrapf(err, "error reading stdout: %s", ctr.ID()) - } - if err := json.NewDecoder(bytes.NewBuffer(out)).Decode(state); err != nil { - return errors.Wrapf(err, "error decoding container status for container %s", ctr.ID()) - } - ctr.state.PID = state.Pid - - switch state.Status { - case "created": - ctr.state.State = define.ContainerStateCreated - case "paused": - ctr.state.State = define.ContainerStatePaused - case "running": - ctr.state.State = define.ContainerStateRunning - case "stopped": - ctr.state.State = define.ContainerStateStopped - default: - return errors.Wrapf(define.ErrInternal, "unrecognized status returned by runtime for container %s: %s", - ctr.ID(), state.Status) - } - - // Only grab exit status if we were not already stopped - // If we were, it should already be in the database - if ctr.state.State == define.ContainerStateStopped && oldState != define.ContainerStateStopped { - var fi os.FileInfo - chWait := make(chan error) - defer close(chWait) - - _, err := WaitForFile(exitFile, chWait, time.Second*5) - if err == nil { - fi, err = os.Stat(exitFile) - } - if err != nil { - ctr.state.ExitCode = -1 - ctr.state.FinishedTime = time.Now() - logrus.Errorf("No exit file for container %s found: %v", ctr.ID(), err) - return nil - } - - return ctr.handleExitFile(exitFile, fi) - } - - return nil -} - -// startContainer starts the given container -// Sets time the container was started, but does not save it. -func (r *OCIRuntime) startContainer(ctr *Container) error { - // TODO: streams should probably *not* be our STDIN/OUT/ERR - redirect to buffers? - runtimeDir, err := util.GetRuntimeDir() - if err != nil { - return err - } - env := []string{fmt.Sprintf("XDG_RUNTIME_DIR=%s", runtimeDir)} - if notify, ok := os.LookupEnv("NOTIFY_SOCKET"); ok { - env = append(env, fmt.Sprintf("NOTIFY_SOCKET=%s", notify)) - } - if err := utils.ExecCmdWithStdStreams(os.Stdin, os.Stdout, os.Stderr, env, r.path, "start", ctr.ID()); err != nil { - return err - } - - ctr.state.StartedTime = time.Now() - - return nil -} - -// killContainer sends the given signal to the given container -func (r *OCIRuntime) killContainer(ctr *Container, signal uint) error { - logrus.Debugf("Sending signal %d to container %s", signal, ctr.ID()) - runtimeDir, err := util.GetRuntimeDir() - if err != nil { - return err - } - env := []string{fmt.Sprintf("XDG_RUNTIME_DIR=%s", runtimeDir)} - if err := utils.ExecCmdWithStdStreams(os.Stdin, os.Stdout, os.Stderr, env, r.path, "kill", ctr.ID(), fmt.Sprintf("%d", signal)); err != nil { - return errors.Wrapf(err, "error sending signal to container %s", ctr.ID()) - } - - return nil -} - -// deleteContainer deletes a container from the OCI runtime -func (r *OCIRuntime) deleteContainer(ctr *Container) error { - runtimeDir, err := util.GetRuntimeDir() - if err != nil { - return err - } - env := []string{fmt.Sprintf("XDG_RUNTIME_DIR=%s", runtimeDir)} - return utils.ExecCmdWithStdStreams(os.Stdin, os.Stdout, os.Stderr, env, r.path, "delete", "--force", ctr.ID()) -} - -// pauseContainer pauses the given container -func (r *OCIRuntime) pauseContainer(ctr *Container) error { - runtimeDir, err := util.GetRuntimeDir() - if err != nil { - return err - } - env := []string{fmt.Sprintf("XDG_RUNTIME_DIR=%s", runtimeDir)} - return utils.ExecCmdWithStdStreams(os.Stdin, os.Stdout, os.Stderr, env, r.path, "pause", ctr.ID()) -} - -// unpauseContainer unpauses the given container -func (r *OCIRuntime) unpauseContainer(ctr *Container) error { - runtimeDir, err := util.GetRuntimeDir() - if err != nil { - return err - } - env := []string{fmt.Sprintf("XDG_RUNTIME_DIR=%s", runtimeDir)} - return utils.ExecCmdWithStdStreams(os.Stdin, os.Stdout, os.Stderr, env, r.path, "resume", ctr.ID()) -} - -// checkpointContainer checkpoints the given container -func (r *OCIRuntime) checkpointContainer(ctr *Container, options ContainerCheckpointOptions) error { - if err := label.SetSocketLabel(ctr.ProcessLabel()); err != nil { - return err - } - // imagePath is used by CRIU to store the actual checkpoint files - imagePath := ctr.CheckpointPath() - // workPath will be used to store dump.log and stats-dump - workPath := ctr.bundlePath() - logrus.Debugf("Writing checkpoint to %s", imagePath) - logrus.Debugf("Writing checkpoint logs to %s", workPath) - args := []string{} - args = append(args, "checkpoint") - args = append(args, "--image-path") - args = append(args, imagePath) - args = append(args, "--work-path") - args = append(args, workPath) - if options.KeepRunning { - args = append(args, "--leave-running") - } - if options.TCPEstablished { - args = append(args, "--tcp-established") - } - args = append(args, ctr.ID()) - return utils.ExecCmdWithStdStreams(os.Stdin, os.Stdout, os.Stderr, nil, r.path, args...) +// OCIRuntime is an implementation of an OCI runtime. +// The OCI runtime implementation is expected to be a fairly thin wrapper around +// the actual runtime, and is not expected to include things like state +// management logic - e.g., we do not expect it to determine on its own that +// calling 'UnpauseContainer()' on a container that is not paused is an error. +// The code calling the OCIRuntime will manage this. +// TODO: May want to move the Attach() code under this umbrella. It's highly OCI +// runtime dependent. +// TODO: May want to move the conmon cleanup code here too - it depends on +// Conmon being in use. +type OCIRuntime interface { + // Name returns the name of the runtime. + Name() string + // Path returns the path to the runtime executable. + Path() string + + // CreateContainer creates the container in the OCI runtime. + CreateContainer(ctr *Container, restoreOptions *ContainerCheckpointOptions) error + // UpdateContainerStatus updates the status of the given container. + // It includes a switch for whether to perform a hard query of the + // runtime. If unset, the exit file (if supported by the implementation) + // will be used. + UpdateContainerStatus(ctr *Container) error + // StartContainer starts the given container. + StartContainer(ctr *Container) error + // KillContainer sends the given signal to the given container. + // If all is set, all processes in the container will be signalled; + // otherwise, only init will be signalled. + KillContainer(ctr *Container, signal uint, all bool) error + // StopContainer stops the given container. + // The container's stop signal (or SIGTERM if unspecified) will be sent + // first. + // After the given timeout, SIGKILL will be sent. + // If the given timeout is 0, SIGKILL will be sent immediately, and the + // stop signal will be omitted. + // If all is set, we will attempt to use the --all flag will `kill` in + // the OCI runtime to kill all processes in the container, including + // exec sessions. This is only supported if the container has cgroups. + StopContainer(ctr *Container, timeout uint, all bool) error + // DeleteContainer deletes the given container from the OCI runtime. + DeleteContainer(ctr *Container) error + // PauseContainer pauses the given container. + PauseContainer(ctr *Container) error + // UnpauseContainer unpauses the given container. + UnpauseContainer(ctr *Container) error + + // ExecContainer executes a command in a running container. + // Returns an int (exit code), error channel (errors from attach), and + // error (errors that occurred attempting to start the exec session). + ExecContainer(ctr *Container, sessionID string, options *ExecOptions) (int, chan error, error) + // ExecStopContainer stops a given exec session in a running container. + // SIGTERM with be sent initially, then SIGKILL after the given timeout. + // If timeout is 0, SIGKILL will be sent immediately, and SIGTERM will + // be omitted. + ExecStopContainer(ctr *Container, sessionID string, timeout uint) error + // ExecContainerCleanup cleans up after an exec session exits. + // It removes any files left by the exec session that are no longer + // needed, including the attach socket. + ExecContainerCleanup(ctr *Container, sessionID string) error + + // CheckpointContainer checkpoints the given container. + // Some OCI runtimes may not support this - if SupportsCheckpoint() + // returns false, this is not implemented, and will always return an + // error. + CheckpointContainer(ctr *Container, options ContainerCheckpointOptions) error + + // SupportsCheckpoint returns whether this OCI runtime + // implementation supports the CheckpointContainer() operation. + SupportsCheckpoint() bool + // SupportsJSONErrors is whether the runtime can return JSON-formatted + // error messages. + SupportsJSONErrors() bool + // SupportsNoCgroups is whether the runtime supports running containers + // without cgroups. + SupportsNoCgroups() bool + + // AttachSocketPath is the path to the socket to attach to a given + // container. + // TODO: If we move Attach code in here, this should be made internal. + // We don't want to force all runtimes to share the same attach + // implementation. + AttachSocketPath(ctr *Container) (string, error) + // ExecAttachSocketPath is the path to the socket to attach to a given + // exec session in the given container. + // TODO: Probably should be made internal. + ExecAttachSocketPath(ctr *Container, sessionID string) (string, error) + // ExitFilePath is the path to a container's exit file. + // All runtime implementations must create an exit file when containers + // exit, containing the exit code of the container (as a string). + // This is the path to that file for a given container. + ExitFilePath(ctr *Container) (string, error) + + // RuntimeInfo returns verbose information about the runtime. + RuntimeInfo() (map[string]interface{}, error) } -func (r *OCIRuntime) featureCheckCheckpointing() bool { - // Check if the runtime implements checkpointing. Currently only - // runc's checkpoint/restore implementation is supported. - cmd := exec.Command(r.path, "checkpoint", "-h") - if err := cmd.Start(); err != nil { - return false - } - if err := cmd.Wait(); err == nil { - return true - } - return false +// ExecOptions are options passed into ExecContainer. They control the command +// that will be executed and how the exec will proceed. +type ExecOptions struct { + // Cmd is the command to execute. + Cmd []string + // CapAdd is a set of capabilities to add to the executed command. + CapAdd []string + // Env is a set of environment variables to add to the container. + Env map[string]string + // Terminal is whether to create a new TTY for the exec session. + Terminal bool + // Cwd is the working directory for the executed command. If unset, the + // working directory of the container will be used. + Cwd string + // User is the user the command will be executed as. If unset, the user + // the container was run as will be used. + User string + // Streams are the streams that will be attached to the container. + Streams *AttachStreams + // PreserveFDs is a number of additional file descriptors (in addition + // to 0, 1, 2) that will be passed to the executed process. The total FDs + // passed will be 3 + PreserveFDs. + PreserveFDs uint + // Resize is a channel where terminal resize events are sent to be + // handled. + Resize chan remotecommand.TerminalSize + // DetachKeys is a set of keys that, when pressed in sequence, will + // detach from the container. + DetachKeys string } diff --git a/libpod/oci_attach_linux.go b/libpod/oci_attach_linux.go index 6cada0801..a383f6eab 100644 --- a/libpod/oci_attach_linux.go +++ b/libpod/oci_attach_linux.go @@ -47,7 +47,11 @@ func (c *Container) attach(streams *AttachStreams, keys string, resize <-chan re registerResizeFunc(resize, c.bundlePath()) - socketPath := buildSocketPath(c.AttachSocketPath()) + attachSock, err := c.AttachSocketPath() + if err != nil { + return err + } + socketPath := buildSocketPath(attachSock) conn, err := net.DialUnix("unixpacket", nil, &net.UnixAddr{Name: socketPath, Net: "unixpacket"}) if err != nil { @@ -108,7 +112,11 @@ func (c *Container) attachToExec(streams *AttachStreams, keys string, resize <-c logrus.Debugf("Attaching to container %s exec session %s", c.ID(), sessionID) // set up the socket path, such that it is the correct length and location for exec - socketPath := buildSocketPath(c.execAttachSocketPath(sessionID)) + sockPath, err := c.execAttachSocketPath(sessionID) + if err != nil { + return err + } + socketPath := buildSocketPath(sockPath) // 2: read from attachFd that the parent process has set up the console socket if _, err := readConmonPipeData(attachFd, ""); err != nil { diff --git a/libpod/oci_conmon_linux.go b/libpod/oci_conmon_linux.go new file mode 100644 index 000000000..448e05bdf --- /dev/null +++ b/libpod/oci_conmon_linux.go @@ -0,0 +1,1395 @@ +// +build linux + +package libpod + +import ( + "bufio" + "bytes" + "fmt" + "io/ioutil" + "os" + "os/exec" + "path/filepath" + "runtime" + "strconv" + "strings" + "syscall" + "time" + + "github.com/containers/libpod/libpod/define" + "github.com/containers/libpod/pkg/cgroups" + "github.com/containers/libpod/pkg/errorhandling" + "github.com/containers/libpod/pkg/lookup" + "github.com/containers/libpod/pkg/rootless" + "github.com/containers/libpod/pkg/util" + "github.com/containers/libpod/utils" + pmount "github.com/containers/storage/pkg/mount" + "github.com/coreos/go-systemd/activation" + spec "github.com/opencontainers/runtime-spec/specs-go" + "github.com/opencontainers/selinux/go-selinux" + "github.com/opencontainers/selinux/go-selinux/label" + "github.com/pkg/errors" + "github.com/sirupsen/logrus" + "golang.org/x/sys/unix" +) + +// ConmonOCIRuntime is an OCI runtime managed by Conmon. +// TODO: Make all calls to OCI runtime have a timeout. +type ConmonOCIRuntime struct { + name string + path string + conmonPath string + conmonEnv []string + cgroupManager string + tmpDir string + exitsDir string + socketsDir string + logSizeMax int64 + noPivot bool + reservePorts bool + supportsJSON bool + supportsNoCgroups bool + sdNotify bool +} + +// Make a new Conmon-based OCI runtime with the given options. +// Conmon will wrap the given OCI runtime, which can be `runc`, `crun`, or +// any runtime with a runc-compatible CLI. +// The first path that points to a valid executable will be used. +// Deliberately private. Someone should not be able to construct this outside of +// libpod. +func newConmonOCIRuntime(name string, paths []string, conmonPath string, runtimeCfg *RuntimeConfig, supportsJSON, supportsNoCgroups bool) (OCIRuntime, error) { + if name == "" { + return nil, errors.Wrapf(define.ErrInvalidArg, "the OCI runtime must be provided a non-empty name") + } + + runtime := new(ConmonOCIRuntime) + runtime.name = name + runtime.conmonPath = conmonPath + + runtime.conmonEnv = runtimeCfg.ConmonEnvVars + runtime.cgroupManager = runtimeCfg.CgroupManager + runtime.tmpDir = runtimeCfg.TmpDir + runtime.logSizeMax = runtimeCfg.MaxLogSize + runtime.noPivot = runtimeCfg.NoPivotRoot + runtime.reservePorts = runtimeCfg.EnablePortReservation + runtime.sdNotify = runtimeCfg.SDNotify + + // TODO: probe OCI runtime for feature and enable automatically if + // available. + runtime.supportsJSON = supportsJSON + runtime.supportsNoCgroups = supportsNoCgroups + + foundPath := false + for _, path := range paths { + stat, err := os.Stat(path) + if err != nil { + if os.IsNotExist(err) { + continue + } + return nil, errors.Wrapf(err, "cannot stat %s", path) + } + if !stat.Mode().IsRegular() { + continue + } + foundPath = true + runtime.path = path + logrus.Debugf("using runtime %q", path) + break + } + + // Search the $PATH as last fallback + if !foundPath { + if foundRuntime, err := exec.LookPath(name); err == nil { + foundPath = true + runtime.path = foundRuntime + logrus.Debugf("using runtime %q from $PATH: %q", name, foundRuntime) + } + } + + if !foundPath { + return nil, errors.Wrapf(define.ErrInvalidArg, "no valid executable found for OCI runtime %s", name) + } + + runtime.exitsDir = filepath.Join(runtime.tmpDir, "exits") + runtime.socketsDir = filepath.Join(runtime.tmpDir, "socket") + + if runtime.cgroupManager != CgroupfsCgroupsManager && runtime.cgroupManager != SystemdCgroupsManager { + return nil, errors.Wrapf(define.ErrInvalidArg, "invalid cgroup manager specified: %s", runtime.cgroupManager) + } + + // Create the exit files and attach sockets directories + if err := os.MkdirAll(runtime.exitsDir, 0750); err != nil { + // The directory is allowed to exist + if !os.IsExist(err) { + return nil, errors.Wrapf(err, "error creating OCI runtime exit files directory %s", + runtime.exitsDir) + } + } + if err := os.MkdirAll(runtime.socketsDir, 0750); err != nil { + // The directory is allowed to exist + if !os.IsExist(err) { + return nil, errors.Wrapf(err, "error creating OCI runtime attach sockets directory %s", + runtime.socketsDir) + } + } + + return runtime, nil +} + +// Name returns the name of the runtime being wrapped by Conmon. +func (r *ConmonOCIRuntime) Name() string { + return r.name +} + +// Path returns the path of the OCI runtime being wrapped by Conmon. +func (r *ConmonOCIRuntime) Path() string { + return r.path +} + +// CreateContainer creates a container. +func (r *ConmonOCIRuntime) CreateContainer(ctr *Container, restoreOptions *ContainerCheckpointOptions) (err error) { + if len(ctr.config.IDMappings.UIDMap) != 0 || len(ctr.config.IDMappings.GIDMap) != 0 { + for _, i := range []string{ctr.state.RunDir, ctr.runtime.config.TmpDir, ctr.config.StaticDir, ctr.state.Mountpoint, ctr.runtime.config.VolumePath} { + if err := makeAccessible(i, ctr.RootUID(), ctr.RootGID()); err != nil { + return err + } + } + + // if we are running a non privileged container, be sure to umount some kernel paths so they are not + // bind mounted inside the container at all. + if !ctr.config.Privileged && !rootless.IsRootless() { + ch := make(chan error) + go func() { + runtime.LockOSThread() + err := func() error { + fd, err := os.Open(fmt.Sprintf("/proc/%d/task/%d/ns/mnt", os.Getpid(), unix.Gettid())) + if err != nil { + return err + } + defer errorhandling.CloseQuiet(fd) + + // create a new mountns on the current thread + if err = unix.Unshare(unix.CLONE_NEWNS); err != nil { + return err + } + defer func() { + if err := unix.Setns(int(fd.Fd()), unix.CLONE_NEWNS); err != nil { + logrus.Errorf("unable to clone new namespace: %q", err) + } + }() + + // don't spread our mounts around. We are setting only /sys to be slave + // so that the cleanup process is still able to umount the storage and the + // changes are propagated to the host. + err = unix.Mount("/sys", "/sys", "none", unix.MS_REC|unix.MS_SLAVE, "") + if err != nil { + return errors.Wrapf(err, "cannot make /sys slave") + } + + mounts, err := pmount.GetMounts() + if err != nil { + return err + } + for _, m := range mounts { + if !strings.HasPrefix(m.Mountpoint, "/sys/kernel") { + continue + } + err = unix.Unmount(m.Mountpoint, 0) + if err != nil && !os.IsNotExist(err) { + return errors.Wrapf(err, "cannot unmount %s", m.Mountpoint) + } + } + return r.createOCIContainer(ctr, restoreOptions) + }() + ch <- err + }() + err := <-ch + return err + } + } + return r.createOCIContainer(ctr, restoreOptions) +} + +// UpdateContainerStatus retrieves the current status of the container from the +// runtime. It updates the container's state but does not save it. +// If useRuntime is false, we will not directly hit runc to see the container's +// status, but will instead only check for the existence of the conmon exit file +// and update state to stopped if it exists. +func (r *ConmonOCIRuntime) UpdateContainerStatus(ctr *Container) error { + exitFile, err := r.ExitFilePath(ctr) + if err != nil { + return err + } + + runtimeDir, err := util.GetRuntimeDir() + if err != nil { + return err + } + + // Store old state so we know if we were already stopped + oldState := ctr.state.State + + state := new(spec.State) + + cmd := exec.Command(r.path, "state", ctr.ID()) + cmd.Env = append(cmd.Env, fmt.Sprintf("XDG_RUNTIME_DIR=%s", runtimeDir)) + + outPipe, err := cmd.StdoutPipe() + if err != nil { + return errors.Wrapf(err, "getting stdout pipe") + } + errPipe, err := cmd.StderrPipe() + if err != nil { + return errors.Wrapf(err, "getting stderr pipe") + } + + if err := cmd.Start(); err != nil { + out, err2 := ioutil.ReadAll(errPipe) + if err2 != nil { + return errors.Wrapf(err, "error getting container %s state", ctr.ID()) + } + if strings.Contains(string(out), "does not exist") { + if err := ctr.removeConmonFiles(); err != nil { + logrus.Debugf("unable to remove conmon files for container %s", ctr.ID()) + } + ctr.state.ExitCode = -1 + ctr.state.FinishedTime = time.Now() + ctr.state.State = define.ContainerStateExited + return nil + } + return errors.Wrapf(err, "error getting container %s state. stderr/out: %s", ctr.ID(), out) + } + defer func() { + _ = cmd.Wait() + }() + + if err := errPipe.Close(); err != nil { + return err + } + out, err := ioutil.ReadAll(outPipe) + if err != nil { + return errors.Wrapf(err, "error reading stdout: %s", ctr.ID()) + } + if err := json.NewDecoder(bytes.NewBuffer(out)).Decode(state); err != nil { + return errors.Wrapf(err, "error decoding container status for container %s", ctr.ID()) + } + ctr.state.PID = state.Pid + + switch state.Status { + case "created": + ctr.state.State = define.ContainerStateCreated + case "paused": + ctr.state.State = define.ContainerStatePaused + case "running": + ctr.state.State = define.ContainerStateRunning + case "stopped": + ctr.state.State = define.ContainerStateStopped + default: + return errors.Wrapf(define.ErrInternal, "unrecognized status returned by runtime for container %s: %s", + ctr.ID(), state.Status) + } + + // Only grab exit status if we were not already stopped + // If we were, it should already be in the database + if ctr.state.State == define.ContainerStateStopped && oldState != define.ContainerStateStopped { + var fi os.FileInfo + chWait := make(chan error) + defer close(chWait) + + _, err := WaitForFile(exitFile, chWait, time.Second*5) + if err == nil { + fi, err = os.Stat(exitFile) + } + if err != nil { + ctr.state.ExitCode = -1 + ctr.state.FinishedTime = time.Now() + logrus.Errorf("No exit file for container %s found: %v", ctr.ID(), err) + return nil + } + + return ctr.handleExitFile(exitFile, fi) + } + + return nil +} + +// StartContainer starts the given container. +// Sets time the container was started, but does not save it. +func (r *ConmonOCIRuntime) StartContainer(ctr *Container) error { + // TODO: streams should probably *not* be our STDIN/OUT/ERR - redirect to buffers? + runtimeDir, err := util.GetRuntimeDir() + if err != nil { + return err + } + env := []string{fmt.Sprintf("XDG_RUNTIME_DIR=%s", runtimeDir)} + if notify, ok := os.LookupEnv("NOTIFY_SOCKET"); ok { + env = append(env, fmt.Sprintf("NOTIFY_SOCKET=%s", notify)) + } + if err := utils.ExecCmdWithStdStreams(os.Stdin, os.Stdout, os.Stderr, env, r.path, "start", ctr.ID()); err != nil { + return err + } + + ctr.state.StartedTime = time.Now() + + return nil +} + +// KillContainer sends the given signal to the given container. +// If all is set, send to all PIDs in the container. +// All is only supported if the container created cgroups. +func (r *ConmonOCIRuntime) KillContainer(ctr *Container, signal uint, all bool) error { + logrus.Debugf("Sending signal %d to container %s", signal, ctr.ID()) + runtimeDir, err := util.GetRuntimeDir() + if err != nil { + return err + } + env := []string{fmt.Sprintf("XDG_RUNTIME_DIR=%s", runtimeDir)} + var args []string + if all { + args = []string{"kill", "--all", ctr.ID(), fmt.Sprintf("%d", signal)} + } else { + args = []string{"kill", ctr.ID(), fmt.Sprintf("%d", signal)} + } + if err := utils.ExecCmdWithStdStreams(os.Stdin, os.Stdout, os.Stderr, env, r.path, args...); err != nil { + return errors.Wrapf(err, "error sending signal to container %s", ctr.ID()) + } + + return nil +} + +// StopContainer stops a container, first using its given stop signal (or +// SIGTERM if no signal was specified), then using SIGKILL. +// Timeout is given in seconds. If timeout is 0, the container will be +// immediately kill with SIGKILL. +// Does not set finished time for container, assumes you will run updateStatus +// after to pull the exit code. +func (r *ConmonOCIRuntime) StopContainer(ctr *Container, timeout uint, all bool) error { + logrus.Debugf("Stopping container %s (PID %d)", ctr.ID(), ctr.state.PID) + + // Ping the container to see if it's alive + // If it's not, it's already stopped, return + err := unix.Kill(ctr.state.PID, 0) + if err == unix.ESRCH { + return nil + } + + stopSignal := ctr.config.StopSignal + if stopSignal == 0 { + stopSignal = uint(syscall.SIGTERM) + } + + if timeout > 0 { + if err := r.KillContainer(ctr, stopSignal, all); err != nil { + // Is the container gone? + // If so, it probably died between the first check and + // our sending the signal + // The container is stopped, so exit cleanly + err := unix.Kill(ctr.state.PID, 0) + if err == unix.ESRCH { + return nil + } + + return err + } + + if err := waitContainerStop(ctr, time.Duration(timeout)*time.Second); err != nil { + logrus.Warnf("Timed out stopping container %s, resorting to SIGKILL", ctr.ID()) + } else { + // No error, the container is dead + return nil + } + } + + if err := r.KillContainer(ctr, 9, all); err != nil { + // Again, check if the container is gone. If it is, exit cleanly. + err := unix.Kill(ctr.state.PID, 0) + if err == unix.ESRCH { + return nil + } + + return errors.Wrapf(err, "error sending SIGKILL to container %s", ctr.ID()) + } + + // Give runtime a few seconds to make it happen + if err := waitContainerStop(ctr, killContainerTimeout); err != nil { + return err + } + + return nil +} + +// DeleteContainer deletes a container from the OCI runtime. +func (r *ConmonOCIRuntime) DeleteContainer(ctr *Container) error { + runtimeDir, err := util.GetRuntimeDir() + if err != nil { + return err + } + env := []string{fmt.Sprintf("XDG_RUNTIME_DIR=%s", runtimeDir)} + return utils.ExecCmdWithStdStreams(os.Stdin, os.Stdout, os.Stderr, env, r.path, "delete", "--force", ctr.ID()) +} + +// PauseContainer pauses the given container. +func (r *ConmonOCIRuntime) PauseContainer(ctr *Container) error { + runtimeDir, err := util.GetRuntimeDir() + if err != nil { + return err + } + env := []string{fmt.Sprintf("XDG_RUNTIME_DIR=%s", runtimeDir)} + return utils.ExecCmdWithStdStreams(os.Stdin, os.Stdout, os.Stderr, env, r.path, "pause", ctr.ID()) +} + +// UnpauseContainer unpauses the given container. +func (r *ConmonOCIRuntime) UnpauseContainer(ctr *Container) error { + runtimeDir, err := util.GetRuntimeDir() + if err != nil { + return err + } + env := []string{fmt.Sprintf("XDG_RUNTIME_DIR=%s", runtimeDir)} + return utils.ExecCmdWithStdStreams(os.Stdin, os.Stdout, os.Stderr, env, r.path, "resume", ctr.ID()) +} + +// ExecContainer executes a command in a running container +// TODO: Split into Create/Start/Attach/Wait +func (r *ConmonOCIRuntime) ExecContainer(c *Container, sessionID string, options *ExecOptions) (int, chan error, error) { + if options == nil { + return -1, nil, errors.Wrapf(define.ErrInvalidArg, "must provide an ExecOptions struct to ExecContainer") + } + if len(options.Cmd) == 0 { + return -1, nil, errors.Wrapf(define.ErrInvalidArg, "must provide a command to execute") + } + + if sessionID == "" { + return -1, nil, errors.Wrapf(define.ErrEmptyID, "must provide a session ID for exec") + } + + // create sync pipe to receive the pid + parentSyncPipe, childSyncPipe, err := newPipe() + if err != nil { + return -1, nil, errors.Wrapf(err, "error creating socket pair") + } + + defer errorhandling.CloseQuiet(parentSyncPipe) + + // create start pipe to set the cgroup before running + // attachToExec is responsible for closing parentStartPipe + childStartPipe, parentStartPipe, err := newPipe() + if err != nil { + return -1, nil, errors.Wrapf(err, "error creating socket pair") + } + + // We want to make sure we close the parent{Start,Attach}Pipes if we fail + // but also don't want to close them after attach to exec is called + attachToExecCalled := false + + defer func() { + if !attachToExecCalled { + errorhandling.CloseQuiet(parentStartPipe) + } + }() + + // create the attach pipe to allow attach socket to be created before + // $RUNTIME exec starts running. This is to make sure we can capture all output + // from the process through that socket, rather than half reading the log, half attaching to the socket + // attachToExec is responsible for closing parentAttachPipe + parentAttachPipe, childAttachPipe, err := newPipe() + if err != nil { + return -1, nil, errors.Wrapf(err, "error creating socket pair") + } + + defer func() { + if !attachToExecCalled { + errorhandling.CloseQuiet(parentAttachPipe) + } + }() + + childrenClosed := false + defer func() { + if !childrenClosed { + errorhandling.CloseQuiet(childSyncPipe) + errorhandling.CloseQuiet(childAttachPipe) + errorhandling.CloseQuiet(childStartPipe) + } + }() + + runtimeDir, err := util.GetRuntimeDir() + if err != nil { + return -1, nil, err + } + + finalEnv := make([]string, 0, len(options.Env)) + for k, v := range options.Env { + finalEnv = append(finalEnv, fmt.Sprintf("%s=%s", k, v)) + } + + processFile, err := prepareProcessExec(c, options.Cmd, finalEnv, options.Terminal, options.Cwd, options.User, sessionID) + if err != nil { + return -1, nil, err + } + + var ociLog string + if logrus.GetLevel() != logrus.DebugLevel && r.supportsJSON { + ociLog = c.execOCILog(sessionID) + } + args := r.sharedConmonArgs(c, sessionID, c.execBundlePath(sessionID), c.execPidPath(sessionID), c.execLogPath(sessionID), c.execExitFileDir(sessionID), ociLog) + + if options.PreserveFDs > 0 { + args = append(args, formatRuntimeOpts("--preserve-fds", fmt.Sprintf("%d", options.PreserveFDs))...) + } + + for _, capability := range options.CapAdd { + args = append(args, formatRuntimeOpts("--cap", capability)...) + } + + if options.Terminal { + args = append(args, "-t") + } + + // Append container ID and command + args = append(args, "-e") + // TODO make this optional when we can detach + args = append(args, "--exec-attach") + args = append(args, "--exec-process-spec", processFile.Name()) + + logrus.WithFields(logrus.Fields{ + "args": args, + }).Debugf("running conmon: %s", r.conmonPath) + execCmd := exec.Command(r.conmonPath, args...) + + if options.Streams != nil { + if options.Streams.AttachInput { + execCmd.Stdin = options.Streams.InputStream + } + if options.Streams.AttachOutput { + execCmd.Stdout = options.Streams.OutputStream + } + if options.Streams.AttachError { + execCmd.Stderr = options.Streams.ErrorStream + } + } + + conmonEnv, extraFiles, err := r.configureConmonEnv(runtimeDir) + if err != nil { + return -1, nil, err + } + + if options.PreserveFDs > 0 { + for fd := 3; fd < int(3+options.PreserveFDs); fd++ { + execCmd.ExtraFiles = append(execCmd.ExtraFiles, os.NewFile(uintptr(fd), fmt.Sprintf("fd-%d", fd))) + } + } + + // we don't want to step on users fds they asked to preserve + // Since 0-2 are used for stdio, start the fds we pass in at preserveFDs+3 + execCmd.Env = append(r.conmonEnv, fmt.Sprintf("_OCI_SYNCPIPE=%d", options.PreserveFDs+3), fmt.Sprintf("_OCI_STARTPIPE=%d", options.PreserveFDs+4), fmt.Sprintf("_OCI_ATTACHPIPE=%d", options.PreserveFDs+5)) + execCmd.Env = append(execCmd.Env, conmonEnv...) + + execCmd.ExtraFiles = append(execCmd.ExtraFiles, childSyncPipe, childStartPipe, childAttachPipe) + execCmd.ExtraFiles = append(execCmd.ExtraFiles, extraFiles...) + execCmd.Dir = c.execBundlePath(sessionID) + execCmd.SysProcAttr = &syscall.SysProcAttr{ + Setpgid: true, + } + + err = startCommandGivenSelinux(execCmd) + + // We don't need children pipes on the parent side + errorhandling.CloseQuiet(childSyncPipe) + errorhandling.CloseQuiet(childAttachPipe) + errorhandling.CloseQuiet(childStartPipe) + childrenClosed = true + + if err != nil { + return -1, nil, errors.Wrapf(err, "cannot start container %s", c.ID()) + } + if err := r.moveConmonToCgroupAndSignal(c, execCmd, parentStartPipe); err != nil { + return -1, nil, err + } + + if options.PreserveFDs > 0 { + for fd := 3; fd < int(3+options.PreserveFDs); fd++ { + // These fds were passed down to the runtime. Close them + // and not interfere + if err := os.NewFile(uintptr(fd), fmt.Sprintf("fd-%d", fd)).Close(); err != nil { + logrus.Debugf("unable to close file fd-%d", fd) + } + } + } + + // TODO Only create if !detach + // Attach to the container before starting it + attachChan := make(chan error) + go func() { + // attachToExec is responsible for closing pipes + attachChan <- c.attachToExec(options.Streams, options.DetachKeys, options.Resize, sessionID, parentStartPipe, parentAttachPipe) + close(attachChan) + }() + attachToExecCalled = true + + pid, err := readConmonPipeData(parentSyncPipe, ociLog) + + return pid, attachChan, err +} + +// ExecStopContainer stops a given exec session in a running container. +func (r *ConmonOCIRuntime) ExecStopContainer(ctr *Container, sessionID string, timeout uint) error { + session, ok := ctr.state.ExecSessions[sessionID] + if !ok { + // TODO This should probably be a separate error + return errors.Wrapf(define.ErrInvalidArg, "no exec session with ID %s found in container %s", sessionID, ctr.ID()) + } + + logrus.Debugf("Going to stop container %s exec session %s", ctr.ID(), sessionID) + + // Is the session dead? + // Ping the PID with signal 0 to see if it still exists. + if err := unix.Kill(session.PID, 0); err != nil { + if err == unix.ESRCH { + return nil + } + return errors.Wrapf(err, "error pinging container %s exec session %s PID %d with signal 0", ctr.ID(), sessionID, session.PID) + } + + if timeout > 0 { + // Use SIGTERM by default, then SIGSTOP after timeout. + logrus.Debugf("Killing exec session %s (PID %d) of container %s with SIGTERM", sessionID, session.PID, ctr.ID()) + if err := unix.Kill(session.PID, unix.SIGTERM); err != nil { + if err == unix.ESRCH { + return nil + } + return errors.Wrapf(err, "error killing container %s exec session %s PID %d with SIGTERM", ctr.ID(), sessionID, session.PID) + } + + // Wait for the PID to stop + if err := waitPidStop(session.PID, time.Duration(timeout)*time.Second); err != nil { + logrus.Warnf("Timed out waiting for container %s exec session %s to stop, resorting to SIGKILL", ctr.ID(), sessionID) + } else { + // No error, container is dead + return nil + } + } + + // SIGTERM did not work. On to SIGKILL. + logrus.Debugf("Killing exec session %s (PID %d) of container %s with SIGKILL", sessionID, session.PID, ctr.ID()) + if err := unix.Kill(session.PID, unix.SIGTERM); err != nil { + if err == unix.ESRCH { + return nil + } + return errors.Wrapf(err, "error killing container %s exec session %s PID %d with SIGKILL", ctr.ID(), sessionID, session.PID) + } + + // Wait for the PID to stop + if err := waitPidStop(session.PID, killContainerTimeout*time.Second); err != nil { + return errors.Wrapf(err, "timed out waiting for container %s exec session %s PID %d to stop after SIGKILL", ctr.ID(), sessionID, session.PID) + } + + return nil +} + +// ExecCleanupContainer cleans up files created when a command is run via +// ExecContainer. This includes the attach socket for the exec session. +func (r *ConmonOCIRuntime) ExecContainerCleanup(ctr *Container, sessionID string) error { + // Clean up the sockets dir. Issue #3962 + // Also ignore if it doesn't exist for some reason; hence the conditional return below + if err := os.RemoveAll(filepath.Join(r.socketsDir, sessionID)); err != nil && !os.IsNotExist(err) { + return err + } + return nil +} + +// CheckpointContainer checkpoints the given container. +func (r *ConmonOCIRuntime) CheckpointContainer(ctr *Container, options ContainerCheckpointOptions) error { + if err := label.SetSocketLabel(ctr.ProcessLabel()); err != nil { + return err + } + // imagePath is used by CRIU to store the actual checkpoint files + imagePath := ctr.CheckpointPath() + // workPath will be used to store dump.log and stats-dump + workPath := ctr.bundlePath() + logrus.Debugf("Writing checkpoint to %s", imagePath) + logrus.Debugf("Writing checkpoint logs to %s", workPath) + args := []string{} + args = append(args, "checkpoint") + args = append(args, "--image-path") + args = append(args, imagePath) + args = append(args, "--work-path") + args = append(args, workPath) + if options.KeepRunning { + args = append(args, "--leave-running") + } + if options.TCPEstablished { + args = append(args, "--tcp-established") + } + args = append(args, ctr.ID()) + return utils.ExecCmdWithStdStreams(os.Stdin, os.Stdout, os.Stderr, nil, r.path, args...) +} + +// SupportsCheckpoint checks if the OCI runtime supports checkpointing +// containers. +func (r *ConmonOCIRuntime) SupportsCheckpoint() bool { + // Check if the runtime implements checkpointing. Currently only + // runc's checkpoint/restore implementation is supported. + cmd := exec.Command(r.path, "checkpoint", "-h") + if err := cmd.Start(); err != nil { + return false + } + if err := cmd.Wait(); err == nil { + return true + } + return false +} + +// SupportsJSONErrors checks if the OCI runtime supports JSON-formatted error +// messages. +func (r *ConmonOCIRuntime) SupportsJSONErrors() bool { + return r.supportsJSON +} + +// SupportsNoCgroups checks if the OCI runtime supports running containers +// without cgroups (the --cgroup-manager=disabled flag). +func (r *ConmonOCIRuntime) SupportsNoCgroups() bool { + return r.supportsNoCgroups +} + +// AttachSocketPath is the path to a single container's attach socket. +func (r *ConmonOCIRuntime) AttachSocketPath(ctr *Container) (string, error) { + if ctr == nil { + return "", errors.Wrapf(define.ErrInvalidArg, "must provide a valid container to get attach socket path") + } + + return filepath.Join(r.socketsDir, ctr.ID(), "attach"), nil +} + +// ExecAttachSocketPath is the path to a container's exec session attach socket. +func (r *ConmonOCIRuntime) ExecAttachSocketPath(ctr *Container, sessionID string) (string, error) { + // We don't even use container, so don't validity check it + if sessionID == "" { + return "", errors.Wrapf(define.ErrInvalidArg, "must provide a valid session ID to get attach socket path") + } + + return filepath.Join(r.socketsDir, sessionID, "attach"), nil +} + +// ExitFilePath is the path to a container's exit file. +func (r *ConmonOCIRuntime) ExitFilePath(ctr *Container) (string, error) { + if ctr == nil { + return "", errors.Wrapf(define.ErrInvalidArg, "must provide a valid container to get exit file path") + } + return filepath.Join(r.exitsDir, ctr.ID()), nil +} + +// RuntimeInfo provides information on the runtime. +func (r *ConmonOCIRuntime) RuntimeInfo() (map[string]interface{}, error) { + runtimePackage := packageVersion(r.path) + conmonPackage := packageVersion(r.conmonPath) + runtimeVersion, err := r.getOCIRuntimeVersion() + if err != nil { + return nil, errors.Wrapf(err, "error getting version of OCI runtime %s", r.name) + } + conmonVersion, err := r.getConmonVersion() + if err != nil { + return nil, errors.Wrapf(err, "error getting conmon version") + } + + info := make(map[string]interface{}) + info["Conmon"] = map[string]interface{}{ + "path": r.conmonPath, + "package": conmonPackage, + "version": conmonVersion, + } + info["OCIRuntime"] = map[string]interface{}{ + "name": r.name, + "path": r.path, + "package": runtimePackage, + "version": runtimeVersion, + } + + return info, nil +} + +// makeAccessible changes the path permission and each parent directory to have --x--x--x +func makeAccessible(path string, uid, gid int) error { + for ; path != "/"; path = filepath.Dir(path) { + st, err := os.Stat(path) + if err != nil { + if os.IsNotExist(err) { + return nil + } + return err + } + if int(st.Sys().(*syscall.Stat_t).Uid) == uid && int(st.Sys().(*syscall.Stat_t).Gid) == gid { + continue + } + if st.Mode()&0111 != 0111 { + if err := os.Chmod(path, st.Mode()|0111); err != nil { + return err + } + } + } + return nil +} + +// Wait for a container which has been sent a signal to stop +func waitContainerStop(ctr *Container, timeout time.Duration) error { + return waitPidStop(ctr.state.PID, timeout) +} + +// Wait for a given PID to stop +func waitPidStop(pid int, timeout time.Duration) error { + done := make(chan struct{}) + chControl := make(chan struct{}) + go func() { + for { + select { + case <-chControl: + return + default: + if err := unix.Kill(pid, 0); err != nil { + if err == unix.ESRCH { + close(done) + return + } + logrus.Errorf("Error pinging PID %d with signal 0: %v", pid, err) + } + time.Sleep(100 * time.Millisecond) + } + } + }() + select { + case <-done: + return nil + case <-time.After(timeout): + close(chControl) + return errors.Errorf("given PIDs did not die within timeout") + } +} + +// createOCIContainer generates this container's main conmon instance and prepares it for starting +func (r *ConmonOCIRuntime) createOCIContainer(ctr *Container, restoreOptions *ContainerCheckpointOptions) (err error) { + var stderrBuf bytes.Buffer + + runtimeDir, err := util.GetRuntimeDir() + if err != nil { + return err + } + + parentSyncPipe, childSyncPipe, err := newPipe() + if err != nil { + return errors.Wrapf(err, "error creating socket pair") + } + defer errorhandling.CloseQuiet(parentSyncPipe) + + childStartPipe, parentStartPipe, err := newPipe() + if err != nil { + return errors.Wrapf(err, "error creating socket pair for start pipe") + } + + defer errorhandling.CloseQuiet(parentStartPipe) + + var ociLog string + if logrus.GetLevel() != logrus.DebugLevel && r.supportsJSON { + ociLog = filepath.Join(ctr.state.RunDir, "oci-log") + } + args := r.sharedConmonArgs(ctr, ctr.ID(), ctr.bundlePath(), filepath.Join(ctr.state.RunDir, "pidfile"), ctr.LogPath(), r.exitsDir, ociLog) + + if ctr.config.Spec.Process.Terminal { + args = append(args, "-t") + } else if ctr.config.Stdin { + args = append(args, "-i") + } + + if ctr.config.ConmonPidFile != "" { + args = append(args, "--conmon-pidfile", ctr.config.ConmonPidFile) + } + + if r.noPivot { + args = append(args, "--no-pivot") + } + + if len(ctr.config.ExitCommand) > 0 { + args = append(args, "--exit-command", ctr.config.ExitCommand[0]) + for _, arg := range ctr.config.ExitCommand[1:] { + args = append(args, []string{"--exit-command-arg", arg}...) + } + } + + if restoreOptions != nil { + args = append(args, "--restore", ctr.CheckpointPath()) + if restoreOptions.TCPEstablished { + args = append(args, "--runtime-opt", "--tcp-established") + } + } + + logrus.WithFields(logrus.Fields{ + "args": args, + }).Debugf("running conmon: %s", r.conmonPath) + + cmd := exec.Command(r.conmonPath, args...) + cmd.Dir = ctr.bundlePath() + cmd.SysProcAttr = &syscall.SysProcAttr{ + Setpgid: true, + } + // TODO this is probably a really bad idea for some uses + // Make this configurable + cmd.Stdin = os.Stdin + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + if ctr.config.Spec.Process.Terminal { + cmd.Stderr = &stderrBuf + } + + // 0, 1 and 2 are stdin, stdout and stderr + conmonEnv, envFiles, err := r.configureConmonEnv(runtimeDir) + if err != nil { + return err + } + + cmd.Env = append(r.conmonEnv, fmt.Sprintf("_OCI_SYNCPIPE=%d", 3), fmt.Sprintf("_OCI_STARTPIPE=%d", 4)) + cmd.Env = append(cmd.Env, conmonEnv...) + cmd.ExtraFiles = append(cmd.ExtraFiles, childSyncPipe, childStartPipe) + cmd.ExtraFiles = append(cmd.ExtraFiles, envFiles...) + + if r.reservePorts && !ctr.config.NetMode.IsSlirp4netns() { + ports, err := bindPorts(ctr.config.PortMappings) + if err != nil { + return err + } + + // Leak the port we bound in the conmon process. These fd's won't be used + // by the container and conmon will keep the ports busy so that another + // process cannot use them. + cmd.ExtraFiles = append(cmd.ExtraFiles, ports...) + } + + if ctr.config.NetMode.IsSlirp4netns() { + if ctr.config.PostConfigureNetNS { + ctr.rootlessSlirpSyncR, ctr.rootlessSlirpSyncW, err = os.Pipe() + if err != nil { + return errors.Wrapf(err, "failed to create rootless network sync pipe") + } + } else { + if ctr.rootlessSlirpSyncR != nil { + defer errorhandling.CloseQuiet(ctr.rootlessSlirpSyncR) + } + if ctr.rootlessSlirpSyncW != nil { + defer errorhandling.CloseQuiet(ctr.rootlessSlirpSyncW) + } + } + // Leak one end in conmon, the other one will be leaked into slirp4netns + cmd.ExtraFiles = append(cmd.ExtraFiles, ctr.rootlessSlirpSyncW) + } + + err = startCommandGivenSelinux(cmd) + // regardless of whether we errored or not, we no longer need the children pipes + childSyncPipe.Close() + childStartPipe.Close() + if err != nil { + return err + } + if err := r.moveConmonToCgroupAndSignal(ctr, cmd, parentStartPipe); err != nil { + return err + } + /* Wait for initial setup and fork, and reap child */ + err = cmd.Wait() + if err != nil { + return err + } + + pid, err := readConmonPipeData(parentSyncPipe, ociLog) + if err != nil { + if err2 := r.DeleteContainer(ctr); err2 != nil { + logrus.Errorf("Error removing container %s from runtime after creation failed", ctr.ID()) + } + return err + } + ctr.state.PID = pid + + conmonPID, err := readConmonPidFile(ctr.config.ConmonPidFile) + if err != nil { + logrus.Warnf("error reading conmon pid file for container %s: %s", ctr.ID(), err.Error()) + } else if conmonPID > 0 { + // conmon not having a pid file is a valid state, so don't set it if we don't have it + logrus.Infof("Got Conmon PID as %d", conmonPID) + ctr.state.ConmonPID = conmonPID + } + + return nil +} + +// prepareProcessExec returns the path of the process.json used in runc exec -p +// caller is responsible to close the returned *os.File if needed. +func prepareProcessExec(c *Container, cmd, env []string, tty bool, cwd, user, sessionID string) (*os.File, error) { + f, err := ioutil.TempFile(c.execBundlePath(sessionID), "exec-process-") + if err != nil { + return nil, err + } + + pspec := c.config.Spec.Process + pspec.Args = cmd + // We need to default this to false else it will inherit terminal as true + // from the container. + pspec.Terminal = false + if tty { + pspec.Terminal = true + } + if len(env) > 0 { + pspec.Env = append(pspec.Env, env...) + } + + if cwd != "" { + pspec.Cwd = cwd + + } + + overrides := c.getUserOverrides() + execUser, err := lookup.GetUserGroupInfo(c.state.Mountpoint, user, overrides) + if err != nil { + return nil, err + } + + // If user was set, look it up in the container to get a UID to use on + // the host + if user != "" { + sgids := make([]uint32, 0, len(execUser.Sgids)) + for _, sgid := range execUser.Sgids { + sgids = append(sgids, uint32(sgid)) + } + processUser := spec.User{ + UID: uint32(execUser.Uid), + GID: uint32(execUser.Gid), + AdditionalGids: sgids, + } + + pspec.User = processUser + } + + hasHomeSet := false + for _, s := range pspec.Env { + if strings.HasPrefix(s, "HOME=") { + hasHomeSet = true + break + } + } + if !hasHomeSet { + pspec.Env = append(pspec.Env, fmt.Sprintf("HOME=%s", execUser.Home)) + } + + processJSON, err := json.Marshal(pspec) + if err != nil { + return nil, err + } + + if err := ioutil.WriteFile(f.Name(), processJSON, 0644); err != nil { + return nil, err + } + return f, nil +} + +// configureConmonEnv gets the environment values to add to conmon's exec struct +// TODO this may want to be less hardcoded/more configurable in the future +func (r *ConmonOCIRuntime) configureConmonEnv(runtimeDir string) ([]string, []*os.File, error) { + env := make([]string, 0, 6) + env = append(env, fmt.Sprintf("XDG_RUNTIME_DIR=%s", runtimeDir)) + env = append(env, fmt.Sprintf("_CONTAINERS_USERNS_CONFIGURED=%s", os.Getenv("_CONTAINERS_USERNS_CONFIGURED"))) + env = append(env, fmt.Sprintf("_CONTAINERS_ROOTLESS_UID=%s", os.Getenv("_CONTAINERS_ROOTLESS_UID"))) + home, err := homeDir() + if err != nil { + return nil, nil, err + } + env = append(env, fmt.Sprintf("HOME=%s", home)) + + extraFiles := make([]*os.File, 0) + if notify, ok := os.LookupEnv("NOTIFY_SOCKET"); ok { + env = append(env, fmt.Sprintf("NOTIFY_SOCKET=%s", notify)) + } + if !r.sdNotify { + if listenfds, ok := os.LookupEnv("LISTEN_FDS"); ok { + env = append(env, fmt.Sprintf("LISTEN_FDS=%s", listenfds), "LISTEN_PID=1") + fds := activation.Files(false) + extraFiles = append(extraFiles, fds...) + } + } else { + logrus.Debug("disabling SD notify") + } + return env, extraFiles, nil +} + +// sharedConmonArgs takes common arguments for exec and create/restore and formats them for the conmon CLI +func (r *ConmonOCIRuntime) sharedConmonArgs(ctr *Container, cuuid, bundlePath, pidPath, logPath, exitDir, ociLogPath string) []string { + // set the conmon API version to be able to use the correct sync struct keys + args := []string{"--api-version", "1"} + if r.cgroupManager == SystemdCgroupsManager && !ctr.config.NoCgroups { + args = append(args, "-s") + } + args = append(args, "-c", ctr.ID()) + args = append(args, "-u", cuuid) + args = append(args, "-r", r.path) + args = append(args, "-b", bundlePath) + args = append(args, "-p", pidPath) + + var logDriver string + switch ctr.LogDriver() { + case JournaldLogging: + logDriver = JournaldLogging + case JSONLogging: + fallthrough + default: //nolint-stylecheck + // No case here should happen except JSONLogging, but keep this here in case the options are extended + logrus.Errorf("%s logging specified but not supported. Choosing k8s-file logging instead", ctr.LogDriver()) + fallthrough + case "": + // to get here, either a user would specify `--log-driver ""`, or this came from another place in libpod + // since the former case is obscure, and the latter case isn't an error, let's silently fallthrough + fallthrough + case KubernetesLogging: + logDriver = fmt.Sprintf("%s:%s", KubernetesLogging, logPath) + } + + args = append(args, "-l", logDriver) + args = append(args, "--exit-dir", exitDir) + args = append(args, "--socket-dir-path", r.socketsDir) + if r.logSizeMax >= 0 { + args = append(args, "--log-size-max", fmt.Sprintf("%v", r.logSizeMax)) + } + + logLevel := logrus.GetLevel() + args = append(args, "--log-level", logLevel.String()) + + if logLevel == logrus.DebugLevel { + logrus.Debugf("%s messages will be logged to syslog", r.conmonPath) + args = append(args, "--syslog") + } + if ociLogPath != "" { + args = append(args, "--runtime-arg", "--log-format=json", "--runtime-arg", "--log", fmt.Sprintf("--runtime-arg=%s", ociLogPath)) + } + if ctr.config.NoCgroups { + logrus.Debugf("Running with no CGroups") + args = append(args, "--runtime-arg", "--cgroup-manager", "--runtime-arg", "disabled") + } + return args +} + +// startCommandGivenSelinux starts a container ensuring to set the labels of +// the process to make sure SELinux doesn't block conmon communication, if SELinux is enabled +func startCommandGivenSelinux(cmd *exec.Cmd) error { + if !selinux.GetEnabled() { + return cmd.Start() + } + // Set the label of the conmon process to be level :s0 + // This will allow the container processes to talk to fifo-files + // passed into the container by conmon + var ( + plabel string + con selinux.Context + err error + ) + plabel, err = selinux.CurrentLabel() + if err != nil { + return errors.Wrapf(err, "Failed to get current SELinux label") + } + + con, err = selinux.NewContext(plabel) + if err != nil { + return errors.Wrapf(err, "Failed to get new context from SELinux label") + } + + runtime.LockOSThread() + if con["level"] != "s0" && con["level"] != "" { + con["level"] = "s0" + if err = label.SetProcessLabel(con.Get()); err != nil { + runtime.UnlockOSThread() + return err + } + } + err = cmd.Start() + // Ignore error returned from SetProcessLabel("") call, + // can't recover. + if labelErr := label.SetProcessLabel(""); labelErr != nil { + logrus.Errorf("unable to set process label: %q", err) + } + runtime.UnlockOSThread() + return err +} + +// moveConmonToCgroupAndSignal gets a container's cgroupParent and moves the conmon process to that cgroup +// it then signals for conmon to start by sending nonse data down the start fd +func (r *ConmonOCIRuntime) moveConmonToCgroupAndSignal(ctr *Container, cmd *exec.Cmd, startFd *os.File) error { + mustCreateCgroup := true + // If cgroup creation is disabled - just signal. + if ctr.config.NoCgroups { + mustCreateCgroup = false + } + + if rootless.IsRootless() { + ownsCgroup, err := cgroups.UserOwnsCurrentSystemdCgroup() + if err != nil { + return err + } + mustCreateCgroup = !ownsCgroup + } + + if mustCreateCgroup { + cgroupParent := ctr.CgroupParent() + if r.cgroupManager == SystemdCgroupsManager { + unitName := createUnitName("libpod-conmon", ctr.ID()) + + realCgroupParent := cgroupParent + splitParent := strings.Split(cgroupParent, "/") + if strings.HasSuffix(cgroupParent, ".slice") && len(splitParent) > 1 { + realCgroupParent = splitParent[len(splitParent)-1] + } + + logrus.Infof("Running conmon under slice %s and unitName %s", realCgroupParent, unitName) + if err := utils.RunUnderSystemdScope(cmd.Process.Pid, realCgroupParent, unitName); err != nil { + logrus.Warnf("Failed to add conmon to systemd sandbox cgroup: %v", err) + } + } else { + cgroupPath := filepath.Join(ctr.config.CgroupParent, "conmon") + control, err := cgroups.New(cgroupPath, &spec.LinuxResources{}) + if err != nil { + logrus.Warnf("Failed to add conmon to cgroupfs sandbox cgroup: %v", err) + } else { + // we need to remove this defer and delete the cgroup once conmon exits + // maybe need a conmon monitor? + if err := control.AddPid(cmd.Process.Pid); err != nil { + logrus.Warnf("Failed to add conmon to cgroupfs sandbox cgroup: %v", err) + } + } + } + } + + /* We set the cgroup, now the child can start creating children */ + if err := writeConmonPipeData(startFd); err != nil { + return err + } + return nil +} + +// newPipe creates a unix socket pair for communication +func newPipe() (parent *os.File, child *os.File, err error) { + fds, err := unix.Socketpair(unix.AF_LOCAL, unix.SOCK_SEQPACKET|unix.SOCK_CLOEXEC, 0) + if err != nil { + return nil, nil, err + } + return os.NewFile(uintptr(fds[1]), "parent"), os.NewFile(uintptr(fds[0]), "child"), nil +} + +// readConmonPidFile attempts to read conmon's pid from its pid file +func readConmonPidFile(pidFile string) (int, error) { + // Let's try reading the Conmon pid at the same time. + if pidFile != "" { + contents, err := ioutil.ReadFile(pidFile) + if err != nil { + return -1, err + } + // Convert it to an int + conmonPID, err := strconv.Atoi(string(contents)) + if err != nil { + return -1, err + } + return conmonPID, nil + } + return 0, nil +} + +// readConmonPipeData attempts to read a syncInfo struct from the pipe +func readConmonPipeData(pipe *os.File, ociLog string) (int, error) { + // syncInfo is used to return data from monitor process to daemon + type syncInfo struct { + Data int `json:"data"` + Message string `json:"message,omitempty"` + } + + // Wait to get container pid from conmon + type syncStruct struct { + si *syncInfo + err error + } + ch := make(chan syncStruct) + go func() { + var si *syncInfo + rdr := bufio.NewReader(pipe) + b, err := rdr.ReadBytes('\n') + if err != nil { + ch <- syncStruct{err: err} + } + if err := json.Unmarshal(b, &si); err != nil { + ch <- syncStruct{err: err} + return + } + ch <- syncStruct{si: si} + }() + + data := -1 + select { + case ss := <-ch: + if ss.err != nil { + if ociLog != "" { + ociLogData, err := ioutil.ReadFile(ociLog) + if err == nil { + var ociErr ociError + if err := json.Unmarshal(ociLogData, &ociErr); err == nil { + return -1, getOCIRuntimeError(ociErr.Msg) + } + } + } + return -1, errors.Wrapf(ss.err, "container create failed (no logs from conmon)") + } + logrus.Debugf("Received: %d", ss.si.Data) + if ss.si.Data < 0 { + if ociLog != "" { + ociLogData, err := ioutil.ReadFile(ociLog) + if err == nil { + var ociErr ociError + if err := json.Unmarshal(ociLogData, &ociErr); err == nil { + return ss.si.Data, getOCIRuntimeError(ociErr.Msg) + } + } + } + // If we failed to parse the JSON errors, then print the output as it is + if ss.si.Message != "" { + return ss.si.Data, getOCIRuntimeError(ss.si.Message) + } + return ss.si.Data, errors.Wrapf(define.ErrInternal, "container create failed") + } + data = ss.si.Data + case <-time.After(ContainerCreateTimeout): + return -1, errors.Wrapf(define.ErrInternal, "container creation timeout") + } + return data, nil +} + +// writeConmonPipeData writes nonse data to a pipe +func writeConmonPipeData(pipe *os.File) error { + someData := []byte{0} + _, err := pipe.Write(someData) + return err +} + +// formatRuntimeOpts prepends opts passed to it with --runtime-opt for passing to conmon +func formatRuntimeOpts(opts ...string) []string { + args := make([]string, 0, len(opts)*2) + for _, o := range opts { + args = append(args, "--runtime-opt", o) + } + return args +} + +// getConmonVersion returns a string representation of the conmon version. +func (r *ConmonOCIRuntime) getConmonVersion() (string, error) { + output, err := utils.ExecCmd(r.conmonPath, "--version") + if err != nil { + return "", err + } + return strings.TrimSuffix(strings.Replace(output, "\n", ", ", 1), "\n"), nil +} + +// getOCIRuntimeVersion returns a string representation of the OCI runtime's +// version. +func (r *ConmonOCIRuntime) getOCIRuntimeVersion() (string, error) { + output, err := utils.ExecCmd(r.path, "--version") + if err != nil { + return "", err + } + return strings.TrimSuffix(output, "\n"), nil +} diff --git a/libpod/oci_conmon_unsupported.go b/libpod/oci_conmon_unsupported.go new file mode 100644 index 000000000..77b06eed3 --- /dev/null +++ b/libpod/oci_conmon_unsupported.go @@ -0,0 +1,130 @@ +// +build !linux + +package libpod + +import ( + "github.com/containers/libpod/libpod/define" +) + +const ( + osNotSupported = "Not supported on this OS" +) + +// ConmonOCIRuntime is not supported on this OS. +type ConmonOCIRuntime struct { +} + +// newConmonOCIRuntime is not supported on this OS. +func newConmonOCIRuntime(name string, paths []string, conmonPath string, runtimeCfg *RuntimeConfig, supportsJSON, supportsNoCgroups bool) (OCIRuntime, error) { + return nil, define.ErrNotImplemented +} + +// Name is not supported on this OS. +func (r *ConmonOCIRuntime) Name() string { + return osNotSupported +} + +// Path is not supported on this OS. +func (r *ConmonOCIRuntime) Path() string { + return osNotSupported +} + +// CreateContainer is not supported on this OS. +func (r *ConmonOCIRuntime) CreateContainer(ctr *Container, restoreOptions *ContainerCheckpointOptions) error { + return define.ErrNotImplemented +} + +// UpdateContainerStatus is not supported on this OS. +func (r *ConmonOCIRuntime) UpdateContainerStatus(ctr *Container, useRuntime bool) error { + return define.ErrNotImplemented +} + +// StartContainer is not supported on this OS. +func (r *ConmonOCIRuntime) StartContainer(ctr *Container) error { + return define.ErrNotImplemented +} + +// KillContainer is not supported on this OS. +func (r *ConmonOCIRuntime) KillContainer(ctr *Container, signal uint, all bool) error { + return define.ErrNotImplemented +} + +// StopContainer is not supported on this OS. +func (r *ConmonOCIRuntime) StopContainer(ctr *Container, timeout uint, all bool) error { + return define.ErrNotImplemented +} + +// DeleteContainer is not supported on this OS. +func (r *ConmonOCIRuntime) DeleteContainer(ctr *Container) error { + return define.ErrNotImplemented +} + +// PauseContainer is not supported on this OS. +func (r *ConmonOCIRuntime) PauseContainer(ctr *Container) error { + return define.ErrNotImplemented +} + +// UnpauseContainer is not supported on this OS. +func (r *ConmonOCIRuntime) UnpauseContainer(ctr *Container) error { + return define.ErrNotImplemented +} + +// ExecContainer is not supported on this OS. +func (r *ConmonOCIRuntime) ExecContainer(ctr *Container, sessionID string, options *ExecOptions) (int, chan error, error) { + return -1, nil, define.ErrNotImplemented +} + +// ExecStopContainer is not supported on this OS. +func (r *ConmonOCIRuntime) ExecStopContainer(ctr *Container, sessionID string, timeout uint) error { + return define.ErrNotImplemented +} + +// CheckpointContainer is not supported on this OS. +func (r *ConmonOCIRuntime) CheckpointContainer(ctr *Container, options ContainerCheckpointOptions) error { + return define.ErrNotImplemented +} + +// SupportsCheckpoint is not supported on this OS. +func (r *ConmonOCIRuntime) SupportsCheckpoint() bool { + return false +} + +// SupportsJSONErrors is not supported on this OS. +func (r *ConmonOCIRuntime) SupportsJSONErrors() bool { + return false +} + +// SupportsNoCgroups is not supported on this OS. +func (r *ConmonOCIRuntime) SupportsNoCgroups() bool { + return false +} + +// AttachSocketPath is not supported on this OS. +func (r *ConmonOCIRuntime) AttachSocketPath(ctr *Container) (string, error) { + return "", define.ErrNotImplemented +} + +// ExecAttachSocketPath is not supported on this OS. +func (r *ConmonOCIRuntime) ExecAttachSocketPath(ctr *Container, sessionID string) (string, error) { + return "", define.ErrNotImplemented +} + +// ExitFilePath is not supported on this OS. +func (r *ConmonOCIRuntime) ExitFilePath(ctr *Container) (string, error) { + return "", define.ErrNotImplemented +} + +// RuntimeInfo is not supported on this OS. +func (r *ConmonOCIRuntime) RuntimeInfo() (map[string]interface{}, error) { + return nil, define.ErrNotImplemented +} + +// Package is not supported on this OS. +func (r *ConmonOCIRuntime) Package() string { + return osNotSupported +} + +// ConmonPackage is not supported on this OS. +func (r *ConmonOCIRuntime) ConmonPackage() string { + return osNotSupported +} diff --git a/libpod/oci_internal_linux.go b/libpod/oci_internal_linux.go deleted file mode 100644 index 437b7cf4d..000000000 --- a/libpod/oci_internal_linux.go +++ /dev/null @@ -1,556 +0,0 @@ -// +build linux - -package libpod - -import ( - "bufio" - "bytes" - "fmt" - "io/ioutil" - "os" - "os/exec" - "path/filepath" - "regexp" - "runtime" - "strconv" - "strings" - "syscall" - "time" - - "github.com/containers/libpod/libpod/define" - "github.com/containers/libpod/pkg/cgroups" - "github.com/containers/libpod/pkg/errorhandling" - "github.com/containers/libpod/pkg/lookup" - "github.com/containers/libpod/pkg/rootless" - "github.com/containers/libpod/pkg/util" - "github.com/containers/libpod/utils" - "github.com/coreos/go-systemd/activation" - spec "github.com/opencontainers/runtime-spec/specs-go" - "github.com/opencontainers/selinux/go-selinux" - "github.com/opencontainers/selinux/go-selinux/label" - "github.com/pkg/errors" - "github.com/sirupsen/logrus" - "golang.org/x/sys/unix" -) - -// createOCIContainer generates this container's main conmon instance and prepares it for starting -func (r *OCIRuntime) createOCIContainer(ctr *Container, restoreOptions *ContainerCheckpointOptions) (err error) { - var stderrBuf bytes.Buffer - - runtimeDir, err := util.GetRuntimeDir() - if err != nil { - return err - } - - parentSyncPipe, childSyncPipe, err := newPipe() - if err != nil { - return errors.Wrapf(err, "error creating socket pair") - } - defer errorhandling.CloseQuiet(parentSyncPipe) - - childStartPipe, parentStartPipe, err := newPipe() - if err != nil { - return errors.Wrapf(err, "error creating socket pair for start pipe") - } - - defer errorhandling.CloseQuiet(parentStartPipe) - - var ociLog string - if logrus.GetLevel() != logrus.DebugLevel && r.supportsJSON { - ociLog = filepath.Join(ctr.state.RunDir, "oci-log") - } - args := r.sharedConmonArgs(ctr, ctr.ID(), ctr.bundlePath(), filepath.Join(ctr.state.RunDir, "pidfile"), ctr.LogPath(), r.exitsDir, ociLog) - - if ctr.config.Spec.Process.Terminal { - args = append(args, "-t") - } else if ctr.config.Stdin { - args = append(args, "-i") - } - - if ctr.config.ConmonPidFile != "" { - args = append(args, "--conmon-pidfile", ctr.config.ConmonPidFile) - } - - if r.noPivot { - args = append(args, "--no-pivot") - } - - if len(ctr.config.ExitCommand) > 0 { - args = append(args, "--exit-command", ctr.config.ExitCommand[0]) - for _, arg := range ctr.config.ExitCommand[1:] { - args = append(args, []string{"--exit-command-arg", arg}...) - } - } - - if restoreOptions != nil { - args = append(args, "--restore", ctr.CheckpointPath()) - if restoreOptions.TCPEstablished { - args = append(args, "--runtime-opt", "--tcp-established") - } - } - - logrus.WithFields(logrus.Fields{ - "args": args, - }).Debugf("running conmon: %s", r.conmonPath) - - cmd := exec.Command(r.conmonPath, args...) - cmd.Dir = ctr.bundlePath() - cmd.SysProcAttr = &syscall.SysProcAttr{ - Setpgid: true, - } - // TODO this is probably a really bad idea for some uses - // Make this configurable - cmd.Stdin = os.Stdin - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - if ctr.config.Spec.Process.Terminal { - cmd.Stderr = &stderrBuf - } - - // 0, 1 and 2 are stdin, stdout and stderr - conmonEnv, envFiles, err := r.configureConmonEnv(runtimeDir) - if err != nil { - return err - } - - cmd.Env = append(r.conmonEnv, fmt.Sprintf("_OCI_SYNCPIPE=%d", 3), fmt.Sprintf("_OCI_STARTPIPE=%d", 4)) - cmd.Env = append(cmd.Env, conmonEnv...) - cmd.ExtraFiles = append(cmd.ExtraFiles, childSyncPipe, childStartPipe) - cmd.ExtraFiles = append(cmd.ExtraFiles, envFiles...) - - if r.reservePorts && !ctr.config.NetMode.IsSlirp4netns() { - ports, err := bindPorts(ctr.config.PortMappings) - if err != nil { - return err - } - - // Leak the port we bound in the conmon process. These fd's won't be used - // by the container and conmon will keep the ports busy so that another - // process cannot use them. - cmd.ExtraFiles = append(cmd.ExtraFiles, ports...) - } - - if ctr.config.NetMode.IsSlirp4netns() { - if ctr.config.PostConfigureNetNS { - ctr.rootlessSlirpSyncR, ctr.rootlessSlirpSyncW, err = os.Pipe() - if err != nil { - return errors.Wrapf(err, "failed to create rootless network sync pipe") - } - } else { - if ctr.rootlessSlirpSyncR != nil { - defer errorhandling.CloseQuiet(ctr.rootlessSlirpSyncR) - } - if ctr.rootlessSlirpSyncW != nil { - defer errorhandling.CloseQuiet(ctr.rootlessSlirpSyncW) - } - } - // Leak one end in conmon, the other one will be leaked into slirp4netns - cmd.ExtraFiles = append(cmd.ExtraFiles, ctr.rootlessSlirpSyncW) - } - - err = startCommandGivenSelinux(cmd) - // regardless of whether we errored or not, we no longer need the children pipes - childSyncPipe.Close() - childStartPipe.Close() - if err != nil { - return err - } - if err := r.moveConmonToCgroupAndSignal(ctr, cmd, parentStartPipe, ctr.ID()); err != nil { - return err - } - /* Wait for initial setup and fork, and reap child */ - err = cmd.Wait() - if err != nil { - return err - } - - pid, err := readConmonPipeData(parentSyncPipe, ociLog) - if err != nil { - if err2 := r.deleteContainer(ctr); err2 != nil { - logrus.Errorf("Error removing container %s from runtime after creation failed", ctr.ID()) - } - return err - } - ctr.state.PID = pid - - conmonPID, err := readConmonPidFile(ctr.config.ConmonPidFile) - if err != nil { - logrus.Warnf("error reading conmon pid file for container %s: %s", ctr.ID(), err.Error()) - } else if conmonPID > 0 { - // conmon not having a pid file is a valid state, so don't set it if we don't have it - logrus.Infof("Got Conmon PID as %d", conmonPID) - ctr.state.ConmonPID = conmonPID - } - - return nil -} - -// prepareProcessExec returns the path of the process.json used in runc exec -p -// caller is responsible to close the returned *os.File if needed. -func prepareProcessExec(c *Container, cmd, env []string, tty bool, cwd, user, sessionID string) (*os.File, error) { - f, err := ioutil.TempFile(c.execBundlePath(sessionID), "exec-process-") - if err != nil { - return nil, err - } - - pspec := c.config.Spec.Process - pspec.Args = cmd - // We need to default this to false else it will inherit terminal as true - // from the container. - pspec.Terminal = false - if tty { - pspec.Terminal = true - } - if len(env) > 0 { - pspec.Env = append(pspec.Env, env...) - } - - if cwd != "" { - pspec.Cwd = cwd - - } - - overrides := c.getUserOverrides() - execUser, err := lookup.GetUserGroupInfo(c.state.Mountpoint, user, overrides) - if err != nil { - return nil, err - } - - // If user was set, look it up in the container to get a UID to use on - // the host - if user != "" { - sgids := make([]uint32, 0, len(execUser.Sgids)) - for _, sgid := range execUser.Sgids { - sgids = append(sgids, uint32(sgid)) - } - processUser := spec.User{ - UID: uint32(execUser.Uid), - GID: uint32(execUser.Gid), - AdditionalGids: sgids, - } - - pspec.User = processUser - } - - hasHomeSet := false - for _, s := range pspec.Env { - if strings.HasPrefix(s, "HOME=") { - hasHomeSet = true - break - } - } - if !hasHomeSet { - pspec.Env = append(pspec.Env, fmt.Sprintf("HOME=%s", execUser.Home)) - } - - processJSON, err := json.Marshal(pspec) - if err != nil { - return nil, err - } - - if err := ioutil.WriteFile(f.Name(), processJSON, 0644); err != nil { - return nil, err - } - return f, nil -} - -// configureConmonEnv gets the environment values to add to conmon's exec struct -// TODO this may want to be less hardcoded/more configurable in the future -func (r *OCIRuntime) configureConmonEnv(runtimeDir string) ([]string, []*os.File, error) { - env := make([]string, 0, 6) - env = append(env, fmt.Sprintf("XDG_RUNTIME_DIR=%s", runtimeDir)) - env = append(env, fmt.Sprintf("_CONTAINERS_USERNS_CONFIGURED=%s", os.Getenv("_CONTAINERS_USERNS_CONFIGURED"))) - env = append(env, fmt.Sprintf("_CONTAINERS_ROOTLESS_UID=%s", os.Getenv("_CONTAINERS_ROOTLESS_UID"))) - home, err := homeDir() - if err != nil { - return nil, nil, err - } - env = append(env, fmt.Sprintf("HOME=%s", home)) - - extraFiles := make([]*os.File, 0) - if notify, ok := os.LookupEnv("NOTIFY_SOCKET"); ok { - env = append(env, fmt.Sprintf("NOTIFY_SOCKET=%s", notify)) - } - if !r.sdNotify { - if listenfds, ok := os.LookupEnv("LISTEN_FDS"); ok { - env = append(env, fmt.Sprintf("LISTEN_FDS=%s", listenfds), "LISTEN_PID=1") - fds := activation.Files(false) - extraFiles = append(extraFiles, fds...) - } - } else { - logrus.Debug("disabling SD notify") - } - return env, extraFiles, nil -} - -// sharedConmonArgs takes common arguments for exec and create/restore and formats them for the conmon CLI -func (r *OCIRuntime) sharedConmonArgs(ctr *Container, cuuid, bundlePath, pidPath, logPath, exitDir, ociLogPath string) []string { - // set the conmon API version to be able to use the correct sync struct keys - args := []string{"--api-version", "1"} - if r.cgroupManager == SystemdCgroupsManager && !ctr.config.NoCgroups { - args = append(args, "-s") - } - args = append(args, "-c", ctr.ID()) - args = append(args, "-u", cuuid) - args = append(args, "-r", r.path) - args = append(args, "-b", bundlePath) - args = append(args, "-p", pidPath) - - var logDriver string - switch ctr.LogDriver() { - case JournaldLogging: - logDriver = JournaldLogging - case JSONLogging: - fallthrough - default: //nolint-stylecheck - // No case here should happen except JSONLogging, but keep this here in case the options are extended - logrus.Errorf("%s logging specified but not supported. Choosing k8s-file logging instead", ctr.LogDriver()) - fallthrough - case "": - // to get here, either a user would specify `--log-driver ""`, or this came from another place in libpod - // since the former case is obscure, and the latter case isn't an error, let's silently fallthrough - fallthrough - case KubernetesLogging: - logDriver = fmt.Sprintf("%s:%s", KubernetesLogging, logPath) - } - - args = append(args, "-l", logDriver) - args = append(args, "--exit-dir", exitDir) - args = append(args, "--socket-dir-path", r.socketsDir) - if r.logSizeMax >= 0 { - args = append(args, "--log-size-max", fmt.Sprintf("%v", r.logSizeMax)) - } - - logLevel := logrus.GetLevel() - args = append(args, "--log-level", logLevel.String()) - - if logLevel == logrus.DebugLevel { - logrus.Debugf("%s messages will be logged to syslog", r.conmonPath) - args = append(args, "--syslog") - } - if ociLogPath != "" { - args = append(args, "--runtime-arg", "--log-format=json", "--runtime-arg", "--log", fmt.Sprintf("--runtime-arg=%s", ociLogPath)) - } - if ctr.config.NoCgroups { - logrus.Debugf("Running with no CGroups") - args = append(args, "--runtime-arg", "--cgroup-manager", "--runtime-arg", "disabled") - } - return args -} - -// startCommandGivenSelinux starts a container ensuring to set the labels of -// the process to make sure SELinux doesn't block conmon communication, if SELinux is enabled -func startCommandGivenSelinux(cmd *exec.Cmd) error { - if !selinux.GetEnabled() { - return cmd.Start() - } - // Set the label of the conmon process to be level :s0 - // This will allow the container processes to talk to fifo-files - // passed into the container by conmon - var ( - plabel string - con selinux.Context - err error - ) - plabel, err = selinux.CurrentLabel() - if err != nil { - return errors.Wrapf(err, "Failed to get current SELinux label") - } - - con, err = selinux.NewContext(plabel) - if err != nil { - return errors.Wrapf(err, "Failed to get new context from SELinux label") - } - - runtime.LockOSThread() - if con["level"] != "s0" && con["level"] != "" { - con["level"] = "s0" - if err = label.SetProcessLabel(con.Get()); err != nil { - runtime.UnlockOSThread() - return err - } - } - err = cmd.Start() - // Ignore error returned from SetProcessLabel("") call, - // can't recover. - if labelErr := label.SetProcessLabel(""); labelErr != nil { - logrus.Errorf("unable to set process label: %q", err) - } - runtime.UnlockOSThread() - return err -} - -// moveConmonToCgroupAndSignal gets a container's cgroupParent and moves the conmon process to that cgroup -// it then signals for conmon to start by sending nonse data down the start fd -func (r *OCIRuntime) moveConmonToCgroupAndSignal(ctr *Container, cmd *exec.Cmd, startFd *os.File, uuid string) error { - mustCreateCgroup := true - // If cgroup creation is disabled - just signal. - if ctr.config.NoCgroups { - mustCreateCgroup = false - } - - if rootless.IsRootless() { - ownsCgroup, err := cgroups.UserOwnsCurrentSystemdCgroup() - if err != nil { - return err - } - mustCreateCgroup = !ownsCgroup - } - - if mustCreateCgroup { - cgroupParent := ctr.CgroupParent() - if r.cgroupManager == SystemdCgroupsManager { - unitName := createUnitName("libpod-conmon", ctr.ID()) - - realCgroupParent := cgroupParent - splitParent := strings.Split(cgroupParent, "/") - if strings.HasSuffix(cgroupParent, ".slice") && len(splitParent) > 1 { - realCgroupParent = splitParent[len(splitParent)-1] - } - - logrus.Infof("Running conmon under slice %s and unitName %s", realCgroupParent, unitName) - if err := utils.RunUnderSystemdScope(cmd.Process.Pid, realCgroupParent, unitName); err != nil { - logrus.Warnf("Failed to add conmon to systemd sandbox cgroup: %v", err) - } - } else { - cgroupPath := filepath.Join(ctr.config.CgroupParent, "conmon") - control, err := cgroups.New(cgroupPath, &spec.LinuxResources{}) - if err != nil { - logrus.Warnf("Failed to add conmon to cgroupfs sandbox cgroup: %v", err) - } else { - // we need to remove this defer and delete the cgroup once conmon exits - // maybe need a conmon monitor? - if err := control.AddPid(cmd.Process.Pid); err != nil { - logrus.Warnf("Failed to add conmon to cgroupfs sandbox cgroup: %v", err) - } - } - } - } - - /* We set the cgroup, now the child can start creating children */ - if err := writeConmonPipeData(startFd); err != nil { - return err - } - return nil -} - -// newPipe creates a unix socket pair for communication -func newPipe() (parent *os.File, child *os.File, err error) { - fds, err := unix.Socketpair(unix.AF_LOCAL, unix.SOCK_SEQPACKET|unix.SOCK_CLOEXEC, 0) - if err != nil { - return nil, nil, err - } - return os.NewFile(uintptr(fds[1]), "parent"), os.NewFile(uintptr(fds[0]), "child"), nil -} - -// readConmonPidFile attempts to read conmon's pid from its pid file -func readConmonPidFile(pidFile string) (int, error) { - // Let's try reading the Conmon pid at the same time. - if pidFile != "" { - contents, err := ioutil.ReadFile(pidFile) - if err != nil { - return -1, err - } - // Convert it to an int - conmonPID, err := strconv.Atoi(string(contents)) - if err != nil { - return -1, err - } - return conmonPID, nil - } - return 0, nil -} - -// readConmonPipeData attempts to read a syncInfo struct from the pipe -func readConmonPipeData(pipe *os.File, ociLog string) (int, error) { - // syncInfo is used to return data from monitor process to daemon - type syncInfo struct { - Data int `json:"data"` - Message string `json:"message,omitempty"` - } - - // Wait to get container pid from conmon - type syncStruct struct { - si *syncInfo - err error - } - ch := make(chan syncStruct) - go func() { - var si *syncInfo - rdr := bufio.NewReader(pipe) - b, err := rdr.ReadBytes('\n') - if err != nil { - ch <- syncStruct{err: err} - } - if err := json.Unmarshal(b, &si); err != nil { - ch <- syncStruct{err: err} - return - } - ch <- syncStruct{si: si} - }() - - data := -1 - select { - case ss := <-ch: - if ss.err != nil { - if ociLog != "" { - ociLogData, err := ioutil.ReadFile(ociLog) - if err == nil { - var ociErr ociError - if err := json.Unmarshal(ociLogData, &ociErr); err == nil { - return -1, getOCIRuntimeError(ociErr.Msg) - } - } - } - return -1, errors.Wrapf(ss.err, "error reading container (probably exited) json message") - } - logrus.Debugf("Received: %d", ss.si.Data) - if ss.si.Data < 0 { - if ociLog != "" { - ociLogData, err := ioutil.ReadFile(ociLog) - if err == nil { - var ociErr ociError - if err := json.Unmarshal(ociLogData, &ociErr); err == nil { - return ss.si.Data, getOCIRuntimeError(ociErr.Msg) - } - } - } - // If we failed to parse the JSON errors, then print the output as it is - if ss.si.Message != "" { - return ss.si.Data, getOCIRuntimeError(ss.si.Message) - } - return ss.si.Data, errors.Wrapf(define.ErrInternal, "container create failed") - } - data = ss.si.Data - case <-time.After(ContainerCreateTimeout): - return -1, errors.Wrapf(define.ErrInternal, "container creation timeout") - } - return data, nil -} - -func getOCIRuntimeError(runtimeMsg string) error { - r := strings.ToLower(runtimeMsg) - if match, _ := regexp.MatchString(".*permission denied.*|.*operation not permitted.*", r); match { - return errors.Wrapf(define.ErrOCIRuntimePermissionDenied, "%s", strings.Trim(runtimeMsg, "\n")) - } - if match, _ := regexp.MatchString(".*executable file not found in.*|.*no such file or directory.*", r); match { - return errors.Wrapf(define.ErrOCIRuntimeNotFound, "%s", strings.Trim(runtimeMsg, "\n")) - } - return errors.Wrapf(define.ErrOCIRuntime, "%s", strings.Trim(runtimeMsg, "\n")) -} - -// writeConmonPipeData writes nonse data to a pipe -func writeConmonPipeData(pipe *os.File) error { - someData := []byte{0} - _, err := pipe.Write(someData) - return err -} - -// formatRuntimeOpts prepends opts passed to it with --runtime-opt for passing to conmon -func formatRuntimeOpts(opts ...string) []string { - args := make([]string, 0, len(opts)*2) - for _, o := range opts { - args = append(args, "--runtime-opt", o) - } - return args -} diff --git a/libpod/oci_linux.go b/libpod/oci_linux.go deleted file mode 100644 index 9ec074704..000000000 --- a/libpod/oci_linux.go +++ /dev/null @@ -1,503 +0,0 @@ -// +build linux - -package libpod - -import ( - "fmt" - "os" - "os/exec" - "path/filepath" - "runtime" - "strconv" - "strings" - "syscall" - "time" - - "github.com/containers/libpod/libpod/define" - "github.com/containers/libpod/pkg/errorhandling" - "github.com/containers/libpod/pkg/rootless" - "github.com/containers/libpod/pkg/util" - "github.com/containers/libpod/utils" - pmount "github.com/containers/storage/pkg/mount" - "github.com/pkg/errors" - "github.com/sirupsen/logrus" - "golang.org/x/sys/unix" - "k8s.io/client-go/tools/remotecommand" -) - -// makeAccessible changes the path permission and each parent directory to have --x--x--x -func makeAccessible(path string, uid, gid int) error { - for ; path != "/"; path = filepath.Dir(path) { - st, err := os.Stat(path) - if err != nil { - if os.IsNotExist(err) { - return nil - } - return err - } - if int(st.Sys().(*syscall.Stat_t).Uid) == uid && int(st.Sys().(*syscall.Stat_t).Gid) == gid { - continue - } - if st.Mode()&0111 != 0111 { - if err := os.Chmod(path, st.Mode()|0111); err != nil { - return err - } - } - } - return nil -} - -// CreateContainer creates a container in the OCI runtime -// TODO terminal support for container -// Presently just ignoring conmon opts related to it -func (r *OCIRuntime) createContainer(ctr *Container, restoreOptions *ContainerCheckpointOptions) (err error) { - if len(ctr.config.IDMappings.UIDMap) != 0 || len(ctr.config.IDMappings.GIDMap) != 0 { - for _, i := range []string{ctr.state.RunDir, ctr.runtime.config.TmpDir, ctr.config.StaticDir, ctr.state.Mountpoint, ctr.runtime.config.VolumePath} { - if err := makeAccessible(i, ctr.RootUID(), ctr.RootGID()); err != nil { - return err - } - } - - // if we are running a non privileged container, be sure to umount some kernel paths so they are not - // bind mounted inside the container at all. - if !ctr.config.Privileged && !rootless.IsRootless() { - ch := make(chan error) - go func() { - runtime.LockOSThread() - err := func() error { - fd, err := os.Open(fmt.Sprintf("/proc/%d/task/%d/ns/mnt", os.Getpid(), unix.Gettid())) - if err != nil { - return err - } - defer errorhandling.CloseQuiet(fd) - - // create a new mountns on the current thread - if err = unix.Unshare(unix.CLONE_NEWNS); err != nil { - return err - } - defer func() { - if err := unix.Setns(int(fd.Fd()), unix.CLONE_NEWNS); err != nil { - logrus.Errorf("unable to clone new namespace: %q", err) - } - }() - - // don't spread our mounts around. We are setting only /sys to be slave - // so that the cleanup process is still able to umount the storage and the - // changes are propagated to the host. - err = unix.Mount("/sys", "/sys", "none", unix.MS_REC|unix.MS_SLAVE, "") - if err != nil { - return errors.Wrapf(err, "cannot make /sys slave") - } - - mounts, err := pmount.GetMounts() - if err != nil { - return err - } - for _, m := range mounts { - if !strings.HasPrefix(m.Mountpoint, "/sys/kernel") { - continue - } - err = unix.Unmount(m.Mountpoint, 0) - if err != nil && !os.IsNotExist(err) { - return errors.Wrapf(err, "cannot unmount %s", m.Mountpoint) - } - } - return r.createOCIContainer(ctr, restoreOptions) - }() - ch <- err - }() - err := <-ch - return err - } - } - return r.createOCIContainer(ctr, restoreOptions) -} - -func (r *OCIRuntime) pathPackage() string { - return packageVersion(r.path) -} - -func (r *OCIRuntime) conmonPackage() string { - return packageVersion(r.conmonPath) -} - -// execContainer executes a command in a running container -// TODO: Add --detach support -// TODO: Convert to use conmon -// TODO: add --pid-file and use that to generate exec session tracking -func (r *OCIRuntime) execContainer(c *Container, cmd, capAdd, env []string, tty bool, cwd, user, sessionID string, streams *AttachStreams, preserveFDs int, resize chan remotecommand.TerminalSize, detachKeys string) (int, chan error, error) { - if len(cmd) == 0 { - return -1, nil, errors.Wrapf(define.ErrInvalidArg, "must provide a command to execute") - } - - if sessionID == "" { - return -1, nil, errors.Wrapf(define.ErrEmptyID, "must provide a session ID for exec") - } - - // create sync pipe to receive the pid - parentSyncPipe, childSyncPipe, err := newPipe() - if err != nil { - return -1, nil, errors.Wrapf(err, "error creating socket pair") - } - - defer errorhandling.CloseQuiet(parentSyncPipe) - - // create start pipe to set the cgroup before running - // attachToExec is responsible for closing parentStartPipe - childStartPipe, parentStartPipe, err := newPipe() - if err != nil { - return -1, nil, errors.Wrapf(err, "error creating socket pair") - } - - // We want to make sure we close the parent{Start,Attach}Pipes if we fail - // but also don't want to close them after attach to exec is called - attachToExecCalled := false - - defer func() { - if !attachToExecCalled { - errorhandling.CloseQuiet(parentStartPipe) - } - }() - - // create the attach pipe to allow attach socket to be created before - // $RUNTIME exec starts running. This is to make sure we can capture all output - // from the process through that socket, rather than half reading the log, half attaching to the socket - // attachToExec is responsible for closing parentAttachPipe - parentAttachPipe, childAttachPipe, err := newPipe() - if err != nil { - return -1, nil, errors.Wrapf(err, "error creating socket pair") - } - - defer func() { - if !attachToExecCalled { - errorhandling.CloseQuiet(parentAttachPipe) - } - }() - - childrenClosed := false - defer func() { - if !childrenClosed { - errorhandling.CloseQuiet(childSyncPipe) - errorhandling.CloseQuiet(childAttachPipe) - errorhandling.CloseQuiet(childStartPipe) - } - }() - - runtimeDir, err := util.GetRuntimeDir() - if err != nil { - return -1, nil, err - } - - processFile, err := prepareProcessExec(c, cmd, env, tty, cwd, user, sessionID) - if err != nil { - return -1, nil, err - } - - var ociLog string - if logrus.GetLevel() != logrus.DebugLevel && r.supportsJSON { - ociLog = c.execOCILog(sessionID) - } - args := r.sharedConmonArgs(c, sessionID, c.execBundlePath(sessionID), c.execPidPath(sessionID), c.execLogPath(sessionID), c.execExitFileDir(sessionID), ociLog) - - if preserveFDs > 0 { - args = append(args, formatRuntimeOpts("--preserve-fds", strconv.Itoa(preserveFDs))...) - } - - for _, capability := range capAdd { - args = append(args, formatRuntimeOpts("--cap", capability)...) - } - - if tty { - args = append(args, "-t") - } - - // Append container ID and command - args = append(args, "-e") - // TODO make this optional when we can detach - args = append(args, "--exec-attach") - args = append(args, "--exec-process-spec", processFile.Name()) - - logrus.WithFields(logrus.Fields{ - "args": args, - }).Debugf("running conmon: %s", r.conmonPath) - execCmd := exec.Command(r.conmonPath, args...) - - if streams.AttachInput { - execCmd.Stdin = streams.InputStream - } - if streams.AttachOutput { - execCmd.Stdout = streams.OutputStream - } - if streams.AttachError { - execCmd.Stderr = streams.ErrorStream - } - - conmonEnv, extraFiles, err := r.configureConmonEnv(runtimeDir) - if err != nil { - return -1, nil, err - } - - if preserveFDs > 0 { - for fd := 3; fd < 3+preserveFDs; fd++ { - execCmd.ExtraFiles = append(execCmd.ExtraFiles, os.NewFile(uintptr(fd), fmt.Sprintf("fd-%d", fd))) - } - } - - // we don't want to step on users fds they asked to preserve - // Since 0-2 are used for stdio, start the fds we pass in at preserveFDs+3 - execCmd.Env = append(r.conmonEnv, fmt.Sprintf("_OCI_SYNCPIPE=%d", preserveFDs+3), fmt.Sprintf("_OCI_STARTPIPE=%d", preserveFDs+4), fmt.Sprintf("_OCI_ATTACHPIPE=%d", preserveFDs+5)) - execCmd.Env = append(execCmd.Env, conmonEnv...) - - execCmd.ExtraFiles = append(execCmd.ExtraFiles, childSyncPipe, childStartPipe, childAttachPipe) - execCmd.ExtraFiles = append(execCmd.ExtraFiles, extraFiles...) - execCmd.Dir = c.execBundlePath(sessionID) - execCmd.SysProcAttr = &syscall.SysProcAttr{ - Setpgid: true, - } - - err = startCommandGivenSelinux(execCmd) - - // We don't need children pipes on the parent side - errorhandling.CloseQuiet(childSyncPipe) - errorhandling.CloseQuiet(childAttachPipe) - errorhandling.CloseQuiet(childStartPipe) - childrenClosed = true - - if err != nil { - return -1, nil, errors.Wrapf(err, "cannot start container %s", c.ID()) - } - if err := r.moveConmonToCgroupAndSignal(c, execCmd, parentStartPipe, sessionID); err != nil { - return -1, nil, err - } - - if preserveFDs > 0 { - for fd := 3; fd < 3+preserveFDs; fd++ { - // These fds were passed down to the runtime. Close them - // and not interfere - if err := os.NewFile(uintptr(fd), fmt.Sprintf("fd-%d", fd)).Close(); err != nil { - logrus.Debugf("unable to close file fd-%d", fd) - } - } - } - - // TODO Only create if !detach - // Attach to the container before starting it - attachChan := make(chan error) - go func() { - // attachToExec is responsible for closing pipes - attachChan <- c.attachToExec(streams, detachKeys, resize, sessionID, parentStartPipe, parentAttachPipe) - close(attachChan) - }() - attachToExecCalled = true - - pid, err := readConmonPipeData(parentSyncPipe, ociLog) - - return pid, attachChan, err -} - -// Wait for a container which has been sent a signal to stop -func waitContainerStop(ctr *Container, timeout time.Duration) error { - done := make(chan struct{}) - chControl := make(chan struct{}) - go func() { - for { - select { - case <-chControl: - return - default: - // Check if the process is still around - err := unix.Kill(ctr.state.PID, 0) - if err == unix.ESRCH { - close(done) - return - } - time.Sleep(100 * time.Millisecond) - } - } - }() - select { - case <-done: - return nil - case <-time.After(timeout): - close(chControl) - logrus.Debugf("container %s did not die within timeout %d", ctr.ID(), timeout) - return errors.Errorf("container %s did not die within timeout", ctr.ID()) - } -} - -// Wait for a set of given PIDs to stop -func waitPidsStop(pids []int, timeout time.Duration) error { - done := make(chan struct{}) - chControl := make(chan struct{}) - go func() { - for { - select { - case <-chControl: - return - default: - allClosed := true - for _, pid := range pids { - if err := unix.Kill(pid, 0); err != unix.ESRCH { - allClosed = false - break - } - } - if allClosed { - close(done) - return - } - time.Sleep(100 * time.Millisecond) - } - } - }() - select { - case <-done: - return nil - case <-time.After(timeout): - close(chControl) - return errors.Errorf("given PIDs did not die within timeout") - } -} - -// stopContainer stops a container, first using its given stop signal (or -// SIGTERM if no signal was specified), then using SIGKILL -// Timeout is given in seconds. If timeout is 0, the container will be -// immediately kill with SIGKILL -// Does not set finished time for container, assumes you will run updateStatus -// after to pull the exit code -func (r *OCIRuntime) stopContainer(ctr *Container, timeout uint) error { - logrus.Debugf("Stopping container %s (PID %d)", ctr.ID(), ctr.state.PID) - - // Ping the container to see if it's alive - // If it's not, it's already stopped, return - err := unix.Kill(ctr.state.PID, 0) - if err == unix.ESRCH { - return nil - } - - stopSignal := ctr.config.StopSignal - if stopSignal == 0 { - stopSignal = uint(syscall.SIGTERM) - } - - if timeout > 0 { - if err := r.killContainer(ctr, stopSignal); err != nil { - // Is the container gone? - // If so, it probably died between the first check and - // our sending the signal - // The container is stopped, so exit cleanly - err := unix.Kill(ctr.state.PID, 0) - if err == unix.ESRCH { - return nil - } - - return err - } - - if err := waitContainerStop(ctr, time.Duration(timeout)*time.Second); err != nil { - logrus.Warnf("Timed out stopping container %s, resorting to SIGKILL", ctr.ID()) - } else { - // No error, the container is dead - return nil - } - } - - var args []string - if rootless.IsRootless() || ctr.config.NoCgroups { - // we don't use --all for rootless containers as the OCI runtime might use - // the cgroups to determine the PIDs, but for rootless containers there is - // not any. - // Same logic for NoCgroups - we can't use cgroups as the user - // explicitly requested none be created. - args = []string{"kill", ctr.ID(), "KILL"} - } else { - args = []string{"kill", "--all", ctr.ID(), "KILL"} - } - - runtimeDir, err := util.GetRuntimeDir() - if err != nil { - return err - } - env := []string{fmt.Sprintf("XDG_RUNTIME_DIR=%s", runtimeDir)} - if err := utils.ExecCmdWithStdStreams(os.Stdin, os.Stdout, os.Stderr, env, r.path, args...); err != nil { - // Again, check if the container is gone. If it is, exit cleanly. - err := unix.Kill(ctr.state.PID, 0) - if err == unix.ESRCH { - return nil - } - - return errors.Wrapf(err, "error sending SIGKILL to container %s", ctr.ID()) - } - - // Give runtime a few seconds to make it happen - if err := waitContainerStop(ctr, killContainerTimeout); err != nil { - return err - } - - return nil -} - -// execStopContainer stops all active exec sessions in a container -// It will also stop all other processes in the container. It is only intended -// to be used to assist in cleanup when removing a container. -// SIGTERM is used by default to stop processes. If SIGTERM fails, SIGKILL will be used. -func (r *OCIRuntime) execStopContainer(ctr *Container, timeout uint) error { - // Do we have active exec sessions? - if len(ctr.state.ExecSessions) == 0 { - return nil - } - - // Get a list of active exec sessions - execSessions := []int{} - for _, session := range ctr.state.ExecSessions { - pid := session.PID - // Ping the PID with signal 0 to see if it still exists - if err := unix.Kill(pid, 0); err == unix.ESRCH { - continue - } - - execSessions = append(execSessions, pid) - } - - // All the sessions may be dead - // If they are, just return - if len(execSessions) == 0 { - return nil - } - runtimeDir, err := util.GetRuntimeDir() - if err != nil { - return err - } - env := []string{fmt.Sprintf("XDG_RUNTIME_DIR=%s", runtimeDir)} - - // If timeout is 0, just use SIGKILL - if timeout > 0 { - // Stop using SIGTERM by default - // Use SIGSTOP after a timeout - logrus.Debugf("Killing all processes in container %s with SIGTERM", ctr.ID()) - if err := utils.ExecCmdWithStdStreams(os.Stdin, os.Stdout, os.Stderr, env, r.path, "kill", "--all", ctr.ID(), "TERM"); err != nil { - return errors.Wrapf(err, "error sending SIGTERM to container %s processes", ctr.ID()) - } - - // Wait for all processes to stop - if err := waitPidsStop(execSessions, time.Duration(timeout)*time.Second); err != nil { - logrus.Warnf("Timed out stopping container %s exec sessions", ctr.ID()) - } else { - // No error, all exec sessions are dead - return nil - } - } - - // Send SIGKILL - logrus.Debugf("Killing all processes in container %s with SIGKILL", ctr.ID()) - if err := utils.ExecCmdWithStdStreams(os.Stdin, os.Stdout, os.Stderr, env, r.path, "kill", "--all", ctr.ID(), "KILL"); err != nil { - return errors.Wrapf(err, "error sending SIGKILL to container %s processes", ctr.ID()) - } - - // Give the processes a few seconds to go down - if err := waitPidsStop(execSessions, killContainerTimeout); err != nil { - return errors.Wrapf(err, "failed to kill container %s exec sessions", ctr.ID()) - } - - return nil -} diff --git a/libpod/oci_missing.go b/libpod/oci_missing.go new file mode 100644 index 000000000..d4524cd34 --- /dev/null +++ b/libpod/oci_missing.go @@ -0,0 +1,189 @@ +package libpod + +import ( + "fmt" + "path/filepath" + "sync" + + "github.com/containers/libpod/libpod/define" + "github.com/pkg/errors" + "github.com/sirupsen/logrus" +) + +var ( + // Only create each missing runtime once. + // Creation makes error messages we don't want to duplicate. + missingRuntimes map[string]*MissingRuntime + // We need a lock for this + missingRuntimesLock sync.Mutex +) + +// MissingRuntime is used when the OCI runtime requested by the container is +// missing (not installed or not in the configuration file). +type MissingRuntime struct { + // Name is the name of the missing runtime. Will be used in errors. + name string + // exitsDir is the directory for exit files. + exitsDir string +} + +// Get a new MissingRuntime for the given name. +// Requires a libpod Runtime so we can make a sane path for the exits dir. +func getMissingRuntime(name string, r *Runtime) (OCIRuntime, error) { + missingRuntimesLock.Lock() + defer missingRuntimesLock.Unlock() + + if missingRuntimes == nil { + missingRuntimes = make(map[string]*MissingRuntime) + } + + runtime, ok := missingRuntimes[name] + if ok { + return runtime, nil + } + + // Once for each missing runtime, we want to error. + logrus.Errorf("OCI Runtime %s is in use by a container, but is not available (not in configuration file or not installed)", name) + + newRuntime := new(MissingRuntime) + newRuntime.name = name + newRuntime.exitsDir = filepath.Join(r.config.TmpDir, "exits") + + missingRuntimes[name] = newRuntime + + return newRuntime, nil +} + +// Name is the name of the missing runtime +func (r *MissingRuntime) Name() string { + return fmt.Sprintf("%s (missing/not available)", r.name) +} + +// Path is not available as the runtime is missing +func (r *MissingRuntime) Path() string { + return "(missing/not available)" +} + +// CreateContainer is not available as the runtime is missing +func (r *MissingRuntime) CreateContainer(ctr *Container, restoreOptions *ContainerCheckpointOptions) error { + return r.printError() +} + +// UpdateContainerStatus is not available as the runtime is missing +func (r *MissingRuntime) UpdateContainerStatus(ctr *Container) error { + return r.printError() +} + +// StartContainer is not available as the runtime is missing +func (r *MissingRuntime) StartContainer(ctr *Container) error { + return r.printError() +} + +// KillContainer is not available as the runtime is missing +// TODO: We could attempt to unix.Kill() the PID as recorded in the state if we +// really want to smooth things out? Won't be perfect, but if the container has +// a PID namespace it could be enough? +func (r *MissingRuntime) KillContainer(ctr *Container, signal uint, all bool) error { + return r.printError() +} + +// StopContainer is not available as the runtime is missing +func (r *MissingRuntime) StopContainer(ctr *Container, timeout uint, all bool) error { + return r.printError() +} + +// DeleteContainer is not available as the runtime is missing +func (r *MissingRuntime) DeleteContainer(ctr *Container) error { + return r.printError() +} + +// PauseContainer is not available as the runtime is missing +func (r *MissingRuntime) PauseContainer(ctr *Container) error { + return r.printError() +} + +// UnpauseContainer is not available as the runtime is missing +func (r *MissingRuntime) UnpauseContainer(ctr *Container) error { + return r.printError() +} + +// ExecContainer is not available as the runtime is missing +func (r *MissingRuntime) ExecContainer(ctr *Container, sessionID string, options *ExecOptions) (int, chan error, error) { + return -1, nil, r.printError() +} + +// ExecStopContainer is not available as the runtime is missing. +// TODO: We can also investigate using unix.Kill() on the PID of the exec +// session here if we want to make stopping containers possible. Won't be +// perfect, though. +func (r *MissingRuntime) ExecStopContainer(ctr *Container, sessionID string, timeout uint) error { + return r.printError() +} + +// ExecContainerCleanup is not available as the runtime is missing +func (r *MissingRuntime) ExecContainerCleanup(ctr *Container, sessionID string) error { + return r.printError() +} + +// CheckpointContainer is not available as the runtime is missing +func (r *MissingRuntime) CheckpointContainer(ctr *Container, options ContainerCheckpointOptions) error { + return r.printError() +} + +// SupportsCheckpoint returns false as checkpointing requires a working runtime +func (r *MissingRuntime) SupportsCheckpoint() bool { + return false +} + +// SupportsJSONErrors returns false as there is no runtime to give errors +func (r *MissingRuntime) SupportsJSONErrors() bool { + return false +} + +// SupportsNoCgroups returns false as there is no runtime to create containers +func (r *MissingRuntime) SupportsNoCgroups() bool { + return false +} + +// AttachSocketPath does not work as there is no runtime to attach to. +// (Theoretically we could follow ExitFilePath but there is no guarantee the +// container is running and thus has an attach socket...) +func (r *MissingRuntime) AttachSocketPath(ctr *Container) (string, error) { + return "", r.printError() +} + +// ExecAttachSocketPath does not work as there is no runtime to attach to. +// (Again, we could follow ExitFilePath, but no guarantee there is an existing +// and running exec session) +func (r *MissingRuntime) ExecAttachSocketPath(ctr *Container, sessionID string) (string, error) { + return "", r.printError() +} + +// ExitFilePath returns the exit file path for containers. +// Here, we mimic what ConmonOCIRuntime does, because there is a chance that the +// container in question is still running happily (config file modified to +// remove a runtime, for example). We can't find the runtime to do anything to +// the container, but Conmon should still place an exit file for it. +func (r *MissingRuntime) ExitFilePath(ctr *Container) (string, error) { + if ctr == nil { + return "", errors.Wrapf(define.ErrInvalidArg, "must provide a valid container to get exit file path") + } + return filepath.Join(r.exitsDir, ctr.ID()), nil +} + +// RuntimeInfo returns information on the missing runtime +func (r *MissingRuntime) RuntimeInfo() (map[string]interface{}, error) { + info := make(map[string]interface{}) + info["OCIRuntime"] = map[string]interface{}{ + "name": r.name, + "path": "missing", + "package": "missing", + "version": "missing", + } + return info, nil +} + +// Return an error indicating the runtime is missing +func (r *MissingRuntime) printError() error { + return errors.Wrapf(define.ErrOCIRuntimeNotFound, "runtime %s is missing", r.name) +} diff --git a/libpod/oci_unsupported.go b/libpod/oci_unsupported.go deleted file mode 100644 index 4a65d4d1d..000000000 --- a/libpod/oci_unsupported.go +++ /dev/null @@ -1,47 +0,0 @@ -// +build !linux - -package libpod - -import ( - "os" - "os/exec" - - "github.com/containers/libpod/libpod/define" - "k8s.io/client-go/tools/remotecommand" -) - -func (r *OCIRuntime) moveConmonToCgroup(ctr *Container, cgroupParent string, cmd *exec.Cmd) error { - return define.ErrOSNotSupported -} - -func newPipe() (parent *os.File, child *os.File, err error) { - return nil, nil, define.ErrNotImplemented -} - -func (r *OCIRuntime) createContainer(ctr *Container, restoreOptions *ContainerCheckpointOptions) (err error) { - return define.ErrNotImplemented -} - -func (r *OCIRuntime) pathPackage() string { - return "" -} - -func (r *OCIRuntime) conmonPackage() string { - return "" -} - -func (r *OCIRuntime) createOCIContainer(ctr *Container, cgroupParent string, restoreOptions *ContainerCheckpointOptions) (err error) { - return define.ErrOSNotSupported -} - -func (r *OCIRuntime) execStopContainer(ctr *Container, timeout uint) error { - return define.ErrOSNotSupported -} - -func (r *OCIRuntime) stopContainer(ctr *Container, timeout uint) error { - return define.ErrOSNotSupported -} - -func (r *OCIRuntime) execContainer(c *Container, cmd, capAdd, env []string, tty bool, cwd, user, sessionID string, streams *AttachStreams, preserveFDs int, resize chan remotecommand.TerminalSize, detachKeys string) (int, chan error, error) { - return -1, nil, define.ErrOSNotSupported -} diff --git a/libpod/oci_util.go b/libpod/oci_util.go new file mode 100644 index 000000000..cb85b153d --- /dev/null +++ b/libpod/oci_util.go @@ -0,0 +1,113 @@ +package libpod + +import ( + "fmt" + "net" + "os" + "regexp" + "strings" + "time" + + "github.com/containers/libpod/libpod/define" + "github.com/cri-o/ocicni/pkg/ocicni" + "github.com/pkg/errors" + "github.com/sirupsen/logrus" +) + +const ( + // CgroupfsCgroupsManager represents cgroupfs native cgroup manager + CgroupfsCgroupsManager = "cgroupfs" + // SystemdCgroupsManager represents systemd native cgroup manager + SystemdCgroupsManager = "systemd" + + // ContainerCreateTimeout is the timeout before we decide we've failed + // to create a container. + // TODO: Make this generic - all OCI runtime operations should use the + // same timeout, this one. + // TODO: Consider dropping from 240 to 60 seconds. I don't think waiting + // 4 minutes versus 1 minute makes a real difference. + ContainerCreateTimeout = 240 * time.Second + + // Timeout before declaring that runtime has failed to kill a given + // container + killContainerTimeout = 5 * time.Second + // DefaultShmSize is the default shm size + DefaultShmSize = 64 * 1024 * 1024 + // NsRunDir is the default directory in which running network namespaces + // are stored + NsRunDir = "/var/run/netns" +) + +// ociError is used to parse the OCI runtime JSON log. It is not part of the +// OCI runtime specifications, it follows what runc does +type ociError struct { + Level string `json:"level,omitempty"` + Time string `json:"time,omitempty"` + Msg string `json:"msg,omitempty"` +} + +// Create systemd unit name for cgroup scopes +func createUnitName(prefix string, name string) string { + return fmt.Sprintf("%s-%s.scope", prefix, name) +} + +// Bind ports to keep them closed on the host +func bindPorts(ports []ocicni.PortMapping) ([]*os.File, error) { + var files []*os.File + notifySCTP := false + for _, i := range ports { + switch i.Protocol { + case "udp": + addr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", i.HostIP, i.HostPort)) + if err != nil { + return nil, errors.Wrapf(err, "cannot resolve the UDP address") + } + + server, err := net.ListenUDP("udp", addr) + if err != nil { + return nil, errors.Wrapf(err, "cannot listen on the UDP port") + } + f, err := server.File() + if err != nil { + return nil, errors.Wrapf(err, "cannot get file for UDP socket") + } + files = append(files, f) + + case "tcp": + addr, err := net.ResolveTCPAddr("tcp4", fmt.Sprintf("%s:%d", i.HostIP, i.HostPort)) + if err != nil { + return nil, errors.Wrapf(err, "cannot resolve the TCP address") + } + + server, err := net.ListenTCP("tcp4", addr) + if err != nil { + return nil, errors.Wrapf(err, "cannot listen on the TCP port") + } + f, err := server.File() + if err != nil { + return nil, errors.Wrapf(err, "cannot get file for TCP socket") + } + files = append(files, f) + case "sctp": + if !notifySCTP { + notifySCTP = true + logrus.Warnf("port reservation for SCTP is not supported") + } + default: + return nil, fmt.Errorf("unknown protocol %s", i.Protocol) + + } + } + return files, nil +} + +func getOCIRuntimeError(runtimeMsg string) error { + r := strings.ToLower(runtimeMsg) + if match, _ := regexp.MatchString(".*permission denied.*|.*operation not permitted.*", r); match { + return errors.Wrapf(define.ErrOCIRuntimePermissionDenied, "%s", strings.Trim(runtimeMsg, "\n")) + } + if match, _ := regexp.MatchString(".*executable file not found in.*|.*no such file or directory.*", r); match { + return errors.Wrapf(define.ErrOCIRuntimeNotFound, "%s", strings.Trim(runtimeMsg, "\n")) + } + return errors.Wrapf(define.ErrOCIRuntime, "%s", strings.Trim(runtimeMsg, "\n")) +} diff --git a/libpod/options.go b/libpod/options.go index ee44439ac..f779b0413 100644 --- a/libpod/options.go +++ b/libpod/options.go @@ -463,6 +463,28 @@ func WithMigrate() RuntimeOption { } } +// WithMigrateRuntime instructs Libpod to change the default OCI runtime on all +// containers during a migration. This is not used if `MigrateRuntime()` is not +// also passed. +// Libpod makes no promises that your containers continue to work with the new +// runtime - migrations between dissimilar runtimes may well break things. +// Use with caution. +func WithMigrateRuntime(requestedRuntime string) RuntimeOption { + return func(rt *Runtime) error { + if rt.valid { + return define.ErrRuntimeFinalized + } + + if requestedRuntime == "" { + return errors.Wrapf(define.ErrInvalidArg, "must provide a non-empty name for new runtime") + } + + rt.migrateRuntime = requestedRuntime + + return nil + } +} + // WithEventsLogger sets the events backend to use. // Currently supported values are "file" for file backend and "journald" for // journald backend. @@ -992,6 +1014,13 @@ func WithNetNS(portMappings []ocicni.PortMapping, postConfigureNetNS bool, netmo ctr.config.NetMode = namespaces.NetworkMode(netmode) ctr.config.CreateNetNS = true ctr.config.PortMappings = portMappings + + if rootless.IsRootless() { + if len(networks) > 0 { + return errors.New("cannot use CNI networks with rootless containers") + } + } + ctr.config.Networks = networks return nil @@ -1465,6 +1494,8 @@ func WithVolumeLabels(labels map[string]string) VolumeCreateOption { } // WithVolumeOptions sets the options of the volume. +// If the "local" driver has been selected, options will be validated. There are +// currently 3 valid options for the "local" driver - o, type, and device. func WithVolumeOptions(options map[string]string) VolumeCreateOption { return func(volume *Volume) error { if volume.valid { @@ -1473,6 +1504,13 @@ func WithVolumeOptions(options map[string]string) VolumeCreateOption { volume.config.Options = make(map[string]string) for key, value := range options { + switch key { + case "type", "device", "o": + volume.config.Options[key] = value + default: + return errors.Wrapf(define.ErrInvalidArg, "unrecognized volume option %q is not supported with local driver", key) + } + volume.config.Options[key] = value } diff --git a/libpod/pod_api.go b/libpod/pod_api.go index 7c786b835..3a194f04b 100644 --- a/libpod/pod_api.go +++ b/libpod/pod_api.go @@ -123,7 +123,7 @@ func (p *Pod) StopWithTimeout(ctx context.Context, cleanup bool, timeout int) (m if timeout > -1 { stopTimeout = uint(timeout) } - if err := ctr.stop(stopTimeout); err != nil { + if err := ctr.stop(stopTimeout, false); err != nil { ctr.lock.Unlock() ctrErrors[ctr.ID()] = err continue @@ -370,7 +370,7 @@ func (p *Pod) Kill(signal uint) (map[string]error, error) { continue } - if err := ctr.ociRuntime.killContainer(ctr, signal); err != nil { + if err := ctr.ociRuntime.KillContainer(ctr, signal, false); err != nil { ctr.lock.Unlock() ctrErrors[ctr.ID()] = err continue diff --git a/libpod/runtime.go b/libpod/runtime.go index cdb5670ba..a06b2bb51 100644 --- a/libpod/runtime.go +++ b/libpod/runtime.go @@ -14,7 +14,6 @@ import ( "strings" "sync" "syscall" - "time" "github.com/BurntSushi/toml" is "github.com/containers/image/v4/storage" @@ -99,8 +98,8 @@ type Runtime struct { store storage.Store storageService *storageService imageContext *types.SystemContext - defaultOCIRuntime *OCIRuntime - ociRuntimes map[string]*OCIRuntime + defaultOCIRuntime OCIRuntime + ociRuntimes map[string]OCIRuntime netPlugin ocicni.CNIPlugin conmonPath string imageRuntime *image.Runtime @@ -114,6 +113,10 @@ type Runtime struct { doRenumber bool doMigrate bool + // System migrate can move containers to a new runtime. + // We make no promises that these migrated containers work on the new + // runtime, though. + migrateRuntime string // valid indicates whether the runtime is ready to use. // valid is set to true when a runtime is returned from GetRuntime(), @@ -349,10 +352,6 @@ func defaultRuntimeConfig() (RuntimeConfig, error) { // SetXdgDirs ensures the XDG_RUNTIME_DIR env and XDG_CONFIG_HOME variables are set. // containers/image uses XDG_RUNTIME_DIR to locate the auth file, XDG_CONFIG_HOME is // use for the libpod.conf configuration file. -// SetXdgDirs internally calls EnableLinger() so that the user's processes are not -// killed once the session is terminated. EnableLinger() also attempts to -// get the runtime directory when XDG_RUNTIME_DIR is not specified. -// This function should only be called when running rootless. func SetXdgDirs() error { if !rootless.IsRootless() { return nil @@ -361,21 +360,6 @@ func SetXdgDirs() error { // Setup XDG_RUNTIME_DIR runtimeDir := os.Getenv("XDG_RUNTIME_DIR") - runtimeDirLinger, err := rootless.EnableLinger() - if err != nil { - return errors.Wrapf(err, "error enabling user session") - } - if runtimeDir == "" && runtimeDirLinger != "" { - if _, err := os.Stat(runtimeDirLinger); err != nil && os.IsNotExist(err) { - chWait := make(chan error) - defer close(chWait) - if _, err := WaitForFile(runtimeDirLinger, chWait, time.Second*10); err != nil { - return errors.Wrapf(err, "waiting for directory '%s'", runtimeDirLinger) - } - } - runtimeDir = runtimeDirLinger - } - if runtimeDir == "" { var err error runtimeDir, err = util.GetRuntimeDir() @@ -396,10 +380,11 @@ func SetXdgDirs() error { // Setup XDG_CONFIG_HOME if cfgHomeDir := os.Getenv("XDG_CONFIG_HOME"); cfgHomeDir == "" { - if cfgHomeDir, err = util.GetRootlessConfigHomeDir(); err != nil { + cfgHomeDir, err := util.GetRootlessConfigHomeDir() + if err != nil { return err } - if err = os.Setenv("XDG_CONFIG_HOME", cfgHomeDir); err != nil { + if err := os.Setenv("XDG_CONFIG_HOME", cfgHomeDir); err != nil { return errors.Wrapf(err, "cannot set XDG_CONFIG_HOME") } } @@ -524,6 +509,17 @@ func newRuntimeFromConfig(ctx context.Context, userConfigPath string, options .. return nil, err } + // storage.conf + storageConfFile, err := storage.DefaultConfigFile(rootless.IsRootless()) + if err != nil { + return nil, err + } + + createStorageConfFile := false + if _, err := os.Stat(storageConfFile); os.IsNotExist(err) { + createStorageConfFile = true + } + defRunConf, err := defaultRuntimeConfig() if err != nil { return nil, err @@ -698,27 +694,21 @@ func newRuntimeFromConfig(ctx context.Context, userConfigPath string, options .. } if rootless.IsRootless() && configPath == "" { - configPath, err := getRootlessConfigPath() - if err != nil { - return nil, err - } - - // storage.conf - storageConfFile, err := storage.DefaultConfigFile(rootless.IsRootless()) - if err != nil { - return nil, err - } - if _, err := os.Stat(storageConfFile); os.IsNotExist(err) { + if createStorageConfFile { if err := util.WriteStorageConfigFile(&runtime.config.StorageConfig, storageConfFile); err != nil { return nil, errors.Wrapf(err, "cannot write config file %s", storageConfFile) } } + configPath, err := getRootlessConfigPath() + if err != nil { + return nil, err + } if configPath != "" { - if err := os.MkdirAll(filepath.Dir(configPath), 0755); err != nil { + if err := os.MkdirAll(filepath.Dir(configPath), 0711); err != nil { return nil, err } - file, err := os.OpenFile(configPath, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0666) + file, err := os.OpenFile(configPath, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0600) if err != nil && !os.IsExist(err) { return nil, errors.Wrapf(err, "cannot open file %s", configPath) } @@ -1053,7 +1043,7 @@ func makeRuntime(ctx context.Context, runtime *Runtime) (err error) { } // Get us at least one working OCI runtime. - runtime.ociRuntimes = make(map[string]*OCIRuntime) + runtime.ociRuntimes = make(map[string]OCIRuntime) // Is the old runtime_path defined? if runtime.config.RuntimePath != nil { @@ -1072,7 +1062,7 @@ func makeRuntime(ctx context.Context, runtime *Runtime) (err error) { json := supportsJSON[name] nocgroups := supportsNoCgroups[name] - ociRuntime, err := newOCIRuntime(name, runtime.config.RuntimePath, runtime.conmonPath, runtime.config, json, nocgroups) + ociRuntime, err := newConmonOCIRuntime(name, runtime.config.RuntimePath, runtime.conmonPath, runtime.config, json, nocgroups) if err != nil { return err } @@ -1086,7 +1076,7 @@ func makeRuntime(ctx context.Context, runtime *Runtime) (err error) { json := supportsJSON[name] nocgroups := supportsNoCgroups[name] - ociRuntime, err := newOCIRuntime(name, paths, runtime.conmonPath, runtime.config, json, nocgroups) + ociRuntime, err := newConmonOCIRuntime(name, paths, runtime.conmonPath, runtime.config, json, nocgroups) if err != nil { // Don't fatally error. // This will allow us to ship configs including optional @@ -1109,7 +1099,7 @@ func makeRuntime(ctx context.Context, runtime *Runtime) (err error) { json := supportsJSON[name] nocgroups := supportsNoCgroups[name] - ociRuntime, err := newOCIRuntime(name, []string{runtime.config.OCIRuntime}, runtime.conmonPath, runtime.config, json, nocgroups) + ociRuntime, err := newConmonOCIRuntime(name, []string{runtime.config.OCIRuntime}, runtime.conmonPath, runtime.config, json, nocgroups) if err != nil { return err } @@ -1474,11 +1464,35 @@ func (r *Runtime) SystemContext() *types.SystemContext { return r.imageContext } +// GetOCIRuntimePath retrieves the path of the default OCI runtime. +func (r *Runtime) GetOCIRuntimePath() string { + return r.defaultOCIRuntime.Path() +} + // Since runc does not currently support cgroupV2 // Change to default crun on first running of libpod.conf // TODO Once runc has support for cgroups, this function should be removed. func cgroupV2Check(configPath string, tmpConfig *RuntimeConfig) error { if !tmpConfig.CgroupCheck && rootless.IsRootless() { + if tmpConfig.CgroupManager == SystemdCgroupsManager { + // If we are running rootless and the systemd manager is requested, be sure that dbus is accessible + session := os.Getenv("DBUS_SESSION_BUS_ADDRESS") + hasSession := session != "" + if hasSession && strings.HasPrefix(session, "unix:path=") { + _, err := os.Stat(strings.TrimPrefix(session, "unix:path=")) + hasSession = err == nil + } + + if !hasSession { + logrus.Warningf("The cgroups manager is set to systemd but there is no systemd user session available") + logrus.Warningf("For using systemd, you may need to login using an user session") + logrus.Warningf("Alternatively, you can enable lingering with: `loginctl enable-linger %d` (possibily as root)", rootless.GetRootlessUID()) + logrus.Warningf("Falling back to --cgroup-manager=cgroupfs") + + tmpConfig.CgroupManager = CgroupfsCgroupsManager + } + + } cgroupsV2, err := cgroups.IsCgroup2UnifiedMode() if err != nil { return err @@ -1492,7 +1506,7 @@ func cgroupV2Check(configPath string, tmpConfig *RuntimeConfig) error { } tmpConfig.CgroupCheck = true tmpConfig.OCIRuntime = path - file, err := os.OpenFile(configPath, os.O_RDWR|os.O_CREATE, 0666) + file, err := os.OpenFile(configPath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666) if err != nil { return errors.Wrapf(err, "cannot open file %s", configPath) } diff --git a/libpod/runtime_cstorage.go b/libpod/runtime_cstorage.go index 47a91c881..2d523a7d2 100644 --- a/libpod/runtime_cstorage.go +++ b/libpod/runtime_cstorage.go @@ -68,7 +68,7 @@ func (r *Runtime) RemoveStorageContainer(idOrName string, force bool) error { func (r *Runtime) removeStorageContainer(idOrName string, force bool) error { targetID, err := r.store.Lookup(idOrName) if err != nil { - if err == storage.ErrLayerUnknown { + if errors.Cause(err) == storage.ErrLayerUnknown { return errors.Wrapf(define.ErrNoSuchCtr, "no container with ID or name %q found", idOrName) } return errors.Wrapf(err, "error looking up container %q", idOrName) @@ -78,7 +78,7 @@ func (r *Runtime) removeStorageContainer(idOrName string, force bool) error { // So we can still error here. ctr, err := r.store.Container(targetID) if err != nil { - if err == storage.ErrContainerUnknown { + if errors.Cause(err) == storage.ErrContainerUnknown { return errors.Wrapf(define.ErrNoSuchCtr, "%q does not refer to a container", idOrName) } return errors.Wrapf(err, "error retrieving container %q", idOrName) @@ -96,7 +96,7 @@ func (r *Runtime) removeStorageContainer(idOrName string, force bool) error { if !force { timesMounted, err := r.store.Mounted(ctr.ID) if err != nil { - if err == storage.ErrContainerUnknown { + if errors.Cause(err) == storage.ErrContainerUnknown { // Container was removed from under us. // It's gone, so don't bother erroring. logrus.Warnf("Storage for container %s already removed", ctr.ID) @@ -109,7 +109,7 @@ func (r *Runtime) removeStorageContainer(idOrName string, force bool) error { } } else { if _, err := r.store.Unmount(ctr.ID, true); err != nil { - if err == storage.ErrContainerUnknown { + if errors.Cause(err) == storage.ErrContainerUnknown { // Container again gone, no error logrus.Warnf("Storage for container %s already removed", ctr.ID) return nil @@ -119,7 +119,7 @@ func (r *Runtime) removeStorageContainer(idOrName string, force bool) error { } if err := r.store.DeleteContainer(ctr.ID); err != nil { - if err == storage.ErrContainerUnknown { + if errors.Cause(err) == storage.ErrContainerUnknown { // Container again gone, no error logrus.Warnf("Storage for container %s already removed", ctr.ID) return nil diff --git a/libpod/runtime_ctr.go b/libpod/runtime_ctr.go index 78176a400..2b214d572 100644 --- a/libpod/runtime_ctr.go +++ b/libpod/runtime_ctr.go @@ -102,7 +102,7 @@ func (r *Runtime) initContainerVariables(rSpec *spec.Spec, config *ContainerConf ctr.config.StopTimeout = define.CtrRemoveTimeout - ctr.config.OCIRuntime = r.defaultOCIRuntime.name + ctr.config.OCIRuntime = r.defaultOCIRuntime.Name() // Set namespace based on current runtime namespace // Do so before options run so they can override it @@ -167,8 +167,8 @@ func (r *Runtime) setupContainer(ctx context.Context, ctr *Container) (c *Contai // Check NoCgroups support if ctr.config.NoCgroups { - if !ctr.ociRuntime.supportsNoCgroups { - return nil, errors.Wrapf(define.ErrInvalidArg, "requested OCI runtime %s is not compatible with NoCgroups", ctr.ociRuntime.name) + if !ctr.ociRuntime.SupportsNoCgroups() { + return nil, errors.Wrapf(define.ErrInvalidArg, "requested OCI runtime %s is not compatible with NoCgroups", ctr.ociRuntime.Name()) } } @@ -264,6 +264,14 @@ func (r *Runtime) setupContainer(ctx context.Context, ctr *Container) (c *Contai g.RemoveMount("/etc/hosts") g.RemoveMount("/run/.containerenv") g.RemoveMount("/run/secrets") + + // Regenerate CGroup paths so they don't point to the old + // container ID. + cgroupPath, err := ctr.getOCICgroupPath() + if err != nil { + return nil, err + } + g.SetLinuxCgroupsPath(cgroupPath) } // Set up storage for the container @@ -287,21 +295,32 @@ func (r *Runtime) setupContainer(ctx context.Context, ctr *Container) (c *Contai // Maintain an array of them - we need to lock them later. ctrNamedVolumes := make([]*Volume, 0, len(ctr.config.NamedVolumes)) for _, vol := range ctr.config.NamedVolumes { - // Check if it exists already - dbVol, err := r.state.Volume(vol.Name) - if err == nil { - ctrNamedVolumes = append(ctrNamedVolumes, dbVol) - // The volume exists, we're good - continue - } else if errors.Cause(err) != define.ErrNoSuchVolume { - return nil, errors.Wrapf(err, "error retrieving named volume %s for new container", vol.Name) + isAnonymous := false + if vol.Name == "" { + // Anonymous volume. We'll need to create it. + // It needs a name first. + vol.Name = stringid.GenerateNonCryptoID() + isAnonymous = true + } else { + // Check if it exists already + dbVol, err := r.state.Volume(vol.Name) + if err == nil { + ctrNamedVolumes = append(ctrNamedVolumes, dbVol) + // The volume exists, we're good + continue + } else if errors.Cause(err) != define.ErrNoSuchVolume { + return nil, errors.Wrapf(err, "error retrieving named volume %s for new container", vol.Name) + } } logrus.Debugf("Creating new volume %s for container", vol.Name) // The volume does not exist, so we need to create it. - newVol, err := r.newVolume(ctx, WithVolumeName(vol.Name), withSetCtrSpecific(), - WithVolumeUID(ctr.RootUID()), WithVolumeGID(ctr.RootGID())) + volOptions := []VolumeCreateOption{WithVolumeName(vol.Name), WithVolumeUID(ctr.RootUID()), WithVolumeGID(ctr.RootGID())} + if isAnonymous { + volOptions = append(volOptions, withSetCtrSpecific()) + } + newVol, err := r.newVolume(ctx, volOptions...) if err != nil { return nil, errors.Wrapf(err, "error creating named volume %q", vol.Name) } @@ -430,7 +449,7 @@ func (r *Runtime) removeContainer(ctx context.Context, c *Container, force bool, } if c.state.State == define.ContainerStatePaused { - if err := c.ociRuntime.killContainer(c, 9); err != nil { + if err := c.ociRuntime.KillContainer(c, 9, false); err != nil { return err } if err := c.unpause(); err != nil { @@ -444,15 +463,15 @@ func (r *Runtime) removeContainer(ctx context.Context, c *Container, force bool, // Check that the container's in a good state to be removed if c.state.State == define.ContainerStateRunning { - if err := c.stop(c.StopTimeout()); err != nil { + if err := c.stop(c.StopTimeout(), true); err != nil { return errors.Wrapf(err, "cannot remove container %s as it could not be stopped", c.ID()) } } // Check that all of our exec sessions have finished - if len(c.state.ExecSessions) != 0 { - if err := c.ociRuntime.execStopContainer(c, c.StopTimeout()); err != nil { - return err + for _, session := range c.state.ExecSessions { + if err := c.ociRuntime.ExecStopContainer(c, session.ID, c.StopTimeout()); err != nil { + return errors.Wrapf(err, "error stopping exec session %s of container %s", session.ID, c.ID()) } } diff --git a/libpod/runtime_img.go b/libpod/runtime_img.go index 8cc501629..35c0cdfb9 100644 --- a/libpod/runtime_img.go +++ b/libpod/runtime_img.go @@ -69,7 +69,7 @@ func (r *Runtime) RemoveImage(ctx context.Context, img *image.Image, force bool) // the image. we figure out which repotag the user is trying to refer // to and untag it. repoName, err := img.MatchRepoTag(img.InputName) - if hasChildren && err == image.ErrRepoTagNotFound { + if hasChildren && errors.Cause(err) == image.ErrRepoTagNotFound { return "", errors.Errorf("unable to delete %q (cannot be forced) - image has dependent child images", img.ID()) } if err != nil { diff --git a/libpod/runtime_migrate.go b/libpod/runtime_migrate.go index c363991e6..d85652232 100644 --- a/libpod/runtime_migrate.go +++ b/libpod/runtime_migrate.go @@ -5,14 +5,15 @@ package libpod import ( "context" "fmt" - "github.com/containers/libpod/pkg/util" "io/ioutil" "os" "path/filepath" "strconv" "syscall" + "github.com/containers/libpod/libpod/define" "github.com/containers/libpod/pkg/rootless" + "github.com/containers/libpod/pkg/util" "github.com/pkg/errors" "github.com/sirupsen/logrus" ) @@ -63,11 +64,34 @@ func (r *Runtime) migrate(ctx context.Context) error { } } + // Did the user request a new runtime? + runtimeChangeRequested := r.migrateRuntime != "" + requestedRuntime, runtimeExists := r.ociRuntimes[r.migrateRuntime] + if !runtimeExists && runtimeChangeRequested { + return errors.Wrapf(define.ErrInvalidArg, "change to runtime %q requested but no such runtime is defined", r.migrateRuntime) + } + for _, ctr := range allCtrs { + needsWrite := false + + // Reset pause process location oldLocation := filepath.Join(ctr.state.RunDir, "conmon.pid") if ctr.config.ConmonPidFile == oldLocation { logrus.Infof("changing conmon PID file for %s", ctr.ID()) ctr.config.ConmonPidFile = filepath.Join(ctr.config.StaticDir, "conmon.pid") + needsWrite = true + } + + // Reset runtime + if runtimeChangeRequested { + logrus.Infof("Resetting container %s runtime to runtime %s", ctr.ID(), r.migrateRuntime) + ctr.config.OCIRuntime = r.migrateRuntime + ctr.ociRuntime = requestedRuntime + + needsWrite = true + } + + if needsWrite { if err := r.state.RewriteContainerConfig(ctr, ctr.config); err != nil { return errors.Wrapf(err, "error rewriting config for container %s", ctr.ID()) } diff --git a/libpod/runtime_volume_linux.go b/libpod/runtime_volume_linux.go index 9df93faf3..5b05acea4 100644 --- a/libpod/runtime_volume_linux.go +++ b/libpod/runtime_volume_linux.go @@ -48,6 +48,15 @@ func (r *Runtime) newVolume(ctx context.Context, options ...VolumeCreateOption) } volume.config.CreatedTime = time.Now() + // Check if volume with given name exists. + exists, err := r.state.HasVolume(volume.config.Name) + if err != nil { + return nil, errors.Wrapf(err, "error checking if volume with name %s exists", volume.config.Name) + } + if exists { + return nil, errors.Wrapf(define.ErrVolumeExists, "volume with name %s already exists", volume.config.Name) + } + if volume.config.Driver == define.VolumeDriverLocal { logrus.Debugf("Validating options for local driver") // Validate options @@ -157,7 +166,14 @@ func (r *Runtime) removeVolume(ctx context.Context, v *Volume, force bool) error // If the volume is still mounted - force unmount it if err := v.unmount(true); err != nil { - return errors.Wrapf(err, "error unmounting volume %s", v.Name()) + if force { + // If force is set, evict the volume, even if errors + // occur. Otherwise we'll never be able to get rid of + // them. + logrus.Errorf("Error unmounting volume %s: %v", v.Name(), err) + } else { + return errors.Wrapf(err, "error unmounting volume %s", v.Name()) + } } // Set volume as invalid so it can no longer be used diff --git a/libpod/volume_inspect.go b/libpod/volume_inspect.go index 87ed9d340..c333b8961 100644 --- a/libpod/volume_inspect.go +++ b/libpod/volume_inspect.go @@ -62,6 +62,9 @@ func (v *Volume) Inspect() (*InspectVolumeData, error) { } data.Scope = v.Scope() data.Options = make(map[string]string) + for k, v := range v.config.Options { + data.Options[k] = v + } data.UID = v.config.UID data.GID = v.config.GID data.ContainerSpecific = v.config.IsCtrSpecific diff --git a/libpod/volume_internal_linux.go b/libpod/volume_internal_linux.go index 9ae4dcf69..4c0332018 100644 --- a/libpod/volume_internal_linux.go +++ b/libpod/volume_internal_linux.go @@ -6,6 +6,8 @@ import ( "io/ioutil" "os/exec" + "github.com/containers/libpod/libpod/define" + "github.com/containers/libpod/pkg/rootless" "github.com/pkg/errors" "github.com/sirupsen/logrus" "golang.org/x/sys/unix" @@ -24,6 +26,11 @@ func (v *Volume) mount() error { return nil } + // We cannot mount volumes as rootless. + if rootless.IsRootless() { + return errors.Wrapf(define.ErrRootless, "cannot mount volumes without root privileges") + } + // Update the volume from the DB to get an accurate mount counter. if err := v.update(); err != nil { return err @@ -108,6 +115,20 @@ func (v *Volume) unmount(force bool) error { return nil } + // We cannot unmount volumes as rootless. + if rootless.IsRootless() { + // If force is set, just clear the counter and bail without + // error, so we can remove volumes from the state if they are in + // an awkward configuration. + if force { + logrus.Errorf("Volume %s is mounted despite being rootless - state is not sane", v.Name()) + v.state.MountCount = 0 + return v.save() + } + + return errors.Wrapf(define.ErrRootless, "cannot mount or unmount volumes without root privileges") + } + if !force { v.state.MountCount = v.state.MountCount - 1 } else { @@ -119,6 +140,10 @@ func (v *Volume) unmount(force bool) error { if v.state.MountCount == 0 { // Unmount the volume if err := unix.Unmount(v.config.MountPoint, unix.MNT_DETACH); err != nil { + if err == unix.EINVAL { + // Ignore EINVAL - the mount no longer exists. + return nil + } return errors.Wrapf(err, "error unmounting volume %s", v.Name()) } logrus.Debugf("Unmounted volume %s", v.Name()) diff --git a/pkg/adapter/containers.go b/pkg/adapter/containers.go index 51efdccc7..ae91bd812 100644 --- a/pkg/adapter/containers.go +++ b/pkg/adapter/containers.go @@ -269,7 +269,7 @@ func (r *LocalRuntime) UmountRootFilesystems(ctx context.Context, cli *cliconfig logrus.Debugf("Error umounting container %s, storage.ErrLayerNotMounted", ctr.ID()) continue } - failures[ctr.ID()] = errors.Wrapf(err, "error unmounting continaner %s", ctr.ID()) + failures[ctr.ID()] = errors.Wrapf(err, "error unmounting container %s", ctr.ID()) } else { ok = append(ok, ctr.ID()) } @@ -437,8 +437,12 @@ func (r *LocalRuntime) Run(ctx context.Context, c *cliconfig.RunValues, exitCode } if c.IsSet("rm") { - if err := r.Runtime.RemoveContainer(ctx, ctr, false, false); err != nil { - logrus.Errorf("Error removing container %s: %v", ctr.ID(), err) + if err := r.Runtime.RemoveContainer(ctx, ctr, false, true); err != nil { + if errors.Cause(err) == define.ErrNoSuchCtr { + logrus.Warnf("Container %s does not exist: %v", ctr.ID(), err) + } else { + logrus.Errorf("Error removing container %s: %v", ctr.ID(), err) + } } } @@ -612,7 +616,9 @@ func (r *LocalRuntime) Start(ctx context.Context, c *cliconfig.StartValues, sigP if c.Attach { inputStream := os.Stdin if !c.Interactive { - inputStream = nil + if !ctr.Stdin() { + inputStream = nil + } } // attach to the container and also start it not already running @@ -663,7 +669,7 @@ func (r *LocalRuntime) Start(ctx context.Context, c *cliconfig.StartValues, sigP lastError = errors.Wrapf(err, "unable to start container %q", container) continue } - fmt.Println(container) + fmt.Println(ctr.ID()) } return exitCode, lastError } @@ -899,7 +905,7 @@ func (r *LocalRuntime) execPS(c *libpod.Container, args []string) ([]string, err }() cmd := append([]string{"ps"}, args...) - ec, err := c.Exec(false, false, []string{}, cmd, "", "", streams, 0, nil, "") + ec, err := c.Exec(false, false, map[string]string{}, cmd, "", "", streams, 0, nil, "") if err != nil { return nil, err } else if ec != 0 { @@ -959,12 +965,6 @@ func (r *LocalRuntime) ExecContainer(ctx context.Context, cli *cliconfig.ExecVal return ec, errors.Wrapf(err, "unable to process environment variables") } - // Build env slice of key=value strings for Exec - envs := []string{} - for k, v := range env { - envs = append(envs, fmt.Sprintf("%s=%s", k, v)) - } - streams := new(libpod.AttachStreams) streams.OutputStream = os.Stdout streams.ErrorStream = os.Stderr @@ -975,7 +975,7 @@ func (r *LocalRuntime) ExecContainer(ctx context.Context, cli *cliconfig.ExecVal streams.AttachOutput = true streams.AttachError = true - ec, err = ExecAttachCtr(ctx, ctr.Container, cli.Tty, cli.Privileged, envs, cmd, cli.User, cli.Workdir, streams, cli.PreserveFDs, cli.DetachKeys) + ec, err = ExecAttachCtr(ctx, ctr.Container, cli.Tty, cli.Privileged, env, cmd, cli.User, cli.Workdir, streams, uint(cli.PreserveFDs), cli.DetachKeys) return define.TranslateExecErrorToExitCode(ec, err), err } @@ -1057,7 +1057,7 @@ func (r *LocalRuntime) CleanupContainers(ctx context.Context, cli *cliconfig.Cle // Only used when cleaning up containers func removeContainer(ctx context.Context, ctr *libpod.Container, runtime *LocalRuntime) error { - if err := runtime.RemoveContainer(ctx, ctr, false, false); err != nil { + if err := runtime.RemoveContainer(ctx, ctr, false, true); err != nil { return errors.Wrapf(err, "failed to cleanup and remove container %v", ctr.ID()) } return nil diff --git a/pkg/adapter/containers_remote.go b/pkg/adapter/containers_remote.go index f7cb28b0c..f4e83a975 100644 --- a/pkg/adapter/containers_remote.go +++ b/pkg/adapter/containers_remote.go @@ -1092,6 +1092,7 @@ func configureVarlinkAttachStdio(reader *bufio.Reader, writer *bufio.Writer, std // These are the special writers that encode input from the client. varlinkStdinWriter := virtwriter.NewVirtWriteCloser(writer, virtwriter.ToStdin) varlinkResizeWriter := virtwriter.NewVirtWriteCloser(writer, virtwriter.TerminalResize) + varlinkHangupWriter := virtwriter.NewVirtWriteCloser(writer, virtwriter.HangUpFromClient) go func() { // Read from the wire and direct to stdout or stderr @@ -1117,7 +1118,6 @@ func configureVarlinkAttachStdio(reader *bufio.Reader, writer *bufio.Writer, std } } }() - if stdin != nil { // Takes stdinput and sends it over the wire after being encoded go func() { @@ -1126,7 +1126,12 @@ func configureVarlinkAttachStdio(reader *bufio.Reader, writer *bufio.Writer, std sendGenericError(ecChan) errChan <- err } - + _, err := varlinkHangupWriter.Write([]byte("EOF")) + if err != nil { + logrus.Errorf("unable to notify server to hangup: %q", err) + } + err = varlinkStdinWriter.Close() + errChan <- err }() } return errChan diff --git a/pkg/adapter/network.go b/pkg/adapter/network.go index d407984ce..9659ae339 100644 --- a/pkg/adapter/network.go +++ b/pkg/adapter/network.go @@ -155,15 +155,14 @@ func (r *LocalRuntime) removeNetwork(ctx context.Context, name string, container // NetworkCreate creates a CNI network func (r *LocalRuntime) NetworkCreate(cli *cliconfig.NetworkCreateValues) (string, error) { - var ( - err error - ) - isGateway := true ipMasq := true subnet := &cli.Network ipRange := cli.IPRange - + runtimeConfig, err := r.GetConfig() + if err != nil { + return "", err + } // if range is provided, make sure it is "in" network if cli.IsSet("subnet") { // if network is provided, does it conflict with existing CNI or live networks @@ -245,6 +244,11 @@ func (r *LocalRuntime) NetworkCreate(cli *cliconfig.NetworkCreateValues) (string plugins = append(plugins, bridge) plugins = append(plugins, network.NewPortMapPlugin()) plugins = append(plugins, network.NewFirewallPlugin()) + // if we find the dnsname plugin, we add configuration for it + if network.HasDNSNamePlugin(runtimeConfig.CNIPluginDir) && !cli.DisableDNS { + // Note: in the future we might like to allow for dynamic domain names + plugins = append(plugins, network.NewDNSNamePlugin(network.DefaultPodmanDomainName)) + } ncList["plugins"] = plugins b, err := json.MarshalIndent(ncList, "", " ") if err != nil { diff --git a/pkg/adapter/runtime.go b/pkg/adapter/runtime.go index 0706d4b6a..84d43c337 100644 --- a/pkg/adapter/runtime.go +++ b/pkg/adapter/runtime.go @@ -186,7 +186,12 @@ func (r *LocalRuntime) CreateVolume(ctx context.Context, c *cliconfig.VolumeCrea } if len(opts) != 0 { - options = append(options, libpod.WithVolumeOptions(opts)) + // We need to process -o for uid, gid + parsedOptions, err := shared.ParseVolumeOptions(opts) + if err != nil { + return "", err + } + options = append(options, parsedOptions...) } newVolume, err := r.NewVolume(ctx, options...) if err != nil { diff --git a/pkg/adapter/runtime_remote.go b/pkg/adapter/runtime_remote.go index 3b808a2ee..870e86896 100644 --- a/pkg/adapter/runtime_remote.go +++ b/pkg/adapter/runtime_remote.go @@ -659,12 +659,39 @@ func (r *LocalRuntime) Push(ctx context.Context, srcName, destination, manifestM } // InspectVolumes returns a slice of volumes based on an arg list or --all -func (r *LocalRuntime) InspectVolumes(ctx context.Context, c *cliconfig.VolumeInspectValues) ([]*Volume, error) { - reply, err := iopodman.GetVolumes().Call(r.Conn, c.InputArgs, c.All) - if err != nil { - return nil, err +func (r *LocalRuntime) InspectVolumes(ctx context.Context, c *cliconfig.VolumeInspectValues) ([]*libpod.InspectVolumeData, error) { + var ( + inspectData []*libpod.InspectVolumeData + volumes []string + ) + + if c.All { + allVolumes, err := r.Volumes(ctx) + if err != nil { + return nil, err + } + for _, vol := range allVolumes { + volumes = append(volumes, vol.Name()) + } + } else { + for _, arg := range c.InputArgs { + volumes = append(volumes, arg) + } } - return varlinkVolumeToVolume(r, reply), nil + + for _, vol := range volumes { + jsonString, err := iopodman.InspectVolume().Call(r.Conn, vol) + if err != nil { + return nil, err + } + inspectJSON := new(libpod.InspectVolumeData) + if err := json.Unmarshal([]byte(jsonString), inspectJSON); err != nil { + return nil, errors.Wrapf(err, "error unmarshalling inspect JSON for volume %s", vol) + } + inspectData = append(inspectData, inspectJSON) + } + + return inspectData, nil } // Volumes returns a slice of adapter.volumes based on information about libpod diff --git a/pkg/adapter/terminal_linux.go b/pkg/adapter/terminal_linux.go index 26cfd7b5e..16e552802 100644 --- a/pkg/adapter/terminal_linux.go +++ b/pkg/adapter/terminal_linux.go @@ -13,7 +13,7 @@ import ( ) // ExecAttachCtr execs and attaches to a container -func ExecAttachCtr(ctx context.Context, ctr *libpod.Container, tty, privileged bool, env, cmd []string, user, workDir string, streams *libpod.AttachStreams, preserveFDs int, detachKeys string) (int, error) { +func ExecAttachCtr(ctx context.Context, ctr *libpod.Container, tty, privileged bool, env map[string]string, cmd []string, user, workDir string, streams *libpod.AttachStreams, preserveFDs uint, detachKeys string) (int, error) { resize := make(chan remotecommand.TerminalSize) haveTerminal := terminal.IsTerminal(int(os.Stdin.Fd())) diff --git a/pkg/hooks/docs/oci-hooks.5.md b/pkg/hooks/docs/oci-hooks.5.md index fc0442283..0a01e1bb8 100644 --- a/pkg/hooks/docs/oci-hooks.5.md +++ b/pkg/hooks/docs/oci-hooks.5.md @@ -88,9 +88,9 @@ $ cat /etc/containers/oci/hooks.d/oci-systemd-hook.json "version": "1.0.0", "hook": { "path": "/usr/libexec/oci/hooks.d/oci-systemd-hook" - } + }, "when": { - "commands": [".*/init$" , ".*/systemd$"], + "commands": [".*/init$" , ".*/systemd$"] }, "stages": ["prestart", "poststop"] } @@ -105,9 +105,9 @@ $ cat /etc/containers/oci/hooks.d/oci-umount.json "hook": { "path": "/usr/libexec/oci/hooks.d/oci-umount", "args": ["oci-umount", "--debug"], - } + }, "when": { - "hasBindMounts": true, + "hasBindMounts": true }, "stages": ["prestart"] } diff --git a/pkg/network/config.go b/pkg/network/config.go index 7eaa83833..37eb0dd64 100644 --- a/pkg/network/config.go +++ b/pkg/network/config.go @@ -14,6 +14,9 @@ const ( // CNIDeviceName is the default network device name and in // reality should have an int appended to it (cni-podman4) CNIDeviceName = "cni-podman" + // DefaultPodmanDomainName is used for the dnsname plugin to define + // a localized domain name for a created network + DefaultPodmanDomainName = "dns.podman" ) // GetDefaultPodmanNetwork outputs the default network for podman @@ -97,3 +100,14 @@ type FirewallConfig struct { func (f FirewallConfig) Bytes() ([]byte, error) { return json.MarshalIndent(f, "", "\t") } + +// DNSNameConfig describes the dns container name resolution plugin config +type DNSNameConfig struct { + PluginType string `json:"type"` + DomainName string `json:"domainName"` +} + +// Bytes outputs the configuration as []byte +func (d DNSNameConfig) Bytes() ([]byte, error) { + return json.MarshalIndent(d, "", "\t") +} diff --git a/pkg/network/devices.go b/pkg/network/devices.go index 85068a7d1..78e1a5aa5 100644 --- a/pkg/network/devices.go +++ b/pkg/network/devices.go @@ -24,19 +24,26 @@ func GetFreeDeviceName() (string, error) { if err != nil { return "", err } + bridgeNames, err := GetBridgeNamesFromFileSystem() + if err != nil { + return "", err + } for { deviceName = fmt.Sprintf("%s%d", CNIDeviceName, deviceNum) - logrus.Debugf("checking if device name %s exists in other cni networks", deviceName) + logrus.Debugf("checking if device name %q exists in other cni networks", deviceName) if util.StringInSlice(deviceName, networkNames) { deviceNum++ continue } - logrus.Debugf("checking if device name %s exists in live networks", deviceName) - if !util.StringInSlice(deviceName, liveNetworksNames) { + logrus.Debugf("checking if device name %q exists in live networks", deviceName) + if util.StringInSlice(deviceName, liveNetworksNames) { + deviceNum++ + continue + } + logrus.Debugf("checking if device name %q already exists as a bridge name ", deviceName) + if !util.StringInSlice(deviceName, bridgeNames) { break } - // TODO Still need to check the bridge names for a conflict but I dont know - // how to get them yet! deviceNum++ } return deviceName, nil diff --git a/pkg/network/files.go b/pkg/network/files.go index d55ec2dfd..2f3932974 100644 --- a/pkg/network/files.go +++ b/pkg/network/files.go @@ -129,3 +129,29 @@ func GetInterfaceNameFromConfig(path string) (string, error) { } return name, nil } + +// GetBridgeNamesFromFileSystem is a convenience function to get all the bridge +// names from the configured networks +func GetBridgeNamesFromFileSystem() ([]string, error) { + var bridgeNames []string + networks, err := LoadCNIConfsFromDir(CNIConfigDir) + if err != nil { + return nil, err + } + for _, n := range networks { + var name string + // iterate network conflists + for _, cniplugin := range n.Plugins { + // iterate plugins + if cniplugin.Network.Type == "bridge" { + plugin := make(map[string]interface{}) + if err := json.Unmarshal(cniplugin.Bytes, &plugin); err != nil { + continue + } + name = plugin["bridge"].(string) + } + } + bridgeNames = append(bridgeNames, name) + } + return bridgeNames, nil +} diff --git a/pkg/network/netconflist.go b/pkg/network/netconflist.go index c3b11b409..e19051b88 100644 --- a/pkg/network/netconflist.go +++ b/pkg/network/netconflist.go @@ -2,6 +2,8 @@ package network import ( "net" + "os" + "path/filepath" ) // NcList describes a generic map @@ -111,3 +113,22 @@ func NewFirewallPlugin() FirewallConfig { Backend: "iptables", } } + +// NewDNSNamePlugin creates the dnsname config with a given +// domainname +func NewDNSNamePlugin(domainName string) DNSNameConfig { + return DNSNameConfig{ + PluginType: "dnsname", + DomainName: domainName, + } +} + +// HasDNSNamePlugin looks to see if the dnsname cni plugin is present +func HasDNSNamePlugin(paths []string) bool { + for _, p := range paths { + if _, err := os.Stat(filepath.Join(p, "dnsname")); err == nil { + return true + } + } + return false +} diff --git a/pkg/rootless/rootless_linux.go b/pkg/rootless/rootless_linux.go index 05d641383..94c42f7d0 100644 --- a/pkg/rootless/rootless_linux.go +++ b/pkg/rootless/rootless_linux.go @@ -11,20 +11,16 @@ import ( "os/exec" gosignal "os/signal" "os/user" - "path/filepath" "runtime" "strconv" - "strings" "sync" - "syscall" "unsafe" "github.com/containers/libpod/pkg/errorhandling" "github.com/containers/storage/pkg/idtools" - "github.com/docker/docker/pkg/signal" - "github.com/godbus/dbus" "github.com/pkg/errors" "github.com/sirupsen/logrus" + "golang.org/x/sys/unix" ) /* @@ -130,7 +126,7 @@ func tryMappingTool(tool string, pid int, hostID int, mappings []idtools.IDMap) func readUserNs(path string) (string, error) { b := make([]byte, 256) - _, err := syscall.Readlink(path, b) + _, err := unix.Readlink(path, b) if err != nil { return "", err } @@ -143,7 +139,7 @@ func readUserNsFd(fd uintptr) (string, error) { func getParentUserNs(fd uintptr) (uintptr, error) { const nsGetParent = 0xb702 - ret, _, errno := syscall.Syscall(syscall.SYS_IOCTL, fd, uintptr(nsGetParent), 0) + ret, _, errno := unix.Syscall(unix.SYS_IOCTL, fd, uintptr(nsGetParent), 0) if errno != 0 { return 0, errno } @@ -179,7 +175,7 @@ func getUserNSFirstChild(fd uintptr) (*os.File, error) { for { nextFd, err := getParentUserNs(fd) if err != nil { - if err == syscall.ENOTTY { + if err == unix.ENOTTY { return os.NewFile(fd, "userns child"), nil } return nil, errors.Wrapf(err, "cannot get parent user namespace") @@ -191,14 +187,14 @@ func getUserNSFirstChild(fd uintptr) (*os.File, error) { } if ns == currentNS { - if err := syscall.Close(int(nextFd)); err != nil { + if err := unix.Close(int(nextFd)); err != nil { return nil, err } // Drop O_CLOEXEC for the fd. - _, _, errno := syscall.Syscall(syscall.SYS_FCNTL, fd, syscall.F_SETFD, 0) + _, _, errno := unix.Syscall(unix.SYS_FCNTL, fd, unix.F_SETFD, 0) if errno != 0 { - if err := syscall.Close(int(fd)); err != nil { + if err := unix.Close(int(fd)); err != nil { logrus.Errorf("failed to close file descriptor %d", fd) } return nil, errno @@ -206,99 +202,13 @@ func getUserNSFirstChild(fd uintptr) (*os.File, error) { return os.NewFile(fd, "userns child"), nil } - if err := syscall.Close(int(fd)); err != nil { + if err := unix.Close(int(fd)); err != nil { return nil, err } fd = nextFd } } -// EnableLinger configures the system to not kill the user processes once the session -// terminates -func EnableLinger() (string, error) { - uid := fmt.Sprintf("%d", GetRootlessUID()) - - conn, err := dbus.SystemBus() - if err == nil { - defer func() { - if err := conn.Close(); err != nil { - logrus.Errorf("unable to close dbus connection: %q", err) - } - }() - } - - lingerEnabled := false - - // If we have a D-BUS connection, attempt to read the LINGER property from it. - if conn != nil { - path := dbus.ObjectPath(fmt.Sprintf("/org/freedesktop/login1/user/_%s", uid)) - ret, err := conn.Object("org.freedesktop.login1", path).GetProperty("org.freedesktop.login1.User.Linger") - if err == nil && ret.Value().(bool) { - lingerEnabled = true - } - } - - xdgRuntimeDir := os.Getenv("XDG_RUNTIME_DIR") - lingerFile := "" - if xdgRuntimeDir != "" && !lingerEnabled { - lingerFile = filepath.Join(xdgRuntimeDir, "libpod/linger") - _, err := os.Stat(lingerFile) - if err == nil { - lingerEnabled = true - } - } - - if !lingerEnabled { - // First attempt with D-BUS, if it fails, then attempt with "loginctl enable-linger" - if conn != nil { - o := conn.Object("org.freedesktop.login1", "/org/freedesktop/login1") - ret := o.Call("org.freedesktop.login1.Manager.SetUserLinger", 0, uint32(GetRootlessUID()), true, true) - if ret.Err == nil { - lingerEnabled = true - } - } - if !lingerEnabled { - err := exec.Command("loginctl", "enable-linger", uid).Run() - if err == nil { - lingerEnabled = true - } else { - logrus.Debugf("cannot run `loginctl enable-linger` for the current user: %v", err) - } - } - if lingerEnabled && lingerFile != "" { - f, err := os.Create(lingerFile) - if err == nil { - if err := f.Close(); err != nil { - logrus.Errorf("failed to close %s", f.Name()) - } - } else { - logrus.Debugf("could not create linger file: %v", err) - } - } - } - - if !lingerEnabled { - return "", nil - } - - // If we have a D-BUS connection, attempt to read the RUNTIME PATH from it. - if conn != nil { - path := dbus.ObjectPath(fmt.Sprintf("/org/freedesktop/login1/user/_%s", uid)) - ret, err := conn.Object("org.freedesktop.login1", path).GetProperty("org.freedesktop.login1.User.RuntimePath") - if err == nil { - return strings.Trim(ret.String(), "\"\n"), nil - } - } - - // If XDG_RUNTIME_DIR is not set and the D-BUS call didn't work, try to get the runtime path with "loginctl" - output, err := exec.Command("loginctl", "-pRuntimePath", "show-user", uid).Output() - if err != nil { - logrus.Debugf("could not get RuntimePath using loginctl: %v", err) - return "", nil - } - return strings.Trim(strings.Replace(string(output), "RuntimePath=", "", -1), "\"\n"), nil -} - // joinUserAndMountNS re-exec podman in a new userNS and join the user and mount // namespace of the specified PID without looking up its parent. Useful to join directly // the conmon process. @@ -394,7 +304,7 @@ func becomeRootInUserNS(pausePid, fileToRead string, fileOutput *os.File) (bool, runtime.LockOSThread() defer runtime.UnlockOSThread() - fds, err := syscall.Socketpair(syscall.AF_UNIX, syscall.SOCK_DGRAM, 0) + fds, err := unix.Socketpair(unix.AF_UNIX, unix.SOCK_DGRAM, 0) if err != nil { return false, -1, err } @@ -431,12 +341,14 @@ func becomeRootInUserNS(pausePid, fileToRead string, fileOutput *os.File) (bool, if err != nil { return false, -1, errors.Wrapf(err, "cannot write setgroups file") } + logrus.Debugf("write setgroups file exited with 0") uidMap := fmt.Sprintf("/proc/%d/uid_map", pid) err = ioutil.WriteFile(uidMap, []byte(fmt.Sprintf("%d %d 1\n", 0, os.Geteuid())), 0666) if err != nil { return false, -1, errors.Wrapf(err, "cannot write uid_map") } + logrus.Debugf("write uid_map exited with 0") } gidsMapped := false @@ -489,21 +401,21 @@ func becomeRootInUserNS(pausePid, fileToRead string, fileOutput *os.File) (bool, signals := []os.Signal{} for sig := 0; sig < numSig; sig++ { - if sig == int(syscall.SIGTSTP) { + if sig == int(unix.SIGTSTP) { continue } - signals = append(signals, syscall.Signal(sig)) + signals = append(signals, unix.Signal(sig)) } gosignal.Notify(c, signals...) defer gosignal.Reset() go func() { for s := range c { - if s == signal.SIGCHLD || s == signal.SIGPIPE { + if s == unix.SIGCHLD || s == unix.SIGPIPE { continue } - if err := syscall.Kill(int(pidC), s.(syscall.Signal)); err != nil { + if err := unix.Kill(int(pidC), s.(unix.Signal)); err != nil { logrus.Errorf("failed to kill %d", int(pidC)) } } @@ -558,7 +470,7 @@ func TryJoinFromFilePaths(pausePidPath string, needNewNamespace bool, paths []st lastErr = nil break } else { - fds, err := syscall.Socketpair(syscall.AF_UNIX, syscall.SOCK_DGRAM, 0) + fds, err := unix.Socketpair(unix.AF_UNIX, unix.SOCK_DGRAM, 0) if err != nil { lastErr = err continue @@ -602,7 +514,7 @@ func TryJoinFromFilePaths(pausePidPath string, needNewNamespace bool, paths []st return joinUserAndMountNS(uint(pausePid), pausePidPath) } -func readMappingsProc(path string) ([]idtools.IDMap, error) { +func ReadMappingsProc(path string) ([]idtools.IDMap, error) { file, err := os.Open(path) if err != nil { return nil, errors.Wrapf(err, "cannot open %s", path) @@ -668,7 +580,7 @@ func ConfigurationMatches() (bool, error) { return false, err } - currentUIDs, err := readMappingsProc("/proc/self/uid_map") + currentUIDs, err := ReadMappingsProc("/proc/self/uid_map") if err != nil { return false, err } @@ -677,7 +589,7 @@ func ConfigurationMatches() (bool, error) { return false, err } - currentGIDs, err := readMappingsProc("/proc/self/gid_map") + currentGIDs, err := ReadMappingsProc("/proc/self/gid_map") if err != nil { return false, err } diff --git a/pkg/rootless/rootless_unsupported.go b/pkg/rootless/rootless_unsupported.go index ddd9182b0..1499b737f 100644 --- a/pkg/rootless/rootless_unsupported.go +++ b/pkg/rootless/rootless_unsupported.go @@ -37,12 +37,6 @@ func GetRootlessGID() int { return -1 } -// EnableLinger configures the system to not kill the user processes once the session -// terminates -func EnableLinger() (string, error) { - return "", nil -} - // TryJoinFromFilePaths attempts to join the namespaces of the pid files in paths. // This is useful when there are already running containers and we // don't have a pause process yet. We can use the paths to the conmon @@ -65,3 +59,8 @@ func ConfigurationMatches() (bool, error) { func GetConfiguredMappings() ([]idtools.IDMap, []idtools.IDMap, error) { return nil, nil, errors.New("this function is not supported on this os") } + +// ReadMappingsProc returns the uid_map and gid_map +func ReadMappingsProc(path string) ([]idtools.IDMap, error) { + return nil, nil +} diff --git a/pkg/spec/createconfig.go b/pkg/spec/createconfig.go index a65263b7d..2addfda4b 100644 --- a/pkg/spec/createconfig.go +++ b/pkg/spec/createconfig.go @@ -104,7 +104,8 @@ type CreateConfig struct { NetworkAlias []string //network-alias PidMode namespaces.PidMode //pid Pod string //pod - CgroupMode namespaces.CgroupMode //cgroup + PodmanPath string + CgroupMode namespaces.CgroupMode //cgroup PortBindings nat.PortMap Privileged bool //privileged Publish []string //publish @@ -153,7 +154,16 @@ func (c *CreateConfig) createExitCommand(runtime *libpod.Runtime) ([]string, err return nil, err } - cmd, _ := os.Executable() + // We need a cleanup process for containers in the current model. + // But we can't assume that the caller is Podman - it could be another + // user of the API. + // As such, provide a way to specify a path to Podman, so we can + // still invoke a cleanup process. + cmd := c.PodmanPath + if cmd == "" { + cmd, _ = os.Executable() + } + command := []string{cmd, "--root", config.StorageConfig.GraphRoot, "--runroot", config.StorageConfig.RunRoot, @@ -195,8 +205,7 @@ func (c *CreateConfig) getContainerCreateOptions(runtime *libpod.Runtime, pod *l if c.Interactive { options = append(options, libpod.WithStdin()) } - if c.Systemd && (strings.HasSuffix(c.Command[0], "init") || - strings.HasSuffix(c.Command[0], "systemd")) { + if c.Systemd { options = append(options, libpod.WithSystemd()) } if c.Name != "" { diff --git a/pkg/spec/spec.go b/pkg/spec/spec.go index 57c6e8da7..da5c14948 100644 --- a/pkg/spec/spec.go +++ b/pkg/spec/spec.go @@ -300,10 +300,19 @@ func (config *CreateConfig) createConfigToOCISpec(runtime *libpod.Runtime, userM blockAccessToKernelFilesystems(config, &g) + var runtimeConfig *libpod.RuntimeConfig + + if runtime != nil { + runtimeConfig, err = runtime.GetConfig() + if err != nil { + return nil, err + } + } + // RESOURCES - PIDS if config.Resources.PidsLimit > 0 { - // if running on rootless on a cgroupv1 machine, pids limit is - // not supported. If the value is still the default + // if running on rootless on a cgroupv1 machine or using the cgroupfs manager, pids + // limit is not supported. If the value is still the default // then ignore the settings. If the caller asked for a // non-default, then try to use it. setPidLimit := true @@ -312,7 +321,7 @@ func (config *CreateConfig) createConfigToOCISpec(runtime *libpod.Runtime, userM if err != nil { return nil, err } - if !cgroup2 && config.Resources.PidsLimit == sysinfo.GetDefaultPidsLimit() { + if (!cgroup2 || (runtimeConfig != nil && runtimeConfig.CgroupManager != libpod.SystemdCgroupsManager)) && config.Resources.PidsLimit == sysinfo.GetDefaultPidsLimit() { setPidLimit = false } } @@ -407,10 +416,13 @@ func (config *CreateConfig) createConfigToOCISpec(runtime *libpod.Runtime, userM if !addedResources { configSpec.Linux.Resources = &spec.LinuxResources{} } - if addedResources && !cgroup2 { - return nil, errors.New("invalid configuration, cannot set resources with rootless containers not using cgroups v2 unified mode") + + canUseResources := cgroup2 && runtimeConfig != nil && (runtimeConfig.CgroupManager == libpod.SystemdCgroupsManager) + + if addedResources && !canUseResources { + return nil, errors.New("invalid configuration, cannot specify resource limits without cgroups v2 and --cgroup-manager=systemd") } - if !cgroup2 { + if !canUseResources { // Force the resources block to be empty instead of having default values. configSpec.Linux.Resources = &spec.LinuxResources{} } diff --git a/pkg/spec/storage.go b/pkg/spec/storage.go index 93919dd0a..095534589 100644 --- a/pkg/spec/storage.go +++ b/pkg/spec/storage.go @@ -11,7 +11,6 @@ import ( "github.com/containers/libpod/libpod" "github.com/containers/libpod/pkg/util" pmount "github.com/containers/storage/pkg/mount" - "github.com/containers/storage/pkg/stringid" spec "github.com/opencontainers/runtime-spec/specs-go" "github.com/pkg/errors" "github.com/sirupsen/logrus" @@ -648,7 +647,7 @@ func (config *CreateConfig) getVolumeMounts() (map[string]spec.Mount, map[string mounts := make(map[string]spec.Mount) volumes := make(map[string]*libpod.ContainerNamedVolume) - volumeFormatErr := errors.Errorf("incorrect volume format, should be host-dir:ctr-dir[:option]") + volumeFormatErr := errors.Errorf("incorrect volume format, should be [host-dir:]ctr-dir[:option]") for _, vol := range config.Volumes { var ( @@ -665,7 +664,11 @@ func (config *CreateConfig) getVolumeMounts() (map[string]spec.Mount, map[string src = splitVol[0] if len(splitVol) == 1 { - dest = src + // This is an anonymous named volume. Only thing given + // is destination. + // Name/source will be blank, and populated by libpod. + src = "" + dest = splitVol[0] } else if len(splitVol) > 1 { dest = splitVol[1] } @@ -675,8 +678,11 @@ func (config *CreateConfig) getVolumeMounts() (map[string]spec.Mount, map[string } } - if err := parse.ValidateVolumeHostDir(src); err != nil { - return nil, nil, err + // Do not check source dir for anonymous volumes + if len(splitVol) > 1 { + if err := parse.ValidateVolumeHostDir(src); err != nil { + return nil, nil, err + } } if err := parse.ValidateVolumeCtrDir(dest); err != nil { return nil, nil, err @@ -732,13 +738,13 @@ func (config *CreateConfig) getImageVolumes() (map[string]spec.Mount, map[string Destination: cleanDest, Source: TypeTmpfs, Type: TypeTmpfs, - Options: []string{"rprivate", "rw", "nodev"}, + Options: []string{"rprivate", "rw", "nodev", "exec"}, } mounts[vol] = mount } else { + // Anonymous volumes have no name. namedVolume := new(libpod.ContainerNamedVolume) - namedVolume.Name = stringid.GenerateNonCryptoID() - namedVolume.Options = []string{"rprivate", "rw", "nodev"} + namedVolume.Options = []string{"rprivate", "rw", "nodev", "exec"} namedVolume.Dest = cleanDest volumes[vol] = namedVolume } diff --git a/pkg/util/utils.go b/pkg/util/utils.go index 0190b106d..d9a84e4e5 100644 --- a/pkg/util/utils.go +++ b/pkg/util/utils.go @@ -318,7 +318,7 @@ func WriteStorageConfigFile(storageOpts *storage.StoreOptions, storageConf strin if err := os.MkdirAll(filepath.Dir(storageConf), 0755); err != nil { return err } - storageFile, err := os.OpenFile(storageConf, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0666) + storageFile, err := os.OpenFile(storageConf, os.O_RDWR|os.O_TRUNC, 0600) if err != nil { return errors.Wrapf(err, "cannot open %s", storageConf) } diff --git a/pkg/varlinkapi/attach.go b/pkg/varlinkapi/attach.go index f8557ae0c..37adbbf55 100644 --- a/pkg/varlinkapi/attach.go +++ b/pkg/varlinkapi/attach.go @@ -70,7 +70,6 @@ func (i *LibpodAPI) Attach(call iopodman.VarlinkCall, name string, detachKeys st } reader, writer, _, pw, streams := setupStreams(call) - go func() { if err := virtwriter.Reader(reader, nil, nil, pw, resize, nil); err != nil { errChan <- err diff --git a/pkg/varlinkapi/containers.go b/pkg/varlinkapi/containers.go index 79fcef11a..b471ee2cf 100644 --- a/pkg/varlinkapi/containers.go +++ b/pkg/varlinkapi/containers.go @@ -9,6 +9,7 @@ import ( "io" "io/ioutil" "os" + "strings" "sync" "syscall" "time" @@ -563,9 +564,14 @@ func (i *LibpodAPI) GetAttachSockets(call iopodman.VarlinkCall, name string) err } } + sockPath, err := ctr.AttachSocketPath() + if err != nil { + return call.ReplyErrorOccurred(err.Error()) + } + s := iopodman.Sockets{ Container_id: ctr.ID(), - Io_socket: ctr.AttachSocketPath(), + Io_socket: sockPath, Control_socket: ctr.ControlSocketPath(), } return call.ReplyGetAttachSockets(s) @@ -811,9 +817,19 @@ func (i *LibpodAPI) ExecContainer(call iopodman.VarlinkCall, opts iopodman.ExecO // ACK the client upgrade request call.ReplyExecContainer() - envs := []string{} + envs := make(map[string]string) if opts.Env != nil { - envs = *opts.Env + // HACK: The Varlink API uses the old []string format for env, + // storage as "k=v". Split on the = and turn into the new map + // format. + for _, env := range *opts.Env { + splitEnv := strings.SplitN(env, "=", 2) + if len(splitEnv) == 1 { + logrus.Errorf("Got badly-formatted environment variable %q in exec", env) + continue + } + envs[splitEnv[0]] = splitEnv[1] + } } var user string diff --git a/pkg/varlinkapi/virtwriter/virtwriter.go b/pkg/varlinkapi/virtwriter/virtwriter.go index 27ecd1f52..dd171943f 100644 --- a/pkg/varlinkapi/virtwriter/virtwriter.go +++ b/pkg/varlinkapi/virtwriter/virtwriter.go @@ -5,6 +5,7 @@ import ( "encoding/binary" "encoding/json" "io" + "time" "github.com/pkg/errors" "k8s.io/client-go/tools/remotecommand" @@ -26,8 +27,14 @@ const ( TerminalResize SocketDest = iota // Quit and detach Quit SocketDest = iota + // Quit from the client + HangUpFromClient SocketDest = iota ) +// ClientHangup signifies that the client wants to drop its +// connection from the server +var ClientHangup = errors.New("client hangup") + // IntToSocketDest returns a socketdest based on integer input func IntToSocketDest(i int) SocketDest { switch i { @@ -41,6 +48,8 @@ func IntToSocketDest(i int) SocketDest { return TerminalResize case Quit.Int(): return Quit + case HangUpFromClient.Int(): + return HangUpFromClient default: return ToStderr } @@ -65,7 +74,7 @@ func NewVirtWriteCloser(w *bufio.Writer, dest SocketDest) VirtWriteCloser { // Close is a required method for a writecloser func (v VirtWriteCloser) Close() error { - return nil + return v.writer.Flush() } // Write prepends a header to the input message. The header is @@ -96,7 +105,6 @@ func Reader(r *bufio.Reader, output, errput, input io.Writer, resize chan remote if r == nil { return errors.Errorf("Reader must not be nil") } - for { n, err := io.ReadFull(r, headerBytes) if err != nil { @@ -107,7 +115,6 @@ func Reader(r *bufio.Reader, output, errput, input io.Writer, resize chan remote } messageSize = int64(binary.BigEndian.Uint32(headerBytes[4:8])) - switch IntToSocketDest(int(headerBytes[0])) { case ToStdout: if output != nil { @@ -161,7 +168,16 @@ func Reader(r *bufio.Reader, output, errput, input io.Writer, resize chan remote execEcChan <- int(ecInt) } return nil - + case HangUpFromClient: + // This sleep allows the pipes to flush themselves before tearing everything down. + // It makes me sick to do it but after a full day I cannot put my finger on the race + // that occurs when closing things up. It would require a significant rewrite of code + // to make the pipes close down properly. Given that we are currently discussing a + // rewrite of all things remote, this hardly seems worth resolving. + // + // reproducer: echo hello | (podman-remote run -i alpine cat) + time.Sleep(1 * time.Second) + return ClientHangup default: // Something really went wrong return errors.New("unknown multiplex destination") diff --git a/pkg/varlinkapi/volumes.go b/pkg/varlinkapi/volumes.go index b41eb5086..2dddd3008 100644 --- a/pkg/varlinkapi/volumes.go +++ b/pkg/varlinkapi/volumes.go @@ -3,6 +3,8 @@ package varlinkapi import ( + "encoding/json" + "github.com/containers/libpod/cmd/podman/shared" "github.com/containers/libpod/cmd/podman/varlink" "github.com/containers/libpod/libpod" @@ -22,7 +24,11 @@ func (i *LibpodAPI) VolumeCreate(call iopodman.VarlinkCall, options iopodman.Vol volumeOptions = append(volumeOptions, libpod.WithVolumeLabels(options.Labels)) } if len(options.Options) > 0 { - volumeOptions = append(volumeOptions, libpod.WithVolumeOptions(options.Options)) + parsedOptions, err := shared.ParseVolumeOptions(options.Options) + if err != nil { + return call.ReplyErrorOccurred(err.Error()) + } + volumeOptions = append(volumeOptions, parsedOptions...) } newVolume, err := i.Runtime.NewVolume(getContext(), volumeOptions...) if err != nil { @@ -80,6 +86,23 @@ func (i *LibpodAPI) GetVolumes(call iopodman.VarlinkCall, args []string, all boo return call.ReplyGetVolumes(volumes) } +// InspectVolume inspects a single volume, returning its JSON as a string. +func (i *LibpodAPI) InspectVolume(call iopodman.VarlinkCall, name string) error { + vol, err := i.Runtime.LookupVolume(name) + if err != nil { + return call.ReplyErrorOccurred(err.Error()) + } + inspectOut, err := vol.Inspect() + if err != nil { + return call.ReplyErrorOccurred(err.Error()) + } + inspectJSON, err := json.Marshal(inspectOut) + if err != nil { + return call.ReplyErrorOccurred(err.Error()) + } + return call.ReplyInspectVolume(string(inspectJSON)) +} + // VolumesPrune removes unused images via a varlink call func (i *LibpodAPI) VolumesPrune(call iopodman.VarlinkCall) error { var errs []string diff --git a/test/e2e/checkpoint_test.go b/test/e2e/checkpoint_test.go index 1caefd299..4b43ceb30 100644 --- a/test/e2e/checkpoint_test.go +++ b/test/e2e/checkpoint_test.go @@ -67,13 +67,13 @@ var _ = Describe("Podman checkpoint", func() { It("podman checkpoint bogus container", func() { session := podmanTest.Podman([]string{"container", "checkpoint", "foobar"}) session.WaitWithDefaultTimeout() - Expect(session.ExitCode()).To(Not(Equal(0))) + Expect(session).To(ExitWithError()) }) It("podman restore bogus container", func() { session := podmanTest.Podman([]string{"container", "restore", "foobar"}) session.WaitWithDefaultTimeout() - Expect(session.ExitCode()).To(Not(Equal(0))) + Expect(session).To(ExitWithError()) }) It("podman checkpoint a running container by id", func() { diff --git a/test/e2e/cp_test.go b/test/e2e/cp_test.go index 3317683de..8d4c3dee7 100644 --- a/test/e2e/cp_test.go +++ b/test/e2e/cp_test.go @@ -53,7 +53,7 @@ var _ = Describe("Podman cp", func() { session = podmanTest.Podman([]string{"cp", srcPath, name + ":foo/"}) session.WaitWithDefaultTimeout() - Expect(session.ExitCode()).To(Not(Equal(0))) + Expect(session).To(ExitWithError()) session = podmanTest.Podman([]string{"cp", srcPath, name + ":foo"}) session.WaitWithDefaultTimeout() @@ -205,7 +205,7 @@ var _ = Describe("Podman cp", func() { session = podmanTest.Podman([]string{"cp", "--pause=false", srcPath, name + ":/test1/"}) session.WaitWithDefaultTimeout() - Expect(session.ExitCode()).To(Not(Equal(0))) + Expect(session).To(ExitWithError()) }) It("podman cp volume", func() { diff --git a/test/e2e/create_staticip_test.go b/test/e2e/create_staticip_test.go index 709e56665..72a0638f9 100644 --- a/test/e2e/create_staticip_test.go +++ b/test/e2e/create_staticip_test.go @@ -40,13 +40,13 @@ var _ = Describe("Podman create with --ip flag", func() { It("Podman create --ip with garbage address", func() { result := podmanTest.Podman([]string{"create", "--name", "test", "--ip", "114232346", ALPINE, "ls"}) result.WaitWithDefaultTimeout() - Expect(result.ExitCode()).ToNot(Equal(0)) + Expect(result).To(ExitWithError()) }) It("Podman create --ip with v6 address", func() { result := podmanTest.Podman([]string{"create", "--name", "test", "--ip", "2001:db8:bad:beef::1", ALPINE, "ls"}) result.WaitWithDefaultTimeout() - Expect(result.ExitCode()).ToNot(Equal(0)) + Expect(result).To(ExitWithError()) }) It("Podman create --ip with non-allocatable IP", func() { @@ -56,7 +56,7 @@ var _ = Describe("Podman create with --ip flag", func() { result = podmanTest.Podman([]string{"start", "test"}) result.WaitWithDefaultTimeout() - Expect(result.ExitCode()).ToNot(Equal(0)) + Expect(result).To(ExitWithError()) }) It("Podman create with specified static IP has correct IP", func() { @@ -88,6 +88,6 @@ var _ = Describe("Podman create with --ip flag", func() { Expect(result.ExitCode()).To(Equal(0)) result = podmanTest.Podman([]string{"start", "test2"}) result.WaitWithDefaultTimeout() - Expect(result.ExitCode()).ToNot(Equal(0)) + Expect(result).To(ExitWithError()) }) }) diff --git a/test/e2e/create_test.go b/test/e2e/create_test.go index 2918cce78..65b747880 100644 --- a/test/e2e/create_test.go +++ b/test/e2e/create_test.go @@ -235,7 +235,7 @@ var _ = Describe("Podman create", func() { It("podman create --pull", func() { session := podmanTest.PodmanNoCache([]string{"create", "--pull", "never", "--name=foo", "nginx"}) session.WaitWithDefaultTimeout() - Expect(session.ExitCode()).To(Not(Equal(0))) + Expect(session).To(ExitWithError()) session = podmanTest.PodmanNoCache([]string{"create", "--pull", "always", "--name=foo", "nginx"}) session.WaitWithDefaultTimeout() diff --git a/test/e2e/exec_test.go b/test/e2e/exec_test.go index 13fdabb81..1c4a9adb9 100644 --- a/test/e2e/exec_test.go +++ b/test/e2e/exec_test.go @@ -203,11 +203,11 @@ var _ = Describe("Podman exec", func() { session := podmanTest.Podman([]string{"exec", "--workdir", "/missing", "test1", "pwd"}) session.WaitWithDefaultTimeout() - Expect(session.ExitCode()).To(Not(Equal(0))) + Expect(session).To(ExitWithError()) session = podmanTest.Podman([]string{"exec", "-w", "/missing", "test1", "pwd"}) session.WaitWithDefaultTimeout() - Expect(session.ExitCode()).To(Not(Equal(0))) + Expect(session).To(ExitWithError()) }) It("podman exec cannot be invoked", func() { diff --git a/test/e2e/export_test.go b/test/e2e/export_test.go index 8406b0e73..1c84c6f4d 100644 --- a/test/e2e/export_test.go +++ b/test/e2e/export_test.go @@ -72,6 +72,6 @@ var _ = Describe("Podman export", func() { outfile := filepath.Join(podmanTest.TempDir, "container:with:colon.tar") result := podmanTest.Podman([]string{"export", "-o", outfile, cid}) result.WaitWithDefaultTimeout() - Expect(result.ExitCode()).To(Not(Equal(0))) + Expect(result).To(ExitWithError()) }) }) diff --git a/test/e2e/generate_kube_test.go b/test/e2e/generate_kube_test.go index 49d2c12a8..5d3b1238a 100644 --- a/test/e2e/generate_kube_test.go +++ b/test/e2e/generate_kube_test.go @@ -40,13 +40,13 @@ var _ = Describe("Podman generate kube", func() { It("podman generate pod kube on bogus object", func() { session := podmanTest.Podman([]string{"generate", "kube", "foobar"}) session.WaitWithDefaultTimeout() - Expect(session.ExitCode()).To(Not(Equal(0))) + Expect(session).To(ExitWithError()) }) It("podman generate service kube on bogus object", func() { session := podmanTest.Podman([]string{"generate", "kube", "-s", "foobar"}) session.WaitWithDefaultTimeout() - Expect(session.ExitCode()).To(Not(Equal(0))) + Expect(session).To(ExitWithError()) }) It("podman generate kube on container", func() { diff --git a/test/e2e/generate_systemd_test.go b/test/e2e/generate_systemd_test.go index 314743a92..91072b023 100644 --- a/test/e2e/generate_systemd_test.go +++ b/test/e2e/generate_systemd_test.go @@ -37,19 +37,19 @@ var _ = Describe("Podman generate systemd", func() { It("podman generate systemd on bogus container/pod", func() { session := podmanTest.Podman([]string{"generate", "systemd", "foobar"}) session.WaitWithDefaultTimeout() - Expect(session.ExitCode()).To(Not(Equal(0))) + Expect(session).To(ExitWithError()) }) It("podman generate systemd bad restart policy", func() { session := podmanTest.Podman([]string{"generate", "systemd", "--restart-policy", "never", "foobar"}) session.WaitWithDefaultTimeout() - Expect(session.ExitCode()).To(Not(Equal(0))) + Expect(session).To(ExitWithError()) }) It("podman generate systemd bad timeout value", func() { session := podmanTest.Podman([]string{"generate", "systemd", "--timeout", "-1", "foobar"}) session.WaitWithDefaultTimeout() - Expect(session.ExitCode()).To(Not(Equal(0))) + Expect(session).To(ExitWithError()) }) It("podman generate systemd good timeout value", func() { diff --git a/test/e2e/healthcheck_run_test.go b/test/e2e/healthcheck_run_test.go index e10aef427..4acea06eb 100644 --- a/test/e2e/healthcheck_run_test.go +++ b/test/e2e/healthcheck_run_test.go @@ -38,7 +38,7 @@ var _ = Describe("Podman healthcheck run", func() { It("podman healthcheck run bogus container", func() { session := podmanTest.Podman([]string{"healthcheck", "run", "foobar"}) session.WaitWithDefaultTimeout() - Expect(session.ExitCode()).To(Not(Equal(0))) + Expect(session).To(ExitWithError()) }) It("podman healthcheck on valid container", func() { diff --git a/test/e2e/images_test.go b/test/e2e/images_test.go index 8203e4273..e125c62b4 100644 --- a/test/e2e/images_test.go +++ b/test/e2e/images_test.go @@ -101,6 +101,13 @@ var _ = Describe("Podman images", func() { Expect(session.LineInOuputStartsWith("docker.io/library/busybox")).To(BeTrue()) }) + It("podman empty images list in JSON format", func() { + session := podmanTest.Podman([]string{"images", "--format=json", "not-existing-image"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + Expect(session.IsJSONOutputValid()).To(BeTrue()) + }) + It("podman images in JSON format", func() { session := podmanTest.Podman([]string{"images", "--format=json"}) session.WaitWithDefaultTimeout() diff --git a/test/e2e/inspect_test.go b/test/e2e/inspect_test.go index 790115133..7d029c52f 100644 --- a/test/e2e/inspect_test.go +++ b/test/e2e/inspect_test.go @@ -46,7 +46,7 @@ var _ = Describe("Podman inspect", func() { SkipIfRemote() session := podmanTest.Podman([]string{"inspect", "foobar4321"}) session.WaitWithDefaultTimeout() - Expect(session.ExitCode()).To(Not(Equal(0))) + Expect(session).To(ExitWithError()) }) It("podman inspect with GO format", func() { @@ -100,6 +100,23 @@ var _ = Describe("Podman inspect", func() { Expect(len(result.OutputToStringArray())).To(Equal(2)) }) + It("podman inspect container and filter for Image{ID}", func() { + SkipIfRemote() + ls, ec, _ := podmanTest.RunLsContainer("") + Expect(ec).To(Equal(0)) + cid := ls.OutputToString() + + result := podmanTest.Podman([]string{"inspect", "--format={{.ImageID}}", cid}) + result.WaitWithDefaultTimeout() + Expect(result.ExitCode()).To(Equal(0)) + Expect(len(result.OutputToStringArray())).To(Equal(1)) + + result = podmanTest.Podman([]string{"inspect", "--format={{.Image}}", cid}) + result.WaitWithDefaultTimeout() + Expect(result.ExitCode()).To(Equal(0)) + Expect(len(result.OutputToStringArray())).To(Equal(1)) + }) + It("podman inspect -l with additional input should fail", func() { SkipIfRemote() result := podmanTest.Podman([]string{"inspect", "-l", "1234foobar"}) diff --git a/test/e2e/kill_test.go b/test/e2e/kill_test.go index 017fe4a3f..834f86b77 100644 --- a/test/e2e/kill_test.go +++ b/test/e2e/kill_test.go @@ -35,7 +35,7 @@ var _ = Describe("Podman kill", func() { It("podman kill bogus container", func() { session := podmanTest.Podman([]string{"kill", "foobar"}) session.WaitWithDefaultTimeout() - Expect(session.ExitCode()).To(Not(Equal(0))) + Expect(session).To(ExitWithError()) }) It("podman container kill a running container by id", func() { diff --git a/test/e2e/load_test.go b/test/e2e/load_test.go index 9209e1770..9ff358d26 100644 --- a/test/e2e/load_test.go +++ b/test/e2e/load_test.go @@ -143,7 +143,7 @@ var _ = Describe("Podman load", func() { It("podman load bogus file", func() { save := podmanTest.PodmanNoCache([]string{"load", "-i", "foobar.tar"}) save.WaitWithDefaultTimeout() - Expect(save.ExitCode()).ToNot(Equal(0)) + Expect(save).To(ExitWithError()) }) It("podman load multiple tags", func() { diff --git a/test/e2e/login_logout_test.go b/test/e2e/login_logout_test.go index 4d476e05f..14cfed5db 100644 --- a/test/e2e/login_logout_test.go +++ b/test/e2e/login_logout_test.go @@ -109,7 +109,7 @@ var _ = Describe("Podman login and logout", func() { session = podmanTest.Podman([]string{"push", ALPINE, testImg}) session.WaitWithDefaultTimeout() - Expect(session.ExitCode()).To(Not(Equal(0))) + Expect(session).To(ExitWithError()) }) It("podman login and logout with flag --authfile", func() { @@ -198,7 +198,7 @@ var _ = Describe("Podman login and logout", func() { session = podmanTest.Podman([]string{"push", ALPINE, "localhost:9001/test-alpine"}) session.WaitWithDefaultTimeout() - Expect(session.ExitCode()).To(Not(Equal(0))) + Expect(session).To(ExitWithError()) session = podmanTest.Podman([]string{"login", "--username", "podmantest", "--password", "test", "localhost:9001"}) session.WaitWithDefaultTimeout() @@ -218,7 +218,7 @@ var _ = Describe("Podman login and logout", func() { session = podmanTest.Podman([]string{"push", ALPINE, testImg}) session.WaitWithDefaultTimeout() - Expect(session.ExitCode()).To(Not(Equal(0))) + Expect(session).To(ExitWithError()) session = podmanTest.Podman([]string{"push", ALPINE, "localhost:9001/test-alpine"}) session.WaitWithDefaultTimeout() @@ -234,10 +234,10 @@ var _ = Describe("Podman login and logout", func() { session = podmanTest.Podman([]string{"push", ALPINE, testImg}) session.WaitWithDefaultTimeout() - Expect(session.ExitCode()).To(Not(Equal(0))) + Expect(session).To(ExitWithError()) session = podmanTest.Podman([]string{"push", ALPINE, "localhost:9001/test-alpine"}) session.WaitWithDefaultTimeout() - Expect(session.ExitCode()).To(Not(Equal(0))) + Expect(session).To(ExitWithError()) }) }) diff --git a/test/e2e/logs_test.go b/test/e2e/logs_test.go index d17f60a5d..f34d85d76 100644 --- a/test/e2e/logs_test.go +++ b/test/e2e/logs_test.go @@ -108,7 +108,7 @@ var _ = Describe("Podman logs", func() { It("podman logs latest and container name should fail", func() { results := podmanTest.Podman([]string{"logs", "-l", "foobar"}) results.WaitWithDefaultTimeout() - Expect(results.ExitCode()).ToNot(Equal(0)) + Expect(results).To(ExitWithError()) }) It("podman logs two containers and should display short container IDs", func() { diff --git a/test/e2e/negative_test.go b/test/e2e/negative_test.go index 3cb54a20a..957609b7e 100644 --- a/test/e2e/negative_test.go +++ b/test/e2e/negative_test.go @@ -33,6 +33,6 @@ var _ = Describe("Podman negative command-line", func() { It("podman snuffleupagus exits non-zero", func() { session := podmanTest.Podman([]string{"snuffleupagus"}) session.WaitWithDefaultTimeout() - Expect(session.ExitCode()).To(Not(Equal(0))) + Expect(session).To(ExitWithError()) }) }) diff --git a/test/e2e/network_create_test.go b/test/e2e/network_create_test.go index 264219178..b83757cc0 100644 --- a/test/e2e/network_create_test.go +++ b/test/e2e/network_create_test.go @@ -182,19 +182,19 @@ var _ = Describe("Podman network create", func() { It("podman network create with invalid subnet", func() { nc := podmanTest.Podman([]string{"network", "create", "--subnet", "10.11.12.0/17000", "fail"}) nc.WaitWithDefaultTimeout() - Expect(nc.ExitCode()).ToNot(BeZero()) + Expect(nc).To(ExitWithError()) }) It("podman network create with invalid IP", func() { nc := podmanTest.Podman([]string{"network", "create", "--subnet", "10.11.0/17000", "fail"}) nc.WaitWithDefaultTimeout() - Expect(nc.ExitCode()).ToNot(BeZero()) + Expect(nc).To(ExitWithError()) }) It("podman network create with invalid gateway for subnet", func() { nc := podmanTest.Podman([]string{"network", "create", "--subnet", "10.11.12.0/24", "--gateway", "192.168.1.1", "fail"}) nc.WaitWithDefaultTimeout() - Expect(nc.ExitCode()).ToNot(BeZero()) + Expect(nc).To(ExitWithError()) }) It("podman network create two networks with same name should fail", func() { @@ -205,13 +205,13 @@ var _ = Describe("Podman network create", func() { ncFail := podmanTest.Podman([]string{"network", "create", "samename"}) ncFail.WaitWithDefaultTimeout() - Expect(ncFail.ExitCode()).ToNot(BeZero()) + Expect(ncFail).To(ExitWithError()) }) It("podman network create with invalid network name", func() { nc := podmanTest.Podman([]string{"network", "create", "foo "}) nc.WaitWithDefaultTimeout() - Expect(nc.ExitCode()).ToNot(BeZero()) + Expect(nc).To(ExitWithError()) }) }) diff --git a/test/e2e/pause_test.go b/test/e2e/pause_test.go index c61131078..39e08e2e8 100644 --- a/test/e2e/pause_test.go +++ b/test/e2e/pause_test.go @@ -52,13 +52,13 @@ var _ = Describe("Podman pause", func() { It("podman pause bogus container", func() { session := podmanTest.Podman([]string{"pause", "foobar"}) session.WaitWithDefaultTimeout() - Expect(session.ExitCode()).To(Not(Equal(0))) + Expect(session).To(ExitWithError()) }) It("podman unpause bogus container", func() { session := podmanTest.Podman([]string{"unpause", "foobar"}) session.WaitWithDefaultTimeout() - Expect(session.ExitCode()).To(Not(Equal(0))) + Expect(session).To(ExitWithError()) }) It("podman pause a created container by id", func() { @@ -70,7 +70,7 @@ var _ = Describe("Podman pause", func() { result := podmanTest.Podman([]string{"pause", cid}) result.WaitWithDefaultTimeout() - Expect(result.ExitCode()).To(Not(Equal(0))) + Expect(result).To(ExitWithError()) Expect(podmanTest.NumberOfContainersRunning()).To(Equal(0)) Expect(podmanTest.GetContainerStatus()).To(ContainSubstring(createdState)) }) diff --git a/test/e2e/play_kube_test.go b/test/e2e/play_kube_test.go index 5d59f0eb0..7069e049d 100644 --- a/test/e2e/play_kube_test.go +++ b/test/e2e/play_kube_test.go @@ -23,7 +23,7 @@ metadata: spec: hostname: {{ .Hostname }} containers: -{{ with .Containers }} +{{ with .Ctrs }} {{ range . }} - command: {{ range .Cmd }} @@ -67,47 +67,128 @@ spec: status: {} ` -type Pod struct { - Name string - Hostname string - Containers []Container -} - -type Container struct { - Cmd []string - Image string - Name string - SecurityContext bool - Caps bool - CapAdd []string - CapDrop []string -} +var ( + defaultCtrName = "testCtr" + defaultCtrCmd = []string{"top"} + defaultCtrImage = ALPINE + defaultPodName = "testPod" +) -func generateKubeYaml(name string, hostname string, ctrs []Container, fileName string) error { +func generateKubeYaml(pod *Pod, fileName string) error { f, err := os.Create(fileName) if err != nil { return err } defer f.Close() - testPod := Pod{name, hostname, ctrs} t, err := template.New("pod").Parse(yamlTemplate) if err != nil { return err } - if err := t.Execute(f, testPod); err != nil { + if err := t.Execute(f, pod); err != nil { return err } return nil } +// Pod describes the options a kube yaml can be configured at pod level +type Pod struct { + Name string + Hostname string + Ctrs []*Ctr +} + +// getPod takes a list of podOptions and returns a pod with sane defaults +// and the configured options +// if no containers are added, it will add the default container +func getPod(options ...podOption) *Pod { + p := Pod{defaultPodName, "", make([]*Ctr, 0)} + for _, option := range options { + option(&p) + } + if len(p.Ctrs) == 0 { + p.Ctrs = []*Ctr{getCtr()} + } + return &p +} + +type podOption func(*Pod) + +func withHostname(h string) podOption { + return func(pod *Pod) { + pod.Hostname = h + } +} + +func withCtr(c *Ctr) podOption { + return func(pod *Pod) { + pod.Ctrs = append(pod.Ctrs, c) + } +} + +// Ctr describes the options a kube yaml can be configured at container level +type Ctr struct { + Name string + Image string + Cmd []string + SecurityContext bool + Caps bool + CapAdd []string + CapDrop []string +} + +// getCtr takes a list of ctrOptions and returns a Ctr with sane defaults +// and the configured options +func getCtr(options ...ctrOption) *Ctr { + c := Ctr{defaultCtrName, defaultCtrImage, defaultCtrCmd, true, false, nil, nil} + for _, option := range options { + option(&c) + } + return &c +} + +type ctrOption func(*Ctr) + +func withCmd(cmd []string) ctrOption { + return func(c *Ctr) { + c.Cmd = cmd + } +} + +func withImage(img string) ctrOption { + return func(c *Ctr) { + c.Image = img + } +} + +func withSecurityContext(sc bool) ctrOption { + return func(c *Ctr) { + c.SecurityContext = sc + } +} + +func withCapAdd(caps []string) ctrOption { + return func(c *Ctr) { + c.CapAdd = caps + c.Caps = true + } +} + +func withCapDrop(caps []string) ctrOption { + return func(c *Ctr) { + c.CapDrop = caps + c.Caps = true + } +} + var _ = Describe("Podman generate kube", func() { var ( tempdir string err error podmanTest *PodmanTestIntegration + kubeYaml string ) BeforeEach(func() { @@ -118,6 +199,8 @@ var _ = Describe("Podman generate kube", func() { podmanTest = PodmanTestCreate(tempdir) podmanTest.Setup() podmanTest.SeedImages() + + kubeYaml = filepath.Join(podmanTest.TempDir, "kube.yaml") }) AfterEach(func() { @@ -127,123 +210,98 @@ var _ = Describe("Podman generate kube", func() { }) It("podman play kube test correct command", func() { - ctrName := "testCtr" - ctrCmd := []string{"top"} - testContainer := Container{ctrCmd, ALPINE, ctrName, true, false, nil, nil} - tempFile := filepath.Join(podmanTest.TempDir, "kube.yaml") - - err := generateKubeYaml("test", "", []Container{testContainer}, tempFile) + err := generateKubeYaml(getPod(), kubeYaml) Expect(err).To(BeNil()) - kube := podmanTest.Podman([]string{"play", "kube", tempFile}) + kube := podmanTest.Podman([]string{"play", "kube", kubeYaml}) kube.WaitWithDefaultTimeout() Expect(kube.ExitCode()).To(Equal(0)) - inspect := podmanTest.Podman([]string{"inspect", ctrName}) + inspect := podmanTest.Podman([]string{"inspect", defaultCtrName}) inspect.WaitWithDefaultTimeout() Expect(inspect.ExitCode()).To(Equal(0)) - Expect(inspect.OutputToString()).To(ContainSubstring(ctrCmd[0])) + Expect(inspect.OutputToString()).To(ContainSubstring(defaultCtrCmd[0])) }) It("podman play kube test correct output", func() { - ctrName := "testCtr" - ctrCmd := []string{"echo", "hello"} - testContainer := Container{ctrCmd, ALPINE, ctrName, true, false, nil, nil} - tempFile := filepath.Join(podmanTest.TempDir, "kube.yaml") + p := getPod(withCtr(getCtr(withCmd([]string{"echo", "hello"})))) - err := generateKubeYaml("test", "", []Container{testContainer}, tempFile) + err := generateKubeYaml(p, kubeYaml) Expect(err).To(BeNil()) - kube := podmanTest.Podman([]string{"play", "kube", tempFile}) + kube := podmanTest.Podman([]string{"play", "kube", kubeYaml}) kube.WaitWithDefaultTimeout() Expect(kube.ExitCode()).To(Equal(0)) - logs := podmanTest.Podman([]string{"logs", ctrName}) + logs := podmanTest.Podman([]string{"logs", defaultCtrName}) logs.WaitWithDefaultTimeout() Expect(logs.ExitCode()).To(Equal(0)) Expect(logs.OutputToString()).To(ContainSubstring("hello")) - inspect := podmanTest.Podman([]string{"inspect", ctrName, "--format", "'{{ .Config.Cmd }}'"}) + inspect := podmanTest.Podman([]string{"inspect", defaultCtrName, "--format", "'{{ .Config.Cmd }}'"}) inspect.WaitWithDefaultTimeout() Expect(inspect.ExitCode()).To(Equal(0)) Expect(inspect.OutputToString()).To(ContainSubstring("hello")) }) It("podman play kube test hostname", func() { - podName := "test" - ctrName := "testCtr" - ctrCmd := []string{"top"} - testContainer := Container{ctrCmd, ALPINE, ctrName, true, false, nil, nil} - tempFile := filepath.Join(podmanTest.TempDir, "kube.yaml") - - err := generateKubeYaml(podName, "", []Container{testContainer}, tempFile) + err := generateKubeYaml(getPod(), kubeYaml) Expect(err).To(BeNil()) - kube := podmanTest.Podman([]string{"play", "kube", tempFile}) + kube := podmanTest.Podman([]string{"play", "kube", kubeYaml}) kube.WaitWithDefaultTimeout() Expect(kube.ExitCode()).To(Equal(0)) - inspect := podmanTest.Podman([]string{"inspect", ctrName, "--format", "{{ .Config.Hostname }}"}) + inspect := podmanTest.Podman([]string{"inspect", defaultCtrName, "--format", "{{ .Config.Hostname }}"}) inspect.WaitWithDefaultTimeout() Expect(inspect.ExitCode()).To(Equal(0)) - Expect(inspect.OutputToString()).To(Equal(podName)) + Expect(inspect.OutputToString()).To(Equal(defaultPodName)) }) It("podman play kube test with customized hostname", func() { hostname := "myhostname" - ctrName := "testCtr" - ctrCmd := []string{"top"} - testContainer := Container{ctrCmd, ALPINE, ctrName, true, false, nil, nil} - tempFile := filepath.Join(podmanTest.TempDir, "kube.yaml") - - err := generateKubeYaml("test", hostname, []Container{testContainer}, tempFile) + err := generateKubeYaml(getPod(withHostname(hostname)), kubeYaml) Expect(err).To(BeNil()) - kube := podmanTest.Podman([]string{"play", "kube", tempFile}) + kube := podmanTest.Podman([]string{"play", "kube", kubeYaml}) kube.WaitWithDefaultTimeout() Expect(kube.ExitCode()).To(Equal(0)) - inspect := podmanTest.Podman([]string{"inspect", ctrName, "--format", "{{ .Config.Hostname }}"}) + inspect := podmanTest.Podman([]string{"inspect", defaultCtrName, "--format", "{{ .Config.Hostname }}"}) inspect.WaitWithDefaultTimeout() Expect(inspect.ExitCode()).To(Equal(0)) Expect(inspect.OutputToString()).To(Equal(hostname)) }) It("podman play kube cap add", func() { - ctrName := "testCtr" - ctrCmd := []string{"cat", "/proc/self/status"} capAdd := "CAP_SYS_ADMIN" - testContainer := Container{ctrCmd, ALPINE, ctrName, true, true, []string{capAdd}, nil} - tempFile := filepath.Join(podmanTest.TempDir, "kube.yaml") + ctr := getCtr(withCapAdd([]string{capAdd}), withCmd([]string{"cat", "/proc/self/status"})) - err := generateKubeYaml("test", "", []Container{testContainer}, tempFile) + err := generateKubeYaml(getPod(withCtr(ctr)), kubeYaml) Expect(err).To(BeNil()) - kube := podmanTest.Podman([]string{"play", "kube", tempFile}) + kube := podmanTest.Podman([]string{"play", "kube", kubeYaml}) kube.WaitWithDefaultTimeout() Expect(kube.ExitCode()).To(Equal(0)) - inspect := podmanTest.Podman([]string{"inspect", ctrName}) + inspect := podmanTest.Podman([]string{"inspect", defaultCtrName}) inspect.WaitWithDefaultTimeout() Expect(inspect.ExitCode()).To(Equal(0)) Expect(inspect.OutputToString()).To(ContainSubstring(capAdd)) }) - It("podman play kube cap add", func() { - ctrName := "testCtr" - ctrCmd := []string{"cat", "/proc/self/status"} - capDrop := "CAP_SYS_ADMIN" - testContainer := Container{ctrCmd, ALPINE, ctrName, true, true, []string{capDrop}, nil} - tempFile := filepath.Join(podmanTest.TempDir, "kube.yaml") + It("podman play kube cap drop", func() { + capDrop := "CAP_CHOWN" + ctr := getCtr(withCapDrop([]string{capDrop})) - err := generateKubeYaml("test", "", []Container{testContainer}, tempFile) + err := generateKubeYaml(getPod(withCtr(ctr)), kubeYaml) Expect(err).To(BeNil()) - kube := podmanTest.Podman([]string{"play", "kube", tempFile}) + kube := podmanTest.Podman([]string{"play", "kube", kubeYaml}) kube.WaitWithDefaultTimeout() Expect(kube.ExitCode()).To(Equal(0)) - inspect := podmanTest.Podman([]string{"inspect", ctrName}) + inspect := podmanTest.Podman([]string{"inspect", defaultCtrName}) inspect.WaitWithDefaultTimeout() Expect(inspect.ExitCode()).To(Equal(0)) Expect(inspect.OutputToString()).To(ContainSubstring(capDrop)) @@ -251,19 +309,14 @@ var _ = Describe("Podman generate kube", func() { It("podman play kube no security context", func() { // expect play kube to not fail if no security context is specified - ctrName := "testCtr" - ctrCmd := "ls" - testContainer := Container{[]string{ctrCmd}, ALPINE, ctrName, false, false, nil, nil} - tempFile := filepath.Join(podmanTest.TempDir, "kube.yaml") - - err := generateKubeYaml("test", "", []Container{testContainer}, tempFile) + err := generateKubeYaml(getPod(withCtr(getCtr(withSecurityContext(false)))), kubeYaml) Expect(err).To(BeNil()) - kube := podmanTest.Podman([]string{"play", "kube", tempFile}) + kube := podmanTest.Podman([]string{"play", "kube", kubeYaml}) kube.WaitWithDefaultTimeout() Expect(kube.ExitCode()).To(Equal(0)) - inspect := podmanTest.Podman([]string{"inspect", ctrName}) + inspect := podmanTest.Podman([]string{"inspect", defaultCtrName}) inspect.WaitWithDefaultTimeout() Expect(inspect.ExitCode()).To(Equal(0)) }) diff --git a/test/e2e/pod_infra_container_test.go b/test/e2e/pod_infra_container_test.go index 3897aa851..c8072f308 100644 --- a/test/e2e/pod_infra_container_test.go +++ b/test/e2e/pod_infra_container_test.go @@ -122,7 +122,7 @@ var _ = Describe("Podman pod create", func() { session = podmanTest.Podman([]string{"run", fedoraMinimal, "curl", "localhost"}) session.WaitWithDefaultTimeout() - Expect(session.ExitCode()).To(Not(Equal(0))) + Expect(session).To(ExitWithError()) }) It("podman pod correctly sets up IPCNS", func() { @@ -218,7 +218,7 @@ var _ = Describe("Podman pod create", func() { session = podmanTest.Podman([]string{"run", "--pod", podID, "--network", "bridge", nginx, "curl", "localhost"}) session.WaitWithDefaultTimeout() - Expect(session.ExitCode()).To(Not(Equal(0))) + Expect(session).To(ExitWithError()) }) It("podman pod container can override pod pid NS", func() { @@ -309,7 +309,7 @@ var _ = Describe("Podman pod create", func() { session = podmanTest.Podman([]string{"rm", infraID}) session.WaitWithDefaultTimeout() - Expect(session.ExitCode()).To(Not(Equal(0))) + Expect(session).To(ExitWithError()) session = podmanTest.Podman([]string{"pod", "rm", podID}) session.WaitWithDefaultTimeout() diff --git a/test/e2e/pod_inspect_test.go b/test/e2e/pod_inspect_test.go index 488dd1685..49c647528 100644 --- a/test/e2e/pod_inspect_test.go +++ b/test/e2e/pod_inspect_test.go @@ -35,7 +35,7 @@ var _ = Describe("Podman pod inspect", func() { It("podman inspect bogus pod", func() { session := podmanTest.Podman([]string{"pod", "inspect", "foobar"}) session.WaitWithDefaultTimeout() - Expect(session.ExitCode()).To(Not(Equal(0))) + Expect(session).Should(ExitWithError()) }) It("podman inspect a pod", func() { diff --git a/test/e2e/pod_kill_test.go b/test/e2e/pod_kill_test.go index 7cf67bbfc..a3efec46c 100644 --- a/test/e2e/pod_kill_test.go +++ b/test/e2e/pod_kill_test.go @@ -36,7 +36,7 @@ var _ = Describe("Podman pod kill", func() { It("podman pod kill bogus", func() { session := podmanTest.Podman([]string{"pod", "kill", "foobar"}) session.WaitWithDefaultTimeout() - Expect(session.ExitCode()).To(Not(Equal(0))) + Expect(session).To(ExitWithError()) }) It("podman pod kill a pod by id", func() { diff --git a/test/e2e/pod_pause_test.go b/test/e2e/pod_pause_test.go index 619ee6f12..73707926d 100644 --- a/test/e2e/pod_pause_test.go +++ b/test/e2e/pod_pause_test.go @@ -38,13 +38,13 @@ var _ = Describe("Podman pod pause", func() { It("podman pod pause bogus pod", func() { session := podmanTest.Podman([]string{"pod", "pause", "foobar"}) session.WaitWithDefaultTimeout() - Expect(session.ExitCode()).To(Not(Equal(0))) + Expect(session).To(ExitWithError()) }) It("podman unpause bogus pod", func() { session := podmanTest.Podman([]string{"pod", "unpause", "foobar"}) session.WaitWithDefaultTimeout() - Expect(session.ExitCode()).To(Not(Equal(0))) + Expect(session).To(ExitWithError()) }) It("podman pod pause a created pod by id", func() { diff --git a/test/e2e/pod_ps_test.go b/test/e2e/pod_ps_test.go index 6d5873caa..aa07be55c 100644 --- a/test/e2e/pod_ps_test.go +++ b/test/e2e/pod_ps_test.go @@ -107,7 +107,7 @@ var _ = Describe("Podman ps", func() { It("podman pod ps mutually exclusive flags", func() { session := podmanTest.Podman([]string{"pod", "ps", "-q", "--format", "{{.ID}}"}) session.WaitWithDefaultTimeout() - Expect(session.ExitCode()).To(Not(Equal(0))) + Expect(session).To(ExitWithError()) }) diff --git a/test/e2e/pod_rm_test.go b/test/e2e/pod_rm_test.go index f0689f152..de68e885a 100644 --- a/test/e2e/pod_rm_test.go +++ b/test/e2e/pod_rm_test.go @@ -135,7 +135,7 @@ var _ = Describe("Podman pod rm", func() { fmt.Printf("Removing all empty pods\n") result := podmanTest.Podman([]string{"pod", "rm", "-a"}) result.WaitWithDefaultTimeout() - Expect(result.ExitCode()).To(Not(Equal(0))) + Expect(result).To(ExitWithError()) foundExpectedError, _ := result.ErrorGrepString("contains containers and cannot be removed") Expect(foundExpectedError).To(Equal(true)) diff --git a/test/e2e/pod_stats_test.go b/test/e2e/pod_stats_test.go index 01176f97c..4d573a2c7 100644 --- a/test/e2e/pod_stats_test.go +++ b/test/e2e/pod_stats_test.go @@ -169,7 +169,7 @@ var _ = Describe("Podman pod stats", func() { Expect(session.ExitCode()).To(Equal(0)) stats := podmanTest.Podman([]string{"pod", "stats", "-a", "--no-reset", "--no-stream", "--format", "\"table {{.ID}} \""}) stats.WaitWithDefaultTimeout() - Expect(stats.ExitCode()).ToNot(Equal(0)) + Expect(stats).To(ExitWithError()) }) }) diff --git a/test/e2e/port_test.go b/test/e2e/port_test.go index 53fc33a01..5bb86d558 100644 --- a/test/e2e/port_test.go +++ b/test/e2e/port_test.go @@ -39,13 +39,13 @@ var _ = Describe("Podman port", func() { It("podman port all and latest", func() { result := podmanTest.Podman([]string{"port", "-a", "-l"}) result.WaitWithDefaultTimeout() - Expect(result.ExitCode()).ToNot(Equal(0)) + Expect(result).To(ExitWithError()) }) It("podman port all and extra", func() { result := podmanTest.Podman([]string{"port", "-a", "foobar"}) result.WaitWithDefaultTimeout() - Expect(result.ExitCode()).ToNot(Equal(0)) + Expect(result).To(ExitWithError()) }) It("podman port -l nginx", func() { diff --git a/test/e2e/ps_test.go b/test/e2e/ps_test.go index efcf44f76..a436d4f09 100644 --- a/test/e2e/ps_test.go +++ b/test/e2e/ps_test.go @@ -80,8 +80,6 @@ var _ = Describe("Podman ps", func() { }) It("podman ps size flag", func() { - SkipIfRootless() - _, ec, _ := podmanTest.RunLsContainer("") Expect(ec).To(Equal(0)) @@ -151,6 +149,13 @@ var _ = Describe("Podman ps", func() { Expect(len(result.OutputToStringArray())).Should(BeNumerically(">", 0)) }) + It("podman ps with no containers is valid json format", func() { + result := podmanTest.Podman([]string{"ps", "--format", "json"}) + result.WaitWithDefaultTimeout() + Expect(result.ExitCode()).To(Equal(0)) + Expect(result.IsJSONOutputValid()).To(BeTrue()) + }) + It("podman ps namespace flag with json format", func() { _, ec, _ := podmanTest.RunLsContainer("test1") Expect(ec).To(Equal(0)) @@ -225,16 +230,14 @@ var _ = Describe("Podman ps", func() { It("podman ps mutually exclusive flags", func() { session := podmanTest.Podman([]string{"ps", "-aqs"}) session.WaitWithDefaultTimeout() - Expect(session.ExitCode()).To(Not(Equal(0))) + Expect(session).To(ExitWithError()) session = podmanTest.Podman([]string{"ps", "-a", "--ns", "-s"}) session.WaitWithDefaultTimeout() - Expect(session.ExitCode()).To(Not(Equal(0))) + Expect(session).To(ExitWithError()) }) It("podman --sort by size", func() { - SkipIfRootless() - session := podmanTest.Podman([]string{"create", "busybox", "ls"}) session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) diff --git a/test/e2e/pull_test.go b/test/e2e/pull_test.go index 68fcaf133..537084220 100644 --- a/test/e2e/pull_test.go +++ b/test/e2e/pull_test.go @@ -40,7 +40,7 @@ var _ = Describe("Podman pull", func() { It("podman pull from docker a not existing image", func() { session := podmanTest.PodmanNoCache([]string{"pull", "ibetthisdoesntexistthere:foo"}) session.WaitWithDefaultTimeout() - Expect(session.ExitCode()).To(Not(Equal(0))) + Expect(session).To(ExitWithError()) }) It("podman pull from docker with tag", func() { @@ -96,7 +96,7 @@ var _ = Describe("Podman pull", func() { It("podman pull bogus image", func() { session := podmanTest.PodmanNoCache([]string{"pull", "umohnani/get-started"}) session.WaitWithDefaultTimeout() - Expect(session.ExitCode()).To(Not(Equal(0))) + Expect(session).To(ExitWithError()) }) It("podman pull from docker-archive", func() { diff --git a/test/e2e/push_test.go b/test/e2e/push_test.go index 4360eeece..50f0ca6d9 100644 --- a/test/e2e/push_test.go +++ b/test/e2e/push_test.go @@ -140,7 +140,7 @@ var _ = Describe("Podman push", func() { push := podmanTest.PodmanNoCache([]string{"push", "--creds=podmantest:test", ALPINE, "localhost:5000/tlstest"}) push.WaitWithDefaultTimeout() - Expect(push.ExitCode()).To(Not(Equal(0))) + Expect(push).To(ExitWithError()) push = podmanTest.PodmanNoCache([]string{"push", "--creds=podmantest:test", "--tls-verify=false", ALPINE, "localhost:5000/tlstest"}) push.WaitWithDefaultTimeout() @@ -151,11 +151,11 @@ var _ = Describe("Podman push", func() { push = podmanTest.PodmanNoCache([]string{"push", "--creds=podmantest:wrongpasswd", ALPINE, "localhost:5000/credstest"}) push.WaitWithDefaultTimeout() - Expect(push.ExitCode()).To(Not(Equal(0))) + Expect(push).To(ExitWithError()) push = podmanTest.PodmanNoCache([]string{"push", "--creds=podmantest:test", "--cert-dir=fakedir", ALPINE, "localhost:5000/certdirtest"}) push.WaitWithDefaultTimeout() - Expect(push.ExitCode()).To(Not(Equal(0))) + Expect(push).To(ExitWithError()) push = podmanTest.PodmanNoCache([]string{"push", "--creds=podmantest:test", ALPINE, "localhost:5000/defaultflags"}) push.WaitWithDefaultTimeout() diff --git a/test/e2e/rmi_test.go b/test/e2e/rmi_test.go index 506adee7e..80e877de1 100644 --- a/test/e2e/rmi_test.go +++ b/test/e2e/rmi_test.go @@ -102,7 +102,7 @@ var _ = Describe("Podman rmi", func() { // Trying without --force should fail result := podmanTest.PodmanNoCache([]string{"rmi", alpineId}) result.WaitWithDefaultTimeout() - Expect(result.ExitCode()).ToNot(Equal(0)) + Expect(result).To(ExitWithError()) // With --force it should work resultForce := podmanTest.PodmanNoCache([]string{"rmi", "-f", alpineId}) diff --git a/test/e2e/run_cpu_test.go b/test/e2e/run_cpu_test.go index 42f17985c..29ceb4e67 100644 --- a/test/e2e/run_cpu_test.go +++ b/test/e2e/run_cpu_test.go @@ -164,12 +164,12 @@ var _ = Describe("Podman run cpu", func() { It("podman run cpus and cpu-period", func() { result := podmanTest.Podman([]string{"run", "--rm", "--cpu-period=5000", "--cpus=0.5", ALPINE, "ls"}) result.WaitWithDefaultTimeout() - Expect(result.ExitCode()).To(Not(Equal(0))) + Expect(result).To(ExitWithError()) }) It("podman run cpus and cpu-quota", func() { result := podmanTest.Podman([]string{"run", "--rm", "--cpu-quota=5000", "--cpus=0.5", ALPINE, "ls"}) result.WaitWithDefaultTimeout() - Expect(result.ExitCode()).To(Not(Equal(0))) + Expect(result).To(ExitWithError()) }) }) diff --git a/test/e2e/run_device_test.go b/test/e2e/run_device_test.go index d3b4b0e32..eae3f574c 100644 --- a/test/e2e/run_device_test.go +++ b/test/e2e/run_device_test.go @@ -37,7 +37,7 @@ var _ = Describe("Podman run device", func() { It("podman run bad device test", func() { session := podmanTest.Podman([]string{"run", "-q", "--device", "/dev/baddevice", ALPINE, "true"}) session.WaitWithDefaultTimeout() - Expect(session.ExitCode()).To(Not(Equal(0))) + Expect(session).To(ExitWithError()) }) It("podman run device test", func() { @@ -70,7 +70,7 @@ var _ = Describe("Podman run device", func() { It("podman run device rename and bad permission test", func() { session := podmanTest.Podman([]string{"run", "-q", "--security-opt", "label=disable", "--device", "/dev/kmsg:/dev/kmsg1:rd", ALPINE, "ls", "--color=never", "/dev/kmsg1"}) session.WaitWithDefaultTimeout() - Expect(session.ExitCode()).To(Not(Equal(0))) + Expect(session).To(ExitWithError()) }) It("podman run device host device and container device parameter are directories", func() { diff --git a/test/e2e/run_dns_test.go b/test/e2e/run_dns_test.go index dc0f4a8fb..02b9ff8d1 100644 --- a/test/e2e/run_dns_test.go +++ b/test/e2e/run_dns_test.go @@ -51,7 +51,7 @@ var _ = Describe("Podman run dns", func() { It("podman run add bad dns server", func() { session := podmanTest.Podman([]string{"run", "--dns=foobar", ALPINE, "ls"}) session.WaitWithDefaultTimeout() - Expect(session.ExitCode()).To(Not(Equal(0))) + Expect(session).To(ExitWithError()) }) It("podman run add dns server", func() { @@ -71,7 +71,7 @@ var _ = Describe("Podman run dns", func() { It("podman run add bad host", func() { session := podmanTest.Podman([]string{"run", "--add-host=foo:1.2", ALPINE, "ls"}) session.WaitWithDefaultTimeout() - Expect(session.ExitCode()).To(Not(Equal(0))) + Expect(session).To(ExitWithError()) }) It("podman run add host", func() { @@ -105,15 +105,15 @@ var _ = Describe("Podman run dns", func() { It("podman run mutually excludes --dns* and --network", func() { session := podmanTest.Podman([]string{"run", "--dns=1.2.3.4", "--network", "container:ALPINE", ALPINE}) session.WaitWithDefaultTimeout() - Expect(session.ExitCode()).To(Not(Equal(0))) + Expect(session).To(ExitWithError()) session = podmanTest.Podman([]string{"run", "--dns-opt=1.2.3.4", "--network", "container:ALPINE", ALPINE}) session.WaitWithDefaultTimeout() - Expect(session.ExitCode()).To(Not(Equal(0))) + Expect(session).To(ExitWithError()) session = podmanTest.Podman([]string{"run", "--dns-search=foobar.com", "--network", "none", ALPINE}) session.WaitWithDefaultTimeout() - Expect(session.ExitCode()).To(Not(Equal(0))) + Expect(session).To(ExitWithError()) session = podmanTest.Podman([]string{"run", "--dns=1.2.3.4", "--network", "host", ALPINE}) session.WaitWithDefaultTimeout() diff --git a/test/e2e/run_networking_test.go b/test/e2e/run_networking_test.go index 31291d373..ec12f709a 100644 --- a/test/e2e/run_networking_test.go +++ b/test/e2e/run_networking_test.go @@ -74,7 +74,7 @@ var _ = Describe("Podman run networking", func() { Expect(results.OutputToString()).To(ContainSubstring("8000")) ncBusy := SystemExec("nc", []string{"-l", "-p", "80"}) - Expect(ncBusy.ExitCode()).ToNot(Equal(0)) + Expect(ncBusy).To(ExitWithError()) }) It("podman run network expose ports in image metadata", func() { @@ -229,7 +229,7 @@ var _ = Describe("Podman run networking", func() { It("podman run network in bogus user created network namespace", func() { session := podmanTest.Podman([]string{"run", "-dt", "--net", "ns:/run/netns/xxy", ALPINE, "wget", "www.podman.io"}) session.Wait(90) - Expect(session.ExitCode()).To(Not(Equal(0))) + Expect(session).To(ExitWithError()) Expect(session.ErrorToString()).To(ContainSubstring("stat /run/netns/xxy: no such file or directory")) }) }) diff --git a/test/e2e/run_ns_test.go b/test/e2e/run_ns_test.go index e3e86fc66..c8ba68efc 100644 --- a/test/e2e/run_ns_test.go +++ b/test/e2e/run_ns_test.go @@ -48,7 +48,7 @@ var _ = Describe("Podman run ns", func() { session = podmanTest.Podman([]string{"run", "--pid=badpid", fedoraMinimal, "bash", "-c", "echo $$"}) session.WaitWithDefaultTimeout() - Expect(session.ExitCode()).To(Not(Equal(0))) + Expect(session).To(ExitWithError()) }) It("podman run --cgroup private test", func() { @@ -102,6 +102,6 @@ var _ = Describe("Podman run ns", func() { It("podman run bad ipc pid test", func() { session := podmanTest.Podman([]string{"run", "--ipc=badpid", fedoraMinimal, "bash", "-c", "echo $$"}) session.WaitWithDefaultTimeout() - Expect(session.ExitCode()).ToNot(Equal(0)) + Expect(session).To(ExitWithError()) }) }) diff --git a/test/e2e/run_staticip_test.go b/test/e2e/run_staticip_test.go index 7a877ebdc..5b4842fea 100644 --- a/test/e2e/run_staticip_test.go +++ b/test/e2e/run_staticip_test.go @@ -40,19 +40,19 @@ var _ = Describe("Podman run with --ip flag", func() { It("Podman run --ip with garbage address", func() { result := podmanTest.Podman([]string{"run", "-ti", "--ip", "114232346", ALPINE, "ls"}) result.WaitWithDefaultTimeout() - Expect(result.ExitCode()).ToNot(Equal(0)) + Expect(result).To(ExitWithError()) }) It("Podman run --ip with v6 address", func() { result := podmanTest.Podman([]string{"run", "-ti", "--ip", "2001:db8:bad:beef::1", ALPINE, "ls"}) result.WaitWithDefaultTimeout() - Expect(result.ExitCode()).ToNot(Equal(0)) + Expect(result).To(ExitWithError()) }) It("Podman run --ip with non-allocatable IP", func() { result := podmanTest.Podman([]string{"run", "-ti", "--ip", "203.0.113.124", ALPINE, "ls"}) result.WaitWithDefaultTimeout() - Expect(result.ExitCode()).ToNot(Equal(0)) + Expect(result).To(ExitWithError()) }) It("Podman run with specified static IP has correct IP", func() { @@ -70,6 +70,6 @@ var _ = Describe("Podman run with --ip flag", func() { Expect(result.ExitCode()).To(Equal(0)) result = podmanTest.Podman([]string{"run", "-ti", "--ip", ip, ALPINE, "ip", "addr"}) result.WaitWithDefaultTimeout() - Expect(result.ExitCode()).ToNot(Equal(0)) + Expect(result).To(ExitWithError()) }) }) diff --git a/test/e2e/run_test.go b/test/e2e/run_test.go index 1e6f1d97d..874aa498e 100644 --- a/test/e2e/run_test.go +++ b/test/e2e/run_test.go @@ -170,7 +170,7 @@ var _ = Describe("Podman run", func() { session := podmanTest.Podman([]string{"run", "-it", "--security-opt", strings.Join([]string{"seccomp=", jsonFile}, ""), ALPINE, "pwd"}) session.WaitWithDefaultTimeout() - Expect(session.ExitCode()).To(Not(Equal(0))) + Expect(session).To(ExitWithError()) match, _ := session.GrepString("Operation not permitted") Expect(match).Should(BeTrue()) }) @@ -730,11 +730,11 @@ USER mail` session := podmanTest.Podman([]string{"run", "--volume", ":/myvol1:z", ALPINE, "touch", "/myvol2/foo.txt"}) session.WaitWithDefaultTimeout() - Expect(session.ExitCode()).ToNot(Equal(0)) + Expect(session).To(ExitWithError()) Expect(session.ErrorToString()).To(ContainSubstring("directory cannot be empty")) session = podmanTest.Podman([]string{"run", "--volume", vol1 + ":", ALPINE, "touch", "/myvol2/foo.txt"}) session.WaitWithDefaultTimeout() - Expect(session.ExitCode()).ToNot(Equal(0)) + Expect(session).To(ExitWithError()) Expect(session.ErrorToString()).To(ContainSubstring("directory cannot be empty")) }) @@ -815,7 +815,7 @@ USER mail` It("podman run --rm failed container should delete itself", func() { session := podmanTest.Podman([]string{"run", "--rm", ALPINE, "foo"}) session.WaitWithDefaultTimeout() - Expect(session.ExitCode()).To(Not(Equal(0))) + Expect(session).To(ExitWithError()) numContainers := podmanTest.NumberOfContainers() Expect(numContainers).To(Equal(0)) @@ -824,7 +824,7 @@ USER mail` It("podman run failed container should NOT delete itself", func() { session := podmanTest.Podman([]string{"run", ALPINE, "foo"}) session.WaitWithDefaultTimeout() - Expect(session.ExitCode()).To(Not(Equal(0))) + Expect(session).To(ExitWithError()) numContainers := podmanTest.NumberOfContainers() Expect(numContainers).To(Equal(1)) @@ -840,28 +840,28 @@ USER mail` It("podman run with bad healthcheck retries", func() { session := podmanTest.Podman([]string{"run", "-dt", "--health-cmd", "[\"foo\"]", "--health-retries", "0", ALPINE, "top"}) session.Wait() - Expect(session.ExitCode()).ToNot(Equal(0)) + Expect(session).To(ExitWithError()) Expect(session.ErrorToString()).To(ContainSubstring("healthcheck-retries must be greater than 0")) }) It("podman run with bad healthcheck timeout", func() { session := podmanTest.Podman([]string{"run", "-dt", "--health-cmd", "[\"foo\"]", "--health-timeout", "0s", ALPINE, "top"}) session.WaitWithDefaultTimeout() - Expect(session.ExitCode()).ToNot(Equal(0)) + Expect(session).To(ExitWithError()) Expect(session.ErrorToString()).To(ContainSubstring("healthcheck-timeout must be at least 1 second")) }) It("podman run with bad healthcheck start-period", func() { session := podmanTest.Podman([]string{"run", "-dt", "--health-cmd", "[\"foo\"]", "--health-start-period", "-1s", ALPINE, "top"}) session.WaitWithDefaultTimeout() - Expect(session.ExitCode()).ToNot(Equal(0)) + Expect(session).To(ExitWithError()) Expect(session.ErrorToString()).To(ContainSubstring("healthcheck-start-period must be 0 seconds or greater")) }) It("podman run with --add-host and --no-hosts fails", func() { session := podmanTest.Podman([]string{"run", "-dt", "--add-host", "test1:127.0.0.1", "--no-hosts", ALPINE, "top"}) session.WaitWithDefaultTimeout() - Expect(session.ExitCode()).ToNot(Equal(0)) + Expect(session).To(ExitWithError()) }) It("podman run --http-proxy test", func() { @@ -990,6 +990,6 @@ USER mail` It("podman run with cgroups=garbage errors", func() { session := podmanTest.Podman([]string{"run", "-d", "--cgroups=garbage", ALPINE, "top"}) session.WaitWithDefaultTimeout() - Expect(session.ExitCode()).To(Not(Equal(0))) + Expect(session).To(ExitWithError()) }) }) diff --git a/test/e2e/run_volume_test.go b/test/e2e/run_volume_test.go index bc3a14b66..c96059787 100644 --- a/test/e2e/run_volume_test.go +++ b/test/e2e/run_volume_test.go @@ -155,7 +155,7 @@ var _ = Describe("Podman run with volumes", func() { session = podmanTest.Podman([]string{"run", "--rm", "--mount", fmt.Sprintf("type=bind,src=%s,target=/run/test,ro=true,rw=false", mountPath), ALPINE, "grep", "/run/test", "/proc/self/mountinfo"}) session.WaitWithDefaultTimeout() - Expect(session.ExitCode()).To(Not(Equal(0))) + Expect(session).To(ExitWithError()) }) It("podman run with volume flag and multiple named volumes", func() { @@ -191,7 +191,7 @@ var _ = Describe("Podman run with volumes", func() { It("podman run with noexec can't exec", func() { session := podmanTest.Podman([]string{"run", "--rm", "-v", "/bin:/hostbin:noexec", ALPINE, "/hostbin/ls", "/"}) session.WaitWithDefaultTimeout() - Expect(session.ExitCode()).To(Not(Equal(0))) + Expect(session).To(ExitWithError()) }) It("podman run with tmpfs named volume mounts and unmounts", func() { @@ -280,4 +280,88 @@ var _ = Describe("Podman run with volumes", func() { session2.WaitWithDefaultTimeout() Expect(session2.ExitCode()).To(Equal(0)) }) + + It("podman run with anonymous volume", func() { + list1 := podmanTest.Podman([]string{"volume", "list", "--quiet"}) + list1.WaitWithDefaultTimeout() + Expect(list1.ExitCode()).To(Equal(0)) + Expect(list1.OutputToString()).To(Equal("")) + + session := podmanTest.Podman([]string{"create", "-v", "/test", ALPINE, "top"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + + list2 := podmanTest.Podman([]string{"volume", "list", "--quiet"}) + list2.WaitWithDefaultTimeout() + Expect(list2.ExitCode()).To(Equal(0)) + arr := list2.OutputToStringArray() + Expect(len(arr)).To(Equal(1)) + Expect(arr[0]).To(Not(Equal(""))) + }) + + It("podman rm -v removes anonymous volume", func() { + list1 := podmanTest.Podman([]string{"volume", "list", "--quiet"}) + list1.WaitWithDefaultTimeout() + Expect(list1.ExitCode()).To(Equal(0)) + Expect(list1.OutputToString()).To(Equal("")) + + ctrName := "testctr" + session := podmanTest.Podman([]string{"create", "--name", ctrName, "-v", "/test", ALPINE, "top"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + + list2 := podmanTest.Podman([]string{"volume", "list", "--quiet"}) + list2.WaitWithDefaultTimeout() + Expect(list2.ExitCode()).To(Equal(0)) + arr := list2.OutputToStringArray() + Expect(len(arr)).To(Equal(1)) + Expect(arr[0]).To(Not(Equal(""))) + + remove := podmanTest.Podman([]string{"rm", "-v", ctrName}) + remove.WaitWithDefaultTimeout() + Expect(remove.ExitCode()).To(Equal(0)) + + list3 := podmanTest.Podman([]string{"volume", "list", "--quiet"}) + list3.WaitWithDefaultTimeout() + Expect(list3.ExitCode()).To(Equal(0)) + Expect(list3.OutputToString()).To(Equal("")) + }) + + It("podman rm -v retains named volume", func() { + list1 := podmanTest.Podman([]string{"volume", "list", "--quiet"}) + list1.WaitWithDefaultTimeout() + Expect(list1.ExitCode()).To(Equal(0)) + Expect(list1.OutputToString()).To(Equal("")) + + ctrName := "testctr" + volName := "testvol" + session := podmanTest.Podman([]string{"create", "--name", ctrName, "-v", fmt.Sprintf("%s:/test", volName), ALPINE, "top"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + + list2 := podmanTest.Podman([]string{"volume", "list", "--quiet"}) + list2.WaitWithDefaultTimeout() + Expect(list2.ExitCode()).To(Equal(0)) + arr := list2.OutputToStringArray() + Expect(len(arr)).To(Equal(1)) + Expect(arr[0]).To(Equal(volName)) + + remove := podmanTest.Podman([]string{"rm", "-v", ctrName}) + remove.WaitWithDefaultTimeout() + Expect(remove.ExitCode()).To(Equal(0)) + + list3 := podmanTest.Podman([]string{"volume", "list", "--quiet"}) + list3.WaitWithDefaultTimeout() + Expect(list3.ExitCode()).To(Equal(0)) + arr2 := list3.OutputToStringArray() + Expect(len(arr2)).To(Equal(1)) + Expect(arr2[0]).To(Equal(volName)) + }) + + It("podman run image volume is not noexec", func() { + session := podmanTest.Podman([]string{"run", "--rm", redis, "grep", "/data", "/proc/self/mountinfo"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + Expect(session.OutputToString()).To(Not(ContainSubstring("noexec"))) + }) }) diff --git a/test/e2e/runlabel_test.go b/test/e2e/runlabel_test.go index 4e2cb501e..52a011efb 100644 --- a/test/e2e/runlabel_test.go +++ b/test/e2e/runlabel_test.go @@ -75,12 +75,12 @@ var _ = Describe("podman container runlabel", func() { It("podman container runlabel bogus label should result in non-zero exit code", func() { result := podmanTest.Podman([]string{"container", "runlabel", "RUN", ALPINE}) result.WaitWithDefaultTimeout() - Expect(result.ExitCode()).ToNot(Equal(0)) + Expect(result).To(ExitWithError()) }) It("podman container runlabel bogus label in remote image should result in non-zero exit", func() { result := podmanTest.Podman([]string{"container", "runlabel", "RUN", "docker.io/library/ubuntu:latest"}) result.WaitWithDefaultTimeout() - Expect(result.ExitCode()).ToNot(Equal(0)) + Expect(result).To(ExitWithError()) }) diff --git a/test/e2e/save_test.go b/test/e2e/save_test.go index be1ede962..52dab923b 100644 --- a/test/e2e/save_test.go +++ b/test/e2e/save_test.go @@ -72,7 +72,7 @@ var _ = Describe("Podman save", func() { save := podmanTest.PodmanNoCache([]string{"save", "-o", outfile, "FOOBAR"}) save.WaitWithDefaultTimeout() - Expect(save.ExitCode()).To(Not(Equal(0))) + Expect(save).To(ExitWithError()) }) It("podman save to directory with oci format", func() { @@ -113,7 +113,7 @@ var _ = Describe("Podman save", func() { save := podmanTest.PodmanNoCache([]string{"save", "--compress", "--format", "docker-dir", "-o", outdir, ALPINE}) save.WaitWithDefaultTimeout() - Expect(save.ExitCode()).To(Not(Equal(0))) + Expect(save).To(ExitWithError()) }) }) diff --git a/test/e2e/start_test.go b/test/e2e/start_test.go index 06ab6aacd..da581f158 100644 --- a/test/e2e/start_test.go +++ b/test/e2e/start_test.go @@ -108,9 +108,9 @@ var _ = Describe("Podman start", func() { start := podmanTest.Podman([]string{"start", "-l"}) start.WaitWithDefaultTimeout() - Expect(start.ExitCode()).Should(BeNumerically(">", 0)) + Expect(start).To(ExitWithError()) - Eventually(podmanTest.NumberOfContainers(), defaultWaitTimeout).Should(BeZero()) + Eventually(podmanTest.NumberOfContainers(), defaultWaitTimeout, 3.0).Should(BeZero()) }) It("podman failed to start without --rm should NOT delete the container", func() { @@ -120,9 +120,9 @@ var _ = Describe("Podman start", func() { start := podmanTest.Podman([]string{"start", "-l"}) start.WaitWithDefaultTimeout() - Expect(start.ExitCode()).Should(BeNumerically(">", 0)) + Expect(start).To(ExitWithError()) - Eventually(podmanTest.NumberOfContainers(), defaultWaitTimeout).Should(Equal(1)) + Eventually(podmanTest.NumberOfContainers(), defaultWaitTimeout, 3.0).Should(Equal(1)) }) It("podman start --sig-proxy should not work without --attach", func() { diff --git a/test/e2e/stats_test.go b/test/e2e/stats_test.go index 4000ab33a..fbf7c9920 100644 --- a/test/e2e/stats_test.go +++ b/test/e2e/stats_test.go @@ -11,6 +11,8 @@ import ( . "github.com/onsi/gomega" ) +// TODO: we need to check the output. Currently, we only check the exit codes +// which is not enough. var _ = Describe("Podman stats", func() { var ( tempdir string @@ -61,6 +63,15 @@ var _ = Describe("Podman stats", func() { Expect(session.ExitCode()).To(Equal(0)) }) + It("podman stats on all running containers", func() { + session := podmanTest.RunTopContainer("") + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + session = podmanTest.Podman([]string{"stats", "--no-stream"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + }) + It("podman stats only output cids", func() { session := podmanTest.RunTopContainer("") session.WaitWithDefaultTimeout() diff --git a/test/e2e/systemd_test.go b/test/e2e/systemd_test.go index 02778d493..9ec48ba00 100644 --- a/test/e2e/systemd_test.go +++ b/test/e2e/systemd_test.go @@ -94,7 +94,7 @@ WantedBy=multi-user.target Expect(pull.ExitCode()).To(Equal(0)) ctrName := "testSystemd" - run := podmanTest.Podman([]string{"run", "--name", ctrName, "-t", "-i", "-d", systemdImage, "init"}) + run := podmanTest.Podman([]string{"run", "--name", ctrName, "-t", "-i", "-d", systemdImage, "/usr/sbin/init"}) run.WaitWithDefaultTimeout() Expect(run.ExitCode()).To(Equal(0)) ctrID := run.OutputToString() diff --git a/test/e2e/volume_create_test.go b/test/e2e/volume_create_test.go index 77e8abbd4..71023f9e2 100644 --- a/test/e2e/volume_create_test.go +++ b/test/e2e/volume_create_test.go @@ -1,6 +1,7 @@ package integration import ( + "fmt" "os" . "github.com/containers/libpod/test/utils" @@ -61,6 +62,25 @@ var _ = Describe("Podman volume create", func() { It("podman create volume with bad volume option", func() { session := podmanTest.Podman([]string{"volume", "create", "--opt", "badOpt=bad"}) session.WaitWithDefaultTimeout() - Expect(session.ExitCode()).To(Not(Equal(0))) + Expect(session).To(ExitWithError()) + }) + + It("podman create volume with o=uid,gid", func() { + volName := "testVol" + uid := "3000" + gid := "4000" + session := podmanTest.Podman([]string{"volume", "create", "--opt", fmt.Sprintf("o=uid=%s,gid=%s", uid, gid), volName}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + + inspectUID := podmanTest.Podman([]string{"volume", "inspect", "--format", "{{ .UID }}", volName}) + inspectUID.WaitWithDefaultTimeout() + Expect(inspectUID.ExitCode()).To(Equal(0)) + Expect(inspectUID.OutputToString()).To(Equal(uid)) + + inspectGID := podmanTest.Podman([]string{"volume", "inspect", "--format", "{{ .GID }}", volName}) + inspectGID.WaitWithDefaultTimeout() + Expect(inspectGID.ExitCode()).To(Equal(0)) + Expect(inspectGID.OutputToString()).To(Equal(gid)) }) }) diff --git a/test/e2e/volume_inspect_test.go b/test/e2e/volume_inspect_test.go index 0683c6bbf..5015e0535 100644 --- a/test/e2e/volume_inspect_test.go +++ b/test/e2e/volume_inspect_test.go @@ -2,6 +2,7 @@ package integration import ( "os" + "strings" . "github.com/containers/libpod/test/utils" . "github.com/onsi/ginkgo" @@ -74,4 +75,16 @@ var _ = Describe("Podman volume inspect", func() { Expect(session.OutputToStringArray()[0]).To(Equal(volName1)) Expect(session.OutputToStringArray()[1]).To(Equal(volName2)) }) + + It("inspect volume finds options", func() { + volName := "testvol" + session := podmanTest.Podman([]string{"volume", "create", "--opt", "type=tmpfs", volName}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + + inspect := podmanTest.Podman([]string{"volume", "inspect", volName}) + inspect.WaitWithDefaultTimeout() + Expect(inspect.ExitCode()).To(Equal(0)) + Expect(strings.Contains(inspect.OutputToString(), "tmpfs")).To(BeTrue()) + }) }) diff --git a/test/e2e/volume_rm_test.go b/test/e2e/volume_rm_test.go index 61cf9b893..6f2020828 100644 --- a/test/e2e/volume_rm_test.go +++ b/test/e2e/volume_rm_test.go @@ -56,7 +56,7 @@ var _ = Describe("Podman volume rm", func() { session = podmanTest.Podman([]string{"volume", "rm", "myvol"}) session.WaitWithDefaultTimeout() - Expect(session.ExitCode()).To(Not(Equal(0))) + Expect(session).To(ExitWithError()) Expect(session.ErrorToString()).To(ContainSubstring(cid)) session = podmanTest.Podman([]string{"volume", "rm", "-f", "myvol"}) @@ -116,7 +116,7 @@ var _ = Describe("Podman volume rm", func() { session = podmanTest.Podman([]string{"volume", "rm", "myv"}) session.WaitWithDefaultTimeout() - Expect(session.ExitCode()).To(Not(Equal(0))) + Expect(session).To(ExitWithError()) session = podmanTest.Podman([]string{"volume", "ls"}) session.WaitWithDefaultTimeout() diff --git a/test/system/005-info.bats b/test/system/005-info.bats index 5df6033fc..f229b0886 100644 --- a/test/system/005-info.bats +++ b/test/system/005-info.bats @@ -11,7 +11,7 @@ load helpers BuildahVersion: *[0-9.]\\\+ Conmon:\\\s\\\+package: Distribution: -OCIRuntime:\\\s\\\+package: +OCIRuntime:\\\s\\\+name: os: rootless: registries: diff --git a/test/system/015-help.bats b/test/system/015-help.bats index a987f04bc..fd4be87b2 100644 --- a/test/system/015-help.bats +++ b/test/system/015-help.bats @@ -55,9 +55,11 @@ function check_help() { # If usage has required arguments, try running without them if expr "$usage" : '.*\[flags\] [A-Z]' >/dev/null; then - dprint "podman $@ $cmd (without required args)" - run_podman 125 "$@" $cmd - is "$output" "Error:" + if [ "$cmd" != "stats"]; then + dprint "podman $@ $cmd (without required args)" + run_podman 125 "$@" $cmd + is "$output" "Error:" + fi fi count=$(expr $count + 1) diff --git a/test/system/075-exec.bats b/test/system/075-exec.bats index 11cb98269..472fdd1ab 100644 --- a/test/system/075-exec.bats +++ b/test/system/075-exec.bats @@ -29,4 +29,24 @@ load helpers run_podman rm $cid } +@test "podman exec - leak check" { + skip_if_remote + + # Start a container in the background then run exec command + # three times and make sure no any exec pid hash file leak + run_podman run -td $IMAGE /bin/sh + cid="$output" + + is "$(check_exec_pid)" "" "exec pid hash file indeed doesn't exist" + + for i in {1..3}; do + run_podman exec $cid /bin/true + done + + is "$(check_exec_pid)" "" "there isn't any exec pid hash file leak" + + run_podman stop --time 1 $cid + run_podman rm -f $cid +} + # vim: filetype=sh diff --git a/test/system/helpers.bash b/test/system/helpers.bash index 3d607f4bd..8c061d2c9 100644 --- a/test/system/helpers.bash +++ b/test/system/helpers.bash @@ -373,5 +373,19 @@ function random_string() { head /dev/urandom | tr -dc a-zA-Z0-9 | head -c$length } + +######################### +# find_exec_pid_files # Returns nothing or exec_pid hash files +######################### +# +# Return exec_pid hash files if exists, otherwise, return nothing +# +function find_exec_pid_files() { + run_podman info --format '{{.store.RunRoot}}' + local storage_path="$output" + if [ -d $storage_path ]; then + find $storage_path -type f -iname 'exec_pid_*' + fi +} # END miscellaneous tools ############################################################################### diff --git a/test/utils/matchers.go b/test/utils/matchers.go new file mode 100644 index 000000000..07c1232e7 --- /dev/null +++ b/test/utils/matchers.go @@ -0,0 +1,61 @@ +package utils + +import ( + "fmt" + + "github.com/onsi/gomega/format" + "github.com/onsi/gomega/gexec" +) + +// ExitWithError matches when assertion is > argument. Default 0 +// Modeled after the gomega Exit() matcher +func ExitWithError(optionalExitCode ...int) *exitMatcher { + exitCode := 0 + if len(optionalExitCode) > 0 { + exitCode = optionalExitCode[0] + } + return &exitMatcher{exitCode: exitCode} +} + +type exitMatcher struct { + exitCode int + actualExitCode int +} + +func (m *exitMatcher) Match(actual interface{}) (success bool, err error) { + exiter, ok := actual.(gexec.Exiter) + if !ok { + return false, fmt.Errorf("ExitWithError must be passed a gexec.Exiter (Missing method ExitCode() int) Got:\n#{format.Object(actual, 1)}") + } + + m.actualExitCode = exiter.ExitCode() + if m.actualExitCode == -1 { + return false, nil + } + return m.actualExitCode > m.exitCode, nil +} + +func (m *exitMatcher) FailureMessage(actual interface{}) (message string) { + if m.actualExitCode == -1 { + return "Expected process to exit. It did not." + } + return format.Message(m.actualExitCode, "to be greater than exit code:", m.exitCode) +} + +func (m *exitMatcher) NegatedFailureMessage(actual interface{}) (message string) { + if m.actualExitCode == -1 { + return "you really shouldn't be able to see this!" + } else { + if m.exitCode == -1 { + return "Expected process not to exit. It did." + } + return format.Message(m.actualExitCode, "is less than or equal to exit code:", m.exitCode) + } +} +func (m *exitMatcher) MatchMayChangeInTheFuture(actual interface{}) bool { + session, ok := actual.(*gexec.Session) + if ok { + return session.ExitCode() == -1 + } + return true +} diff --git a/test/utils/utils.go b/test/utils/utils.go index 7d373bd56..ad78d9792 100644 --- a/test/utils/utils.go +++ b/test/utils/utils.go @@ -268,7 +268,7 @@ func (s *PodmanSession) ErrorGrepString(term string) (bool, []string) { return matches, greps } -//LineInOutputStartsWith returns true if a line in a +// LineInOutputStartsWith returns true if a line in a // session output starts with the supplied string func (s *PodmanSession) LineInOuputStartsWith(term string) bool { for _, i := range s.OutputToStringArray() { @@ -279,7 +279,7 @@ func (s *PodmanSession) LineInOuputStartsWith(term string) bool { return false } -//LineInOutputContains returns true if a line in a +// LineInOutputContains returns true if a line in a // session output contains the supplied string func (s *PodmanSession) LineInOutputContains(term string) bool { for _, i := range s.OutputToStringArray() { @@ -290,7 +290,7 @@ func (s *PodmanSession) LineInOutputContains(term string) bool { return false } -//LineInOutputContainsTag returns true if a line in the +// LineInOutputContainsTag returns true if a line in the // session's output contains the repo-tag pair as returned // by podman-images(1). func (s *PodmanSession) LineInOutputContainsTag(repo, tag string) bool { @@ -348,7 +348,7 @@ func StringInSlice(s string, sl []string) bool { return false } -//tagOutPutToMap parses each string in imagesOutput and returns +// tagOutPutToMap parses each string in imagesOutput and returns // a map of repo:tag pairs. Notice, the first array item will // be skipped as it's considered to be the header. func tagOutputToMap(imagesOutput []string) map[string]string { @@ -371,7 +371,7 @@ func tagOutputToMap(imagesOutput []string) map[string]string { return m } -//GetHostDistributionInfo returns a struct with its distribution name and version +// GetHostDistributionInfo returns a struct with its distribution name and version func GetHostDistributionInfo() HostOS { f, err := os.Open(OSReleasePath) defer f.Close() @@ -415,7 +415,7 @@ func IsKernelNewerThan(version string) (bool, error) { } -//IsCommandAvaible check if command exist +// IsCommandAvaible check if command exist func IsCommandAvailable(command string) bool { check := exec.Command("bash", "-c", strings.Join([]string{"command -v", command}, " ")) err := check.Run() diff --git a/troubleshooting.md b/troubleshooting.md index 89c850356..c4e577645 100644 --- a/troubleshooting.md +++ b/troubleshooting.md @@ -142,7 +142,7 @@ 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. +If the entry in the Dockerfile looked like: RUN useradd -u 99999000 -g users newuser then add the `--no-log-init` parameter to change it to: `RUN useradd --no-log-init -u 99999000 -g users newuser`. This option tells useradd to stop creating the lastlog file. ### 7) Permission denied when running Podman commands @@ -410,3 +410,22 @@ You'll need to either: * configure the host to use cgroups v1 * update the image to use an updated version of systemd. + +### 17) rootless containers exit once the user session exits + + +You need to set lingering mode through loginctl to prevent user processes to be killed once +the user session completed. + +#### Symptom + +Once the user logs out all the containers exit. + +#### Solution +You'll need to either: + +* loginctl enable-linger $UID + +or as root if your user has not enough privileges. + +* sudo loginctl enable-linger $UID diff --git a/vendor/github.com/containers/psgo/.travis.yml b/vendor/github.com/containers/psgo/.travis.yml index 1399d24d3..c07bb140b 100644 --- a/vendor/github.com/containers/psgo/.travis.yml +++ b/vendor/github.com/containers/psgo/.travis.yml @@ -14,6 +14,6 @@ before_install: - sudo apt-get install -qq bats script: - - make validate || travis_terminate 1 - - make build || travis_terminate 1 - - make test || travis_terminate 1 + - make validate + - make build + - make test diff --git a/vendor/github.com/containers/psgo/Makefile b/vendor/github.com/containers/psgo/Makefile index 6050b9d5b..361820784 100644 --- a/vendor/github.com/containers/psgo/Makefile +++ b/vendor/github.com/containers/psgo/Makefile @@ -1,4 +1,5 @@ export GO111MODULE=off +export GOPROXY=https://proxy.golang.org SHELL= /bin/bash GO ?= go @@ -9,11 +10,17 @@ PROJECT := github.com/containers/psgo BATS_TESTS := *.bats GO_SRC=$(shell find . -name \*.go) +GO_BUILD=$(GO) build +# Go module support: set `-mod=vendor` to use the vendored sources +ifeq ($(shell go help mod >/dev/null 2>&1 && echo true), true) + GO_BUILD=GO111MODULE=on $(GO) build -mod=vendor +endif + all: validate build .PHONY: build build: $(GO_SRC) - $(GO) build -buildmode=pie -o $(BUILD_DIR)/$(NAME) $(PROJECT)/sample + $(GO_BUILD) -buildmode=pie -o $(BUILD_DIR)/$(NAME) $(PROJECT)/sample .PHONY: clean clean: diff --git a/vendor/github.com/containers/psgo/go.mod b/vendor/github.com/containers/psgo/go.mod index a194ec196..d9d54c5f7 100644 --- a/vendor/github.com/containers/psgo/go.mod +++ b/vendor/github.com/containers/psgo/go.mod @@ -6,6 +6,6 @@ require ( github.com/opencontainers/runc v0.0.0-20190425234816-dae70e8efea4 github.com/pkg/errors v0.0.0-20190227000051-27936f6d90f9 github.com/sirupsen/logrus v0.0.0-20190403091019-9b3cdde74fbe - github.com/stretchr/testify v1.3.0 + github.com/stretchr/testify v1.4.0 golang.org/x/sys v0.0.0-20190425145619-16072639606e ) diff --git a/vendor/github.com/containers/psgo/go.sum b/vendor/github.com/containers/psgo/go.sum index da6c750db..bbdd99730 100644 --- a/vendor/github.com/containers/psgo/go.sum +++ b/vendor/github.com/containers/psgo/go.sum @@ -16,8 +16,12 @@ github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190425145619-16072639606e h1:4ktJgTV34+N3qOZUc5fAaG3Pb11qzMm3PkAoTAgUZ2I= golang.org/x/sys v0.0.0-20190425145619-16072639606e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/vendor/github.com/containers/psgo/internal/cgroups/cgroups.go b/vendor/github.com/containers/psgo/internal/cgroups/cgroups.go new file mode 100644 index 000000000..eecaf87cb --- /dev/null +++ b/vendor/github.com/containers/psgo/internal/cgroups/cgroups.go @@ -0,0 +1,44 @@ +// Copyright 2019 psgo authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package cgroups + +import ( + "sync" + "syscall" +) + +const ( + CgroupRoot = "/sys/fs/cgroup" + cgroup2SuperMagic = 0x63677270 +) + +var ( + isUnifiedOnce sync.Once + isUnified bool + isUnifiedErr error +) + +// IsCgroup2UnifiedMode returns whether we are running in cgroup or cgroupv2 mode. +func IsCgroup2UnifiedMode() (bool, error) { + isUnifiedOnce.Do(func() { + var st syscall.Statfs_t + if err := syscall.Statfs(CgroupRoot, &st); err != nil { + isUnified, isUnifiedErr = false, err + } else { + isUnified, isUnifiedErr = st.Type == cgroup2SuperMagic, nil + } + }) + return isUnified, isUnifiedErr +} diff --git a/vendor/github.com/containers/psgo/internal/proc/pids.go b/vendor/github.com/containers/psgo/internal/proc/pids.go index ff4887364..69e8befc1 100644 --- a/vendor/github.com/containers/psgo/internal/proc/pids.go +++ b/vendor/github.com/containers/psgo/internal/proc/pids.go @@ -1,4 +1,4 @@ -// Copyright 2018 psgo authors +// Copyright 2018-2019 psgo authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -18,8 +18,11 @@ import ( "bufio" "fmt" "os" + "path/filepath" "strconv" "strings" + + "github.com/containers/psgo/internal/cgroups" ) // GetPIDs extracts and returns all PIDs from /proc. @@ -49,45 +52,98 @@ func GetPIDs() ([]string, error) { return pids, nil } -// pidCgroupPath returns the path to the pid's pids cgroup. -func pidCgroupPath(pid string) (string, error) { +// GetPIDsFromCgroup returns a strings slice of all pids listesd in pid's pids +// cgroup. It automatically detects if we're running in unified mode or not. +func GetPIDsFromCgroup(pid string) ([]string, error) { + unified, err := cgroups.IsCgroup2UnifiedMode() + if err != nil { + return nil, err + } + if unified { + return getPIDsFromCgroupV2(pid) + } + return getPIDsFromCgroupV1(pid) +} + +// getPIDsFromCgroupV1 returns a strings slice of all pids listesd in pid's pids +// cgroup. +func getPIDsFromCgroupV1(pid string) ([]string, error) { + // First, find the corresponding path to the PID cgroup. f, err := os.Open(fmt.Sprintf("/proc/%s/cgroup", pid)) if err != nil { - return "", err + return nil, err } defer f.Close() scanner := bufio.NewScanner(f) + cgroupPath := "" for scanner.Scan() { fields := strings.Split(scanner.Text(), ":") if len(fields) != 3 { continue } if fields[1] == "pids" { - return fmt.Sprintf("/sys/fs/cgroup/pids/%s/cgroup.procs", fields[2]), nil + cgroupPath = fmt.Sprintf("/sys/fs/cgroup/pids/%s/cgroup.procs", fields[2]) } } - return "", fmt.Errorf("couldn't find pids group for PID %s", pid) + + if cgroupPath == "" { + return nil, fmt.Errorf("couldn't find v1 pids group for PID %s", pid) + } + + // Second, extract the PIDs inside the cgroup. + f, err = os.Open(cgroupPath) + if err != nil { + return nil, err + } + defer f.Close() + + pids := []string{} + scanner = bufio.NewScanner(f) + for scanner.Scan() { + pids = append(pids, scanner.Text()) + } + + return pids, nil } -// GetPIDsFromCgroup returns a strings slice of all pids listesd in pid's pids +// getPIDsFromCgroupV2 returns a strings slice of all pids listesd in pid's pids // cgroup. -func GetPIDsFromCgroup(pid string) ([]string, error) { - cgroupPath, err := pidCgroupPath(pid) +func getPIDsFromCgroupV2(pid string) ([]string, error) { + // First, find the corresponding path to the PID cgroup. + f, err := os.Open(fmt.Sprintf("/proc/%s/cgroup", pid)) if err != nil { return nil, err } + defer f.Close() + + scanner := bufio.NewScanner(f) + cgroupSlice := "" + for scanner.Scan() { + fields := strings.Split(scanner.Text(), ":") + if len(fields) != 3 { + continue + } + cgroupSlice = fields[2] + break + } + + if cgroupSlice == "" { + return nil, fmt.Errorf("couldn't find v2 pids group for PID %s", pid) + } - f, err := os.Open(cgroupPath) + // Second, extract the PIDs inside the cgroup. + f, err = os.Open(filepath.Join(cgroups.CgroupRoot, cgroupSlice, "cgroup.procs")) if err != nil { return nil, err } defer f.Close() pids := []string{} - scanner := bufio.NewScanner(f) + scanner = bufio.NewScanner(f) for scanner.Scan() { pids = append(pids, scanner.Text()) } + return pids, nil } diff --git a/vendor/github.com/containers/storage/.cirrus.yml b/vendor/github.com/containers/storage/.cirrus.yml index a823bab55..619e077d0 100644 --- a/vendor/github.com/containers/storage/.cirrus.yml +++ b/vendor/github.com/containers/storage/.cirrus.yml @@ -82,7 +82,7 @@ lint_task: build_script: | echo "deb http://deb.debian.org/debian stretch-backports main" > /etc/apt/sources.list.d/backports.list apt-get update - apt-get install -y libbtrfs-dev libostree-dev libdevmapper-dev + apt-get install -y libbtrfs-dev libdevmapper-dev test_script: make lint # Update metadata on VM images referenced by this repository state diff --git a/vendor/github.com/containers/storage/.golangci.yml b/vendor/github.com/containers/storage/.golangci.yml index bd8ccfcb1..ec4ebb187 100644 --- a/vendor/github.com/containers/storage/.golangci.yml +++ b/vendor/github.com/containers/storage/.golangci.yml @@ -1,7 +1,5 @@ --- run: - build-tags: - - ostree concurrency: 6 deadline: 5m linters: diff --git a/vendor/github.com/containers/storage/Makefile b/vendor/github.com/containers/storage/Makefile index fa0fddaeb..90e5ca499 100644 --- a/vendor/github.com/containers/storage/Makefile +++ b/vendor/github.com/containers/storage/Makefile @@ -29,7 +29,7 @@ GIT_BRANCH := $(shell git rev-parse --abbrev-ref HEAD 2>/dev/null) GIT_BRANCH_CLEAN := $(shell echo $(GIT_BRANCH) | sed -e "s/[^[:alnum:]]/-/g") EPOCH_TEST_COMMIT := 0418ebf59f9e1f564831c0ba9378b7f8e40a1c73 NATIVETAGS := -AUTOTAGS := $(shell ./hack/btrfs_tag.sh) $(shell ./hack/libdm_tag.sh) $(shell ./hack/ostree_tag.sh) +AUTOTAGS := $(shell ./hack/btrfs_tag.sh) $(shell ./hack/libdm_tag.sh) BUILDFLAGS := -tags "$(AUTOTAGS) $(TAGS)" $(FLAGS) GO ?= go diff --git a/vendor/github.com/containers/storage/VERSION b/vendor/github.com/containers/storage/VERSION index 80138e714..43ded9062 100644 --- a/vendor/github.com/containers/storage/VERSION +++ b/vendor/github.com/containers/storage/VERSION @@ -1 +1 @@ -1.13.4 +1.13.5 diff --git a/vendor/github.com/containers/storage/drivers/overlay/overlay.go b/vendor/github.com/containers/storage/drivers/overlay/overlay.go index 032e5b28a..97222fe7a 100644 --- a/vendor/github.com/containers/storage/drivers/overlay/overlay.go +++ b/vendor/github.com/containers/storage/drivers/overlay/overlay.go @@ -26,7 +26,6 @@ import ( "github.com/containers/storage/pkg/idtools" "github.com/containers/storage/pkg/locker" "github.com/containers/storage/pkg/mount" - "github.com/containers/storage/pkg/ostree" "github.com/containers/storage/pkg/parsers" "github.com/containers/storage/pkg/system" units "github.com/docker/go-units" @@ -88,7 +87,6 @@ type overlayOptions struct { imageStores []string quota quota.Quota mountProgram string - ostreeRepo string skipMountHome bool mountOptions string ignoreChownErrors bool @@ -108,7 +106,6 @@ type Driver struct { supportsDType bool usingMetacopy bool locker *locker.Locker - convert map[string]bool } var ( @@ -234,12 +231,6 @@ func Init(home string, options graphdriver.Options) (graphdriver.Driver, error) } } - if opts.ostreeRepo != "" { - if err := ostree.CreateOSTreeRepository(opts.ostreeRepo, rootUID, rootGID); err != nil { - return nil, err - } - } - d := &Driver{ name: "overlay", home: home, @@ -251,7 +242,6 @@ func Init(home string, options graphdriver.Options) (graphdriver.Driver, error) usingMetacopy: usingMetacopy, locker: locker.New(), options: *opts, - convert: make(map[string]bool), } d.naiveDiff = graphdriver.NewNaiveDiffDriver(d, graphdriver.NewNaiveLayerIDMapUpdater(d)) @@ -316,24 +306,12 @@ func parseOptions(options []string) (*overlayOptions, error) { return nil, fmt.Errorf("overlay: can't stat program %s: %v", val, err) } o.mountProgram = val - case "overlay2.ostree_repo", "overlay.ostree_repo", ".ostree_repo": - logrus.Debugf("overlay: ostree_repo=%s", val) - if !ostree.OstreeSupport() { - return nil, fmt.Errorf("overlay: ostree_repo specified but support for ostree is missing") - } - o.ostreeRepo = val case ".ignore_chown_errors", "overlay2.ignore_chown_errors", "overlay.ignore_chown_errors": logrus.Debugf("overlay: ignore_chown_errors=%s", val) o.ignoreChownErrors, err = strconv.ParseBool(val) if err != nil { return nil, err } - case "overlay2.skip_mount_home", "overlay.skip_mount_home", ".skip_mount_home": - logrus.Debugf("overlay: skip_mount_home=%s", val) - o.skipMountHome, err = strconv.ParseBool(val) - if err != nil { - return nil, err - } default: return nil, fmt.Errorf("overlay: Unknown option %s", key) } @@ -556,10 +534,6 @@ func (d *Driver) Create(id, parent string, opts *graphdriver.CreateOpts) (retErr } } - if d.options.ostreeRepo != "" { - d.convert[id] = true - } - return d.create(id, parent, opts) } @@ -766,11 +740,6 @@ func (d *Driver) Remove(id string) error { d.locker.Lock(id) defer d.locker.Unlock(id) - // Ignore errors, we don't want to fail if the ostree branch doesn't exist, - if d.options.ostreeRepo != "" { - ostree.DeleteOSTree(d.options.ostreeRepo, id) - } - dir := d.dir(id) lid, err := ioutil.ReadFile(path.Join(dir, "link")) if err == nil { @@ -1125,13 +1094,6 @@ func (d *Driver) ApplyDiff(id, parent string, options graphdriver.ApplyDiffOpts) return 0, err } - _, convert := d.convert[id] - if convert { - if err := ostree.ConvertToOSTree(d.options.ostreeRepo, applyDir, id); err != nil { - return 0, err - } - } - return directory.Size(applyDir) } diff --git a/vendor/github.com/containers/storage/drivers/vfs/driver.go b/vendor/github.com/containers/storage/drivers/vfs/driver.go index 6c02a45dc..58a1635ae 100644 --- a/vendor/github.com/containers/storage/drivers/vfs/driver.go +++ b/vendor/github.com/containers/storage/drivers/vfs/driver.go @@ -11,7 +11,6 @@ import ( "github.com/containers/storage/drivers" "github.com/containers/storage/pkg/archive" "github.com/containers/storage/pkg/idtools" - "github.com/containers/storage/pkg/ostree" "github.com/containers/storage/pkg/parsers" "github.com/containers/storage/pkg/system" "github.com/opencontainers/selinux/go-selinux/label" @@ -51,11 +50,6 @@ func Init(home string, options graphdriver.Options) (graphdriver.Driver, error) case "vfs.imagestore", ".imagestore": d.homes = append(d.homes, strings.Split(val, ",")...) continue - case "vfs.ostree_repo", ".ostree_repo": - if !ostree.OstreeSupport() { - return nil, fmt.Errorf("vfs: ostree_repo specified but support for ostree is missing") - } - d.ostreeRepo = val case "vfs.mountopt": return nil, fmt.Errorf("vfs driver does not support mount options") case ".ignore_chown_errors", "vfs.ignore_chown_errors": @@ -69,15 +63,6 @@ func Init(home string, options graphdriver.Options) (graphdriver.Driver, error) return nil, fmt.Errorf("vfs driver does not support %s options", key) } } - if d.ostreeRepo != "" { - rootUID, rootGID, err := idtools.GetRootUIDGID(options.UIDMaps, options.GIDMaps) - if err != nil { - return nil, err - } - if err := ostree.CreateOSTreeRepository(d.ostreeRepo, rootUID, rootGID); err != nil { - return nil, err - } - } d.updater = graphdriver.NewNaiveLayerIDMapUpdater(d) d.naiveDiff = graphdriver.NewNaiveDiffDriver(d, d.updater) @@ -92,7 +77,6 @@ type Driver struct { name string homes []string idMappings *idtools.IDMappings - ostreeRepo string ignoreChownErrors bool naiveDiff graphdriver.DiffDriver updater graphdriver.LayerIDMapUpdater @@ -191,11 +175,6 @@ func (d *Driver) create(id, parent string, opts *graphdriver.CreateOpts, ro bool } } - if ro && d.ostreeRepo != "" { - if err := ostree.ConvertToOSTree(d.ostreeRepo, dir, id); err != nil { - return err - } - } return nil } @@ -216,10 +195,6 @@ func (d *Driver) dir(id string) string { // Remove deletes the content from the directory for a given id. func (d *Driver) Remove(id string) error { - if d.ostreeRepo != "" { - // Ignore errors, we don't want to fail if the ostree branch doesn't exist, - ostree.DeleteOSTree(d.ostreeRepo, id) - } return system.EnsureRemoveAll(d.dir(id)) } diff --git a/vendor/github.com/containers/storage/go.mod b/vendor/github.com/containers/storage/go.mod index d8f943d30..934e82ad2 100644 --- a/vendor/github.com/containers/storage/go.mod +++ b/vendor/github.com/containers/storage/go.mod @@ -15,7 +15,6 @@ require ( github.com/opencontainers/go-digest v1.0.0-rc1 github.com/opencontainers/runc v1.0.0-rc8 github.com/opencontainers/selinux v1.2.2 - github.com/ostreedev/ostree-go v0.0.0-20190702140239-759a8c1ac913 github.com/pkg/errors v0.8.1 github.com/pquerna/ffjson v0.0.0-20181028064349-e517b90714f7 github.com/sirupsen/logrus v1.4.2 diff --git a/vendor/github.com/containers/storage/go.sum b/vendor/github.com/containers/storage/go.sum index 159bfdba2..a0e05dd1d 100644 --- a/vendor/github.com/containers/storage/go.sum +++ b/vendor/github.com/containers/storage/go.sum @@ -35,8 +35,6 @@ github.com/opencontainers/runc v1.0.0-rc8 h1:dDCFes8Hj1r/i5qnypONo5jdOme/8HWZC/a github.com/opencontainers/runc v1.0.0-rc8/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= github.com/opencontainers/selinux v1.2.2 h1:Kx9J6eDG5/24A6DtUquGSpJQ+m2MUTahn4FtGEe8bFg= github.com/opencontainers/selinux v1.2.2/go.mod h1:+BLncwf63G4dgOzykXAxcmnFlUaOlkDdmw/CqsW6pjs= -github.com/ostreedev/ostree-go v0.0.0-20190702140239-759a8c1ac913 h1:TnbXhKzrTOyuvWrjI8W6pcoI9XPbLHFXCdN2dtUw7Rw= -github.com/ostreedev/ostree-go v0.0.0-20190702140239-759a8c1ac913/go.mod h1:J6OG6YJVEWopen4avK3VNQSnALmmjvniMmni/YFYAwc= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= diff --git a/vendor/github.com/containers/storage/pkg/config/config.go b/vendor/github.com/containers/storage/pkg/config/config.go index c53f70421..091040140 100644 --- a/vendor/github.com/containers/storage/pkg/config/config.go +++ b/vendor/github.com/containers/storage/pkg/config/config.go @@ -85,11 +85,6 @@ type OptionsConfig struct { RemapGroup string `toml:"remap-group"` // Thinpool container options to be handed to thinpool drivers Thinpool struct{ ThinpoolOptionsConfig } `toml:"thinpool"` - // OSTree repository - OstreeRepo string `toml:"ostree_repo"` - - // Do not create a bind mount on the storage home - SkipMountHome string `toml:"skip_mount_home"` // Alternative program to use for the mount of the file system MountProgram string `toml:"mount_program"` diff --git a/vendor/github.com/containers/storage/pkg/lockfile/lockfile_unix.go b/vendor/github.com/containers/storage/pkg/lockfile/lockfile_unix.go index 1fed414f7..228c8cf24 100644 --- a/vendor/github.com/containers/storage/pkg/lockfile/lockfile_unix.go +++ b/vendor/github.com/containers/storage/pkg/lockfile/lockfile_unix.go @@ -104,7 +104,7 @@ func (l *lockfile) lock(l_type int16, recursive bool) { // If we're the first reference on the lock, we need to open the file again. fd, err := openLock(l.file, l.ro) if err != nil { - panic(fmt.Sprintf("error opening %q", l.file)) + panic(fmt.Sprintf("error opening %q: %v", l.file, err)) } unix.CloseOnExec(fd) l.fd = uintptr(fd) diff --git a/vendor/github.com/containers/storage/pkg/ostree/no_ostree.go b/vendor/github.com/containers/storage/pkg/ostree/no_ostree.go deleted file mode 100644 index bf83ccf25..000000000 --- a/vendor/github.com/containers/storage/pkg/ostree/no_ostree.go +++ /dev/null @@ -1,19 +0,0 @@ -// +build !ostree !cgo - -package ostree - -func OstreeSupport() bool { - return false -} - -func DeleteOSTree(repoLocation, id string) error { - return nil -} - -func CreateOSTreeRepository(repoLocation string, rootUID int, rootGID int) error { - return nil -} - -func ConvertToOSTree(repoLocation, root, id string) error { - return nil -} diff --git a/vendor/github.com/containers/storage/pkg/ostree/ostree.go b/vendor/github.com/containers/storage/pkg/ostree/ostree.go deleted file mode 100644 index 7d324f2b2..000000000 --- a/vendor/github.com/containers/storage/pkg/ostree/ostree.go +++ /dev/null @@ -1,198 +0,0 @@ -// +build ostree,cgo - -package ostree - -import ( - "fmt" - "golang.org/x/sys/unix" - "os" - "path/filepath" - "runtime" - "syscall" - "time" - "unsafe" - - "github.com/containers/storage/pkg/idtools" - "github.com/containers/storage/pkg/system" - glib "github.com/ostreedev/ostree-go/pkg/glibobject" - "github.com/ostreedev/ostree-go/pkg/otbuiltin" - "github.com/pkg/errors" -) - -// #cgo pkg-config: glib-2.0 gobject-2.0 ostree-1 -// #include <glib.h> -// #include <glib-object.h> -// #include <gio/gio.h> -// #include <stdlib.h> -// #include <ostree.h> -// #include <gio/ginputstream.h> -import "C" - -func OstreeSupport() bool { - return true -} - -func fixFiles(dir string, usermode bool) (bool, []string, error) { - var SkipOstree = errors.New("skip ostree deduplication") - - var whiteouts []string - - err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error { - if info.Mode()&(os.ModeNamedPipe|os.ModeSocket|os.ModeDevice) != 0 { - if !usermode { - stat, ok := info.Sys().(*syscall.Stat_t) - if !ok { - return errors.New("not syscall.Stat_t") - } - - if stat.Rdev == 0 && (stat.Mode&unix.S_IFCHR) != 0 { - whiteouts = append(whiteouts, path) - return nil - } - } - // Skip the ostree deduplication if we encounter a file type that - // ostree does not manage. - return SkipOstree - } - if info.IsDir() { - if usermode { - if err := os.Chmod(path, info.Mode()|0700); err != nil { - return err - } - } - } else if usermode && (info.Mode().IsRegular()) { - if err := os.Chmod(path, info.Mode()|0600); err != nil { - return err - } - } - return nil - }) - if err == SkipOstree { - return true, nil, nil - } - if err != nil { - return false, nil, err - } - return false, whiteouts, nil -} - -// Create prepares the filesystem for the OSTREE driver and copies the directory for the given id under the parent. -func ConvertToOSTree(repoLocation, root, id string) error { - runtime.LockOSThread() - defer runtime.UnlockOSThread() - repo, err := otbuiltin.OpenRepo(repoLocation) - if err != nil { - return errors.Wrap(err, "could not open the OSTree repository") - } - - skip, whiteouts, err := fixFiles(root, os.Getuid() != 0) - if err != nil { - return errors.Wrap(err, "could not prepare the OSTree directory") - } - if skip { - return nil - } - - if _, err := repo.PrepareTransaction(); err != nil { - return errors.Wrap(err, "could not prepare the OSTree transaction") - } - - if skip { - return nil - } - - commitOpts := otbuiltin.NewCommitOptions() - commitOpts.Timestamp = time.Now() - commitOpts.LinkCheckoutSpeedup = true - commitOpts.Parent = "0000000000000000000000000000000000000000000000000000000000000000" - branch := fmt.Sprintf("containers-storage/%s", id) - - for _, w := range whiteouts { - if err := os.Remove(w); err != nil { - return errors.Wrap(err, "could not delete whiteout file") - } - } - - if _, err := repo.Commit(root, branch, commitOpts); err != nil { - return errors.Wrap(err, "could not commit the layer") - } - - if _, err := repo.CommitTransaction(); err != nil { - return errors.Wrap(err, "could not complete the OSTree transaction") - } - - if err := system.EnsureRemoveAll(root); err != nil { - return errors.Wrap(err, "could not delete layer") - } - - checkoutOpts := otbuiltin.NewCheckoutOptions() - checkoutOpts.RequireHardlinks = true - checkoutOpts.Whiteouts = false - if err := otbuiltin.Checkout(repoLocation, root, branch, checkoutOpts); err != nil { - return errors.Wrap(err, "could not checkout from OSTree") - } - - for _, w := range whiteouts { - if err := unix.Mknod(w, unix.S_IFCHR, 0); err != nil { - return errors.Wrap(err, "could not recreate whiteout file") - } - } - return nil -} - -func CreateOSTreeRepository(repoLocation string, rootUID int, rootGID int) error { - runtime.LockOSThread() - defer runtime.UnlockOSThread() - - _, err := os.Stat(repoLocation) - if err != nil && !os.IsNotExist(err) { - return err - } else if err != nil { - if err := idtools.MkdirAllAs(repoLocation, 0700, rootUID, rootGID); err != nil { - return errors.Wrap(err, "could not create OSTree repository directory: %v") - } - - if _, err := otbuiltin.Init(repoLocation, otbuiltin.NewInitOptions()); err != nil { - return errors.Wrap(err, "could not create OSTree repository") - } - } - return nil -} - -func openRepo(path string) (*C.struct_OstreeRepo, error) { - var cerr *C.GError - cpath := C.CString(path) - defer C.free(unsafe.Pointer(cpath)) - pathc := C.g_file_new_for_path(cpath) - defer C.g_object_unref(C.gpointer(pathc)) - repo := C.ostree_repo_new(pathc) - r := glib.GoBool(glib.GBoolean(C.ostree_repo_open(repo, nil, &cerr))) - if !r { - C.g_object_unref(C.gpointer(repo)) - return nil, glib.ConvertGError(glib.ToGError(unsafe.Pointer(cerr))) - } - return repo, nil -} - -func DeleteOSTree(repoLocation, id string) error { - runtime.LockOSThread() - defer runtime.UnlockOSThread() - - repo, err := openRepo(repoLocation) - if err != nil { - return err - } - defer C.g_object_unref(C.gpointer(repo)) - - branch := fmt.Sprintf("containers-storage/%s", id) - - cbranch := C.CString(branch) - defer C.free(unsafe.Pointer(cbranch)) - - var cerr *C.GError - r := glib.GoBool(glib.GBoolean(C.ostree_repo_set_ref_immediate(repo, nil, cbranch, nil, nil, &cerr))) - if !r { - return glib.ConvertGError(glib.ToGError(unsafe.Pointer(cerr))) - } - return nil -} diff --git a/vendor/github.com/containers/storage/storage.conf b/vendor/github.com/containers/storage/storage.conf index 3e7bf62f0..efd46eefb 100644 --- a/vendor/github.com/containers/storage/storage.conf +++ b/vendor/github.com/containers/storage/storage.conf @@ -61,13 +61,6 @@ mountopt = "nodev" # remap-user = "storage" # remap-group = "storage" -# If specified, use OSTree to deduplicate files with the overlay backend. -ostree_repo = "" - -# Set to skip a PRIVATE bind mount on the storage home directory. Only supported by -# certain container storage drivers (overlay). -skip_mount_home = "false" - [storage.options.thinpool] # Storage Options for thinpool diff --git a/vendor/github.com/containers/storage/store.go b/vendor/github.com/containers/storage/store.go index af69a4b2d..6e4bd4ee0 100644 --- a/vendor/github.com/containers/storage/store.go +++ b/vendor/github.com/containers/storage/store.go @@ -3357,12 +3357,6 @@ func ReloadConfigurationFile(configFile string, storeOptions *StoreOptions) { if config.Storage.Options.Size != "" { storeOptions.GraphDriverOptions = append(storeOptions.GraphDriverOptions, fmt.Sprintf("%s.size=%s", config.Storage.Driver, config.Storage.Options.Size)) } - if config.Storage.Options.OstreeRepo != "" { - storeOptions.GraphDriverOptions = append(storeOptions.GraphDriverOptions, fmt.Sprintf("%s.ostree_repo=%s", config.Storage.Driver, config.Storage.Options.OstreeRepo)) - } - if config.Storage.Options.SkipMountHome != "" { - storeOptions.GraphDriverOptions = append(storeOptions.GraphDriverOptions, fmt.Sprintf("%s.skip_mount_home=%s", config.Storage.Driver, config.Storage.Options.SkipMountHome)) - } if config.Storage.Options.MountProgram != "" { storeOptions.GraphDriverOptions = append(storeOptions.GraphDriverOptions, fmt.Sprintf("%s.mount_program=%s", config.Storage.Driver, config.Storage.Options.MountProgram)) } diff --git a/vendor/modules.txt b/vendor/modules.txt index c01409444..65a99869e 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -108,14 +108,15 @@ github.com/containers/image/v4/pkg/blobinfocache/memory github.com/containers/image/v4/pkg/blobinfocache/internal/prioritize # github.com/containers/libtrust v0.0.0-20190913040956-14b96171aa3b github.com/containers/libtrust -# github.com/containers/psgo v1.3.1 +# github.com/containers/psgo v1.3.2 github.com/containers/psgo github.com/containers/psgo/internal/capabilities github.com/containers/psgo/internal/dev github.com/containers/psgo/internal/proc github.com/containers/psgo/internal/process +github.com/containers/psgo/internal/cgroups github.com/containers/psgo/internal/host -# github.com/containers/storage v1.13.4 +# github.com/containers/storage v1.13.5 github.com/containers/storage github.com/containers/storage/pkg/archive github.com/containers/storage/pkg/chrootarchive @@ -154,7 +155,6 @@ github.com/containers/storage/pkg/loopback github.com/containers/storage/drivers/overlayutils github.com/containers/storage/drivers/quota github.com/containers/storage/pkg/fsutils -github.com/containers/storage/pkg/ostree github.com/containers/storage/drivers/copy # github.com/coreos/go-iptables v0.4.2 github.com/coreos/go-iptables/iptables @@ -346,12 +346,12 @@ github.com/onsi/ginkgo/internal/specrunner # github.com/onsi/gomega v1.7.0 github.com/onsi/gomega github.com/onsi/gomega/gexec +github.com/onsi/gomega/format github.com/onsi/gomega/internal/assertion github.com/onsi/gomega/internal/asyncassertion github.com/onsi/gomega/internal/testingtsupport github.com/onsi/gomega/matchers github.com/onsi/gomega/types -github.com/onsi/gomega/format github.com/onsi/gomega/gbytes github.com/onsi/gomega/internal/oraclematcher github.com/onsi/gomega/matchers/support/goraph/bipartitegraph diff --git a/version/version.go b/version/version.go index 2c4d69b78..c0dbeadfe 100644 --- a/version/version.go +++ b/version/version.go @@ -4,7 +4,7 @@ package version // NOTE: remember to bump the version at the top // of the top-level README.md file when this is // bumped. -const Version = "1.6.2-dev" +const Version = "1.6.3-dev" // RemoteAPIVersion is the version for the remote // client API. It is used to determine compatibility |