diff options
41 files changed, 634 insertions, 421 deletions
@@ -97,7 +97,7 @@ in the [API.md](https://github.com/containers/libpod/blob/master/API.md) file in [func ReceiveFile(path: string, delete: bool) int](#ReceiveFile) -[func RemoveContainer(name: string, force: bool) string](#RemoveContainer) +[func RemoveContainer(name: string, force: bool, removeVolumes: bool) string](#RemoveContainer) [func RemoveImage(name: string, force: bool) string](#RemoveImage) @@ -135,6 +135,8 @@ in the [API.md](https://github.com/containers/libpod/blob/master/API.md) file in [type BuildInfo](#BuildInfo) +[type BuildOptions](#BuildOptions) + [type Container](#Container) [type ContainerChanges](#ContainerChanges) @@ -775,9 +777,9 @@ method ReceiveFile(path: [string](https://godoc.org/builtin#string), delete: [bo ### <a name="RemoveContainer"></a>func RemoveContainer <div style="background-color: #E8E8E8; padding: 15px; margin: 10px; border-radius: 10px;"> -method RemoveContainer(name: [string](https://godoc.org/builtin#string), force: [bool](https://godoc.org/builtin#bool)) [string](https://godoc.org/builtin#string)</div> +method RemoveContainer(name: [string](https://godoc.org/builtin#string), force: [bool](https://godoc.org/builtin#bool), removeVolumes: [bool](https://godoc.org/builtin#bool)) [string](https://godoc.org/builtin#string)</div> RemoveContainer takes requires the name or ID of container as well a boolean representing whether a running -container can be stopped and removed. Upon successful removal of the container, its ID is returned. If the +container can be stopped and removed. It also takes a flag on whether or not to remove builtin volumes. Upon successful removal of the container, its ID is returned. If the container cannot be found by name or ID, a [ContainerNotFound](#ContainerNotFound) error will be returned. #### Example ~~~ @@ -972,53 +974,84 @@ a [ContainerNotFound](#ContainerNotFound) error is returned. BuildInfo is used to describe user input for building images -dockerfile [[]string](#[]string) +additionalTags [[]string](#[]string) -tags [[]string](#[]string) +annotations [[]string](#[]string) -add_hosts [[]string](#[]string) +buildArgs [map[string]](#map[string]) -cgroup_parent [string](https://godoc.org/builtin#string) +buildOptions [BuildOptions](#BuildOptions) -cpu_period [int](https://godoc.org/builtin#int) +cniConfigDir [string](https://godoc.org/builtin#string) -cpu_quota [int](https://godoc.org/builtin#int) +cniPluginDir [string](https://godoc.org/builtin#string) -cpu_shares [int](https://godoc.org/builtin#int) +compression [string](https://godoc.org/builtin#string) -cpuset_cpus [string](https://godoc.org/builtin#string) +contextDir [string](https://godoc.org/builtin#string) -cpuset_mems [string](https://godoc.org/builtin#string) +defaultsMountFilePath [string](https://godoc.org/builtin#string) -memory [string](https://godoc.org/builtin#string) +dockerfiles [[]string](#[]string) -memory_swap [string](https://godoc.org/builtin#string) +err [string](https://godoc.org/builtin#string) -security_opts [[]string](#[]string) +forceRmIntermediateCtrs [bool](https://godoc.org/builtin#bool) -shm_size [string](https://godoc.org/builtin#string) +iidfile [string](https://godoc.org/builtin#string) -ulimit [[]string](#[]string) +label [[]string](#[]string) -volume [[]string](#[]string) +layers [bool](https://godoc.org/builtin#bool) + +nocache [bool](https://godoc.org/builtin#bool) + +out [string](https://godoc.org/builtin#string) + +output [string](https://godoc.org/builtin#string) + +outputFormat [string](https://godoc.org/builtin#string) + +pullPolicy [string](https://godoc.org/builtin#string) + +quiet [bool](https://godoc.org/builtin#bool) + +remoteIntermediateCtrs [bool](https://godoc.org/builtin#bool) + +reportWriter [string](https://godoc.org/builtin#string) + +runtimeArgs [[]string](#[]string) + +signaturePolicyPath [string](https://godoc.org/builtin#string) squash [bool](https://godoc.org/builtin#bool) +### <a name="BuildOptions"></a>type BuildOptions -pull [bool](https://godoc.org/builtin#bool) +BuildOptions are are used to describe describe physical attributes of the build -pull_always [bool](https://godoc.org/builtin#bool) +addHosts [[]string](#[]string) -force_rm [bool](https://godoc.org/builtin#bool) +cgroupParent [string](https://godoc.org/builtin#string) -rm [bool](https://godoc.org/builtin#bool) +cpuPeriod [int](https://godoc.org/builtin#int) -label [[]string](#[]string) +cpuQuota [int](https://godoc.org/builtin#int) -annotations [[]string](#[]string) +cpuShares [int](https://godoc.org/builtin#int) + +cpusetCpus [string](https://godoc.org/builtin#string) + +cpusetMems [string](https://godoc.org/builtin#string) + +memory [int](https://godoc.org/builtin#int) -build_args [map[string]](#map[string]) +memorySwap [int](https://godoc.org/builtin#int) -image_format [string](https://godoc.org/builtin#string) +shmSize [string](https://godoc.org/builtin#string) + +ulimit [[]string](#[]string) + +volume [[]string](#[]string) ### <a name="Container"></a>type Container @@ -94,6 +94,7 @@ help: ifeq ("$(wildcard $(GOPKGDIR))","") mkdir -p "$(GOPKGBASEDIR)" ln -sf "$(CURDIR)" "$(GOPKGBASEDIR)" + ln -sf "$(CURDIR)/vendor/github.com/varlink" "$(FIRST_GOPATH)/src/github.com/varlink" endif touch $@ diff --git a/cmd/podman/attach.go b/cmd/podman/attach.go index c29886825..ed175bdf4 100644 --- a/cmd/podman/attach.go +++ b/cmd/podman/attach.go @@ -74,7 +74,7 @@ func attachCmd(c *cliconfig.AttachValues) error { inputStream = nil } - if err := startAttachCtr(ctr, os.Stdout, os.Stderr, inputStream, c.DetachKeys, c.SigProxy, false); err != nil { + if err := startAttachCtr(ctr, os.Stdout, os.Stderr, inputStream, c.DetachKeys, c.SigProxy, false); err != nil && errors.Cause(err) != libpod.ErrDetach { return errors.Wrapf(err, "error attaching to container %s", ctr.ID()) } diff --git a/cmd/podman/build.go b/cmd/podman/build.go index fef93ac47..30a734377 100644 --- a/cmd/podman/build.go +++ b/cmd/podman/build.go @@ -1,7 +1,6 @@ package main import ( - "io/ioutil" "os" "path/filepath" "strings" @@ -9,10 +8,9 @@ import ( "github.com/containers/buildah" "github.com/containers/buildah/imagebuildah" buildahcli "github.com/containers/buildah/pkg/cli" - "github.com/containers/buildah/pkg/parse" "github.com/containers/libpod/cmd/podman/cliconfig" - "github.com/containers/libpod/cmd/podman/libpodruntime" - "github.com/containers/libpod/pkg/rootless" + "github.com/containers/libpod/libpod/adapter" + "github.com/docker/go-units" "github.com/pkg/errors" "github.com/sirupsen/logrus" "github.com/spf13/cobra" @@ -76,7 +74,6 @@ func getDockerfiles(files []string) []string { func buildCmd(c *cliconfig.BuildValues) error { // 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 := "" tags := []string{} if c.Flag("tag").Changed { @@ -86,6 +83,7 @@ func buildCmd(c *cliconfig.BuildValues) error { tags = tags[1:] } } + pullPolicy := imagebuildah.PullNever if c.Pull { pullPolicy = imagebuildah.PullIfMissing @@ -173,16 +171,17 @@ func buildCmd(c *cliconfig.BuildValues) error { dockerfiles = append(dockerfiles, filepath.Join(contextDir, "Dockerfile")) } + runtime, err := adapter.GetRuntime(&c.PodmanCommand) + if err != nil { + return errors.Wrapf(err, "could not get runtime") + } + runtimeFlags := []string{} for _, arg := range c.RuntimeOpts { runtimeFlags = append(runtimeFlags, "--"+arg) } // end from buildah - runtime, err := libpodruntime.GetRuntime(&c.PodmanCommand) - if err != nil { - return errors.Wrapf(err, "could not get runtime") - } defer runtime.Shutdown(false) var stdout, stderr, reporter *os.File @@ -201,72 +200,64 @@ func buildCmd(c *cliconfig.BuildValues) error { reporter = f } - systemContext, err := parse.SystemContextFromOptions(c.PodmanCommand.Command) - if err != nil { - return errors.Wrapf(err, "error building system context") - } - systemContext.AuthFilePath = getAuthFile(c.Authfile) - commonOpts, err := parse.CommonBuildOptions(c.PodmanCommand.Command) - if err != nil { - return err + var memoryLimit, memorySwap int64 + if c.Flags().Changed("memory") { + memoryLimit, err = units.RAMInBytes(c.Memory) + if err != nil { + return err + } } - namespaceOptions, networkPolicy, err := parse.NamespaceOptions(c.PodmanCommand.Command) - if err != nil { - return errors.Wrapf(err, "error parsing namespace-related options") - } - usernsOption, idmappingOptions, err := parse.IDMappingOptions(c.PodmanCommand.Command) - if err != nil { - return errors.Wrapf(err, "error parsing ID mapping options") + if c.Flags().Changed("memory-swap") { + memorySwap, err = units.RAMInBytes(c.MemorySwap) + if err != nil { + return err + } } - namespaceOptions.AddOrReplace(usernsOption...) - ociruntime := runtime.GetOCIRuntimePath() - if c.Flag("runtime").Changed { - ociruntime = c.Runtime + buildOpts := buildah.CommonBuildOptions{ + AddHost: c.AddHost, + CgroupParent: c.CgroupParent, + CPUPeriod: c.CPUPeriod, + CPUQuota: c.CPUQuota, + CPUShares: c.CPUShares, + CPUSetCPUs: c.CPUSetCPUs, + CPUSetMems: c.CPUSetMems, + Memory: memoryLimit, + MemorySwap: memorySwap, + ShmSize: c.ShmSize, + Ulimit: c.Ulimit, + Volumes: c.Volume, } + options := imagebuildah.BuildOptions{ - ContextDirectory: contextDir, - PullPolicy: pullPolicy, - Compression: imagebuildah.Gzip, - Quiet: c.Quiet, - SignaturePolicyPath: c.SignaturePolicy, - Args: args, - Output: output, + CommonBuildOpts: &buildOpts, AdditionalTags: tags, - Out: stdout, - Err: stderr, - ReportWriter: reporter, - Runtime: ociruntime, - RuntimeArgs: runtimeFlags, - OutputFormat: format, - SystemContext: systemContext, - NamespaceOptions: namespaceOptions, - ConfigureNetwork: networkPolicy, - CNIPluginPath: c.CNIPlugInPath, + Annotations: c.Annotation, + Args: args, CNIConfigDir: c.CNIConfigDir, - IDMappingOptions: idmappingOptions, - CommonBuildOpts: commonOpts, + CNIPluginPath: c.CNIPlugInPath, + Compression: imagebuildah.Gzip, + ContextDirectory: contextDir, DefaultMountsFilePath: c.GlobalFlags.DefaultMountsFile, + Err: stderr, + ForceRmIntermediateCtrs: c.ForceRm, IIDFile: c.Iidfile, - Squash: c.Squash, Labels: c.Label, - Annotations: c.Annotation, Layers: layers, NoCache: c.NoCache, + Out: stdout, + Output: output, + OutputFormat: format, + PullPolicy: pullPolicy, + Quiet: c.Quiet, RemoveIntermediateCtrs: c.Rm, - ForceRmIntermediateCtrs: c.ForceRm, - } - - if c.Quiet { - options.ReportWriter = ioutil.Discard - } - - if rootless.IsRootless() { - options.Isolation = buildah.IsolationOCIRootless + ReportWriter: reporter, + RuntimeArgs: runtimeFlags, + SignaturePolicyPath: c.SignaturePolicy, + Squash: c.Squash, } - - return runtime.Build(getContext(), options, dockerfiles...) + return runtime.Build(getContext(), c, options, dockerfiles) } // Tail returns a string slice after the first element unless there are diff --git a/cmd/podman/cleanup.go b/cmd/podman/cleanup.go index b1f727d33..e465a30e6 100644 --- a/cmd/podman/cleanup.go +++ b/cmd/podman/cleanup.go @@ -37,6 +37,7 @@ func init() { flags.BoolVarP(&cleanupCommand.All, "all", "a", false, "Cleans up all containers") flags.BoolVarP(&cleanupCommand.Latest, "latest", "l", false, "Act on the latest container podman is aware of") + flags.BoolVar(&cleanupCommand.Remove, "rm", false, "After cleanup, remove the container entirely") } func cleanupCmd(c *cliconfig.CleanupValues) error { @@ -55,12 +56,25 @@ func cleanupCmd(c *cliconfig.CleanupValues) error { ctx := getContext() for _, ctr := range cleanupContainers { - if err = ctr.Cleanup(ctx); err != nil { - if lastError != nil { - fmt.Fprintln(os.Stderr, lastError) + hadError := false + if c.Remove { + if err := runtime.RemoveContainer(ctx, ctr, false, false); err != nil { + if lastError != nil { + fmt.Fprintln(os.Stderr, lastError) + } + lastError = errors.Wrapf(err, "failed to cleanup and remove container %v", ctr.ID()) + hadError = true } - lastError = errors.Wrapf(err, "failed to cleanup container %v", ctr.ID()) } else { + if err := ctr.Cleanup(ctx); err != nil { + if lastError != nil { + fmt.Fprintln(os.Stderr, lastError) + } + lastError = errors.Wrapf(err, "failed to cleanup container %v", ctr.ID()) + hadError = true + } + } + if !hadError { fmt.Println(ctr.ID()) } } diff --git a/cmd/podman/cliconfig/config.go b/cmd/podman/cliconfig/config.go index b925d29ff..f38bcaa62 100644 --- a/cmd/podman/cliconfig/config.go +++ b/cmd/podman/cliconfig/config.go @@ -135,6 +135,11 @@ type PruneImagesValues struct { All bool } +type PruneContainersValues struct { + PodmanCommand + Force bool +} + type ImportValues struct { PodmanCommand Change []string @@ -531,6 +536,7 @@ type CleanupValues struct { PodmanCommand All bool Latest bool + Remove bool } type SystemPruneValues struct { diff --git a/cmd/podman/commands.go b/cmd/podman/commands.go index 90e2ab5cf..baa49d2af 100644 --- a/cmd/podman/commands.go +++ b/cmd/podman/commands.go @@ -10,7 +10,6 @@ import ( func getMainCommands() []*cobra.Command { rootCommands := []*cobra.Command{ _attachCommand, - _buildCommand, _commitCommand, _createCommand, _diffCommand, @@ -54,7 +53,6 @@ func getMainCommands() []*cobra.Command { // Commands that the local client implements func getImageSubCommands() []*cobra.Command { return []*cobra.Command{ - _buildCommand, _loadCommand, _saveCommand, _signCommand, diff --git a/cmd/podman/containers_prune.go b/cmd/podman/containers_prune.go index acc138fe0..bae578e1d 100644 --- a/cmd/podman/containers_prune.go +++ b/cmd/podman/containers_prune.go @@ -13,13 +13,12 @@ import ( ) var ( - pruneContainersCommand cliconfig.ContainersPrune + pruneContainersCommand cliconfig.PruneContainersValues pruneContainersDescription = ` podman container prune Removes all exited containers ` - _pruneContainersCommand = &cobra.Command{ Use: "prune", Short: "Remove all stopped containers", @@ -35,9 +34,11 @@ var ( func init() { pruneContainersCommand.Command = _pruneContainersCommand pruneContainersCommand.SetUsageTemplate(UsageTemplate()) + flags := pruneContainersCommand.Flags() + flags.BoolVarP(&pruneContainersCommand.Force, "force", "f", false, "Force removal of a running container. The default is false") } -func pruneContainers(runtime *adapter.LocalRuntime, ctx context.Context, maxWorkers int, force bool) error { +func pruneContainers(runtime *adapter.LocalRuntime, ctx context.Context, maxWorkers int, force, volumes bool) error { var deleteFuncs []shared.ParallelWorkerInput filter := func(c *libpod.Container) bool { @@ -57,7 +58,7 @@ func pruneContainers(runtime *adapter.LocalRuntime, ctx context.Context, maxWork for _, container := range delContainers { con := container f := func() error { - return runtime.RemoveContainer(ctx, con, force) + return runtime.RemoveContainer(ctx, con, force, volumes) } deleteFuncs = append(deleteFuncs, shared.ParallelWorkerInput{ @@ -70,7 +71,7 @@ func pruneContainers(runtime *adapter.LocalRuntime, ctx context.Context, maxWork return printParallelOutput(deleteErrors, errCount) } -func pruneContainersCmd(c *cliconfig.ContainersPrune) error { +func pruneContainersCmd(c *cliconfig.PruneContainersValues) error { runtime, err := adapter.GetRuntime(&c.PodmanCommand) if err != nil { return errors.Wrapf(err, "could not get runtime") @@ -83,5 +84,5 @@ func pruneContainersCmd(c *cliconfig.ContainersPrune) error { } logrus.Debugf("Setting maximum workers to %d", maxWorkers) - return pruneContainers(runtime, getContext(), maxWorkers, c.Bool("force")) + return pruneContainers(runtime, getContext(), maxWorkers, c.Bool("force"), c.Bool("volumes")) } diff --git a/cmd/podman/create.go b/cmd/podman/create.go index 1a7f419c0..392163424 100644 --- a/cmd/podman/create.go +++ b/cmd/podman/create.go @@ -646,9 +646,10 @@ func parseCreateOpts(ctx context.Context, c *cliconfig.PodmanCommand, runtime *l } var ImageVolumes map[string]struct{} - if data != nil { + if data != nil && c.String("image-volume") != "ignore" { ImageVolumes = data.Config.Volumes } + var imageVolType = map[string]string{ "bind": "", "tmpfs": "", diff --git a/cmd/podman/image.go b/cmd/podman/image.go index edc37b28a..4f9c7cd6a 100644 --- a/cmd/podman/image.go +++ b/cmd/podman/image.go @@ -18,6 +18,7 @@ var ( //imageSubCommands are implemented both in local and remote clients var imageSubCommands = []*cobra.Command{ + _buildCommand, _historyCommand, _imageExistsCommand, _imagesCommand, diff --git a/cmd/podman/main.go b/cmd/podman/main.go index a6f0c500a..f9820c075 100644 --- a/cmd/podman/main.go +++ b/cmd/podman/main.go @@ -30,6 +30,7 @@ var ( // Commands that the remote and local client have // implemented. var mainCommands = []*cobra.Command{ + _buildCommand, _exportCommand, _historyCommand, _imagesCommand, diff --git a/cmd/podman/rm.go b/cmd/podman/rm.go index 1e5e9d254..d170e5357 100644 --- a/cmd/podman/rm.go +++ b/cmd/podman/rm.go @@ -39,8 +39,7 @@ func init() { flags.BoolVarP(&rmCommand.All, "all", "a", false, "Remove all containers") flags.BoolVarP(&rmCommand.Force, "force", "f", false, "Force removal of a running container. The default is false") flags.BoolVarP(&rmCommand.Latest, "latest", "l", false, "Act on the latest container podman is aware of") - flags.BoolVarP(&rmCommand.Volumes, "volumes", "v", false, "Remove the volumes associated with the container (Not implemented yet)") - + flags.BoolVarP(&rmCommand.Volumes, "volumes", "v", false, "Remove the volumes associated with the container") } // saveCmd saves the image to either docker-archive or oci @@ -79,7 +78,7 @@ func rmCmd(c *cliconfig.RmValues) error { for _, container := range delContainers { con := container f := func() error { - return runtime.RemoveContainer(ctx, con, c.Force) + return runtime.RemoveContainer(ctx, con, c.Force, c.Volumes) } deleteFuncs = append(deleteFuncs, shared.ParallelWorkerInput{ diff --git a/cmd/podman/run.go b/cmd/podman/run.go index 8649dc190..64f8b6856 100644 --- a/cmd/podman/run.go +++ b/cmd/podman/run.go @@ -118,13 +118,21 @@ func runCmd(c *cliconfig.RunValues) error { } } if err := startAttachCtr(ctr, outputStream, errorStream, inputStream, c.String("detach-keys"), c.Bool("sig-proxy"), true); err != nil { + // We've manually detached from the container + // Do not perform cleanup, or wait for container exit code + // Just exit immediately + if errors.Cause(err) == libpod.ErrDetach { + exitCode = 0 + return nil + } + // This means the command did not exist exitCode = 127 if strings.Index(err.Error(), "permission denied") > -1 { exitCode = 126 } if c.IsSet("rm") { - if deleteError := runtime.RemoveContainer(ctx, ctr, true); deleteError != nil { + if deleteError := runtime.RemoveContainer(ctx, ctr, true, false); deleteError != nil { logrus.Errorf("unable to remove container %s after failing to start and attach to it", ctr.ID()) } } @@ -147,28 +155,12 @@ func runCmd(c *cliconfig.RunValues) error { exitCode = int(ecode) } - if createConfig.Rm { - return runtime.RemoveContainer(ctx, ctr, true) - } - - if err := ctr.Cleanup(ctx); err != nil { - // If the container has been removed already, no need to error on cleanup - // Also, if it was restarted, don't error either - if errors.Cause(err) == libpod.ErrNoSuchCtr || - errors.Cause(err) == libpod.ErrCtrRemoved || - errors.Cause(err) == libpod.ErrCtrStateInvalid { - return nil - } - - return err - } - return nil } // Read a container's exit file func readExitFile(runtimeTmp, ctrID string) (int, error) { - exitFile := filepath.Join(runtimeTmp, "exits", ctrID) + exitFile := filepath.Join(runtimeTmp, "exits", fmt.Sprintf("%s-old", ctrID)) logrus.Debugf("Attempting to read container %s exit code from file %s", ctrID, exitFile) diff --git a/cmd/podman/start.go b/cmd/podman/start.go index 344719fca..3a606d662 100644 --- a/cmd/podman/start.go +++ b/cmd/podman/start.go @@ -108,6 +108,13 @@ func startCmd(c *cliconfig.StartValues) error { // attach to the container and also start it not already running err = startAttachCtr(ctr, os.Stdout, os.Stderr, inputStream, c.DetachKeys, sigProxy, !ctrRunning) + if errors.Cause(err) == libpod.ErrDetach { + // User manually detached + // Exit cleanly immediately + exitCode = 0 + return nil + } + if ctrRunning { return err } @@ -137,7 +144,7 @@ func startCmd(c *cliconfig.StartValues) error { logrus.Errorf("unable to detect if container %s should be deleted", ctr.ID()) } if createArtifact.Rm { - if rmErr := runtime.RemoveContainer(ctx, ctr, true); rmErr != nil { + if rmErr := runtime.RemoveContainer(ctx, ctr, true, false); rmErr != nil { logrus.Errorf("unable to remove container %s after it failed to start", ctr.ID()) } } diff --git a/cmd/podman/system_prune.go b/cmd/podman/system_prune.go index a88027558..a91d7bf0a 100644 --- a/cmd/podman/system_prune.go +++ b/cmd/podman/system_prune.go @@ -76,7 +76,7 @@ Are you sure you want to continue? [y/N] `, volumeString) ctx := getContext() fmt.Println("Deleted Containers") - lasterr := pruneContainers(runtime, ctx, shared.Parallelize("rm"), false) + lasterr := pruneContainers(runtime, ctx, shared.Parallelize("rm"), false, false) if c.Bool("volumes") { fmt.Println("Deleted Volumes") err := volumePrune(runtime, getContext()) diff --git a/cmd/podman/varlink/io.podman.varlink b/cmd/podman/varlink/io.podman.varlink index 03ea06dfc..3b7a11fc3 100644 --- a/cmd/podman/varlink/io.podman.varlink +++ b/cmd/podman/varlink/io.podman.varlink @@ -311,33 +311,50 @@ type IDMap ( size: int ) +# BuildOptions are are used to describe describe physical attributes of the build +type BuildOptions ( + addHosts: []string, + cgroupParent: string, + cpuPeriod: int, + cpuQuota: int, + cpuShares: int, + cpusetCpus: string, + cpusetMems: string, + memory: int, + memorySwap: int, + shmSize: string, + ulimit: []string, + volume: []string +) + # BuildInfo is used to describe user input for building images type BuildInfo ( - # paths to one or more dockerfiles - dockerfile: []string, - tags: []string, - add_hosts: []string, - cgroup_parent: string, - cpu_period: int, - cpu_quota: int, - cpu_shares: int, - cpuset_cpus: string, - cpuset_mems: string, - memory: string, - memory_swap: string, - security_opts: []string, - shm_size: string, - ulimit: []string, - volume: []string, - squash: bool, - pull: bool, - pull_always: bool, - force_rm: bool, - rm: bool, - label: []string, + additionalTags: []string, annotations: []string, - build_args: [string]string, - image_format: string + buildArgs: [string]string, + buildOptions: BuildOptions, + cniConfigDir: string, + cniPluginDir: string, + compression: string, + contextDir: string, + defaultsMountFilePath: string, + dockerfiles: []string, + err: string, + forceRmIntermediateCtrs: bool, + iidfile: string, + label: []string, + layers: bool, + nocache: bool, + out: string, + output: string, + outputFormat: string, + pullPolicy: string, + quiet: bool, + remoteIntermediateCtrs: bool, + reportWriter: string, + runtimeArgs: []string, + signaturePolicyPath: string, + squash: bool ) # MoreResponse is a struct for when responses from varlink requires longer output @@ -583,7 +600,7 @@ method GetAttachSockets(name: string) -> (sockets: Sockets) # a [ContainerNotFound](#ContainerNotFound) error is returned. method WaitContainer(name: string) -> (exitcode: int) -# RemoveContainer takes requires the name or ID of container as well a boolean representing whether a running +# RemoveContainer takes requires the name or ID of container as well a boolean representing whether a running and a boolean indicating whether to remove builtin volumes # container can be stopped and removed. Upon successful removal of the container, its ID is returned. If the # container cannot be found by name or ID, a [ContainerNotFound](#ContainerNotFound) error will be returned. # #### Example @@ -593,7 +610,7 @@ method WaitContainer(name: string) -> (exitcode: int) # "container": "62f4fd98cb57f529831e8f90610e54bba74bd6f02920ffb485e15376ed365c20" # } # ~~~ -method RemoveContainer(name: string, force: bool) -> (container: string) +method RemoveContainer(name: string, force: bool, removeVolumes: bool) -> (container: string) # DeleteStoppedContainers will delete all containers that are not running. It will return a list the deleted # container IDs. See also [RemoveContainer](RemoveContainer). diff --git a/commands.md b/commands.md index c7d03d5ad..37fc57f65 100644 --- a/commands.md +++ b/commands.md @@ -73,6 +73,7 @@ | [podman-unpause(1)](/docs/podman-unpause.1.md) | Unpause one or more running containers |[![...](/docs/play.png)](https://asciinema.org/a/141292)| | [podman-varlink(1)](/docs/podman-varlink.1.md) | Run the varlink backend || | [podman-version(1)](/docs/podman-version.1.md) | Display the version information |[![...](/docs/play.png)](https://asciinema.org/a/mfrn61pjZT9Fc8L4NbfdSqfgu)| +| [podman-volume(1)](/docs/podman-volume.1.md) | Manage Volumes || | [podman-volume-create(1)](/docs/podman-volume-create.1.md) | Create a volume || | [podman-volume-inspect(1)](/docs/podman-volume-inspect.1.md) | Get detailed information on one or more volumes || | [podman-volume-ls(1)](/docs/podman-volume-ls.1.md) | List all the available volumes || diff --git a/completions/bash/podman b/completions/bash/podman index 9df87aef4..d367b8237 100644 --- a/completions/bash/podman +++ b/completions/bash/podman @@ -1691,6 +1691,7 @@ _podman_container_run() { --oom-score-adj --pid --pids-limit + --pod --publish -p --runtime --rootfs diff --git a/contrib/perftest/main.go b/contrib/perftest/main.go index 6a6725ab9..c0a91209f 100644 --- a/contrib/perftest/main.go +++ b/contrib/perftest/main.go @@ -218,7 +218,7 @@ func runSingleThreadedStressTest(ctx context.Context, client *libpod.Runtime, im //Delete Container deleteStartTime := time.Now() - err = client.RemoveContainer(ctx, ctr, true) + err = client.RemoveContainer(ctx, ctr, true, false) if err != nil { return nil, err } diff --git a/docs/podman.1.md b/docs/podman.1.md index 51ef00383..760f27310 100644 --- a/docs/podman.1.md +++ b/docs/podman.1.md @@ -168,6 +168,7 @@ the exit codes follow the `chroot` standard, see below: | [podman-umount(1)](podman-umount.1.md) | Unmount a working container's root filesystem. | | [podman-unpause(1)](podman-unpause.1.md) | Unpause one or more containers. | | [podman-version(1)](podman-version.1.md) | Display the Podman version information. | +| [podman-volume(1)](podman-volume.1.md) | Manage Volumes. | | [podman-wait(1)](podman-wait.1.md) | Wait on one or more containers to stop and print their exit codes. | ## FILES diff --git a/libpod/adapter/runtime.go b/libpod/adapter/runtime.go index 7dd845616..3146cf5db 100644 --- a/libpod/adapter/runtime.go +++ b/libpod/adapter/runtime.go @@ -9,6 +9,9 @@ import ( "os" "strconv" + "github.com/containers/buildah" + "github.com/containers/buildah/imagebuildah" + "github.com/containers/buildah/pkg/parse" "github.com/containers/image/docker/reference" "github.com/containers/image/types" "github.com/containers/libpod/cmd/podman/cliconfig" @@ -254,3 +257,51 @@ func libpodVolumeToVolume(volumes []*libpod.Volume) []*Volume { } return vols } + +// Build is the wrapper to build images +func (r *LocalRuntime) Build(ctx context.Context, c *cliconfig.BuildValues, options imagebuildah.BuildOptions, dockerfiles []string) error { + namespaceOptions, networkPolicy, err := parse.NamespaceOptions(c.PodmanCommand.Command) + if err != nil { + return errors.Wrapf(err, "error parsing namespace-related options") + } + usernsOption, idmappingOptions, err := parse.IDMappingOptions(c.PodmanCommand.Command) + if err != nil { + return errors.Wrapf(err, "error parsing ID mapping options") + } + namespaceOptions.AddOrReplace(usernsOption...) + + systemContext, err := parse.SystemContextFromOptions(c.PodmanCommand.Command) + if err != nil { + return errors.Wrapf(err, "error building system context") + } + + authfile := c.Authfile + if len(c.Authfile) == 0 { + authfile = os.Getenv("REGISTRY_AUTH_FILE") + } + + systemContext.AuthFilePath = authfile + commonOpts, err := parse.CommonBuildOptions(c.PodmanCommand.Command) + if err != nil { + return err + } + + options.NamespaceOptions = namespaceOptions + options.ConfigureNetwork = networkPolicy + options.IDMappingOptions = idmappingOptions + options.CommonBuildOpts = commonOpts + options.SystemContext = systemContext + + if c.Flag("runtime").Changed { + options.Runtime = r.GetOCIRuntimePath() + } + if c.Quiet { + options.ReportWriter = ioutil.Discard + } + + if rootless.IsRootless() { + options.Isolation = buildah.IsolationOCIRootless + } + + return r.Runtime.Build(ctx, options, dockerfiles...) +} diff --git a/libpod/adapter/runtime_remote.go b/libpod/adapter/runtime_remote.go index d0d827582..a96676ee2 100644 --- a/libpod/adapter/runtime_remote.go +++ b/libpod/adapter/runtime_remote.go @@ -7,19 +7,22 @@ import ( "context" "encoding/json" "fmt" - "github.com/pkg/errors" "io" + "io/ioutil" "os" "strings" "time" + "github.com/containers/buildah/imagebuildah" "github.com/containers/image/docker/reference" "github.com/containers/image/types" "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/cmd/podman/varlink" "github.com/containers/libpod/libpod" "github.com/containers/libpod/libpod/image" + "github.com/containers/storage/pkg/archive" "github.com/opencontainers/go-digest" + "github.com/pkg/errors" "github.com/sirupsen/logrus" "github.com/varlink/go/varlink" ) @@ -376,6 +379,108 @@ func (r *LocalRuntime) Export(name string, path string) error { // Import implements the remote calls required to import a container image to the store func (r *LocalRuntime) Import(ctx context.Context, source, reference string, changes []string, history string, quiet bool) (string, error) { // First we send the file to the host + tempFile, err := r.SendFileOverVarlink(source) + if err != nil { + return "", err + } + return iopodman.ImportImage().Call(r.Conn, strings.TrimRight(tempFile, ":"), reference, history, changes, true) +} + +func (r *LocalRuntime) Build(ctx context.Context, c *cliconfig.BuildValues, options imagebuildah.BuildOptions, dockerfiles []string) error { + buildOptions := iopodman.BuildOptions{ + AddHosts: options.CommonBuildOpts.AddHost, + CgroupParent: options.CommonBuildOpts.CgroupParent, + CpuPeriod: int64(options.CommonBuildOpts.CPUPeriod), + CpuQuota: options.CommonBuildOpts.CPUQuota, + CpuShares: int64(options.CommonBuildOpts.CPUShares), + CpusetCpus: options.CommonBuildOpts.CPUSetMems, + CpusetMems: options.CommonBuildOpts.CPUSetMems, + Memory: options.CommonBuildOpts.Memory, + MemorySwap: options.CommonBuildOpts.MemorySwap, + ShmSize: options.CommonBuildOpts.ShmSize, + Ulimit: options.CommonBuildOpts.Ulimit, + Volume: options.CommonBuildOpts.Volumes, + } + + buildinfo := iopodman.BuildInfo{ + AdditionalTags: options.AdditionalTags, + Annotations: options.Annotations, + BuildArgs: options.Args, + BuildOptions: buildOptions, + CniConfigDir: options.CNIConfigDir, + CniPluginDir: options.CNIPluginPath, + Compression: string(options.Compression), + DefaultsMountFilePath: options.DefaultMountsFilePath, + Dockerfiles: dockerfiles, + //Err: string(options.Err), + ForceRmIntermediateCtrs: options.ForceRmIntermediateCtrs, + Iidfile: options.IIDFile, + Label: options.Labels, + Layers: options.Layers, + Nocache: options.NoCache, + //Out: + Output: options.Output, + OutputFormat: options.OutputFormat, + PullPolicy: options.PullPolicy.String(), + Quiet: options.Quiet, + RemoteIntermediateCtrs: options.RemoveIntermediateCtrs, + //ReportWriter: + RuntimeArgs: options.RuntimeArgs, + SignaturePolicyPath: options.SignaturePolicyPath, + Squash: options.Squash, + } + // tar the file + logrus.Debugf("creating tarball of context dir %s", options.ContextDirectory) + input, err := archive.Tar(options.ContextDirectory, archive.Uncompressed) + if err != nil { + return errors.Wrapf(err, "unable to create tarball of context dir %s", options.ContextDirectory) + } + + // Write the tarball to the fs + // TODO we might considering sending this without writing to the fs for the sake of performance + // under given conditions like memory availability. + outputFile, err := ioutil.TempFile("", "varlink_tar_send") + if err != nil { + return err + } + defer outputFile.Close() + logrus.Debugf("writing context dir tarball to %s", outputFile.Name()) + + _, err = io.Copy(outputFile, input) + if err != nil { + return err + } + + logrus.Debugf("completed writing context dir tarball %s", outputFile.Name()) + // Send the context dir tarball over varlink. + tempFile, err := r.SendFileOverVarlink(outputFile.Name()) + if err != nil { + return err + } + buildinfo.ContextDir = strings.Replace(tempFile, ":", "", -1) + + reply, err := iopodman.BuildImage().Send(r.Conn, varlink.More, buildinfo) + if err != nil { + return err + } + + for { + responses, flags, err := reply() + if err != nil { + return err + } + for _, line := range responses.Logs { + fmt.Print(line) + } + if flags&varlink.Continues == 0 { + break + } + } + return err +} + +// SendFileOverVarlink sends a file over varlink in an upgraded connection +func (r *LocalRuntime) SendFileOverVarlink(source string) (string, error) { fs, err := os.Open(source) if err != nil { return "", err @@ -385,6 +490,7 @@ func (r *LocalRuntime) Import(ctx context.Context, source, reference string, cha if err != nil { return "", err } + logrus.Debugf("sending %s over varlink connection", source) reply, err := iopodman.SendFile().Send(r.Conn, varlink.Upgrade, "", int64(fileInfo.Size())) if err != nil { return "", err @@ -399,6 +505,7 @@ func (r *LocalRuntime) Import(ctx context.Context, source, reference string, cha if err != nil { return "", err } + logrus.Debugf("file transfer complete for %s", source) r.Conn.Writer.Flush() // All was sent, wait for the ACK from the server @@ -412,7 +519,8 @@ func (r *LocalRuntime) Import(ctx context.Context, source, reference string, cha return "", err } - return iopodman.ImportImage().Call(r.Conn, strings.TrimRight(tempFile, ":"), reference, history, changes, true) + + return tempFile, nil } // GetAllVolumes retrieves all the volumes @@ -436,7 +544,7 @@ func (r *LocalRuntime) GetContainers(filters ...libpod.ContainerFilter) ([]*libp // RemoveContainer removes the given container // If force is specified, the container will be stopped first // Otherwise, RemoveContainer will return an error if the container is running -func (r *LocalRuntime) RemoveContainer(ctx context.Context, c *libpod.Container, force bool) error { +func (r *LocalRuntime) RemoveContainer(ctx context.Context, c *libpod.Container, force, volumes bool) error { return libpod.ErrNotImplemented } diff --git a/libpod/container.go b/libpod/container.go index fec61533d..75f4a4a4f 100644 --- a/libpod/container.go +++ b/libpod/container.go @@ -358,8 +358,7 @@ type ContainerConfig struct { ExitCommand []string `json:"exitCommand,omitempty"` // LocalVolumes are the built-in volumes we get from the --volumes-from flag // It picks up the built-in volumes of the container used by --volumes-from - LocalVolumes []string - + LocalVolumes []spec.Mount // IsInfra is a bool indicating whether this container is an infra container used for // sharing kernel namespaces in a pod IsInfra bool `json:"pause"` diff --git a/libpod/container_attach_linux.go b/libpod/container_attach_linux.go index 1d6f0bd96..3ff6ddc76 100644 --- a/libpod/container_attach_linux.go +++ b/libpod/container_attach_linux.go @@ -109,8 +109,8 @@ func (c *Container) attachContainerSocket(resize <-chan remotecommand.TerminalSi case err := <-receiveStdoutError: return err case err := <-stdinDone: - if _, ok := err.(utils.DetachError); ok { - return nil + if err == ErrDetach { + return err } if streams.AttachOutput || streams.AttachError { return <-receiveStdoutError diff --git a/libpod/container_internal.go b/libpod/container_internal.go index b0dcc853e..b2ebad777 100644 --- a/libpod/container_internal.go +++ b/libpod/container_internal.go @@ -10,21 +10,16 @@ import ( "path/filepath" "strconv" "strings" - "syscall" "time" - "github.com/containers/buildah/imagebuildah" "github.com/containers/libpod/pkg/ctime" "github.com/containers/libpod/pkg/hooks" "github.com/containers/libpod/pkg/hooks/exec" "github.com/containers/libpod/pkg/rootless" "github.com/containers/storage" "github.com/containers/storage/pkg/archive" - "github.com/containers/storage/pkg/chrootarchive" "github.com/containers/storage/pkg/mount" - "github.com/opencontainers/runc/libcontainer/user" spec "github.com/opencontainers/runtime-spec/specs-go" - "github.com/opencontainers/runtime-tools/generate" "github.com/opencontainers/selinux/go-selinux/label" "github.com/pkg/errors" "github.com/sirupsen/logrus" @@ -489,9 +484,20 @@ func (c *Container) removeConmonFiles() error { return errors.Wrapf(err, "error removing container %s OOM file", c.ID()) } + // Instead of outright deleting the exit file, rename it (if it exists). + // We want to retain it so we can get the exit code of containers which + // are removed (at least until we have a workable events system) exitFile := filepath.Join(c.runtime.ociRuntime.exitsDir, c.ID()) - if err := os.Remove(exitFile); err != nil && !os.IsNotExist(err) { - return errors.Wrapf(err, "error removing container %s exit file", c.ID()) + oldExitFile := filepath.Join(c.runtime.ociRuntime.exitsDir, fmt.Sprintf("%s-old", c.ID())) + if _, err := os.Stat(exitFile); err != nil { + if !os.IsNotExist(err) { + return errors.Wrapf(err, "error running stat on container %s exit file", c.ID()) + } + } else if err == nil { + // Rename should replace the old exit file (if it exists) + if err := os.Rename(exitFile, oldExitFile); err != nil { + return errors.Wrapf(err, "error renaming container %s exit file", c.ID()) + } } return nil @@ -1042,113 +1048,6 @@ func (c *Container) writeStringToRundir(destFile, output string) (string, error) return filepath.Join(c.state.DestinationRunDir, destFile), nil } -func (c *Container) addLocalVolumes(ctx context.Context, g *generate.Generator, execUser *user.ExecUser) error { - var uid, gid int - mountPoint := c.state.Mountpoint - if !c.state.Mounted { - return errors.Wrapf(ErrInternal, "container is not mounted") - } - newImage, err := c.runtime.imageRuntime.NewFromLocal(c.config.RootfsImageID) - if err != nil { - return err - } - imageData, err := newImage.Inspect(ctx) - if err != nil { - return err - } - // Add the built-in volumes of the container passed in to --volumes-from - for _, vol := range c.config.LocalVolumes { - if imageData.Config.Volumes == nil { - imageData.Config.Volumes = map[string]struct{}{ - vol: {}, - } - } else { - imageData.Config.Volumes[vol] = struct{}{} - } - } - - if c.config.User != "" { - if execUser == nil { - return errors.Wrapf(ErrInternal, "nil pointer passed to addLocalVolumes for execUser") - } - uid = execUser.Uid - gid = execUser.Gid - } - - for k := range imageData.Config.Volumes { - mount := spec.Mount{ - Destination: k, - Type: "bind", - Options: []string{"private", "bind", "rw"}, - } - if MountExists(g.Mounts(), k) { - continue - } - volumePath := filepath.Join(c.config.StaticDir, "volumes", k) - - // Ensure the symlinks are resolved - resolvedSymlink, err := imagebuildah.ResolveSymLink(mountPoint, k) - if err != nil { - return errors.Wrapf(ErrCtrStateInvalid, "cannot resolve %s in %s for container %s", k, mountPoint, c.ID()) - } - var srcPath string - if resolvedSymlink != "" { - srcPath = filepath.Join(mountPoint, resolvedSymlink) - } else { - srcPath = filepath.Join(mountPoint, k) - } - - if _, err := os.Stat(srcPath); os.IsNotExist(err) { - logrus.Infof("Volume image mount point %s does not exist in root FS, need to create it", k) - if err = os.MkdirAll(srcPath, 0755); err != nil { - return errors.Wrapf(err, "error creating directory %q for volume %q in container %q", volumePath, k, c.ID()) - } - - if err = os.Chown(srcPath, uid, gid); err != nil { - return errors.Wrapf(err, "error chowning directory %q for volume %q in container %q", srcPath, k, c.ID()) - } - } - - if _, err := os.Stat(volumePath); os.IsNotExist(err) { - if err = os.MkdirAll(volumePath, 0755); err != nil { - return errors.Wrapf(err, "error creating directory %q for volume %q in container %q", volumePath, k, c.ID()) - } - - if err = os.Chown(volumePath, uid, gid); err != nil { - return errors.Wrapf(err, "error chowning directory %q for volume %q in container %q", volumePath, k, c.ID()) - } - - if err = label.Relabel(volumePath, c.config.MountLabel, false); err != nil { - return errors.Wrapf(err, "error relabeling directory %q for volume %q in container %q", volumePath, k, c.ID()) - } - if err = chrootarchive.NewArchiver(nil).CopyWithTar(srcPath, volumePath); err != nil && !os.IsNotExist(err) { - return errors.Wrapf(err, "error populating directory %q for volume %q in container %q using contents of %q", volumePath, k, c.ID(), srcPath) - } - - // Set the volume path with the same owner and permission of source path - sstat, _ := os.Stat(srcPath) - st, ok := sstat.Sys().(*syscall.Stat_t) - if !ok { - return fmt.Errorf("could not convert to syscall.Stat_t") - } - uid := int(st.Uid) - gid := int(st.Gid) - - if err := os.Lchown(volumePath, uid, gid); err != nil { - return err - } - if os.Chmod(volumePath, sstat.Mode()); err != nil { - return err - } - - } - - mount.Source = volumePath - g.AddMount(mount) - } - return nil -} - // Save OCI spec to disk, replacing any existing specs for the container func (c *Container) saveSpec(spec *spec.Spec) error { // If the OCI spec already exists, we need to replace it @@ -1292,3 +1191,30 @@ func getExcludedCGroups() (excludes []string) { excludes = []string{"rdma"} return } + +// namedVolumes returns named volumes for the container +func (c *Container) namedVolumes() ([]string, error) { + var volumes []string + for _, vol := range c.config.Spec.Mounts { + if strings.HasPrefix(vol.Source, c.runtime.config.VolumePath) { + volume := strings.TrimPrefix(vol.Source, c.runtime.config.VolumePath+"/") + split := strings.Split(volume, "/") + volume = split[0] + if _, err := c.runtime.state.Volume(volume); err == nil { + volumes = append(volumes, volume) + } + } + } + return volumes, nil +} + +// this should be from chrootarchive. +func (c *Container) copyWithTarFromImage(src, dest string) error { + mountpoint, err := c.mount() + if err != nil { + return err + } + a := archive.NewDefaultArchiver() + source := filepath.Join(mountpoint, src) + return a.CopyWithTar(source, dest) +} diff --git a/libpod/container_internal_linux.go b/libpod/container_internal_linux.go index bcdfdaee3..65cb47c8c 100644 --- a/libpod/container_internal_linux.go +++ b/libpod/container_internal_linux.go @@ -235,13 +235,6 @@ func (c *Container) generateSpec(ctx context.Context) (*spec.Spec, error) { } } - // Bind builtin image volumes - if c.config.Rootfs == "" && c.config.ImageVolumes { - if err := c.addLocalVolumes(ctx, &g, execUser); err != nil { - return nil, errors.Wrapf(err, "error mounting image volumes") - } - } - if c.config.User != "" { // User and Group must go together g.SetProcessUID(uint32(execUser.Uid)) diff --git a/libpod/errors.go b/libpod/errors.go index 30a19d30f..dd82d0796 100644 --- a/libpod/errors.go +++ b/libpod/errors.go @@ -4,6 +4,7 @@ import ( "errors" "github.com/containers/libpod/libpod/image" + "github.com/containers/libpod/utils" ) var ( @@ -56,6 +57,10 @@ var ( // ErrInternal indicates an internal library error ErrInternal = errors.New("internal libpod error") + // ErrDetach indicates that an attach session was manually detached by + // the user. + ErrDetach = utils.ErrDetach + // 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/options.go b/libpod/options.go index d965c058e..06737776b 100644 --- a/libpod/options.go +++ b/libpod/options.go @@ -11,6 +11,7 @@ import ( "github.com/containers/storage" "github.com/containers/storage/pkg/idtools" "github.com/cri-o/ocicni/pkg/ocicni" + spec "github.com/opencontainers/runtime-spec/specs-go" "github.com/pkg/errors" ) @@ -1058,7 +1059,7 @@ func WithUserVolumes(volumes []string) CtrCreateOption { // from a container passed in to the --volumes-from flag. // This stores the built-in volume information in the Config so we can // add them when creating the container. -func WithLocalVolumes(volumes []string) CtrCreateOption { +func WithLocalVolumes(volumes []spec.Mount) CtrCreateOption { return func(ctr *Container) error { if ctr.valid { return ErrCtrFinalized diff --git a/libpod/runtime_ctr.go b/libpod/runtime_ctr.go index 4f8192198..185090cf7 100644 --- a/libpod/runtime_ctr.go +++ b/libpod/runtime_ctr.go @@ -177,9 +177,12 @@ func (r *Runtime) newContainer(ctx context.Context, rSpec *spec.Spec, options .. if err != nil { newVol, err := r.newVolume(ctx, WithVolumeName(vol.Source)) if err != nil { - logrus.Errorf("error creating named volume %q: %v", vol.Source, err) + return nil, errors.Wrapf(err, "error creating named volume %q", vol.Source) } ctr.config.Spec.Mounts[i].Source = newVol.MountPoint() + if err := ctr.copyWithTarFromImage(ctr.config.Spec.Mounts[i].Destination, ctr.config.Spec.Mounts[i].Source); err != nil && !os.IsNotExist(err) { + return nil, errors.Wrapf(err, "Failed to copy content into new volume mount %q", vol.Source) + } continue } ctr.config.Spec.Mounts[i].Source = volInfo.MountPoint() @@ -225,17 +228,19 @@ func (r *Runtime) newContainer(ctx context.Context, rSpec *spec.Spec, options .. // RemoveContainer removes the given container // If force is specified, the container will be stopped first +// If removeVolume is specified, named volumes used by the container will +// be removed also if and only if the container is the sole user // Otherwise, RemoveContainer will return an error if the container is running -func (r *Runtime) RemoveContainer(ctx context.Context, c *Container, force bool) error { +func (r *Runtime) RemoveContainer(ctx context.Context, c *Container, force bool, removeVolume bool) error { r.lock.Lock() defer r.lock.Unlock() - return r.removeContainer(ctx, c, force) + return r.removeContainer(ctx, c, force, removeVolume) } // Internal function to remove a container // Locks the container, but does not lock the runtime -func (r *Runtime) removeContainer(ctx context.Context, c *Container, force bool) error { +func (r *Runtime) removeContainer(ctx context.Context, c *Container, force bool, removeVolume bool) error { if !c.valid { if ok, _ := r.state.HasContainer(c.ID()); !ok { // Container probably already removed @@ -248,6 +253,7 @@ func (r *Runtime) removeContainer(ctx context.Context, c *Container, force bool) // To avoid races around removing a container and the pod it is in var pod *Pod var err error + runtime := c.runtime if c.config.Pod != "" { pod, err = r.state.Pod(c.config.Pod) if err != nil { @@ -333,6 +339,13 @@ func (r *Runtime) removeContainer(ctx context.Context, c *Container, force bool) return errors.Wrapf(ErrCtrExists, "container %s has dependent containers which must be removed before it: %s", c.ID(), depsStr) } + var volumes []string + if removeVolume { + volumes, err = c.namedVolumes() + if err != nil { + logrus.Errorf("unable to retrieve builtin volumes for container %v: %v", c.ID(), err) + } + } var cleanupErr error // Remove the container from the state if c.config.Pod != "" { @@ -397,6 +410,14 @@ func (r *Runtime) removeContainer(ctx context.Context, c *Container, force bool) } } + for _, v := range volumes { + if volume, err := runtime.state.Volume(v); err == nil { + if err := runtime.removeVolume(ctx, volume, false, true); err != nil && err != ErrNoSuchVolume && err != ErrVolumeBeingUsed { + logrus.Errorf("cleanup volume (%s): %v", v, err) + } + } + } + return cleanupErr } diff --git a/libpod/runtime_img.go b/libpod/runtime_img.go index c20aa77a3..1e9689362 100644 --- a/libpod/runtime_img.go +++ b/libpod/runtime_img.go @@ -43,7 +43,7 @@ func (r *Runtime) RemoveImage(ctx context.Context, img *image.Image, force bool) if len(imageCtrs) > 0 && len(img.Names()) <= 1 { if force { for _, ctr := range imageCtrs { - if err := r.removeContainer(ctx, ctr, true); err != nil { + if err := r.removeContainer(ctx, ctr, true, false); err != nil { return "", errors.Wrapf(err, "error removing image %s: container %s using image could not be removed", img.ID(), ctr.ID()) } } diff --git a/libpod/volume_internal.go b/libpod/volume_internal.go index 800e6d106..0de8a2350 100644 --- a/libpod/volume_internal.go +++ b/libpod/volume_internal.go @@ -5,10 +5,6 @@ import ( "path/filepath" ) -// VolumePath is the path under which all volumes that are created using the -// local driver will be created -// const VolumePath = "/var/lib/containers/storage/volumes" - // Creates a new volume func newVolume(runtime *Runtime) (*Volume, error) { volume := new(Volume) diff --git a/pkg/spec/createconfig.go b/pkg/spec/createconfig.go index f6c77d61c..8da44a2f0 100644 --- a/pkg/spec/createconfig.go +++ b/pkg/spec/createconfig.go @@ -13,6 +13,7 @@ import ( "github.com/containers/libpod/pkg/namespaces" "github.com/containers/libpod/pkg/rootless" "github.com/containers/storage" + "github.com/containers/storage/pkg/stringid" "github.com/cri-o/ocicni/pkg/ocicni" "github.com/docker/go-connections/nat" spec "github.com/opencontainers/runtime-spec/specs-go" @@ -134,8 +135,8 @@ type CreateConfig struct { SeccompProfilePath string //SecurityOpts SecurityOpts []string Rootfs string - LocalVolumes []string //Keeps track of the built-in volumes of container used in the --volumes-from flag - Syslog bool // Whether to enable syslog on exit commands + LocalVolumes []spec.Mount //Keeps track of the built-in volumes of container used in the --volumes-from flag + Syslog bool // Whether to enable syslog on exit commands } func u32Ptr(i int64) *uint32 { u := uint32(i); return &u } @@ -216,7 +217,7 @@ func (c *CreateConfig) initFSMounts() []spec.Mount { //GetVolumeMounts takes user provided input for bind mounts and creates Mount structs func (c *CreateConfig) GetVolumeMounts(specMounts []spec.Mount) ([]spec.Mount, error) { - var m []spec.Mount + m := c.LocalVolumes for _, i := range c.Volumes { var options []string spliti := strings.Split(i, ":") @@ -234,22 +235,31 @@ func (c *CreateConfig) GetVolumeMounts(specMounts []spec.Mount) ([]spec.Mount, e logrus.Debugf("User mount %s:%s options %v", spliti[0], spliti[1], options) } - // volumes from image config - if c.ImageVolumeType != "tmpfs" { + if c.ImageVolumeType == "ignore" { return m, nil } + for vol := range c.BuiltinImgVolumes { if libpod.MountExists(specMounts, vol) { continue } + mount := spec.Mount{ Destination: vol, - Type: string(TypeTmpfs), - Source: string(TypeTmpfs), - Options: []string{"rprivate", "rw", "noexec", "nosuid", "nodev", "tmpcopyup"}, + Type: c.ImageVolumeType, + Options: []string{"rprivate", "rw", "nodev"}, + } + if c.ImageVolumeType == "tmpfs" { + mount.Source = "tmpfs" + mount.Options = append(mount.Options, "tmpcopyup") + } else { + // This will cause a new local Volume to be created on your system + mount.Source = stringid.GenerateNonCryptoID() + mount.Options = append(mount.Options, "bind") } m = append(m, mount) } + return m, nil } @@ -257,6 +267,11 @@ func (c *CreateConfig) GetVolumeMounts(specMounts []spec.Mount) ([]spec.Mount, e // and adds it to c.Volumes of the current container. func (c *CreateConfig) GetVolumesFrom() error { var options string + + if rootless.SkipStorageSetup() { + return nil + } + for _, vol := range c.VolumesFrom { splitVol := strings.SplitN(vol, ":", 2) if len(splitVol) == 2 { @@ -266,6 +281,10 @@ func (c *CreateConfig) GetVolumesFrom() error { if err != nil { return errors.Wrapf(err, "error looking up container %q", splitVol[0]) } + inspect, err := ctr.Inspect(false) + if err != nil { + return errors.Wrapf(err, "error inspecting %q", splitVol[0]) + } var createArtifact CreateConfig artifact, err := ctr.GetArtifact("create-config") if err != nil { @@ -274,9 +293,13 @@ func (c *CreateConfig) GetVolumesFrom() error { if err := json.Unmarshal(artifact, &createArtifact); err != nil { return err } - for key := range createArtifact.BuiltinImgVolumes { - c.LocalVolumes = append(c.LocalVolumes, key) + for _, m := range inspect.Mounts { + if m.Destination == key { + c.LocalVolumes = append(c.LocalVolumes, m) + break + } + } } for _, i := range createArtifact.Volumes { @@ -340,7 +363,13 @@ func (c *CreateConfig) createExitCommand() []string { if c.Syslog { command = append(command, "--syslog") } - return append(command, []string{"container", "cleanup"}...) + command = append(command, []string{"container", "cleanup"}...) + + if c.Rm { + command = append(command, "--rm") + } + + return command } // GetContainerCreateOptions takes a CreateConfig and returns a slice of CtrCreateOptions @@ -518,11 +547,9 @@ func (c *CreateConfig) GetContainerCreateOptions(runtime *libpod.Runtime, pod *l if c.CgroupParent != "" { options = append(options, libpod.WithCgroupParent(c.CgroupParent)) } - // For a rootless container always cleanup the storage/network as they - // run in a different namespace thus not reusable when we restart. - if c.Detach || rootless.IsRootless() { - options = append(options, libpod.WithExitCommand(c.createExitCommand())) - } + + // Always use a cleanup process to clean up Podman after termination + options = append(options, libpod.WithExitCommand(c.createExitCommand())) return options, nil } diff --git a/pkg/util/utils.go b/pkg/util/utils.go index 52f431881..db8a3d5bb 100644 --- a/pkg/util/utils.go +++ b/pkg/util/utils.go @@ -259,8 +259,8 @@ func GetRootlessStorageOpts() (storage.StoreOptions, error) { return opts, nil } -// GetRootlessVolumeInfo returns where all the name volumes will be created in rootless mode -func GetRootlessVolumeInfo() (string, error) { +// GetRootlessVolumePath returns where all the name volumes will be created in rootless mode +func GetRootlessVolumePath() (string, error) { dataDir, _, err := GetRootlessDirInfo() if err != nil { return "", err @@ -307,15 +307,13 @@ func GetDefaultStoreOptions() (storage.StoreOptions, string, error) { err error ) storageOpts := storage.DefaultStoreOptions - volumePath := "/var/lib/containers/storage" - + volumePath := filepath.Join(storageOpts.GraphRoot, "volumes") if rootless.IsRootless() { storageOpts, err = GetRootlessStorageOpts() if err != nil { return storageOpts, volumePath, err } - - volumePath, err = GetRootlessVolumeInfo() + volumePath, err = GetRootlessVolumePath() if err != nil { return storageOpts, volumePath, err } diff --git a/pkg/varlinkapi/containers.go b/pkg/varlinkapi/containers.go index 2b2832838..8a52efa61 100644 --- a/pkg/varlinkapi/containers.go +++ b/pkg/varlinkapi/containers.go @@ -358,13 +358,13 @@ func (i *LibpodAPI) WaitContainer(call iopodman.VarlinkCall, name string) error } // RemoveContainer ... -func (i *LibpodAPI) RemoveContainer(call iopodman.VarlinkCall, name string, force bool) error { +func (i *LibpodAPI) RemoveContainer(call iopodman.VarlinkCall, name string, force bool, removeVolumes bool) error { ctx := getContext() ctr, err := i.Runtime.LookupContainer(name) if err != nil { return call.ReplyContainerNotFound(name) } - if err := i.Runtime.RemoveContainer(ctx, ctr, force); err != nil { + if err := i.Runtime.RemoveContainer(ctx, ctr, force, removeVolumes); err != nil { return call.ReplyErrorOccurred(err.Error()) } return call.ReplyRemoveContainer(ctr.ID()) @@ -385,7 +385,7 @@ func (i *LibpodAPI) DeleteStoppedContainers(call iopodman.VarlinkCall) error { return call.ReplyErrorOccurred(err.Error()) } if state != libpod.ContainerStateRunning { - if err := i.Runtime.RemoveContainer(ctx, ctr, false); err != nil { + if err := i.Runtime.RemoveContainer(ctx, ctr, false, false); err != nil { return call.ReplyErrorOccurred(err.Error()) } deletedContainers = append(deletedContainers, ctr.ID()) diff --git a/pkg/varlinkapi/containers_create.go b/pkg/varlinkapi/containers_create.go index f1835a189..6b53b22c6 100644 --- a/pkg/varlinkapi/containers_create.go +++ b/pkg/varlinkapi/containers_create.go @@ -131,9 +131,14 @@ func varlinkCreateToCreateConfig(ctx context.Context, create iopodman.Create, ru } imageID := data.ID + var ImageVolumes map[string]struct{} + if data != nil && create.Image_volume_type != "ignore" { + ImageVolumes = data.Config.Volumes + } + config := &cc.CreateConfig{ Runtime: runtime, - BuiltinImgVolumes: data.Config.Volumes, + BuiltinImgVolumes: ImageVolumes, ConmonPidFile: create.Conmon_pidfile, ImageVolumeType: create.Image_volume_type, CapAdd: create.Cap_add, diff --git a/pkg/varlinkapi/images.go b/pkg/varlinkapi/images.go index ca920dfeb..534419f6f 100644 --- a/pkg/varlinkapi/images.go +++ b/pkg/varlinkapi/images.go @@ -5,6 +5,7 @@ import ( "encoding/json" "fmt" "io" + "io/ioutil" "os" "path/filepath" "strings" @@ -24,7 +25,7 @@ import ( sysreg "github.com/containers/libpod/pkg/registries" "github.com/containers/libpod/pkg/util" "github.com/containers/libpod/utils" - "github.com/docker/go-units" + "github.com/containers/storage/pkg/archive" "github.com/opencontainers/image-spec/specs-go/v1" "github.com/opencontainers/runtime-spec/specs-go" "github.com/pkg/errors" @@ -110,83 +111,46 @@ func (i *LibpodAPI) GetImage(call iopodman.VarlinkCall, id string) error { // BuildImage ... func (i *LibpodAPI) BuildImage(call iopodman.VarlinkCall, config iopodman.BuildInfo) error { var ( - memoryLimit int64 - memorySwap int64 - namespace []buildah.NamespaceOption - err error + namespace []buildah.NamespaceOption + err error ) systemContext := types.SystemContext{} - dockerfiles := config.Dockerfile - contextDir := "" - - 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/") { - continue - } - absFile, err := filepath.Abs(dockerfiles[i]) - if err != nil { - return errors.Wrapf(err, "error determining path to file %q", dockerfiles[i]) - } - contextDir = filepath.Dir(absFile) - dockerfiles[i], err = filepath.Rel(contextDir, absFile) - if err != nil { - return errors.Wrapf(err, "error determining path to file %q", dockerfiles[i]) - } - break - } - - pullPolicy := imagebuildah.PullNever - if config.Pull { - pullPolicy = imagebuildah.PullIfMissing - } - - if config.Pull_always { - pullPolicy = imagebuildah.PullAlways - } - manifestType := "oci" //nolint - if config.Image_format != "" { - manifestType = config.Image_format - } + contextDir := config.ContextDir - if strings.HasPrefix(manifestType, "oci") { - manifestType = buildah.OCIv1ImageManifest - } else if strings.HasPrefix(manifestType, "docker") { - manifestType = buildah.Dockerv2ImageManifest - } else { - return call.ReplyErrorOccurred(fmt.Sprintf("unrecognized image type %q", manifestType)) + newContextDir, err := ioutil.TempDir("", "buildTarball") + if err != nil { + call.ReplyErrorOccurred("unable to create tempdir") } + logrus.Debugf("created new context dir at %s", newContextDir) - if config.Memory != "" { - memoryLimit, err = units.RAMInBytes(config.Memory) - if err != nil { - return call.ReplyErrorOccurred(err.Error()) - } + reader, err := os.Open(contextDir) + if err != nil { + logrus.Errorf("failed to open the context dir tar file %s", contextDir) + return call.ReplyErrorOccurred(fmt.Sprintf("unable to open context dir tar file %s", contextDir)) } - - if config.Memory_swap != "" { - memorySwap, err = units.RAMInBytes(config.Memory_swap) - if err != nil { - return call.ReplyErrorOccurred(err.Error()) - } + defer reader.Close() + if err := archive.Untar(reader, newContextDir, &archive.TarOptions{}); err != nil { + logrus.Errorf("fail to untar the context dir tarball (%s) to the context dir (%s)", contextDir, newContextDir) + return call.ReplyErrorOccurred(fmt.Sprintf("unable to untar context dir %s", contextDir)) } + logrus.Debugf("untar of %s successful", contextDir) + // All output (stdout, stderr) is captured in output as well output := bytes.NewBuffer([]byte{}) + commonOpts := &buildah.CommonBuildOptions{ - AddHost: config.Add_hosts, - CgroupParent: config.Cgroup_parent, - CPUPeriod: uint64(config.Cpu_period), - CPUQuota: config.Cpu_quota, - CPUSetCPUs: config.Cpuset_cpus, - CPUSetMems: config.Cpuset_mems, - Memory: memoryLimit, - MemorySwap: memorySwap, - ShmSize: config.Shm_size, - Ulimit: config.Ulimit, - Volumes: config.Volume, + AddHost: config.BuildOptions.AddHosts, + CgroupParent: config.BuildOptions.CgroupParent, + CPUPeriod: uint64(config.BuildOptions.CpuPeriod), + CPUQuota: config.BuildOptions.CpuQuota, + CPUSetCPUs: config.BuildOptions.CpusetCpus, + CPUSetMems: config.BuildOptions.CpusetMems, + Memory: config.BuildOptions.Memory, + MemorySwap: config.BuildOptions.MemorySwap, + ShmSize: config.BuildOptions.ShmSize, + Ulimit: config.BuildOptions.Ulimit, + Volumes: config.BuildOptions.Volume, } hostNetwork := buildah.NamespaceOption{ @@ -197,37 +161,68 @@ func (i *LibpodAPI) BuildImage(call iopodman.VarlinkCall, config iopodman.BuildI namespace = append(namespace, hostNetwork) options := imagebuildah.BuildOptions{ - ContextDirectory: contextDir, - PullPolicy: pullPolicy, - Compression: imagebuildah.Gzip, - Quiet: false, - //SignaturePolicyPath: - Args: config.Build_args, - //Output: - AdditionalTags: config.Tags, - //Runtime: runtime. - //RuntimeArgs: , - OutputFormat: manifestType, - SystemContext: &systemContext, - CommonBuildOpts: commonOpts, - Squash: config.Squash, - Labels: config.Label, - Annotations: config.Annotations, - ReportWriter: output, - NamespaceOptions: namespace, + CommonBuildOpts: commonOpts, + AdditionalTags: config.AdditionalTags, + Annotations: config.Annotations, + Args: config.BuildArgs, + CNIConfigDir: config.CniConfigDir, + CNIPluginPath: config.CniPluginDir, + Compression: stringCompressionToArchiveType(config.Compression), + ContextDirectory: newContextDir, + DefaultMountsFilePath: config.DefaultsMountFilePath, + Err: output, + ForceRmIntermediateCtrs: config.ForceRmIntermediateCtrs, + IIDFile: config.Iidfile, + Labels: config.Label, + Layers: config.Layers, + NoCache: config.Nocache, + Out: output, + Output: config.Output, + NamespaceOptions: namespace, + OutputFormat: config.OutputFormat, + PullPolicy: stringPullPolicyToType(config.PullPolicy), + Quiet: config.Quiet, + RemoveIntermediateCtrs: config.RemoteIntermediateCtrs, + ReportWriter: output, + RuntimeArgs: config.RuntimeArgs, + SignaturePolicyPath: config.SignaturePolicyPath, + Squash: config.Squash, + SystemContext: &systemContext, } if call.WantsMore() { call.Continues = true } - c := build(i.Runtime, options, config.Dockerfile) + var newPathDockerFiles []string + + for _, d := range config.Dockerfiles { + if strings.HasPrefix(d, "http://") || + strings.HasPrefix(d, "https://") || + strings.HasPrefix(d, "git://") || + strings.HasPrefix(d, "github.com/") { + newPathDockerFiles = append(newPathDockerFiles, d) + continue + } + base := filepath.Base(d) + newPathDockerFiles = append(newPathDockerFiles, filepath.Join(newContextDir, base)) + } + + c := build(i.Runtime, options, newPathDockerFiles) var log []string done := false for { - line, err := output.ReadString('\n') + outputLine, err := output.ReadString('\n') if err == nil { - log = append(log, line) + log = append(log, outputLine) + if call.WantsMore() { + // we want to reply with what we have + br := iopodman.MoreResponse{ + Logs: log, + } + call.ReplyBuildImage(br) + log = []string{} + } continue } else if err == io.EOF { select { @@ -237,15 +232,10 @@ func (i *LibpodAPI) BuildImage(call iopodman.VarlinkCall, config iopodman.BuildI } done = true default: - if !call.WantsMore() { + if call.WantsMore() { time.Sleep(1 * time.Second) break } - br := iopodman.MoreResponse{ - Logs: log, - } - call.ReplyBuildImage(br) - log = []string{} } } else { return call.ReplyErrorOccurred(err.Error()) @@ -255,7 +245,8 @@ func (i *LibpodAPI) BuildImage(call iopodman.VarlinkCall, config iopodman.BuildI } } call.Continues = false - newImage, err := i.Runtime.ImageRuntime().NewFromLocal(config.Tags[0]) + + newImage, err := i.Runtime.ImageRuntime().NewFromLocal(config.Output) if err != nil { return call.ReplyErrorOccurred(err.Error()) } diff --git a/pkg/varlinkapi/transfers.go b/pkg/varlinkapi/transfers.go index 0cb7e5e2e..9a97bc810 100644 --- a/pkg/varlinkapi/transfers.go +++ b/pkg/varlinkapi/transfers.go @@ -8,6 +8,7 @@ import ( "os" "github.com/containers/libpod/cmd/podman/varlink" + "github.com/sirupsen/logrus" ) // SendFile allows a client to send a file to the varlink server @@ -34,6 +35,7 @@ func (i *LibpodAPI) SendFile(call iopodman.VarlinkCall, ftype string, length int return err } + logrus.Debugf("successfully received %s", outputFile.Name()) // Send an ACK to the client call.Call.Writer.WriteString(fmt.Sprintf("%s:", outputFile.Name())) call.Call.Writer.Flush() diff --git a/pkg/varlinkapi/util.go b/pkg/varlinkapi/util.go index 7f6f95d3b..7e487c03a 100644 --- a/pkg/varlinkapi/util.go +++ b/pkg/varlinkapi/util.go @@ -3,11 +3,14 @@ package varlinkapi import ( "context" "strconv" + "strings" "time" + "github.com/containers/buildah" "github.com/containers/libpod/cmd/podman/shared" "github.com/containers/libpod/cmd/podman/varlink" "github.com/containers/libpod/libpod" + "github.com/containers/storage/pkg/archive" ) // getContext returns a non-nil, empty context @@ -133,3 +136,27 @@ func handlePodCall(call iopodman.VarlinkCall, pod *libpod.Pod, ctrErrs map[strin return nil } + +func stringCompressionToArchiveType(s string) archive.Compression { + switch strings.ToUpper(s) { + case "BZIP2": + return archive.Bzip2 + case "GZIP": + return archive.Gzip + case "XZ": + return archive.Xz + } + return archive.Uncompressed +} + +func stringPullPolicyToType(s string) buildah.PullPolicy { + switch strings.ToUpper(s) { + case "PULLIFMISSING": + return buildah.PullIfMissing + case "PULLALWAYS": + return buildah.PullAlways + case "PULLNEVER": + return buildah.PullNever + } + return buildah.PullIfMissing +} diff --git a/test/e2e/create_test.go b/test/e2e/create_test.go index b28a0c428..9a526b778 100644 --- a/test/e2e/create_test.go +++ b/test/e2e/create_test.go @@ -142,7 +142,7 @@ var _ = Describe("Podman create", func() { } mountPath := filepath.Join(podmanTest.TempDir, "secrets") os.Mkdir(mountPath, 0755) - session := podmanTest.Podman([]string{"create", "--name", "test", "--rm", "--mount", fmt.Sprintf("type=bind,src=%s,target=/create/test", mountPath), ALPINE, "grep", "/create/test", "/proc/self/mountinfo"}) + session := podmanTest.Podman([]string{"create", "--name", "test", "--mount", fmt.Sprintf("type=bind,src=%s,target=/create/test", mountPath), ALPINE, "grep", "/create/test", "/proc/self/mountinfo"}) session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) session = podmanTest.Podman([]string{"start", "test"}) @@ -153,7 +153,7 @@ var _ = Describe("Podman create", func() { Expect(session.ExitCode()).To(Equal(0)) Expect(session.OutputToString()).To(ContainSubstring("/create/test rw")) - session = podmanTest.Podman([]string{"create", "--name", "test_ro", "--rm", "--mount", fmt.Sprintf("type=bind,src=%s,target=/create/test,ro", mountPath), ALPINE, "grep", "/create/test", "/proc/self/mountinfo"}) + session = podmanTest.Podman([]string{"create", "--name", "test_ro", "--mount", fmt.Sprintf("type=bind,src=%s,target=/create/test,ro", mountPath), ALPINE, "grep", "/create/test", "/proc/self/mountinfo"}) session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) session = podmanTest.Podman([]string{"start", "test_ro"}) @@ -164,7 +164,7 @@ var _ = Describe("Podman create", func() { Expect(session.ExitCode()).To(Equal(0)) Expect(session.OutputToString()).To(ContainSubstring("/create/test ro")) - session = podmanTest.Podman([]string{"create", "--name", "test_shared", "--rm", "--mount", fmt.Sprintf("type=bind,src=%s,target=/create/test,shared", mountPath), ALPINE, "grep", "/create/test", "/proc/self/mountinfo"}) + session = podmanTest.Podman([]string{"create", "--name", "test_shared", "--mount", fmt.Sprintf("type=bind,src=%s,target=/create/test,shared", mountPath), ALPINE, "grep", "/create/test", "/proc/self/mountinfo"}) session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) session = podmanTest.Podman([]string{"start", "test_shared"}) @@ -180,7 +180,7 @@ var _ = Describe("Podman create", func() { mountPath = filepath.Join(podmanTest.TempDir, "scratchpad") os.Mkdir(mountPath, 0755) - session = podmanTest.Podman([]string{"create", "--name", "test_tmpfs", "--rm", "--mount", "type=tmpfs,target=/create/test", ALPINE, "grep", "/create/test", "/proc/self/mountinfo"}) + session = podmanTest.Podman([]string{"create", "--name", "test_tmpfs", "--mount", "type=tmpfs,target=/create/test", ALPINE, "grep", "/create/test", "/proc/self/mountinfo"}) session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) session = podmanTest.Podman([]string{"start", "test_tmpfs"}) diff --git a/test/e2e/run_test.go b/test/e2e/run_test.go index 22a36bb6c..6bd49de33 100644 --- a/test/e2e/run_test.go +++ b/test/e2e/run_test.go @@ -322,7 +322,7 @@ var _ = Describe("Podman run", func() { os.Setenv("NOTIFY_SOCKET", sock) defer os.Unsetenv("NOTIFY_SOCKET") - session := podmanTest.Podman([]string{"run", "--rm", ALPINE, "printenv", "NOTIFY_SOCKET"}) + session := podmanTest.Podman([]string{"run", ALPINE, "printenv", "NOTIFY_SOCKET"}) session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) Expect(len(session.OutputToStringArray())).To(BeNumerically(">", 0)) diff --git a/utils/utils.go b/utils/utils.go index c7c5ab5cf..4a91b304f 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -9,6 +9,7 @@ import ( systemdDbus "github.com/coreos/go-systemd/dbus" "github.com/godbus/dbus" + "github.com/pkg/errors" ) // ExecCmd executes a command with args and returns its output as a string along @@ -82,12 +83,9 @@ func newProp(name string, units interface{}) systemdDbus.Property { } } -// DetachError is special error which returned in case of container detach. -type DetachError struct{} - -func (DetachError) Error() string { - return "detached from container" -} +// ErrDetach is an error indicating that the user manually detached from the +// container. +var ErrDetach = errors.New("detached from container") // CopyDetachable is similar to io.Copy but support a detach key sequence to break out. func CopyDetachable(dst io.Writer, src io.Reader, keys []byte) (written int64, err error) { @@ -108,7 +106,7 @@ func CopyDetachable(dst io.Writer, src io.Reader, keys []byte) (written int64, e } if i == len(keys)-1 { // src.Close() - return 0, DetachError{} + return 0, ErrDetach } nr, er = src.Read(buf) } |