diff options
30 files changed, 269 insertions, 115 deletions
@@ -295,7 +295,7 @@ $(SRCBINDIR)/podman$(BINSFX): $(SRCBINDIR) $(SOURCES) go.mod go.sum $(SRCBINDIR)/podman-remote-static: $(SRCBINDIR) $(SOURCES) go.mod go.sum CGO_ENABLED=0 \ - GOOS=$(GOOS) \ + GOOS=linux \ GOARCH=$(GOARCH) \ $(GO) build \ $(BUILDFLAGS) \ diff --git a/cmd/podman/machine/ssh.go b/cmd/podman/machine/ssh.go index 4a86da67a..8261f3607 100644 --- a/cmd/podman/machine/ssh.go +++ b/cmd/podman/machine/ssh.go @@ -9,6 +9,7 @@ import ( "github.com/containers/common/pkg/completion" "github.com/containers/common/pkg/config" "github.com/containers/podman/v4/cmd/podman/registry" + "github.com/containers/podman/v4/cmd/podman/utils" "github.com/containers/podman/v4/pkg/machine" "github.com/pkg/errors" "github.com/spf13/cobra" @@ -89,7 +90,8 @@ func ssh(cmd *cobra.Command, args []string) error { if err != nil { return errors.Wrapf(err, "vm %s not found", vmName) } - return vm.SSH(vmName, sshOpts) + err = vm.SSH(vmName, sshOpts) + return utils.HandleOSExecError(err) } func remoteConnectionUsername() (string, error) { diff --git a/cmd/podman/system/unshare.go b/cmd/podman/system/unshare.go index 0ae5b81ad..1ed08eac3 100644 --- a/cmd/podman/system/unshare.go +++ b/cmd/podman/system/unshare.go @@ -2,10 +2,10 @@ package system import ( "os" - "os/exec" "github.com/containers/common/pkg/completion" "github.com/containers/podman/v4/cmd/podman/registry" + "github.com/containers/podman/v4/cmd/podman/utils" "github.com/containers/podman/v4/pkg/domain/entities" "github.com/containers/podman/v4/pkg/rootless" "github.com/pkg/errors" @@ -60,22 +60,5 @@ func unshare(cmd *cobra.Command, args []string) error { } err := registry.ContainerEngine().Unshare(registry.Context(), args, unshareOptions) - if err != nil { - if exitError, ok := err.(*exec.ExitError); ok { - // the user command inside the unshare env has failed - // we set the exit code, do not return the error to the user - // otherwise "exit status X" will be printed - registry.SetExitCode(exitError.ExitCode()) - return nil - } - // cmd.Run() can return fs.ErrNotExist, fs.ErrPermission or exec.ErrNotFound - // follow podman run/exec standard with the exit codes - if errors.Is(err, os.ErrNotExist) || errors.Is(err, exec.ErrNotFound) { - registry.SetExitCode(127) - } else if errors.Is(err, os.ErrPermission) { - registry.SetExitCode(126) - } - return err - } - return nil + return utils.HandleOSExecError(err) } diff --git a/cmd/podman/utils/error.go b/cmd/podman/utils/error.go index 2aaa71373..3efff0301 100644 --- a/cmd/podman/utils/error.go +++ b/cmd/podman/utils/error.go @@ -4,10 +4,12 @@ import ( "errors" "fmt" "os" + "os/exec" "strconv" "strings" buildahCLI "github.com/containers/buildah/pkg/cli" + "github.com/containers/podman/v4/cmd/podman/registry" ) type OutputErrors []error @@ -43,3 +45,33 @@ func ExitCodeFromBuildError(errorMsg string) (int, error) { } return buildahCLI.ExecErrorCodeGeneric, errors.New("message does not contains a valid exit code") } + +// HandleOSExecError checks the given error for an exec.ExitError error and +// sets the same podman exit code as the error. +// No error will be returned in this case to make sure things like podman +// unshare false work correctly without extra output. +// When the exec file does not exists we set the exit code to 127, for +// permission errors 126 is used as exit code. In this case we still return +// the error so the user gets an error message. +// If the error is nil it returns nil. +func HandleOSExecError(err error) error { + if err == nil { + return nil + } + var exitError *exec.ExitError + if errors.As(err, &exitError) { + // the user command inside the unshare/ssh env has failed + // we set the exit code, do not return the error to the user + // otherwise "exit status X" will be printed + registry.SetExitCode(exitError.ExitCode()) + return nil + } + // cmd.Run() can return fs.ErrNotExist, fs.ErrPermission or exec.ErrNotFound + // follow podman run/exec standard with the exit codes + if errors.Is(err, os.ErrNotExist) || errors.Is(err, exec.ErrNotFound) { + registry.SetExitCode(127) + } else if errors.Is(err, os.ErrPermission) { + registry.SetExitCode(126) + } + return err +} diff --git a/cmd/podman/utils/signals_linux.go b/cmd/podman/utils/signals_linux.go deleted file mode 100644 index dd0507c0e..000000000 --- a/cmd/podman/utils/signals_linux.go +++ /dev/null @@ -1,15 +0,0 @@ -//go:build !windows -// +build !windows - -package utils - -import ( - "os" - - "golang.org/x/sys/unix" -) - -// Platform specific signal synonyms -var ( - SIGHUP os.Signal = unix.SIGHUP -) diff --git a/cmd/podman/utils/signals_windows.go b/cmd/podman/utils/signals_windows.go deleted file mode 100644 index e6fcc1b32..000000000 --- a/cmd/podman/utils/signals_windows.go +++ /dev/null @@ -1,15 +0,0 @@ -//go:build windows -// +build windows - -package utils - -import ( - "os" - - "golang.org/x/sys/windows" -) - -// Platform specific signal synonyms -var ( - SIGHUP os.Signal = windows.SIGHUP -) diff --git a/cmd/podman/volumes/export.go b/cmd/podman/volumes/export.go index 5086323f9..113f79a0b 100644 --- a/cmd/podman/volumes/export.go +++ b/cmd/podman/volumes/export.go @@ -8,6 +8,7 @@ import ( "github.com/containers/podman/v4/cmd/podman/common" "github.com/containers/podman/v4/cmd/podman/registry" "github.com/containers/podman/v4/pkg/domain/entities" + "github.com/containers/podman/v4/pkg/errorhandling" "github.com/containers/podman/v4/utils" "github.com/pkg/errors" "github.com/sirupsen/logrus" @@ -58,10 +59,13 @@ func export(cmd *cobra.Command, args []string) error { return errors.New("expects output path, use --output=[path]") } inspectOpts.Type = common.VolumeType - volumeData, _, err := containerEngine.VolumeInspect(ctx, args, inspectOpts) + volumeData, errs, err := containerEngine.VolumeInspect(ctx, args, inspectOpts) if err != nil { return err } + if len(errs) > 0 { + return errorhandling.JoinErrors(errs) + } if len(volumeData) < 1 { return errors.New("no volume data found") } diff --git a/cmd/podman/volumes/import.go b/cmd/podman/volumes/import.go index 988c5536d..76a311643 100644 --- a/cmd/podman/volumes/import.go +++ b/cmd/podman/volumes/import.go @@ -8,6 +8,7 @@ import ( "github.com/containers/podman/v4/cmd/podman/parse" "github.com/containers/podman/v4/cmd/podman/registry" "github.com/containers/podman/v4/pkg/domain/entities" + "github.com/containers/podman/v4/pkg/errorhandling" "github.com/containers/podman/v4/utils" "github.com/pkg/errors" "github.com/spf13/cobra" @@ -60,10 +61,14 @@ func importVol(cmd *cobra.Command, args []string) error { } inspectOpts.Type = common.VolumeType - volumeData, _, err := containerEngine.VolumeInspect(ctx, volumes, inspectOpts) + inspectOpts.Type = common.VolumeType + volumeData, errs, err := containerEngine.VolumeInspect(ctx, volumes, inspectOpts) if err != nil { return err } + if len(errs) > 0 { + return errorhandling.JoinErrors(errs) + } if len(volumeData) < 1 { return errors.New("no volume data found") } diff --git a/contrib/systemd/system/podman-restart.service.in b/contrib/systemd/system/podman-restart.service.in index 1f13e57e1..de0249381 100644 --- a/contrib/systemd/system/podman-restart.service.in +++ b/contrib/systemd/system/podman-restart.service.in @@ -2,12 +2,15 @@ Description=Podman Start All Containers With Restart Policy Set To Always Documentation=man:podman-start(1) StartLimitIntervalSec=0 +Wants=network-online.target +After=network-online.target [Service] Type=oneshot RemainAfterExit=true Environment=LOGGING="--log-level=info" ExecStart=@@PODMAN@@ $LOGGING start --all --filter restart-policy=always +ExecStop=/bin/sh -c '@@PODMAN@@ $LOGGING stop $(@@PODMAN@@ container ls --filter restart-policy=always -q)' [Install] WantedBy=default.target diff --git a/docs/source/markdown/podman-machine-ssh.1.md b/docs/source/markdown/podman-machine-ssh.1.md index db0350961..6a1455df1 100644 --- a/docs/source/markdown/podman-machine-ssh.1.md +++ b/docs/source/markdown/podman-machine-ssh.1.md @@ -14,6 +14,7 @@ first argument must be the virtual machine name. The optional command to execute can then follow. If no command is provided, an interactive session with the virtual machine is established. +The exit code from ssh command will be forwarded to the podman machine ssh caller, see [Exit Codes](#Exit-Codes). ## OPTIONS @@ -25,6 +26,35 @@ Print usage statement. Username to use when SSH-ing into the VM. +## Exit Codes + +The exit code from `podman machine ssh` gives information about why the command failed. +When `podman machine ssh` commands exit with a non-zero code, +the exit codes follow the `chroot` standard, see below: + + **125** The error is with podman **_itself_** + + $ podman machine ssh --foo; echo $? + Error: unknown flag: --foo + 125 + + **126** Executing a _contained command_ and the _command_ cannot be invoked + + $ podman machine ssh /etc; echo $? + Error: fork/exec /etc: permission denied + 126 + + **127** Executing a _contained command_ and the _command_ cannot be found + + $ podman machine ssh foo; echo $? + Error: fork/exec /usr/bin/bogus: no such file or directory + 127 + + **Exit code** _contained command_ exit code + + $ podman machine ssh /bin/sh -c 'exit 3'; echo $? + 3 + ## EXAMPLES To get an interactive session with the default virtual machine: diff --git a/docs/source/markdown/podman-volume-import.1.md b/docs/source/markdown/podman-volume-import.1.md index 71956f43a..4ae9ae1e2 100644 --- a/docs/source/markdown/podman-volume-import.1.md +++ b/docs/source/markdown/podman-volume-import.1.md @@ -1,7 +1,7 @@ % podman-volume-import(1) ## NAME -podman\-volume\-import - Import tarball contents into a podman volume +podman\-volume\-import - Import tarball contents into an existing podman volume ## SYNOPSIS **podman volume import** *volume* [*source*] @@ -11,9 +11,9 @@ podman\-volume\-import - Import tarball contents into a podman volume **podman volume import** imports the contents of a tarball into the podman volume's mount point. **podman volume import** can consume piped input when using `-` as source path. -Note: Following command is not supported by podman-remote. +The given volume must already exist and will not be created by podman volume import. -**podman volume import VOLUME [SOURCE]** +Note: Following command is not supported by podman-remote. #### **--help** diff --git a/docs/source/markdown/podman-volume.1.md b/docs/source/markdown/podman-volume.1.md index d05f007c8..476d58591 100644 --- a/docs/source/markdown/podman-volume.1.md +++ b/docs/source/markdown/podman-volume.1.md @@ -16,7 +16,7 @@ podman volume is a set of subcommands that manage volumes. | create | [podman-volume-create(1)](podman-volume-create.1.md) | Create a new volume. | | exists | [podman-volume-exists(1)](podman-volume-exists.1.md) | Check if the given volume exists. | | export | [podman-volume-export(1)](podman-volume-export.1.md) | Exports volume to external tar. | -| import | [podman-volume-import(1)](podman-volume-import.1.md) | Import tarball contents into a podman volume. | +| import | [podman-volume-import(1)](podman-volume-import.1.md) | Import tarball contents into an existing podman volume. | | inspect | [podman-volume-inspect(1)](podman-volume-inspect.1.md) | Get detailed information on one or more volumes. | | ls | [podman-volume-ls(1)](podman-volume-ls.1.md) | List all the available volumes. | | mount | [podman-volume-mount(1)](podman-volume-mount.1.md) | Mount a volume filesystem. | @@ -72,4 +72,5 @@ require ( google.golang.org/protobuf v1.28.0 gopkg.in/inf.v0 v0.9.1 gopkg.in/yaml.v2 v2.4.0 + gopkg.in/yaml.v3 v3.0.1 // indirect ) @@ -2047,8 +2047,9 @@ gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk= diff --git a/libpod/container_config.go b/libpod/container_config.go index ae3bc5865..30b84adcf 100644 --- a/libpod/container_config.go +++ b/libpod/container_config.go @@ -243,12 +243,12 @@ type ContainerNetworkConfig struct { // This cannot be set unless CreateNetNS is set. // If not set, the container will be dynamically assigned an IP by CNI. // Deprecated: Do no use this anymore, this is only for DB backwards compat. - StaticIP net.IP `json:"staticIP"` + StaticIP net.IP `json:"staticIP,omitempty"` // StaticMAC is a static MAC to request for the container. // This cannot be set unless CreateNetNS is set. // If not set, the container will be dynamically assigned a MAC by CNI. // Deprecated: Do no use this anymore, this is only for DB backwards compat. - StaticMAC types.HardwareAddr `json:"staticMAC"` + StaticMAC types.HardwareAddr `json:"staticMAC,omitempty"` // PortMappings are the ports forwarded to the container's network // namespace // These are not used unless CreateNetNS is true diff --git a/pkg/domain/infra/runtime_libpod.go b/pkg/domain/infra/runtime_libpod.go index ac557e9de..daa6f0cbf 100644 --- a/pkg/domain/infra/runtime_libpod.go +++ b/pkg/domain/infra/runtime_libpod.go @@ -9,9 +9,9 @@ import ( "os" "os/signal" "sync" + "syscall" "github.com/containers/common/pkg/cgroups" - "github.com/containers/podman/v4/cmd/podman/utils" "github.com/containers/podman/v4/libpod" "github.com/containers/podman/v4/pkg/domain/entities" "github.com/containers/podman/v4/pkg/namespaces" @@ -375,7 +375,7 @@ func ParseIDMapping(mode namespaces.UsernsMode, uidMapSlice, gidMapSlice []strin func StartWatcher(rt *libpod.Runtime) { // Setup the signal notifier ch := make(chan os.Signal, 1) - signal.Notify(ch, utils.SIGHUP) + signal.Notify(ch, syscall.SIGHUP) go func() { for { diff --git a/pkg/machine/config.go b/pkg/machine/config.go index d34776714..abbebc9f9 100644 --- a/pkg/machine/config.go +++ b/pkg/machine/config.go @@ -138,14 +138,15 @@ type DistributionDownload interface { Get() *Download } type InspectInfo struct { - ConfigPath VMFile - Created time.Time - Image ImageConfig - LastUp time.Time - Name string - Resources ResourceConfig - SSHConfig SSHConfig - State Status + ConfigPath VMFile + ConnectionInfo ConnectionConfig + Created time.Time + Image ImageConfig + LastUp time.Time + Name string + Resources ResourceConfig + SSHConfig SSHConfig + State Status } func (rc RemoteConnectionType) MakeSSHURL(host, path, port, userName string) url.URL { @@ -286,11 +287,11 @@ func NewMachineFile(path string, symlink *string) (*VMFile, error) { // makeSymlink for macOS creates a symlink in $HOME/.podman/ // for a machinefile like a socket func (m *VMFile) makeSymlink(symlink *string) error { - homedir, err := os.UserHomeDir() + homeDir, err := os.UserHomeDir() if err != nil { return err } - sl := filepath.Join(homedir, ".podman", *symlink) + sl := filepath.Join(homeDir, ".podman", *symlink) // make the symlink dir and throw away if it already exists if err := os.MkdirAll(filepath.Dir(sl), 0700); err != nil && !errors2.Is(err, os.ErrNotExist) { return err @@ -335,3 +336,9 @@ type SSHConfig struct { // RemoteUsername of the vm user RemoteUsername string } + +// ConnectionConfig contains connections like sockets, etc. +type ConnectionConfig struct { + // PodmanSocket is the exported podman service socket + PodmanSocket *VMFile `json:"PodmanSocket"` +} diff --git a/pkg/machine/e2e/config.go b/pkg/machine/e2e/config.go index c17b840d3..248a2f0ad 100644 --- a/pkg/machine/e2e/config.go +++ b/pkg/machine/e2e/config.go @@ -85,6 +85,14 @@ func (ms *machineSession) outputToString() string { return strings.Join(fields, " ") } +// errorToString returns the error output from a session in string form +func (ms *machineSession) errorToString() string { + if ms == nil || ms.Err == nil || ms.Err.Contents() == nil { + return "" + } + return string(ms.Err.Contents()) +} + // newMB constructor for machine test builders func newMB() (*machineTestBuilder, error) { mb := machineTestBuilder{ diff --git a/pkg/machine/e2e/inspect_test.go b/pkg/machine/e2e/inspect_test.go index 2c9de5664..cdf13bb1a 100644 --- a/pkg/machine/e2e/inspect_test.go +++ b/pkg/machine/e2e/inspect_test.go @@ -2,6 +2,7 @@ package e2e import ( "encoding/json" + "strings" "github.com/containers/podman/v4/pkg/machine" "github.com/containers/podman/v4/pkg/machine/qemu" @@ -86,6 +87,7 @@ var _ = Describe("podman machine stop", func() { var inspectInfo []machine.InspectInfo err = jsoniter.Unmarshal(inspectSession.Bytes(), &inspectInfo) Expect(err).To(BeNil()) + Expect(strings.HasSuffix(inspectInfo[0].ConnectionInfo.PodmanSocket.GetPath(), "podman.sock")) inspect := new(inspectMachine) inspect = inspect.withFormat("{{.Name}}") diff --git a/pkg/machine/e2e/ssh_test.go b/pkg/machine/e2e/ssh_test.go index 155d39a64..9ee31ac26 100644 --- a/pkg/machine/e2e/ssh_test.go +++ b/pkg/machine/e2e/ssh_test.go @@ -56,5 +56,12 @@ var _ = Describe("podman machine ssh", func() { Expect(err).To(BeNil()) Expect(sshSession).To(Exit(0)) Expect(sshSession.outputToString()).To(ContainSubstring("Fedora CoreOS")) + + // keep exit code + sshSession, err = mb.setName(name).setCmd(ssh.withSSHComand([]string{"false"})).run() + Expect(err).To(BeNil()) + Expect(sshSession).To(Exit(1)) + Expect(sshSession.outputToString()).To(Equal("")) + Expect(sshSession.errorToString()).To(Equal("")) }) }) diff --git a/pkg/machine/fcos.go b/pkg/machine/fcos.go index df58b8a1e..77427139a 100644 --- a/pkg/machine/fcos.go +++ b/pkg/machine/fcos.go @@ -146,13 +146,6 @@ func GetFCOSDownload(imageStream string) (*FcosDownloadInfo, error) { //nolint:s streamType string ) - // This is being hard set to testing. Once podman4 is in the - // fcos trees, we should remove it and re-release at least on - // macs. - // TODO: remove when podman4.0 is in coreos - - imageStream = "podman-testing" //nolint:staticcheck - switch imageStream { case "podman-testing": streamType = "podman-testing" diff --git a/pkg/machine/qemu/machine.go b/pkg/machine/qemu/machine.go index e3fb3b970..b9f23662e 100644 --- a/pkg/machine/qemu/machine.go +++ b/pkg/machine/qemu/machine.go @@ -952,7 +952,8 @@ func (v *MachineVM) SSH(_ string, opts machine.SSHOptions) error { sshDestination := username + "@localhost" port := strconv.Itoa(v.Port) - args := []string{"-i", v.IdentityPath, "-p", port, sshDestination, "-o", "UserKnownHostsFile=/dev/null", "-o", "StrictHostKeyChecking=no"} + args := []string{"-i", v.IdentityPath, "-p", port, sshDestination, "-o", "UserKnownHostsFile=/dev/null", + "-o", "StrictHostKeyChecking=no", "-o", "LogLevel=ERROR"} if len(opts.Args) > 0 { args = append(args, opts.Args...) } else { @@ -1471,16 +1472,22 @@ func (v *MachineVM) Inspect() (*machine.InspectInfo, error) { if err != nil { return nil, err } - + connInfo := new(machine.ConnectionConfig) + podmanSocket, err := v.forwardSocketPath() + if err != nil { + return nil, err + } + connInfo.PodmanSocket = podmanSocket return &machine.InspectInfo{ - ConfigPath: v.ConfigPath, - Created: v.Created, - Image: v.ImageConfig, - LastUp: v.LastUp, - Name: v.Name, - Resources: v.ResourceConfig, - SSHConfig: v.SSHConfig, - State: state, + ConfigPath: v.ConfigPath, + ConnectionInfo: *connInfo, + Created: v.Created, + Image: v.ImageConfig, + LastUp: v.LastUp, + Name: v.Name, + Resources: v.ResourceConfig, + SSHConfig: v.SSHConfig, + State: state, }, nil } diff --git a/pkg/specgenutil/specgen.go b/pkg/specgenutil/specgen.go index 9cb2f200b..efaade9cd 100644 --- a/pkg/specgenutil/specgen.go +++ b/pkg/specgenutil/specgen.go @@ -622,7 +622,14 @@ func FillOutSpecGen(s *specgen.SpecGenerator, c *entities.ContainerCreateOptions if opt == "no-new-privileges" { s.ContainerSecurityConfig.NoNewPrivileges = true } else { - con := strings.SplitN(opt, "=", 2) + // Docker deprecated the ":" syntax but still supports it, + // so we need to as well + var con []string + if strings.Contains(opt, "=") { + con = strings.SplitN(opt, "=", 2) + } else { + con = strings.SplitN(opt, ":", 2) + } if len(con) != 2 { return fmt.Errorf("invalid --security-opt 1: %q", opt) } @@ -650,6 +657,12 @@ func FillOutSpecGen(s *specgen.SpecGenerator, c *entities.ContainerCreateOptions } case "unmask": s.ContainerSecurityConfig.Unmask = append(s.ContainerSecurityConfig.Unmask, con[1:]...) + case "no-new-privileges": + noNewPrivileges, err := strconv.ParseBool(con[1]) + if err != nil { + return fmt.Errorf("invalid --security-opt 2: %q", opt) + } + s.ContainerSecurityConfig.NoNewPrivileges = noNewPrivileges default: return fmt.Errorf("invalid --security-opt 2: %q", opt) } diff --git a/test/e2e/checkpoint_test.go b/test/e2e/checkpoint_test.go index 787178cd3..1da199714 100644 --- a/test/e2e/checkpoint_test.go +++ b/test/e2e/checkpoint_test.go @@ -676,8 +676,8 @@ var _ = Describe("Podman checkpoint", func() { }) It("podman checkpoint and restore container with root file-system changes using --ignore-rootfs during restore", func() { // Start the container - localRunString := getRunString([]string{"--rm", ALPINE, "top"}) - session := podmanTest.Podman(localRunString) + // test that restore works without network namespace (https://github.com/containers/podman/issues/14389) + session := podmanTest.Podman([]string{"run", "--network=none", "-d", "--rm", ALPINE, "top"}) session.WaitWithDefaultTimeout() Expect(session).Should(Exit(0)) Expect(podmanTest.NumberOfContainersRunning()).To(Equal(1)) diff --git a/test/e2e/logs_test.go b/test/e2e/logs_test.go index 4e6dcb8af..0d24a7e17 100644 --- a/test/e2e/logs_test.go +++ b/test/e2e/logs_test.go @@ -102,12 +102,12 @@ var _ = Describe("Podman logs", func() { It("tail 99 lines: "+log, func() { skipIfJournaldInContainer() - logc := podmanTest.Podman([]string{"run", "--log-driver", log, "-dt", ALPINE, "sh", "-c", "echo podman; echo podman; echo podman"}) + name := "test1" + logc := podmanTest.Podman([]string{"run", "--name", name, "--log-driver", log, ALPINE, "sh", "-c", "echo podman; echo podman; echo podman"}) logc.WaitWithDefaultTimeout() Expect(logc).To(Exit(0)) - cid := logc.OutputToString() - results := podmanTest.Podman([]string{"logs", "--tail", "99", cid}) + results := podmanTest.Podman([]string{"logs", "--tail", "99", name}) results.WaitWithDefaultTimeout() Expect(results).To(Exit(0)) Expect(results.OutputToStringArray()).To(HaveLen(3)) @@ -116,11 +116,17 @@ var _ = Describe("Podman logs", func() { It("tail 800 lines: "+log, func() { skipIfJournaldInContainer() + // this uses -d so that we do not have 1000 unnecessary lines printed in every test log logc := podmanTest.Podman([]string{"run", "--log-driver", log, "-dt", ALPINE, "sh", "-c", "i=1; while [ \"$i\" -ne 1000 ]; do echo \"line $i\"; i=$((i + 1)); done"}) logc.WaitWithDefaultTimeout() Expect(logc).To(Exit(0)) cid := logc.OutputToString() + // make sure we wait for the container to finish writing its output + wait := podmanTest.Podman([]string{"wait", cid}) + wait.WaitWithDefaultTimeout() + Expect(wait).To(Exit(0)) + results := podmanTest.Podman([]string{"logs", "--tail", "800", cid}) results.WaitWithDefaultTimeout() Expect(results).To(Exit(0)) diff --git a/test/e2e/volume_create_test.go b/test/e2e/volume_create_test.go index 0bf5acbf1..499283cab 100644 --- a/test/e2e/volume_create_test.go +++ b/test/e2e/volume_create_test.go @@ -110,15 +110,24 @@ var _ = Describe("Podman volume create", func() { Expect(session.OutputToString()).To(ContainSubstring("hello")) }) - It("podman import volume should fail", func() { + It("podman import/export volume should fail", func() { // try import on volume or source which does not exists - if podmanTest.RemoteTest { - Skip("Volume export check does not work with a remote client") - } + SkipIfRemote("Volume export check does not work with a remote client") session := podmanTest.Podman([]string{"volume", "import", "notfound", "notfound.tar"}) session.WaitWithDefaultTimeout() Expect(session).To(ExitWithError()) + Expect(session.ErrorToString()).To(ContainSubstring("open notfound.tar: no such file or directory")) + + session = podmanTest.Podman([]string{"volume", "import", "notfound", "-"}) + session.WaitWithDefaultTimeout() + Expect(session).To(ExitWithError()) + Expect(session.ErrorToString()).To(ContainSubstring("no such volume notfound")) + + session = podmanTest.Podman([]string{"volume", "export", "notfound"}) + session.WaitWithDefaultTimeout() + Expect(session).To(ExitWithError()) + Expect(session.ErrorToString()).To(ContainSubstring("no such volume notfound")) }) It("podman create volume with bad volume option", func() { diff --git a/test/system/030-run.bats b/test/system/030-run.bats index 283c3aea9..241831257 100644 --- a/test/system/030-run.bats +++ b/test/system/030-run.bats @@ -855,4 +855,15 @@ EOF run_podman rmi $test_image } +@test "podman create --security-opt" { + run_podman create --security-opt no-new-privileges=true $IMAGE + run_podman rm $output + run_podman create --security-opt no-new-privileges:true $IMAGE + run_podman rm $output + run_podman create --security-opt no-new-privileges=false $IMAGE + run_podman rm $output + run_podman create --security-opt no-new-privileges $IMAGE + run_podman rm $output +} + # vim: filetype=sh diff --git a/vendor/gopkg.in/yaml.v3/decode.go b/vendor/gopkg.in/yaml.v3/decode.go index df36e3a30..0173b6982 100644 --- a/vendor/gopkg.in/yaml.v3/decode.go +++ b/vendor/gopkg.in/yaml.v3/decode.go @@ -100,7 +100,10 @@ func (p *parser) peek() yaml_event_type_t { if p.event.typ != yaml_NO_EVENT { return p.event.typ } - if !yaml_parser_parse(&p.parser, &p.event) { + // It's curious choice from the underlying API to generally return a + // positive result on success, but on this case return true in an error + // scenario. This was the source of bugs in the past (issue #666). + if !yaml_parser_parse(&p.parser, &p.event) || p.parser.error != yaml_NO_ERROR { p.fail() } return p.event.typ @@ -320,6 +323,8 @@ type decoder struct { decodeCount int aliasCount int aliasDepth int + + mergedFields map[interface{}]bool } var ( @@ -808,6 +813,11 @@ func (d *decoder) mapping(n *Node, out reflect.Value) (good bool) { } } + mergedFields := d.mergedFields + d.mergedFields = nil + + var mergeNode *Node + mapIsNew := false if out.IsNil() { out.Set(reflect.MakeMap(outt)) @@ -815,11 +825,18 @@ func (d *decoder) mapping(n *Node, out reflect.Value) (good bool) { } for i := 0; i < l; i += 2 { if isMerge(n.Content[i]) { - d.merge(n.Content[i+1], out) + mergeNode = n.Content[i+1] continue } k := reflect.New(kt).Elem() if d.unmarshal(n.Content[i], k) { + if mergedFields != nil { + ki := k.Interface() + if mergedFields[ki] { + continue + } + mergedFields[ki] = true + } kkind := k.Kind() if kkind == reflect.Interface { kkind = k.Elem().Kind() @@ -833,6 +850,12 @@ func (d *decoder) mapping(n *Node, out reflect.Value) (good bool) { } } } + + d.mergedFields = mergedFields + if mergeNode != nil { + d.merge(n, mergeNode, out) + } + d.stringMapType = stringMapType d.generalMapType = generalMapType return true @@ -844,7 +867,8 @@ func isStringMap(n *Node) bool { } l := len(n.Content) for i := 0; i < l; i += 2 { - if n.Content[i].ShortTag() != strTag { + shortTag := n.Content[i].ShortTag() + if shortTag != strTag && shortTag != mergeTag { return false } } @@ -861,7 +885,6 @@ func (d *decoder) mappingStruct(n *Node, out reflect.Value) (good bool) { var elemType reflect.Type if sinfo.InlineMap != -1 { inlineMap = out.Field(sinfo.InlineMap) - inlineMap.Set(reflect.New(inlineMap.Type()).Elem()) elemType = inlineMap.Type().Elem() } @@ -870,6 +893,9 @@ func (d *decoder) mappingStruct(n *Node, out reflect.Value) (good bool) { d.prepare(n, field) } + mergedFields := d.mergedFields + d.mergedFields = nil + var mergeNode *Node var doneFields []bool if d.uniqueKeys { doneFields = make([]bool, len(sinfo.FieldsList)) @@ -879,13 +905,20 @@ func (d *decoder) mappingStruct(n *Node, out reflect.Value) (good bool) { for i := 0; i < l; i += 2 { ni := n.Content[i] if isMerge(ni) { - d.merge(n.Content[i+1], out) + mergeNode = n.Content[i+1] continue } if !d.unmarshal(ni, name) { continue } - if info, ok := sinfo.FieldsMap[name.String()]; ok { + sname := name.String() + if mergedFields != nil { + if mergedFields[sname] { + continue + } + mergedFields[sname] = true + } + if info, ok := sinfo.FieldsMap[sname]; ok { if d.uniqueKeys { if doneFields[info.Id] { d.terrors = append(d.terrors, fmt.Sprintf("line %d: field %s already set in type %s", ni.Line, name.String(), out.Type())) @@ -911,6 +944,11 @@ func (d *decoder) mappingStruct(n *Node, out reflect.Value) (good bool) { d.terrors = append(d.terrors, fmt.Sprintf("line %d: field %s not found in type %s", ni.Line, name.String(), out.Type())) } } + + d.mergedFields = mergedFields + if mergeNode != nil { + d.merge(n, mergeNode, out) + } return true } @@ -918,19 +956,29 @@ func failWantMap() { failf("map merge requires map or sequence of maps as the value") } -func (d *decoder) merge(n *Node, out reflect.Value) { - switch n.Kind { +func (d *decoder) merge(parent *Node, merge *Node, out reflect.Value) { + mergedFields := d.mergedFields + if mergedFields == nil { + d.mergedFields = make(map[interface{}]bool) + for i := 0; i < len(parent.Content); i += 2 { + k := reflect.New(ifaceType).Elem() + if d.unmarshal(parent.Content[i], k) { + d.mergedFields[k.Interface()] = true + } + } + } + + switch merge.Kind { case MappingNode: - d.unmarshal(n, out) + d.unmarshal(merge, out) case AliasNode: - if n.Alias != nil && n.Alias.Kind != MappingNode { + if merge.Alias != nil && merge.Alias.Kind != MappingNode { failWantMap() } - d.unmarshal(n, out) + d.unmarshal(merge, out) case SequenceNode: - // Step backwards as earlier nodes take precedence. - for i := len(n.Content) - 1; i >= 0; i-- { - ni := n.Content[i] + for i := 0; i < len(merge.Content); i++ { + ni := merge.Content[i] if ni.Kind == AliasNode { if ni.Alias != nil && ni.Alias.Kind != MappingNode { failWantMap() @@ -943,6 +991,8 @@ func (d *decoder) merge(n *Node, out reflect.Value) { default: failWantMap() } + + d.mergedFields = mergedFields } func isMerge(n *Node) bool { diff --git a/vendor/gopkg.in/yaml.v3/parserc.go b/vendor/gopkg.in/yaml.v3/parserc.go index ac66fccc0..268558a0d 100644 --- a/vendor/gopkg.in/yaml.v3/parserc.go +++ b/vendor/gopkg.in/yaml.v3/parserc.go @@ -687,6 +687,9 @@ func yaml_parser_parse_node(parser *yaml_parser_t, event *yaml_event_t, block, i func yaml_parser_parse_block_sequence_entry(parser *yaml_parser_t, event *yaml_event_t, first bool) bool { if first { token := peek_token(parser) + if token == nil { + return false + } parser.marks = append(parser.marks, token.start_mark) skip_token(parser) } @@ -786,7 +789,7 @@ func yaml_parser_split_stem_comment(parser *yaml_parser_t, stem_len int) { } token := peek_token(parser) - if token.typ != yaml_BLOCK_SEQUENCE_START_TOKEN && token.typ != yaml_BLOCK_MAPPING_START_TOKEN { + if token == nil || token.typ != yaml_BLOCK_SEQUENCE_START_TOKEN && token.typ != yaml_BLOCK_MAPPING_START_TOKEN { return } @@ -813,6 +816,9 @@ func yaml_parser_split_stem_comment(parser *yaml_parser_t, stem_len int) { func yaml_parser_parse_block_mapping_key(parser *yaml_parser_t, event *yaml_event_t, first bool) bool { if first { token := peek_token(parser) + if token == nil { + return false + } parser.marks = append(parser.marks, token.start_mark) skip_token(parser) } @@ -922,6 +928,9 @@ func yaml_parser_parse_block_mapping_value(parser *yaml_parser_t, event *yaml_ev func yaml_parser_parse_flow_sequence_entry(parser *yaml_parser_t, event *yaml_event_t, first bool) bool { if first { token := peek_token(parser) + if token == nil { + return false + } parser.marks = append(parser.marks, token.start_mark) skip_token(parser) } diff --git a/vendor/modules.txt b/vendor/modules.txt index e28d91bde..9bd500ee8 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -864,7 +864,8 @@ gopkg.in/tomb.v1 # gopkg.in/yaml.v2 v2.4.0 ## explicit gopkg.in/yaml.v2 -# gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b +# gopkg.in/yaml.v3 v3.0.1 +## explicit gopkg.in/yaml.v3 # sigs.k8s.io/yaml v1.3.0 sigs.k8s.io/yaml |