diff options
39 files changed, 881 insertions, 237 deletions
diff --git a/cmd/podman/common/create.go b/cmd/podman/common/create.go index 708afb7b7..280175f95 100644 --- a/cmd/podman/common/create.go +++ b/cmd/podman/common/create.go @@ -335,7 +335,7 @@ func DefineCreateFlags(cmd *cobra.Command, cf *ContainerCLIOpts) { createFlags.BoolVar( &cf.HTTPProxy, - "http-proxy", true, + "http-proxy", containerConfig.Containers.HTTPProxy, "Set proxy environment variables in the container based on the host proxy vars", ) diff --git a/cmd/podman/containers/checkpoint.go b/cmd/podman/containers/checkpoint.go index 4a477eb10..14abfd5a7 100644 --- a/cmd/podman/containers/checkpoint.go +++ b/cmd/podman/containers/checkpoint.go @@ -58,6 +58,9 @@ func init() { flags.BoolVar(&checkpointOptions.IgnoreRootFS, "ignore-rootfs", false, "Do not include root file-system changes when exporting") flags.BoolVar(&checkpointOptions.IgnoreVolumes, "ignore-volumes", false, "Do not export volumes associated with container") + flags.BoolVarP(&checkpointOptions.PreCheckPoint, "pre-checkpoint", "P", false, "Dump container's memory information only, leave the container running") + flags.BoolVar(&checkpointOptions.WithPrevious, "with-previous", false, "Checkpoint container with pre-checkpoint images") + validate.AddLatestFlag(checkpointCommand, &checkpointOptions.Latest) } @@ -72,6 +75,9 @@ func checkpoint(cmd *cobra.Command, args []string) error { if checkpointOptions.Export == "" && checkpointOptions.IgnoreVolumes { return errors.Errorf("--ignore-volumes can only be used with --export") } + if checkpointOptions.WithPrevious && checkpointOptions.PreCheckPoint { + return errors.Errorf("--with-previous can not be used with --pre-checkpoint") + } responses, err := registry.ContainerEngine().ContainerCheckpoint(context.Background(), args, checkpointOptions) if err != nil { return err diff --git a/cmd/podman/containers/restore.go b/cmd/podman/containers/restore.go index 5245a68fa..49c0be88e 100644 --- a/cmd/podman/containers/restore.go +++ b/cmd/podman/containers/restore.go @@ -59,6 +59,10 @@ func init() { flags.StringVarP(&restoreOptions.Name, nameFlagName, "n", "", "Specify new name for container restored from exported checkpoint (only works with --import)") _ = restoreCommand.RegisterFlagCompletionFunc(nameFlagName, completion.AutocompleteNone) + importPreviousFlagName := "import-previous" + flags.StringVar(&restoreOptions.ImportPrevious, importPreviousFlagName, "", "Restore from exported pre-checkpoint archive (tar.gz)") + _ = restoreCommand.RegisterFlagCompletionFunc(importPreviousFlagName, completion.AutocompleteDefault) + flags.BoolVar(&restoreOptions.IgnoreRootFS, "ignore-rootfs", false, "Do not apply root file-system changes when importing from exported checkpoint") flags.BoolVar(&restoreOptions.IgnoreStaticIP, "ignore-static-ip", false, "Ignore IP address set via --static-ip") flags.BoolVar(&restoreOptions.IgnoreStaticMAC, "ignore-static-mac", false, "Ignore MAC address set via --mac-address") @@ -71,6 +75,9 @@ func restore(_ *cobra.Command, args []string) error { if rootless.IsRootless() { return errors.New("restoring a container requires root") } + if restoreOptions.Import == "" && restoreOptions.ImportPrevious != "" { + return errors.Errorf("--import-previous can only be used with --import") + } if restoreOptions.Import == "" && restoreOptions.IgnoreRootFS { return errors.Errorf("--ignore-rootfs can only be used with --import") } diff --git a/cmd/podman/images/build.go b/cmd/podman/images/build.go index 3aca104e3..c0aa27ca1 100644 --- a/cmd/podman/images/build.go +++ b/cmd/podman/images/build.go @@ -135,6 +135,16 @@ func buildFlags(cmd *cobra.Command) { logrus.Errorf("error setting up build flags: %v", err) os.Exit(1) } + // --http-proxy flag + // containers.conf defaults to true but we want to force false by default for remote, since settings do not apply + if registry.IsRemote() { + flag = fromAndBudFlags.Lookup("http-proxy") + buildOpts.HTTPProxy = false + if err := flag.Value.Set("false"); err != nil { + logrus.Errorf("unable to set --https-proxy to %v: %v", false, err) + } + flag.DefValue = "false" + } flags.AddFlagSet(&fromAndBudFlags) // Add the completion functions fromAndBudFlagsCompletions := buildahCLI.GetFromAndBudFlagsCompletions() diff --git a/cmd/podman/images/search.go b/cmd/podman/images/search.go index c2ef7d767..c8ea4b04a 100644 --- a/cmd/podman/images/search.go +++ b/cmd/podman/images/search.go @@ -26,6 +26,12 @@ type searchOptionsWrapper struct { Format string // For go templating } +// listEntryTag is a utility structure used for json serialization. +type listEntryTag struct { + Name string + Tags []string +} + var ( searchOptions = searchOptionsWrapper{} searchDescription = `Search registries for a given image. Can search all the default registries or a specific registry. @@ -149,14 +155,13 @@ func imageSearch(cmd *cobra.Command, args []string) error { if len(searchOptions.Filters) != 0 { return errors.Errorf("filters are not applicable to list tags result") } + if report.IsJSON(searchOptions.Format) { + listTagsEntries := buildListTagsJson(searchReport) + return printJson(listTagsEntries) + } row = "{{.Name}}\t{{.Tag}}\n" case report.IsJSON(searchOptions.Format): - prettyJSON, err := json.MarshalIndent(searchReport, "", " ") - if err != nil { - return err - } - fmt.Println(string(prettyJSON)) - return nil + return printJson(searchReport) case cmd.Flags().Changed("format"): renderHeaders = parse.HasTable(searchOptions.Format) row = report.NormalizeFormat(searchOptions.Format) @@ -180,3 +185,33 @@ func imageSearch(cmd *cobra.Command, args []string) error { return tmpl.Execute(w, searchReport) } + +func printJson(v interface{}) error { + prettyJSON, err := json.MarshalIndent(v, "", " ") + if err != nil { + return err + } + fmt.Println(string(prettyJSON)) + return nil +} + +func buildListTagsJson(searchReport []entities.ImageSearchReport) []listEntryTag { + entries := []listEntryTag{} + +ReportLoop: + for _, report := range searchReport { + for idx, entry := range entries { + if entry.Name == report.Name { + entries[idx].Tags = append(entries[idx].Tags, report.Tag) + continue ReportLoop + } + } + newElem := listEntryTag{ + report.Name, + []string{report.Tag}, + } + + entries = append(entries, newElem) + } + return entries +} diff --git a/docs/source/markdown/podman-container-checkpoint.1.md b/docs/source/markdown/podman-container-checkpoint.1.md index 6a9469156..ea05979cd 100644 --- a/docs/source/markdown/podman-container-checkpoint.1.md +++ b/docs/source/markdown/podman-container-checkpoint.1.md @@ -58,12 +58,26 @@ This option must be used in combination with the **--export, -e** option. When this option is specified, the content of volumes associated with the container will not be included into the checkpoint tar.gz file. +#### **--pre-checkpoint**, **-P** + +Dump the container's memory information only, leaving the container running. Later +operations will supersede prior dumps. It only works on runc 1.0-rc3 or higher. + +#### **--with-previous** + +Check out the container with previous criu image files in pre-dump. It only works +without **--pre-checkpoint** or **-P**. It only works on runc 1.0-rc3 or higher. + ## EXAMPLE podman container checkpoint mywebserver podman container checkpoint 860a4b23 +podman container checkpoint -P -e pre-checkpoint.tar.gz -l + +podman container checkpoint --with-previous -e checkpoint.tar.gz -l + ## SEE ALSO podman(1), podman-container-restore(1) diff --git a/docs/source/markdown/podman-container-restore.1.md b/docs/source/markdown/podman-container-restore.1.md index 0593e6fe9..192b8765b 100644 --- a/docs/source/markdown/podman-container-restore.1.md +++ b/docs/source/markdown/podman-container-restore.1.md @@ -48,6 +48,11 @@ Import a checkpoint tar.gz file, which was exported by Podman. This can be used to import a checkpointed container from another host. Do not specify a *container* argument when using this option. +#### **--import-previous** + +Import a pre-checkpoint tar.gz file which was exported by Podman. This option +must be used with **-i** or **--import**. It only works on runc 1.0-rc3 or higher. + #### **--name**, **-n** This is only available in combination with **--import, -i**. If a container is restored @@ -98,6 +103,8 @@ podman container restore mywebserver podman container restore 860a4b23 +podman container restore --import-previous pre-checkpoint.tar.gz --import checkpoint.tar.gz + ## SEE ALSO podman(1), podman-container-checkpoint(1) @@ -11,7 +11,7 @@ require ( github.com/containernetworking/cni v0.8.0 github.com/containernetworking/plugins v0.9.0 github.com/containers/buildah v1.18.1-0.20201222143428-b9fdee076426 - github.com/containers/common v0.31.2 + github.com/containers/common v0.33.0 github.com/containers/conmon v2.0.20+incompatible github.com/containers/image/v5 v5.9.0 github.com/containers/psgo v1.5.2 @@ -99,8 +99,8 @@ github.com/containernetworking/plugins v0.9.0/go.mod h1:dbWv4dI0QrBGuVgj+TuVQ6wJ github.com/containers/buildah v1.18.1-0.20201222143428-b9fdee076426 h1:hgNSbIO7KUJ9jHSEHwM5D2qii5t/5f2yfxZepJFYm18= github.com/containers/buildah v1.18.1-0.20201222143428-b9fdee076426/go.mod h1:AM7JcGaUtTJgR6fZL2zBg5PCSCSDiX/sNdMSyrkoJ10= github.com/containers/common v0.31.1/go.mod h1:Fehe82hQfJQvDspnRrV9rcdAWG3IalNHEt0F6QWNBHQ= -github.com/containers/common v0.31.2 h1:sNYwvLA4B7SpEiAWTUvkItPlCrUa2vcxh0FTKXKoC3Q= -github.com/containers/common v0.31.2/go.mod h1:Fehe82hQfJQvDspnRrV9rcdAWG3IalNHEt0F6QWNBHQ= +github.com/containers/common v0.33.0 h1:7Z6aAQ2s2iniEXd/IoGgc0ukmgmzAE8Oa929t6huVB8= +github.com/containers/common v0.33.0/go.mod h1:mjDo/NKeweL/onaspLhZ38WnHXaYmrELHclIdvSnYpY= github.com/containers/conmon v2.0.20+incompatible h1:YbCVSFSCqFjjVwHTPINGdMX1F6JXHGTUje2ZYobNrkg= github.com/containers/conmon v2.0.20+incompatible/go.mod h1:hgwZ2mtuDrppv78a/cOBNiCm6O0UMWGx1mu7P00nu5I= github.com/containers/image/v5 v5.9.0 h1:dRmUtcluQcmasNo3DpnRoZjfU0rOu1qZeL6wlDJr10Q= @@ -257,7 +257,6 @@ github.com/google/shlex v0.0.0-20181106134648-c34317bd91bf h1:7+FW5aGwISbqUtkfmI github.com/google/shlex v0.0.0-20181106134648-c34317bd91bf/go.mod h1:RpwtwJQFrIEPstU94h88MWPXP2ektJZ8cZ0YntAmXiE= github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.4 h1:0ecGp3skIrHWPNGPJDaBIghfA6Sp7Ruo2Io8eLKzWm0= github.com/google/uuid v1.1.4/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= diff --git a/libpod/container_api.go b/libpod/container_api.go index 2c7dc79c9..87ff764e3 100644 --- a/libpod/container_api.go +++ b/libpod/container_api.go @@ -706,6 +706,13 @@ type ContainerCheckpointOptions struct { // IgnoreVolumes tells the API to not export or not to import // the content of volumes associated with the container IgnoreVolumes bool + // Pre Checkpoint container and leave container running + PreCheckPoint bool + // Dump container with Pre Checkpoint images + WithPrevious bool + // ImportPrevious tells the API to restore container with two + // images. One is TargetFile, the other is ImportPrevious. + ImportPrevious string } // Checkpoint checkpoints a container @@ -718,6 +725,12 @@ func (c *Container) Checkpoint(ctx context.Context, options ContainerCheckpointO } } + if options.WithPrevious { + if err := c.canWithPrevious(); err != nil { + return err + } + } + if !c.batched { c.lock.Lock() defer c.lock.Unlock() diff --git a/libpod/container_internal.go b/libpod/container_internal.go index 540230c26..c7548e0e5 100644 --- a/libpod/container_internal.go +++ b/libpod/container_internal.go @@ -134,6 +134,11 @@ func (c *Container) CheckpointPath() string { return filepath.Join(c.bundlePath(), "checkpoint") } +// PreCheckpointPath returns the path to the directory containing the pre-checkpoint-images +func (c *Container) PreCheckPointPath() string { + return filepath.Join(c.bundlePath(), "pre-checkpoint") +} + // AttachSocketPath retrieves the path of the container's attach socket func (c *Container) AttachSocketPath() (string, error) { return c.ociRuntime.AttachSocketPath(c) @@ -2023,6 +2028,12 @@ func (c *Container) checkReadyForRemoval() error { return nil } +// canWithPrevious return the stat of the preCheckPoint dir +func (c *Container) canWithPrevious() error { + _, err := os.Stat(c.PreCheckPointPath()) + return err +} + // writeJSONFile marshalls and writes the given data to a JSON file // in the bundle path func (c *Container) writeJSONFile(v interface{}, file string) error { diff --git a/libpod/container_internal_linux.go b/libpod/container_internal_linux.go index e10b5c3ab..575047f95 100644 --- a/libpod/container_internal_linux.go +++ b/libpod/container_internal_linux.go @@ -811,6 +811,9 @@ func (c *Container) exportCheckpoint(options ContainerCheckpointOptions) error { "spec.dump", "network.status"} + if options.PreCheckPoint { + includeFiles[0] = "pre-checkpoint" + } // Get root file-system changes included in the checkpoint archive rootfsDiffPath := filepath.Join(c.bundlePath(), "rootfs-diff.tar") deleteFilesList := filepath.Join(c.bundlePath(), "deleted.files") @@ -1014,6 +1017,15 @@ func (c *Container) checkpoint(ctx context.Context, options ContainerCheckpointO defer c.newContainerEvent(events.Checkpoint) + // There is a bug from criu: https://github.com/checkpoint-restore/criu/issues/116 + // We have to change the symbolic link from absolute path to relative path + if options.WithPrevious { + os.Remove(path.Join(c.CheckpointPath(), "parent")) + if err := os.Symlink("../pre-checkpoint", path.Join(c.CheckpointPath(), "parent")); err != nil { + return err + } + } + if options.TargetFile != "" { if err = c.exportCheckpoint(options); err != nil { return err @@ -1022,7 +1034,7 @@ func (c *Container) checkpoint(ctx context.Context, options ContainerCheckpointO logrus.Debugf("Checkpointed container %s", c.ID()) - if !options.KeepRunning { + if !options.KeepRunning && !options.PreCheckPoint { c.state.State = define.ContainerStateStopped // Cleanup Storage and Network @@ -1031,7 +1043,7 @@ func (c *Container) checkpoint(ctx context.Context, options ContainerCheckpointO } } - if !options.Keep { + if !options.Keep && !options.PreCheckPoint { cleanup := []string{ "dump.log", "stats-dump", @@ -1079,6 +1091,21 @@ func (c *Container) importCheckpoint(input string) error { return nil } +func (c *Container) importPreCheckpoint(input string) error { + archiveFile, err := os.Open(input) + if err != nil { + return errors.Wrap(err, "failed to open pre-checkpoint archive for import") + } + + defer archiveFile.Close() + + err = archive.Untar(archiveFile, c.bundlePath(), nil) + if err != nil { + return errors.Wrapf(err, "Unpacking of pre-checkpoint archive %s failed", input) + } + return nil +} + func (c *Container) restore(ctx context.Context, options ContainerCheckpointOptions) (retErr error) { if err := c.checkpointRestoreSupported(); err != nil { return err @@ -1088,6 +1115,12 @@ func (c *Container) restore(ctx context.Context, options ContainerCheckpointOpti return errors.Wrapf(define.ErrCtrStateInvalid, "container %s is running or paused, cannot restore", c.ID()) } + if options.ImportPrevious != "" { + if err := c.importPreCheckpoint(options.ImportPrevious); err != nil { + return err + } + } + if options.TargetFile != "" { if err := c.importCheckpoint(options.TargetFile); err != nil { return err @@ -1321,6 +1354,10 @@ func (c *Container) restore(ctx context.Context, options ContainerCheckpointOpti if err != nil { logrus.Debugf("Non-fatal: removal of checkpoint directory (%s) failed: %v", c.CheckpointPath(), err) } + err = os.RemoveAll(c.PreCheckPointPath()) + if err != nil { + logrus.Debugf("Non-fatal: removal of pre-checkpoint directory (%s) failed: %v", c.PreCheckPointPath(), err) + } cleanup := [...]string{"restore.log", "dump.log", "stats-dump", "stats-restore", "network.status", "rootfs-diff.tar", "deleted.files"} for _, del := range cleanup { file := filepath.Join(c.bundlePath(), del) diff --git a/libpod/oci_attach_linux.go b/libpod/oci_attach_linux.go index fbc95510e..4556eba94 100644 --- a/libpod/oci_attach_linux.go +++ b/libpod/oci_attach_linux.go @@ -28,6 +28,15 @@ const ( AttachPipeStderr = 3 ) +func openUnixSocket(path string) (*net.UnixConn, error) { + fd, err := unix.Open(path, unix.O_PATH, 0) + if err != nil { + return nil, err + } + defer unix.Close(fd) + return net.DialUnix("unixpacket", nil, &net.UnixAddr{Name: fmt.Sprintf("/proc/self/fd/%d", fd), Net: "unixpacket"}) +} + // Attach to the given container // Does not check if state is appropriate // started is only required if startContainer is true @@ -52,11 +61,10 @@ func (c *Container) attach(streams *define.AttachStreams, keys string, resize <- if err != nil { return err } - socketPath := buildSocketPath(attachSock) - conn, err := net.DialUnix("unixpacket", nil, &net.UnixAddr{Name: socketPath, Net: "unixpacket"}) + conn, err := openUnixSocket(attachSock) if err != nil { - return errors.Wrapf(err, "failed to connect to container's attach socket: %v", socketPath) + return errors.Wrapf(err, "failed to connect to container's attach socket: %v", attachSock) } defer func() { if err := conn.Close(); err != nil { @@ -124,7 +132,6 @@ func (c *Container) attachToExec(streams *define.AttachStreams, keys *string, se 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 { @@ -132,9 +139,9 @@ func (c *Container) attachToExec(streams *define.AttachStreams, keys *string, se } // 2: then attach - conn, err := net.DialUnix("unixpacket", nil, &net.UnixAddr{Name: socketPath, Net: "unixpacket"}) + conn, err := openUnixSocket(sockPath) if err != nil { - return errors.Wrapf(err, "failed to connect to container's attach socket: %v", socketPath) + return errors.Wrapf(err, "failed to connect to container's attach socket: %v", sockPath) } defer func() { if err := conn.Close(); err != nil { @@ -182,16 +189,6 @@ func registerResizeFunc(resize <-chan remotecommand.TerminalSize, bundlePath str }) } -func buildSocketPath(socketPath string) string { - maxUnixLength := unixPathLength() - if maxUnixLength < len(socketPath) { - socketPath = socketPath[0:maxUnixLength] - } - - logrus.Debug("connecting to socket ", socketPath) - return socketPath -} - func setupStdioChannels(streams *define.AttachStreams, conn *net.UnixConn, detachKeys []byte) (chan error, chan error) { receiveStdoutError := make(chan error) go func() { diff --git a/libpod/oci_attach_linux_cgo.go b/libpod/oci_attach_linux_cgo.go deleted file mode 100644 index d81243360..000000000 --- a/libpod/oci_attach_linux_cgo.go +++ /dev/null @@ -1,11 +0,0 @@ -//+build linux,cgo - -package libpod - -//#include <sys/un.h> -// extern int unix_path_length(){struct sockaddr_un addr; return sizeof(addr.sun_path) - 1;} -import "C" - -func unixPathLength() int { - return int(C.unix_path_length()) -} diff --git a/libpod/oci_attach_linux_nocgo.go b/libpod/oci_attach_linux_nocgo.go deleted file mode 100644 index a514a555d..000000000 --- a/libpod/oci_attach_linux_nocgo.go +++ /dev/null @@ -1,7 +0,0 @@ -//+build linux,!cgo - -package libpod - -func unixPathLength() int { - return 107 -} diff --git a/libpod/oci_conmon_exec_linux.go b/libpod/oci_conmon_exec_linux.go index d6b63f25e..dc5dd03df 100644 --- a/libpod/oci_conmon_exec_linux.go +++ b/libpod/oci_conmon_exec_linux.go @@ -2,7 +2,6 @@ package libpod import ( "fmt" - "net" "net/http" "os" "os/exec" @@ -512,7 +511,6 @@ func attachExecHTTP(c *Container, sessionID string, r *http.Request, w http.Resp 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(pipes.attachPipe, ""); err != nil { @@ -520,9 +518,9 @@ func attachExecHTTP(c *Container, sessionID string, r *http.Request, w http.Resp } // 2: then attach - conn, err := net.DialUnix("unixpacket", nil, &net.UnixAddr{Name: socketPath, Net: "unixpacket"}) + conn, err := openUnixSocket(sockPath) if err != nil { - return errors.Wrapf(err, "failed to connect to container's attach socket: %v", socketPath) + return errors.Wrapf(err, "failed to connect to container's attach socket: %v", sockPath) } defer func() { if err := conn.Close(); err != nil { diff --git a/libpod/oci_conmon_linux.go b/libpod/oci_conmon_linux.go index 9796d6988..23bfb29d7 100644 --- a/libpod/oci_conmon_linux.go +++ b/libpod/oci_conmon_linux.go @@ -529,13 +529,12 @@ func (r *ConmonOCIRuntime) HTTPAttach(ctr *Container, req *http.Request, w http. if err != nil { return err } - socketPath := buildSocketPath(attachSock) var conn *net.UnixConn if streamAttach { - newConn, err := net.DialUnix("unixpacket", nil, &net.UnixAddr{Name: socketPath, Net: "unixpacket"}) + newConn, err := openUnixSocket(attachSock) if err != nil { - return errors.Wrapf(err, "failed to connect to container's attach socket: %v", socketPath) + return errors.Wrapf(err, "failed to connect to container's attach socket: %v", attachSock) } conn = newConn defer func() { @@ -544,7 +543,7 @@ func (r *ConmonOCIRuntime) HTTPAttach(ctr *Container, req *http.Request, w http. } }() - logrus.Debugf("Successfully connected to container %s attach socket %s", ctr.ID(), socketPath) + logrus.Debugf("Successfully connected to container %s attach socket %s", ctr.ID(), attachSock) } detachString := ctr.runtime.config.Engine.DetachKeys @@ -769,10 +768,14 @@ func (r *ConmonOCIRuntime) CheckpointContainer(ctr *Container, options Container } // imagePath is used by CRIU to store the actual checkpoint files imagePath := ctr.CheckpointPath() + if options.PreCheckPoint { + imagePath = ctr.PreCheckPointPath() + } // 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) + logrus.Debugf("Pre-dump the container %t", options.PreCheckPoint) args := []string{} args = append(args, r.runtimeFlags...) args = append(args, "checkpoint") @@ -786,6 +789,15 @@ func (r *ConmonOCIRuntime) CheckpointContainer(ctr *Container, options Container if options.TCPEstablished { args = append(args, "--tcp-established") } + if !options.PreCheckPoint && options.KeepRunning { + args = append(args, "--leave-running") + } + if options.PreCheckPoint { + args = append(args, "--pre-dump") + } + if !options.PreCheckPoint && options.WithPrevious { + args = append(args, "--parent-path", ctr.PreCheckPointPath()) + } runtimeDir, err := util.GetRuntimeDir() if err != nil { return err @@ -794,6 +806,7 @@ func (r *ConmonOCIRuntime) CheckpointContainer(ctr *Container, options Container return errors.Wrapf(err, "cannot set XDG_RUNTIME_DIR") } args = append(args, ctr.ID()) + logrus.Debugf("the args to checkpoint: %s %s", r.path, strings.Join(args, " ")) return utils.ExecCmdWithStdStreams(os.Stdin, os.Stdout, os.Stderr, nil, r.path, args...) } diff --git a/pkg/domain/entities/containers.go b/pkg/domain/entities/containers.go index a67ecebd5..96687b1de 100644 --- a/pkg/domain/entities/containers.go +++ b/pkg/domain/entities/containers.go @@ -178,6 +178,8 @@ type CheckpointOptions struct { Latest bool LeaveRunning bool TCPEstablished bool + PreCheckPoint bool + WithPrevious bool } type CheckpointReport struct { @@ -196,6 +198,7 @@ type RestoreOptions struct { Latest bool Name string TCPEstablished bool + ImportPrevious string } type RestoreReport struct { diff --git a/pkg/domain/infra/abi/containers.go b/pkg/domain/infra/abi/containers.go index 620f05192..b5f5a0e91 100644 --- a/pkg/domain/infra/abi/containers.go +++ b/pkg/domain/infra/abi/containers.go @@ -473,6 +473,8 @@ func (ic *ContainerEngine) ContainerCheckpoint(ctx context.Context, namesOrIds [ IgnoreRootfs: options.IgnoreRootFS, IgnoreVolumes: options.IgnoreVolumes, KeepRunning: options.LeaveRunning, + PreCheckPoint: options.PreCheckPoint, + WithPrevious: options.WithPrevious, } if options.All { @@ -513,6 +515,7 @@ func (ic *ContainerEngine) ContainerRestore(ctx context.Context, namesOrIds []st IgnoreVolumes: options.IgnoreVolumes, IgnoreStaticIP: options.IgnoreStaticIP, IgnoreStaticMAC: options.IgnoreStaticMAC, + ImportPrevious: options.ImportPrevious, } filterFuncs := []libpod.ContainerFilter{ diff --git a/pkg/specgen/generate/container.go b/pkg/specgen/generate/container.go index c771e8bc8..2feb1d3b2 100644 --- a/pkg/specgen/generate/container.go +++ b/pkg/specgen/generate/container.go @@ -100,15 +100,9 @@ func CompleteSpec(ctx context.Context, r *libpod.Runtime, s *specgen.SpecGenerat if err != nil { return nil, err } - // First transform the os env into a map. We need it for the labels later in - // any case. - osEnv, err := envLib.ParseSlice(os.Environ()) - if err != nil { - return nil, errors.Wrap(err, "error parsing host environment variables") - } // Get Default Environment from containers.conf - defaultEnvs, err := envLib.ParseSlice(rtc.GetDefaultEnv()) + defaultEnvs, err := envLib.ParseSlice(rtc.GetDefaultEnvEx(s.EnvHost, s.HTTPProxy)) if err != nil { return nil, errors.Wrap(err, "error parsing fields in containers.conf") } @@ -133,6 +127,12 @@ func CompleteSpec(ctx context.Context, r *libpod.Runtime, s *specgen.SpecGenerat defaultEnvs = envLib.Join(defaultEnvs, envs) } + // First transform the os env into a map. We need it for the labels later in + // any case. + osEnv, err := envLib.ParseSlice(os.Environ()) + if err != nil { + return nil, errors.Wrap(err, "error parsing host environment variables") + } // Caller Specified defaults if s.EnvHost { defaultEnvs = envLib.Join(defaultEnvs, osEnv) diff --git a/pkg/systemd/generate/common.go b/pkg/systemd/generate/common.go index fb921cd72..8901298db 100644 --- a/pkg/systemd/generate/common.go +++ b/pkg/systemd/generate/common.go @@ -71,3 +71,30 @@ func quoteArguments(command []string) []string { } return command } + +func removeDetachArg(args []string, argCount int) []string { + // "--detach=false" could also be in the container entrypoint + // split them off so we do not remove it there + realArgs := args[len(args)-argCount:] + flagArgs := removeArg("-d=false", args[:len(args)-argCount]) + flagArgs = removeArg("--detach=false", flagArgs) + return append(flagArgs, realArgs...) +} + +func removeReplaceArg(args []string, argCount int) []string { + // "--replace=false" could also be in the container entrypoint + // split them off so we do not remove it there + realArgs := args[len(args)-argCount:] + flagArgs := removeArg("--replace=false", args[:len(args)-argCount]) + return append(flagArgs, realArgs...) +} + +func removeArg(arg string, args []string) []string { + newArgs := []string{} + for _, a := range args { + if a != arg { + newArgs = append(newArgs, a) + } + } + return newArgs +} diff --git a/pkg/systemd/generate/containers.go b/pkg/systemd/generate/containers.go index cfa02dc9d..b64b2593c 100644 --- a/pkg/systemd/generate/containers.go +++ b/pkg/systemd/generate/containers.go @@ -14,6 +14,7 @@ import ( "github.com/containers/podman/v2/version" "github.com/pkg/errors" "github.com/sirupsen/logrus" + "github.com/spf13/pflag" ) // containerInfo contains data required for generating a container's systemd @@ -44,6 +45,9 @@ type containerInfo struct { // Executable is the path to the podman executable. Will be auto-filled if // left empty. Executable string + // RootFlags contains the root flags which were used to create the container + // Only used with --new + RootFlags string // TimeStamp at the time of creating the unit file. Will be set internally. TimeStamp string // CreateCommand is the full command plus arguments of the process the @@ -185,22 +189,30 @@ func executeContainerTemplate(info *containerInfo, options entities.GenerateSyst info.ContainerIDFile = "%t/" + info.ServiceName + ".ctr-id" // The create command must at least have three arguments: // /usr/bin/podman run $IMAGE - index := 2 - if info.CreateCommand[1] == "container" { - index = 3 + index := 0 + for i, arg := range info.CreateCommand { + if arg == "run" || arg == "create" { + index = i + 1 + break + } } - if len(info.CreateCommand) < index+1 { + if index == 0 { return "", errors.Errorf("container's create command is too short or invalid: %v", info.CreateCommand) } // We're hard-coding the first five arguments and append the // CreateCommand with a stripped command and subcommand. - startCommand := []string{ - info.Executable, + startCommand := []string{info.Executable} + if index > 2 { + // include root flags + info.RootFlags = strings.Join(quoteArguments(info.CreateCommand[1:index-1]), " ") + startCommand = append(startCommand, info.CreateCommand[1:index-1]...) + } + startCommand = append(startCommand, "run", "--conmon-pidfile", "{{.PIDFile}}", "--cidfile", "{{.ContainerIDFile}}", "--cgroups=no-conmon", - } + ) // If the container is in a pod, make sure that the // --pod-id-file is set correctly. if info.pod != nil { @@ -210,23 +222,27 @@ func executeContainerTemplate(info *containerInfo, options entities.GenerateSyst } // Presence check for certain flags/options. - hasDetachParam := false - hasNameParam := false - hasReplaceParam := false - for _, p := range info.CreateCommand[index:] { - switch p { - case "--detach", "-d": - hasDetachParam = true - case "--name": - hasNameParam = true - case "--replace": - hasReplaceParam = true - } - if strings.HasPrefix(p, "--name=") { - hasNameParam = true - } + fs := pflag.NewFlagSet("args", pflag.ContinueOnError) + fs.ParseErrorsWhitelist.UnknownFlags = true + fs.Usage = func() {} + fs.SetInterspersed(false) + fs.BoolP("detach", "d", false, "") + fs.String("name", "", "") + fs.Bool("replace", false, "") + fs.Parse(info.CreateCommand[index:]) + + hasDetachParam, err := fs.GetBool("detach") + if err != nil { + return "", err + } + hasNameParam := fs.Lookup("name").Changed + hasReplaceParam, err := fs.GetBool("replace") + if err != nil { + return "", err } + remainingCmd := info.CreateCommand[index:] + if !hasDetachParam { // Enforce detaching // @@ -240,6 +256,13 @@ func executeContainerTemplate(info *containerInfo, options entities.GenerateSyst // will wait the `podman run` command exit until failed // with timeout error. startCommand = append(startCommand, "-d") + + if fs.Changed("detach") { + // this can only happen if --detach=false is set + // in that case we need to remove it otherwise we + // would overwrite the previous detach arg to false + remainingCmd = removeDetachArg(remainingCmd, fs.NArg()) + } } if hasNameParam && !hasReplaceParam { // Enforce --replace for named containers. This will @@ -247,14 +270,21 @@ func executeContainerTemplate(info *containerInfo, options entities.GenerateSyst // start after system crashes (see // github.com/containers/podman/issues/5485). startCommand = append(startCommand, "--replace") + + if fs.Changed("replace") { + // this can only happen if --replace=false is set + // in that case we need to remove it otherwise we + // would overwrite the previous replace arg to false + remainingCmd = removeReplaceArg(remainingCmd, fs.NArg()) + } } - startCommand = append(startCommand, info.CreateCommand[index:]...) + startCommand = append(startCommand, remainingCmd...) startCommand = quoteArguments(startCommand) info.ExecStartPre = "/bin/rm -f {{.PIDFile}} {{.ContainerIDFile}}" info.ExecStart = strings.Join(startCommand, " ") - info.ExecStop = "{{.Executable}} stop --ignore --cidfile {{.ContainerIDFile}} {{if (ge .StopTimeout 0)}}-t {{.StopTimeout}}{{end}}" - info.ExecStopPost = "{{.Executable}} rm --ignore -f --cidfile {{.ContainerIDFile}}" + info.ExecStop = "{{.Executable}} {{if .RootFlags}}{{ .RootFlags}} {{end}}stop --ignore --cidfile {{.ContainerIDFile}} {{if (ge .StopTimeout 0)}}-t {{.StopTimeout}}{{end}}" + info.ExecStopPost = "{{.Executable}} {{if .RootFlags}}{{ .RootFlags}} {{end}}rm --ignore -f --cidfile {{.ContainerIDFile}}" } info.TimeoutStopSec = minTimeoutStopSec + info.StopTimeout diff --git a/pkg/systemd/generate/containers_test.go b/pkg/systemd/generate/containers_test.go index b8f3a90f9..c8e65bfe3 100644 --- a/pkg/systemd/generate/containers_test.go +++ b/pkg/systemd/generate/containers_test.go @@ -122,9 +122,9 @@ Environment=PODMAN_SYSTEMD_UNIT=%n Restart=always TimeoutStopSec=70 ExecStartPre=/bin/rm -f %t/jadda-jadda.pid %t/jadda-jadda.ctr-id -ExecStart=/usr/bin/podman run --conmon-pidfile %t/jadda-jadda.pid --cidfile %t/jadda-jadda.ctr-id --cgroups=no-conmon -d --replace --name jadda-jadda --hostname hello-world awesome-image:latest command arg1 ... argN "foo=arg \"with \" space" -ExecStop=/usr/bin/podman stop --ignore --cidfile %t/jadda-jadda.ctr-id -t 10 -ExecStopPost=/usr/bin/podman rm --ignore -f --cidfile %t/jadda-jadda.ctr-id +ExecStart=/usr/bin/podman container run --conmon-pidfile %t/jadda-jadda.pid --cidfile %t/jadda-jadda.ctr-id --cgroups=no-conmon -d --replace --name jadda-jadda --hostname hello-world awesome-image:latest command arg1 ... argN "foo=arg \"with \" space" +ExecStop=/usr/bin/podman container stop --ignore --cidfile %t/jadda-jadda.ctr-id -t 10 +ExecStopPost=/usr/bin/podman container rm --ignore -f --cidfile %t/jadda-jadda.ctr-id PIDFile=%t/jadda-jadda.pid Type=forking @@ -228,6 +228,107 @@ Type=forking WantedBy=multi-user.target default.target ` + genGoodNewDetach := func(detachparam string) string { + goodNewDetach := `# jadda-jadda.service +# autogenerated by Podman CI + +[Unit] +Description=Podman jadda-jadda.service +Documentation=man:podman-generate-systemd(1) +Wants=network.target +After=network-online.target + +[Service] +Environment=PODMAN_SYSTEMD_UNIT=%n +Restart=always +TimeoutStopSec=102 +ExecStartPre=/bin/rm -f %t/jadda-jadda.pid %t/jadda-jadda.ctr-id +ExecStart=/usr/bin/podman run --conmon-pidfile %t/jadda-jadda.pid --cidfile %t/jadda-jadda.ctr-id --cgroups=no-conmon ` + + detachparam + + ` awesome-image:latest +ExecStop=/usr/bin/podman stop --ignore --cidfile %t/jadda-jadda.ctr-id -t 42 +ExecStopPost=/usr/bin/podman rm --ignore -f --cidfile %t/jadda-jadda.ctr-id +PIDFile=%t/jadda-jadda.pid +Type=forking + +[Install] +WantedBy=multi-user.target default.target +` + return goodNewDetach + } + + goodNameNewDetachFalseWithCmd := `# jadda-jadda.service +# autogenerated by Podman CI + +[Unit] +Description=Podman jadda-jadda.service +Documentation=man:podman-generate-systemd(1) +Wants=network.target +After=network-online.target + +[Service] +Environment=PODMAN_SYSTEMD_UNIT=%n +Restart=always +TimeoutStopSec=102 +ExecStartPre=/bin/rm -f %t/jadda-jadda.pid %t/jadda-jadda.ctr-id +ExecStart=/usr/bin/podman run --conmon-pidfile %t/jadda-jadda.pid --cidfile %t/jadda-jadda.ctr-id --cgroups=no-conmon -d --replace --name test -p 80:80 awesome-image:latest somecmd --detach=false +ExecStop=/usr/bin/podman stop --ignore --cidfile %t/jadda-jadda.ctr-id -t 42 +ExecStopPost=/usr/bin/podman rm --ignore -f --cidfile %t/jadda-jadda.ctr-id +PIDFile=%t/jadda-jadda.pid +Type=forking + +[Install] +WantedBy=multi-user.target default.target +` + + goodNewRootFlags := `# jadda-jadda.service +# autogenerated by Podman CI + +[Unit] +Description=Podman jadda-jadda.service +Documentation=man:podman-generate-systemd(1) +Wants=network.target +After=network-online.target + +[Service] +Environment=PODMAN_SYSTEMD_UNIT=%n +Restart=always +TimeoutStopSec=102 +ExecStartPre=/bin/rm -f %t/jadda-jadda.pid %t/jadda-jadda.ctr-id +ExecStart=/usr/bin/podman --events-backend none --runroot /root run --conmon-pidfile %t/jadda-jadda.pid --cidfile %t/jadda-jadda.ctr-id --cgroups=no-conmon -d awesome-image:latest +ExecStop=/usr/bin/podman --events-backend none --runroot /root stop --ignore --cidfile %t/jadda-jadda.ctr-id -t 42 +ExecStopPost=/usr/bin/podman --events-backend none --runroot /root rm --ignore -f --cidfile %t/jadda-jadda.ctr-id +PIDFile=%t/jadda-jadda.pid +Type=forking + +[Install] +WantedBy=multi-user.target default.target +` + + goodContainerCreate := `# jadda-jadda.service +# autogenerated by Podman CI + +[Unit] +Description=Podman jadda-jadda.service +Documentation=man:podman-generate-systemd(1) +Wants=network.target +After=network-online.target + +[Service] +Environment=PODMAN_SYSTEMD_UNIT=%n +Restart=always +TimeoutStopSec=70 +ExecStartPre=/bin/rm -f %t/jadda-jadda.pid %t/jadda-jadda.ctr-id +ExecStart=/usr/bin/podman container run --conmon-pidfile %t/jadda-jadda.pid --cidfile %t/jadda-jadda.ctr-id --cgroups=no-conmon -d awesome-image:latest +ExecStop=/usr/bin/podman container stop --ignore --cidfile %t/jadda-jadda.ctr-id -t 10 +ExecStopPost=/usr/bin/podman container rm --ignore -f --cidfile %t/jadda-jadda.ctr-id +PIDFile=%t/jadda-jadda.pid +Type=forking + +[Install] +WantedBy=multi-user.target default.target +` + tests := []struct { name string info containerInfo @@ -321,7 +422,7 @@ WantedBy=multi-user.target default.target PIDFile: "/run/containers/storage/overlay-containers/639c53578af4d84b8800b4635fa4e680ee80fd67e0e6a2d4eea48d1e3230f401/userdata/conmon.pid", StopTimeout: 10, PodmanVersion: "CI", - CreateCommand: []string{"I'll get stripped", "container", "run", "-d", "--name", "jadda-jadda", "--hostname", "hello-world", "awesome-image:latest", "command", "arg1", "...", "argN"}, + CreateCommand: []string{"I'll get stripped", "run", "-d", "--name", "jadda-jadda", "--hostname", "hello-world", "awesome-image:latest", "command", "arg1", "...", "argN"}, EnvVariable: EnvVariable, }, goodWithExplicitShortDetachParam, @@ -337,7 +438,7 @@ WantedBy=multi-user.target default.target PIDFile: "/run/containers/storage/overlay-containers/639c53578af4d84b8800b4635fa4e680ee80fd67e0e6a2d4eea48d1e3230f401/userdata/conmon.pid", StopTimeout: 10, PodmanVersion: "CI", - CreateCommand: []string{"I'll get stripped", "container", "run", "-d", "--name", "jadda-jadda", "--hostname", "hello-world", "awesome-image:latest", "command", "arg1", "...", "argN"}, + CreateCommand: []string{"I'll get stripped", "run", "-d", "--name", "jadda-jadda", "--hostname", "hello-world", "awesome-image:latest", "command", "arg1", "...", "argN"}, EnvVariable: EnvVariable, pod: &podInfo{ PodIDFile: "/tmp/pod-foobar.pod-id-file", @@ -356,7 +457,7 @@ WantedBy=multi-user.target default.target PIDFile: "/run/containers/storage/overlay-containers/639c53578af4d84b8800b4635fa4e680ee80fd67e0e6a2d4eea48d1e3230f401/userdata/conmon.pid", StopTimeout: 10, PodmanVersion: "CI", - CreateCommand: []string{"I'll get stripped", "container", "run", "--detach", "--name", "jadda-jadda", "--hostname", "hello-world", "awesome-image:latest", "command", "arg1", "...", "argN"}, + CreateCommand: []string{"I'll get stripped", "run", "--detach", "--name", "jadda-jadda", "--hostname", "hello-world", "awesome-image:latest", "command", "arg1", "...", "argN"}, EnvVariable: EnvVariable, }, goodNameNewDetach, @@ -372,13 +473,141 @@ WantedBy=multi-user.target default.target PIDFile: "/run/containers/storage/overlay-containers/639c53578af4d84b8800b4635fa4e680ee80fd67e0e6a2d4eea48d1e3230f401/userdata/conmon.pid", StopTimeout: 10, PodmanVersion: "CI", - CreateCommand: []string{"I'll get stripped", "container", "run", "awesome-image:latest"}, + CreateCommand: []string{"I'll get stripped", "run", "awesome-image:latest"}, EnvVariable: EnvVariable, }, goodIDNew, true, false, }, + {"good with explicit detach=true param", + containerInfo{ + Executable: "/usr/bin/podman", + ServiceName: "jadda-jadda", + ContainerNameOrID: "jadda-jadda", + RestartPolicy: "always", + PIDFile: "/var/run/containers/storage/overlay-containers/639c53578af4d84b8800b4635fa4e680ee80fd67e0e6a2d4eea48d1e3230f401/userdata/conmon.pid", + StopTimeout: 42, + PodmanVersion: "CI", + CreateCommand: []string{"I'll get stripped", "run", "--detach=true", "awesome-image:latest"}, + EnvVariable: EnvVariable, + }, + genGoodNewDetach("--detach=true"), + true, + false, + }, + {"good with explicit detach=false param", + containerInfo{ + Executable: "/usr/bin/podman", + ServiceName: "jadda-jadda", + ContainerNameOrID: "jadda-jadda", + RestartPolicy: "always", + PIDFile: "/var/run/containers/storage/overlay-containers/639c53578af4d84b8800b4635fa4e680ee80fd67e0e6a2d4eea48d1e3230f401/userdata/conmon.pid", + StopTimeout: 42, + PodmanVersion: "CI", + CreateCommand: []string{"I'll get stripped", "run", "--detach=false", "awesome-image:latest"}, + EnvVariable: EnvVariable, + }, + genGoodNewDetach("-d"), + true, + false, + }, + {"good with explicit detach=false param", + containerInfo{ + Executable: "/usr/bin/podman", + ServiceName: "jadda-jadda", + ContainerNameOrID: "jadda-jadda", + RestartPolicy: "always", + PIDFile: "/var/run/containers/storage/overlay-containers/639c53578af4d84b8800b4635fa4e680ee80fd67e0e6a2d4eea48d1e3230f401/userdata/conmon.pid", + StopTimeout: 42, + PodmanVersion: "CI", + CreateCommand: []string{"I'll get stripped", "run", "--name", "test", "-p", "80:80", "--detach=false", "awesome-image:latest", "somecmd", "--detach=false"}, + EnvVariable: EnvVariable, + }, + goodNameNewDetachFalseWithCmd, + true, + false, + }, + {"good with multiple detach=false params", + containerInfo{ + Executable: "/usr/bin/podman", + ServiceName: "jadda-jadda", + ContainerNameOrID: "jadda-jadda", + RestartPolicy: "always", + PIDFile: "/var/run/containers/storage/overlay-containers/639c53578af4d84b8800b4635fa4e680ee80fd67e0e6a2d4eea48d1e3230f401/userdata/conmon.pid", + StopTimeout: 42, + PodmanVersion: "CI", + CreateCommand: []string{"I'll get stripped", "run", "--name", "test", "-p", "80:80", "--detach=false", "--detach=false", "awesome-image:latest", "somecmd", "--detach=false"}, + EnvVariable: EnvVariable, + }, + goodNameNewDetachFalseWithCmd, + true, + false, + }, + {"good with multiple shorthand params detach first", + containerInfo{ + Executable: "/usr/bin/podman", + ServiceName: "jadda-jadda", + ContainerNameOrID: "jadda-jadda", + RestartPolicy: "always", + PIDFile: "/var/run/containers/storage/overlay-containers/639c53578af4d84b8800b4635fa4e680ee80fd67e0e6a2d4eea48d1e3230f401/userdata/conmon.pid", + StopTimeout: 42, + PodmanVersion: "CI", + CreateCommand: []string{"I'll get stripped", "run", "-dti", "awesome-image:latest"}, + EnvVariable: EnvVariable, + }, + genGoodNewDetach("-dti"), + true, + false, + }, + {"good with multiple shorthand params detach last", + containerInfo{ + Executable: "/usr/bin/podman", + ServiceName: "jadda-jadda", + ContainerNameOrID: "jadda-jadda", + RestartPolicy: "always", + PIDFile: "/var/run/containers/storage/overlay-containers/639c53578af4d84b8800b4635fa4e680ee80fd67e0e6a2d4eea48d1e3230f401/userdata/conmon.pid", + StopTimeout: 42, + PodmanVersion: "CI", + CreateCommand: []string{"I'll get stripped", "run", "-tid", "awesome-image:latest"}, + EnvVariable: EnvVariable, + }, + genGoodNewDetach("-tid"), + true, + false, + }, + {"good with root flags", + containerInfo{ + Executable: "/usr/bin/podman", + ServiceName: "jadda-jadda", + ContainerNameOrID: "jadda-jadda", + RestartPolicy: "always", + PIDFile: "/var/run/containers/storage/overlay-containers/639c53578af4d84b8800b4635fa4e680ee80fd67e0e6a2d4eea48d1e3230f401/userdata/conmon.pid", + StopTimeout: 42, + PodmanVersion: "CI", + CreateCommand: []string{"I'll get stripped", "--events-backend", "none", "--runroot", "/root", "run", "awesome-image:latest"}, + EnvVariable: EnvVariable, + }, + goodNewRootFlags, + true, + false, + }, + {"good with container create", + containerInfo{ + Executable: "/usr/bin/podman", + ServiceName: "jadda-jadda", + ContainerNameOrID: "jadda-jadda", + RestartPolicy: "always", + PIDFile: "/var/run/containers/storage/overlay-containers/639c53578af4d84b8800b4635fa4e680ee80fd67e0e6a2d4eea48d1e3230f401/userdata/conmon.pid", + StopTimeout: 10, + PodmanVersion: "CI", + CreateCommand: []string{"I'll get stripped", "container", "create", "awesome-image:latest"}, + EnvVariable: EnvVariable, + }, + goodContainerCreate, + true, + false, + }, } for _, tt := range tests { test := tt diff --git a/pkg/systemd/generate/pods.go b/pkg/systemd/generate/pods.go index fc582e42a..7678a240f 100644 --- a/pkg/systemd/generate/pods.go +++ b/pkg/systemd/generate/pods.go @@ -14,6 +14,7 @@ import ( "github.com/containers/podman/v2/version" "github.com/pkg/errors" "github.com/sirupsen/logrus" + "github.com/spf13/pflag" ) // podInfo contains data required for generating a pod's systemd @@ -44,6 +45,9 @@ type podInfo struct { // Executable is the path to the podman executable. Will be auto-filled if // left empty. Executable string + // RootFlags contains the root flags which were used to create the container + // Only used with --new + RootFlags string // TimeStamp at the time of creating the unit file. Will be set internally. TimeStamp string // CreateCommand is the full command plus arguments of the process the @@ -264,7 +268,8 @@ func executePodTemplate(info *podInfo, options entities.GenerateSystemdOptions) if podCreateIndex == 0 { return "", errors.Errorf("pod does not appear to be created via `podman pod create`: %v", info.CreateCommand) } - podRootArgs = info.CreateCommand[0 : podCreateIndex-2] + podRootArgs = info.CreateCommand[1 : podCreateIndex-1] + info.RootFlags = strings.Join(quoteArguments(podRootArgs), " ") podCreateArgs = filterPodFlags(info.CreateCommand[podCreateIndex+1:]) } // We're hard-coding the first five arguments and append the @@ -277,17 +282,26 @@ func executePodTemplate(info *podInfo, options entities.GenerateSystemdOptions) "--pod-id-file", "{{.PodIDFile}}"}...) // Presence check for certain flags/options. - hasNameParam := false - hasReplaceParam := false - for _, p := range podCreateArgs { - switch p { - case "--name": - hasNameParam = true - case "--replace": - hasReplaceParam = true - } + fs := pflag.NewFlagSet("args", pflag.ContinueOnError) + fs.ParseErrorsWhitelist.UnknownFlags = true + fs.Usage = func() {} + fs.SetInterspersed(false) + fs.String("name", "", "") + fs.Bool("replace", false, "") + fs.Parse(podCreateArgs) + + hasNameParam := fs.Lookup("name").Changed + hasReplaceParam, err := fs.GetBool("replace") + if err != nil { + return "", err } if hasNameParam && !hasReplaceParam { + if fs.Changed("replace") { + // this can only happen if --replace=false is set + // in that case we need to remove it otherwise we + // would overwrite the previous replace arg to false + podCreateArgs = removeReplaceArg(podCreateArgs, fs.NArg()) + } podCreateArgs = append(podCreateArgs, "--replace") } @@ -296,9 +310,9 @@ func executePodTemplate(info *podInfo, options entities.GenerateSystemdOptions) info.ExecStartPre1 = "/bin/rm -f {{.PIDFile}} {{.PodIDFile}}" info.ExecStartPre2 = strings.Join(startCommand, " ") - info.ExecStart = "{{.Executable}} pod start --pod-id-file {{.PodIDFile}}" - info.ExecStop = "{{.Executable}} pod stop --ignore --pod-id-file {{.PodIDFile}} {{if (ge .StopTimeout 0)}}-t {{.StopTimeout}}{{end}}" - info.ExecStopPost = "{{.Executable}} pod rm --ignore -f --pod-id-file {{.PodIDFile}}" + info.ExecStart = "{{.Executable}} {{if .RootFlags}}{{ .RootFlags}} {{end}}pod start --pod-id-file {{.PodIDFile}}" + info.ExecStop = "{{.Executable}} {{if .RootFlags}}{{ .RootFlags}} {{end}}pod stop --ignore --pod-id-file {{.PodIDFile}} {{if (ge .StopTimeout 0)}}-t {{.StopTimeout}}{{end}}" + info.ExecStopPost = "{{.Executable}} {{if .RootFlags}}{{ .RootFlags}} {{end}}pod rm --ignore -f --pod-id-file {{.PodIDFile}}" } info.TimeoutStopSec = minTimeoutStopSec + info.StopTimeout diff --git a/pkg/systemd/generate/pods_test.go b/pkg/systemd/generate/pods_test.go index 93432ef96..c0d98df45 100644 --- a/pkg/systemd/generate/pods_test.go +++ b/pkg/systemd/generate/pods_test.go @@ -89,6 +89,60 @@ Type=forking WantedBy=multi-user.target default.target ` + podGoodNamedNewWithRootArgs := `# pod-123abc.service +# autogenerated by Podman CI + +[Unit] +Description=Podman pod-123abc.service +Documentation=man:podman-generate-systemd(1) +Wants=network.target +After=network-online.target +Requires=container-1.service container-2.service +Before=container-1.service container-2.service + +[Service] +Environment=PODMAN_SYSTEMD_UNIT=%n +Restart=on-failure +TimeoutStopSec=70 +ExecStartPre=/bin/rm -f %t/pod-123abc.pid %t/pod-123abc.pod-id +ExecStartPre=/usr/bin/podman --events-backend none --runroot /root pod create --infra-conmon-pidfile %t/pod-123abc.pid --pod-id-file %t/pod-123abc.pod-id --name foo "bar=arg with space" --replace +ExecStart=/usr/bin/podman --events-backend none --runroot /root pod start --pod-id-file %t/pod-123abc.pod-id +ExecStop=/usr/bin/podman --events-backend none --runroot /root pod stop --ignore --pod-id-file %t/pod-123abc.pod-id -t 10 +ExecStopPost=/usr/bin/podman --events-backend none --runroot /root pod rm --ignore -f --pod-id-file %t/pod-123abc.pod-id +PIDFile=%t/pod-123abc.pid +Type=forking + +[Install] +WantedBy=multi-user.target default.target +` + + podGoodNamedNewWithReplaceFalse := `# pod-123abc.service +# autogenerated by Podman CI + +[Unit] +Description=Podman pod-123abc.service +Documentation=man:podman-generate-systemd(1) +Wants=network.target +After=network-online.target +Requires=container-1.service container-2.service +Before=container-1.service container-2.service + +[Service] +Environment=PODMAN_SYSTEMD_UNIT=%n +Restart=on-failure +TimeoutStopSec=70 +ExecStartPre=/bin/rm -f %t/pod-123abc.pid %t/pod-123abc.pod-id +ExecStartPre=/usr/bin/podman pod create --infra-conmon-pidfile %t/pod-123abc.pid --pod-id-file %t/pod-123abc.pod-id --name foo --replace +ExecStart=/usr/bin/podman pod start --pod-id-file %t/pod-123abc.pod-id +ExecStop=/usr/bin/podman pod stop --ignore --pod-id-file %t/pod-123abc.pod-id -t 10 +ExecStopPost=/usr/bin/podman pod rm --ignore -f --pod-id-file %t/pod-123abc.pod-id +PIDFile=%t/pod-123abc.pid +Type=forking + +[Install] +WantedBy=multi-user.target default.target +` + tests := []struct { name string info podInfo @@ -106,6 +160,23 @@ WantedBy=multi-user.target default.target StopTimeout: 42, PodmanVersion: "CI", RequiredServices: []string{"container-1", "container-2"}, + CreateCommand: []string{"podman", "pod", "create", "--name", "foo", "bar=arg with space"}, + }, + podGood, + false, + false, + }, + {"pod with root args", + podInfo{ + Executable: "/usr/bin/podman", + ServiceName: "pod-123abc", + InfraNameOrID: "jadda-jadda-infra", + RestartPolicy: "always", + PIDFile: "/var/run/containers/storage/overlay-containers/639c53578af4d84b8800b4635fa4e680ee80fd67e0e6a2d4eea48d1e3230f401/userdata/conmon.pid", + StopTimeout: 42, + PodmanVersion: "CI", + RequiredServices: []string{"container-1", "container-2"}, + CreateCommand: []string{"podman", "--events-backend", "none", "--runroot", "/root", "pod", "create", "--name", "foo", "bar=arg with space"}, }, podGood, false, @@ -127,6 +198,38 @@ WantedBy=multi-user.target default.target true, false, }, + {"pod --new with root args", + podInfo{ + Executable: "/usr/bin/podman", + ServiceName: "pod-123abc", + InfraNameOrID: "jadda-jadda-infra", + RestartPolicy: "on-failure", + PIDFile: "/var/run/containers/storage/overlay-containers/639c53578af4d84b8800b4635fa4e680ee80fd67e0e6a2d4eea48d1e3230f401/userdata/conmon.pid", + StopTimeout: 10, + PodmanVersion: "CI", + RequiredServices: []string{"container-1", "container-2"}, + CreateCommand: []string{"podman", "--events-backend", "none", "--runroot", "/root", "pod", "create", "--name", "foo", "bar=arg with space"}, + }, + podGoodNamedNewWithRootArgs, + true, + false, + }, + {"pod --new with --replace=false", + podInfo{ + Executable: "/usr/bin/podman", + ServiceName: "pod-123abc", + InfraNameOrID: "jadda-jadda-infra", + RestartPolicy: "on-failure", + PIDFile: "/var/run/containers/storage/overlay-containers/639c53578af4d84b8800b4635fa4e680ee80fd67e0e6a2d4eea48d1e3230f401/userdata/conmon.pid", + StopTimeout: 10, + PodmanVersion: "CI", + RequiredServices: []string{"container-1", "container-2"}, + CreateCommand: []string{"podman", "pod", "create", "--name", "foo", "--replace=false"}, + }, + podGoodNamedNewWithReplaceFalse, + true, + false, + }, } for _, tt := range tests { diff --git a/test/e2e/checkpoint_test.go b/test/e2e/checkpoint_test.go index 4c6b2cf5c..abc37792a 100644 --- a/test/e2e/checkpoint_test.go +++ b/test/e2e/checkpoint_test.go @@ -4,6 +4,7 @@ import ( "net" "os" "os/exec" + "strings" "github.com/containers/podman/v2/pkg/criu" . "github.com/containers/podman/v2/test/utils" @@ -747,4 +748,78 @@ var _ = Describe("Podman checkpoint", func() { // Remove exported checkpoint os.Remove(checkpointFileName) }) + + It("podman checkpoint container with --pre-checkpoint", func() { + if !strings.Contains(podmanTest.OCIRuntime, "runc") { + Skip("Test only works on runc 1.0-rc3 or higher.") + } + localRunString := getRunString([]string{ALPINE, "top"}) + session := podmanTest.Podman(localRunString) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + cid := session.OutputToString() + + result := podmanTest.Podman([]string{"container", "checkpoint", "-P", cid}) + result.WaitWithDefaultTimeout() + + Expect(result.ExitCode()).To(Equal(0)) + Expect(podmanTest.NumberOfContainersRunning()).To(Equal(1)) + Expect(podmanTest.GetContainerStatus()).To(ContainSubstring("Up")) + + result = podmanTest.Podman([]string{"container", "checkpoint", "--with-previous", cid}) + result.WaitWithDefaultTimeout() + + Expect(result.ExitCode()).To(Equal(0)) + Expect(podmanTest.NumberOfContainersRunning()).To(Equal(0)) + Expect(podmanTest.GetContainerStatus()).To(ContainSubstring("Exited")) + + result = podmanTest.Podman([]string{"container", "restore", cid}) + result.WaitWithDefaultTimeout() + + Expect(result.ExitCode()).To(Equal(0)) + Expect(podmanTest.NumberOfContainersRunning()).To(Equal(1)) + Expect(podmanTest.GetContainerStatus()).To(ContainSubstring("Up")) + }) + + It("podman checkpoint container with --pre-checkpoint and export (migration)", func() { + if !strings.Contains(podmanTest.OCIRuntime, "runc") { + Skip("Test only works on runc 1.0-rc3 or higher.") + } + localRunString := getRunString([]string{ALPINE, "top"}) + session := podmanTest.Podman(localRunString) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + cid := session.OutputToString() + preCheckpointFileName := "/tmp/pre-checkpoint-" + cid + ".tar.gz" + checkpointFileName := "/tmp/checkpoint-" + cid + ".tar.gz" + + result := podmanTest.Podman([]string{"container", "checkpoint", "-P", "-e", preCheckpointFileName, cid}) + result.WaitWithDefaultTimeout() + + Expect(result.ExitCode()).To(Equal(0)) + Expect(podmanTest.NumberOfContainersRunning()).To(Equal(1)) + Expect(podmanTest.GetContainerStatus()).To(ContainSubstring("Up")) + + result = podmanTest.Podman([]string{"container", "checkpoint", "--with-previous", "-e", checkpointFileName, cid}) + result.WaitWithDefaultTimeout() + + Expect(result.ExitCode()).To(Equal(0)) + Expect(podmanTest.NumberOfContainersRunning()).To(Equal(0)) + Expect(podmanTest.GetContainerStatus()).To(ContainSubstring("Exited")) + + result = podmanTest.Podman([]string{"rm", "-f", cid}) + result.WaitWithDefaultTimeout() + Expect(result.ExitCode()).To(Equal(0)) + Expect(podmanTest.NumberOfContainersRunning()).To(Equal(0)) + + result = podmanTest.Podman([]string{"container", "restore", "-i", checkpointFileName, "--import-previous", preCheckpointFileName}) + result.WaitWithDefaultTimeout() + + Expect(result.ExitCode()).To(Equal(0)) + Expect(podmanTest.NumberOfContainersRunning()).To(Equal(1)) + Expect(podmanTest.GetContainerStatus()).To(ContainSubstring("Up")) + + os.Remove(checkpointFileName) + os.Remove(preCheckpointFileName) + }) }) diff --git a/test/e2e/generate_systemd_test.go b/test/e2e/generate_systemd_test.go index 3f059300b..be9727591 100644 --- a/test/e2e/generate_systemd_test.go +++ b/test/e2e/generate_systemd_test.go @@ -59,8 +59,7 @@ var _ = Describe("Podman generate systemd", func() { session = podmanTest.Podman([]string{"generate", "systemd", "--restart-policy", "bogus", "foobar"}) session.WaitWithDefaultTimeout() Expect(session).To(ExitWithError()) - found, _ := session.ErrorGrepString("bogus is not a valid restart policy") - Expect(found).Should(BeTrue()) + Expect(session.ErrorToString()).To(ContainSubstring("bogus is not a valid restart policy")) }) It("podman generate systemd good timeout value", func() { @@ -71,12 +70,8 @@ var _ = Describe("Podman generate systemd", func() { session = podmanTest.Podman([]string{"generate", "systemd", "--time", "1234", "foobar"}) session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) - - found, _ := session.GrepString(" stop -t 1234 ") - Expect(found).To(BeTrue()) - - found, _ = session.GrepString("TimeoutStopSec=1294") - Expect(found).To(BeTrue()) + Expect(session.OutputToString()).To(ContainSubstring("TimeoutStopSec=1294")) + Expect(session.OutputToString()).To(ContainSubstring(" stop -t 1234 ")) }) It("podman generate systemd", func() { @@ -87,6 +82,9 @@ var _ = Describe("Podman generate systemd", func() { session := podmanTest.Podman([]string{"generate", "systemd", "nginx"}) session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) + + // The podman commands in the unit should not contain the root flags + Expect(session.OutputToString()).ToNot(ContainSubstring(" --runroot")) }) It("podman generate systemd --files --name", func() { @@ -101,9 +99,7 @@ var _ = Describe("Podman generate systemd", func() { for _, file := range session.OutputToStringArray() { os.Remove(file) } - - found, _ := session.GrepString("/container-nginx.service") - Expect(found).To(BeTrue()) + Expect(session.OutputToString()).To(ContainSubstring("/container-nginx.service")) }) It("podman generate systemd with timeout", func() { @@ -114,9 +110,7 @@ var _ = Describe("Podman generate systemd", func() { session := podmanTest.Podman([]string{"generate", "systemd", "--time", "5", "nginx"}) session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) - - found, _ := session.GrepString("podman stop -t 5") - Expect(found).To(BeTrue()) + Expect(session.OutputToString()).To(ContainSubstring("podman stop -t 5")) }) It("podman generate systemd pod --name", func() { @@ -137,35 +131,19 @@ var _ = Describe("Podman generate systemd", func() { Expect(session.ExitCode()).To(Equal(0)) // Grepping the output (in addition to unit tests) - found, _ := session.GrepString("# pod-foo.service") - Expect(found).To(BeTrue()) - - found, _ = session.GrepString("Requires=container-foo-1.service container-foo-2.service") - Expect(found).To(BeTrue()) - - found, _ = session.GrepString("# container-foo-1.service") - Expect(found).To(BeTrue()) - - found, _ = session.GrepString(" start foo-1") - Expect(found).To(BeTrue()) - - found, _ = session.GrepString("-infra") // infra container - Expect(found).To(BeTrue()) - - found, _ = session.GrepString("# container-foo-2.service") - Expect(found).To(BeTrue()) - - found, _ = session.GrepString(" stop -t 42 foo-2") - Expect(found).To(BeTrue()) - - found, _ = session.GrepString("BindsTo=pod-foo.service") - Expect(found).To(BeTrue()) - - found, _ = session.GrepString("PIDFile=") - Expect(found).To(BeTrue()) - - found, _ = session.GrepString("/userdata/conmon.pid") - Expect(found).To(BeTrue()) + Expect(session.OutputToString()).To(ContainSubstring("# pod-foo.service")) + Expect(session.OutputToString()).To(ContainSubstring("Requires=container-foo-1.service container-foo-2.service")) + Expect(session.OutputToString()).To(ContainSubstring("# container-foo-1.service")) + Expect(session.OutputToString()).To(ContainSubstring(" start foo-1")) + Expect(session.OutputToString()).To(ContainSubstring("-infra")) // infra container + Expect(session.OutputToString()).To(ContainSubstring("# container-foo-2.service")) + Expect(session.OutputToString()).To(ContainSubstring(" stop -t 42 foo-2")) + Expect(session.OutputToString()).To(ContainSubstring("BindsTo=pod-foo.service")) + Expect(session.OutputToString()).To(ContainSubstring("PIDFile=")) + Expect(session.OutputToString()).To(ContainSubstring("/userdata/conmon.pid")) + + // The podman commands in the unit should not contain the root flags + Expect(session.OutputToString()).ToNot(ContainSubstring(" --runroot")) }) It("podman generate systemd pod --name --files", func() { @@ -185,11 +163,8 @@ var _ = Describe("Podman generate systemd", func() { os.Remove(file) } - found, _ := session.GrepString("/pod-foo.service") - Expect(found).To(BeTrue()) - - found, _ = session.GrepString("/container-foo-1.service") - Expect(found).To(BeTrue()) + Expect(session.OutputToString()).To(ContainSubstring("/pod-foo.service")) + Expect(session.OutputToString()).To(ContainSubstring("/container-foo-1.service")) }) It("podman generate systemd --new --name foo", func() { @@ -202,14 +177,13 @@ var _ = Describe("Podman generate systemd", func() { Expect(session.ExitCode()).To(Equal(0)) // Grepping the output (in addition to unit tests) - found, _ := session.GrepString("# container-foo.service") - Expect(found).To(BeTrue()) - - found, _ = session.GrepString(" --replace ") - Expect(found).To(BeTrue()) - - found, _ = session.GrepString("stop --ignore --cidfile %t/container-foo.ctr-id -t 42") - Expect(found).To(BeTrue()) + Expect(session.OutputToString()).To(ContainSubstring("# container-foo.service")) + Expect(session.OutputToString()).To(ContainSubstring(" --replace ")) + Expect(session.OutputToString()).To(ContainSubstring(" stop --ignore --cidfile %t/container-foo.ctr-id -t 42")) + if !IsRemote() { + // The podman commands in the unit should contain the root flags if generate systemd --new is used + Expect(session.OutputToString()).To(ContainSubstring(" --runroot")) + } }) It("podman generate systemd --new --name=foo", func() { @@ -222,14 +196,9 @@ var _ = Describe("Podman generate systemd", func() { Expect(session.ExitCode()).To(Equal(0)) // Grepping the output (in addition to unit tests) - found, _ := session.GrepString("# container-foo.service") - Expect(found).To(BeTrue()) - - found, _ = session.GrepString(" --replace ") - Expect(found).To(BeTrue()) - - found, _ = session.GrepString("stop --ignore --cidfile %t/container-foo.ctr-id -t 42") - Expect(found).To(BeTrue()) + Expect(session.OutputToString()).To(ContainSubstring("# container-foo.service")) + Expect(session.OutputToString()).To(ContainSubstring(" --replace ")) + Expect(session.OutputToString()).To(ContainSubstring(" stop --ignore --cidfile %t/container-foo.ctr-id -t 42")) }) It("podman generate systemd --new without explicit detaching param", func() { @@ -242,8 +211,7 @@ var _ = Describe("Podman generate systemd", func() { Expect(session.ExitCode()).To(Equal(0)) // Grepping the output (in addition to unit tests) - found, _ := session.GrepString("--cgroups=no-conmon -d") - Expect(found).To(BeTrue()) + Expect(session.OutputToString()).To(ContainSubstring("--cgroups=no-conmon -d")) }) It("podman generate systemd --new with explicit detaching param in middle", func() { @@ -256,8 +224,7 @@ var _ = Describe("Podman generate systemd", func() { Expect(session.ExitCode()).To(Equal(0)) // Grepping the output (in addition to unit tests) - found, _ := session.GrepString("--name foo alpine top") - Expect(found).To(BeTrue()) + Expect(session.OutputToString()).To(ContainSubstring("--name foo alpine top")) }) It("podman generate systemd --new pod", func() { @@ -280,8 +247,8 @@ var _ = Describe("Podman generate systemd", func() { Expect(session.ExitCode()).To(Equal(0)) // Grepping the output (in addition to unit tests) - found, _ := session.GrepString("# con-foo.service") - Expect(found).To(BeTrue()) + Expect(session.OutputToString()).To(ContainSubstring("# con-foo.service")) + }) It("podman generate systemd --separator _", func() { @@ -294,8 +261,7 @@ var _ = Describe("Podman generate systemd", func() { Expect(session.ExitCode()).To(Equal(0)) // Grepping the output (in addition to unit tests) - found, _ := session.GrepString("# container_foo.service") - Expect(found).To(BeTrue()) + Expect(session.OutputToString()).To(ContainSubstring("# container_foo.service")) }) It("podman generate systemd pod --pod-prefix p", func() { @@ -316,17 +282,10 @@ var _ = Describe("Podman generate systemd", func() { Expect(session.ExitCode()).To(Equal(0)) // Grepping the output (in addition to unit tests) - found, _ := session.GrepString("# p-foo.service") - Expect(found).To(BeTrue()) - - found, _ = session.GrepString("Requires=container-foo-1.service container-foo-2.service") - Expect(found).To(BeTrue()) - - found, _ = session.GrepString("# container-foo-1.service") - Expect(found).To(BeTrue()) - - found, _ = session.GrepString("BindsTo=p-foo.service") - Expect(found).To(BeTrue()) + Expect(session.OutputToString()).To(ContainSubstring("# p-foo.service")) + Expect(session.OutputToString()).To(ContainSubstring("Requires=container-foo-1.service container-foo-2.service")) + Expect(session.OutputToString()).To(ContainSubstring("# container-foo-1.service")) + Expect(session.OutputToString()).To(ContainSubstring("BindsTo=p-foo.service")) }) It("podman generate systemd pod --pod-prefix p --container-prefix con --separator _ change all prefixes/separator", func() { @@ -347,20 +306,11 @@ var _ = Describe("Podman generate systemd", func() { Expect(session.ExitCode()).To(Equal(0)) // Grepping the output (in addition to unit tests) - found, _ := session.GrepString("# p_foo.service") - Expect(found).To(BeTrue()) - - found, _ = session.GrepString("Requires=con_foo-1.service con_foo-2.service") - Expect(found).To(BeTrue()) - - found, _ = session.GrepString("# con_foo-1.service") - Expect(found).To(BeTrue()) - - found, _ = session.GrepString("# con_foo-2.service") - Expect(found).To(BeTrue()) - - found, _ = session.GrepString("BindsTo=p_foo.service") - Expect(found).To(BeTrue()) + Expect(session.OutputToString()).To(ContainSubstring("# p_foo.service")) + Expect(session.OutputToString()).To(ContainSubstring("Requires=con_foo-1.service con_foo-2.service")) + Expect(session.OutputToString()).To(ContainSubstring("# con_foo-1.service")) + Expect(session.OutputToString()).To(ContainSubstring("# con_foo-2.service")) + Expect(session.OutputToString()).To(ContainSubstring("BindsTo=p_foo.service")) }) It("podman generate systemd pod with containers --new", func() { @@ -386,26 +336,13 @@ var _ = Describe("Podman generate systemd", func() { Expect(session.ExitCode()).To(Equal(0)) // Grepping the output (in addition to unit tests) - found, _ := session.GrepString("# pod-foo.service") - Expect(found).To(BeTrue()) - - found, _ = session.GrepString("Requires=container-foo-1.service container-foo-2.service") - Expect(found).To(BeTrue()) - - found, _ = session.GrepString("BindsTo=pod-foo.service") - Expect(found).To(BeTrue()) - - found, _ = session.GrepString("pod create --infra-conmon-pidfile %t/pod-foo.pid --pod-id-file %t/pod-foo.pod-id --name foo") - Expect(found).To(BeTrue()) - - found, _ = session.GrepString("ExecStartPre=/bin/rm -f %t/pod-foo.pid %t/pod-foo.pod-id") - Expect(found).To(BeTrue()) - - found, _ = session.GrepString("pod stop --ignore --pod-id-file %t/pod-foo.pod-id -t 10") - Expect(found).To(BeTrue()) - - found, _ = session.GrepString("pod rm --ignore -f --pod-id-file %t/pod-foo.pod-id") - Expect(found).To(BeTrue()) + Expect(session.OutputToString()).To(ContainSubstring("# pod-foo.service")) + Expect(session.OutputToString()).To(ContainSubstring("Requires=container-foo-1.service container-foo-2.service")) + Expect(session.OutputToString()).To(ContainSubstring("BindsTo=pod-foo.service")) + Expect(session.OutputToString()).To(ContainSubstring("pod create --infra-conmon-pidfile %t/pod-foo.pid --pod-id-file %t/pod-foo.pod-id --name foo")) + Expect(session.OutputToString()).To(ContainSubstring("ExecStartPre=/bin/rm -f %t/pod-foo.pid %t/pod-foo.pod-id")) + Expect(session.OutputToString()).To(ContainSubstring("pod stop --ignore --pod-id-file %t/pod-foo.pod-id -t 10")) + Expect(session.OutputToString()).To(ContainSubstring("pod rm --ignore -f --pod-id-file %t/pod-foo.pod-id")) }) It("podman generate systemd --format json", func() { diff --git a/test/e2e/search_test.go b/test/e2e/search_test.go index f809c5afe..1d86ae744 100644 --- a/test/e2e/search_test.go +++ b/test/e2e/search_test.go @@ -124,6 +124,16 @@ registries = ['{{.Host}}:{{.Port}}']` Expect(search.OutputToString()).To(ContainSubstring("docker.io/library/alpine")) }) + It("podman search format json list tags", func() { + search := podmanTest.Podman([]string{"search", "--list-tags", "--format", "json", "alpine"}) + search.WaitWithDefaultTimeout() + Expect(search.ExitCode()).To(Equal(0)) + Expect(search.IsJSONOutputValid()).To(BeTrue()) + Expect(search.OutputToString()).To(ContainSubstring("docker.io/library/alpine")) + Expect(search.OutputToString()).To(ContainSubstring("3.10")) + Expect(search.OutputToString()).To(ContainSubstring("2.7")) + }) + It("podman search no-trunc flag", func() { search := podmanTest.Podman([]string{"search", "--no-trunc", "alpine"}) search.WaitWithDefaultTimeout() diff --git a/vendor/github.com/containers/common/pkg/auth/auth.go b/vendor/github.com/containers/common/pkg/auth/auth.go index 21b988187..8daaf4c08 100644 --- a/vendor/github.com/containers/common/pkg/auth/auth.go +++ b/vendor/github.com/containers/common/pkg/auth/auth.go @@ -16,10 +16,17 @@ import ( "golang.org/x/crypto/ssh/terminal" ) -// GetDefaultAuthFile returns env value REGISTRY_AUTH_FILE as default --authfile path -// used in multiple --authfile flag definitions +// GetDefaultAuthFile returns env value REGISTRY_AUTH_FILE as default +// --authfile path used in multiple --authfile flag definitions +// Will fail over to DOCKER_CONFIG if REGISTRY_AUTH_FILE environment is not set func GetDefaultAuthFile() string { - return os.Getenv("REGISTRY_AUTH_FILE") + authfile := os.Getenv("REGISTRY_AUTH_FILE") + if authfile == "" { + if authfile, ok := os.LookupEnv("DOCKER_CONFIG"); ok { + logrus.Infof("Using DOCKER_CONFIG environment variable for authfile path %s", authfile) + } + } + return authfile } // CheckAuthFile validates filepath given by --authfile diff --git a/vendor/github.com/containers/common/pkg/completion/completion.go b/vendor/github.com/containers/common/pkg/completion/completion.go index 07451e992..90fe2f111 100644 --- a/vendor/github.com/containers/common/pkg/completion/completion.go +++ b/vendor/github.com/containers/common/pkg/completion/completion.go @@ -91,3 +91,51 @@ func AutocompleteSubgidName(cmd *cobra.Command, args []string, toComplete string func AutocompleteSubuidName(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { return autocompleteSubIDName("/etc/subuid") } + +// AutocompleteArch - Autocomplete platform supported by container engines +func AutocompletePlatform(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + completions := []string{ + "linux/386", + "linux/amd64", + "linux/arm", + "linux/arm64", + "linux/ppc64", + "linux/ppc64le", + "linux/mips", + "linux/mipsle", + "linux/mips64", + "linux/mips64le", + "linux/riscv64", + "linux/s390x", + "windows/386", + "windows/amd64", + "windows/arm", + } + return completions, cobra.ShellCompDirectiveNoFileComp +} + +// AutocompleteArch - Autocomplete architectures supported by container engines +func AutocompleteArch(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + completions := []string{ + "386", + "amd64", + "arm", + "arm64", + "ppc64", + "ppc64le", + "mips", + "mipsle", + "mips64", + "mips64le", + "riscv64", + "s390x", + } + + return completions, cobra.ShellCompDirectiveNoFileComp +} + +// AutocompleteOS - Autocomplete OS supported by container engines +func AutocompleteOS(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + completions := []string{"linux", "windows"} + return completions, cobra.ShellCompDirectiveNoFileComp +} diff --git a/vendor/github.com/containers/common/pkg/config/config.go b/vendor/github.com/containers/common/pkg/config/config.go index ea08ab6ad..16817f7b3 100644 --- a/vendor/github.com/containers/common/pkg/config/config.go +++ b/vendor/github.com/containers/common/pkg/config/config.go @@ -746,13 +746,20 @@ func (c *Config) FindConmon() (string, error) { } // GetDefaultEnv returns the environment variables for the container. -// It will checn the HTTPProxy and HostEnv booleans and add the appropriate +// It will check the HTTPProxy and HostEnv booleans and add the appropriate // environment variables to the container. func (c *Config) GetDefaultEnv() []string { + return c.GetDefaultEnvEx(c.Containers.EnvHost, c.Containers.HTTPProxy) +} + +// GetDefaultEnvEx returns the environment variables for the container. +// It will check the HTTPProxy and HostEnv boolean parameters and return the appropriate +// environment variables for the container. +func (c *Config) GetDefaultEnvEx(envHost, httpProxy bool) []string { var env []string - if c.Containers.EnvHost { + if envHost { env = append(env, os.Environ()...) - } else if c.Containers.HTTPProxy { + } else if httpProxy { proxy := []string{"http_proxy", "https_proxy", "ftp_proxy", "no_proxy", "HTTP_PROXY", "HTTPS_PROXY", "FTP_PROXY", "NO_PROXY"} for _, p := range proxy { if val, ok := os.LookupEnv(p); ok { diff --git a/vendor/github.com/containers/common/pkg/config/default.go b/vendor/github.com/containers/common/pkg/config/default.go index 6b7aee987..2e26fb7b8 100644 --- a/vendor/github.com/containers/common/pkg/config/default.go +++ b/vendor/github.com/containers/common/pkg/config/default.go @@ -184,7 +184,7 @@ func DefaultConfig() (*Config, error) { "TERM=xterm", }, EnvHost: false, - HTTPProxy: false, + HTTPProxy: true, Init: false, InitPath: "", IPCNS: "private", diff --git a/vendor/github.com/containers/common/pkg/retry/retry.go b/vendor/github.com/containers/common/pkg/retry/retry.go index f6ecab0c0..a06c7c08d 100644 --- a/vendor/github.com/containers/common/pkg/retry/retry.go +++ b/vendor/github.com/containers/common/pkg/retry/retry.go @@ -30,7 +30,7 @@ func RetryIfNecessary(ctx context.Context, operation func() error, retryOptions if retryOptions.Delay != 0 { delay = retryOptions.Delay } - logrus.Infof("Warning: failed, retrying in %s ... (%d/%d). Error: %v", delay, attempt+1, retryOptions.MaxRetry, err) + logrus.Warnf("failed, retrying in %s ... (%d/%d). Error: %v", delay, attempt+1, retryOptions.MaxRetry, err) select { case <-time.After(delay): break @@ -69,7 +69,7 @@ func isRetryable(err error) bool { } return isRetryable(e.Err) case syscall.Errno: - return e != syscall.ECONNREFUSED + return shouldRestart(e) case errcode.Errors: // if this error is a group of errors, process them all in turn for i := range e { @@ -93,3 +93,11 @@ func isRetryable(err error) bool { return false } + +func shouldRestart(e error) bool { + switch e { + case syscall.ECONNREFUSED, syscall.EINTR, syscall.EAGAIN, syscall.EBUSY, syscall.ENETDOWN, syscall.ENETUNREACH, syscall.ENETRESET, syscall.ECONNABORTED, syscall.ECONNRESET, syscall.ETIMEDOUT, syscall.EHOSTDOWN, syscall.EHOSTUNREACH: + return true + } + return shouldRestartPlatform(e) +} diff --git a/vendor/github.com/containers/common/pkg/retry/retry_linux.go b/vendor/github.com/containers/common/pkg/retry/retry_linux.go new file mode 100644 index 000000000..9da0ba287 --- /dev/null +++ b/vendor/github.com/containers/common/pkg/retry/retry_linux.go @@ -0,0 +1,9 @@ +package retry + +import ( + "syscall" +) + +func shouldRestartPlatform(e error) bool { + return e == syscall.ERESTART +} diff --git a/vendor/github.com/containers/common/pkg/retry/retry_unsupported.go b/vendor/github.com/containers/common/pkg/retry/retry_unsupported.go new file mode 100644 index 000000000..cf55b2a94 --- /dev/null +++ b/vendor/github.com/containers/common/pkg/retry/retry_unsupported.go @@ -0,0 +1,7 @@ +// +build !linux + +package retry + +func shouldRestartPlatform(e error) bool { + return false +} diff --git a/vendor/github.com/containers/common/pkg/seccomp/default_linux.go b/vendor/github.com/containers/common/pkg/seccomp/default_linux.go index a127571b5..5c4427318 100644 --- a/vendor/github.com/containers/common/pkg/seccomp/default_linux.go +++ b/vendor/github.com/containers/common/pkg/seccomp/default_linux.go @@ -378,7 +378,6 @@ func DefaultProfile() *Seccomp { "utimensat_time64", "utimes", "vfork", - "vmsplice", "wait4", "waitid", "waitpid", diff --git a/vendor/github.com/containers/common/pkg/seccomp/seccomp.json b/vendor/github.com/containers/common/pkg/seccomp/seccomp.json index 8fb509345..d6f3f4938 100644 --- a/vendor/github.com/containers/common/pkg/seccomp/seccomp.json +++ b/vendor/github.com/containers/common/pkg/seccomp/seccomp.json @@ -378,7 +378,6 @@ "utimensat_time64", "utimes", "vfork", - "vmsplice", "wait4", "waitid", "waitpid", diff --git a/vendor/github.com/containers/common/version/version.go b/vendor/github.com/containers/common/version/version.go index 4366848ea..b696294ac 100644 --- a/vendor/github.com/containers/common/version/version.go +++ b/vendor/github.com/containers/common/version/version.go @@ -1,4 +1,4 @@ package version // Version is the version of the build. -const Version = "0.31.2" +const Version = "0.33.0" diff --git a/vendor/modules.txt b/vendor/modules.txt index e660c262e..e15b16188 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -89,7 +89,7 @@ github.com/containers/buildah/pkg/parse github.com/containers/buildah/pkg/rusage github.com/containers/buildah/pkg/supplemented github.com/containers/buildah/util -# github.com/containers/common v0.31.2 +# github.com/containers/common v0.33.0 github.com/containers/common/pkg/apparmor github.com/containers/common/pkg/apparmor/internal/supported github.com/containers/common/pkg/auth |