diff options
83 files changed, 1345 insertions, 353 deletions
diff --git a/cmd/podman/common/create.go b/cmd/podman/common/create.go index 4598e535d..d73fa653f 100644 --- a/cmd/podman/common/create.go +++ b/cmd/podman/common/create.go @@ -201,6 +201,20 @@ func DefineCreateFlags(cmd *cobra.Command, cf *entities.ContainerCreateOptions, ) _ = cmd.RegisterFlagCompletionFunc(envFlagName, completion.AutocompleteNone) + unsetenvFlagName := "unsetenv" + createFlags.StringArrayVar( + &cf.UnsetEnv, + unsetenvFlagName, []string{}, + "Unset environment default variables in container", + ) + _ = cmd.RegisterFlagCompletionFunc(unsetenvFlagName, completion.AutocompleteNone) + + createFlags.BoolVar( + &cf.UnsetEnvAll, + "unsetenv-all", false, + "Unset all default environment variables in container", + ) + if !registry.IsRemote() { createFlags.BoolVar( &cf.EnvHost, diff --git a/cmd/podman/common/create_opts.go b/cmd/podman/common/create_opts.go index 6283eb28e..aacdfd274 100644 --- a/cmd/podman/common/create_opts.go +++ b/cmd/podman/common/create_opts.go @@ -297,6 +297,8 @@ func ContainerCreateToContainerCLIOpts(cc handlers.CreateContainerConfig, rtc *c Systemd: "true", // podman default TmpFS: parsedTmp, TTY: cc.Config.Tty, + UnsetEnv: cc.UnsetEnv, + UnsetEnvAll: cc.UnsetEnvAll, User: cc.Config.User, UserNS: string(cc.HostConfig.UsernsMode), UTS: string(cc.HostConfig.UTSMode), diff --git a/cmd/podman/containers/checkpoint.go b/cmd/podman/containers/checkpoint.go index 4fa72d520..d92bc3e5e 100644 --- a/cmd/podman/containers/checkpoint.go +++ b/cmd/podman/containers/checkpoint.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "strings" + "time" "github.com/containers/common/pkg/completion" "github.com/containers/podman/v3/cmd/podman/common" @@ -40,6 +41,11 @@ var ( var checkpointOptions entities.CheckpointOptions +type checkpointStatistics struct { + PodmanDuration int64 `json:"podman_checkpoint_duration"` + ContainerStatistics []*entities.CheckpointReport `json:"container_statistics"` +} + func init() { registry.Commands = append(registry.Commands, registry.CliCommand{ Command: checkpointCommand, @@ -63,11 +69,19 @@ func init() { flags.StringP("compress", "c", "zstd", "Select compression algorithm (gzip, none, zstd) for checkpoint archive.") _ = checkpointCommand.RegisterFlagCompletionFunc("compress", common.AutocompleteCheckpointCompressType) + flags.BoolVar( + &checkpointOptions.PrintStats, + "print-stats", + false, + "Display checkpoint statistics", + ) + validate.AddLatestFlag(checkpointCommand, &checkpointOptions.Latest) } func checkpoint(cmd *cobra.Command, args []string) error { var errs utils.OutputErrors + podmanStart := time.Now() if cmd.Flags().Changed("compress") { if checkpointOptions.Export == "" { return errors.Errorf("--compress can only be used with --export") @@ -102,12 +116,30 @@ func checkpoint(cmd *cobra.Command, args []string) error { if err != nil { return err } + podmanFinished := time.Now() + + var statistics checkpointStatistics + for _, r := range responses { if r.Err == nil { - fmt.Println(r.Id) + if checkpointOptions.PrintStats { + statistics.ContainerStatistics = append(statistics.ContainerStatistics, r) + } else { + fmt.Println(r.Id) + } } else { errs = append(errs, r.Err) } } + + if checkpointOptions.PrintStats { + statistics.PodmanDuration = podmanFinished.Sub(podmanStart).Microseconds() + j, err := json.MarshalIndent(statistics, "", " ") + if err != nil { + return err + } + fmt.Println(string(j)) + } + return errs.PrintErrors() } diff --git a/cmd/podman/containers/restore.go b/cmd/podman/containers/restore.go index 05214f32c..217adc887 100644 --- a/cmd/podman/containers/restore.go +++ b/cmd/podman/containers/restore.go @@ -3,6 +3,7 @@ package containers import ( "context" "fmt" + "time" "github.com/containers/common/pkg/completion" "github.com/containers/podman/v3/cmd/podman/common" @@ -39,6 +40,11 @@ var ( var restoreOptions entities.RestoreOptions +type restoreStatistics struct { + PodmanDuration int64 `json:"podman_restore_duration"` + ContainerStatistics []*entities.RestoreReport `json:"container_statistics"` +} + func init() { registry.Commands = append(registry.Commands, registry.CliCommand{ Command: restoreCommand, @@ -75,11 +81,19 @@ func init() { flags.StringVar(&restoreOptions.Pod, "pod", "", "Restore container into existing Pod (only works with --import)") _ = restoreCommand.RegisterFlagCompletionFunc("pod", common.AutocompletePodsRunning) + flags.BoolVar( + &restoreOptions.PrintStats, + "print-stats", + false, + "Display restore statistics", + ) + validate.AddLatestFlag(restoreCommand, &restoreOptions.Latest) } func restore(cmd *cobra.Command, args []string) error { var errs utils.OutputErrors + podmanStart := time.Now() if rootless.IsRootless() { return errors.New("restoring a container requires root") } @@ -132,12 +146,30 @@ func restore(cmd *cobra.Command, args []string) error { if err != nil { return err } + podmanFinished := time.Now() + + var statistics restoreStatistics + for _, r := range responses { if r.Err == nil { - fmt.Println(r.Id) + if restoreOptions.PrintStats { + statistics.ContainerStatistics = append(statistics.ContainerStatistics, r) + } else { + fmt.Println(r.Id) + } } else { errs = append(errs, r.Err) } } + + if restoreOptions.PrintStats { + statistics.PodmanDuration = podmanFinished.Sub(podmanStart).Microseconds() + j, err := json.MarshalIndent(statistics, "", " ") + if err != nil { + return err + } + fmt.Println(string(j)) + } + return errs.PrintErrors() } diff --git a/cmd/podman/generate/systemd.go b/cmd/podman/generate/systemd.go index 2ab33c26b..cdc103865 100644 --- a/cmd/podman/generate/systemd.go +++ b/cmd/podman/generate/systemd.go @@ -21,6 +21,7 @@ import ( const ( restartPolicyFlagName = "restart-policy" timeFlagName = "time" + newFlagName = "new" ) var ( @@ -53,10 +54,11 @@ func init() { flags := systemdCmd.Flags() flags.BoolVarP(&systemdOptions.Name, "name", "n", false, "Use container/pod names instead of IDs") flags.BoolVarP(&files, "files", "f", false, "Generate .service files instead of printing to stdout") + flags.BoolVar(&systemdOptions.TemplateUnitFile, "template", false, "Make it a template file and use %i and %I specifiers. Working only for containers") flags.UintVarP(&systemdTimeout, timeFlagName, "t", containerConfig.Engine.StopTimeout, "Stop timeout override") _ = systemdCmd.RegisterFlagCompletionFunc(timeFlagName, completion.AutocompleteNone) - flags.BoolVarP(&systemdOptions.New, "new", "", false, "Create a new container or pod instead of starting an existing one") + flags.BoolVar(&systemdOptions.New, newFlagName, false, "Create a new container or pod instead of starting an existing one") flags.BoolVarP(&systemdOptions.NoHeader, "no-header", "", false, "Skip header generation") containerPrefixFlagName := "container-prefix" @@ -93,6 +95,13 @@ func systemd(cmd *cobra.Command, args []string) error { logrus.Warnln("The generated units should be placed on your remote system") } + if cmd.Flags().Changed(newFlagName) && !systemdOptions.New && systemdOptions.TemplateUnitFile { + return errors.New("--template cannot be set with --new=false") + } + if !systemdOptions.New && systemdOptions.TemplateUnitFile { + systemdOptions.New = true + } + reports, err := registry.ContainerEngine().GenerateSystemd(registry.GetContext(), args[0], systemdOptions) if err != nil { return err diff --git a/docs/source/markdown/podman-container-checkpoint.1.md b/docs/source/markdown/podman-container-checkpoint.1.md index 56fd848ac..1faa40a94 100644 --- a/docs/source/markdown/podman-container-checkpoint.1.md +++ b/docs/source/markdown/podman-container-checkpoint.1.md @@ -68,6 +68,40 @@ Dump the *container's* memory information only, leaving the *container* running. operations will supersede prior dumps. It only works on `runc 1.0-rc3` or `higher`.\ The default is **false**. +#### **--print-stats** + +Print out statistics about checkpointing the container(s). The output is +rendered in a JSON array and contains information about how much time different +checkpoint operations required. Many of the checkpoint statistics are created +by CRIU and just passed through to Podman. The following information is provided +in the JSON array: + +- **podman_checkpoint_duration**: Overall time (in microseconds) needed to create + all checkpoints. + +- **runtime_checkpoint_duration**: Time (in microseconds) the container runtime + needed to create the checkpoint. + +- **freezing_time**: Time (in microseconds) CRIU needed to pause (freeze) all + processes in the container (measured by CRIU). + +- **frozen_time**: Time (in microseconds) all processes in the container were + paused (measured by CRIU). + +- **memdump_time**: Time (in microseconds) needed to extract all required memory + pages from all container processes (measured by CRIU). + +- **memwrite_time**: Time (in microseconds) needed to write all required memory + pages to the corresponding checkpoint image files (measured by CRIU). + +- **pages_scanned**: Number of memory pages scanned to determine if they need + to be checkpointed (measured by CRIU). + +- **pages_written**: Number of memory pages actually written to the checkpoint + image files (measured by CRIU). + +The default is **false**. + #### **--tcp-established** Checkpoint a *container* with established TCP connections. If the checkpoint @@ -106,7 +140,7 @@ Dump the container's memory information of the latest container into an archive ``` ## SEE ALSO -**[podman(1)](podman.1.md)**, **[podman-container-restore(1)](podman-container-restore.1.md)** +**[podman(1)](podman.1.md)**, **[podman-container-restore(1)](podman-container-restore.1.md)**, **criu(8)** ## HISTORY September 2018, Originally compiled by Adrian Reber <areber@redhat.com> diff --git a/docs/source/markdown/podman-container-restore.1.md b/docs/source/markdown/podman-container-restore.1.md index 856008cc0..962770ea0 100644 --- a/docs/source/markdown/podman-container-restore.1.md +++ b/docs/source/markdown/podman-container-restore.1.md @@ -102,6 +102,30 @@ from (see **[podman pod create --share](podman-pod-create.1.md#--share)**). This option requires at least CRIU 3.16. +#### **--print-stats** + +Print out statistics about restoring the container(s). The output is +rendered in a JSON array and contains information about how much time different +restore operations required. Many of the restore statistics are created +by CRIU and just passed through to Podman. The following information is provided +in the JSON array: + +- **podman_restore_duration**: Overall time (in microseconds) needed to restore + all checkpoints. + +- **runtime_restore_duration**: Time (in microseconds) the container runtime + needed to restore the checkpoint. + +- **forking_time**: Time (in microseconds) CRIU needed to create (fork) all + processes in the restored container (measured by CRIU). + +- **restore_time**: Time (in microseconds) CRIU needed to restore all processes + in the container (measured by CRIU). + +- **pages_restored**: Number of memory pages restored (measured by CRIU). + +The default is **false**. + #### **--publish**, **-p**=*port* Replaces the ports that the *container* publishes, as configured during the @@ -137,7 +161,7 @@ $ podman run --rm -p 2345:80 -d webserver ``` ## SEE ALSO -**[podman(1)](podman.1.md)**, **[podman-container-checkpoint(1)](podman-container-checkpoint.1.md)**, **[podman-run(1)](podman-run.1.md)**, **[podman-pod-create(1)](podman-pod-create.1.md)** +**[podman(1)](podman.1.md)**, **[podman-container-checkpoint(1)](podman-container-checkpoint.1.md)**, **[podman-run(1)](podman-run.1.md)**, **[podman-pod-create(1)](podman-pod-create.1.md)**, **criu(8)** ## HISTORY September 2018, Originally compiled by Adrian Reber <areber@redhat.com> diff --git a/docs/source/markdown/podman-create.1.md b/docs/source/markdown/podman-create.1.md index b0d7b8f12..811d16880 100644 --- a/docs/source/markdown/podman-create.1.md +++ b/docs/source/markdown/podman-create.1.md @@ -1047,6 +1047,18 @@ Remote connections use local containers.conf for defaults Set the umask inside the container. Defaults to `0022`. Remote connections use local containers.conf for defaults +#### **--unsetenv**=*env* + +Unset default environment variables for the container. Default environment +variables include variables provided natively by Podman, environment variables +configured by the image, and environment variables from containers.conf. + +#### **--unsetenv-all**=*true|false* + +Unset all default environment variables for the container. Default environment +variables include variables provided natively by Podman, environment variables +configured by the image, and environment variables from containers.conf. + #### **--uidmap**=*container_uid*:*from_uid*:*amount* Run the container in a new user namespace using the supplied mapping. This diff --git a/docs/source/markdown/podman-generate-systemd.1.md b/docs/source/markdown/podman-generate-systemd.1.md index 0acbb9d8c..356ac0629 100644 --- a/docs/source/markdown/podman-generate-systemd.1.md +++ b/docs/source/markdown/podman-generate-systemd.1.md @@ -59,6 +59,12 @@ Set the systemd unit name prefix for pods. The default is *pod*. Set the systemd unit name separator between the name/id of a container/pod and the prefix. The default is *-*. +#### **--template** + +Add template specifiers to run multiple services from the systemd unit file. + +Note that if `--new` was not set to true, it is set to true by default. However, if `--new` is set to `false` explicitly the command will fail. + ## EXAMPLES ### Generate and print a systemd unit file for a container diff --git a/docs/source/markdown/podman-run.1.md b/docs/source/markdown/podman-run.1.md index 0fdd47a78..3d908444b 100644 --- a/docs/source/markdown/podman-run.1.md +++ b/docs/source/markdown/podman-run.1.md @@ -1117,6 +1117,18 @@ Remote connections use local containers.conf for defaults Set the umask inside the container. Defaults to `0022`. Remote connections use local containers.conf for defaults +#### **--unsetenv**=*env* + +Unset default environment variables for the container. Default environment +variables include variables provided natively by Podman, environment variables +configured by the image, and environment variables from containers.conf. + +#### **--unsetenv-all**=*true|false* + +Unset all default environment variables for the container. Default environment +variables include variables provided natively by Podman, environment variables +configured by the image, and environment variables from containers.conf. + #### **--uidmap**=*container_uid*:*from_uid*:*amount* Run the container in a new user namespace using the supplied mapping. This diff --git a/libpod/container.go b/libpod/container.go index 86989a02f..c38acb513 100644 --- a/libpod/container.go +++ b/libpod/container.go @@ -259,6 +259,8 @@ type ContainerSecret struct { GID uint32 // Mode is the mode of the secret file Mode uint32 + // Secret target inside container + Target string } // ContainerNetworkDescriptions describes the relationship between the CNI diff --git a/libpod/container_api.go b/libpod/container_api.go index 38223316e..a41bb03df 100644 --- a/libpod/container_api.go +++ b/libpod/container_api.go @@ -794,21 +794,29 @@ type ContainerCheckpointOptions struct { // container no PID 1 will be in the namespace and that is not // possible. Pod string + // PrintStats tells the API to fill out the statistics about + // how much time each component in the stack requires to + // checkpoint a container. + PrintStats bool } // Checkpoint checkpoints a container -func (c *Container) Checkpoint(ctx context.Context, options ContainerCheckpointOptions) error { +// The return values *define.CRIUCheckpointRestoreStatistics and int64 (time +// the runtime needs to checkpoint the container) are only set if +// options.PrintStats is set to true. Not setting options.PrintStats to true +// will return nil and 0. +func (c *Container) Checkpoint(ctx context.Context, options ContainerCheckpointOptions) (*define.CRIUCheckpointRestoreStatistics, int64, error) { logrus.Debugf("Trying to checkpoint container %s", c.ID()) if options.TargetFile != "" { if err := c.prepareCheckpointExport(); err != nil { - return err + return nil, 0, err } } if options.WithPrevious { if err := c.canWithPrevious(); err != nil { - return err + return nil, 0, err } } @@ -817,14 +825,18 @@ func (c *Container) Checkpoint(ctx context.Context, options ContainerCheckpointO defer c.lock.Unlock() if err := c.syncContainer(); err != nil { - return err + return nil, 0, err } } return c.checkpoint(ctx, options) } // Restore restores a container -func (c *Container) Restore(ctx context.Context, options ContainerCheckpointOptions) error { +// The return values *define.CRIUCheckpointRestoreStatistics and int64 (time +// the runtime needs to restore the container) are only set if +// options.PrintStats is set to true. Not setting options.PrintStats to true +// will return nil and 0. +func (c *Container) Restore(ctx context.Context, options ContainerCheckpointOptions) (*define.CRIUCheckpointRestoreStatistics, int64, error) { if options.Pod == "" { logrus.Debugf("Trying to restore container %s", c.ID()) } else { @@ -835,7 +847,7 @@ func (c *Container) Restore(ctx context.Context, options ContainerCheckpointOpti defer c.lock.Unlock() if err := c.syncContainer(); err != nil { - return err + return nil, 0, err } } defer c.newContainerEvent(events.Restore) diff --git a/libpod/container_inspect.go b/libpod/container_inspect.go index 277c3b960..0dae810de 100644 --- a/libpod/container_inspect.go +++ b/libpod/container_inspect.go @@ -300,8 +300,7 @@ func (c *Container) generateInspectContainerConfig(spec *spec.Spec) *define.Insp ctrConfig.User = c.config.User if spec.Process != nil { ctrConfig.Tty = spec.Process.Terminal - ctrConfig.Env = []string{} - ctrConfig.Env = append(ctrConfig.Env, spec.Process.Env...) + ctrConfig.Env = append([]string{}, spec.Process.Env...) ctrConfig.WorkingDir = spec.Process.Cwd } diff --git a/libpod/container_internal.go b/libpod/container_internal.go index de23a4aeb..4bf15be86 100644 --- a/libpod/container_internal.go +++ b/libpod/container_internal.go @@ -1089,7 +1089,7 @@ func (c *Container) init(ctx context.Context, retainRetries bool) error { } // With the spec complete, do an OCI create - if err := c.ociRuntime.CreateContainer(c, nil); err != nil { + if _, err = c.ociRuntime.CreateContainer(c, nil); err != nil { // Fedora 31 is carrying a patch to display improved error // messages to better handle the V2 transition. This is NOT // upstream in any OCI runtime. diff --git a/libpod/container_internal_linux.go b/libpod/container_internal_linux.go index 91453574e..85b1e9139 100644 --- a/libpod/container_internal_linux.go +++ b/libpod/container_internal_linux.go @@ -709,18 +709,6 @@ func (c *Container) generateSpec(ctx context.Context) (*spec.Spec, error) { g.AddAnnotation(annotations.ContainerManager, annotations.ContainerManagerLibpod) } - // Only add container environment variable if not already present - foundContainerEnv := false - for _, env := range g.Config.Process.Env { - if strings.HasPrefix(env, "container=") { - foundContainerEnv = true - break - } - } - if !foundContainerEnv { - g.AddProcessEnv("container", "libpod") - } - cgroupPath, err := c.getOCICgroupPath() if err != nil { return nil, err @@ -1129,25 +1117,26 @@ func (c *Container) checkpointRestoreSupported(version int) error { return nil } -func (c *Container) checkpoint(ctx context.Context, options ContainerCheckpointOptions) error { +func (c *Container) checkpoint(ctx context.Context, options ContainerCheckpointOptions) (*define.CRIUCheckpointRestoreStatistics, int64, error) { if err := c.checkpointRestoreSupported(criu.MinCriuVersion); err != nil { - return err + return nil, 0, err } if c.state.State != define.ContainerStateRunning { - return errors.Wrapf(define.ErrCtrStateInvalid, "%q is not running, cannot checkpoint", c.state.State) + return nil, 0, errors.Wrapf(define.ErrCtrStateInvalid, "%q is not running, cannot checkpoint", c.state.State) } if c.AutoRemove() && options.TargetFile == "" { - return errors.Errorf("cannot checkpoint containers that have been started with '--rm' unless '--export' is used") + return nil, 0, errors.Errorf("cannot checkpoint containers that have been started with '--rm' unless '--export' is used") } if err := crutils.CRCreateFileWithLabel(c.bundlePath(), "dump.log", c.MountLabel()); err != nil { - return err + return nil, 0, err } - if err := c.ociRuntime.CheckpointContainer(c, options); err != nil { - return err + runtimeCheckpointDuration, err := c.ociRuntime.CheckpointContainer(c, options) + if err != nil { + return nil, 0, err } // Save network.status. This is needed to restore the container with @@ -1155,7 +1144,7 @@ func (c *Container) checkpoint(ctx context.Context, options ContainerCheckpointO // with one interface. // FIXME: will this break something? if _, err := metadata.WriteJSONFile(c.getNetworkStatus(), c.bundlePath(), metadata.NetworkStatusFile); err != nil { - return err + return nil, 0, err } defer c.newContainerEvent(events.Checkpoint) @@ -1165,13 +1154,13 @@ func (c *Container) checkpoint(ctx context.Context, options ContainerCheckpointO 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 + return nil, 0, err } } if options.TargetFile != "" { if err := c.exportCheckpoint(options); err != nil { - return err + return nil, 0, err } } @@ -1183,8 +1172,35 @@ func (c *Container) checkpoint(ctx context.Context, options ContainerCheckpointO // Cleanup Storage and Network if err := c.cleanup(ctx); err != nil { - return err + return nil, 0, err + } + } + + criuStatistics, err := func() (*define.CRIUCheckpointRestoreStatistics, error) { + if !options.PrintStats { + return nil, nil + } + statsDirectory, err := os.Open(c.bundlePath()) + if err != nil { + return nil, errors.Wrapf(err, "Not able to open %q", c.bundlePath()) + } + + dumpStatistics, err := stats.CriuGetDumpStats(statsDirectory) + if err != nil { + return nil, errors.Wrap(err, "Displaying checkpointing statistics not possible") } + + return &define.CRIUCheckpointRestoreStatistics{ + FreezingTime: dumpStatistics.GetFreezingTime(), + FrozenTime: dumpStatistics.GetFrozenTime(), + MemdumpTime: dumpStatistics.GetMemdumpTime(), + MemwriteTime: dumpStatistics.GetMemwriteTime(), + PagesScanned: dumpStatistics.GetPagesScanned(), + PagesWritten: dumpStatistics.GetPagesWritten(), + }, nil + }() + if err != nil { + return nil, 0, err } if !options.Keep && !options.PreCheckPoint { @@ -1203,7 +1219,7 @@ func (c *Container) checkpoint(ctx context.Context, options ContainerCheckpointO } c.state.FinishedTime = time.Now() - return c.save() + return criuStatistics, runtimeCheckpointDuration, c.save() } func (c *Container) importCheckpoint(input string) error { @@ -1236,7 +1252,7 @@ func (c *Container) importPreCheckpoint(input string) error { return nil } -func (c *Container) restore(ctx context.Context, options ContainerCheckpointOptions) (retErr error) { +func (c *Container) restore(ctx context.Context, options ContainerCheckpointOptions) (criuStatistics *define.CRIUCheckpointRestoreStatistics, runtimeRestoreDuration int64, retErr error) { minCriuVersion := func() int { if options.Pod == "" { return criu.MinCriuVersion @@ -1244,37 +1260,37 @@ func (c *Container) restore(ctx context.Context, options ContainerCheckpointOpti return criu.PodCriuVersion }() if err := c.checkpointRestoreSupported(minCriuVersion); err != nil { - return err + return nil, 0, err } if options.Pod != "" && !crutils.CRRuntimeSupportsPodCheckpointRestore(c.ociRuntime.Path()) { - return errors.Errorf("runtime %s does not support pod restore", c.ociRuntime.Path()) + return nil, 0, errors.Errorf("runtime %s does not support pod restore", c.ociRuntime.Path()) } if !c.ensureState(define.ContainerStateConfigured, define.ContainerStateExited) { - return errors.Wrapf(define.ErrCtrStateInvalid, "container %s is running or paused, cannot restore", c.ID()) + return nil, 0, 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 + return nil, 0, err } } if options.TargetFile != "" { if err := c.importCheckpoint(options.TargetFile); err != nil { - return err + return nil, 0, err } } // Let's try to stat() CRIU's inventory file. If it does not exist, it makes // no sense to try a restore. This is a minimal check if a checkpoint exist. if _, err := os.Stat(filepath.Join(c.CheckpointPath(), "inventory.img")); os.IsNotExist(err) { - return errors.Wrapf(err, "a complete checkpoint for this container cannot be found, cannot restore") + return nil, 0, errors.Wrapf(err, "a complete checkpoint for this container cannot be found, cannot restore") } if err := crutils.CRCreateFileWithLabel(c.bundlePath(), "restore.log", c.MountLabel()); err != nil { - return err + return nil, 0, err } // If a container is restored multiple times from an exported checkpoint with @@ -1311,7 +1327,7 @@ func (c *Container) restore(ctx context.Context, options ContainerCheckpointOpti // container with the same networks settings as during checkpointing. aliases, err := c.GetAllNetworkAliases() if err != nil { - return err + return nil, 0, err } netOpts := make(map[string]types.PerNetworkOptions, len(netStatus)) for network, status := range netStatus { @@ -1336,7 +1352,7 @@ func (c *Container) restore(ctx context.Context, options ContainerCheckpointOpti if perNetOpts.InterfaceName == "" { eth, exists := c.state.NetInterfaceDescriptions.getInterfaceByName(network) if !exists { - return errors.Errorf("no network interface name for container %s on network %s", c.config.ID, network) + return nil, 0, errors.Errorf("no network interface name for container %s on network %s", c.config.ID, network) } perNetOpts.InterfaceName = eth } @@ -1354,7 +1370,7 @@ func (c *Container) restore(ctx context.Context, options ContainerCheckpointOpti }() if err := c.prepare(); err != nil { - return err + return nil, 0, err } // Read config @@ -1363,7 +1379,7 @@ func (c *Container) restore(ctx context.Context, options ContainerCheckpointOpti g, err := generate.NewFromFile(jsonPath) if err != nil { logrus.Debugf("generate.NewFromFile failed with %v", err) - return err + return nil, 0, err } // Restoring from an import means that we are doing migration @@ -1379,7 +1395,7 @@ func (c *Container) restore(ctx context.Context, options ContainerCheckpointOpti } if err := g.AddOrReplaceLinuxNamespace(string(spec.NetworkNamespace), netNSPath); err != nil { - return err + return nil, 0, err } } @@ -1388,23 +1404,23 @@ func (c *Container) restore(ctx context.Context, options ContainerCheckpointOpti // the ones from the infrastructure container. pod, err := c.runtime.LookupPod(options.Pod) if err != nil { - return errors.Wrapf(err, "pod %q cannot be retrieved", options.Pod) + return nil, 0, errors.Wrapf(err, "pod %q cannot be retrieved", options.Pod) } infraContainer, err := pod.InfraContainer() if err != nil { - return errors.Wrapf(err, "cannot retrieved infra container from pod %q", options.Pod) + return nil, 0, errors.Wrapf(err, "cannot retrieved infra container from pod %q", options.Pod) } infraContainer.lock.Lock() if err := infraContainer.syncContainer(); err != nil { infraContainer.lock.Unlock() - return errors.Wrapf(err, "Error syncing infrastructure container %s status", infraContainer.ID()) + return nil, 0, errors.Wrapf(err, "Error syncing infrastructure container %s status", infraContainer.ID()) } if infraContainer.state.State != define.ContainerStateRunning { if err := infraContainer.initAndStart(ctx); err != nil { infraContainer.lock.Unlock() - return errors.Wrapf(err, "Error starting infrastructure container %s status", infraContainer.ID()) + return nil, 0, errors.Wrapf(err, "Error starting infrastructure container %s status", infraContainer.ID()) } } infraContainer.lock.Unlock() @@ -1412,56 +1428,56 @@ func (c *Container) restore(ctx context.Context, options ContainerCheckpointOpti if c.config.IPCNsCtr != "" { nsPath, err := infraContainer.namespacePath(IPCNS) if err != nil { - return errors.Wrapf(err, "cannot retrieve IPC namespace path for Pod %q", options.Pod) + return nil, 0, errors.Wrapf(err, "cannot retrieve IPC namespace path for Pod %q", options.Pod) } if err := g.AddOrReplaceLinuxNamespace(string(spec.IPCNamespace), nsPath); err != nil { - return err + return nil, 0, err } } if c.config.NetNsCtr != "" { nsPath, err := infraContainer.namespacePath(NetNS) if err != nil { - return errors.Wrapf(err, "cannot retrieve network namespace path for Pod %q", options.Pod) + return nil, 0, errors.Wrapf(err, "cannot retrieve network namespace path for Pod %q", options.Pod) } if err := g.AddOrReplaceLinuxNamespace(string(spec.NetworkNamespace), nsPath); err != nil { - return err + return nil, 0, err } } if c.config.PIDNsCtr != "" { nsPath, err := infraContainer.namespacePath(PIDNS) if err != nil { - return errors.Wrapf(err, "cannot retrieve PID namespace path for Pod %q", options.Pod) + return nil, 0, errors.Wrapf(err, "cannot retrieve PID namespace path for Pod %q", options.Pod) } if err := g.AddOrReplaceLinuxNamespace(string(spec.PIDNamespace), nsPath); err != nil { - return err + return nil, 0, err } } if c.config.UTSNsCtr != "" { nsPath, err := infraContainer.namespacePath(UTSNS) if err != nil { - return errors.Wrapf(err, "cannot retrieve UTS namespace path for Pod %q", options.Pod) + return nil, 0, errors.Wrapf(err, "cannot retrieve UTS namespace path for Pod %q", options.Pod) } if err := g.AddOrReplaceLinuxNamespace(string(spec.UTSNamespace), nsPath); err != nil { - return err + return nil, 0, err } } if c.config.CgroupNsCtr != "" { nsPath, err := infraContainer.namespacePath(CgroupNS) if err != nil { - return errors.Wrapf(err, "cannot retrieve Cgroup namespace path for Pod %q", options.Pod) + return nil, 0, errors.Wrapf(err, "cannot retrieve Cgroup namespace path for Pod %q", options.Pod) } if err := g.AddOrReplaceLinuxNamespace(string(spec.CgroupNamespace), nsPath); err != nil { - return err + return nil, 0, err } } } if err := c.makeBindMounts(); err != nil { - return err + return nil, 0, err } if options.TargetFile != "" { @@ -1483,12 +1499,12 @@ func (c *Container) restore(ctx context.Context, options ContainerCheckpointOpti // Cleanup for a working restore. if err := c.removeConmonFiles(); err != nil { - return err + return nil, 0, err } // Save the OCI spec to disk if err := c.saveSpec(g.Config); err != nil { - return err + return nil, 0, err } // When restoring from an imported archive, allow restoring the content of volumes. @@ -1499,24 +1515,24 @@ func (c *Container) restore(ctx context.Context, options ContainerCheckpointOpti volumeFile, err := os.Open(volumeFilePath) if err != nil { - return errors.Wrapf(err, "failed to open volume file %s", volumeFilePath) + return nil, 0, errors.Wrapf(err, "failed to open volume file %s", volumeFilePath) } defer volumeFile.Close() volume, err := c.runtime.GetVolume(v.Name) if err != nil { - return errors.Wrapf(err, "failed to retrieve volume %s", v.Name) + return nil, 0, errors.Wrapf(err, "failed to retrieve volume %s", v.Name) } mountPoint, err := volume.MountPoint() if err != nil { - return err + return nil, 0, err } if mountPoint == "" { - return errors.Wrapf(err, "unable to import volume %s as it is not mounted", volume.Name()) + return nil, 0, errors.Wrapf(err, "unable to import volume %s as it is not mounted", volume.Name()) } if err := archive.UntarUncompressed(volumeFile, mountPoint, nil); err != nil { - return errors.Wrapf(err, "Failed to extract volume %s to %s", volumeFilePath, mountPoint) + return nil, 0, errors.Wrapf(err, "Failed to extract volume %s to %s", volumeFilePath, mountPoint) } } } @@ -1524,16 +1540,43 @@ func (c *Container) restore(ctx context.Context, options ContainerCheckpointOpti // Before actually restarting the container, apply the root file-system changes if !options.IgnoreRootfs { if err := crutils.CRApplyRootFsDiffTar(c.bundlePath(), c.state.Mountpoint); err != nil { - return err + return nil, 0, err } if err := crutils.CRRemoveDeletedFiles(c.ID(), c.bundlePath(), c.state.Mountpoint); err != nil { - return err + return nil, 0, err } } - if err := c.ociRuntime.CreateContainer(c, &options); err != nil { - return err + runtimeRestoreDuration, err = c.ociRuntime.CreateContainer(c, &options) + if err != nil { + return nil, 0, err + } + + criuStatistics, err = func() (*define.CRIUCheckpointRestoreStatistics, error) { + if !options.PrintStats { + return nil, nil + } + statsDirectory, err := os.Open(c.bundlePath()) + if err != nil { + return nil, errors.Wrapf(err, "Not able to open %q", c.bundlePath()) + } + + restoreStatistics, err := stats.CriuGetRestoreStats(statsDirectory) + if err != nil { + return nil, errors.Wrap(err, "Displaying restore statistics not possible") + } + + return &define.CRIUCheckpointRestoreStatistics{ + PagesCompared: restoreStatistics.GetPagesCompared(), + PagesSkippedCow: restoreStatistics.GetPagesSkippedCow(), + ForkingTime: restoreStatistics.GetForkingTime(), + RestoreTime: restoreStatistics.GetRestoreTime(), + PagesRestored: restoreStatistics.GetPagesRestored(), + }, nil + }() + if err != nil { + return nil, 0, err } logrus.Debugf("Restored container %s", c.ID()) @@ -1572,7 +1615,7 @@ func (c *Container) restore(ctx context.Context, options ContainerCheckpointOpti } } - return c.save() + return criuStatistics, runtimeRestoreDuration, c.save() } // Retrieves a container's "root" net namespace container dependency. @@ -1833,8 +1876,17 @@ rootless=%d return errors.Wrapf(err, "error creating secrets mount") } for _, secret := range c.Secrets() { + secretFileName := secret.Name + base := "/run/secrets" + if secret.Target != "" { + secretFileName = secret.Target + //If absolute path for target given remove base. + if filepath.IsAbs(secretFileName) { + base = "" + } + } src := filepath.Join(c.config.SecretsPath, secret.Name) - dest := filepath.Join("/run/secrets", secret.Name) + dest := filepath.Join(base, secretFileName) c.state.BindMounts[dest] = src } } diff --git a/libpod/container_log_linux.go b/libpod/container_log_linux.go index 4029d0af7..d3d7c8397 100644 --- a/libpod/container_log_linux.go +++ b/libpod/container_log_linux.go @@ -37,13 +37,21 @@ func (c *Container) initializeJournal(ctx context.Context) error { m := make(map[string]string) m["SYSLOG_IDENTIFIER"] = "podman" m["PODMAN_ID"] = c.ID() - m["CONTAINER_ID_FULL"] = c.ID() history := events.History m["PODMAN_EVENT"] = history.String() + container := events.Container + m["PODMAN_TYPE"] = container.String() + m["PODMAN_TIME"] = time.Now().Format(time.RFC3339Nano) return journal.Send("", journal.PriInfo, m) } func (c *Container) readFromJournal(ctx context.Context, options *logs.LogOptions, logChannel chan *logs.LogLine) error { + // We need the container's events in the same journal to guarantee + // consistency, see #10323. + if options.Follow && c.runtime.config.Engine.EventsLogger != "journald" { + return errors.Errorf("using --follow with the journald --log-driver but without the journald --events-backend (%s) is not supported", c.runtime.config.Engine.EventsLogger) + } + journal, err := sdjournal.NewJournal() if err != nil { return err @@ -89,6 +97,7 @@ func (c *Container) readFromJournal(ctx context.Context, options *logs.LogOption // exponential backoff. var cursor string var cursorError error + var containerCouldBeLogging bool for i := 1; i <= 3; i++ { cursor, cursorError = journal.GetCursor() hundreds := 1 @@ -105,12 +114,6 @@ func (c *Container) readFromJournal(ctx context.Context, options *logs.LogOption return errors.Wrap(cursorError, "initial journal cursor") } - // We need the container's events in the same journal to guarantee - // consistency, see #10323. - if options.Follow && c.runtime.config.Engine.EventsLogger != "journald" { - return errors.Errorf("using --follow with the journald --log-driver but without the journald --events-backend (%s) is not supported", c.runtime.config.Engine.EventsLogger) - } - options.WaitGroup.Add(1) go func() { defer func() { @@ -172,7 +175,7 @@ func (c *Container) readFromJournal(ctx context.Context, options *logs.LogOption doTailFunc() } // Unless we follow, quit. - if !options.Follow { + if !options.Follow || !containerCouldBeLogging { return } // Sleep until something's happening on the journal. @@ -201,11 +204,14 @@ func (c *Container) readFromJournal(ctx context.Context, options *logs.LogOption logrus.Errorf("Failed to translate event: %v", err) return } - if status == events.Exited { + switch status { + case events.History, events.Init, events.Start, events.Restart: + containerCouldBeLogging = true + case events.Exited: + containerCouldBeLogging = false if doTail { doTailFunc() } - return } continue } diff --git a/libpod/define/checkpoint_restore.go b/libpod/define/checkpoint_restore.go new file mode 100644 index 000000000..536bdde9a --- /dev/null +++ b/libpod/define/checkpoint_restore.go @@ -0,0 +1,32 @@ +package define + +// This contains values reported by CRIU during +// checkpointing or restoring. +// All names are the same as reported by CRIU. +type CRIUCheckpointRestoreStatistics struct { + // Checkpoint values + // Time required to freeze/pause/quiesce the processes + FreezingTime uint32 `json:"freezing_time,omitempty"` + // Time the processes are actually not running during checkpointing + FrozenTime uint32 `json:"frozen_time,omitempty"` + // Time required to extract memory pages from the processes + MemdumpTime uint32 `json:"memdump_time,omitempty"` + // Time required to write memory pages to disk + MemwriteTime uint32 `json:"memwrite_time,omitempty"` + // Number of memory pages CRIU analyzed + PagesScanned uint64 `json:"pages_scanned,omitempty"` + // Number of memory pages written + PagesWritten uint64 `json:"pages_written,omitempty"` + + // Restore values + // Number of pages compared during restore + PagesCompared uint64 `json:"pages_compared,omitempty"` + // Number of COW pages skipped during restore + PagesSkippedCow uint64 `json:"pages_skipped_cow,omitempty"` + // Time required to fork processes + ForkingTime uint32 `json:"forking_time,omitempty"` + // Time required to restore + RestoreTime uint32 `json:"restore_time,omitempty"` + // Number of memory pages restored + PagesRestored uint64 `json:"pages_restored,omitempty"` +} diff --git a/libpod/network/cni/cni_conversion.go b/libpod/network/cni/cni_conversion.go index 70d259b60..788165b5e 100644 --- a/libpod/network/cni/cni_conversion.go +++ b/libpod/network/cni/cni_conversion.go @@ -295,10 +295,6 @@ func (n *cniNetwork) createCNIConfigListFromNetwork(network *types.Network, writ // Note: in the future we might like to allow for dynamic domain names plugins = append(plugins, newDNSNamePlugin(defaultPodmanDomainName)) } - // Add the podman-machine CNI plugin if we are in a machine - if n.isMachine { - plugins = append(plugins, newPodmanMachinePlugin()) - } case types.MacVLANNetworkDriver: plugins = append(plugins, newVLANPlugin(types.MacVLANNetworkDriver, network.NetworkInterface, vlanPluginMode, mtu, ipamConf)) @@ -369,3 +365,14 @@ func convertSpecgenPortsToCNIPorts(ports []types.PortMapping) ([]cniPortMapEntry } return cniPorts, nil } + +func removeMachinePlugin(conf *libcni.NetworkConfigList) *libcni.NetworkConfigList { + plugins := make([]*libcni.NetworkConfig, 0, len(conf.Plugins)) + for _, net := range conf.Plugins { + if net.Network.Type != "podman-machine" { + plugins = append(plugins, net) + } + } + conf.Plugins = plugins + return conf +} diff --git a/libpod/network/cni/cni_types.go b/libpod/network/cni/cni_types.go index c70cb92b6..e5eb777de 100644 --- a/libpod/network/cni/cni_types.go +++ b/libpod/network/cni/cni_types.go @@ -110,12 +110,6 @@ type dnsNameConfig struct { Capabilities map[string]bool `json:"capabilities"` } -// podmanMachineConfig enables port handling on the host OS -type podmanMachineConfig struct { - PluginType string `json:"type"` - Capabilities map[string]bool `json:"capabilities"` -} - // ncList describes a generic map type ncList map[string]interface{} @@ -285,12 +279,3 @@ func newVLANPlugin(pluginType, device, mode string, mtu int, ipam ipamConfig) VL } return m } - -func newPodmanMachinePlugin() podmanMachineConfig { - caps := make(map[string]bool, 1) - caps["portMappings"] = true - return podmanMachineConfig{ - PluginType: "podman-machine", - Capabilities: caps, - } -} diff --git a/libpod/network/cni/config_test.go b/libpod/network/cni/config_test.go index 0dfc6173c..c2e5fc985 100644 --- a/libpod/network/cni/config_test.go +++ b/libpod/network/cni/config_test.go @@ -965,19 +965,6 @@ var _ = Describe("Config", func() { Expect(logString).To(ContainSubstring("dnsname and internal networks are incompatible")) }) - It("create config with podman machine plugin", func() { - libpodNet, err := getNetworkInterface(cniConfDir, true) - Expect(err).To(BeNil()) - - network := types.Network{} - network1, err := libpodNet.NetworkCreate(network) - Expect(err).To(BeNil()) - Expect(network1.Driver).To(Equal("bridge")) - path := filepath.Join(cniConfDir, network1.Name+".conflist") - Expect(path).To(BeARegularFile()) - grepInFile(path, `"type": "podman-machine",`) - }) - It("network inspect partial ID", func() { network := types.Network{Name: "net4"} network1, err := libpodNet.NetworkCreate(network) diff --git a/libpod/network/cni/network.go b/libpod/network/cni/network.go index 3e9cdaa47..41e3e414e 100644 --- a/libpod/network/cni/network.go +++ b/libpod/network/cni/network.go @@ -150,6 +150,13 @@ func (n *cniNetwork) loadNetworks() error { continue } + // podman < v4.0 used the podman-machine cni plugin for podman machine port forwarding + // since this is now build into podman we no longer use the plugin + // old configs may still contain it so we just remove it here + if n.isMachine { + conf = removeMachinePlugin(conf) + } + if _, err := n.cniConf.ValidateNetworkList(context.Background(), conf); err != nil { logrus.Warnf("Error validating CNI config file %s: %v", file, err) continue diff --git a/libpod/networking_linux.go b/libpod/networking_linux.go index fc91155fa..8ce435efd 100644 --- a/libpod/networking_linux.go +++ b/libpod/networking_linux.go @@ -87,12 +87,28 @@ func (c *Container) GetNetworkAliases(netName string) ([]string, error) { return aliases, nil } +// convertPortMappings will remove the HostIP part from the ports when running inside podman machine. +// This is need because a HostIP of 127.0.0.1 would now allow the gvproxy forwarder to reach to open ports. +// For machine the HostIP must only be used by gvproxy and never in the VM. +func (c *Container) convertPortMappings() []types.PortMapping { + if !c.runtime.config.Engine.MachineEnabled || len(c.config.PortMappings) == 0 { + return c.config.PortMappings + } + // if we run in a machine VM we have to ignore the host IP part + newPorts := make([]types.PortMapping, 0, len(c.config.PortMappings)) + for _, port := range c.config.PortMappings { + port.HostIP = "" + newPorts = append(newPorts, port) + } + return newPorts +} + func (c *Container) getNetworkOptions() (types.NetworkOptions, error) { opts := types.NetworkOptions{ ContainerID: c.config.ID, ContainerName: getCNIPodName(c), } - opts.PortMappings = c.config.PortMappings + opts.PortMappings = c.convertPortMappings() networks, _, err := c.networks() if err != nil { return opts, err @@ -591,32 +607,9 @@ func (r *Runtime) GetRootlessNetNs(new bool) (*RootlessNetNS, error) { return rootlessNetNS, nil } -// setPrimaryMachineIP is used for podman-machine and it sets -// and environment variable with the IP address of the podman-machine -// host. -func setPrimaryMachineIP() error { - // no connection is actually made here - conn, err := net.Dial("udp", "8.8.8.8:80") - if err != nil { - return err - } - defer func() { - if err := conn.Close(); err != nil { - logrus.Error(err) - } - }() - addr := conn.LocalAddr().(*net.UDPAddr) - return os.Setenv("PODMAN_MACHINE_HOST", addr.IP.String()) -} - // setUpNetwork will set up the the networks, on error it will also tear down the cni // networks. If rootless it will join/create the rootless network namespace. func (r *Runtime) setUpNetwork(ns string, opts types.NetworkOptions) (map[string]types.StatusBlock, error) { - if r.config.MachineEnabled() { - if err := setPrimaryMachineIP(); err != nil { - return nil, err - } - } rootlessNetNS, err := r.GetRootlessNetNs(true) if err != nil { return nil, err @@ -650,7 +643,18 @@ func getCNIPodName(c *Container) string { } // Create and configure a new network namespace for a container -func (r *Runtime) configureNetNS(ctr *Container, ctrNS ns.NetNS) (map[string]types.StatusBlock, error) { +func (r *Runtime) configureNetNS(ctr *Container, ctrNS ns.NetNS) (status map[string]types.StatusBlock, rerr error) { + if err := r.exposeMachinePorts(ctr.config.PortMappings); err != nil { + return nil, err + } + defer func() { + // make sure to unexpose the gvproxy ports when an error happens + if rerr != nil { + if err := r.unexposeMachinePorts(ctr.config.PortMappings); err != nil { + logrus.Errorf("failed to free gvproxy machine ports: %v", err) + } + } + }() if ctr.config.NetMode.IsSlirp4netns() { return nil, r.setupSlirp4netns(ctr, ctrNS) } @@ -836,6 +840,10 @@ func (r *Runtime) teardownCNI(ctr *Container) error { // Tear down a network namespace, undoing all state associated with it. func (r *Runtime) teardownNetNS(ctr *Container) error { + if err := r.unexposeMachinePorts(ctr.config.PortMappings); err != nil { + // do not return an error otherwise we would prevent network cleanup + logrus.Errorf("failed to free gvproxy machine ports: %v", err) + } if err := r.teardownCNI(ctr); err != nil { return err } @@ -1206,7 +1214,7 @@ func (c *Container) NetworkDisconnect(nameOrID, netName string, force bool) erro ContainerID: c.config.ID, ContainerName: getCNIPodName(c), } - opts.PortMappings = c.config.PortMappings + opts.PortMappings = c.convertPortMappings() eth, exists := c.state.NetInterfaceDescriptions.getInterfaceByName(netName) if !exists { return errors.Errorf("no network interface name for container %s on network %s", c.config.ID, netName) @@ -1298,7 +1306,7 @@ func (c *Container) NetworkConnect(nameOrID, netName string, aliases []string) e ContainerID: c.config.ID, ContainerName: getCNIPodName(c), } - opts.PortMappings = c.config.PortMappings + opts.PortMappings = c.convertPortMappings() eth, exists := c.state.NetInterfaceDescriptions.getInterfaceByName(netName) if !exists { return errors.Errorf("no network interface name for container %s on network %s", c.config.ID, netName) diff --git a/libpod/networking_machine.go b/libpod/networking_machine.go new file mode 100644 index 000000000..7cb2a00f7 --- /dev/null +++ b/libpod/networking_machine.go @@ -0,0 +1,121 @@ +package libpod + +import ( + "bytes" + "context" + "errors" + "fmt" + "io" + "io/ioutil" + "net" + "net/http" + "strconv" + "strings" + + "github.com/containers/podman/v3/libpod/network/types" + "github.com/sirupsen/logrus" +) + +const machineGvproxyEndpoint = "gateway.containers.internal" + +// machineExpose is the struct for the gvproxy port forwarding api send via json +type machineExpose struct { + // Local is the local address on the vm host, format is ip:port + Local string `json:"local"` + // Remote is used to specify the vm ip:port + Remote string `json:"remote,omitempty"` + // Protocol to forward, tcp or udp + Protocol string `json:"protocol"` +} + +func requestMachinePorts(expose bool, ports []types.PortMapping) error { + url := "http://" + machineGvproxyEndpoint + "/services/forwarder/" + if expose { + url = url + "expose" + } else { + url = url + "unexpose" + } + ctx := context.Background() + client := &http.Client{} + buf := new(bytes.Buffer) + for num, port := range ports { + protocols := strings.Split(port.Protocol, ",") + for _, protocol := range protocols { + for i := uint16(0); i < port.Range; i++ { + machinePort := machineExpose{ + Local: net.JoinHostPort(port.HostIP, strconv.FormatInt(int64(port.HostPort+i), 10)), + Protocol: protocol, + } + if expose { + // only set the remote port the ip will be automatically be set by gvproxy + machinePort.Remote = ":" + strconv.FormatInt(int64(port.HostPort+i), 10) + } + + // post request + if err := json.NewEncoder(buf).Encode(machinePort); err != nil { + if expose { + // in case of an error make sure to unexpose the other ports + if cerr := requestMachinePorts(false, ports[:num]); cerr != nil { + logrus.Errorf("failed to free gvproxy machine ports: %v", cerr) + } + } + return err + } + if err := makeMachineRequest(ctx, client, url, buf); err != nil { + if expose { + // in case of an error make sure to unexpose the other ports + if cerr := requestMachinePorts(false, ports[:num]); cerr != nil { + logrus.Errorf("failed to free gvproxy machine ports: %v", cerr) + } + } + return err + } + buf.Reset() + } + } + } + return nil +} + +func makeMachineRequest(ctx context.Context, client *http.Client, url string, buf io.Reader) error { + //var buf io.ReadWriter + req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, buf) + if err != nil { + return err + } + req.Header.Add("Accept", "application/json") + req.Header.Add("Content-Type", "application/json") + resp, err := client.Do(req) + if err != nil { + return err + } + defer resp.Body.Close() + if resp.StatusCode != http.StatusOK { + return annotateGvproxyResponseError(resp.Body) + } + return nil +} + +func annotateGvproxyResponseError(r io.Reader) error { + b, err := ioutil.ReadAll(r) + if err == nil && len(b) > 0 { + return fmt.Errorf("something went wrong with the request: %q", string(b)) + } + return errors.New("something went wrong with the request, could not read response") +} + +// exposeMachinePorts exposes the ports for podman machine via gvproxy +func (r *Runtime) exposeMachinePorts(ports []types.PortMapping) error { + if !r.config.Engine.MachineEnabled { + return nil + } + return requestMachinePorts(true, ports) +} + +// unexposeMachinePorts closes the ports for podman machine via gvproxy +func (r *Runtime) unexposeMachinePorts(ports []types.PortMapping) error { + if !r.config.Engine.MachineEnabled { + return nil + } + return requestMachinePorts(false, ports) +} diff --git a/libpod/networking_slirp4netns.go b/libpod/networking_slirp4netns.go index 674075e23..67ea31c1c 100644 --- a/libpod/networking_slirp4netns.go +++ b/libpod/networking_slirp4netns.go @@ -509,7 +509,7 @@ func (r *Runtime) setupRootlessPortMappingViaRLK(ctr *Container, netnsPath strin childIP := getRootlessPortChildIP(ctr, netStatus) cfg := rootlessport.Config{ - Mappings: ctr.config.PortMappings, + Mappings: ctr.convertPortMappings(), NetNSPath: netnsPath, ExitFD: 3, ReadyFD: 4, @@ -594,7 +594,7 @@ func (r *Runtime) setupRootlessPortMappingViaSlirp(ctr *Container, cmd *exec.Cmd // for each port we want to add we need to open a connection to the slirp4netns control socket // and send the add_hostfwd command. - for _, i := range ctr.config.PortMappings { + for _, i := range ctr.convertPortMappings() { conn, err := net.Dial("unix", apiSocket) if err != nil { return errors.Wrapf(err, "cannot open connection to %s", apiSocket) diff --git a/libpod/oci.go b/libpod/oci.go index c92d9a077..f45c1a105 100644 --- a/libpod/oci.go +++ b/libpod/oci.go @@ -23,7 +23,10 @@ type OCIRuntime interface { Path() string // CreateContainer creates the container in the OCI runtime. - CreateContainer(ctr *Container, restoreOptions *ContainerCheckpointOptions) error + // The returned int64 contains the microseconds needed to restore + // the given container if it is a restore and if restoreOptions.PrintStats + // is true. In all other cases the returned int64 is 0. + CreateContainer(ctr *Container, restoreOptions *ContainerCheckpointOptions) (int64, error) // UpdateContainerStatus updates the status of the given container. UpdateContainerStatus(ctr *Container) error // StartContainer starts the given container. @@ -101,8 +104,10 @@ type OCIRuntime interface { // CheckpointContainer checkpoints the given container. // Some OCI runtimes may not support this - if SupportsCheckpoint() // returns false, this is not implemented, and will always return an - // error. - CheckpointContainer(ctr *Container, options ContainerCheckpointOptions) error + // error. If CheckpointOptions.PrintStats is true the first return parameter + // contains the number of microseconds the runtime needed to checkpoint + // the given container. + CheckpointContainer(ctr *Container, options ContainerCheckpointOptions) (int64, error) // CheckConmonRunning verifies that the given container's Conmon // instance is still running. Runtimes without Conmon, or systems where diff --git a/libpod/oci_conmon_linux.go b/libpod/oci_conmon_linux.go index db906fabb..533a0d78b 100644 --- a/libpod/oci_conmon_linux.go +++ b/libpod/oci_conmon_linux.go @@ -183,35 +183,39 @@ func hasCurrentUserMapped(ctr *Container) bool { } // CreateContainer creates a container. -func (r *ConmonOCIRuntime) CreateContainer(ctr *Container, restoreOptions *ContainerCheckpointOptions) error { +func (r *ConmonOCIRuntime) CreateContainer(ctr *Container, restoreOptions *ContainerCheckpointOptions) (int64, error) { // always make the run dir accessible to the current user so that the PID files can be read without // being in the rootless user namespace. if err := makeAccessible(ctr.state.RunDir, 0, 0); err != nil { - return err + return 0, err } if !hasCurrentUserMapped(ctr) { for _, i := range []string{ctr.state.RunDir, ctr.runtime.config.Engine.TmpDir, ctr.config.StaticDir, ctr.state.Mountpoint, ctr.runtime.config.Engine.VolumePath} { if err := makeAccessible(i, ctr.RootUID(), ctr.RootGID()); err != nil { - return err + return 0, err } } // if we are running a non privileged container, be sure to umount some kernel paths so they are not // bind mounted inside the container at all. if !ctr.config.Privileged && !rootless.IsRootless() { - ch := make(chan error) + type result struct { + restoreDuration int64 + err error + } + ch := make(chan result) go func() { runtime.LockOSThread() - err := func() error { + restoreDuration, err := func() (int64, error) { fd, err := os.Open(fmt.Sprintf("/proc/%d/task/%d/ns/mnt", os.Getpid(), unix.Gettid())) if err != nil { - return err + return 0, err } defer errorhandling.CloseQuiet(fd) // create a new mountns on the current thread if err = unix.Unshare(unix.CLONE_NEWNS); err != nil { - return err + return 0, err } defer func() { if err := unix.Setns(int(fd.Fd()), unix.CLONE_NEWNS); err != nil { @@ -224,12 +228,12 @@ func (r *ConmonOCIRuntime) CreateContainer(ctr *Container, restoreOptions *Conta // changes are propagated to the host. err = unix.Mount("/sys", "/sys", "none", unix.MS_REC|unix.MS_SLAVE, "") if err != nil { - return errors.Wrapf(err, "cannot make /sys slave") + return 0, errors.Wrapf(err, "cannot make /sys slave") } mounts, err := pmount.GetMounts() if err != nil { - return err + return 0, err } for _, m := range mounts { if !strings.HasPrefix(m.Mountpoint, "/sys/kernel") { @@ -237,15 +241,18 @@ func (r *ConmonOCIRuntime) CreateContainer(ctr *Container, restoreOptions *Conta } err = unix.Unmount(m.Mountpoint, 0) if err != nil && !os.IsNotExist(err) { - return errors.Wrapf(err, "cannot unmount %s", m.Mountpoint) + return 0, errors.Wrapf(err, "cannot unmount %s", m.Mountpoint) } } return r.createOCIContainer(ctr, restoreOptions) }() - ch <- err + ch <- result{ + restoreDuration: restoreDuration, + err: err, + } }() - err := <-ch - return err + r := <-ch + return r.restoreDuration, r.err } } return r.createOCIContainer(ctr, restoreOptions) @@ -760,9 +767,9 @@ func (r *ConmonOCIRuntime) AttachResize(ctr *Container, newSize define.TerminalS } // CheckpointContainer checkpoints the given container. -func (r *ConmonOCIRuntime) CheckpointContainer(ctr *Container, options ContainerCheckpointOptions) error { +func (r *ConmonOCIRuntime) CheckpointContainer(ctr *Container, options ContainerCheckpointOptions) (int64, error) { if err := label.SetSocketLabel(ctr.ProcessLabel()); err != nil { - return err + return 0, err } // imagePath is used by CRIU to store the actual checkpoint files imagePath := ctr.CheckpointPath() @@ -802,14 +809,25 @@ func (r *ConmonOCIRuntime) CheckpointContainer(ctr *Container, options Container } runtimeDir, err := util.GetRuntimeDir() if err != nil { - return err + return 0, err } if err = os.Setenv("XDG_RUNTIME_DIR", runtimeDir); err != nil { - return errors.Wrapf(err, "cannot set XDG_RUNTIME_DIR") + return 0, 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...) + + runtimeCheckpointStarted := time.Now() + err = utils.ExecCmdWithStdStreams(os.Stdin, os.Stdout, os.Stderr, nil, r.path, args...) + + runtimeCheckpointDuration := func() int64 { + if options.PrintStats { + return time.Since(runtimeCheckpointStarted).Microseconds() + } + return 0 + }() + + return runtimeCheckpointDuration, err } func (r *ConmonOCIRuntime) CheckConmonRunning(ctr *Container) (bool, error) { @@ -984,23 +1002,23 @@ func (r *ConmonOCIRuntime) getLogTag(ctr *Container) (string, error) { } // createOCIContainer generates this container's main conmon instance and prepares it for starting -func (r *ConmonOCIRuntime) createOCIContainer(ctr *Container, restoreOptions *ContainerCheckpointOptions) error { +func (r *ConmonOCIRuntime) createOCIContainer(ctr *Container, restoreOptions *ContainerCheckpointOptions) (int64, error) { var stderrBuf bytes.Buffer runtimeDir, err := util.GetRuntimeDir() if err != nil { - return err + return 0, err } parentSyncPipe, childSyncPipe, err := newPipe() if err != nil { - return errors.Wrapf(err, "error creating socket pair") + return 0, errors.Wrapf(err, "error creating socket pair") } defer errorhandling.CloseQuiet(parentSyncPipe) childStartPipe, parentStartPipe, err := newPipe() if err != nil { - return errors.Wrapf(err, "error creating socket pair for start pipe") + return 0, errors.Wrapf(err, "error creating socket pair for start pipe") } defer errorhandling.CloseQuiet(parentStartPipe) @@ -1012,12 +1030,12 @@ func (r *ConmonOCIRuntime) createOCIContainer(ctr *Container, restoreOptions *Co logTag, err := r.getLogTag(ctr) if err != nil { - return err + return 0, err } if ctr.config.CgroupsMode == cgroupSplit { if err := utils.MoveUnderCgroupSubtree("runtime"); err != nil { - return err + return 0, err } } @@ -1068,7 +1086,7 @@ func (r *ConmonOCIRuntime) createOCIContainer(ctr *Container, restoreOptions *Co } else { fds, err := strconv.Atoi(val) if err != nil { - return fmt.Errorf("converting LISTEN_FDS=%s: %w", val, err) + return 0, fmt.Errorf("converting LISTEN_FDS=%s: %w", val, err) } preserveFDs = uint(fds) } @@ -1149,7 +1167,7 @@ func (r *ConmonOCIRuntime) createOCIContainer(ctr *Container, restoreOptions *Co if r.reservePorts && !rootless.IsRootless() && !ctr.config.NetMode.IsSlirp4netns() { ports, err := bindPorts(ctr.config.PortMappings) if err != nil { - return err + return 0, err } filesToClose = append(filesToClose, ports...) @@ -1165,12 +1183,12 @@ func (r *ConmonOCIRuntime) createOCIContainer(ctr *Container, restoreOptions *Co if havePortMapping { ctr.rootlessPortSyncR, ctr.rootlessPortSyncW, err = os.Pipe() if err != nil { - return errors.Wrapf(err, "failed to create rootless port sync pipe") + return 0, errors.Wrapf(err, "failed to create rootless port sync pipe") } } ctr.rootlessSlirpSyncR, ctr.rootlessSlirpSyncW, err = os.Pipe() if err != nil { - return errors.Wrapf(err, "failed to create rootless network sync pipe") + return 0, errors.Wrapf(err, "failed to create rootless network sync pipe") } } else { if ctr.rootlessSlirpSyncR != nil { @@ -1189,22 +1207,25 @@ func (r *ConmonOCIRuntime) createOCIContainer(ctr *Container, restoreOptions *Co cmd.ExtraFiles = append(cmd.ExtraFiles, ctr.rootlessPortSyncW) } } - + var runtimeRestoreStarted time.Time + if restoreOptions != nil { + runtimeRestoreStarted = time.Now() + } err = startCommandGivenSelinux(cmd, ctr) // regardless of whether we errored or not, we no longer need the children pipes childSyncPipe.Close() childStartPipe.Close() if err != nil { - return err + return 0, err } if err := r.moveConmonToCgroupAndSignal(ctr, cmd, parentStartPipe); err != nil { - return err + return 0, err } /* Wait for initial setup and fork, and reap child */ err = cmd.Wait() if err != nil { - return err + return 0, err } pid, err := readConmonPipeData(parentSyncPipe, ociLog) @@ -1212,7 +1233,7 @@ func (r *ConmonOCIRuntime) createOCIContainer(ctr *Container, restoreOptions *Co if err2 := r.DeleteContainer(ctr); err2 != nil { logrus.Errorf("Removing container %s from runtime after creation failed", ctr.ID()) } - return err + return 0, err } ctr.state.PID = pid @@ -1238,13 +1259,20 @@ func (r *ConmonOCIRuntime) createOCIContainer(ctr *Container, restoreOptions *Co } } + runtimeRestoreDuration := func() int64 { + if restoreOptions != nil && restoreOptions.PrintStats { + return time.Since(runtimeRestoreStarted).Microseconds() + } + return 0 + }() + // These fds were passed down to the runtime. Close them // and not interfere for _, f := range filesToClose { errorhandling.CloseQuiet(f) } - return nil + return runtimeRestoreDuration, nil } // configureConmonEnv gets the environment values to add to conmon's exec struct diff --git a/libpod/oci_missing.go b/libpod/oci_missing.go index fcf2ffca8..65ff818b4 100644 --- a/libpod/oci_missing.go +++ b/libpod/oci_missing.go @@ -66,8 +66,8 @@ func (r *MissingRuntime) Path() string { } // CreateContainer is not available as the runtime is missing -func (r *MissingRuntime) CreateContainer(ctr *Container, restoreOptions *ContainerCheckpointOptions) error { - return r.printError() +func (r *MissingRuntime) CreateContainer(ctr *Container, restoreOptions *ContainerCheckpointOptions) (int64, error) { + return 0, r.printError() } // UpdateContainerStatus is not available as the runtime is missing @@ -153,8 +153,8 @@ func (r *MissingRuntime) ExecUpdateStatus(ctr *Container, sessionID string) (boo } // CheckpointContainer is not available as the runtime is missing -func (r *MissingRuntime) CheckpointContainer(ctr *Container, options ContainerCheckpointOptions) error { - return r.printError() +func (r *MissingRuntime) CheckpointContainer(ctr *Container, options ContainerCheckpointOptions) (int64, error) { + return 0, r.printError() } // CheckConmonRunning is not available as the runtime is missing diff --git a/pkg/api/handlers/libpod/containers.go b/pkg/api/handlers/libpod/containers.go index 343c0d0b3..3aeebc334 100644 --- a/pkg/api/handlers/libpod/containers.go +++ b/pkg/api/handlers/libpod/containers.go @@ -214,6 +214,7 @@ func Checkpoint(w http.ResponseWriter, r *http.Request) { TCPEstablished bool `schema:"tcpEstablished"` Export bool `schema:"export"` IgnoreRootFS bool `schema:"ignoreRootFS"` + PrintStats bool `schema:"printStats"` }{ // override any golang type defaults } @@ -248,11 +249,12 @@ func Checkpoint(w http.ResponseWriter, r *http.Request) { KeepRunning: query.LeaveRunning, TCPEstablished: query.TCPEstablished, IgnoreRootfs: query.IgnoreRootFS, + PrintStats: query.PrintStats, } if query.Export { options.TargetFile = targetFile } - err = ctr.Checkpoint(r.Context(), options) + criuStatistics, runtimeCheckpointDuration, err := ctr.Checkpoint(r.Context(), options) if err != nil { utils.InternalServerError(w, err) return @@ -267,7 +269,15 @@ func Checkpoint(w http.ResponseWriter, r *http.Request) { utils.WriteResponse(w, http.StatusOK, f) return } - utils.WriteResponse(w, http.StatusOK, entities.CheckpointReport{Id: ctr.ID()}) + utils.WriteResponse( + w, + http.StatusOK, + entities.CheckpointReport{ + Id: ctr.ID(), + RuntimeDuration: runtimeCheckpointDuration, + CRIUStatistics: criuStatistics, + }, + ) } func Restore(w http.ResponseWriter, r *http.Request) { @@ -284,6 +294,7 @@ func Restore(w http.ResponseWriter, r *http.Request) { IgnoreVolumes bool `schema:"ignoreVolumes"` IgnoreStaticIP bool `schema:"ignoreStaticIP"` IgnoreStaticMAC bool `schema:"ignoreStaticMAC"` + PrintStats bool `schema:"printStats"` }{ // override any golang type defaults } @@ -319,17 +330,26 @@ func Restore(w http.ResponseWriter, r *http.Request) { IgnoreRootfs: query.IgnoreRootFS, IgnoreStaticIP: query.IgnoreStaticIP, IgnoreStaticMAC: query.IgnoreStaticMAC, + PrintStats: query.PrintStats, } if query.Import { options.TargetFile = targetFile options.Name = query.Name } - err = ctr.Restore(r.Context(), options) + criuStatistics, runtimeRestoreDuration, err := ctr.Restore(r.Context(), options) if err != nil { utils.InternalServerError(w, err) return } - utils.WriteResponse(w, http.StatusOK, entities.RestoreReport{Id: ctr.ID()}) + utils.WriteResponse( + w, + http.StatusOK, + entities.RestoreReport{ + Id: ctr.ID(), + RuntimeDuration: runtimeRestoreDuration, + CRIUStatistics: criuStatistics, + }, + ) } func InitContainer(w http.ResponseWriter, r *http.Request) { diff --git a/pkg/api/handlers/libpod/generate.go b/pkg/api/handlers/libpod/generate.go index 117c5e2aa..5205d875d 100644 --- a/pkg/api/handlers/libpod/generate.go +++ b/pkg/api/handlers/libpod/generate.go @@ -17,14 +17,15 @@ func GenerateSystemd(w http.ResponseWriter, r *http.Request) { runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime) decoder := r.Context().Value(api.DecoderKey).(*schema.Decoder) query := struct { - Name bool `schema:"useName"` - New bool `schema:"new"` - NoHeader bool `schema:"noHeader"` - RestartPolicy *string `schema:"restartPolicy"` - StopTimeout uint `schema:"stopTimeout"` - ContainerPrefix string `schema:"containerPrefix"` - PodPrefix string `schema:"podPrefix"` - Separator string `schema:"separator"` + Name bool `schema:"useName"` + New bool `schema:"new"` + NoHeader bool `schema:"noHeader"` + TemplateUnitFile bool `schema:"templateUnitFile"` + RestartPolicy *string `schema:"restartPolicy"` + StopTimeout uint `schema:"stopTimeout"` + ContainerPrefix string `schema:"containerPrefix"` + PodPrefix string `schema:"podPrefix"` + Separator string `schema:"separator"` }{ StopTimeout: util.DefaultContainerConfig().Engine.StopTimeout, ContainerPrefix: "container", @@ -40,14 +41,15 @@ func GenerateSystemd(w http.ResponseWriter, r *http.Request) { containerEngine := abi.ContainerEngine{Libpod: runtime} options := entities.GenerateSystemdOptions{ - Name: query.Name, - New: query.New, - NoHeader: query.NoHeader, - RestartPolicy: query.RestartPolicy, - StopTimeout: &query.StopTimeout, - ContainerPrefix: query.ContainerPrefix, - PodPrefix: query.PodPrefix, - Separator: query.Separator, + Name: query.Name, + New: query.New, + NoHeader: query.NoHeader, + TemplateUnitFile: query.TemplateUnitFile, + RestartPolicy: query.RestartPolicy, + StopTimeout: &query.StopTimeout, + ContainerPrefix: query.ContainerPrefix, + PodPrefix: query.PodPrefix, + Separator: query.Separator, } report, err := containerEngine.GenerateSystemd(r.Context(), utils.GetName(r), options) diff --git a/pkg/api/handlers/types.go b/pkg/api/handlers/types.go index 35120a1a5..f850db3d8 100644 --- a/pkg/api/handlers/types.go +++ b/pkg/api/handlers/types.go @@ -116,6 +116,8 @@ type CreateContainerConfig struct { dockerContainer.Config // desired container configuration HostConfig dockerContainer.HostConfig // host dependent configuration for container NetworkingConfig dockerNetwork.NetworkingConfig // network configuration for container + UnsetEnv []string // unset specified default environment variables + UnsetEnvAll bool // unset all default environment variables } // swagger:model IDResponse diff --git a/pkg/api/server/register_containers.go b/pkg/api/server/register_containers.go index c4919182b..601e1251b 100644 --- a/pkg/api/server/register_containers.go +++ b/pkg/api/server/register_containers.go @@ -1441,6 +1441,10 @@ func (s *APIServer) registerContainersHandlers(r *mux.Router) error { // name: ignoreRootFS // type: boolean // description: do not include root file-system changes when exporting + // - in: query + // name: printStats + // type: boolean + // description: add checkpoint statistics to the returned CheckpointReport // produces: // - application/json // responses: @@ -1495,6 +1499,10 @@ func (s *APIServer) registerContainersHandlers(r *mux.Router) error { // name: ignoreStaticMAC // type: boolean // description: ignore MAC address if set statically + // - in: query + // name: printStats + // type: boolean + // description: add restore statistics to the returned RestoreReport // produces: // - application/json // responses: diff --git a/pkg/bindings/connection.go b/pkg/bindings/connection.go index a2be44ab4..b2e949f67 100644 --- a/pkg/bindings/connection.go +++ b/pkg/bindings/connection.go @@ -145,7 +145,7 @@ func pingNewConnection(ctx context.Context) error { return err } // the ping endpoint sits at / in this case - response, err := client.DoRequest(nil, http.MethodGet, "/_ping", nil, nil) + response, err := client.DoRequest(ctx, nil, http.MethodGet, "/_ping", nil, nil) if err != nil { return err } @@ -306,7 +306,7 @@ func unixClient(_url *url.URL) Connection { } // DoRequest assembles the http request and returns the response -func (c *Connection) DoRequest(httpBody io.Reader, httpMethod, endpoint string, queryParams url.Values, header map[string]string, pathValues ...string) (*APIResponse, error) { +func (c *Connection) DoRequest(ctx context.Context, httpBody io.Reader, httpMethod, endpoint string, queryParams url.Values, header map[string]string, pathValues ...string) (*APIResponse, error) { var ( err error response *http.Response @@ -328,7 +328,7 @@ func (c *Connection) DoRequest(httpBody io.Reader, httpMethod, endpoint string, uri := fmt.Sprintf("http://d/v%d.%d.%d/libpod"+endpoint, params...) logrus.Debugf("DoRequest Method: %s URI: %v", httpMethod, uri) - req, err := http.NewRequestWithContext(context.WithValue(context.Background(), clientKey, c), httpMethod, uri, httpBody) + req, err := http.NewRequestWithContext(ctx, httpMethod, uri, httpBody) if err != nil { return nil, err } diff --git a/pkg/bindings/containers/archive.go b/pkg/bindings/containers/archive.go index 876f5340b..d64fbffd6 100644 --- a/pkg/bindings/containers/archive.go +++ b/pkg/bindings/containers/archive.go @@ -23,7 +23,7 @@ func Stat(ctx context.Context, nameOrID string, path string) (*entities.Containe params := url.Values{} params.Set("path", path) - response, err := conn.DoRequest(nil, http.MethodHead, "/containers/%s/archive", params, nil, nameOrID) + response, err := conn.DoRequest(ctx, nil, http.MethodHead, "/containers/%s/archive", params, nil, nameOrID) if err != nil { return nil, err } @@ -71,7 +71,7 @@ func CopyFromArchiveWithOptions(ctx context.Context, nameOrID string, path strin params.Set("path", path) return func() error { - response, err := conn.DoRequest(reader, http.MethodPut, "/containers/%s/archive", params, nil, nameOrID) + response, err := conn.DoRequest(ctx, reader, http.MethodPut, "/containers/%s/archive", params, nil, nameOrID) if err != nil { return err } @@ -92,7 +92,7 @@ func CopyToArchive(ctx context.Context, nameOrID string, path string, writer io. params := url.Values{} params.Set("path", path) - response, err := conn.DoRequest(nil, http.MethodGet, "/containers/%s/archive", params, nil, nameOrID) + response, err := conn.DoRequest(ctx, nil, http.MethodGet, "/containers/%s/archive", params, nil, nameOrID) if err != nil { return nil, err } diff --git a/pkg/bindings/containers/attach.go b/pkg/bindings/containers/attach.go index 47de89b33..baa3f182e 100644 --- a/pkg/bindings/containers/attach.go +++ b/pkg/bindings/containers/attach.go @@ -130,7 +130,7 @@ func Attach(ctx context.Context, nameOrID string, stdin io.Reader, stdout io.Wri IdleConnTimeout: time.Duration(0), } conn.Client.Transport = t - response, err := conn.DoRequest(nil, http.MethodPost, "/containers/%s/attach", params, headers, nameOrID) + response, err := conn.DoRequest(ctx, nil, http.MethodPost, "/containers/%s/attach", params, headers, nameOrID) if err != nil { return err } @@ -322,7 +322,7 @@ func resizeTTY(ctx context.Context, endpoint string, height *int, width *int) er params.Set("w", strconv.Itoa(*width)) } params.Set("running", "true") - rsp, err := conn.DoRequest(nil, http.MethodPost, endpoint, params, nil) + rsp, err := conn.DoRequest(ctx, nil, http.MethodPost, endpoint, params, nil) if err != nil { return err } @@ -407,7 +407,7 @@ func ExecStartAndAttach(ctx context.Context, sessionID string, options *ExecStar // We need to inspect the exec session first to determine whether to use // -t. - resp, err := conn.DoRequest(nil, http.MethodGet, "/exec/%s/json", nil, nil, sessionID) + resp, err := conn.DoRequest(ctx, nil, http.MethodGet, "/exec/%s/json", nil, nil, sessionID) if err != nil { return err } @@ -478,7 +478,7 @@ func ExecStartAndAttach(ctx context.Context, sessionID string, options *ExecStar IdleConnTimeout: time.Duration(0), } conn.Client.Transport = t - response, err := conn.DoRequest(bytes.NewReader(bodyJSON), http.MethodPost, "/exec/%s/start", nil, nil, sessionID) + response, err := conn.DoRequest(ctx, bytes.NewReader(bodyJSON), http.MethodPost, "/exec/%s/start", nil, nil, sessionID) if err != nil { return err } diff --git a/pkg/bindings/containers/checkpoint.go b/pkg/bindings/containers/checkpoint.go index 7f7080f13..2ad2c6931 100644 --- a/pkg/bindings/containers/checkpoint.go +++ b/pkg/bindings/containers/checkpoint.go @@ -23,7 +23,7 @@ func Checkpoint(ctx context.Context, nameOrID string, options *CheckpointOptions if err != nil { return nil, err } - response, err := conn.DoRequest(nil, http.MethodPost, "/containers/%s/checkpoint", params, nil, nameOrID) + response, err := conn.DoRequest(ctx, nil, http.MethodPost, "/containers/%s/checkpoint", params, nil, nameOrID) if err != nil { return nil, err } @@ -52,7 +52,7 @@ func Restore(ctx context.Context, nameOrID string, options *RestoreOptions) (*en if i := options.GetImportAchive(); options.Changed("ImportArchive") { params.Set("import", i) } - response, err := conn.DoRequest(nil, http.MethodPost, "/containers/%s/restore", params, nil, nameOrID) + response, err := conn.DoRequest(ctx, nil, http.MethodPost, "/containers/%s/restore", params, nil, nameOrID) if err != nil { return nil, err } diff --git a/pkg/bindings/containers/commit.go b/pkg/bindings/containers/commit.go index a4adebb1f..372a99d32 100644 --- a/pkg/bindings/containers/commit.go +++ b/pkg/bindings/containers/commit.go @@ -24,7 +24,7 @@ func Commit(ctx context.Context, nameOrID string, options *CommitOptions) (handl return handlers.IDResponse{}, err } params.Set("container", nameOrID) - response, err := conn.DoRequest(nil, http.MethodPost, "/commit", params, nil) + response, err := conn.DoRequest(ctx, nil, http.MethodPost, "/commit", params, nil) if err != nil { return id, err } diff --git a/pkg/bindings/containers/containers.go b/pkg/bindings/containers/containers.go index aafb83f65..14a173025 100644 --- a/pkg/bindings/containers/containers.go +++ b/pkg/bindings/containers/containers.go @@ -38,7 +38,7 @@ func List(ctx context.Context, options *ListOptions) ([]entities.ListContainer, if err != nil { return nil, err } - response, err := conn.DoRequest(nil, http.MethodGet, "/containers/json", params, nil) + response, err := conn.DoRequest(ctx, nil, http.MethodGet, "/containers/json", params, nil) if err != nil { return containers, err } @@ -64,7 +64,7 @@ func Prune(ctx context.Context, options *PruneOptions) ([]*reports.PruneReport, if err != nil { return nil, err } - response, err := conn.DoRequest(nil, http.MethodPost, "/containers/prune", params, nil) + response, err := conn.DoRequest(ctx, nil, http.MethodPost, "/containers/prune", params, nil) if err != nil { return nil, err } @@ -90,7 +90,7 @@ func Remove(ctx context.Context, nameOrID string, options *RemoveOptions) error if err != nil { return err } - response, err := conn.DoRequest(nil, http.MethodDelete, "/containers/%s", params, nil, nameOrID) + response, err := conn.DoRequest(ctx, nil, http.MethodDelete, "/containers/%s", params, nil, nameOrID) if err != nil { return err } @@ -115,7 +115,7 @@ func Inspect(ctx context.Context, nameOrID string, options *InspectOptions) (*de if err != nil { return nil, err } - response, err := conn.DoRequest(nil, http.MethodGet, "/containers/%s/json", params, nil, nameOrID) + response, err := conn.DoRequest(ctx, nil, http.MethodGet, "/containers/%s/json", params, nil, nameOrID) if err != nil { return nil, err } @@ -140,7 +140,7 @@ func Kill(ctx context.Context, nameOrID string, options *KillOptions) error { if err != nil { return err } - response, err := conn.DoRequest(nil, http.MethodPost, "/containers/%s/kill", params, nil, nameOrID) + response, err := conn.DoRequest(ctx, nil, http.MethodPost, "/containers/%s/kill", params, nil, nameOrID) if err != nil { return err } @@ -160,7 +160,7 @@ func Pause(ctx context.Context, nameOrID string, options *PauseOptions) error { if err != nil { return err } - response, err := conn.DoRequest(nil, http.MethodPost, "/containers/%s/pause", nil, nil, nameOrID) + response, err := conn.DoRequest(ctx, nil, http.MethodPost, "/containers/%s/pause", nil, nil, nameOrID) if err != nil { return err } @@ -184,7 +184,7 @@ func Restart(ctx context.Context, nameOrID string, options *RestartOptions) erro if err != nil { return err } - response, err := conn.DoRequest(nil, http.MethodPost, "/containers/%s/restart", params, nil, nameOrID) + response, err := conn.DoRequest(ctx, nil, http.MethodPost, "/containers/%s/restart", params, nil, nameOrID) if err != nil { return err } @@ -209,7 +209,7 @@ func Start(ctx context.Context, nameOrID string, options *StartOptions) error { if err != nil { return err } - response, err := conn.DoRequest(nil, http.MethodPost, "/containers/%s/start", params, nil, nameOrID) + response, err := conn.DoRequest(ctx, nil, http.MethodPost, "/containers/%s/start", params, nil, nameOrID) if err != nil { return err } @@ -235,7 +235,7 @@ func Stats(ctx context.Context, containers []string, options *StatsOptions) (cha params.Add("containers", c) } - response, err := conn.DoRequest(nil, http.MethodGet, "/containers/stats", params, nil) + response, err := conn.DoRequest(ctx, nil, http.MethodGet, "/containers/stats", params, nil) if err != nil { return nil, err } @@ -293,7 +293,7 @@ func Top(ctx context.Context, nameOrID string, options *TopOptions) ([]string, e psArgs := strings.Join(options.GetDescriptors(), ",") params.Add("ps_args", psArgs) } - response, err := conn.DoRequest(nil, http.MethodGet, "/containers/%s/top", params, nil, nameOrID) + response, err := conn.DoRequest(ctx, nil, http.MethodGet, "/containers/%s/top", params, nil, nameOrID) if err != nil { return nil, err } @@ -326,7 +326,7 @@ func Unpause(ctx context.Context, nameOrID string, options *UnpauseOptions) erro if err != nil { return err } - response, err := conn.DoRequest(nil, http.MethodPost, "/containers/%s/unpause", nil, nil, nameOrID) + response, err := conn.DoRequest(ctx, nil, http.MethodPost, "/containers/%s/unpause", nil, nil, nameOrID) if err != nil { return err } @@ -351,7 +351,7 @@ func Wait(ctx context.Context, nameOrID string, options *WaitOptions) (int32, er if err != nil { return exitCode, err } - response, err := conn.DoRequest(nil, http.MethodPost, "/containers/%s/wait", params, nil, nameOrID) + response, err := conn.DoRequest(ctx, nil, http.MethodPost, "/containers/%s/wait", params, nil, nameOrID) if err != nil { return exitCode, err } @@ -372,7 +372,7 @@ func Exists(ctx context.Context, nameOrID string, options *ExistsOptions) (bool, if err != nil { return false, err } - response, err := conn.DoRequest(nil, http.MethodGet, "/containers/%s/exists", params, nil, nameOrID) + response, err := conn.DoRequest(ctx, nil, http.MethodGet, "/containers/%s/exists", params, nil, nameOrID) if err != nil { return false, err } @@ -395,7 +395,7 @@ func Stop(ctx context.Context, nameOrID string, options *StopOptions) error { if err != nil { return err } - response, err := conn.DoRequest(nil, http.MethodPost, "/containers/%s/stop", params, nil, nameOrID) + response, err := conn.DoRequest(ctx, nil, http.MethodPost, "/containers/%s/stop", params, nil, nameOrID) if err != nil { return err } @@ -416,7 +416,7 @@ func Export(ctx context.Context, nameOrID string, w io.Writer, options *ExportOp if err != nil { return err } - response, err := conn.DoRequest(nil, http.MethodGet, "/containers/%s/export", params, nil, nameOrID) + response, err := conn.DoRequest(ctx, nil, http.MethodGet, "/containers/%s/export", params, nil, nameOrID) if err != nil { return err } @@ -441,7 +441,7 @@ func ContainerInit(ctx context.Context, nameOrID string, options *InitOptions) e if err != nil { return err } - response, err := conn.DoRequest(nil, http.MethodPost, "/containers/%s/init", nil, nil, nameOrID) + response, err := conn.DoRequest(ctx, nil, http.MethodPost, "/containers/%s/init", nil, nil, nameOrID) if err != nil { return err } @@ -462,7 +462,7 @@ func ShouldRestart(ctx context.Context, nameOrID string, options *ShouldRestartO if err != nil { return false, err } - response, err := conn.DoRequest(nil, http.MethodPost, "/containers/%s/shouldrestart", nil, nil, nameOrID) + response, err := conn.DoRequest(ctx, nil, http.MethodPost, "/containers/%s/shouldrestart", nil, nil, nameOrID) if err != nil { return false, err } diff --git a/pkg/bindings/containers/create.go b/pkg/bindings/containers/create.go index c0b9538a6..83b5b5ac7 100644 --- a/pkg/bindings/containers/create.go +++ b/pkg/bindings/containers/create.go @@ -26,7 +26,7 @@ func CreateWithSpec(ctx context.Context, s *specgen.SpecGenerator, options *Crea return ccr, err } stringReader := strings.NewReader(specgenString) - response, err := conn.DoRequest(stringReader, http.MethodPost, "/containers/create", nil, nil) + response, err := conn.DoRequest(ctx, stringReader, http.MethodPost, "/containers/create", nil, nil) if err != nil { return ccr, err } diff --git a/pkg/bindings/containers/diff.go b/pkg/bindings/containers/diff.go index e4ec49809..65a4f81bb 100644 --- a/pkg/bindings/containers/diff.go +++ b/pkg/bindings/containers/diff.go @@ -22,7 +22,7 @@ func Diff(ctx context.Context, nameOrID string, options *DiffOptions) ([]archive if err != nil { return nil, err } - response, err := conn.DoRequest(nil, http.MethodGet, "/containers/%s/changes", params, nil, nameOrID) + response, err := conn.DoRequest(ctx, nil, http.MethodGet, "/containers/%s/changes", params, nil, nameOrID) if err != nil { return nil, err } diff --git a/pkg/bindings/containers/exec.go b/pkg/bindings/containers/exec.go index 12b31aba3..e41ed66c6 100644 --- a/pkg/bindings/containers/exec.go +++ b/pkg/bindings/containers/exec.go @@ -35,7 +35,7 @@ func ExecCreate(ctx context.Context, nameOrID string, config *handlers.ExecCreat } jsonReader := strings.NewReader(string(requestJSON)) - resp, err := conn.DoRequest(jsonReader, http.MethodPost, "/containers/%s/exec", nil, nil, nameOrID) + resp, err := conn.DoRequest(ctx, jsonReader, http.MethodPost, "/containers/%s/exec", nil, nil, nameOrID) if err != nil { return "", err } @@ -63,7 +63,7 @@ func ExecInspect(ctx context.Context, sessionID string, options *ExecInspectOpti logrus.Debugf("Inspecting session ID %q", sessionID) - resp, err := conn.DoRequest(nil, http.MethodGet, "/exec/%s/json", nil, nil, sessionID) + resp, err := conn.DoRequest(ctx, nil, http.MethodGet, "/exec/%s/json", nil, nil, sessionID) if err != nil { return nil, err } @@ -101,7 +101,7 @@ func ExecStart(ctx context.Context, sessionID string, options *ExecStartOptions) return err } - resp, err := conn.DoRequest(bytes.NewReader(bodyJSON), http.MethodPost, "/exec/%s/start", nil, nil, sessionID) + resp, err := conn.DoRequest(ctx, bytes.NewReader(bodyJSON), http.MethodPost, "/exec/%s/start", nil, nil, sessionID) if err != nil { return err } diff --git a/pkg/bindings/containers/healthcheck.go b/pkg/bindings/containers/healthcheck.go index 0e65a5a46..990d8dc69 100644 --- a/pkg/bindings/containers/healthcheck.go +++ b/pkg/bindings/containers/healthcheck.go @@ -22,7 +22,7 @@ func RunHealthCheck(ctx context.Context, nameOrID string, options *HealthCheckOp var ( status define.HealthCheckResults ) - response, err := conn.DoRequest(nil, http.MethodGet, "/containers/%s/healthcheck", nil, nil, nameOrID) + response, err := conn.DoRequest(ctx, nil, http.MethodGet, "/containers/%s/healthcheck", nil, nil, nameOrID) if err != nil { return nil, err } diff --git a/pkg/bindings/containers/logs.go b/pkg/bindings/containers/logs.go index 37ffdf0a5..df1dd22ea 100644 --- a/pkg/bindings/containers/logs.go +++ b/pkg/bindings/containers/logs.go @@ -29,7 +29,7 @@ func Logs(ctx context.Context, nameOrID string, options *LogOptions, stdoutChan, if options.Stdout == nil && options.Stderr == nil { params.Set("stdout", strconv.FormatBool(true)) } - response, err := conn.DoRequest(nil, http.MethodGet, "/containers/%s/logs", params, nil, nameOrID) + response, err := conn.DoRequest(ctx, nil, http.MethodGet, "/containers/%s/logs", params, nil, nameOrID) if err != nil { return err } diff --git a/pkg/bindings/containers/mount.go b/pkg/bindings/containers/mount.go index c07998fd3..5756c4cab 100644 --- a/pkg/bindings/containers/mount.go +++ b/pkg/bindings/containers/mount.go @@ -21,7 +21,7 @@ func Mount(ctx context.Context, nameOrID string, options *MountOptions) (string, var ( path string ) - response, err := conn.DoRequest(nil, http.MethodPost, "/containers/%s/mount", nil, nil, nameOrID) + response, err := conn.DoRequest(ctx, nil, http.MethodPost, "/containers/%s/mount", nil, nil, nameOrID) if err != nil { return path, err } @@ -41,7 +41,7 @@ func Unmount(ctx context.Context, nameOrID string, options *UnmountOptions) erro if err != nil { return err } - response, err := conn.DoRequest(nil, http.MethodPost, "/containers/%s/unmount", nil, nil, nameOrID) + response, err := conn.DoRequest(ctx, nil, http.MethodPost, "/containers/%s/unmount", nil, nil, nameOrID) if err != nil { return err } @@ -61,7 +61,7 @@ func GetMountedContainerPaths(ctx context.Context, options *MountedContainerPath return nil, err } mounts := make(map[string]string) - response, err := conn.DoRequest(nil, http.MethodGet, "/containers/showmounted", nil, nil) + response, err := conn.DoRequest(ctx, nil, http.MethodGet, "/containers/showmounted", nil, nil) if err != nil { return mounts, err } diff --git a/pkg/bindings/containers/rename.go b/pkg/bindings/containers/rename.go index 172d7838a..29dfc581b 100644 --- a/pkg/bindings/containers/rename.go +++ b/pkg/bindings/containers/rename.go @@ -20,7 +20,7 @@ func Rename(ctx context.Context, nameOrID string, options *RenameOptions) error if err != nil { return err } - response, err := conn.DoRequest(nil, http.MethodPost, "/containers/%s/rename", params, nil, nameOrID) + response, err := conn.DoRequest(ctx, nil, http.MethodPost, "/containers/%s/rename", params, nil, nameOrID) if err != nil { return err } diff --git a/pkg/bindings/generate/generate.go b/pkg/bindings/generate/generate.go index 742956515..641c14231 100644 --- a/pkg/bindings/generate/generate.go +++ b/pkg/bindings/generate/generate.go @@ -22,7 +22,7 @@ func Systemd(ctx context.Context, nameOrID string, options *SystemdOptions) (*en return nil, err } - response, err := conn.DoRequest(nil, http.MethodGet, "/generate/%s/systemd", params, nil, nameOrID) + response, err := conn.DoRequest(ctx, nil, http.MethodGet, "/generate/%s/systemd", params, nil, nameOrID) if err != nil { return nil, err } @@ -54,7 +54,7 @@ func Kube(ctx context.Context, nameOrIDs []string, options *KubeOptions) (*entit for _, name := range nameOrIDs { params.Add("names", name) } - response, err := conn.DoRequest(nil, http.MethodGet, "/generate/kube", params, nil) + response, err := conn.DoRequest(ctx, nil, http.MethodGet, "/generate/kube", params, nil) if err != nil { return nil, err } diff --git a/pkg/bindings/generate/types.go b/pkg/bindings/generate/types.go index 3c9ea87d4..6f2594604 100644 --- a/pkg/bindings/generate/types.go +++ b/pkg/bindings/generate/types.go @@ -16,6 +16,8 @@ type SystemdOptions struct { New *bool // NoHeader - Removes autogenerated by Podman and timestamp if set to true NoHeader *bool + // TemplateUnitFile - Create a template unit file that uses the identity specifiers + TemplateUnitFile *bool // RestartPolicy - systemd restart policy. RestartPolicy *string // StopTimeout - time when stopping the container. diff --git a/pkg/bindings/generate/types_systemd_options.go b/pkg/bindings/generate/types_systemd_options.go index 7a778a52b..b26aa7fc2 100644 --- a/pkg/bindings/generate/types_systemd_options.go +++ b/pkg/bindings/generate/types_systemd_options.go @@ -62,6 +62,21 @@ func (o *SystemdOptions) GetNoHeader() bool { return *o.NoHeader } +// WithTemplateUnitFile set field TemplateUnitFile to given value +func (o *SystemdOptions) WithTemplateUnitFile(value bool) *SystemdOptions { + o.TemplateUnitFile = &value + return o +} + +// GetTemplateUnitFile returns value of field TemplateUnitFile +func (o *SystemdOptions) GetTemplateUnitFile() bool { + if o.TemplateUnitFile == nil { + var z bool + return z + } + return *o.TemplateUnitFile +} + // WithRestartPolicy set field RestartPolicy to given value func (o *SystemdOptions) WithRestartPolicy(value string) *SystemdOptions { o.RestartPolicy = &value diff --git a/pkg/bindings/images/build.go b/pkg/bindings/images/build.go index 403d90721..3b0bebe9f 100644 --- a/pkg/bindings/images/build.go +++ b/pkg/bindings/images/build.go @@ -392,7 +392,7 @@ func Build(ctx context.Context, containerFiles []string, options entities.BuildO if err != nil { return nil, err } - response, err := conn.DoRequest(tarfile, http.MethodPost, "/build", params, headers) + response, err := conn.DoRequest(ctx, tarfile, http.MethodPost, "/build", params, headers) if err != nil { return nil, err } diff --git a/pkg/bindings/images/diff.go b/pkg/bindings/images/diff.go index 671b73089..3df0b9615 100644 --- a/pkg/bindings/images/diff.go +++ b/pkg/bindings/images/diff.go @@ -19,7 +19,7 @@ func Diff(ctx context.Context, nameOrID string, options *DiffOptions) ([]archive return nil, err } - response, err := conn.DoRequest(nil, http.MethodGet, "/images/%s/changes", nil, nil, nameOrID) + response, err := conn.DoRequest(ctx, nil, http.MethodGet, "/images/%s/changes", nil, nil, nameOrID) if err != nil { return nil, err } diff --git a/pkg/bindings/images/images.go b/pkg/bindings/images/images.go index 959481e0d..dfb500772 100644 --- a/pkg/bindings/images/images.go +++ b/pkg/bindings/images/images.go @@ -23,7 +23,7 @@ func Exists(ctx context.Context, nameOrID string, options *ExistsOptions) (bool, if err != nil { return false, err } - response, err := conn.DoRequest(nil, http.MethodGet, "/images/%s/exists", nil, nil, nameOrID) + response, err := conn.DoRequest(ctx, nil, http.MethodGet, "/images/%s/exists", nil, nil, nameOrID) if err != nil { return false, err } @@ -47,7 +47,7 @@ func List(ctx context.Context, options *ListOptions) ([]*entities.ImageSummary, if err != nil { return nil, err } - response, err := conn.DoRequest(nil, http.MethodGet, "/images/json", params, nil) + response, err := conn.DoRequest(ctx, nil, http.MethodGet, "/images/json", params, nil) if err != nil { return imageSummary, err } @@ -71,7 +71,7 @@ func GetImage(ctx context.Context, nameOrID string, options *GetOptions) (*entit return nil, err } inspectedData := entities.ImageInspectReport{} - response, err := conn.DoRequest(nil, http.MethodGet, "/images/%s/json", params, nil, nameOrID) + response, err := conn.DoRequest(ctx, nil, http.MethodGet, "/images/%s/json", params, nil, nameOrID) if err != nil { return &inspectedData, err } @@ -94,7 +94,7 @@ func Tree(ctx context.Context, nameOrID string, options *TreeOptions) (*entities if err != nil { return nil, err } - response, err := conn.DoRequest(nil, http.MethodGet, "/images/%s/tree", params, nil, nameOrID) + response, err := conn.DoRequest(ctx, nil, http.MethodGet, "/images/%s/tree", params, nil, nameOrID) if err != nil { return nil, err } @@ -114,7 +114,7 @@ func History(ctx context.Context, nameOrID string, options *HistoryOptions) ([]* if err != nil { return nil, err } - response, err := conn.DoRequest(nil, http.MethodGet, "/images/%s/history", nil, nil, nameOrID) + response, err := conn.DoRequest(ctx, nil, http.MethodGet, "/images/%s/history", nil, nil, nameOrID) if err != nil { return history, err } @@ -129,7 +129,7 @@ func Load(ctx context.Context, r io.Reader) (*entities.ImageLoadReport, error) { if err != nil { return nil, err } - response, err := conn.DoRequest(r, http.MethodPost, "/images/load", nil, nil) + response, err := conn.DoRequest(ctx, r, http.MethodPost, "/images/load", nil, nil) if err != nil { return nil, err } @@ -155,7 +155,7 @@ func Export(ctx context.Context, nameOrIDs []string, w io.Writer, options *Expor for _, ref := range nameOrIDs { params.Add("references", ref) } - response, err := conn.DoRequest(nil, http.MethodGet, "/images/export", params, nil) + response, err := conn.DoRequest(ctx, nil, http.MethodGet, "/images/export", params, nil) if err != nil { return err } @@ -185,7 +185,7 @@ func Prune(ctx context.Context, options *PruneOptions) ([]*reports.PruneReport, if err != nil { return nil, err } - response, err := conn.DoRequest(nil, http.MethodPost, "/images/prune", params, nil) + response, err := conn.DoRequest(ctx, nil, http.MethodPost, "/images/prune", params, nil) if err != nil { return deleted, err } @@ -207,7 +207,7 @@ func Tag(ctx context.Context, nameOrID, tag, repo string, options *TagOptions) e params := url.Values{} params.Set("tag", tag) params.Set("repo", repo) - response, err := conn.DoRequest(nil, http.MethodPost, "/images/%s/tag", params, nil, nameOrID) + response, err := conn.DoRequest(ctx, nil, http.MethodPost, "/images/%s/tag", params, nil, nameOrID) if err != nil { return err } @@ -229,7 +229,7 @@ func Untag(ctx context.Context, nameOrID, tag, repo string, options *UntagOption params := url.Values{} params.Set("tag", tag) params.Set("repo", repo) - response, err := conn.DoRequest(nil, http.MethodPost, "/images/%s/untag", params, nil, nameOrID) + response, err := conn.DoRequest(ctx, nil, http.MethodPost, "/images/%s/untag", params, nil, nameOrID) if err != nil { return err } @@ -257,7 +257,7 @@ func Import(ctx context.Context, r io.Reader, options *ImportOptions) (*entities if err != nil { return nil, err } - response, err := conn.DoRequest(r, http.MethodPost, "/images/import", params, nil) + response, err := conn.DoRequest(ctx, r, http.MethodPost, "/images/import", params, nil) if err != nil { return nil, err } @@ -298,7 +298,7 @@ func Push(ctx context.Context, source string, destination string, options *PushO params.Set("destination", destination) path := fmt.Sprintf("/images/%s/push", source) - response, err := conn.DoRequest(nil, http.MethodPost, path, params, header) + response, err := conn.DoRequest(ctx, nil, http.MethodPost, path, params, header) if err != nil { return err } @@ -334,7 +334,7 @@ func Search(ctx context.Context, term string, options *SearchOptions) ([]entitie return nil, err } - response, err := conn.DoRequest(nil, http.MethodGet, "/images/search", params, header) + response, err := conn.DoRequest(ctx, nil, http.MethodGet, "/images/search", params, header) if err != nil { return nil, err } diff --git a/pkg/bindings/images/pull.go b/pkg/bindings/images/pull.go index 7dfe9560c..be21aa593 100644 --- a/pkg/bindings/images/pull.go +++ b/pkg/bindings/images/pull.go @@ -47,7 +47,7 @@ func Pull(ctx context.Context, rawImage string, options *PullOptions) ([]string, return nil, err } - response, err := conn.DoRequest(nil, http.MethodPost, "/images/pull", params, header) + response, err := conn.DoRequest(ctx, nil, http.MethodPost, "/images/pull", params, header) if err != nil { return nil, err } diff --git a/pkg/bindings/images/rm.go b/pkg/bindings/images/rm.go index 461eb7729..47d7c2a4b 100644 --- a/pkg/bindings/images/rm.go +++ b/pkg/bindings/images/rm.go @@ -32,7 +32,7 @@ func Remove(ctx context.Context, images []string, options *RemoveOptions) (*enti for _, image := range images { params.Add("images", image) } - response, err := conn.DoRequest(nil, http.MethodDelete, "/images/remove", params, nil) + response, err := conn.DoRequest(ctx, nil, http.MethodDelete, "/images/remove", params, nil) if err != nil { return nil, []error{err} } diff --git a/pkg/bindings/manifests/manifests.go b/pkg/bindings/manifests/manifests.go index 6aa4961f1..af74eb406 100644 --- a/pkg/bindings/manifests/manifests.go +++ b/pkg/bindings/manifests/manifests.go @@ -42,7 +42,7 @@ func Create(ctx context.Context, names, images []string, options *CreateOptions) params.Add("image", i) } - response, err := conn.DoRequest(nil, http.MethodPost, "/manifests/create", params, nil) + response, err := conn.DoRequest(ctx, nil, http.MethodPost, "/manifests/create", params, nil) if err != nil { return "", err } @@ -57,7 +57,7 @@ func Exists(ctx context.Context, name string, options *ExistsOptions) (bool, err if err != nil { return false, err } - response, err := conn.DoRequest(nil, http.MethodGet, "/manifests/%s/exists", nil, nil, name) + response, err := conn.DoRequest(ctx, nil, http.MethodGet, "/manifests/%s/exists", nil, nil, name) if err != nil { return false, err } @@ -77,7 +77,7 @@ func Inspect(ctx context.Context, name string, options *InspectOptions) (*manife if err != nil { return nil, err } - response, err := conn.DoRequest(nil, http.MethodGet, "/manifests/%s/json", nil, nil, name) + response, err := conn.DoRequest(ctx, nil, http.MethodGet, "/manifests/%s/json", nil, nil, name) if err != nil { return nil, err } @@ -102,7 +102,7 @@ func Add(ctx context.Context, name string, options *AddOptions) (string, error) return "", err } stringReader := strings.NewReader(optionsString) - response, err := conn.DoRequest(stringReader, http.MethodPost, "/manifests/%s/add", nil, nil, name) + response, err := conn.DoRequest(ctx, stringReader, http.MethodPost, "/manifests/%s/add", nil, nil, name) if err != nil { return "", err } @@ -125,7 +125,7 @@ func Remove(ctx context.Context, name, digest string, options *RemoveOptions) (s } params := url.Values{} params.Set("digest", digest) - response, err := conn.DoRequest(nil, http.MethodDelete, "/manifests/%s", params, nil, name) + response, err := conn.DoRequest(ctx, nil, http.MethodDelete, "/manifests/%s", params, nil, name) if err != nil { return "", err } @@ -163,7 +163,7 @@ func Push(ctx context.Context, name, destination string, options *images.PushOpt } params.Set("image", name) params.Set("destination", destination) - response, err := conn.DoRequest(nil, http.MethodPost, "/manifests/%s/push", params, nil, name) + response, err := conn.DoRequest(ctx, nil, http.MethodPost, "/manifests/%s/push", params, nil, name) if err != nil { return "", err } @@ -187,7 +187,7 @@ func Push(ctx context.Context, name, destination string, options *images.PushOpt // return "", err // } // stringReader := strings.NewReader(optionsString) -// response, err := conn.DoRequest(stringReader, http.MethodPost, "/manifests/%s/annotate", params, name) +// response, err := conn.DoRequest(ctx, stringReader, http.MethodPost, "/manifests/%s/annotate", params, name) // if err != nil { // return "", err // } diff --git a/pkg/bindings/network/network.go b/pkg/bindings/network/network.go index 5a0a34f56..172598be1 100644 --- a/pkg/bindings/network/network.go +++ b/pkg/bindings/network/network.go @@ -28,7 +28,7 @@ func Create(ctx context.Context, network *types.Network) (types.Network, error) return report, err } reader := strings.NewReader(networkConfig) - response, err := conn.DoRequest(reader, http.MethodPost, "/networks/create", nil, nil) + response, err := conn.DoRequest(ctx, reader, http.MethodPost, "/networks/create", nil, nil) if err != nil { return report, err } @@ -44,7 +44,7 @@ func Inspect(ctx context.Context, nameOrID string, _ *InspectOptions) (types.Net if err != nil { return net, err } - response, err := conn.DoRequest(nil, http.MethodGet, "/networks/%s/json", nil, nil, nameOrID) + response, err := conn.DoRequest(ctx, nil, http.MethodGet, "/networks/%s/json", nil, nil, nameOrID) if err != nil { return net, err } @@ -69,7 +69,7 @@ func Remove(ctx context.Context, nameOrID string, options *RemoveOptions) ([]*en if err != nil { return nil, err } - response, err := conn.DoRequest(nil, http.MethodDelete, "/networks/%s", params, nil, nameOrID) + response, err := conn.DoRequest(ctx, nil, http.MethodDelete, "/networks/%s", params, nil, nameOrID) if err != nil { return nil, err } @@ -92,7 +92,7 @@ func List(ctx context.Context, options *ListOptions) ([]types.Network, error) { if err != nil { return nil, err } - response, err := conn.DoRequest(nil, http.MethodGet, "/networks/json", params, nil) + response, err := conn.DoRequest(ctx, nil, http.MethodGet, "/networks/json", params, nil) if err != nil { return netList, err } @@ -128,7 +128,7 @@ func Disconnect(ctx context.Context, networkName string, ContainerNameOrID strin return err } stringReader := strings.NewReader(body) - response, err := conn.DoRequest(stringReader, http.MethodPost, "/networks/%s/disconnect", params, nil, networkName) + response, err := conn.DoRequest(ctx, stringReader, http.MethodPost, "/networks/%s/disconnect", params, nil, networkName) if err != nil { return err } @@ -163,7 +163,7 @@ func Connect(ctx context.Context, networkName string, ContainerNameOrID string, return err } stringReader := strings.NewReader(body) - response, err := conn.DoRequest(stringReader, http.MethodPost, "/networks/%s/connect", params, nil, networkName) + response, err := conn.DoRequest(ctx, stringReader, http.MethodPost, "/networks/%s/connect", params, nil, networkName) if err != nil { return err } @@ -178,7 +178,7 @@ func Exists(ctx context.Context, nameOrID string, options *ExistsOptions) (bool, if err != nil { return false, err } - response, err := conn.DoRequest(nil, http.MethodGet, "/networks/%s/exists", nil, nil, nameOrID) + response, err := conn.DoRequest(ctx, nil, http.MethodGet, "/networks/%s/exists", nil, nil, nameOrID) if err != nil { return false, err } @@ -204,7 +204,7 @@ func Prune(ctx context.Context, options *PruneOptions) ([]*entities.NetworkPrune return nil, err } - response, err := conn.DoRequest(nil, http.MethodPost, "/networks/prune", params, nil) + response, err := conn.DoRequest(ctx, nil, http.MethodPost, "/networks/prune", params, nil) if err != nil { return nil, err } diff --git a/pkg/bindings/play/play.go b/pkg/bindings/play/play.go index bdd13d03d..2cd7c3997 100644 --- a/pkg/bindings/play/play.go +++ b/pkg/bindings/play/play.go @@ -45,7 +45,7 @@ func Kube(ctx context.Context, path string, options *KubeOptions) (*entities.Pla return nil, err } - response, err := conn.DoRequest(f, http.MethodPost, "/play/kube", params, header) + response, err := conn.DoRequest(ctx, f, http.MethodPost, "/play/kube", params, header) if err != nil { return nil, err } @@ -74,7 +74,7 @@ func KubeDown(ctx context.Context, path string) (*entities.PlayKubeReport, error logrus.Warn(err) } }() - response, err := conn.DoRequest(f, http.MethodDelete, "/play/kube", nil, nil) + response, err := conn.DoRequest(ctx, f, http.MethodDelete, "/play/kube", nil, nil) if err != nil { return nil, err } diff --git a/pkg/bindings/pods/pods.go b/pkg/bindings/pods/pods.go index 3b5832373..9e32f766d 100644 --- a/pkg/bindings/pods/pods.go +++ b/pkg/bindings/pods/pods.go @@ -29,7 +29,7 @@ func CreatePodFromSpec(ctx context.Context, spec *entities.PodSpec) (*entities.P return nil, err } stringReader := strings.NewReader(specString) - response, err := conn.DoRequest(stringReader, http.MethodPost, "/pods/create", nil, nil) + response, err := conn.DoRequest(ctx, stringReader, http.MethodPost, "/pods/create", nil, nil) if err != nil { return nil, err } @@ -44,7 +44,7 @@ func Exists(ctx context.Context, nameOrID string, options *ExistsOptions) (bool, if err != nil { return false, err } - response, err := conn.DoRequest(nil, http.MethodGet, "/pods/%s/exists", nil, nil, nameOrID) + response, err := conn.DoRequest(ctx, nil, http.MethodGet, "/pods/%s/exists", nil, nil, nameOrID) if err != nil { return false, err } @@ -66,7 +66,7 @@ func Inspect(ctx context.Context, nameOrID string, options *InspectOptions) (*en if err != nil { return nil, err } - response, err := conn.DoRequest(nil, http.MethodGet, "/pods/%s/json", nil, nil, nameOrID) + response, err := conn.DoRequest(ctx, nil, http.MethodGet, "/pods/%s/json", nil, nil, nameOrID) if err != nil { return nil, err } @@ -92,7 +92,7 @@ func Kill(ctx context.Context, nameOrID string, options *KillOptions) (*entities if err != nil { return nil, err } - response, err := conn.DoRequest(nil, http.MethodPost, "/pods/%s/kill", params, nil, nameOrID) + response, err := conn.DoRequest(ctx, nil, http.MethodPost, "/pods/%s/kill", params, nil, nameOrID) if err != nil { return nil, err } @@ -112,7 +112,7 @@ func Pause(ctx context.Context, nameOrID string, options *PauseOptions) (*entiti if err != nil { return nil, err } - response, err := conn.DoRequest(nil, http.MethodPost, "/pods/%s/pause", nil, nil, nameOrID) + response, err := conn.DoRequest(ctx, nil, http.MethodPost, "/pods/%s/pause", nil, nil, nameOrID) if err != nil { return nil, err } @@ -133,7 +133,7 @@ func Prune(ctx context.Context, options *PruneOptions) ([]*entities.PodPruneRepo if err != nil { return nil, err } - response, err := conn.DoRequest(nil, http.MethodPost, "/pods/prune", nil, nil) + response, err := conn.DoRequest(ctx, nil, http.MethodPost, "/pods/prune", nil, nil) if err != nil { return nil, err } @@ -159,7 +159,7 @@ func List(ctx context.Context, options *ListOptions) ([]*entities.ListPodsReport if err != nil { return nil, err } - response, err := conn.DoRequest(nil, http.MethodGet, "/pods/json", params, nil) + response, err := conn.DoRequest(ctx, nil, http.MethodGet, "/pods/json", params, nil) if err != nil { return podsReports, err } @@ -179,7 +179,7 @@ func Restart(ctx context.Context, nameOrID string, options *RestartOptions) (*en if err != nil { return nil, err } - response, err := conn.DoRequest(nil, http.MethodPost, "/pods/%s/restart", nil, nil, nameOrID) + response, err := conn.DoRequest(ctx, nil, http.MethodPost, "/pods/%s/restart", nil, nil, nameOrID) if err != nil { return nil, err } @@ -203,7 +203,7 @@ func Remove(ctx context.Context, nameOrID string, options *RemoveOptions) (*enti if err != nil { return nil, err } - response, err := conn.DoRequest(nil, http.MethodDelete, "/pods/%s", params, nil, nameOrID) + response, err := conn.DoRequest(ctx, nil, http.MethodDelete, "/pods/%s", params, nil, nameOrID) if err != nil { return nil, err } @@ -223,7 +223,7 @@ func Start(ctx context.Context, nameOrID string, options *StartOptions) (*entiti if err != nil { return nil, err } - response, err := conn.DoRequest(nil, http.MethodPost, "/pods/%s/start", nil, nil, nameOrID) + response, err := conn.DoRequest(ctx, nil, http.MethodPost, "/pods/%s/start", nil, nil, nameOrID) if err != nil { return nil, err } @@ -252,7 +252,7 @@ func Stop(ctx context.Context, nameOrID string, options *StopOptions) (*entities if err != nil { return nil, err } - response, err := conn.DoRequest(nil, http.MethodPost, "/pods/%s/stop", params, nil, nameOrID) + response, err := conn.DoRequest(ctx, nil, http.MethodPost, "/pods/%s/stop", params, nil, nameOrID) if err != nil { return nil, err } @@ -279,7 +279,7 @@ func Top(ctx context.Context, nameOrID string, options *TopOptions) ([]string, e if descriptors := options.GetDescriptors(); len(descriptors) > 0 { params.Set("ps_args", strings.Join(descriptors, ",")) } - response, err := conn.DoRequest(nil, http.MethodGet, "/pods/%s/top", params, nil, nameOrID) + response, err := conn.DoRequest(ctx, nil, http.MethodGet, "/pods/%s/top", params, nil, nameOrID) if err != nil { return nil, err } @@ -312,7 +312,7 @@ func Unpause(ctx context.Context, nameOrID string, options *UnpauseOptions) (*en if err != nil { return nil, err } - response, err := conn.DoRequest(nil, http.MethodPost, "/pods/%s/unpause", nil, nil, nameOrID) + response, err := conn.DoRequest(ctx, nil, http.MethodPost, "/pods/%s/unpause", nil, nil, nameOrID) if err != nil { return nil, err } @@ -339,7 +339,7 @@ func Stats(ctx context.Context, namesOrIDs []string, options *StatsOptions) ([]* } var reports []*entities.PodStatsReport - response, err := conn.DoRequest(nil, http.MethodGet, "/pods/stats", params, nil) + response, err := conn.DoRequest(ctx, nil, http.MethodGet, "/pods/stats", params, nil) if err != nil { return nil, err } diff --git a/pkg/bindings/secrets/secrets.go b/pkg/bindings/secrets/secrets.go index c439971c9..3847188a5 100644 --- a/pkg/bindings/secrets/secrets.go +++ b/pkg/bindings/secrets/secrets.go @@ -22,7 +22,7 @@ func List(ctx context.Context, options *ListOptions) ([]*entities.SecretInfoRepo if err != nil { return nil, err } - response, err := conn.DoRequest(nil, http.MethodGet, "/secrets/json", params, nil) + response, err := conn.DoRequest(ctx, nil, http.MethodGet, "/secrets/json", params, nil) if err != nil { return secrs, err } @@ -40,7 +40,7 @@ func Inspect(ctx context.Context, nameOrID string, options *InspectOptions) (*en if err != nil { return nil, err } - response, err := conn.DoRequest(nil, http.MethodGet, "/secrets/%s/json", nil, nil, nameOrID) + response, err := conn.DoRequest(ctx, nil, http.MethodGet, "/secrets/%s/json", nil, nil, nameOrID) if err != nil { return inspect, err } @@ -56,7 +56,7 @@ func Remove(ctx context.Context, nameOrID string) error { return err } - response, err := conn.DoRequest(nil, http.MethodDelete, "/secrets/%s", nil, nil, nameOrID) + response, err := conn.DoRequest(ctx, nil, http.MethodDelete, "/secrets/%s", nil, nil, nameOrID) if err != nil { return err } @@ -80,7 +80,7 @@ func Create(ctx context.Context, reader io.Reader, options *CreateOptions) (*ent return nil, err } - response, err := conn.DoRequest(reader, http.MethodPost, "/secrets/create", params, nil) + response, err := conn.DoRequest(ctx, reader, http.MethodPost, "/secrets/create", params, nil) if err != nil { return nil, err } diff --git a/pkg/bindings/system/info.go b/pkg/bindings/system/info.go index 8a307a4ca..8d7c30b26 100644 --- a/pkg/bindings/system/info.go +++ b/pkg/bindings/system/info.go @@ -14,7 +14,7 @@ func Info(ctx context.Context, _ *InfoOptions) (*define.Info, error) { if err != nil { return nil, err } - response, err := conn.DoRequest(nil, http.MethodGet, "/info", nil, nil) + response, err := conn.DoRequest(ctx, nil, http.MethodGet, "/info", nil, nil) if err != nil { return nil, err } diff --git a/pkg/bindings/system/system.go b/pkg/bindings/system/system.go index 719cde52e..3f59b3d7e 100644 --- a/pkg/bindings/system/system.go +++ b/pkg/bindings/system/system.go @@ -27,7 +27,7 @@ func Events(ctx context.Context, eventChan chan entities.Event, cancelChan chan if err != nil { return err } - response, err := conn.DoRequest(nil, http.MethodGet, "/events", params, nil) + response, err := conn.DoRequest(ctx, nil, http.MethodGet, "/events", params, nil) if err != nil { return err } @@ -73,7 +73,7 @@ func Prune(ctx context.Context, options *PruneOptions) (*entities.SystemPruneRep if err != nil { return nil, err } - response, err := conn.DoRequest(nil, http.MethodPost, "/system/prune", params, nil) + response, err := conn.DoRequest(ctx, nil, http.MethodPost, "/system/prune", params, nil) if err != nil { return nil, err } @@ -101,7 +101,7 @@ func Version(ctx context.Context, options *VersionOptions) (*entities.SystemVers if err != nil { return nil, err } - response, err := conn.DoRequest(nil, http.MethodGet, "/version", nil, nil) + response, err := conn.DoRequest(ctx, nil, http.MethodGet, "/version", nil, nil) if err != nil { return nil, err } @@ -142,7 +142,7 @@ func DiskUsage(ctx context.Context, options *DiskOptions) (*entities.SystemDfRep if err != nil { return nil, err } - response, err := conn.DoRequest(nil, http.MethodGet, "/system/df", nil, nil) + response, err := conn.DoRequest(ctx, nil, http.MethodGet, "/system/df", nil, nil) if err != nil { return nil, err } diff --git a/pkg/bindings/test/connection_test.go b/pkg/bindings/test/connection_test.go new file mode 100644 index 000000000..561cf32b5 --- /dev/null +++ b/pkg/bindings/test/connection_test.go @@ -0,0 +1,68 @@ +package test_bindings + +import ( + "context" + "time" + + "github.com/containers/podman/v3/pkg/bindings/containers" + "github.com/containers/podman/v3/pkg/bindings/system" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + "github.com/onsi/gomega/gexec" +) + +var _ = Describe("Podman connection", func() { + var ( + bt *bindingTest + s *gexec.Session + ) + + BeforeEach(func() { + bt = newBindingTest() + bt.RestoreImagesFromCache() + s = bt.startAPIService() + time.Sleep(1 * time.Second) + err := bt.NewConnection() + Expect(err).To(BeNil()) + }) + + AfterEach(func() { + s.Kill() + bt.cleanup() + }) + + It("request on cancelled context results in error", func() { + ctx, cancel := context.WithCancel(bt.conn) + cancel() + _, err := system.Version(ctx, nil) + Expect(err).To(MatchError(ctx.Err())) + }) + + It("cancel request in flight reports cancelled context", func() { + var name = "top" + _, err := bt.RunTopContainer(&name, nil) + Expect(err).To(BeNil()) + + errChan := make(chan error) + ctx, cancel := context.WithCancel(bt.conn) + + go func() { + defer close(errChan) + _, err := containers.Wait(ctx, name, nil) + errChan <- err + }() + + // Wait for the goroutine to fire the request + time.Sleep(1 * time.Second) + + cancel() + + select { + case err, ok := <-errChan: + Expect(ok).To(BeTrue()) + Expect(err).To(MatchError(ctx.Err())) + case <-time.NewTimer(1 * time.Second).C: + Fail("cancelled request did not return in less than 1 second") + } + }) +}) diff --git a/pkg/bindings/volumes/volumes.go b/pkg/bindings/volumes/volumes.go index 56cf13ade..ce5a01c49 100644 --- a/pkg/bindings/volumes/volumes.go +++ b/pkg/bindings/volumes/volumes.go @@ -29,7 +29,7 @@ func Create(ctx context.Context, config entities.VolumeCreateOptions, options *C return nil, err } stringReader := strings.NewReader(createString) - response, err := conn.DoRequest(stringReader, http.MethodPost, "/volumes/create", nil, nil) + response, err := conn.DoRequest(ctx, stringReader, http.MethodPost, "/volumes/create", nil, nil) if err != nil { return nil, err } @@ -51,7 +51,7 @@ func Inspect(ctx context.Context, nameOrID string, options *InspectOptions) (*en if err != nil { return nil, err } - response, err := conn.DoRequest(nil, http.MethodGet, "/volumes/%s/json", nil, nil, nameOrID) + response, err := conn.DoRequest(ctx, nil, http.MethodGet, "/volumes/%s/json", nil, nil, nameOrID) if err != nil { return &inspect, err } @@ -74,7 +74,7 @@ func List(ctx context.Context, options *ListOptions) ([]*entities.VolumeListRepo if err != nil { return nil, err } - response, err := conn.DoRequest(nil, http.MethodGet, "/volumes/json", params, nil) + response, err := conn.DoRequest(ctx, nil, http.MethodGet, "/volumes/json", params, nil) if err != nil { return vols, err } @@ -96,7 +96,7 @@ func Prune(ctx context.Context, options *PruneOptions) ([]*reports.PruneReport, if err != nil { return nil, err } - response, err := conn.DoRequest(nil, http.MethodPost, "/volumes/prune", params, nil) + response, err := conn.DoRequest(ctx, nil, http.MethodPost, "/volumes/prune", params, nil) if err != nil { return nil, err } @@ -116,7 +116,7 @@ func Remove(ctx context.Context, nameOrID string, options *RemoveOptions) error if err != nil { return err } - response, err := conn.DoRequest(nil, http.MethodDelete, "/volumes/%s", params, nil, nameOrID) + response, err := conn.DoRequest(ctx, nil, http.MethodDelete, "/volumes/%s", params, nil, nameOrID) if err != nil { return err } @@ -131,7 +131,7 @@ func Exists(ctx context.Context, nameOrID string, options *ExistsOptions) (bool, if err != nil { return false, err } - response, err := conn.DoRequest(nil, http.MethodGet, "/volumes/%s/exists", nil, nil, nameOrID) + response, err := conn.DoRequest(ctx, nil, http.MethodGet, "/volumes/%s/exists", nil, nil, nameOrID) if err != nil { return false, err } diff --git a/pkg/domain/entities/containers.go b/pkg/domain/entities/containers.go index 869c616ea..8b7cd62d9 100644 --- a/pkg/domain/entities/containers.go +++ b/pkg/domain/entities/containers.go @@ -190,11 +190,14 @@ type CheckpointOptions struct { PreCheckPoint bool WithPrevious bool Compression archive.Compression + PrintStats bool } type CheckpointReport struct { - Err error - Id string //nolint + Err error `json:"-"` + Id string `json:"Id` //nolint + RuntimeDuration int64 `json:"runtime_checkpoint_duration"` + CRIUStatistics *define.CRIUCheckpointRestoreStatistics `json:"criu_statistics"` } type RestoreOptions struct { @@ -211,11 +214,14 @@ type RestoreOptions struct { ImportPrevious string PublishPorts []nettypes.PortMapping Pod string + PrintStats bool } type RestoreReport struct { - Err error - Id string //nolint + Err error `json:"-"` + Id string `json:"Id` //nolint + RuntimeDuration int64 `json:"runtime_restore_duration"` + CRIUStatistics *define.CRIUCheckpointRestoreStatistics `json:"criu_statistics"` } type ContainerCreateReport struct { diff --git a/pkg/domain/entities/generate.go b/pkg/domain/entities/generate.go index 7809c5241..dfb5bfc6c 100644 --- a/pkg/domain/entities/generate.go +++ b/pkg/domain/entities/generate.go @@ -20,6 +20,8 @@ type GenerateSystemdOptions struct { Separator string // NoHeader - skip header generation NoHeader bool + // TemplateUnitFile - make use of %i and %I to differentiate between the different instances of the unit + TemplateUnitFile bool } // GenerateSystemdReport diff --git a/pkg/domain/entities/pods.go b/pkg/domain/entities/pods.go index 70d2be1e6..b255785c2 100644 --- a/pkg/domain/entities/pods.go +++ b/pkg/domain/entities/pods.go @@ -248,6 +248,8 @@ type ContainerCreateOptions struct { TTY bool Timezone string Umask string + UnsetEnv []string + UnsetEnvAll bool UIDMap []string Ulimit []string User string diff --git a/pkg/domain/infra/abi/containers.go b/pkg/domain/infra/abi/containers.go index c30129001..69c628669 100644 --- a/pkg/domain/infra/abi/containers.go +++ b/pkg/domain/infra/abi/containers.go @@ -515,6 +515,7 @@ func (ic *ContainerEngine) ContainerCheckpoint(ctx context.Context, namesOrIds [ PreCheckPoint: options.PreCheckPoint, WithPrevious: options.WithPrevious, Compression: options.Compression, + PrintStats: options.PrintStats, } if options.All { @@ -531,10 +532,12 @@ func (ic *ContainerEngine) ContainerCheckpoint(ctx context.Context, namesOrIds [ } reports := make([]*entities.CheckpointReport, 0, len(cons)) for _, con := range cons { - err = con.Checkpoint(ctx, checkOpts) + criuStatistics, runtimeCheckpointDuration, err := con.Checkpoint(ctx, checkOpts) reports = append(reports, &entities.CheckpointReport{ - Err: err, - Id: con.ID(), + Err: err, + Id: con.ID(), + RuntimeDuration: runtimeCheckpointDuration, + CRIUStatistics: criuStatistics, }) } return reports, nil @@ -557,6 +560,7 @@ func (ic *ContainerEngine) ContainerRestore(ctx context.Context, namesOrIds []st IgnoreStaticMAC: options.IgnoreStaticMAC, ImportPrevious: options.ImportPrevious, Pod: options.Pod, + PrintStats: options.PrintStats, } filterFuncs := []libpod.ContainerFilter{ @@ -579,10 +583,12 @@ func (ic *ContainerEngine) ContainerRestore(ctx context.Context, namesOrIds []st } reports := make([]*entities.RestoreReport, 0, len(cons)) for _, con := range cons { - err := con.Restore(ctx, restoreOptions) + criuStatistics, runtimeRestoreDuration, err := con.Restore(ctx, restoreOptions) reports = append(reports, &entities.RestoreReport{ - Err: err, - Id: con.ID(), + Err: err, + Id: con.ID(), + RuntimeDuration: runtimeRestoreDuration, + CRIUStatistics: criuStatistics, }) } return reports, nil diff --git a/pkg/domain/infra/tunnel/generate.go b/pkg/domain/infra/tunnel/generate.go index 9f69abb1a..3a35dd59c 100644 --- a/pkg/domain/infra/tunnel/generate.go +++ b/pkg/domain/infra/tunnel/generate.go @@ -8,7 +8,7 @@ import ( ) func (ic *ContainerEngine) GenerateSystemd(ctx context.Context, nameOrID string, opts entities.GenerateSystemdOptions) (*entities.GenerateSystemdReport, error) { - options := new(generate.SystemdOptions).WithUseName(opts.Name).WithContainerPrefix(opts.ContainerPrefix).WithNew(opts.New).WithNoHeader(opts.NoHeader) + options := new(generate.SystemdOptions).WithUseName(opts.Name).WithContainerPrefix(opts.ContainerPrefix).WithNew(opts.New).WithNoHeader(opts.NoHeader).WithTemplateUnitFile(opts.TemplateUnitFile) options.WithPodPrefix(opts.PodPrefix).WithSeparator(opts.Separator) if opts.RestartPolicy != nil { options.WithRestartPolicy(*opts.RestartPolicy) diff --git a/pkg/machine/qemu/machine.go b/pkg/machine/qemu/machine.go index a7174aac3..57c32bf74 100644 --- a/pkg/machine/qemu/machine.go +++ b/pkg/machine/qemu/machine.go @@ -664,9 +664,6 @@ func (v *MachineVM) startHostNetworking() error { return err } - // Listen on all at port 7777 for setting up and tearing - // down forwarding - listenSocket := "tcp://0.0.0.0:7777" qemuSocket, pidFile, err := v.getSocketandPid() if err != nil { return err @@ -676,7 +673,7 @@ func (v *MachineVM) startHostNetworking() error { files := []*os.File{os.Stdin, os.Stdout, os.Stderr} attr.Files = files cmd := []string{binary} - cmd = append(cmd, []string{"-listen", listenSocket, "-listen-qemu", fmt.Sprintf("unix://%s", qemuSocket), "-pid-file", pidFile}...) + cmd = append(cmd, []string{"-listen-qemu", fmt.Sprintf("unix://%s", qemuSocket), "-pid-file", pidFile}...) // Add the ssh port cmd = append(cmd, []string{"-ssh-port", fmt.Sprintf("%d", v.Port)}...) if logrus.GetLevel() == logrus.DebugLevel { diff --git a/pkg/specgen/generate/container.go b/pkg/specgen/generate/container.go index 002b4ace3..40a18a6ac 100644 --- a/pkg/specgen/generate/container.go +++ b/pkg/specgen/generate/container.go @@ -88,9 +88,6 @@ func CompleteSpec(ctx context.Context, r *libpod.Runtime, s *specgen.SpecGenerat if err != nil { return nil, errors.Wrap(err, "error parsing fields in containers.conf") } - if defaultEnvs["container"] == "" { - defaultEnvs["container"] = "podman" - } var envs map[string]string // Image Environment defaults @@ -101,9 +98,16 @@ func CompleteSpec(ctx context.Context, r *libpod.Runtime, s *specgen.SpecGenerat if err != nil { return nil, errors.Wrap(err, "Env fields from image failed to parse") } - defaultEnvs = envLib.Join(defaultEnvs, envs) + defaultEnvs = envLib.Join(envLib.DefaultEnvVariables(), envLib.Join(defaultEnvs, envs)) + } + + for _, e := range s.UnsetEnv { + delete(defaultEnvs, e) } + if s.UnsetEnvAll { + defaultEnvs = make(map[string]string) + } // First transform the os env into a map. We need it for the labels later in // any case. osEnv, err := envLib.ParseSlice(os.Environ()) diff --git a/pkg/specgen/generate/container_create.go b/pkg/specgen/generate/container_create.go index f3dc28b01..f90fef9e8 100644 --- a/pkg/specgen/generate/container_create.go +++ b/pkg/specgen/generate/container_create.go @@ -474,6 +474,7 @@ func createContainerOptions(ctx context.Context, rt *libpod.Runtime, s *specgen. UID: s.UID, GID: s.GID, Mode: s.Mode, + Target: s.Target, }) } options = append(options, libpod.WithSecrets(secrs)) diff --git a/pkg/specgen/generate/oci.go b/pkg/specgen/generate/oci.go index beccd9fc2..1b022b912 100644 --- a/pkg/specgen/generate/oci.go +++ b/pkg/specgen/generate/oci.go @@ -298,7 +298,6 @@ func SpecGenToOCI(ctx context.Context, s *specgen.SpecGenerator, rt *libpod.Runt for key, val := range s.Annotations { g.AddAnnotation(key, val) } - g.AddProcessEnv("container", "podman") g.Config.Linux.Resources = s.ResourceLimits // Devices @@ -332,6 +331,7 @@ func SpecGenToOCI(ctx context.Context, s *specgen.SpecGenerator, rt *libpod.Runt BlockAccessToKernelFilesystems(s.Privileged, s.PidNS.IsHost(), s.Mask, s.Unmask, &g) + g.ClearProcessEnv() for name, val := range s.Env { g.AddProcessEnv(name, val) } diff --git a/pkg/specgen/specgen.go b/pkg/specgen/specgen.go index d777287d7..0e257ad4c 100644 --- a/pkg/specgen/specgen.go +++ b/pkg/specgen/specgen.go @@ -194,6 +194,13 @@ type ContainerBasicConfig struct { // The execution domain system allows Linux to provide limited support // for binaries compiled under other UNIX-like operating systems. Personality *spec.LinuxPersonality `json:"personality,omitempty"` + // UnsetEnv unsets the specified default environment variables from the image or from buildin or containers.conf + // Optional. + UnsetEnv []string `json:"unsetenv,omitempty"` + // UnsetEnvAll unsetall default environment variables from the image or from buildin or containers.conf + // UnsetEnvAll unsets all default environment variables from the image or from buildin + // Optional. + UnsetEnvAll bool `json:"unsetenvall,omitempty"` } // ContainerStorageConfig contains information on the storage configuration of a @@ -540,6 +547,7 @@ func (s *SpecGenerator) GetImage() (*libimage.Image, string) { type Secret struct { Source string + Target string UID uint32 GID uint32 Mode uint32 diff --git a/pkg/specgenutil/specgen.go b/pkg/specgenutil/specgen.go index 04d3add32..c110b9e97 100644 --- a/pkg/specgenutil/specgen.go +++ b/pkg/specgenutil/specgen.go @@ -711,6 +711,8 @@ func FillOutSpecGen(s *specgen.SpecGenerator, c *entities.ContainerCreateOptions s.Umask = c.Umask s.PidFile = c.PidFile s.Volatile = c.Rm + s.UnsetEnv = c.UnsetEnv + s.UnsetEnvAll = c.UnsetEnvAll // Initcontainers s.InitContainerType = c.InitContainerType @@ -874,6 +876,7 @@ func parseSecrets(secrets []string) ([]specgen.Secret, map[string]string, error) if len(split) == 1 { mountSecret := specgen.Secret{ Source: val, + Target: target, UID: uid, GID: gid, Mode: mode, @@ -939,11 +942,9 @@ func parseSecrets(secrets []string) ([]specgen.Secret, map[string]string, error) return nil, nil, errors.Wrapf(secretParseError, "no source found %s", val) } if secretType == "mount" { - if target != "" { - return nil, nil, errors.Wrapf(secretParseError, "target option is invalid for mounted secrets") - } mountSecret := specgen.Secret{ Source: source, + Target: target, UID: uid, GID: gid, Mode: mode, diff --git a/pkg/systemd/generate/common.go b/pkg/systemd/generate/common.go index 3515bb3b7..24c85a27e 100644 --- a/pkg/systemd/generate/common.go +++ b/pkg/systemd/generate/common.go @@ -23,7 +23,7 @@ func validateRestartPolicy(restart string) error { return errors.Errorf("%s is not a valid restart policy", restart) } -const headerTemplate = `# {{{{.ServiceName}}}}.service +const headerTemplate = `# {{{{.ServiceName}}}}{{{{- if (eq .IdentifySpecifier true) }}}}@{{{{- end}}}}.service {{{{- if (eq .GenerateNoHeader false) }}}} # autogenerated by Podman {{{{.PodmanVersion}}}} {{{{- if .TimeStamp}}}} @@ -32,7 +32,7 @@ const headerTemplate = `# {{{{.ServiceName}}}}.service {{{{- end}}}} [Unit] -Description=Podman {{{{.ServiceName}}}}.service +Description=Podman {{{{.ServiceName}}}}.service{{{{- if (eq .IdentifySpecifier true) }}}} for %I{{{{- end}}}} Documentation=man:podman-generate-systemd(1) Wants=network-online.target After=network-online.target diff --git a/pkg/systemd/generate/containers.go b/pkg/systemd/generate/containers.go index 037652a6d..95ff13371 100644 --- a/pkg/systemd/generate/containers.go +++ b/pkg/systemd/generate/containers.go @@ -90,6 +90,8 @@ type containerInfo struct { // Location of the RunRoot for the container. Required for ensuring the tmpfs // or volume exists and is mounted when coming online at boot. RunRoot string + // Add %i and %I to description and execute parts + IdentifySpecifier bool } const containerTemplate = headerTemplate + ` @@ -99,7 +101,7 @@ After={{{{- range $index, $value := .BoundToServices -}}}}{{{{if $index}}}} {{{{ {{{{- end}}}} [Service] -Environment={{{{.EnvVariable}}}}=%n +Environment={{{{.EnvVariable}}}}=%n{{{{- if (eq .IdentifySpecifier true) }}}}-%i{{{{- end}}}} {{{{- if .ExtraEnvs}}}} Environment={{{{- range $index, $value := .ExtraEnvs -}}}}{{{{if $index}}}} {{{{end}}}}{{{{ $value }}}}{{{{end}}}} {{{{- end}}}} @@ -204,6 +206,46 @@ func containerServiceName(ctr *libpod.Container, options entities.GenerateSystem return nameOrID, serviceName } +// setContainerNameForTemplate updates startCommand to contain the name argument with +// a value that includes the identify specifier. +// In case startCommand doesn't contain that argument it's added after "run" and its +// value will be set to info.ServiceName concated with the identify specifier %i. +func setContainerNameForTemplate(startCommand []string, info *containerInfo) ([]string, error) { + // find the index of "--name" in the command slice + nameIx := -1 + for argIx, arg := range startCommand { + if arg == "--name" { + nameIx = argIx + 1 + break + } + if strings.HasPrefix(arg, "--name=") { + nameIx = argIx + break + } + } + switch { + case nameIx == -1: + // if not found, add --name argument in the command slice before the "run" argument. + // it's assumed that the command slice contains this argument. + runIx := -1 + for argIx, arg := range startCommand { + if arg == "run" { + runIx = argIx + break + } + } + if runIx == -1 { + return startCommand, fmt.Errorf("\"run\" is missing in the command arguments") + } + startCommand = append(startCommand[:runIx+1], startCommand[runIx:]...) + startCommand[runIx+1] = fmt.Sprintf("--name=%s-%%i", info.ServiceName) + default: + // append the identity specifier (%i) to the end of the --name value + startCommand[nameIx] = fmt.Sprintf("%s-%%i", startCommand[nameIx]) + } + return startCommand, nil +} + // executeContainerTemplate executes the container template on the specified // containerInfo. Note that the containerInfo is also post processed and // completed, which allows for an easier unit testing. @@ -273,7 +315,6 @@ func executeContainerTemplate(info *containerInfo, options entities.GenerateSyst "--rm", ) remainingCmd := info.CreateCommand[index:] - // Presence check for certain flags/options. fs := pflag.NewFlagSet("args", pflag.ContinueOnError) fs.ParseErrorsWhitelist.UnknownFlags = true @@ -389,6 +430,13 @@ func executeContainerTemplate(info *containerInfo, options entities.GenerateSyst startCommand = append(startCommand, remainingCmd...) startCommand = escapeSystemdArguments(startCommand) + if options.TemplateUnitFile { + info.IdentifySpecifier = true + startCommand, err = setContainerNameForTemplate(startCommand, info) + if err != nil { + return "", err + } + } info.ExecStart = strings.Join(startCommand, " ") } diff --git a/pkg/systemd/generate/containers_test.go b/pkg/systemd/generate/containers_test.go index f46513459..eab2c2e67 100644 --- a/pkg/systemd/generate/containers_test.go +++ b/pkg/systemd/generate/containers_test.go @@ -1,6 +1,7 @@ package generate import ( + "fmt" "testing" "github.com/containers/podman/v3/pkg/domain/entities" @@ -522,6 +523,32 @@ NotifyAccess=all [Install] WantedBy=multi-user.target default.target ` + + templateGood := `# container-foo@.service +# autogenerated by Podman CI + +[Unit] +Description=Podman container-foo.service for %I +Documentation=man:podman-generate-systemd(1) +Wants=network-online.target +After=network-online.target +RequiresMountsFor=/var/run/containers/storage + +[Service] +Environment=PODMAN_SYSTEMD_UNIT=%n-%i +Restart=on-failure +StartLimitBurst=42 +TimeoutStopSec=70 +ExecStartPre=/bin/rm -f %t/%n.ctr-id +ExecStart=/usr/bin/podman run --name=container-foo-%i --cidfile=%t/%n.ctr-id --cgroups=no-conmon --rm --sdnotify=conmon -d awesome-image:latest +ExecStop=/usr/bin/podman stop --ignore --cidfile=%t/%n.ctr-id +ExecStopPost=/usr/bin/podman rm -f --ignore --cidfile=%t/%n.ctr-id +Type=notify +NotifyAccess=all + +[Install] +WantedBy=multi-user.target default.target +` tests := []struct { name string info containerInfo @@ -529,6 +556,7 @@ WantedBy=multi-user.target default.target new bool noHeader bool wantErr bool + template bool }{ {"good with id", @@ -547,6 +575,7 @@ WantedBy=multi-user.target default.target false, false, false, + false, }, {"good with noHeader", containerInfo{ @@ -564,6 +593,7 @@ WantedBy=multi-user.target default.target false, true, false, + false, }, {"good with name", containerInfo{ @@ -581,6 +611,7 @@ WantedBy=multi-user.target default.target false, false, false, + false, }, {"good with name and bound to", containerInfo{ @@ -599,6 +630,7 @@ WantedBy=multi-user.target default.target false, false, false, + false, }, {"good with name and generic", containerInfo{ @@ -617,6 +649,7 @@ WantedBy=multi-user.target default.target true, false, false, + false, }, {"good with name and sdnotify", containerInfo{ @@ -635,6 +668,7 @@ WantedBy=multi-user.target default.target true, false, false, + false, }, {"good with explicit short detach param", containerInfo{ @@ -653,6 +687,7 @@ WantedBy=multi-user.target default.target true, false, false, + false, }, {"good with explicit short detach param and podInfo", containerInfo{ @@ -674,6 +709,7 @@ WantedBy=multi-user.target default.target true, false, false, + false, }, {"good with explicit full detach param", containerInfo{ @@ -692,6 +728,7 @@ WantedBy=multi-user.target default.target true, false, false, + false, }, {"good with id and no param", containerInfo{ @@ -710,6 +747,7 @@ WantedBy=multi-user.target default.target true, false, false, + false, }, {"good with explicit detach=true param", containerInfo{ @@ -728,6 +766,7 @@ WantedBy=multi-user.target default.target true, false, false, + false, }, {"good with explicit detach=false param", containerInfo{ @@ -746,6 +785,7 @@ WantedBy=multi-user.target default.target true, false, false, + false, }, {"good with explicit detach=false param", containerInfo{ @@ -764,6 +804,7 @@ WantedBy=multi-user.target default.target true, false, false, + false, }, {"good with multiple detach=false params", containerInfo{ @@ -782,6 +823,7 @@ WantedBy=multi-user.target default.target true, false, false, + false, }, {"good with multiple shorthand params detach first", containerInfo{ @@ -800,6 +842,7 @@ WantedBy=multi-user.target default.target true, false, false, + false, }, {"good with multiple shorthand params detach last", containerInfo{ @@ -818,6 +861,7 @@ WantedBy=multi-user.target default.target true, false, false, + false, }, {"good with container create", containerInfo{ @@ -836,6 +880,7 @@ WantedBy=multi-user.target default.target true, false, false, + false, }, {"good with journald log tag (see #9034)", containerInfo{ @@ -854,6 +899,7 @@ WantedBy=multi-user.target default.target true, false, false, + false, }, {"good with special chars", containerInfo{ @@ -872,6 +918,7 @@ WantedBy=multi-user.target default.target true, false, false, + false, }, {"good with ID files", containerInfo{ @@ -890,6 +937,7 @@ WantedBy=multi-user.target default.target true, false, false, + false, }, {"good with pod ID files", containerInfo{ @@ -911,6 +959,7 @@ WantedBy=multi-user.target default.target true, false, false, + false, }, {"good with environment variables", containerInfo{ @@ -930,6 +979,7 @@ WantedBy=multi-user.target default.target true, false, false, + false, }, {"good with restart policy", containerInfo{ @@ -948,14 +998,34 @@ WantedBy=multi-user.target default.target true, false, false, + false, + }, + {"good template", + containerInfo{ + Executable: "/usr/bin/podman", + ServiceName: "container-foo", + ContainerNameOrID: "foo", + PIDFile: "/run/containers/storage/overlay-containers/639c53578af4d84b8800b4635fa4e680ee80fd67e0e6a2d4eea48d1e3230f401/userdata/conmon.pid", + StopTimeout: 10, + PodmanVersion: "CI", + GraphRoot: "/var/lib/containers/storage", + RunRoot: "/var/run/containers/storage", + CreateCommand: []string{"I'll get stripped", "create", "--restart", "on-failure:42", "awesome-image:latest"}, + }, + templateGood, + true, + false, + false, + true, }, } for _, tt := range tests { test := tt t.Run(tt.name, func(t *testing.T) { opts := entities.GenerateSystemdOptions{ - New: test.new, - NoHeader: test.noHeader, + New: test.new, + NoHeader: test.noHeader, + TemplateUnitFile: test.template, } test.info.RestartPolicy = define.DefaultRestartPolicy got, err := executeContainerTemplate(&test.info, opts) @@ -967,3 +1037,48 @@ WantedBy=multi-user.target default.target }) } } + +func TestSetContainerNameForTemplate(t *testing.T) { + tt := []struct { + name string + startCommand []string + info *containerInfo + expected []string + err error + }{ + { + name: "no name argument is set", + startCommand: []string{"/usr/bin/podman", "run", "busybox", "top"}, + info: &containerInfo{ServiceName: "container-122"}, + expected: []string{"/usr/bin/podman", "run", "--name=container-122-%i", "busybox", "top"}, + err: nil, + }, + { + name: "--name=value is used in arguments", + startCommand: []string{"/usr/bin/podman", "run", "--name=lovely_james", "busybox", "top"}, + info: &containerInfo{}, + expected: []string{"/usr/bin/podman", "run", "--name=lovely_james-%i", "busybox", "top"}, + err: nil, + }, + { + name: "--name value is used in arguments", + startCommand: []string{"/usr/bin/podman", "run", "--name", "lovely_james", "busybox", "top"}, + info: &containerInfo{}, + expected: []string{"/usr/bin/podman", "run", "--name", "lovely_james-%i", "busybox", "top"}, + err: nil, + }, + { + name: "--name value is used in arguments", + startCommand: []string{"/usr/bin/podman", "create", "busybox", "top"}, + info: &containerInfo{}, + expected: []string{"/usr/bin/podman", "create", "busybox", "top"}, + err: fmt.Errorf("\"run\" is missing in the command arguments"), + }, + } + + for _, te := range tt { + res, err := setContainerNameForTemplate(te.startCommand, te.info) + assert.Equal(t, te.err, err) + assert.Equal(t, te.expected, res) + } +} diff --git a/pkg/systemd/generate/pods.go b/pkg/systemd/generate/pods.go index e755b8eea..38f7e8e3e 100644 --- a/pkg/systemd/generate/pods.go +++ b/pkg/systemd/generate/pods.go @@ -79,6 +79,8 @@ type podInfo struct { // Location of the RunRoot for the pod. Required for ensuring the tmpfs // or volume exists and is mounted when coming online at boot. RunRoot string + // Add %i and %I to description and execute parts - this should not be used + IdentifySpecifier bool } const podTemplate = headerTemplate + `Requires={{{{- range $index, $value := .RequiredServices -}}}}{{{{if $index}}}} {{{{end}}}}{{{{ $value }}}}.service{{{{end}}}} @@ -108,6 +110,9 @@ WantedBy=multi-user.target default.target // Based on the options, the return value might be the content of all units or // the files they been written to. func PodUnits(pod *libpod.Pod, options entities.GenerateSystemdOptions) (map[string]string, error) { + if options.TemplateUnitFile { + return nil, errors.New("--template is not supported for pods") + } // Error out if the pod has no infra container, which we require to be the // main service. if !pod.HasInfraContainer() { diff --git a/test/e2e/checkpoint_test.go b/test/e2e/checkpoint_test.go index be6b782b5..6b294802d 100644 --- a/test/e2e/checkpoint_test.go +++ b/test/e2e/checkpoint_test.go @@ -1,6 +1,7 @@ package integration import ( + "encoding/json" "fmt" "net" "os" @@ -12,6 +13,7 @@ import ( "github.com/checkpoint-restore/go-criu/v5/stats" "github.com/containers/podman/v3/pkg/checkpoint/crutils" "github.com/containers/podman/v3/pkg/criu" + "github.com/containers/podman/v3/pkg/domain/entities" . "github.com/containers/podman/v3/test/utils" "github.com/containers/podman/v3/utils" . "github.com/onsi/ginkgo" @@ -1244,4 +1246,97 @@ var _ = Describe("Podman checkpoint", func() { // Remove exported checkpoint os.Remove(fileName) }) + + It("podman checkpoint and restore containers with --print-stats", func() { + session1 := podmanTest.Podman(getRunString([]string{redis})) + session1.WaitWithDefaultTimeout() + Expect(session1).Should(Exit(0)) + + session2 := podmanTest.Podman(getRunString([]string{redis, "top"})) + session2.WaitWithDefaultTimeout() + Expect(session2).Should(Exit(0)) + + result := podmanTest.Podman([]string{ + "container", + "checkpoint", + "-a", + "--print-stats", + }) + result.WaitWithDefaultTimeout() + + Expect(result).Should(Exit(0)) + Expect(podmanTest.NumberOfContainersRunning()).To(Equal(0)) + + type checkpointStatistics struct { + PodmanDuration int64 `json:"podman_checkpoint_duration"` + ContainerStatistics []*entities.CheckpointReport `json:"container_statistics"` + } + + cS := new(checkpointStatistics) + err := json.Unmarshal([]byte(result.OutputToString()), cS) + Expect(err).ShouldNot(HaveOccurred()) + + Expect(len(cS.ContainerStatistics)).To(Equal(2)) + Expect(cS.PodmanDuration).To(BeNumerically(">", cS.ContainerStatistics[0].RuntimeDuration)) + Expect(cS.PodmanDuration).To(BeNumerically(">", cS.ContainerStatistics[1].RuntimeDuration)) + Expect(cS.ContainerStatistics[0].RuntimeDuration).To( + BeNumerically(">", cS.ContainerStatistics[0].CRIUStatistics.FrozenTime), + ) + Expect(cS.ContainerStatistics[1].RuntimeDuration).To( + BeNumerically(">", cS.ContainerStatistics[1].CRIUStatistics.FrozenTime), + ) + + ps := podmanTest.Podman([]string{ + "ps", + "-q", + "--no-trunc", + }) + ps.WaitWithDefaultTimeout() + Expect(ps).Should(Exit(0)) + Expect(ps.LineInOutputContains(session1.OutputToString())).To(BeFalse()) + Expect(ps.LineInOutputContains(session2.OutputToString())).To(BeFalse()) + + result = podmanTest.Podman([]string{ + "container", + "restore", + "-a", + "--print-stats", + }) + result.WaitWithDefaultTimeout() + + Expect(result).Should(Exit(0)) + Expect(podmanTest.NumberOfContainersRunning()).To(Equal(2)) + Expect(podmanTest.GetContainerStatus()).To(ContainSubstring("Up")) + Expect(podmanTest.GetContainerStatus()).To(Not(ContainSubstring("Exited"))) + + type restoreStatistics struct { + PodmanDuration int64 `json:"podman_restore_duration"` + ContainerStatistics []*entities.RestoreReport `json:"container_statistics"` + } + + rS := new(restoreStatistics) + err = json.Unmarshal([]byte(result.OutputToString()), rS) + Expect(err).ShouldNot(HaveOccurred()) + + Expect(len(cS.ContainerStatistics)).To(Equal(2)) + Expect(cS.PodmanDuration).To(BeNumerically(">", cS.ContainerStatistics[0].RuntimeDuration)) + Expect(cS.PodmanDuration).To(BeNumerically(">", cS.ContainerStatistics[1].RuntimeDuration)) + Expect(cS.ContainerStatistics[0].RuntimeDuration).To( + BeNumerically(">", cS.ContainerStatistics[0].CRIUStatistics.RestoreTime), + ) + Expect(cS.ContainerStatistics[1].RuntimeDuration).To( + BeNumerically(">", cS.ContainerStatistics[1].CRIUStatistics.RestoreTime), + ) + + result = podmanTest.Podman([]string{ + "rm", + "-t", + "0", + "-fa", + }) + result.WaitWithDefaultTimeout() + Expect(result).Should(Exit(0)) + Expect(podmanTest.NumberOfContainersRunning()).To(Equal(0)) + }) + }) diff --git a/test/e2e/run_test.go b/test/e2e/run_test.go index ed2d8938d..d6d729d3a 100644 --- a/test/e2e/run_test.go +++ b/test/e2e/run_test.go @@ -1723,6 +1723,50 @@ WORKDIR /madethis`, BB) }) + It("podman run --secret source=mysecret,type=mount with target", func() { + secretsString := "somesecretdata" + secretFilePath := filepath.Join(podmanTest.TempDir, "secret") + err := ioutil.WriteFile(secretFilePath, []byte(secretsString), 0755) + Expect(err).To(BeNil()) + + session := podmanTest.Podman([]string{"secret", "create", "mysecret_target", secretFilePath}) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + + session = podmanTest.Podman([]string{"run", "--secret", "source=mysecret_target,type=mount,target=hello", "--name", "secr_target", ALPINE, "cat", "/run/secrets/hello"}) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + Expect(session.OutputToString()).To(Equal(secretsString)) + + session = podmanTest.Podman([]string{"inspect", "secr_target", "--format", " {{(index .Config.Secrets 0).Name}}"}) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + Expect(session.OutputToString()).To(ContainSubstring("mysecret_target")) + + }) + + It("podman run --secret source=mysecret,type=mount with target at /tmp", func() { + secretsString := "somesecretdata" + secretFilePath := filepath.Join(podmanTest.TempDir, "secret") + err := ioutil.WriteFile(secretFilePath, []byte(secretsString), 0755) + Expect(err).To(BeNil()) + + session := podmanTest.Podman([]string{"secret", "create", "mysecret_target2", secretFilePath}) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + + session = podmanTest.Podman([]string{"run", "--secret", "source=mysecret_target2,type=mount,target=/tmp/hello", "--name", "secr_target2", ALPINE, "cat", "/tmp/hello"}) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + Expect(session.OutputToString()).To(Equal(secretsString)) + + session = podmanTest.Podman([]string{"inspect", "secr_target2", "--format", " {{(index .Config.Secrets 0).Name}}"}) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + Expect(session.OutputToString()).To(ContainSubstring("mysecret_target2")) + + }) + It("podman run --secret source=mysecret,type=env", func() { secretsString := "somesecretdata" secretFilePath := filepath.Join(podmanTest.TempDir, "secret") @@ -1748,10 +1792,6 @@ WORKDIR /madethis`, BB) session := podmanTest.Podman([]string{"secret", "create", "mysecret", secretFilePath}) session.WaitWithDefaultTimeout() Expect(session).Should(Exit(0)) - // target with mount type should fail - session = podmanTest.Podman([]string{"run", "--secret", "source=mysecret,type=mount,target=anotherplace", "--name", "secr", ALPINE, "cat", "/run/secrets/mysecret"}) - session.WaitWithDefaultTimeout() - Expect(session).To(ExitWithError()) session = podmanTest.Podman([]string{"run", "--secret", "source=mysecret,type=env,target=anotherplace", "--name", "secr", ALPINE, "printenv", "anotherplace"}) session.WaitWithDefaultTimeout() diff --git a/test/system/030-run.bats b/test/system/030-run.bats index 2c8d08b99..ba21cd21d 100644 --- a/test/system/030-run.bats +++ b/test/system/030-run.bats @@ -736,4 +736,26 @@ EOF is "$output" "$random_1" "output matches STDIN" } +@test "podman run defaultenv" { + run_podman run --rm $IMAGE printenv + is "$output" ".*TERM=xterm" "output matches TERM" + is "$output" ".*container=podman" "output matches container=podman" + + run_podman run --unsetenv=TERM --rm $IMAGE printenv + is "$output" ".*container=podman" "output matches container=podman" + run grep TERM <<<$output + is "$output" "" "unwanted TERM environment variable despite --unsetenv=TERM" + + run_podman run --unsetenv-all --rm $IMAGE /bin/printenv + run grep TERM <<<$output + is "$output" "" "unwanted TERM environment variable despite --unsetenv-all" + run grep container <<<$output + is "$output" "" "unwanted container environment variable despite --unsetenv-all" + run grep PATH <<<$output + is "$output" "" "unwanted PATH environment variable despite --unsetenv-all" + + run_podman run --unsetenv-all --env TERM=abc --rm $IMAGE /bin/printenv + is "$output" ".*TERM=abc" "missing TERM environment variable despite TERM being set on commandline" +} + # vim: filetype=sh diff --git a/test/system/035-logs.bats b/test/system/035-logs.bats index 7fb3e62e4..44984eaad 100644 --- a/test/system/035-logs.bats +++ b/test/system/035-logs.bats @@ -89,6 +89,27 @@ ${cid[0]} d" "Sequential output from logs" _log_test_multi journald } +function _log_test_restarted() { + run_podman run --log-driver=$1 --name logtest $IMAGE sh -c 'start=0; if test -s log; then start=`tail -n 1 log`; fi; seq `expr $start + 1` `expr $start + 10` | tee -a log' + run_podman start -a logtest + logfile=$(mktemp -p ${PODMAN_TMPDIR} logfileXXXXXXXX) + $PODMAN $_PODMAN_TEST_OPTS logs -f logtest > $logfile + expected=$(mktemp -p ${PODMAN_TMPDIR} expectedXXXXXXXX) + seq 1 20 > $expected + diff -u ${expected} ${logfile} +} + +@test "podman logs restarted - k8s-file" { + _log_test_restarted k8s-file +} + +@test "podman logs restarted journald" { + # We can't use journald on RHEL as rootless: rhbz#1895105 + skip_if_journald_unavailable + + _log_test_restarted journald +} + @test "podman logs - journald log driver requires journald events backend" { skip_if_remote "remote does not support --events-backend" # We can't use journald on RHEL as rootless: rhbz#1895105 diff --git a/test/system/090-events.bats b/test/system/090-events.bats index 1fb542ccd..5af6a3793 100644 --- a/test/system/090-events.bats +++ b/test/system/090-events.bats @@ -102,6 +102,17 @@ function _events_disjunctive_filters() { _events_disjunctive_filters --events-backend=journald } +@test "events with file backend and journald logdriver with --follow failure" { + skip_if_remote "remote does not support --events-backend" + skip_if_journald_unavailable "system does not support journald events" + run_podman --events-backend=file run --log-driver=journald --name=test $IMAGE echo hi + is "$output" "hi" "Should support events-backend=file" + + run_podman 125 --events-backend=file logs --follow test + is "$output" "Error: using --follow with the journald --log-driver but without the journald --events-backend (file) is not supported" "Should fail with reasonable error message when events-backend and events-logger do not match" + +} + @test "events with disjunctive filters - default" { _events_disjunctive_filters "" } diff --git a/test/system/250-systemd.bats b/test/system/250-systemd.bats index 98241c309..e997ab6f9 100644 --- a/test/system/250-systemd.bats +++ b/test/system/250-systemd.bats @@ -9,6 +9,7 @@ load helpers.systemd SERVICE_NAME="podman_test_$(random_string)" UNIT_FILE="$UNIT_DIR/$SERVICE_NAME.service" +TEMPLATE_FILE_PREFIX="$UNIT_DIR/$SERVICE_NAME" function setup() { skip_if_remote "systemd tests are meaningless over remote" @@ -173,10 +174,14 @@ function check_listen_env() { if is_remote; then is "$output" "$stdenv" "LISTEN Environment did not pass: $context" else - is "$output" "$stdenv + out=$(for o in $output; do echo $o; done| sort) + std=$(echo "$stdenv LISTEN_PID=1 LISTEN_FDS=1 -LISTEN_FDNAMES=listen_fdnames" "LISTEN Environment passed: $context" +LISTEN_FDNAMES=listen_fdnames" | sort) + echo "<$out>" + echo "<$std>" + is "$out" "$std" "LISTEN Environment passed: $context" fi } @@ -201,4 +206,62 @@ LISTEN_FDNAMES=listen_fdnames" "LISTEN Environment passed: $context" check_listen_env "$stdenv" "podman start" } +@test "podman generate - systemd template" { + cname=$(random_string) + run_podman create --name $cname $IMAGE top + + run_podman generate systemd --template -n $cname + echo "$output" > "$TEMPLATE_FILE_PREFIX@.service" + run_podman rm -f $cname + + systemctl daemon-reload + + INSTANCE="$SERVICE_NAME@1.service" + run systemctl start "$INSTANCE" + if [ $status -ne 0 ]; then + die "Error starting systemd unit $INSTANCE, output: $output" + fi + + run systemctl status "$INSTANCE" + if [ $status -ne 0 ]; then + die "Non-zero status of systemd unit $INSTANCE, output: $output" + fi + + run systemctl stop "$INSTANCE" + if [ $status -ne 0 ]; then + die "Error stopping systemd unit $INSTANCE, output: $output" + fi + + if [[ -z "$status" ]]; then + run systemctl is-active "$INSTANCE" + if [ $status -ne 0 ]; then + die "Error checking stauts of systemd unit $INSTANCE, output: $output" + fi + is "$output" "$status" "$INSTANCE not in expected state" + fi + + rm -f "$TEMPLATE_FILE_PREFIX@.service" + systemctl daemon-reload +} + +@test "podman generate - systemd template no support for pod" { + cname=$(random_string) + podname=$(random_string) + run_podman pod create --name $podname + run_podman run --pod $podname -dt --name $cname $IMAGE top + + run_podman 125 generate systemd --new --template -n $podname + is "$output" ".*--template is not supported for pods.*" "Error message contains 'not supported'" + + run_podman rm -f $cname + run_podman pod rm -f $podname +} + +@test "podman generate - systemd template only used on --new" { + cname=$(random_string) + run_podman create --name $cname $IMAGE top + run_podman 125 generate systemd --new=false --template -n $cname + is "$output" ".*--template cannot be set" "Error message should be '--template requires --new'" +} + # vim: filetype=sh diff --git a/test/system/500-networking.bats b/test/system/500-networking.bats index 21350ed36..deadfa90a 100644 --- a/test/system/500-networking.bats +++ b/test/system/500-networking.bats @@ -172,7 +172,7 @@ load helpers # FIXME: debugging for #11871 run_podman exec $cid cat /etc/resolv.conf - if is_rootless; then + if is_rootless && ! is_remote; then run_podman unshare --rootless-cni cat /etc/resolv.conf fi ps uxww |