diff options
120 files changed, 2111 insertions, 883 deletions
@@ -186,10 +186,6 @@ ifdef HOMEBREW_PREFIX endif endif -# For building pause/pause.c -GCC ?= gcc -PAUSE_CFLAGS = -Os -static -Wall -Werror -DVERSION=v$(RELEASE_VERSION) - ### ### Primary entry-point targets ### @@ -201,7 +197,7 @@ default: all all: binaries docs .PHONY: binaries -binaries: podman podman-remote rootlessport pause +binaries: podman podman-remote rootlessport ## Build podman, podman-remote and rootlessport binaries # Extract text following double-# for targets, as their description for # the `help` target. Otherwise These simple-substitutions are resolved @@ -379,12 +375,6 @@ bin/rootlessport: .gopathok $(SOURCES) go.mod go.sum .PHONY: rootlessport rootlessport: bin/rootlessport -bin/pause: pause/pause.c - $(GCC) $(PAUSE_CFLAGS) pause/pause.c -o bin/pause - -.PHONY: pause -pause: bin/pause - ### ### Secondary binary-build targets ### @@ -744,7 +734,7 @@ install.remote-nobuild: install.remote: podman-remote install.remote-nobuild .PHONY: install.bin-nobuild -install.bin-nobuild: install.pause +install.bin-nobuild: install ${SELINUXOPT} -d -m 755 $(DESTDIR)$(BINDIR) install ${SELINUXOPT} -m 755 bin/podman $(DESTDIR)$(BINDIR)/podman test -z "${SELINUXOPT}" || chcon --verbose --reference=$(DESTDIR)$(BINDIR)/podman bin/podman @@ -798,10 +788,8 @@ install.docker-docs-nobuild: .PHONY: install.docker-docs install.docker-docs: docker-docs install.docker-docs-nobuild -.PHONY: install.pause -install.pause: pause - install ${SELINUXOPT} -m 755 -d $(DESTDIR)$(LIBEXECPODMAN)/pause - install ${SELINUXOPT} -m 755 bin/pause $(DESTDIR)$(LIBEXECPODMAN)/pause/pause +.PHONY: install.docker-full +install.docker-full: install.docker install.docker-docs .PHONY: install.systemd ifneq (,$(findstring systemd,$(BUILDTAGS))) @@ -832,9 +820,6 @@ else install.systemd: endif -.PHONY: install.pause -install.pause: pause - .PHONY: install.tools install.tools: .install.goimports .install.gitvalidation .install.md2man .install.ginkgo .install.golangci-lint .install.bats ## Install needed tools 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..e8dd25978 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, @@ -49,6 +55,7 @@ func init() { flags.BoolVarP(&checkpointOptions.Keep, "keep", "k", false, "Keep all temporary checkpoint files") flags.BoolVarP(&checkpointOptions.LeaveRunning, "leave-running", "R", false, "Leave the container running after writing checkpoint to disk") flags.BoolVar(&checkpointOptions.TCPEstablished, "tcp-established", false, "Checkpoint a container with established TCP connections") + flags.BoolVar(&checkpointOptions.FileLocks, "file-locks", false, "Checkpoint a container with file locks") flags.BoolVarP(&checkpointOptions.All, "all", "a", false, "Checkpoint all running containers") exportFlagName := "export" @@ -63,11 +70,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 +117,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..cf0ad5f80 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" @@ -11,7 +12,6 @@ import ( "github.com/containers/podman/v3/cmd/podman/validate" "github.com/containers/podman/v3/pkg/domain/entities" "github.com/containers/podman/v3/pkg/rootless" - "github.com/containers/podman/v3/pkg/specgenutil" "github.com/pkg/errors" "github.com/spf13/cobra" ) @@ -39,6 +39,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, @@ -48,6 +53,7 @@ func init() { flags.BoolVarP(&restoreOptions.All, "all", "a", false, "Restore all checkpointed containers") flags.BoolVarP(&restoreOptions.Keep, "keep", "k", false, "Keep all temporary checkpoint files") flags.BoolVar(&restoreOptions.TCPEstablished, "tcp-established", false, "Restore a container with established TCP connections") + flags.BoolVar(&restoreOptions.FileLocks, "file-locks", false, "Restore a container with file locks") importFlagName := "import" flags.StringVarP(&restoreOptions.Import, importFlagName, "i", "", "Restore from exported checkpoint archive (tar.gz)") @@ -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") } @@ -106,12 +120,7 @@ func restore(cmd *cobra.Command, args []string) error { if err != nil { return err } - if len(inputPorts) > 0 { - restoreOptions.PublishPorts, err = specgenutil.CreatePortBindings(inputPorts) - if err != nil { - return err - } - } + restoreOptions.PublishPorts = inputPorts argLen := len(args) if restoreOptions.Import != "" { @@ -132,12 +141,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/cmd/podman/root.go b/cmd/podman/root.go index 418a70e1e..9e4c8d24d 100644 --- a/cmd/podman/root.go +++ b/cmd/podman/root.go @@ -163,20 +163,6 @@ func persistentPreRunE(cmd *cobra.Command, args []string) error { return err } - for _, env := range cfg.Engine.Env { - splitEnv := strings.SplitN(env, "=", 2) - if len(splitEnv) != 2 { - return fmt.Errorf("invalid environment variable for engine %s, valid configuration is KEY=value pair", env) - } - // skip if the env is already defined - if _, ok := os.LookupEnv(splitEnv[0]); ok { - logrus.Debugf("environment variable %s is already defined, skip the settings from containers.conf", splitEnv[0]) - continue - } - if err := os.Setenv(splitEnv[0], splitEnv[1]); err != nil { - return err - } - } // Hard code TMPDIR functions to use /var/tmp, if user did not override if _, ok := os.LookupEnv("TMPDIR"); !ok { if tmpdir, err := cfg.ImageCopyTmpDir(); err != nil { diff --git a/contrib/cirrus/setup_environment.sh b/contrib/cirrus/setup_environment.sh index 3786054a7..90d28b7ac 100755 --- a/contrib/cirrus/setup_environment.sh +++ b/contrib/cirrus/setup_environment.sh @@ -20,6 +20,13 @@ die_unknown() { } msg "************************************************************" +msg "FIXME: force-install catatonit 0.17.0 until CI images are updated" +msg "************************************************************" +# FIXME: this is just a temporary workaround to force-install +# catatonit 0.17.0. Please remove once the images are updated. +./hack/install_catatonit.sh --force + +msg "************************************************************" msg "Setting up runtime environment" msg "************************************************************" show_env_vars diff --git a/contrib/fedora-minimal/Dockerfile b/contrib/fedora-minimal/Dockerfile deleted file mode 100644 index a051b3204..000000000 --- a/contrib/fedora-minimal/Dockerfile +++ /dev/null @@ -1 +0,0 @@ -FROM registry.fedoraproject.org/fedora-minimal:latest diff --git a/contrib/fedora-minimal/README.md b/contrib/fedora-minimal/README.md deleted file mode 100644 index 52bf94b53..000000000 --- a/contrib/fedora-minimal/README.md +++ /dev/null @@ -1,4 +0,0 @@ -This dockerfile exists so that the container image can be "mirrored" -onto quay.io automatically, so automated testing can be more resilient. - -https://quay.io/repository/libpod/fedora-minimal?tab=builds diff --git a/contrib/spec/podman.spec.in b/contrib/spec/podman.spec.in index 474add1af..cb041df6c 100644 --- a/contrib/spec/podman.spec.in +++ b/contrib/spec/podman.spec.in @@ -58,6 +58,7 @@ BuildRequires: libselinux-devel BuildRequires: pkgconfig BuildRequires: make BuildRequires: systemd-devel +Requires: catatonit >= 0.1.7 Requires: containers-common Requires: conmon Requires: containernetworking-plugins >= 0.6.0-3 @@ -529,7 +530,6 @@ export GOPATH=%{buildroot}/%{gopath}:$(pwd)/vendor:%{gopath} %{_usr}/lib/tmpfiles.d/podman.conf %dir %{_libexecdir}/%{name} %{_libexecdir}/%{name}/rootlessport -%{_libexecdir}/%{name}/pause/pause %if 0%{?with_devel} %files -n libpod-devel -f devel.file-list diff --git a/docs/source/markdown/podman-container-checkpoint.1.md b/docs/source/markdown/podman-container-checkpoint.1.md index 56fd848ac..200920ca9 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 @@ -76,6 +110,14 @@ restore. Defaults to not checkpointing *containers* with established TCP connections.\ The default is **false**. +#### **--file-locks** + +Checkpoint a *container* with file locks. If an application running in the container +is using file locks, this OPTION is required during checkpoint and restore. Otherwise +checkpointing *containers* with file locks is expected to fail. If file locks are not +used, this option is ignored.\ +The default is **false**. + #### **--with-previous** Check out the *container* with previous criu image files in pre-dump. It only works on `runc 1.0-rc3` or `higher`.\ @@ -106,7 +148,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..10477fc77 100644 --- a/docs/source/markdown/podman-container-restore.1.md +++ b/docs/source/markdown/podman-container-restore.1.md @@ -81,6 +81,7 @@ to import a checkpointed *container* from another host.\ Import a pre-checkpoint tar.gz file which was exported by Podman. This option must be used with **-i** or **--import**. It only works on `runc 1.0-rc3` or `higher`. +*IMPORTANT: This OPTION is not supported on the remote client.* #### **--name**, **-n**=*name* @@ -102,6 +103,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 @@ -118,6 +143,14 @@ option is ignored. Defaults to not restoring *containers* with established TCP connections.\ The default is **false**. +#### **--file-locks** + +Restore a *container* with file locks. This option is required to +restore file locks from a checkpoint image. If the checkpoint image +does not contain file locks, this option is ignored. Defaults to not +restoring file locks.\ +The default is **false**. + ## EXAMPLE Restores the container "mywebserver". ``` @@ -137,7 +170,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..b58fd1e18 100644 --- a/docs/source/markdown/podman-create.1.md +++ b/docs/source/markdown/podman-create.1.md @@ -606,7 +606,9 @@ Current supported mount TYPEs are **bind**, **volume**, **image**, **tmpfs** and · ro, readonly: true or false (default). - . U, chown: true or false (default). Change recursively the owner and group of the source volume based on the UID and GID of the container. + . U, chown: true or false (default). Change recursively the owner and group of the source volume based on the UID and GID of the container. + + · idmap: true or false (default). If specified, create an idmapped mount to the target user namespace in the container. Options specific to image: @@ -622,7 +624,9 @@ Current supported mount TYPEs are **bind**, **volume**, **image**, **tmpfs** and . relabel: shared, private. - . U, chown: true or false (default). Change recursively the owner and group of the source volume based on the UID and GID of the container. + · idmap: true or false (default). If specified, create an idmapped mount to the target user namespace in the container. + + . U, chown: true or false (default). Change recursively the owner and group of the source volume based on the UID and GID of the container. Options specific to tmpfs: @@ -636,7 +640,7 @@ Current supported mount TYPEs are **bind**, **volume**, **image**, **tmpfs** and · notmpcopyup: Disable copying files from the image to the tmpfs. - . U, chown: true or false (default). Change recursively the owner and group of the source volume based on the UID and GID of the container. + . U, chown: true or false (default). Change recursively the owner and group of the source volume based on the UID and GID of the container. Options specific to devpts: @@ -1047,6 +1051,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..90c456544 100644 --- a/docs/source/markdown/podman-run.1.md +++ b/docs/source/markdown/podman-run.1.md @@ -633,7 +633,9 @@ Current supported mount TYPEs are **bind**, **volume**, **image**, **tmpfs** and · ro, readonly: true or false (default). - . U, chown: true or false (default). Change recursively the owner and group of the source volume based on the UID and GID of the container. + . U, chown: true or false (default). Change recursively the owner and group of the source volume based on the UID and GID of the container. + + · idmap: true or false (default). If specified, create an idmapped mount to the target user namespace in the container. Options specific to image: @@ -649,7 +651,9 @@ Current supported mount TYPEs are **bind**, **volume**, **image**, **tmpfs** and . relabel: shared, private. - . U, chown: true or false (default). Change recursively the owner and group of the source volume based on the UID and GID of the container. + · idmap: true or false (default). If specified, create an idmapped mount to the target user namespace in the container. + + . U, chown: true or false (default). Change recursively the owner and group of the source volume based on the UID and GID of the container. Options specific to tmpfs: @@ -663,7 +667,7 @@ Current supported mount TYPEs are **bind**, **volume**, **image**, **tmpfs** and · notmpcopyup: Disable copying files from the image to the tmpfs. - . U, chown: true or false (default). Change recursively the owner and group of the source volume based on the UID and GID of the container. + . U, chown: true or false (default). Change recursively the owner and group of the source volume based on the UID and GID of the container. Options specific to devpts: @@ -1117,6 +1121,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 @@ -12,7 +12,7 @@ require ( github.com/containernetworking/cni v1.0.1 github.com/containernetworking/plugins v1.0.1 github.com/containers/buildah v1.23.1 - github.com/containers/common v0.46.1-0.20211110143743-73e7b462c358 + github.com/containers/common v0.46.1-0.20211115170340-7ae7bd1c3f8e github.com/containers/conmon v2.0.20+incompatible github.com/containers/image/v5 v5.16.1 github.com/containers/ocicrypt v1.1.2 @@ -258,8 +258,8 @@ github.com/containernetworking/plugins v1.0.1/go.mod h1:QHCfGpaTwYTbbH+nZXKVTxNB github.com/containers/buildah v1.23.1 h1:Tpc9DsRuU+0Oofewpxb6OJVNQjCu7yloN/obUqzfDTY= github.com/containers/buildah v1.23.1/go.mod h1:4WnrN0yrA7ab0ppgunixu2WM1rlD2rG8QLJAKbEkZlQ= github.com/containers/common v0.44.2/go.mod h1:7sdP4vmI5Bm6FPFxb3lvAh1Iktb6tiO1MzjUzhxdoGo= -github.com/containers/common v0.46.1-0.20211110143743-73e7b462c358 h1:dK2AgGBdWspdQNw28Wc4peY25QeyYV4H9ViQaFaQ9XQ= -github.com/containers/common v0.46.1-0.20211110143743-73e7b462c358/go.mod h1:bu8gizEkgAz6gXHvUw2cMtI5ErxB+fn/hv49RWk5N1A= +github.com/containers/common v0.46.1-0.20211115170340-7ae7bd1c3f8e h1:YSuo3zGivcgQhRV1TOJ6zW3VjyjoU7BJMRyh71v/Zdc= +github.com/containers/common v0.46.1-0.20211115170340-7ae7bd1c3f8e/go.mod h1:bu8gizEkgAz6gXHvUw2cMtI5ErxB+fn/hv49RWk5N1A= github.com/containers/conmon v2.0.20+incompatible h1:YbCVSFSCqFjjVwHTPINGdMX1F6JXHGTUje2ZYobNrkg= github.com/containers/conmon v2.0.20+incompatible/go.mod h1:hgwZ2mtuDrppv78a/cOBNiCm6O0UMWGx1mu7P00nu5I= github.com/containers/image/v5 v5.16.0/go.mod h1:XgTpfAPLRGOd1XYyCU5cISFr777bLmOerCSpt/v7+Q4= @@ -93,19 +93,25 @@ done rc=0 +# As of 2021-11 podman has a bunch of external helper binaries +if [[ -z "$CONTAINERS_HELPER_BINARY_DIR" ]]; then + export CONTAINERS_HELPER_BINARY_DIR=$(pwd)/bin +fi + # Root if [ -z "$ROOTLESS_ONLY" ]; then echo "# bats ${bats_filter[@]} $TESTS" sudo --preserve-env=PODMAN \ --preserve-env=PODMAN_TEST_DEBUG \ --preserve-env=OCI_RUNTIME \ + --preserve-env=CONTAINERS_HELPER_BINARY_DIR \ bats "${bats_opts[@]}" "${bats_filter[@]}" $TESTS rc=$? fi -# Rootless -echo "--------------------------------------------------" -if [ -z "$ROOT_ONLY" ]; then +# Rootless. (Only if we're not already root) +if [[ -z "$ROOT_ONLY" && "$(id -u)" != 0 ]]; then + echo "--------------------------------------------------" echo "\$ bats ${bats_filter[@]} $TESTS" bats "${bats_opts[@]}" "${bats_filter[@]}" $TESTS rc=$((rc | $?)) diff --git a/hack/install_catatonit.sh b/hack/install_catatonit.sh index 0a02b75ab..a35e349f5 100755 --- a/hack/install_catatonit.sh +++ b/hack/install_catatonit.sh @@ -4,22 +4,23 @@ CATATONIT_PATH="${BASE_PATH}/catatonit" CATATONIT_VERSION="v0.1.7" set -e -if [ -f $CATATONIT_PATH ]; then +if [ -f $CATATONIT_PATH ] && [ -z "$1" ]; then echo "skipping ... catatonit is already installed" -else - echo "installing catatonit to $CATATONIT_PATH" - buildDir=$(mktemp -d) - git clone https://github.com/openSUSE/catatonit.git $buildDir + exit 0 +fi - pushd $buildDir - echo `pwd` - git reset --hard ${CATATONIT_VERSION} - autoreconf -fi - ./configure - make - install ${SELINUXOPT} -d -m 755 $BASE_PATH - install ${SELINUXOPT} -m 755 catatonit $CATATONIT_PATH - popd +echo "installing catatonit to $CATATONIT_PATH" +buildDir=$(mktemp -d) +git clone https://github.com/openSUSE/catatonit.git $buildDir - rm -rf $buildDir -fi +pushd $buildDir +echo `pwd` +git reset --hard ${CATATONIT_VERSION} +autoreconf -fi +./configure +make +install ${SELINUXOPT} -d -m 755 $BASE_PATH +install ${SELINUXOPT} -m 755 catatonit $CATATONIT_PATH +popd + +rm -rf $buildDir 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..7ae9f497c 100644 --- a/libpod/container_api.go +++ b/libpod/container_api.go @@ -794,21 +794,32 @@ 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 + // FileLocks tells the API to checkpoint/restore a container + // with file-locks + FileLocks 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 +828,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 +850,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..871c6787a 100644 --- a/libpod/container_internal.go +++ b/libpod/container_internal.go @@ -1050,8 +1050,8 @@ func (c *Container) cniHosts() string { var hosts string for _, status := range c.getNetworkStatus() { for _, netInt := range status.Interfaces { - for _, netAddress := range netInt.Networks { - hosts += fmt.Sprintf("%s\t%s %s\n", netAddress.Subnet.IP.String(), c.Hostname(), c.config.Name) + for _, netAddress := range netInt.Subnets { + hosts += fmt.Sprintf("%s\t%s %s\n", netAddress.IPNet.IP.String(), c.Hostname(), c.config.Name) } } } @@ -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..364b77f29 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 { @@ -1325,8 +1341,8 @@ func (c *Container) restore(ctx context.Context, options ContainerCheckpointOpti perNetOpts.StaticMAC = netInt.MacAddress } if !options.IgnoreStaticIP { - for _, netAddress := range netInt.Networks { - perNetOpts.StaticIPs = append(perNetOpts.StaticIPs, netAddress.Subnet.IP) + for _, netAddress := range netInt.Subnets { + perNetOpts.StaticIPs = append(perNetOpts.StaticIPs, netAddress.IPNet.IP) } } // Normally interfaces have a length of 1, only for some special cni configs we could get more. @@ -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 } } @@ -1891,9 +1943,9 @@ func (c *Container) generateResolvConf() (string, error) { netStatus := c.getNetworkStatus() for _, status := range netStatus { for _, netInt := range status.Interfaces { - for _, netAddress := range netInt.Networks { + for _, netAddress := range netInt.Subnets { // Note: only using To16() does not work since it also returns a valid ip for ipv4 - if netAddress.Subnet.IP.To4() == nil && netAddress.Subnet.IP.To16() != nil { + if netAddress.IPNet.IP.To4() == nil && netAddress.IPNet.IP.To16() != nil { ipv6 = true } } @@ -2099,7 +2151,7 @@ func (c *Container) getHosts() string { if depCtr != nil { for _, status := range depCtr.getNetworkStatus() { for _, netInt := range status.Interfaces { - for _, netAddress := range netInt.Networks { + for _, netAddress := range netInt.Subnets { if netAddress.Gateway != nil { hosts += fmt.Sprintf("%s host.containers.internal\n", netAddress.Gateway.String()) } 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/network/cni/run.go b/libpod/network/cni/run.go index 667ed3ab1..d0ff49b73 100644 --- a/libpod/network/cni/run.go +++ b/libpod/network/cni/run.go @@ -135,8 +135,8 @@ func CNIResultToStatus(res cnitypes.Result) (types.StatusBlock, error) { cniInt := cniResult.Interfaces[*ip.Interface] netInt, ok := interfaces[cniInt.Name] if ok { - netInt.Networks = append(netInt.Networks, types.NetAddress{ - Subnet: types.IPNet{IPNet: ip.Address}, + netInt.Subnets = append(netInt.Subnets, types.NetAddress{ + IPNet: types.IPNet{IPNet: ip.Address}, Gateway: ip.Gateway, }) interfaces[cniInt.Name] = netInt @@ -147,8 +147,8 @@ func CNIResultToStatus(res cnitypes.Result) (types.StatusBlock, error) { } interfaces[cniInt.Name] = types.NetInterface{ MacAddress: types.HardwareAddr(mac), - Networks: []types.NetAddress{{ - Subnet: types.IPNet{IPNet: ip.Address}, + Subnets: []types.NetAddress{{ + IPNet: types.IPNet{IPNet: ip.Address}, Gateway: ip.Gateway, }}, } diff --git a/libpod/network/cni/run_test.go b/libpod/network/cni/run_test.go index 6c54f82ef..f6cc2d412 100644 --- a/libpod/network/cni/run_test.go +++ b/libpod/network/cni/run_test.go @@ -133,8 +133,8 @@ var _ = Describe("run CNI", func() { Expect(res).To(HaveLen(1)) Expect(res).To(HaveKey(defNet)) Expect(res[defNet].Interfaces).To(HaveKey(intName)) - Expect(res[defNet].Interfaces[intName].Networks).To(HaveLen(1)) - Expect(res[defNet].Interfaces[intName].Networks[0].Subnet.IP.String()).To(ContainSubstring("10.88.0.")) + Expect(res[defNet].Interfaces[intName].Subnets).To(HaveLen(1)) + Expect(res[defNet].Interfaces[intName].Subnets[0].IPNet.IP.String()).To(ContainSubstring("10.88.0.")) Expect(res[defNet].Interfaces[intName].MacAddress).To(HaveLen(6)) // default network has no dns Expect(res[defNet].DNSServerIPs).To(BeEmpty()) @@ -170,8 +170,8 @@ var _ = Describe("run CNI", func() { Expect(res).To(HaveLen(1)) Expect(res).To(HaveKey(defNet)) Expect(res[defNet].Interfaces).To(HaveKey(intName)) - Expect(res[defNet].Interfaces[intName].Networks).To(HaveLen(1)) - Expect(res[defNet].Interfaces[intName].Networks[0].Subnet.IP).To(Equal(ip)) + Expect(res[defNet].Interfaces[intName].Subnets).To(HaveLen(1)) + Expect(res[defNet].Interfaces[intName].Subnets[0].IPNet.IP).To(Equal(ip)) Expect(res[defNet].Interfaces[intName].MacAddress).To(HaveLen(6)) // default network has no dns Expect(res[defNet].DNSServerIPs).To(BeEmpty()) @@ -209,8 +209,8 @@ var _ = Describe("run CNI", func() { Expect(res).To(HaveLen(1)) Expect(res).To(HaveKey(defNet)) Expect(res[defNet].Interfaces).To(HaveKey(intName)) - Expect(res[defNet].Interfaces[intName].Networks).To(HaveLen(1)) - Expect(res[defNet].Interfaces[intName].Networks[0].Subnet.IP.String()).To(ContainSubstring("10.88.0.")) + Expect(res[defNet].Interfaces[intName].Subnets).To(HaveLen(1)) + Expect(res[defNet].Interfaces[intName].Subnets[0].IPNet.IP.String()).To(ContainSubstring("10.88.0.")) Expect(res[defNet].Interfaces[intName].MacAddress).To(HaveLen(6)) // default network has no dns Expect(res[defNet].DNSServerIPs).To(BeEmpty()) @@ -263,8 +263,8 @@ var _ = Describe("run CNI", func() { Expect(res).To(HaveLen(1)) Expect(res).To(HaveKey(defNet)) Expect(res[defNet].Interfaces).To(HaveKey(intName)) - Expect(res[defNet].Interfaces[intName].Networks).To(HaveLen(1)) - containerIP := res[defNet].Interfaces[intName].Networks[0].Subnet.IP.String() + Expect(res[defNet].Interfaces[intName].Subnets).To(HaveLen(1)) + containerIP := res[defNet].Interfaces[intName].Subnets[0].IPNet.IP.String() Expect(containerIP).To(ContainSubstring("10.88.0.")) Expect(res[defNet].Interfaces[intName].MacAddress).To(HaveLen(6)) // default network has no dns @@ -324,8 +324,8 @@ var _ = Describe("run CNI", func() { Expect(res).To(HaveLen(1)) Expect(res).To(HaveKey(defNet)) Expect(res[defNet].Interfaces).To(HaveKey(intName)) - Expect(res[defNet].Interfaces[intName].Networks).To(HaveLen(1)) - Expect(res[defNet].Interfaces[intName].Networks[0].Subnet.IP.String()).To(ContainSubstring("10.88.0.")) + Expect(res[defNet].Interfaces[intName].Subnets).To(HaveLen(1)) + Expect(res[defNet].Interfaces[intName].Subnets[0].IPNet.IP.String()).To(ContainSubstring("10.88.0.")) Expect(res[defNet].Interfaces[intName].MacAddress).To(HaveLen(6)) for _, proto := range []string{"tcp", "udp"} { @@ -386,8 +386,8 @@ var _ = Describe("run CNI", func() { Expect(res).To(HaveKey(netName1)) Expect(res[netName1].Interfaces).To(HaveKey(intName1)) - Expect(res[netName1].Interfaces[intName1].Networks).To(HaveLen(1)) - ipInt1 := res[netName1].Interfaces[intName1].Networks[0].Subnet.IP + Expect(res[netName1].Interfaces[intName1].Subnets).To(HaveLen(1)) + ipInt1 := res[netName1].Interfaces[intName1].Subnets[0].IPNet.IP Expect(ipInt1).ToNot(BeEmpty()) macInt1 := res[netName1].Interfaces[intName1].MacAddress Expect(macInt1).To(HaveLen(6)) @@ -436,8 +436,8 @@ var _ = Describe("run CNI", func() { Expect(res).To(HaveKey(netName2)) Expect(res[netName2].Interfaces).To(HaveKey(intName2)) - Expect(res[netName2].Interfaces[intName2].Networks).To(HaveLen(1)) - ipInt2 := res[netName2].Interfaces[intName2].Networks[0].Subnet.IP + Expect(res[netName2].Interfaces[intName2].Subnets).To(HaveLen(1)) + ipInt2 := res[netName2].Interfaces[intName2].Subnets[0].IPNet.IP Expect(ipInt2).ToNot(BeEmpty()) macInt2 := res[netName2].Interfaces[intName2].MacAddress Expect(macInt2).To(HaveLen(6)) @@ -576,16 +576,16 @@ var _ = Describe("run CNI", func() { Expect(res).To(HaveKey(netName1)) Expect(res[netName1].Interfaces).To(HaveKey(intName1)) - Expect(res[netName1].Interfaces[intName1].Networks).To(HaveLen(1)) - ipInt1 := res[netName1].Interfaces[intName1].Networks[0].Subnet.IP + Expect(res[netName1].Interfaces[intName1].Subnets).To(HaveLen(1)) + ipInt1 := res[netName1].Interfaces[intName1].Subnets[0].IPNet.IP Expect(ipInt1.String()).To(ContainSubstring("192.168.0.")) macInt1 := res[netName1].Interfaces[intName1].MacAddress Expect(macInt1).To(HaveLen(6)) Expect(res).To(HaveKey(netName2)) Expect(res[netName2].Interfaces).To(HaveKey(intName2)) - Expect(res[netName2].Interfaces[intName2].Networks).To(HaveLen(1)) - ipInt2 := res[netName2].Interfaces[intName2].Networks[0].Subnet.IP + Expect(res[netName2].Interfaces[intName2].Subnets).To(HaveLen(1)) + ipInt2 := res[netName2].Interfaces[intName2].Subnets[0].IPNet.IP Expect(ipInt2.String()).To(ContainSubstring("192.168.1.")) macInt2 := res[netName2].Interfaces[intName2].MacAddress Expect(macInt2).To(HaveLen(6)) @@ -701,13 +701,13 @@ var _ = Describe("run CNI", func() { Expect(res).To(HaveLen(1)) Expect(res).To(HaveKey(netName)) Expect(res[netName].Interfaces).To(HaveKey(interfaceName)) - Expect(res[netName].Interfaces[interfaceName].Networks).To(HaveLen(2)) - Expect(res[netName].Interfaces[interfaceName].Networks[0].Subnet.IP.String()).To(Equal(ip1.String())) - Expect(res[netName].Interfaces[interfaceName].Networks[0].Subnet.Mask).To(Equal(subnet1.Mask)) - Expect(res[netName].Interfaces[interfaceName].Networks[0].Gateway).To(Equal(net.ParseIP("192.168.0.1"))) - Expect(res[netName].Interfaces[interfaceName].Networks[1].Subnet.IP.String()).To(Equal(ip2.String())) - Expect(res[netName].Interfaces[interfaceName].Networks[1].Subnet.Mask).To(Equal(subnet2.Mask)) - Expect(res[netName].Interfaces[interfaceName].Networks[1].Gateway).To(Equal(net.ParseIP("fd41:0a75:2ca0:48a9::1"))) + Expect(res[netName].Interfaces[interfaceName].Subnets).To(HaveLen(2)) + Expect(res[netName].Interfaces[interfaceName].Subnets[0].IPNet.IP.String()).To(Equal(ip1.String())) + Expect(res[netName].Interfaces[interfaceName].Subnets[0].IPNet.Mask).To(Equal(subnet1.Mask)) + Expect(res[netName].Interfaces[interfaceName].Subnets[0].Gateway).To(Equal(net.ParseIP("192.168.0.1"))) + Expect(res[netName].Interfaces[interfaceName].Subnets[1].IPNet.IP.String()).To(Equal(ip2.String())) + Expect(res[netName].Interfaces[interfaceName].Subnets[1].IPNet.Mask).To(Equal(subnet2.Mask)) + Expect(res[netName].Interfaces[interfaceName].Subnets[1].Gateway).To(Equal(net.ParseIP("fd41:0a75:2ca0:48a9::1"))) Expect(res[netName].Interfaces[interfaceName].MacAddress).To(Equal(types.HardwareAddr(mac))) // default network has no dns Expect(res[netName].DNSServerIPs).To(BeEmpty()) @@ -799,9 +799,9 @@ var _ = Describe("run CNI", func() { Expect(res).To(HaveLen(1)) Expect(res).To(HaveKey(netName)) Expect(res[netName].Interfaces).To(HaveKey(intName)) - Expect(res[netName].Interfaces[intName].Networks).To(HaveLen(1)) - Expect(res[netName].Interfaces[intName].Networks[0].Subnet.IP.String()).To(Equal(ip)) - Expect(res[netName].Interfaces[intName].Networks[0].Subnet.Mask).To(Equal(net.CIDRMask(24, 32))) + Expect(res[netName].Interfaces[intName].Subnets).To(HaveLen(1)) + Expect(res[netName].Interfaces[intName].Subnets[0].IPNet.IP.String()).To(Equal(ip)) + Expect(res[netName].Interfaces[intName].Subnets[0].IPNet.Mask).To(Equal(net.CIDRMask(24, 32))) // check in the container namespace if the settings are applied err = netNSContainer.Do(func(_ ns.NetNS) error { @@ -902,11 +902,11 @@ var _ = Describe("run CNI", func() { Expect(res).To(HaveLen(1)) Expect(res).To(HaveKey(netName)) Expect(res[netName].Interfaces).To(HaveKey(interfaceName)) - Expect(res[netName].Interfaces[interfaceName].Networks).To(HaveLen(2)) - Expect(res[netName].Interfaces[interfaceName].Networks[0].Subnet.IP.String()).To(Equal(ip1.String())) - Expect(res[netName].Interfaces[interfaceName].Networks[0].Subnet.Mask).To(Equal(mask1)) - Expect(res[netName].Interfaces[interfaceName].Networks[1].Subnet.IP.String()).To(Equal(ip2.String())) - Expect(res[netName].Interfaces[interfaceName].Networks[1].Subnet.Mask).To(Equal(mask2)) + Expect(res[netName].Interfaces[interfaceName].Subnets).To(HaveLen(2)) + Expect(res[netName].Interfaces[interfaceName].Subnets[0].IPNet.IP.String()).To(Equal(ip1.String())) + Expect(res[netName].Interfaces[interfaceName].Subnets[0].IPNet.Mask).To(Equal(mask1)) + Expect(res[netName].Interfaces[interfaceName].Subnets[1].IPNet.IP.String()).To(Equal(ip2.String())) + Expect(res[netName].Interfaces[interfaceName].Subnets[1].IPNet.Mask).To(Equal(mask2)) // dualstack network dns Expect(res[netName].DNSServerIPs).To(HaveLen(2)) Expect(res[netName].DNSSearchDomains).To(HaveLen(1)) diff --git a/libpod/network/netavark/run_test.go b/libpod/network/netavark/run_test.go index 3279203cc..67dc51c10 100644 --- a/libpod/network/netavark/run_test.go +++ b/libpod/network/netavark/run_test.go @@ -131,10 +131,10 @@ var _ = Describe("run netavark", func() { Expect(res).To(HaveLen(1)) Expect(res).To(HaveKey(defNet)) Expect(res[defNet].Interfaces).To(HaveKey(intName)) - Expect(res[defNet].Interfaces[intName].Networks).To(HaveLen(1)) - ip := res[defNet].Interfaces[intName].Networks[0].Subnet.IP + Expect(res[defNet].Interfaces[intName].Subnets).To(HaveLen(1)) + ip := res[defNet].Interfaces[intName].Subnets[0].IPNet.IP Expect(ip.String()).To(ContainSubstring("10.88.0.")) - gw := res[defNet].Interfaces[intName].Networks[0].Gateway + gw := res[defNet].Interfaces[intName].Subnets[0].Gateway util.NormalizeIP(&gw) Expect(gw.String()).To(Equal("10.88.0.1")) macAddress := res[defNet].Interfaces[intName].MacAddress @@ -222,8 +222,8 @@ var _ = Describe("run netavark", func() { Expect(res).To(HaveLen(1)) Expect(res).To(HaveKey(defNet)) Expect(res[defNet].Interfaces).To(HaveKey(intName)) - Expect(res[defNet].Interfaces[intName].Networks).To(HaveLen(1)) - ip1 := res[defNet].Interfaces[intName].Networks[0].Subnet.IP + Expect(res[defNet].Interfaces[intName].Subnets).To(HaveLen(1)) + ip1 := res[defNet].Interfaces[intName].Subnets[0].IPNet.IP Expect(ip1.String()).To(ContainSubstring("10.88.0.")) Expect(res[defNet].Interfaces[intName].MacAddress).To(HaveLen(6)) @@ -246,8 +246,8 @@ var _ = Describe("run netavark", func() { Expect(res).To(HaveLen(1)) Expect(res).To(HaveKey(defNet)) Expect(res[defNet].Interfaces).To(HaveKey(intName)) - Expect(res[defNet].Interfaces[intName].Networks).To(HaveLen(1)) - ip2 := res[defNet].Interfaces[intName].Networks[0].Subnet.IP + Expect(res[defNet].Interfaces[intName].Subnets).To(HaveLen(1)) + ip2 := res[defNet].Interfaces[intName].Subnets[0].IPNet.IP Expect(ip2.String()).To(ContainSubstring("10.88.0.")) Expect(res[defNet].Interfaces[intName].MacAddress).To(HaveLen(6)) Expect(ip1.Equal(ip2)).To(BeFalse(), "IP1 %s should not be equal to IP2 %s", ip1.String(), ip2.String()) @@ -286,14 +286,14 @@ var _ = Describe("run netavark", func() { Expect(res).To(HaveLen(1)) Expect(res).To(HaveKey(netName)) Expect(res[netName].Interfaces).To(HaveKey(intName)) - Expect(res[netName].Interfaces[intName].Networks).To(HaveLen(2)) - ip1 := res[netName].Interfaces[intName].Networks[0].Subnet.IP + Expect(res[netName].Interfaces[intName].Subnets).To(HaveLen(2)) + ip1 := res[netName].Interfaces[intName].Subnets[0].IPNet.IP Expect(ip1.String()).To(ContainSubstring("10.0.0.")) - gw1 := res[netName].Interfaces[intName].Networks[0].Gateway + gw1 := res[netName].Interfaces[intName].Subnets[0].Gateway Expect(gw1.String()).To(Equal("10.0.0.1")) - ip2 := res[netName].Interfaces[intName].Networks[1].Subnet.IP + ip2 := res[netName].Interfaces[intName].Subnets[1].IPNet.IP Expect(ip2.String()).To(ContainSubstring("fd10:88:a::")) - gw2 := res[netName].Interfaces[intName].Networks[0].Gateway + gw2 := res[netName].Interfaces[intName].Subnets[0].Gateway Expect(gw2.String()).To(Equal("fd10:88:a::1")) Expect(res[netName].Interfaces[intName].MacAddress).To(HaveLen(6)) @@ -380,14 +380,14 @@ var _ = Describe("run netavark", func() { Expect(res).To(HaveKey(netName2)) Expect(res[netName1].Interfaces).To(HaveKey(intName1)) Expect(res[netName2].Interfaces).To(HaveKey(intName2)) - Expect(res[netName1].Interfaces[intName1].Networks).To(HaveLen(1)) - ip1 := res[netName1].Interfaces[intName1].Networks[0].Subnet.IP + Expect(res[netName1].Interfaces[intName1].Subnets).To(HaveLen(1)) + ip1 := res[netName1].Interfaces[intName1].Subnets[0].IPNet.IP Expect(ip1.String()).To(ContainSubstring("10.0.0.")) - gw1 := res[netName1].Interfaces[intName1].Networks[0].Gateway + gw1 := res[netName1].Interfaces[intName1].Subnets[0].Gateway Expect(gw1.String()).To(Equal("10.0.0.1")) - ip2 := res[netName2].Interfaces[intName2].Networks[0].Subnet.IP + ip2 := res[netName2].Interfaces[intName2].Subnets[0].IPNet.IP Expect(ip2.String()).To(ContainSubstring("10.1.0.")) - gw2 := res[netName2].Interfaces[intName2].Networks[0].Gateway + gw2 := res[netName2].Interfaces[intName2].Subnets[0].Gateway Expect(gw2.String()).To(Equal("10.1.0.1")) mac1 := res[netName1].Interfaces[intName1].MacAddress Expect(mac1).To(HaveLen(6)) @@ -481,8 +481,8 @@ var _ = Describe("run netavark", func() { Expect(res).To(HaveLen(1)) Expect(res).To(HaveKey(defNet)) Expect(res[defNet].Interfaces).To(HaveKey(intName)) - Expect(res[defNet].Interfaces[intName].Networks).To(HaveLen(1)) - Expect(res[defNet].Interfaces[intName].Networks[0].Subnet.IP.String()).To(ContainSubstring("10.88.0.")) + Expect(res[defNet].Interfaces[intName].Subnets).To(HaveLen(1)) + Expect(res[defNet].Interfaces[intName].Subnets[0].IPNet.IP.String()).To(ContainSubstring("10.88.0.")) Expect(res[defNet].Interfaces[intName].MacAddress).To(HaveLen(6)) // default network has no dns Expect(res[defNet].DNSServerIPs).To(BeEmpty()) @@ -535,8 +535,8 @@ var _ = Describe("run netavark", func() { Expect(res).To(HaveLen(1)) Expect(res).To(HaveKey(defNet)) Expect(res[defNet].Interfaces).To(HaveKey(intName)) - Expect(res[defNet].Interfaces[intName].Networks).To(HaveLen(1)) - containerIP := res[defNet].Interfaces[intName].Networks[0].Subnet.IP.String() + Expect(res[defNet].Interfaces[intName].Subnets).To(HaveLen(1)) + containerIP := res[defNet].Interfaces[intName].Subnets[0].IPNet.IP.String() Expect(containerIP).To(ContainSubstring("10.88.0.")) Expect(res[defNet].Interfaces[intName].MacAddress).To(HaveLen(6)) // default network has no dns @@ -593,10 +593,10 @@ var _ = Describe("run netavark", func() { Expect(res).To(HaveLen(1)) Expect(res).To(HaveKey(defNet)) Expect(res[defNet].Interfaces).To(HaveKey(intName)) - Expect(res[defNet].Interfaces[intName].Networks).To(HaveLen(1)) - ip := res[defNet].Interfaces[intName].Networks[0].Subnet.IP + Expect(res[defNet].Interfaces[intName].Subnets).To(HaveLen(1)) + ip := res[defNet].Interfaces[intName].Subnets[0].IPNet.IP Expect(ip.String()).To(ContainSubstring("10.88.0.")) - gw := res[defNet].Interfaces[intName].Networks[0].Gateway + gw := res[defNet].Interfaces[intName].Subnets[0].Gateway Expect(gw.String()).To(Equal("10.88.0.1")) macAddress := res[defNet].Interfaces[intName].MacAddress Expect(macAddress).To(HaveLen(6)) diff --git a/libpod/network/types/network.go b/libpod/network/types/network.go index ba5e018fd..105641e70 100644 --- a/libpod/network/types/network.go +++ b/libpod/network/types/network.go @@ -38,11 +38,11 @@ type Network struct { ID string `json:"id"` // Driver for this Network, e.g. bridge, macvlan... Driver string `json:"driver"` - // InterfaceName is the network interface name on the host. + // NetworkInterface is the network interface name on the host. NetworkInterface string `json:"network_interface,omitempty"` // Created contains the timestamp when this network was created. Created time.Time `json:"created,omitempty"` - // Subnets to use. + // Subnets to use for this network. Subnets []Subnet `json:"subnets,omitempty"` // IPv6Enabled if set to true an ipv6 subnet should be created for this net. IPv6Enabled bool `json:"ipv6_enabled"` @@ -177,24 +177,24 @@ type StatusBlock struct { // NetInterface contains the settings for a given network interface. type NetInterface struct { - // Networks list of assigned subnets with their gateway. - Networks []NetAddress `json:"networks,omitempty"` + // Subnets list of assigned subnets with their gateway. + Subnets []NetAddress `json:"subnets,omitempty"` // MacAddress for this Interface. MacAddress HardwareAddr `json:"mac_address"` } -// NetAddress contains the subnet and gateway. +// NetAddress contains the ip address, subnet and gateway. type NetAddress struct { - // Subnet of this NetAddress. Note that the subnet contains the - // actual ip of the net interface and not the network address. - Subnet IPNet `json:"subnet"` - // Gateway for the Subnet. This can be nil if there is no gateway, e.g. internal network. + // IPNet of this NetAddress. Note that this is a subnet but it has to contain the + // actual ip of the network interface and not the network address. + IPNet IPNet `json:"ipnet"` + // Gateway for the network. This can be empty if there is no gateway, e.g. internal network. Gateway net.IP `json:"gateway,omitempty"` } // PerNetworkOptions are options which should be set on a per network basis. type PerNetworkOptions struct { - // StaticIPv4 for this container. Optional. + // StaticIPs for this container. Optional. StaticIPs []net.IP `json:"static_ips,omitempty"` // Aliases contains a list of names which the dns server should resolve // to this container. Should only be set when DNSEnabled is true on the Network. diff --git a/libpod/networking_linux.go b/libpod/networking_linux.go index fc91155fa..314a74427 100644 --- a/libpod/networking_linux.go +++ b/libpod/networking_linux.go @@ -4,6 +4,7 @@ package libpod import ( "crypto/rand" + "crypto/sha1" "fmt" "io/ioutil" "net" @@ -87,12 +88,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 @@ -384,10 +401,7 @@ func (r *Runtime) GetRootlessNetNs(new bool) (*RootlessNetNS, error) { return nil, nil } var rootlessNetNS *RootlessNetNS - runDir, err := util.GetRuntimeDir() - if err != nil { - return nil, err - } + runDir := r.config.Engine.TmpDir lfile := filepath.Join(runDir, "rootless-netns.lock") lock, err := lockfile.GetLockfile(lfile) @@ -413,7 +427,15 @@ func (r *Runtime) GetRootlessNetNs(new bool) (*RootlessNetNS, error) { if err != nil { return nil, err } - path := filepath.Join(nsDir, rootlessNetNsName) + + // create a hash from the static dir + // the cleanup will check if there are running containers + // if you run a several libpod instances with different root/runroot directories this check will fail + // we want one netns for each libpod static dir so we use the hash to prevent name collisions + hash := sha1.Sum([]byte(r.config.Engine.StaticDir)) + netnsName := fmt.Sprintf("%s-%x", rootlessNetNsName, hash[:10]) + + path := filepath.Join(nsDir, netnsName) ns, err := ns.GetNS(path) if err != nil { if !new { @@ -421,8 +443,8 @@ func (r *Runtime) GetRootlessNetNs(new bool) (*RootlessNetNS, error) { return nil, errors.Wrap(err, "error getting rootless network namespace") } // create a new namespace - logrus.Debug("creating rootless network namespace") - ns, err = netns.NewNSWithName(rootlessNetNsName) + logrus.Debugf("creating rootless network namespace with name %q", netnsName) + ns, err = netns.NewNSWithName(netnsName) if err != nil { return nil, errors.Wrap(err, "error creating rootless network namespace") } @@ -591,32 +613,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 +649,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 +846,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 } @@ -929,8 +943,8 @@ func (r *Runtime) reloadContainerNetwork(ctr *Container) (map[string]types.Statu Aliases: aliases[network], StaticMAC: netInt.MacAddress, } - for _, netAddress := range netInt.Networks { - perNetOpts.StaticIPs = append(perNetOpts.StaticIPs, netAddress.Subnet.IP) + for _, netAddress := range netInt.Subnets { + perNetOpts.StaticIPs = append(perNetOpts.StaticIPs, netAddress.IPNet.IP) } // Normally interfaces have a length of 1, only for some special cni configs we could get more. // For now just use the first interface to get the ips this should be good enough for most cases. @@ -1116,25 +1130,25 @@ func (c *Container) setupNetworkDescriptions(networks []string) error { func resultToBasicNetworkConfig(result types.StatusBlock) (define.InspectBasicNetworkConfig, error) { config := define.InspectBasicNetworkConfig{} for _, netInt := range result.Interfaces { - for _, netAddress := range netInt.Networks { - size, _ := netAddress.Subnet.Mask.Size() - if netAddress.Subnet.IP.To4() != nil { + for _, netAddress := range netInt.Subnets { + size, _ := netAddress.IPNet.Mask.Size() + if netAddress.IPNet.IP.To4() != nil { //ipv4 if config.IPAddress == "" { - config.IPAddress = netAddress.Subnet.IP.String() + config.IPAddress = netAddress.IPNet.IP.String() config.IPPrefixLen = size config.Gateway = netAddress.Gateway.String() } else { - config.SecondaryIPAddresses = append(config.SecondaryIPAddresses, netAddress.Subnet.IP.String()) + config.SecondaryIPAddresses = append(config.SecondaryIPAddresses, netAddress.IPNet.IP.String()) } } else { //ipv6 if config.GlobalIPv6Address == "" { - config.GlobalIPv6Address = netAddress.Subnet.IP.String() + config.GlobalIPv6Address = netAddress.IPNet.IP.String() config.GlobalIPv6PrefixLen = size config.IPv6Gateway = netAddress.Gateway.String() } else { - config.SecondaryIPv6Addresses = append(config.SecondaryIPv6Addresses, netAddress.Subnet.IP.String()) + config.SecondaryIPv6Addresses = append(config.SecondaryIPv6Addresses, netAddress.IPNet.IP.String()) } } } @@ -1206,7 +1220,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 +1312,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..cc1b3cfdc 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) @@ -660,12 +660,12 @@ func getRootlessPortChildIP(c *Container, netStatus map[string]types.StatusBlock var ipv6 net.IP for _, status := range netStatus { for _, netInt := range status.Interfaces { - for _, netAddress := range netInt.Networks { - ipv4 := netAddress.Subnet.IP.To4() + for _, netAddress := range netInt.Subnets { + ipv4 := netAddress.IPNet.IP.To4() if ipv4 != nil { return ipv4.String() } - ipv6 = netAddress.Subnet.IP + ipv6 = netAddress.IPNet.IP } } } 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..e007d0b92 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() @@ -787,6 +794,9 @@ func (r *ConmonOCIRuntime) CheckpointContainer(ctr *Container, options Container if options.TCPEstablished { args = append(args, "--tcp-established") } + if options.FileLocks { + args = append(args, "--file-locks") + } if !options.PreCheckPoint && options.KeepRunning { args = append(args, "--leave-running") } @@ -802,14 +812,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 +1005,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 +1033,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 +1089,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) } @@ -1083,6 +1104,9 @@ func (r *ConmonOCIRuntime) createOCIContainer(ctr *Container, restoreOptions *Co if restoreOptions.TCPEstablished { args = append(args, "--runtime-opt", "--tcp-established") } + if restoreOptions.FileLocks { + args = append(args, "--runtime-opt", "--file-locks") + } if restoreOptions.Pod != "" { mountLabel := ctr.config.MountLabel processLabel := ctr.config.ProcessLabel @@ -1149,7 +1173,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 +1189,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 +1213,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 +1239,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 +1265,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/pause/pause.c b/pause/pause.c deleted file mode 100644 index 1e363bd7a..000000000 --- a/pause/pause.c +++ /dev/null @@ -1,69 +0,0 @@ -/* -Copyright 2016 The Kubernetes Authors. -Copyright 2021 The Podman Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -#include <signal.h> -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <sys/types.h> -#include <sys/wait.h> -#include <unistd.h> - -#define STRINGIFY(x) #x -#define VERSION_STRING(x) STRINGIFY(x) - -#ifndef VERSION -#define VERSION HEAD -#endif - -static void sigdown(int signo) { - psignal(signo, "Shutting down, got signal"); - exit(0); -} - -static void sigreap(int signo) { - while (waitpid(-1, NULL, WNOHANG) > 0) - ; -} - -int main(int argc, char **argv) { - int i; - for (i = 1; i < argc; ++i) { - if (!strcasecmp(argv[i], "-v")) { - printf("pause.c %s\n", VERSION_STRING(VERSION)); - return 0; - } - } - - if (getpid() != 1) - /* Not an error because pause sees use outside of infra containers. */ - fprintf(stderr, "Warning: pause should be the first process\n"); - - if (sigaction(SIGINT, &(struct sigaction){.sa_handler = sigdown}, NULL) < 0) - return 1; - if (sigaction(SIGTERM, &(struct sigaction){.sa_handler = sigdown}, NULL) < 0) - return 2; - if (sigaction(SIGCHLD, &(struct sigaction){.sa_handler = sigreap, - .sa_flags = SA_NOCLDSTOP}, - NULL) < 0) - return 3; - - for (;;) - pause(); - fprintf(stderr, "Error: infinite loop terminated\n"); - return 42; -} diff --git a/pkg/api/handlers/libpod/containers.go b/pkg/api/handlers/libpod/containers.go index 343c0d0b3..d5da22a91 100644 --- a/pkg/api/handlers/libpod/containers.go +++ b/pkg/api/handlers/libpod/containers.go @@ -1,9 +1,11 @@ package libpod import ( + "fmt" "io/ioutil" "net/http" "os" + "strings" "github.com/containers/podman/v3/libpod" "github.com/containers/podman/v3/libpod/define" @@ -206,7 +208,9 @@ func ShowMountedContainers(w http.ResponseWriter, r *http.Request) { } func Checkpoint(w http.ResponseWriter, r *http.Request) { - var targetFile string + runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime) + containerEngine := abi.ContainerEngine{Libpod: runtime} + decoder := r.Context().Value(api.DecoderKey).(*schema.Decoder) query := struct { Keep bool `schema:"keep"` @@ -214,6 +218,9 @@ func Checkpoint(w http.ResponseWriter, r *http.Request) { TCPEstablished bool `schema:"tcpEstablished"` Export bool `schema:"export"` IgnoreRootFS bool `schema:"ignoreRootFS"` + PrintStats bool `schema:"printStats"` + PreCheckpoint bool `schema:"preCheckpoint"` + WithPrevious bool `schema:"withPrevious"` }{ // override any golang type defaults } @@ -223,57 +230,70 @@ func Checkpoint(w http.ResponseWriter, r *http.Request) { errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String())) return } + name := utils.GetName(r) - runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime) - ctr, err := runtime.LookupContainer(name) - if err != nil { + if _, err := runtime.LookupContainer(name); err != nil { utils.ContainerNotFound(w, name, err) return } + names := []string{name} + + options := entities.CheckpointOptions{ + Keep: query.Keep, + LeaveRunning: query.LeaveRunning, + TCPEstablished: query.TCPEstablished, + IgnoreRootFS: query.IgnoreRootFS, + PrintStats: query.PrintStats, + PreCheckPoint: query.PreCheckpoint, + WithPrevious: query.WithPrevious, + } + if query.Export { - tmpFile, err := ioutil.TempFile("", "checkpoint") + f, err := ioutil.TempFile("", "checkpoint") if err != nil { utils.InternalServerError(w, err) return } - defer os.Remove(tmpFile.Name()) - if err := tmpFile.Close(); err != nil { + defer os.Remove(f.Name()) + if err := f.Close(); err != nil { utils.InternalServerError(w, err) return } - targetFile = tmpFile.Name() - } - options := libpod.ContainerCheckpointOptions{ - Keep: query.Keep, - KeepRunning: query.LeaveRunning, - TCPEstablished: query.TCPEstablished, - IgnoreRootfs: query.IgnoreRootFS, + options.Export = f.Name() } - if query.Export { - options.TargetFile = targetFile - } - err = ctr.Checkpoint(r.Context(), options) + + reports, err := containerEngine.ContainerCheckpoint(r.Context(), names, options) if err != nil { utils.InternalServerError(w, err) return } - if query.Export { - f, err := os.Open(targetFile) - if err != nil { - utils.InternalServerError(w, err) + + if !query.Export { + if len(reports) != 1 { + utils.InternalServerError(w, fmt.Errorf("expected 1 restore report but got %d", len(reports))) return } - defer f.Close() - utils.WriteResponse(w, http.StatusOK, f) + if reports[0].Err != nil { + utils.InternalServerError(w, reports[0].Err) + return + } + utils.WriteResponse(w, http.StatusOK, reports[0]) return } - utils.WriteResponse(w, http.StatusOK, entities.CheckpointReport{Id: ctr.ID()}) + + f, err := os.Open(options.Export) + if err != nil { + utils.InternalServerError(w, err) + return + } + defer f.Close() + utils.WriteResponse(w, http.StatusOK, f) } func Restore(w http.ResponseWriter, r *http.Request) { - var ( - targetFile string - ) + runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime) + containerEngine := abi.ContainerEngine{Libpod: runtime} + decoder := r.Context().Value(api.DecoderKey).(*schema.Decoder) query := struct { Keep bool `schema:"keep"` @@ -284,6 +304,8 @@ func Restore(w http.ResponseWriter, r *http.Request) { IgnoreVolumes bool `schema:"ignoreVolumes"` IgnoreStaticIP bool `schema:"ignoreStaticIP"` IgnoreStaticMAC bool `schema:"ignoreStaticMAC"` + PrintStats bool `schema:"printStats"` + PublishPorts string `schema:"publishPorts"` }{ // override any golang type defaults } @@ -292,44 +314,55 @@ func Restore(w http.ResponseWriter, r *http.Request) { errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String())) return } - name := utils.GetName(r) - runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime) - ctr, err := runtime.LookupContainer(name) - if err != nil { - utils.ContainerNotFound(w, name, err) - return + + options := entities.RestoreOptions{ + Name: query.Name, + Keep: query.Keep, + TCPEstablished: query.TCPEstablished, + IgnoreRootFS: query.IgnoreRootFS, + IgnoreVolumes: query.IgnoreVolumes, + IgnoreStaticIP: query.IgnoreStaticIP, + IgnoreStaticMAC: query.IgnoreStaticMAC, + PrintStats: query.PrintStats, + PublishPorts: strings.Fields(query.PublishPorts), } + + var names []string if query.Import { t, err := ioutil.TempFile("", "restore") if err != nil { utils.InternalServerError(w, err) return } - defer t.Close() + defer os.Remove(t.Name()) if err := compat.SaveFromBody(t, r); err != nil { utils.InternalServerError(w, err) return } - targetFile = t.Name() + options.Import = t.Name() + } else { + name := utils.GetName(r) + if _, err := runtime.LookupContainer(name); err != nil { + utils.ContainerNotFound(w, name, err) + return + } + names = []string{name} } - options := libpod.ContainerCheckpointOptions{ - Keep: query.Keep, - TCPEstablished: query.TCPEstablished, - IgnoreRootfs: query.IgnoreRootFS, - IgnoreStaticIP: query.IgnoreStaticIP, - IgnoreStaticMAC: query.IgnoreStaticMAC, - } - if query.Import { - options.TargetFile = targetFile - options.Name = query.Name - } - err = ctr.Restore(r.Context(), options) + reports, err := containerEngine.ContainerRestore(r.Context(), names, options) if err != nil { utils.InternalServerError(w, err) return } - utils.WriteResponse(w, http.StatusOK, entities.RestoreReport{Id: ctr.ID()}) + if len(reports) != 1 { + utils.InternalServerError(w, fmt.Errorf("expected 1 restore report but got %d", len(reports))) + return + } + if reports[0].Err != nil { + utils.InternalServerError(w, reports[0].Err) + return + } + utils.WriteResponse(w, http.StatusOK, reports[0]) } 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..7b4ec093d 100644 --- a/pkg/bindings/containers/checkpoint.go +++ b/pkg/bindings/containers/checkpoint.go @@ -2,7 +2,9 @@ package containers import ( "context" + "io" "net/http" + "os" "github.com/containers/podman/v3/pkg/bindings" "github.com/containers/podman/v3/pkg/domain/entities" @@ -23,13 +25,34 @@ 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) + + // "export" is a bool for the server so override it in the parameters + // if set. + export := false + if options.Export != nil && *options.Export != "" { + export = true + params.Set("export", "true") + } + response, err := conn.DoRequest(ctx, nil, http.MethodPost, "/containers/%s/checkpoint", params, nil, nameOrID) if err != nil { return nil, err } defer response.Body.Close() - return &report, response.Process(&report) + if !export { + return &report, response.Process(&report) + } + + f, err := os.OpenFile(*options.Export, os.O_RDWR|os.O_CREATE, 0600) + if err != nil { + return nil, err + } + defer f.Close() + if _, err := io.Copy(f, response.Body); err != nil { + return nil, err + } + + return &entities.CheckpointReport{}, nil } // Restore restores a checkpointed container to running. The container is identified by the nameOrID option. All @@ -47,12 +70,26 @@ func Restore(ctx context.Context, nameOrID string, options *RestoreOptions) (*en if err != nil { return nil, err } - // The import key is a reserved golang term - params.Del("ImportArchive") - if i := options.GetImportAchive(); options.Changed("ImportArchive") { - params.Set("import", i) + + for _, p := range options.PublishPorts { + params.Add("publishPorts", p) + } + + params.Del("ImportArchive") // The import key is a reserved golang term + + // Open the to-be-imported archive if needed. + var r io.Reader + if i := options.GetImportAchive(); i != "" { + params.Set("import", "true") + r, err = os.Open(i) + if err != nil { + return nil, err + } + // Hard-code the name since it will be ignored in any case. + nameOrID = "import" } - response, err := conn.DoRequest(nil, http.MethodPost, "/containers/%s/restore", params, nil, nameOrID) + + response, err := conn.DoRequest(ctx, r, 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/containers/types.go b/pkg/bindings/containers/types.go index 3a7d5a4c7..81a53a549 100644 --- a/pkg/bindings/containers/types.go +++ b/pkg/bindings/containers/types.go @@ -50,12 +50,17 @@ type CheckpointOptions struct { Keep *bool LeaveRunning *bool TCPEstablished *bool + PrintStats *bool + PreCheckpoint *bool + WithPrevious *bool + FileLocks *bool } //go:generate go run ../generator/generator.go RestoreOptions // RestoreOptions are optional options for restoring containers type RestoreOptions struct { IgnoreRootfs *bool + IgnoreVolumes *bool IgnoreStaticIP *bool IgnoreStaticMAC *bool ImportAchive *string @@ -63,6 +68,9 @@ type RestoreOptions struct { Name *string TCPEstablished *bool Pod *string + PrintStats *bool + PublishPorts []string + FileLocks *bool } //go:generate go run ../generator/generator.go CreateOptions @@ -86,7 +94,8 @@ type ExecInspectOptions struct{} //go:generate go run ../generator/generator.go ExecStartOptions // ExecStartOptions are optional options for starting // exec sessions -type ExecStartOptions struct{} +type ExecStartOptions struct { +} //go:generate go run ../generator/generator.go HealthCheckOptions // HealthCheckOptions are optional options for checking diff --git a/pkg/bindings/containers/types_checkpoint_options.go b/pkg/bindings/containers/types_checkpoint_options.go index 7b28c4045..391748d76 100644 --- a/pkg/bindings/containers/types_checkpoint_options.go +++ b/pkg/bindings/containers/types_checkpoint_options.go @@ -91,3 +91,63 @@ func (o *CheckpointOptions) GetTCPEstablished() bool { } return *o.TCPEstablished } + +// WithPrintStats set field PrintStats to given value +func (o *CheckpointOptions) WithPrintStats(value bool) *CheckpointOptions { + o.PrintStats = &value + return o +} + +// GetPrintStats returns value of field PrintStats +func (o *CheckpointOptions) GetPrintStats() bool { + if o.PrintStats == nil { + var z bool + return z + } + return *o.PrintStats +} + +// WithPreCheckpoint set field PreCheckpoint to given value +func (o *CheckpointOptions) WithPreCheckpoint(value bool) *CheckpointOptions { + o.PreCheckpoint = &value + return o +} + +// GetPreCheckpoint returns value of field PreCheckpoint +func (o *CheckpointOptions) GetPreCheckpoint() bool { + if o.PreCheckpoint == nil { + var z bool + return z + } + return *o.PreCheckpoint +} + +// WithWithPrevious set field WithPrevious to given value +func (o *CheckpointOptions) WithWithPrevious(value bool) *CheckpointOptions { + o.WithPrevious = &value + return o +} + +// GetWithPrevious returns value of field WithPrevious +func (o *CheckpointOptions) GetWithPrevious() bool { + if o.WithPrevious == nil { + var z bool + return z + } + return *o.WithPrevious +} + +// WithFileLocks set field FileLocks to given value +func (o *CheckpointOptions) WithFileLocks(value bool) *CheckpointOptions { + o.FileLocks = &value + return o +} + +// GetFileLocks returns value of field FileLocks +func (o *CheckpointOptions) GetFileLocks() bool { + if o.FileLocks == nil { + var z bool + return z + } + return *o.FileLocks +} diff --git a/pkg/bindings/containers/types_restore_options.go b/pkg/bindings/containers/types_restore_options.go index 6eea108f4..7af2bba32 100644 --- a/pkg/bindings/containers/types_restore_options.go +++ b/pkg/bindings/containers/types_restore_options.go @@ -32,6 +32,21 @@ func (o *RestoreOptions) GetIgnoreRootfs() bool { return *o.IgnoreRootfs } +// WithIgnoreVolumes set field IgnoreVolumes to given value +func (o *RestoreOptions) WithIgnoreVolumes(value bool) *RestoreOptions { + o.IgnoreVolumes = &value + return o +} + +// GetIgnoreVolumes returns value of field IgnoreVolumes +func (o *RestoreOptions) GetIgnoreVolumes() bool { + if o.IgnoreVolumes == nil { + var z bool + return z + } + return *o.IgnoreVolumes +} + // WithIgnoreStaticIP set field IgnoreStaticIP to given value func (o *RestoreOptions) WithIgnoreStaticIP(value bool) *RestoreOptions { o.IgnoreStaticIP = &value @@ -136,3 +151,48 @@ func (o *RestoreOptions) GetPod() string { } return *o.Pod } + +// WithPrintStats set field PrintStats to given value +func (o *RestoreOptions) WithPrintStats(value bool) *RestoreOptions { + o.PrintStats = &value + return o +} + +// GetPrintStats returns value of field PrintStats +func (o *RestoreOptions) GetPrintStats() bool { + if o.PrintStats == nil { + var z bool + return z + } + return *o.PrintStats +} + +// WithPublishPorts set field PublishPorts to given value +func (o *RestoreOptions) WithPublishPorts(value []string) *RestoreOptions { + o.PublishPorts = value + return o +} + +// GetPublishPorts returns value of field PublishPorts +func (o *RestoreOptions) GetPublishPorts() []string { + if o.PublishPorts == nil { + var z []string + return z + } + return o.PublishPorts +} + +// WithFileLocks set field FileLocks to given value +func (o *RestoreOptions) WithFileLocks(value bool) *RestoreOptions { + o.FileLocks = &value + return o +} + +// GetFileLocks returns value of field FileLocks +func (o *RestoreOptions) GetFileLocks() bool { + if o.FileLocks == nil { + var z bool + return z + } + return *o.FileLocks +} 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/checkpoint/checkpoint_restore.go b/pkg/checkpoint/checkpoint_restore.go index da82c9745..3a300daaf 100644 --- a/pkg/checkpoint/checkpoint_restore.go +++ b/pkg/checkpoint/checkpoint_restore.go @@ -16,6 +16,7 @@ import ( "github.com/containers/podman/v3/pkg/domain/entities" "github.com/containers/podman/v3/pkg/errorhandling" "github.com/containers/podman/v3/pkg/specgen/generate" + "github.com/containers/podman/v3/pkg/specgenutil" "github.com/containers/storage/pkg/archive" spec "github.com/opencontainers/runtime-spec/specs-go" "github.com/pkg/errors" @@ -195,7 +196,12 @@ func CRImportCheckpoint(ctx context.Context, runtime *libpod.Runtime, restoreOpt } if len(restoreOptions.PublishPorts) > 0 { - ports, err := generate.ParsePortMapping(restoreOptions.PublishPorts, nil) + pubPorts, err := specgenutil.CreatePortBindings(restoreOptions.PublishPorts) + if err != nil { + return nil, err + } + + ports, err := generate.ParsePortMapping(pubPorts, nil) if err != nil { return nil, err } diff --git a/pkg/domain/entities/containers.go b/pkg/domain/entities/containers.go index 869c616ea..1677c067f 100644 --- a/pkg/domain/entities/containers.go +++ b/pkg/domain/entities/containers.go @@ -190,11 +190,15 @@ type CheckpointOptions struct { PreCheckPoint bool WithPrevious bool Compression archive.Compression + PrintStats bool + FileLocks 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 { @@ -209,13 +213,17 @@ type RestoreOptions struct { Name string TCPEstablished bool ImportPrevious string - PublishPorts []nettypes.PortMapping + PublishPorts []string Pod string + PrintStats bool + FileLocks 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..e04c7a38a 100644 --- a/pkg/domain/infra/abi/containers.go +++ b/pkg/domain/infra/abi/containers.go @@ -515,6 +515,8 @@ func (ic *ContainerEngine) ContainerCheckpoint(ctx context.Context, namesOrIds [ PreCheckPoint: options.PreCheckPoint, WithPrevious: options.WithPrevious, Compression: options.Compression, + PrintStats: options.PrintStats, + FileLocks: options.FileLocks, } if options.All { @@ -531,10 +533,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 +561,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 +584,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/containers.go b/pkg/domain/infra/tunnel/containers.go index 5b5a1912c..2127f8749 100644 --- a/pkg/domain/infra/tunnel/containers.go +++ b/pkg/domain/infra/tunnel/containers.go @@ -302,6 +302,17 @@ func (ic *ContainerEngine) ContainerExport(ctx context.Context, nameOrID string, } func (ic *ContainerEngine) ContainerCheckpoint(ctx context.Context, namesOrIds []string, opts entities.CheckpointOptions) ([]*entities.CheckpointReport, error) { + options := new(containers.CheckpointOptions) + options.WithFileLocks(opts.FileLocks) + options.WithIgnoreRootfs(opts.IgnoreRootFS) + options.WithKeep(opts.Keep) + options.WithExport(opts.Export) + options.WithTCPEstablished(opts.TCPEstablished) + options.WithPrintStats(opts.PrintStats) + options.WithPreCheckpoint(opts.PreCheckPoint) + options.WithLeaveRunning(opts.LeaveRunning) + options.WithWithPrevious(opts.WithPrevious) + var ( err error ctrs = []entities.ListContainer{} @@ -325,19 +336,41 @@ func (ic *ContainerEngine) ContainerCheckpoint(ctx context.Context, namesOrIds [ } } reports := make([]*entities.CheckpointReport, 0, len(ctrs)) - options := new(containers.CheckpointOptions).WithExport(opts.Export).WithIgnoreRootfs(opts.IgnoreRootFS).WithKeep(opts.Keep) - options.WithLeaveRunning(opts.LeaveRunning).WithTCPEstablished(opts.TCPEstablished) for _, c := range ctrs { report, err := containers.Checkpoint(ic.ClientCtx, c.ID, options) if err != nil { reports = append(reports, &entities.CheckpointReport{Id: c.ID, Err: err}) + } else { + reports = append(reports, report) } - reports = append(reports, report) } return reports, nil } func (ic *ContainerEngine) ContainerRestore(ctx context.Context, namesOrIds []string, opts entities.RestoreOptions) ([]*entities.RestoreReport, error) { + if opts.ImportPrevious != "" { + return nil, fmt.Errorf("--import-previous is not supported on the remote client") + } + + options := new(containers.RestoreOptions) + options.WithFileLocks(opts.FileLocks) + options.WithIgnoreRootfs(opts.IgnoreRootFS) + options.WithIgnoreVolumes(opts.IgnoreVolumes) + options.WithIgnoreStaticIP(opts.IgnoreStaticIP) + options.WithIgnoreStaticMAC(opts.IgnoreStaticMAC) + options.WithKeep(opts.Keep) + options.WithName(opts.Name) + options.WithTCPEstablished(opts.TCPEstablished) + options.WithPod(opts.Pod) + options.WithPrintStats(opts.PrintStats) + options.WithPublishPorts(opts.PublishPorts) + + if opts.Import != "" { + options.WithImportAchive(opts.Import) + report, err := containers.Restore(ic.ClientCtx, "", options) + return []*entities.RestoreReport{report}, err + } + var ( err error ctrs = []entities.ListContainer{} @@ -360,7 +393,6 @@ func (ic *ContainerEngine) ContainerRestore(ctx context.Context, namesOrIds []st } } reports := make([]*entities.RestoreReport, 0, len(ctrs)) - options := new(containers.RestoreOptions) for _, c := range ctrs { report, err := containers.Restore(ic.ClientCtx, c.ID, options) if err != 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/ignition.go b/pkg/machine/ignition.go index 42d729458..e19940b22 100644 --- a/pkg/machine/ignition.go +++ b/pkg/machine/ignition.go @@ -81,7 +81,7 @@ func NewIgnitionFile(ign DynamicIgnition) error { // so a listening host knows it can being interacting with it ready := `[Unit] Requires=dev-virtio\\x2dports-%s.device -After=remove-moby.service +After=remove-moby.service sshd.socket sshd.service OnFailure=emergency.target OnFailureJobMode=isolate [Service] 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/machine/qemu/options_darwin_arm64.go b/pkg/machine/qemu/options_darwin_arm64.go index 43cd3d69d..727a275d2 100644 --- a/pkg/machine/qemu/options_darwin_arm64.go +++ b/pkg/machine/qemu/options_darwin_arm64.go @@ -24,7 +24,7 @@ func (v *MachineVM) addArchOptions() []string { func (v *MachineVM) prepare() error { ovmfDir := getOvmfDir(v.ImagePath, v.Name) - cmd := []string{"dd", "if=/dev/zero", "conv=sync", "bs=1m", "count=64", "of=" + ovmfDir} + cmd := []string{"/bin/dd", "if=/dev/zero", "conv=sync", "bs=1m", "count=64", "of=" + ovmfDir} return exec.Command(cmd[0], cmd[1:]...).Run() } diff --git a/pkg/rootless/rootless_linux.c b/pkg/rootless/rootless_linux.c index 6ce4b1e29..e71d5d999 100644 --- a/pkg/rootless/rootless_linux.c +++ b/pkg/rootless/rootless_linux.c @@ -19,6 +19,33 @@ #include <sys/select.h> #include <stdio.h> +#define cleanup_free __attribute__ ((cleanup (cleanup_freep))) +#define cleanup_close __attribute__ ((cleanup (cleanup_closep))) +#define cleanup_dir __attribute__ ((cleanup (cleanup_dirp))) + +static inline void +cleanup_freep (void *p) +{ + void **pp = (void **) p; + free (*pp); +} + +static inline void +cleanup_closep (void *p) +{ + int *pp = p; + if (*pp >= 0) + TEMP_FAILURE_RETRY (close (*pp)); +} + +static inline void +cleanup_dirp (DIR **p) +{ + DIR *dir = *p; + if (dir) + closedir (dir); +} + int rename_noreplace (int olddirfd, const char *oldpath, int newdirfd, const char *newpath) { int ret; @@ -106,6 +133,11 @@ do_pause () for (i = 0; sig[i]; i++) sigaction (sig[i], &act, NULL); + /* Attempt to execv catatonit to keep the pause process alive. */ + execl ("/usr/libexec/podman/catatonit", "catatonit", "-P", NULL); + execl ("/usr/bin/catatonit", "catatonit", "-P", NULL); + /* and if the catatonit executable could not be found, fallback here... */ + prctl (PR_SET_NAME, "podman pause", NULL, NULL, NULL); while (1) pause (); @@ -114,8 +146,8 @@ do_pause () static char ** get_cmd_line_args () { - int fd; - char *buffer; + cleanup_free char *buffer = NULL; + cleanup_close int fd = -1; size_t allocated; size_t used = 0; int ret; @@ -134,10 +166,7 @@ get_cmd_line_args () { ret = TEMP_FAILURE_RETRY (read (fd, buffer + used, allocated - used)); if (ret < 0) - { - free (buffer); - return NULL; - } + return NULL; if (ret == 0) break; @@ -148,30 +177,21 @@ get_cmd_line_args () allocated += 512; char *tmp = realloc (buffer, allocated); if (tmp == NULL) - { - free (buffer); - return NULL; - } + return NULL; buffer = tmp; } } - close (fd); for (i = 0; i < used; i++) if (buffer[i] == '\0') argc++; if (argc == 0) - { - free (buffer); - return NULL; - } + return NULL; argv = malloc (sizeof (char *) * (argc + 1)); if (argv == NULL) - { - free (buffer); - return NULL; - } + return NULL; + argc = 0; argv[argc++] = buffer; @@ -181,15 +201,19 @@ get_cmd_line_args () argv[argc] = NULL; + /* Move ownership. */ + buffer = NULL; + return argv; } static bool can_use_shortcut () { - int argc; - char **argv; + cleanup_free char **argv = NULL; + cleanup_free char *argv0 = NULL; bool ret = true; + int argc; #ifdef DISABLE_JOIN_SHORTCUT return false; @@ -199,12 +223,10 @@ can_use_shortcut () if (argv == NULL) return false; + argv0 = argv[0]; + if (strstr (argv[0], "podman") == NULL) - { - free (argv[0]); - free (argv); - return false; - } + return false; for (argc = 0; argv[argc]; argc++) { @@ -229,11 +251,25 @@ can_use_shortcut () } } - free (argv[0]); - free (argv); return ret; } +static int +open_namespace (int pid_to_join, const char *ns_file) +{ + char ns_path[PATH_MAX]; + int ret; + + ret = snprintf (ns_path, PATH_MAX, "/proc/%d/ns/%s", pid_to_join, ns_file); + if (ret == PATH_MAX) + { + fprintf (stderr, "internal error: namespace path too long\n"); + return -1; + } + + return open (ns_path, O_CLOEXEC | O_RDONLY); +} + int is_fd_inherited(int fd) { @@ -250,8 +286,7 @@ static void __attribute__((constructor)) init() const char *listen_pid; const char *listen_fds; const char *listen_fdnames; - - DIR *d; + cleanup_dir DIR *d = NULL; pause = getenv ("_PODMAN_PAUSE"); if (pause && pause[0]) @@ -299,7 +334,6 @@ static void __attribute__((constructor)) init() FD_SET (fd % FD_SETSIZE, &(open_files_set[fd / FD_SETSIZE])); } - closedir (d); } listen_pid = getenv("LISTEN_PID"); @@ -317,7 +351,7 @@ static void __attribute__((constructor)) init() if (saved_systemd_listen_pid == NULL || saved_systemd_listen_fds == NULL) { - fprintf (stderr, "save socket listen environments error: %s\n", strerror (errno)); + fprintf (stderr, "save socket listen environments error: %m\n"); _exit (EXIT_FAILURE); } } @@ -327,73 +361,70 @@ static void __attribute__((constructor)) init() xdg_runtime_dir = getenv ("XDG_RUNTIME_DIR"); if (geteuid () != 0 && xdg_runtime_dir && xdg_runtime_dir[0] && can_use_shortcut ()) { - int r; - int fd; + cleanup_free char *cwd = NULL; + cleanup_close int userns_fd = -1; + cleanup_close int mntns_fd = -1; + cleanup_close int fd = -1; long pid; char buf[12]; uid_t uid; gid_t gid; char path[PATH_MAX]; const char *const suffix = "/libpod/tmp/pause.pid"; - char *cwd = getcwd (NULL, 0); char uid_fmt[16]; char gid_fmt[16]; size_t len; + int r; + cwd = getcwd (NULL, 0); if (cwd == NULL) { - fprintf (stderr, "error getting current working directory: %s\n", strerror (errno)); + fprintf (stderr, "error getting current working directory: %m\n"); _exit (EXIT_FAILURE); } len = snprintf (path, PATH_MAX, "%s%s", xdg_runtime_dir, suffix); if (len >= PATH_MAX) { - fprintf (stderr, "invalid value for XDG_RUNTIME_DIR: %s", strerror (ENAMETOOLONG)); + errno = ENAMETOOLONG; + fprintf (stderr, "invalid value for XDG_RUNTIME_DIR: %m"); exit (EXIT_FAILURE); } fd = open (path, O_RDONLY); if (fd < 0) - { - free (cwd); - return; - } + return; r = TEMP_FAILURE_RETRY (read (fd, buf, sizeof (buf) - 1)); - close (fd); + if (r < 0) - { - free (cwd); - return; - } + return; buf[r] = '\0'; pid = strtol (buf, NULL, 10); if (pid == LONG_MAX) - { - free (cwd); - return; - } + return; uid = geteuid (); gid = getegid (); - sprintf (path, "/proc/%ld/ns/user", pid); - fd = open (path, O_RDONLY); - if (fd < 0 || setns (fd, 0) < 0) - { - free (cwd); - return; - } - close (fd); + userns_fd = open_namespace (pid, "user"); + if (userns_fd < 0) + return; - /* Errors here cannot be ignored as we already joined a ns. */ - sprintf (path, "/proc/%ld/ns/mnt", pid); - fd = open (path, O_RDONLY); - if (fd < 0) + mntns_fd = open_namespace (pid, "mnt"); + if (mntns_fd < 0) + return; + + if (setns (userns_fd, 0) < 0) + return; + + /* The user namespace was joined, after this point errors are + not recoverable anymore. */ + + if (setns (mntns_fd, 0) < 0) { - fprintf (stderr, "cannot open %s: %s", path, strerror (errno)); + fprintf (stderr, "cannot join mount namespace for %ld: %m", pid); exit (EXIT_FAILURE); } @@ -404,33 +435,24 @@ static void __attribute__((constructor)) init() setenv ("_CONTAINERS_ROOTLESS_UID", uid_fmt, 1); setenv ("_CONTAINERS_ROOTLESS_GID", gid_fmt, 1); - r = setns (fd, 0); - if (r < 0) - { - fprintf (stderr, "cannot join mount namespace for %ld: %s", pid, strerror (errno)); - exit (EXIT_FAILURE); - } - close (fd); - if (syscall_setresgid (0, 0, 0) < 0) { - fprintf (stderr, "cannot setresgid: %s\n", strerror (errno)); + fprintf (stderr, "cannot setresgid: %m\n"); _exit (EXIT_FAILURE); } if (syscall_setresuid (0, 0, 0) < 0) { - fprintf (stderr, "cannot setresuid: %s\n", strerror (errno)); + fprintf (stderr, "cannot setresuid: %m\n"); _exit (EXIT_FAILURE); } if (chdir (cwd) < 0) { - fprintf (stderr, "cannot chdir to %s: %s\n", cwd, strerror (errno)); + fprintf (stderr, "cannot chdir to %s: %m\n", cwd); _exit (EXIT_FAILURE); } - free (cwd); rootless_uid_init = uid; rootless_gid_init = gid; } @@ -529,7 +551,7 @@ create_pause_process (const char *pause_pid_file_path, char **argv) fd = mkstemp (tmp_file_path); if (fd < 0) { - fprintf (stderr, "error creating temporary file: %s\n", strerror (errno)); + fprintf (stderr, "error creating temporary file: %m\n"); kill (pid, SIGKILL); _exit (EXIT_FAILURE); } @@ -537,7 +559,7 @@ create_pause_process (const char *pause_pid_file_path, char **argv) r = TEMP_FAILURE_RETRY (write (fd, pid_str, strlen (pid_str))); if (r < 0) { - fprintf (stderr, "cannot write to file descriptor: %s\n", strerror (errno)); + fprintf (stderr, "cannot write to file descriptor: %m\n"); kill (pid, SIGKILL); _exit (EXIT_FAILURE); } @@ -555,7 +577,7 @@ create_pause_process (const char *pause_pid_file_path, char **argv) r = TEMP_FAILURE_RETRY (write (p[1], "0", 1)); if (r < 0) { - fprintf (stderr, "cannot write to pipe: %s\n", strerror (errno)); + fprintf (stderr, "cannot write to pipe: %m\n"); _exit (EXIT_FAILURE); } close (p[1]); @@ -590,22 +612,6 @@ create_pause_process (const char *pause_pid_file_path, char **argv) } } -static int -open_namespace (int pid_to_join, const char *ns_file) -{ - char ns_path[PATH_MAX]; - int ret; - - ret = snprintf (ns_path, PATH_MAX, "/proc/%d/ns/%s", pid_to_join, ns_file); - if (ret == PATH_MAX) - { - fprintf (stderr, "internal error: namespace path too long\n"); - return -1; - } - - return open (ns_path, O_CLOEXEC | O_RDONLY); -} - static void join_namespace_or_die (const char *name, int ns_fd) { @@ -619,18 +625,20 @@ join_namespace_or_die (const char *name, int ns_fd) int reexec_userns_join (int pid_to_join, char *pause_pid_file_path) { + cleanup_close int userns_fd = -1; + cleanup_close int mntns_fd = -1; + cleanup_free char *cwd = NULL; char uid[16]; char gid[16]; - char **argv; + cleanup_free char *argv0 = NULL; + cleanup_free char **argv = NULL; int pid; - int mnt_ns = -1; - int user_ns = -1; - char *cwd = getcwd (NULL, 0); sigset_t sigset, oldsigset; + cwd = getcwd (NULL, 0); if (cwd == NULL) { - fprintf (stderr, "error getting current working directory: %s\n", strerror (errno)); + fprintf (stderr, "error getting current working directory: %m\n"); _exit (EXIT_FAILURE); } @@ -640,32 +648,27 @@ reexec_userns_join (int pid_to_join, char *pause_pid_file_path) argv = get_cmd_line_args (); if (argv == NULL) { - fprintf (stderr, "cannot read argv: %s\n", strerror (errno)); + fprintf (stderr, "cannot read argv: %m\n"); _exit (EXIT_FAILURE); } - user_ns = open_namespace (pid_to_join, "user"); - if (user_ns < 0) - return user_ns; - mnt_ns = open_namespace (pid_to_join, "mnt"); - if (mnt_ns < 0) - { - close (user_ns); - return mnt_ns; - } + argv0 = argv[0]; + + userns_fd = open_namespace (pid_to_join, "user"); + if (userns_fd < 0) + return userns_fd; + mntns_fd = open_namespace (pid_to_join, "mnt"); + if (mntns_fd < 0) + return mntns_fd; pid = fork (); if (pid < 0) - fprintf (stderr, "cannot fork: %s\n", strerror (errno)); + fprintf (stderr, "cannot fork: %m\n"); if (pid) { int f; - /* We passed down these fds, close them. */ - close (user_ns); - close (mnt_ns); - for (f = 3; f <= open_files_max_fd; f++) if (is_fd_inherited (f)) close (f); @@ -681,22 +684,22 @@ reexec_userns_join (int pid_to_join, char *pause_pid_file_path) if (sigfillset (&sigset) < 0) { - fprintf (stderr, "cannot fill sigset: %s\n", strerror (errno)); + fprintf (stderr, "cannot fill sigset: %m\n"); _exit (EXIT_FAILURE); } if (sigdelset (&sigset, SIGCHLD) < 0) { - fprintf (stderr, "cannot sigdelset(SIGCHLD): %s\n", strerror (errno)); + fprintf (stderr, "cannot sigdelset(SIGCHLD): %m\n"); _exit (EXIT_FAILURE); } if (sigdelset (&sigset, SIGTERM) < 0) { - fprintf (stderr, "cannot sigdelset(SIGTERM): %s\n", strerror (errno)); + fprintf (stderr, "cannot sigdelset(SIGTERM): %m\n"); _exit (EXIT_FAILURE); } if (sigprocmask (SIG_BLOCK, &sigset, &oldsigset) < 0) { - fprintf (stderr, "cannot block signals: %s\n", strerror (errno)); + fprintf (stderr, "cannot block signals: %m\n"); _exit (EXIT_FAILURE); } @@ -717,33 +720,30 @@ reexec_userns_join (int pid_to_join, char *pause_pid_file_path) if (prctl (PR_SET_PDEATHSIG, SIGTERM, 0, 0, 0) < 0) { - fprintf (stderr, "cannot prctl(PR_SET_PDEATHSIG): %s\n", strerror (errno)); + fprintf (stderr, "cannot prctl(PR_SET_PDEATHSIG): %m\n"); _exit (EXIT_FAILURE); } - join_namespace_or_die ("user", user_ns); - join_namespace_or_die ("mnt", mnt_ns); - close (user_ns); - close (mnt_ns); + join_namespace_or_die ("user", userns_fd); + join_namespace_or_die ("mnt", mntns_fd); if (syscall_setresgid (0, 0, 0) < 0) { - fprintf (stderr, "cannot setresgid: %s\n", strerror (errno)); + fprintf (stderr, "cannot setresgid: %m\n"); _exit (EXIT_FAILURE); } if (syscall_setresuid (0, 0, 0) < 0) { - fprintf (stderr, "cannot setresuid: %s\n", strerror (errno)); + fprintf (stderr, "cannot setresuid: %m\n"); _exit (EXIT_FAILURE); } if (chdir (cwd) < 0) { - fprintf (stderr, "cannot chdir to %s: %s\n", cwd, strerror (errno)); + fprintf (stderr, "cannot chdir to %s: %m\n", cwd); _exit (EXIT_FAILURE); } - free (cwd); if (pause_pid_file_path && pause_pid_file_path[0] != '\0') { @@ -752,7 +752,7 @@ reexec_userns_join (int pid_to_join, char *pause_pid_file_path) } if (sigprocmask (SIG_SETMASK, &oldsigset, NULL) < 0) { - fprintf (stderr, "cannot block signals: %s\n", strerror (errno)); + fprintf (stderr, "cannot block signals: %m\n"); _exit (EXIT_FAILURE); } @@ -784,7 +784,7 @@ static int copy_file_to_fd (const char *file_to_read, int outfd) { char buf[512]; - int fd; + cleanup_close int fd = -1; fd = open (file_to_read, O_RDONLY); if (fd < 0) @@ -796,10 +796,7 @@ copy_file_to_fd (const char *file_to_read, int outfd) r = TEMP_FAILURE_RETRY (read (fd, buf, sizeof buf)); if (r < 0) - { - close (fd); - return r; - } + return r; if (r == 0) break; @@ -808,43 +805,40 @@ copy_file_to_fd (const char *file_to_read, int outfd) { w = TEMP_FAILURE_RETRY (write (outfd, &buf[t], r - t)); if (w < 0) - { - close (fd); - return w; - } + return w; t += w; } } - close (fd); return 0; } int reexec_in_user_namespace (int ready, char *pause_pid_file_path, char *file_to_read, int outputfd) { + cleanup_free char **argv = NULL; + cleanup_free char *argv0 = NULL; + cleanup_free char *cwd = NULL; + sigset_t sigset, oldsigset; int ret; pid_t pid; char b; - char **argv; char uid[16]; char gid[16]; - char *cwd = getcwd (NULL, 0); - sigset_t sigset, oldsigset; + cwd = getcwd (NULL, 0); if (cwd == NULL) { - fprintf (stderr, "error getting current working directory: %s\n", strerror (errno)); + fprintf (stderr, "error getting current working directory: %m\n"); _exit (EXIT_FAILURE); } - sprintf (uid, "%d", geteuid ()); sprintf (gid, "%d", getegid ()); pid = syscall_clone (CLONE_NEWUSER|CLONE_NEWNS|SIGCHLD, NULL); if (pid < 0) { - fprintf (stderr, "cannot clone: %s\n", strerror (errno)); + fprintf (stderr, "cannot clone: %m\n"); check_proc_sys_userns_file (_max_user_namespaces); check_proc_sys_userns_file (_unprivileged_user_namespaces); } @@ -872,32 +866,34 @@ reexec_in_user_namespace (int ready, char *pause_pid_file_path, char *file_to_re if (sigfillset (&sigset) < 0) { - fprintf (stderr, "cannot fill sigset: %s\n", strerror (errno)); + fprintf (stderr, "cannot fill sigset: %m\n"); _exit (EXIT_FAILURE); } if (sigdelset (&sigset, SIGCHLD) < 0) { - fprintf (stderr, "cannot sigdelset(SIGCHLD): %s\n", strerror (errno)); + fprintf (stderr, "cannot sigdelset(SIGCHLD): %m\n"); _exit (EXIT_FAILURE); } if (sigdelset (&sigset, SIGTERM) < 0) { - fprintf (stderr, "cannot sigdelset(SIGTERM): %s\n", strerror (errno)); + fprintf (stderr, "cannot sigdelset(SIGTERM): %m\n"); _exit (EXIT_FAILURE); } if (sigprocmask (SIG_BLOCK, &sigset, &oldsigset) < 0) { - fprintf (stderr, "cannot block signals: %s\n", strerror (errno)); + fprintf (stderr, "cannot block signals: %m\n"); _exit (EXIT_FAILURE); } argv = get_cmd_line_args (); if (argv == NULL) { - fprintf (stderr, "cannot read argv: %s\n", strerror (errno)); + fprintf (stderr, "cannot read argv: %m\n"); _exit (EXIT_FAILURE); } + argv0 = argv[0]; + if (do_socket_activation) { char s[32]; @@ -916,7 +912,7 @@ reexec_in_user_namespace (int ready, char *pause_pid_file_path, char *file_to_re ret = TEMP_FAILURE_RETRY (read (ready, &b, 1)); if (ret < 0) { - fprintf (stderr, "cannot read from sync pipe: %s\n", strerror (errno)); + fprintf (stderr, "cannot read from sync pipe: %m\n"); _exit (EXIT_FAILURE); } if (ret != 1 || b != '0') @@ -924,25 +920,24 @@ reexec_in_user_namespace (int ready, char *pause_pid_file_path, char *file_to_re if (syscall_setresgid (0, 0, 0) < 0) { - fprintf (stderr, "cannot setresgid: %s\n", strerror (errno)); + fprintf (stderr, "cannot setresgid: %m\n"); TEMP_FAILURE_RETRY (write (ready, "1", 1)); _exit (EXIT_FAILURE); } if (syscall_setresuid (0, 0, 0) < 0) { - fprintf (stderr, "cannot setresuid: %s\n", strerror (errno)); + fprintf (stderr, "cannot setresuid: %m\n"); TEMP_FAILURE_RETRY (write (ready, "1", 1)); _exit (EXIT_FAILURE); } if (chdir (cwd) < 0) { - fprintf (stderr, "cannot chdir to %s: %s\n", cwd, strerror (errno)); + fprintf (stderr, "cannot chdir to %s: %m\n", cwd); TEMP_FAILURE_RETRY (write (ready, "1", 1)); _exit (EXIT_FAILURE); } - free (cwd); if (pause_pid_file_path && pause_pid_file_path[0] != '\0') { @@ -956,14 +951,14 @@ reexec_in_user_namespace (int ready, char *pause_pid_file_path, char *file_to_re ret = TEMP_FAILURE_RETRY (write (ready, "0", 1)); if (ret < 0) { - fprintf (stderr, "cannot write to ready pipe: %s\n", strerror (errno)); - _exit (EXIT_FAILURE); + fprintf (stderr, "cannot write to ready pipe: %m\n"); + _exit (EXIT_FAILURE); } close (ready); if (sigprocmask (SIG_SETMASK, &oldsigset, NULL) < 0) { - fprintf (stderr, "cannot block signals: %s\n", strerror (errno)); + fprintf (stderr, "cannot block signals: %m\n"); _exit (EXIT_FAILURE); } 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/generate/pod_create.go b/pkg/specgen/generate/pod_create.go index bfd81739a..72dd249e7 100644 --- a/pkg/specgen/generate/pod_create.go +++ b/pkg/specgen/generate/pod_create.go @@ -29,19 +29,16 @@ func buildPauseImage(rt *libpod.Runtime, rtConfig *config.Config) (string, error return imageName, nil } - // NOTE: Having the pause binary in its own directory keeps the door - // open for replacing the image building with using an overlay root FS. - // The latter turned out to be complex and error prone (see #11956) but - // we may be able to come up with a proper solution at a later point in - // time. - pausePath, err := rtConfig.FindHelperBinary("pause/pause", false) + // Also look into the path as some distributions install catatonit in + // /usr/bin. + catatonitPath, err := rtConfig.FindHelperBinary("catatonit", true) if err != nil { return "", fmt.Errorf("finding pause binary: %w", err) } buildContent := fmt.Sprintf(`FROM scratch -COPY %s /pause -ENTRYPOINT ["/pause"]`, pausePath) +COPY %s /catatonit +ENTRYPOINT ["/catatonit", "-P"]`, catatonitPath) tmpF, err := ioutil.TempFile("", "pause.containerfile") if err != nil { 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/specgenutil/volumes.go b/pkg/specgenutil/volumes.go index 184bfadf8..8ff770f9c 100644 --- a/pkg/specgenutil/volumes.go +++ b/pkg/specgenutil/volumes.go @@ -355,6 +355,8 @@ func getBindMount(args []string) (spec.Mount, error) { newMount.Options = append(newMount.Options, "U") } setOwnership = true + case "idmap": + newMount.Options = append(newMount.Options, "idmap") case "consistency": // Often used on MACs and mistakenly on Linux platforms. // Since Docker ignores this option so shall we. 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/pkg/util/mountOpts.go b/pkg/util/mountOpts.go index f13dc94ec..959763dba 100644 --- a/pkg/util/mountOpts.go +++ b/pkg/util/mountOpts.go @@ -33,6 +33,7 @@ func ProcessOptions(options []string, isTmpfs bool, sourcePath string) ([]string // Some options have parameters - size, mode splitOpt := strings.SplitN(opt, "=", 2) switch splitOpt[0] { + case "idmap": case "O": if len(options) > 1 { return nil, errors.Wrapf(ErrDupeMntOption, "'O' option can not be used with other options") diff --git a/test/apiv2/40-pods.at b/test/apiv2/40-pods.at index f45e85f61..0a5201213 100644 --- a/test/apiv2/40-pods.at +++ b/test/apiv2/40-pods.at @@ -110,11 +110,11 @@ t GET libpod/pods/fakename/top 404 \ .cause="no such pod" t GET libpod/pods/foo/top 200 \ - .Processes[0][-1]="/pause" \ + .Processes[0][-1]="/catatonit -P" \ .Titles[-1]="COMMAND" t GET libpod/pods/foo/top?ps_args=args,pid 200 \ - .Processes[0][0]="/pause" \ + .Processes[0][0]="/catatonit -P" \ .Processes[0][1]="1" \ .Titles[0]="COMMAND" \ .Titles[1]="PID" \ diff --git a/test/e2e/checkpoint_test.go b/test/e2e/checkpoint_test.go index be6b782b5..4963b04fc 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" @@ -33,7 +35,6 @@ var _ = Describe("Podman checkpoint", func() { ) BeforeEach(func() { - SkipIfRemote("checkpoint not supported in remote mode") SkipIfRootless("checkpoint not supported in rootless mode") tempdir, err = CreateTempDirInTempDir() if err != nil { @@ -181,7 +182,7 @@ var _ = Describe("Podman checkpoint", func() { session2.WaitWithDefaultTimeout() Expect(session2).Should(Exit(0)) - result := podmanTest.Podman([]string{"container", "checkpoint", "-l"}) + result := podmanTest.Podman([]string{"container", "checkpoint", "second"}) result.WaitWithDefaultTimeout() Expect(result).Should(Exit(0)) @@ -193,7 +194,7 @@ var _ = Describe("Podman checkpoint", func() { Expect(ps.LineInOutputContains(session1.OutputToString())).To(BeTrue()) Expect(ps.LineInOutputContains(session2.OutputToString())).To(BeFalse()) - result = podmanTest.Podman([]string{"container", "restore", "-l"}) + result = podmanTest.Podman([]string{"container", "restore", "second"}) result.WaitWithDefaultTimeout() Expect(result).Should(Exit(0)) @@ -256,7 +257,7 @@ var _ = Describe("Podman checkpoint", func() { Fail("Container failed to get ready") } - IP := podmanTest.Podman([]string{"inspect", "-l", "--format={{.NetworkSettings.IPAddress}}"}) + IP := podmanTest.Podman([]string{"inspect", cid, "--format={{.NetworkSettings.IPAddress}}"}) IP.WaitWithDefaultTimeout() Expect(IP).Should(Exit(0)) @@ -265,7 +266,7 @@ var _ = Describe("Podman checkpoint", func() { Expect(err).To(BeNil()) // This should fail as the container has established TCP connections - result := podmanTest.Podman([]string{"container", "checkpoint", "-l"}) + result := podmanTest.Podman([]string{"container", "checkpoint", cid}) result.WaitWithDefaultTimeout() Expect(result).Should(Exit(125)) @@ -273,7 +274,7 @@ var _ = Describe("Podman checkpoint", func() { Expect(podmanTest.GetContainerStatus()).To(ContainSubstring("Up")) // Now it should work thanks to "--tcp-established" - result = podmanTest.Podman([]string{"container", "checkpoint", "-l", "--tcp-established"}) + result = podmanTest.Podman([]string{"container", "checkpoint", cid, "--tcp-established"}) result.WaitWithDefaultTimeout() Expect(result).Should(Exit(0)) @@ -281,7 +282,7 @@ var _ = Describe("Podman checkpoint", func() { Expect(podmanTest.GetContainerStatus()).To(ContainSubstring("Exited")) // Restore should fail as the checkpoint image contains established TCP connections - result = podmanTest.Podman([]string{"container", "restore", "-l"}) + result = podmanTest.Podman([]string{"container", "restore", cid}) result.WaitWithDefaultTimeout() Expect(result).Should(Exit(125)) @@ -289,7 +290,7 @@ var _ = Describe("Podman checkpoint", func() { Expect(podmanTest.GetContainerStatus()).To(ContainSubstring("Exited")) // Now it should work thanks to "--tcp-established" - result = podmanTest.Podman([]string{"container", "restore", "-l", "--tcp-established"}) + result = podmanTest.Podman([]string{"container", "restore", cid, "--tcp-established"}) result.WaitWithDefaultTimeout() Expect(result).Should(Exit(0)) @@ -348,11 +349,11 @@ var _ = Describe("Podman checkpoint", func() { session.WaitWithDefaultTimeout() Expect(session).Should(Exit(0)) - IPBefore := podmanTest.Podman([]string{"inspect", "-l", "--format={{.NetworkSettings.IPAddress}}"}) + IPBefore := podmanTest.Podman([]string{"inspect", "test_name", "--format={{.NetworkSettings.IPAddress}}"}) IPBefore.WaitWithDefaultTimeout() Expect(IPBefore).Should(Exit(0)) - MACBefore := podmanTest.Podman([]string{"inspect", "-l", "--format={{.NetworkSettings.MacAddress}}"}) + MACBefore := podmanTest.Podman([]string{"inspect", "test_name", "--format={{.NetworkSettings.MacAddress}}"}) MACBefore.WaitWithDefaultTimeout() Expect(MACBefore).Should(Exit(0)) @@ -366,11 +367,11 @@ var _ = Describe("Podman checkpoint", func() { result = podmanTest.Podman([]string{"container", "restore", "test_name"}) result.WaitWithDefaultTimeout() - IPAfter := podmanTest.Podman([]string{"inspect", "-l", "--format={{.NetworkSettings.IPAddress}}"}) + IPAfter := podmanTest.Podman([]string{"inspect", "test_name", "--format={{.NetworkSettings.IPAddress}}"}) IPAfter.WaitWithDefaultTimeout() Expect(IPAfter).Should(Exit(0)) - MACAfter := podmanTest.Podman([]string{"inspect", "-l", "--format={{.NetworkSettings.MacAddress}}"}) + MACAfter := podmanTest.Podman([]string{"inspect", "test_name", "--format={{.NetworkSettings.MacAddress}}"}) MACAfter.WaitWithDefaultTimeout() Expect(MACAfter).Should(Exit(0)) @@ -401,7 +402,7 @@ var _ = Describe("Podman checkpoint", func() { cid := session.OutputToString() fileName := "/tmp/checkpoint-" + cid + ".tar.gz" - result := podmanTest.Podman([]string{"container", "checkpoint", "-l", "-e", fileName}) + result := podmanTest.Podman([]string{"container", "checkpoint", cid, "-e", fileName}) result.WaitWithDefaultTimeout() // As the container has been started with '--rm' it will be completely @@ -453,7 +454,7 @@ var _ = Describe("Podman checkpoint", func() { fileName := "/tmp/checkpoint-" + cid + ".tar" // Checkpoint with the default algorithm - result := podmanTest.Podman([]string{"container", "checkpoint", "-l", "-e", fileName}) + result := podmanTest.Podman([]string{"container", "checkpoint", cid, "-e", fileName}) result.WaitWithDefaultTimeout() // As the container has been started with '--rm' it will be completely @@ -471,7 +472,7 @@ var _ = Describe("Podman checkpoint", func() { Expect(podmanTest.GetContainerStatus()).To(ContainSubstring("Up")) // Checkpoint with the zstd algorithm - result = podmanTest.Podman([]string{"container", "checkpoint", "-l", "-e", fileName, "--compress", "zstd"}) + result = podmanTest.Podman([]string{"container", "checkpoint", cid, "-e", fileName, "--compress", "zstd"}) result.WaitWithDefaultTimeout() // As the container has been started with '--rm' it will be completely @@ -489,7 +490,7 @@ var _ = Describe("Podman checkpoint", func() { Expect(podmanTest.GetContainerStatus()).To(ContainSubstring("Up")) // Checkpoint with the none algorithm - result = podmanTest.Podman([]string{"container", "checkpoint", "-l", "-e", fileName, "-c", "none"}) + result = podmanTest.Podman([]string{"container", "checkpoint", cid, "-e", fileName, "-c", "none"}) result.WaitWithDefaultTimeout() // As the container has been started with '--rm' it will be completely @@ -507,7 +508,7 @@ var _ = Describe("Podman checkpoint", func() { Expect(podmanTest.GetContainerStatus()).To(ContainSubstring("Up")) // Checkpoint with the gzip algorithm - result = podmanTest.Podman([]string{"container", "checkpoint", "-l", "-e", fileName, "-c", "gzip"}) + result = podmanTest.Podman([]string{"container", "checkpoint", cid, "-e", fileName, "-c", "gzip"}) result.WaitWithDefaultTimeout() // As the container has been started with '--rm' it will be completely @@ -525,7 +526,7 @@ var _ = Describe("Podman checkpoint", func() { Expect(podmanTest.GetContainerStatus()).To(ContainSubstring("Up")) // Checkpoint with the non-existing algorithm - result = podmanTest.Podman([]string{"container", "checkpoint", "-l", "-e", fileName, "-c", "non-existing"}) + result = podmanTest.Podman([]string{"container", "checkpoint", cid, "-e", fileName, "-c", "non-existing"}) result.WaitWithDefaultTimeout() Expect(result).Should(Exit(125)) @@ -553,15 +554,15 @@ var _ = Describe("Podman checkpoint", func() { fileName := "/tmp/checkpoint-" + cid + ".tar.gz" // Change the container's root file-system - result := podmanTest.Podman([]string{"exec", "-l", "/bin/sh", "-c", "echo test" + cid + "test > /test.output"}) + result := podmanTest.Podman([]string{"exec", cid, "/bin/sh", "-c", "echo test" + cid + "test > /test.output"}) result.WaitWithDefaultTimeout() Expect(result).Should(Exit(0)) - result = podmanTest.Podman([]string{"exec", "-l", "/bin/sh", "-c", "rm /etc/motd"}) + result = podmanTest.Podman([]string{"exec", cid, "/bin/sh", "-c", "rm /etc/motd"}) result.WaitWithDefaultTimeout() Expect(result).Should(Exit(0)) - result = podmanTest.Podman([]string{"diff", "-l"}) + result = podmanTest.Podman([]string{"diff", cid}) result.WaitWithDefaultTimeout() Expect(result).Should(Exit(0)) Expect(result.OutputToString()).To(ContainSubstring("C /etc")) @@ -570,7 +571,7 @@ var _ = Describe("Podman checkpoint", func() { Expect(len(result.OutputToStringArray())).To(Equal(3)) // Checkpoint the container - result = podmanTest.Podman([]string{"container", "checkpoint", "-l", "-e", fileName}) + result = podmanTest.Podman([]string{"container", "checkpoint", cid, "-e", fileName}) result.WaitWithDefaultTimeout() Expect(result).Should(Exit(0)) @@ -587,12 +588,12 @@ var _ = Describe("Podman checkpoint", func() { Expect(podmanTest.GetContainerStatus()).To(ContainSubstring("Up")) // Verify the changes to the container's root file-system - result = podmanTest.Podman([]string{"exec", "-l", "cat", "/test.output"}) + result = podmanTest.Podman([]string{"exec", cid, "cat", "/test.output"}) result.WaitWithDefaultTimeout() Expect(result).Should(Exit(0)) Expect(result.OutputToString()).To(ContainSubstring("test" + cid + "test")) - result = podmanTest.Podman([]string{"diff", "-l"}) + result = podmanTest.Podman([]string{"diff", cid}) result.WaitWithDefaultTimeout() Expect(result).Should(Exit(0)) Expect(result.OutputToString()).To(ContainSubstring("C /etc")) @@ -614,12 +615,12 @@ var _ = Describe("Podman checkpoint", func() { fileName := "/tmp/checkpoint-" + cid + ".tar.gz" // Change the container's root file-system - result := podmanTest.Podman([]string{"exec", "-l", "/bin/sh", "-c", "echo test" + cid + "test > /test.output"}) + result := podmanTest.Podman([]string{"exec", cid, "/bin/sh", "-c", "echo test" + cid + "test > /test.output"}) result.WaitWithDefaultTimeout() Expect(result).Should(Exit(0)) // Checkpoint the container - result = podmanTest.Podman([]string{"container", "checkpoint", "-l", "-e", fileName}) + result = podmanTest.Podman([]string{"container", "checkpoint", cid, "-e", fileName}) result.WaitWithDefaultTimeout() Expect(result).Should(Exit(0)) @@ -636,7 +637,7 @@ var _ = Describe("Podman checkpoint", func() { Expect(podmanTest.GetContainerStatus()).To(ContainSubstring("Up")) // Verify the changes to the container's root file-system - result = podmanTest.Podman([]string{"exec", "-l", "cat", "/test.output"}) + result = podmanTest.Podman([]string{"exec", cid, "cat", "/test.output"}) result.WaitWithDefaultTimeout() Expect(result).Should(Exit(1)) Expect(result.ErrorToString()).To(ContainSubstring("cat: can't open '/test.output': No such file or directory")) @@ -655,12 +656,12 @@ var _ = Describe("Podman checkpoint", func() { fileName := "/tmp/checkpoint-" + cid + ".tar.gz" // Change the container's root file-system - result := podmanTest.Podman([]string{"exec", "-l", "/bin/sh", "-c", "echo test" + cid + "test > /test.output"}) + result := podmanTest.Podman([]string{"exec", cid, "/bin/sh", "-c", "echo test" + cid + "test > /test.output"}) result.WaitWithDefaultTimeout() Expect(result).Should(Exit(0)) // Checkpoint the container - result = podmanTest.Podman([]string{"container", "checkpoint", "--ignore-rootfs", "-l", "-e", fileName}) + result = podmanTest.Podman([]string{"container", "checkpoint", "--ignore-rootfs", cid, "-e", fileName}) result.WaitWithDefaultTimeout() Expect(result).Should(Exit(0)) @@ -677,7 +678,7 @@ var _ = Describe("Podman checkpoint", func() { Expect(podmanTest.GetContainerStatus()).To(ContainSubstring("Up")) // Verify the changes to the container's root file-system - result = podmanTest.Podman([]string{"exec", "-l", "cat", "/test.output"}) + result = podmanTest.Podman([]string{"exec", cid, "cat", "/test.output"}) result.WaitWithDefaultTimeout() Expect(result).Should(Exit(1)) Expect(result.ErrorToString()).To(ContainSubstring("cat: can't open '/test.output': No such file or directory")) @@ -697,7 +698,7 @@ var _ = Describe("Podman checkpoint", func() { fileName := "/tmp/checkpoint-" + cid + ".tar.gz" // Checkpoint the container - result := podmanTest.Podman([]string{"container", "checkpoint", "-l", "-e", fileName}) + result := podmanTest.Podman([]string{"container", "checkpoint", cid, "-e", fileName}) result.WaitWithDefaultTimeout() Expect(result).Should(Exit(0)) @@ -714,11 +715,11 @@ var _ = Describe("Podman checkpoint", func() { Expect(podmanTest.GetContainerStatus()).To(ContainSubstring("Up")) // Exec in the container - result = podmanTest.Podman([]string{"exec", "-l", "/bin/sh", "-c", "echo " + cid + " > /test.output"}) + result = podmanTest.Podman([]string{"exec", cid, "/bin/sh", "-c", "echo " + cid + " > /test.output"}) result.WaitWithDefaultTimeout() Expect(result).Should(Exit(0)) - result = podmanTest.Podman([]string{"exec", "-l", "cat", "/test.output"}) + result = podmanTest.Podman([]string{"exec", cid, "cat", "/test.output"}) result.WaitWithDefaultTimeout() Expect(result).Should(Exit(0)) Expect(result.OutputToString()).To(ContainSubstring(cid)) @@ -736,7 +737,7 @@ var _ = Describe("Podman checkpoint", func() { Expect(podmanTest.NumberOfContainersRunning()).To(Equal(1)) // Checkpoint the container - this should fail as it was started with --rm - result := podmanTest.Podman([]string{"container", "checkpoint", "-l"}) + result := podmanTest.Podman([]string{"container", "checkpoint", cid}) result.WaitWithDefaultTimeout() Expect(result).To(ExitWithError()) Expect(result.ErrorToString()).To(ContainSubstring("cannot checkpoint containers that have been started with '--rm'")) @@ -744,7 +745,7 @@ var _ = Describe("Podman checkpoint", func() { // Checkpointing with --export should still work fileName := "/tmp/checkpoint-" + cid + ".tar.gz" - result = podmanTest.Podman([]string{"container", "checkpoint", "-l", "-e", fileName}) + result = podmanTest.Podman([]string{"container", "checkpoint", cid, "-e", fileName}) result.WaitWithDefaultTimeout() // As the container has been started with '--rm' it will be completely @@ -794,21 +795,21 @@ var _ = Describe("Podman checkpoint", func() { // Add file in volume0 result := podmanTest.Podman([]string{ - "exec", "-l", "/bin/sh", "-c", "echo " + cid + " > /volume0/test.output", + "exec", cid, "/bin/sh", "-c", "echo " + cid + " > /volume0/test.output", }) result.WaitWithDefaultTimeout() Expect(result).Should(Exit(0)) // Add file in volume1 result = podmanTest.Podman([]string{ - "exec", "-l", "/bin/sh", "-c", "echo " + cid + " > /volume1/test.output", + "exec", cid, "/bin/sh", "-c", "echo " + cid + " > /volume1/test.output", }) result.WaitWithDefaultTimeout() Expect(result).Should(Exit(0)) // Add file in volume2 result = podmanTest.Podman([]string{ - "exec", "-l", "/bin/sh", "-c", "echo " + cid + " > /volume2/test.output", + "exec", cid, "/bin/sh", "-c", "echo " + cid + " > /volume2/test.output", }) result.WaitWithDefaultTimeout() Expect(result).Should(Exit(0)) @@ -816,7 +817,7 @@ var _ = Describe("Podman checkpoint", func() { checkpointFileName := "/tmp/checkpoint-" + cid + ".tar.gz" // Checkpoint the container - result = podmanTest.Podman([]string{"container", "checkpoint", "-l", "-e", checkpointFileName}) + result = podmanTest.Podman([]string{"container", "checkpoint", cid, "-e", checkpointFileName}) result.WaitWithDefaultTimeout() Expect(result).Should(Exit(0)) Expect(podmanTest.NumberOfContainersRunning()).To(Equal(0)) @@ -844,19 +845,19 @@ var _ = Describe("Podman checkpoint", func() { Expect(podmanTest.GetContainerStatus()).To(ContainSubstring("Up")) // Validate volume0 content - result = podmanTest.Podman([]string{"exec", "-l", "cat", "/volume0/test.output"}) + result = podmanTest.Podman([]string{"exec", cid, "cat", "/volume0/test.output"}) result.WaitWithDefaultTimeout() Expect(result).Should(Exit(0)) Expect(result.OutputToString()).To(ContainSubstring(cid)) // Validate volume1 content - result = podmanTest.Podman([]string{"exec", "-l", "cat", "/volume1/test.output"}) + result = podmanTest.Podman([]string{"exec", cid, "cat", "/volume1/test.output"}) result.WaitWithDefaultTimeout() Expect(result).Should(Exit(0)) Expect(result.OutputToString()).To(ContainSubstring(cid)) // Validate volume2 content - result = podmanTest.Podman([]string{"exec", "-l", "cat", "/volume2/test.output"}) + result = podmanTest.Podman([]string{"exec", cid, "cat", "/volume2/test.output"}) result.WaitWithDefaultTimeout() Expect(result).Should(Exit(0)) Expect(result.OutputToString()).To(ContainSubstring(cid)) @@ -898,6 +899,7 @@ var _ = Describe("Podman checkpoint", func() { }) It("podman checkpoint container with --pre-checkpoint and export (migration)", func() { + SkipIfRemote("--import-previous is not yet supported on the remote client") if !strings.Contains(podmanTest.OCIRuntime, "runc") { Skip("Test only works on runc 1.0-rc3 or higher.") } @@ -960,7 +962,7 @@ var _ = Describe("Podman checkpoint", func() { conn.Close() // Checkpoint the container - result := podmanTest.Podman([]string{"container", "checkpoint", "-l", "-e", fileName}) + result := podmanTest.Podman([]string{"container", "checkpoint", cid, "-e", fileName}) result.WaitWithDefaultTimeout() // As the container has been started with '--rm' it will be completely @@ -982,6 +984,7 @@ var _ = Describe("Podman checkpoint", func() { // Open a network connection to the redis server via initial port mapping // This should fail conn, err = net.DialTimeout("tcp4", fmt.Sprintf("localhost:%d", randomPort), time.Duration(3)*time.Second) + Expect(err).ToNot(BeNil()) Expect(err.Error()).To(ContainSubstring("connection refused")) // Open a network connection to the redis server via new port mapping fmt.Fprintf(os.Stderr, "Trying to reconnect to redis server at localhost:%d", newRandomPort) @@ -1021,7 +1024,7 @@ var _ = Describe("Podman checkpoint", func() { Skip("CRIU is missing or too old.") } if !crutils.CRRuntimeSupportsPodCheckpointRestore(podmanTest.OCIRuntime) { - Skip("runtime does not support pod restore") + Skip("runtime does not support pod restore: " + podmanTest.OCIRuntime) } // Create a pod session := podmanTest.Podman([]string{ @@ -1168,7 +1171,7 @@ var _ = Describe("Podman checkpoint", func() { cid := session.OutputToString() fileName := "/tmp/checkpoint-" + cid + ".tar.gz" - result := podmanTest.Podman([]string{"container", "checkpoint", "-l", "-e", fileName}) + result := podmanTest.Podman([]string{"container", "checkpoint", cid, "-e", fileName}) result.WaitWithDefaultTimeout() // As the container has been started with '--rm' it will be completely @@ -1210,7 +1213,7 @@ var _ = Describe("Podman checkpoint", func() { result := podmanTest.Podman([]string{ "container", "checkpoint", - "-l", "-e", + cid, "-e", fileName, }) result.WaitWithDefaultTimeout() @@ -1244,4 +1247,134 @@ 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)) + }) + + It("podman checkpoint and restore container with --file-locks", func() { + if !strings.Contains(podmanTest.OCIRuntime, "runc") { + // TODO: Enable test for crun when this feature has been released + // https://github.com/containers/crun/pull/783 + Skip("FIXME: requires crun >= 1.4") + } + localRunString := getRunString([]string{"--name", "test_name", ALPINE, "flock", "test.lock", "sleep", "100"}) + session := podmanTest.Podman(localRunString) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + + // Checkpoint is expected to fail without --file-locks + result := podmanTest.Podman([]string{"container", "checkpoint", "test_name"}) + result.WaitWithDefaultTimeout() + Expect(result).Should(Exit(125)) + Expect(result.ErrorToString()).To(ContainSubstring("criu failed")) + Expect(podmanTest.NumberOfContainersRunning()).To(Equal(1)) + + // Checkpoint is expected to succeed with --file-locks + result = podmanTest.Podman([]string{"container", "checkpoint", "--file-locks", "test_name"}) + result.WaitWithDefaultTimeout() + Expect(result).Should(Exit(0)) + Expect(podmanTest.NumberOfContainersRunning()).To(Equal(0)) + Expect(podmanTest.GetContainerStatus()).To(ContainSubstring("Exited")) + + result = podmanTest.Podman([]string{"container", "restore", "--file-locks", "test_name"}) + result.WaitWithDefaultTimeout() + + Expect(result).Should(Exit(0)) + Expect(podmanTest.NumberOfContainersRunning()).To(Equal(1)) + Expect(podmanTest.GetContainerStatus()).To(ContainSubstring("Up")) + + result = podmanTest.Podman([]string{"rm", "-t", "0", "-f", "test_name"}) + result.WaitWithDefaultTimeout() + Expect(result).Should(Exit(0)) + Expect(podmanTest.NumberOfContainersRunning()).To(Equal(0)) + }) }) diff --git a/test/e2e/config.go b/test/e2e/config.go index 2552595ad..9c810575b 100644 --- a/test/e2e/config.go +++ b/test/e2e/config.go @@ -2,7 +2,7 @@ package integration var ( redis = "quay.io/libpod/redis:alpine" - fedoraMinimal = "quay.io/libpod/fedora-minimal:latest" + fedoraMinimal = "registry.fedoraproject.org/fedora-minimal:34" ALPINE = "quay.io/libpod/alpine:latest" ALPINELISTTAG = "quay.io/libpod/alpine:3.10.2" ALPINELISTDIGEST = "quay.io/libpod/alpine@sha256:fa93b01658e3a5a1686dc3ae55f170d8de487006fb53a28efcd12ab0710a2e5f" diff --git a/test/e2e/images_test.go b/test/e2e/images_test.go index 56af64f04..b07e287ac 100644 --- a/test/e2e/images_test.go +++ b/test/e2e/images_test.go @@ -144,7 +144,7 @@ var _ = Describe("Podman images", func() { result := podmanTest.Podman([]string{"images", "-q", "-f", "reference=quay.io*"}) result.WaitWithDefaultTimeout() Expect(result).Should(Exit(0)) - Expect(len(result.OutputToStringArray())).To(Equal(8)) + Expect(len(result.OutputToStringArray())).To(Equal(7)) retalpine := podmanTest.Podman([]string{"images", "-f", "reference=a*pine"}) retalpine.WaitWithDefaultTimeout() diff --git a/test/e2e/pod_create_test.go b/test/e2e/pod_create_test.go index 12aeffd1b..f5a2caad7 100644 --- a/test/e2e/pod_create_test.go +++ b/test/e2e/pod_create_test.go @@ -369,13 +369,13 @@ var _ = Describe("Podman pod create", func() { check1 := podmanTest.Podman([]string{"container", "inspect", "--format", "{{.Config.Entrypoint}}", data.Containers[0].ID}) check1.WaitWithDefaultTimeout() Expect(check1).Should(Exit(0)) - Expect(check1.OutputToString()).To(Equal("/pause")) + Expect(check1.OutputToString()).To(Equal("/catatonit -P")) // check the Path and Args check2 := podmanTest.Podman([]string{"container", "inspect", "--format", "{{.Path}}:{{.Args}}", data.Containers[0].ID}) check2.WaitWithDefaultTimeout() Expect(check2).Should(Exit(0)) - Expect(check2.OutputToString()).To(Equal("/pause:[/pause]")) + Expect(check2.OutputToString()).To(Equal("/catatonit:[-P]")) }) It("podman create pod with --infra-command", func() { diff --git a/test/e2e/run_test.go b/test/e2e/run_test.go index ed2d8938d..05cb986c6 100644 --- a/test/e2e/run_test.go +++ b/test/e2e/run_test.go @@ -1516,7 +1516,7 @@ USER mail`, BB) }) It("podman run --privileged and --group-add", func() { - groupName := "kvm" + groupName := "mail" session := podmanTest.Podman([]string{"run", "-t", "-i", "--group-add", groupName, "--privileged", fedoraMinimal, "groups"}) session.WaitWithDefaultTimeout() Expect(session).Should(Exit(0)) @@ -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 diff --git a/test/system/700-play.bats b/test/system/700-play.bats index c3e5e9354..b77d41920 100644 --- a/test/system/700-play.bats +++ b/test/system/700-play.bats @@ -11,7 +11,7 @@ function teardown() { run_podman rm -t 0 -f -a run_podman image list --format '{{.ID}} {{.Repository}}' while read id name; do - if [[ "$name" =~ /pause ]]; then + if [[ "$name" =~ /podman-pause ]]; then run_podman rmi $id fi done <<<"$output" diff --git a/utils/utils.go b/utils/utils.go index 109ae088b..f2e7beef9 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -5,6 +5,7 @@ import ( "fmt" "io" "io/ioutil" + "math/rand" "os" "os/exec" "strconv" @@ -203,7 +204,16 @@ func moveProcessToScope(pidPath, slice, scope string) error { // MovePauseProcessToScope moves the pause process used for rootless mode to keep the namespaces alive to // a separate scope. func MovePauseProcessToScope(pausePidPath string) { - err := moveProcessToScope(pausePidPath, "user.slice", "podman-pause.scope") + var err error + + for i := 0; i < 3; i++ { + r := rand.Int() + err = moveProcessToScope(pausePidPath, "user.slice", fmt.Sprintf("podman-pause-%d.scope", r)) + if err == nil { + return + } + } + if err != nil { unified, err2 := cgroups.IsCgroup2UnifiedMode() if err2 != nil { diff --git a/utils/utils_supported.go b/utils/utils_supported.go index 1404e3194..0f0c9a9ba 100644 --- a/utils/utils_supported.go +++ b/utils/utils_supported.go @@ -44,15 +44,6 @@ func RunUnderSystemdScope(pid int, slice string, unitName string) error { ch := make(chan string) _, err = conn.StartTransientUnit(unitName, "replace", properties, ch) if err != nil { - // On errors check if the cgroup already exists, if it does move the process there - if props, err := conn.GetUnitTypeProperties(unitName, "Scope"); err == nil { - if cgroup, ok := props["ControlGroup"].(string); ok && cgroup != "" { - if err := moveUnderCgroup(cgroup, "", []uint32{uint32(pid)}); err == nil { - return nil - } - // On errors return the original error message we got from StartTransientUnit. - } - } return err } diff --git a/vendor/github.com/containers/common/libimage/manifests/manifests.go b/vendor/github.com/containers/common/libimage/manifests/manifests.go index 8d1abfba9..45223cc2f 100644 --- a/vendor/github.com/containers/common/libimage/manifests/manifests.go +++ b/vendor/github.com/containers/common/libimage/manifests/manifests.go @@ -353,9 +353,12 @@ func (l *list) Add(ctx context.Context, sys *types.SystemContext, ref types.Imag } if instanceInfo.OS == "" { instanceInfo.OS = config.OS + instanceInfo.OSVersion = config.OSVersion + instanceInfo.OSFeatures = config.OSFeatures } if instanceInfo.Architecture == "" { instanceInfo.Architecture = config.Architecture + instanceInfo.Variant = config.Variant } } manifestBytes, manifestType, err := src.GetManifest(ctx, instanceInfo.instanceDigest) diff --git a/vendor/github.com/containers/common/pkg/config/config.go b/vendor/github.com/containers/common/pkg/config/config.go index 2eda0290a..1a5370a39 100644 --- a/vendor/github.com/containers/common/pkg/config/config.go +++ b/vendor/github.com/containers/common/pkg/config/config.go @@ -563,6 +563,10 @@ func NewConfig(userConfigPath string) (*Config, error) { return nil, err } + if err := config.setupEnv(); err != nil { + return nil, err + } + return config, nil } @@ -1146,7 +1150,14 @@ func (c *Config) ActiveDestination() (uri, identity string, err error) { // FindHelperBinary will search the given binary name in the configured directories. // If searchPATH is set to true it will also search in $PATH. func (c *Config) FindHelperBinary(name string, searchPATH bool) (string, error) { - for _, path := range c.Engine.HelperBinariesDir { + dir_list := c.Engine.HelperBinariesDir + + // If set, search this directory first. This is used in testing. + if dir, found := os.LookupEnv("CONTAINERS_HELPER_BINARY_DIR"); found { + dir_list = append([]string{dir}, dir_list...) + } + + for _, path := range dir_list { fullpath := filepath.Join(path, name) if fi, err := os.Stat(fullpath); err == nil && fi.Mode().IsRegular() { return fullpath, nil @@ -1180,3 +1191,23 @@ func (c *Config) ImageCopyTmpDir() (string, error) { return "", errors.Errorf("invalid image_copy_tmp_dir value %q (relative paths are not accepted)", c.Engine.ImageCopyTmpDir) } + +// setupEnv sets the environment variables for the engine +func (c *Config) setupEnv() error { + for _, env := range c.Engine.Env { + splitEnv := strings.SplitN(env, "=", 2) + if len(splitEnv) != 2 { + logrus.Warnf("invalid environment variable for engine %s, valid configuration is KEY=value pair", env) + continue + } + // skip if the env is already defined + if _, ok := os.LookupEnv(splitEnv[0]); ok { + logrus.Debugf("environment variable %s is already defined, skip the settings from containers.conf", splitEnv[0]) + continue + } + if err := os.Setenv(splitEnv[0], splitEnv[1]); err != nil { + return err + } + } + return nil +} diff --git a/vendor/github.com/containers/common/pkg/parse/parse.go b/vendor/github.com/containers/common/pkg/parse/parse.go index 02e670c50..fda129c83 100644 --- a/vendor/github.com/containers/common/pkg/parse/parse.go +++ b/vendor/github.com/containers/common/pkg/parse/parse.go @@ -66,6 +66,7 @@ func ValidateVolumeOpts(options []string) ([]string, error) { // are intended to be always safe to use, even not on OS // X). continue + case "idmap": default: return nil, errors.Errorf("invalid option type %q", opt) } diff --git a/vendor/modules.txt b/vendor/modules.txt index 9b6bd34bf..916cf41ae 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -97,7 +97,7 @@ github.com/containers/buildah/pkg/rusage github.com/containers/buildah/pkg/sshagent github.com/containers/buildah/pkg/util github.com/containers/buildah/util -# github.com/containers/common v0.46.1-0.20211110143743-73e7b462c358 +# github.com/containers/common v0.46.1-0.20211115170340-7ae7bd1c3f8e github.com/containers/common/libimage github.com/containers/common/libimage/manifests github.com/containers/common/pkg/apparmor |