diff options
70 files changed, 1134 insertions, 539 deletions
diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 9a4563308..cc8b618f4 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -3,9 +3,9 @@ Thanks for sending a pull request! Please make sure you've read our contributing guidelines and how to submit a pull request (https://github.com/containers/podman/blob/main/CONTRIBUTING.md#submitting-pull-requests). -In case you're only changing docs, make sure to prefix the pull-request title with "[CI:DOCS]". That will prevent functional tests from running and save time and energy. +In case you're only changing docs, make sure to prefix the pull-request title with "[CI:DOCS]". That will prevent functional tests from running and save time and energy. -Finally, be sure to sign commits with your real name. Since by opening +Finally, be sure to sign commits with your real name. Since by opening a PR you already have commits, you can add signatures if needed with something like `git commit -s --amend`. --> @@ -18,7 +18,7 @@ is required: Enter your extended release note in the block below. If the PR requires additional action from users switching to the new release, include the string "action required". -For more information on release notes please follow the kubernetes model: +For more information on release notes, please follow the Kubernetes model: https://git.k8s.io/community/contributors/guide/release-notes.md --> @@ -29,8 +29,6 @@ EPOCH_TEST_COMMIT ?= $(shell git merge-base $${DEST_BRANCH:-main} HEAD) HEAD ?= HEAD PROJECT := github.com/containers/podman GIT_BASE_BRANCH ?= origin/main -GIT_BRANCH ?= $(shell git rev-parse --abbrev-ref HEAD 2>/dev/null) -GIT_BRANCH_CLEAN ?= $(shell echo $(GIT_BRANCH) | sed -e "s/[^[:alnum:]]/-/g") LIBPOD_INSTANCE := libpod_dev PREFIX ?= /usr/local BINDIR ?= ${PREFIX}/bin @@ -80,18 +78,18 @@ FISHINSTALLDIR=${PREFIX}/share/fish/vendor_completions.d SELINUXOPT ?= $(shell test -x /usr/sbin/selinuxenabled && selinuxenabled && echo -Z) COMMIT_NO ?= $(shell git rev-parse HEAD 2> /dev/null || true) -GIT_COMMIT ?= $(if $(shell git status --porcelain --untracked-files=no),${COMMIT_NO}-dirty,${COMMIT_NO}) +GIT_COMMIT ?= $(if $(shell git status --porcelain --untracked-files=no),$(call err_if_empty,COMMIT_NO)-dirty,$(COMMIT_NO)) DATE_FMT = %s ifdef SOURCE_DATE_EPOCH - BUILD_INFO ?= $(shell date -u -d "@$(SOURCE_DATE_EPOCH)" "+$(DATE_FMT)" 2>/dev/null || date -u -r "$(SOURCE_DATE_EPOCH)" "+$(DATE_FMT)" 2>/dev/null || date -u "+$(DATE_FMT)") + BUILD_INFO ?= $(shell date -u -d "@$(call err_if_empty,SOURCE_DATE_EPOCH)" "+$(DATE_FMT)" 2>/dev/null || date -u -r "$(SOURCE_DATE_EPOCH)" "+$(DATE_FMT)" 2>/dev/null || date -u "+$(DATE_FMT)") else BUILD_INFO ?= $(shell date "+$(DATE_FMT)") endif LIBPOD := ${PROJECT}/v4/libpod GOFLAGS ?= -trimpath LDFLAGS_PODMAN ?= \ - -X $(LIBPOD)/define.gitCommit=$(GIT_COMMIT) \ - -X $(LIBPOD)/define.buildInfo=$(BUILD_INFO) \ + $(if $(GIT_COMMIT),-X $(LIBPOD)/define.gitCommit=$(GIT_COMMIT),) \ + $(if $(BUILD_INFO),-X $(LIBPOD)/define.buildInfo=$(BUILD_INFO),) \ -X $(LIBPOD)/config._installPrefix=$(PREFIX) \ -X $(LIBPOD)/config._etcDir=$(ETCDIR) \ -X github.com/containers/common/pkg/config.additionalHelperBinariesDir=$(HELPER_BINARIES_DIR)\ @@ -107,7 +105,7 @@ GINKGOTIMEOUT ?= -timeout=90m # Conditional required to produce empty-output if binary not built yet. RELEASE_VERSION = $(shell if test -x test/version/version; then test/version/version; fi) -RELEASE_NUMBER = $(shell echo "$(RELEASE_VERSION)" | sed -e 's/^v\(.*\)/\1/') +RELEASE_NUMBER = $(shell echo "$(call err_if_empty,RELEASE_VERSION)" | sed -e 's/^v\(.*\)/\1/') # If non-empty, logs all output from server during remote system testing PODMAN_SERVER_LOG ?= @@ -138,7 +136,7 @@ err_if_empty = $(if $(strip $($(1))),$(strip $($(1))),$(error Required variable CGO_ENABLED ?= 1 # Default to the native OS type and architecture unless otherwise specified NATIVE_GOOS := $(shell env -u GOOS $(GO) env GOOS) -GOOS ?= $(NATIVE_GOOS) +GOOS ?= $(call err_if_empty,NATIVE_GOOS) # Default to the native architecture type NATIVE_GOARCH := $(shell env -u GOARCH $(GO) env GOARCH) GOARCH ?= $(NATIVE_GOARCH) @@ -158,7 +156,7 @@ export GOOS GOARCH CGO_ENABLED BINSFX SRCBINDIR # Need to use CGO for mDNS resolution, but cross builds need CGO disabled # See https://github.com/golang/go/issues/12524 for details DARWIN_GCO := 0 -ifeq ($(NATIVE_GOOS),darwin) +ifeq ($(call err_if_empty,NATIVE_GOOS),darwin) ifdef HOMEBREW_PREFIX DARWIN_GCO := 1 endif @@ -189,8 +187,8 @@ binaries: podman podman-remote rootlessport ## Build podman, podman-remote and r # at reference-time (due to `=` and not `=:`). _HLP_TGTS_RX = '^[[:print:]]+:.*?\#\# .*$$' _HLP_TGTS_CMD = grep -E $(_HLP_TGTS_RX) $(MAKEFILE_LIST) -_HLP_TGTS_LEN = $(shell $(_HLP_TGTS_CMD) | cut -d : -f 1 | wc -L) -_HLPFMT = "%-$(_HLP_TGTS_LEN)s %s\n" +_HLP_TGTS_LEN = $(shell $(call err_if_empty,_HLP_TGTS_CMD) | cut -d : -f 1 | wc -L) +_HLPFMT = "%-$(call err_if_empty,_HLP_TGTS_LEN)s %s\n" .PHONY: help help: ## (Default) Print listing of key targets with their descriptions @printf $(_HLPFMT) "Target:" "Description:" @@ -250,7 +248,7 @@ validate: lint .gitvalidation validate.completions man-page-check swagger-check .PHONY: build-all-new-commits build-all-new-commits: # Validate that all the commits build on top of $(GIT_BASE_BRANCH) - git rebase $(GIT_BASE_BRANCH) -x "$(MAKE)" + git rebase $(call err_if_empty,GIT_BASE_BRANCH) -x "$(MAKE)" .PHONY: vendor vendor: @@ -441,7 +439,7 @@ docs: $(MANPAGES) ## Generate documentation # docs/remote-docs.sh requires a locally executable 'podman-remote' binary # in addition to the target-archetecture binary (if any). -podman-remote-%-docs: podman-remote-$(NATIVE_GOOS) +podman-remote-%-docs: podman-remote-$(call err_if_empty,NATIVE_GOOS) $(eval GOOS := $*) $(MAKE) docs $(MANPAGES) rm -rf docs/build/remote @@ -639,7 +637,7 @@ podman-release-%.tar.gz: test/version/version $(eval SUBDIR := podman-v$(call err_if_empty,RELEASE_NUMBER)) $(eval _DSTARGS := "DESTDIR=$(TMPDIR)/$(SUBDIR)" "PREFIX=/usr") $(eval GOARCH := $*) - mkdir -p "$(TMPDIR)/$(SUBDIR)" + mkdir -p "$(call err_if_empty,TMPDIR)/$(SUBDIR)" $(MAKE) GOOS=$(GOOS) GOARCH=$(NATIVE_GOARCH) \ clean-binaries docs podman-remote-$(GOOS)-docs if [[ "$(GOARCH)" != "$(NATIVE_GOARCH)" ]]; then \ @@ -660,7 +658,7 @@ podman-remote-release-%.zip: test/version/version ## Build podman-remote for %=$ $(eval GOOS := $(firstword $(subst _, ,$*))) $(eval GOARCH := $(lastword $(subst _, ,$*))) $(eval _GOPLAT := GOOS=$(call err_if_empty,GOOS) GOARCH=$(call err_if_empty,GOARCH)) - mkdir -p "$(TMPDIR)/$(SUBDIR)" + mkdir -p "$(call err_if_empty,TMPDIR)/$(SUBDIR)" $(MAKE) GOOS=$(GOOS) GOARCH=$(NATIVE_GOARCH) \ clean-binaries podman-remote-$(GOOS)-docs if [[ "$(GOARCH)" != "$(NATIVE_GOARCH)" ]]; then \ @@ -679,8 +677,8 @@ podman-remote-release-%.zip: test/version/version ## Build podman-remote for %=$ .PHONY: podman.msi podman.msi: test/version/version ## Build podman-remote, package for installation on Windows - $(MAKE) podman-v$(RELEASE_NUMBER).msi -podman-v$(RELEASE_NUMBER).msi: podman-remote-windows podman-remote-windows-docs podman-winpath win-sshproxy + $(MAKE) podman-v$(call err_if_empty,RELEASE_NUMBER).msi +podman-v%.msi: test/version/version podman-remote-windows podman-remote-windows-docs podman-winpath win-sshproxy $(eval DOCFILE := docs/build/remote/windows) find $(DOCFILE) -print | \ wixl-heat --var var.ManSourceDir --component-group ManFiles \ @@ -715,7 +713,7 @@ package: ## Build rpm packages # a full path to test installed podman or you risk to call another executable. .PHONY: package-install package-install: package ## Install rpm packages - sudo ${PKG_MANAGER} -y install ${HOME}/rpmbuild/RPMS/*/*.rpm + sudo $(call err_if_empty,PKG_MANAGER) -y install ${HOME}/rpmbuild/RPMS/*/*.rpm /usr/bin/podman version /usr/bin/podman info # will catch a broken conmon diff --git a/cmd/podman/common/completion.go b/cmd/podman/common/completion.go index 3720e9608..5eef5f982 100644 --- a/cmd/podman/common/completion.go +++ b/cmd/podman/common/completion.go @@ -4,6 +4,7 @@ import ( "bufio" "fmt" "os" + "path" "reflect" "strconv" "strings" @@ -21,6 +22,7 @@ import ( "github.com/containers/podman/v4/pkg/signal" systemdDefine "github.com/containers/podman/v4/pkg/systemd/define" "github.com/containers/podman/v4/pkg/util" + securejoin "github.com/cyphar/filepath-securejoin" "github.com/spf13/cobra" ) @@ -282,6 +284,61 @@ func getNetworks(cmd *cobra.Command, toComplete string, cType completeType) ([]s return suggestions, cobra.ShellCompDirectiveNoFileComp } +func getPathCompletion(root string, toComplete string) []string { + if toComplete == "" { + toComplete = "/" + } + // Important: securejoin is required to make sure we never leave the root mount point + userpath, err := securejoin.SecureJoin(root, toComplete) + if err != nil { + cobra.CompErrorln(err.Error()) + return nil + } + var base string + f, err := os.Open(userpath) + if err != nil { + // Do not use path.Dir() since this cleans the paths which + // then no longer matches the user input. + userpath, base = path.Split(userpath) + toComplete, _ = path.Split(toComplete) + f, err = os.Open(userpath) + if err != nil { + return nil + } + } + stat, err := f.Stat() + if err != nil { + cobra.CompErrorln(err.Error()) + return nil + } + if !stat.IsDir() { + // nothing to complete since it is no dir + return nil + } + entries, err := f.ReadDir(-1) + if err != nil { + cobra.CompErrorln(err.Error()) + return nil + } + completions := make([]string, 0, len(entries)) + for _, e := range entries { + if strings.HasPrefix(e.Name(), base) { + completions = append(completions, simplePathJoinUnix(toComplete, e.Name())) + } + } + return completions +} + +// simplePathJoinUnix joins to path components by adding a slash only if p1 doesn't end with one. +// We cannot use path.Join() for the completions logic because this one always calls Clean() on +// the path which changes it from the input. +func simplePathJoinUnix(p1, p2 string) string { + if p1[len(p1)-1] == '/' { + return p1 + p2 + } + return p1 + "/" + p2 +} + // validCurrentCmdLine validates the current cmd line // It utilizes the Args function from the cmd struct // In most cases the Args function validates the args length but it @@ -523,8 +580,32 @@ func AutocompleteCreateRun(cmd *cobra.Command, args []string, toComplete string) } return getImages(cmd, toComplete) } - // TODO: add path completion for files in the image - return nil, cobra.ShellCompDirectiveDefault + // Mount the image and provide path completion + engine, err := setupImageEngine(cmd) + if err != nil { + cobra.CompErrorln(err.Error()) + return nil, cobra.ShellCompDirectiveDefault + } + + resp, err := engine.Mount(registry.Context(), []string{args[0]}, entities.ImageMountOptions{}) + if err != nil { + cobra.CompErrorln(err.Error()) + return nil, cobra.ShellCompDirectiveDefault + } + defer func() { + _, err := engine.Unmount(registry.Context(), []string{args[0]}, entities.ImageUnmountOptions{}) + if err != nil { + cobra.CompErrorln(err.Error()) + } + }() + if len(resp) != 1 { + return nil, cobra.ShellCompDirectiveDefault + } + + // So this uses ShellCompDirectiveDefault to also still provide normal shell + // completion in case no path matches. This is useful if someone tries to get + // completion for paths that are not available in the image, e.g. /proc/... + return getPathCompletion(resp[0].Path, toComplete), cobra.ShellCompDirectiveDefault | cobra.ShellCompDirectiveNoSpace } // AutocompleteRegistries - Autocomplete registries. @@ -572,14 +653,39 @@ func AutocompleteCpCommand(cmd *cobra.Command, args []string, toComplete string) return nil, cobra.ShellCompDirectiveNoFileComp } if len(args) < 2 { + if i := strings.IndexByte(toComplete, ':'); i > -1 { + // Looks like the user already set the container. + // Lets mount it and provide path completion for files in the container. + engine, err := setupContainerEngine(cmd) + if err != nil { + cobra.CompErrorln(err.Error()) + return nil, cobra.ShellCompDirectiveDefault + } + + resp, err := engine.ContainerMount(registry.Context(), []string{toComplete[:i]}, entities.ContainerMountOptions{}) + if err != nil { + cobra.CompErrorln(err.Error()) + return nil, cobra.ShellCompDirectiveDefault + } + defer func() { + _, err := engine.ContainerUnmount(registry.Context(), []string{toComplete[:i]}, entities.ContainerUnmountOptions{}) + if err != nil { + cobra.CompErrorln(err.Error()) + } + }() + if len(resp) != 1 { + return nil, cobra.ShellCompDirectiveDefault + } + return prefixSlice(toComplete[:i+1], getPathCompletion(resp[0].Path, toComplete[i+1:])), cobra.ShellCompDirectiveDefault | cobra.ShellCompDirectiveNoSpace + } + // Suggest containers when they match the input otherwise normal shell completion is used containers, _ := getContainers(cmd, toComplete, completeDefault) for _, container := range containers { - // TODO: Add path completion for inside the container if possible if strings.HasPrefix(container, toComplete) { - return containers, cobra.ShellCompDirectiveNoSpace + return suffixCompSlice(":", containers), cobra.ShellCompDirectiveNoSpace } } - // else complete paths + // else complete paths on the host return nil, cobra.ShellCompDirectiveDefault } // don't complete more than 2 args diff --git a/cmd/podman/common/create.go b/cmd/podman/common/create.go index d28becc8a..f89035be3 100644 --- a/cmd/podman/common/create.go +++ b/cmd/podman/common/create.go @@ -12,7 +12,7 @@ import ( "github.com/spf13/cobra" ) -const sizeWithUnitFormat = "(format: `<number>[<unit>]`, where unit = b (bytes), k (kilobytes), m (megabytes), or g (gigabytes))" +const sizeWithUnitFormat = "(format: `<number>[<unit>]`, where unit = b (bytes), k (kibibytes), m (mebibytes), or g (gibibytes))" var containerConfig = registry.PodmanConfig() @@ -255,9 +255,8 @@ func DefineCreateFlags(cmd *cobra.Command, cf *entities.ContainerCreateOptions, _ = cmd.RegisterFlagCompletionFunc(hostUserFlagName, completion.AutocompleteNone) imageVolumeFlagName := "image-volume" - createFlags.StringVar( - &cf.ImageVolume, - imageVolumeFlagName, DefaultImageVolume, + createFlags.String( + imageVolumeFlagName, containerConfig.Engine.ImageVolumeMode, `Tells podman how to handle the builtin image volumes ("bind"|"tmpfs"|"ignore")`, ) _ = cmd.RegisterFlagCompletionFunc(imageVolumeFlagName, AutocompleteImageVolume) diff --git a/cmd/podman/common/default.go b/cmd/podman/common/default.go index 7caec50ff..6f78d3d29 100644 --- a/cmd/podman/common/default.go +++ b/cmd/podman/common/default.go @@ -5,9 +5,6 @@ import ( ) var ( - - // DefaultImageVolume default value - DefaultImageVolume = "bind" // Pull in configured json library json = registry.JSONLibrary() ) diff --git a/cmd/podman/containers/create.go b/cmd/podman/containers/create.go index c62ddd6eb..0a513c606 100644 --- a/cmd/podman/containers/create.go +++ b/cmd/podman/containers/create.go @@ -102,16 +102,25 @@ func init() { createFlags(containerCreateCommand) } -func create(cmd *cobra.Command, args []string) error { - var ( - err error - ) +func commonFlags(cmd *cobra.Command) error { + var err error flags := cmd.Flags() cliVals.Net, err = common.NetFlagsToNetOptions(nil, *flags) if err != nil { return err } + if cmd.Flags().Changed("image-volume") { + cliVals.ImageVolume = cmd.Flag("image-volume").Value.String() + } + return nil +} + +func create(cmd *cobra.Command, args []string) error { + if err := commonFlags(cmd); err != nil { + return err + } + // Check if initctr is used with --pod and the value is correct if initctr := InitContainerType; cmd.Flags().Changed("init-ctr") { if !cmd.Flags().Changed("pod") { @@ -123,7 +132,7 @@ func create(cmd *cobra.Command, args []string) error { cliVals.InitContainerType = initctr } - cliVals, err = CreateInit(cmd, cliVals, false) + cliVals, err := CreateInit(cmd, cliVals, false) if err != nil { return err } diff --git a/cmd/podman/containers/run.go b/cmd/podman/containers/run.go index 951981293..a6c500afa 100644 --- a/cmd/podman/containers/run.go +++ b/cmd/podman/containers/run.go @@ -109,7 +109,9 @@ func init() { } func run(cmd *cobra.Command, args []string) error { - var err error + if err := commonFlags(cmd); err != nil { + return err + } // TODO: Breaking change should be made fatal in next major Release if cliVals.TTY && cliVals.Interactive && !term.IsTerminal(int(os.Stdin.Fd())) { @@ -122,14 +124,10 @@ func run(cmd *cobra.Command, args []string) error { } } - flags := cmd.Flags() - cliVals.Net, err = common.NetFlagsToNetOptions(nil, *flags) - if err != nil { - return err - } runOpts.CIDFile = cliVals.CIDFile runOpts.Rm = cliVals.Rm - if cliVals, err = CreateInit(cmd, cliVals, false); err != nil { + cliVals, err := CreateInit(cmd, cliVals, false) + if err != nil { return err } diff --git a/cmd/podman/images/load.go b/cmd/podman/images/load.go index dbb7c32fa..c18c32387 100644 --- a/cmd/podman/images/load.go +++ b/cmd/podman/images/load.go @@ -110,6 +110,6 @@ func load(cmd *cobra.Command, args []string) error { if err != nil { return err } - fmt.Println("Loaded image(s): " + strings.Join(response.Names, ",")) + fmt.Println("Loaded image: " + strings.Join(response.Names, "\nLoaded image: ")) return nil } 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/reset.go b/cmd/podman/system/reset.go index 176573bf6..20f15a34f 100644 --- a/cmd/podman/system/reset.go +++ b/cmd/podman/system/reset.go @@ -91,18 +91,10 @@ func reset(cmd *cobra.Command, args []string) { registry.ContainerEngine().Shutdown(registry.Context()) registry.ImageEngine().Shutdown(registry.Context()) - engine, err := infra.NewSystemEngine(entities.ResetMode, registry.PodmanConfig()) - if err != nil { - logrus.Error(err) - os.Exit(define.ExecErrorCodeGeneric) - } - defer engine.Shutdown(registry.Context()) - - if err := engine.Reset(registry.Context()); err != nil { + // Do not try to shut the engine down, as a Reset engine is not valid + // after its creation. + if _, err := infra.NewSystemEngine(entities.ResetMode, registry.PodmanConfig()); err != nil { logrus.Error(err) - // FIXME change this to return the error like other commands - // defer will never run on os.Exit() - //nolint:gocritic os.Exit(define.ExecErrorCodeGeneric) } 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/contrib/cirrus/runner.sh b/contrib/cirrus/runner.sh index c871f1f54..b9f43f395 100755 --- a/contrib/cirrus/runner.sh +++ b/contrib/cirrus/runner.sh @@ -312,6 +312,11 @@ function _run_release() { if [[ -n "$dev" ]]; then die "Releases must never contain '-dev' in output of 'podman info' ($dev)" fi + + commit=$(bin/podman info --format='{{.Version.GitCommit}}' | tr -d '[:space:]') + if [[ -z "$commit" ]]; then + die "Releases must contain a non-empty Version.GitCommit in 'podman info'" + fi msg "All OK" } diff --git a/contrib/podmanimage/README.md b/contrib/podmanimage/README.md index 4f184ca28..7e35bdf0a 100644 --- a/contrib/podmanimage/README.md +++ b/contrib/podmanimage/README.md @@ -4,7 +4,7 @@ ## Overview -This directory contains the Dockerfiles necessary to create the podmanimage container +This directory contains the Containerfiles necessary to create the podmanimage container images that are housed on quay.io under the Podman account. All repositories where the images live are public and can be pulled without credentials. These container images are secured and the resulting containers can run safely with privileges within the container. diff --git a/contrib/podmanimage/stable/Containerfile b/contrib/podmanimage/stable/Containerfile new file mode 100644 index 000000000..40a2cb5f3 --- /dev/null +++ b/contrib/podmanimage/stable/Containerfile @@ -0,0 +1,56 @@ +# stable/Containerfile +# +# Build a Podman container image from the latest +# stable version of Podman on the Fedoras Updates System. +# https://bodhi.fedoraproject.org/updates/?search=podman +# This image can be used to create a secured container +# that runs safely with privileges within the container. +# +FROM registry.fedoraproject.org/fedora:latest + +# Don't include container-selinux and remove +# directories used by dnf that are just taking +# up space. +RUN dnf -y update && \ + rpm --setcaps shadow-utils 2>/dev/null && \ + dnf -y install podman fuse-overlayfs \ + --exclude container-selinux && \ + dnf clean all && \ + rm -rf /var/cache /var/log/dnf* /var/log/yum.* + +RUN useradd podman; \ +echo -e "podman:1:999\npodman:1001:64535" > /etc/subuid; \ +echo -e "podman:1:999\npodman:1001:64535" > /etc/subgid; + +ARG _REPO_URL="https://raw.githubusercontent.com/containers/podman/main/contrib/podmanimage/stable" +ADD $_REPO_URL/storage.conf /etc/containers/storage.conf +ADD $_REPO_URL/containers.conf /etc/containers/containers.conf +ADD $_REPO_URL/podman-containers.conf /home/podman/.config/containers/containers.conf + +RUN mkdir -p /home/podman/.local/share/containers && \ + chown podman:podman -R /home/podman && \ + chmod 644 /etc/containers/containers.conf + +# Copy & modify the defaults to provide reference if runtime changes needed. +# Changes here are required for running with fuse-overlay storage inside container. +RUN sed -i -e 's|^#mount_program|mount_program|g' \ + -e '/additionalimage.*/a "/var/lib/shared",' \ + -e 's|^mountopt[[:space:]]*=.*$|mountopt = "nodev,fsync=0"|g' \ + /usr/share/containers/storage.conf \ + > /etc/containers/storage.conf + +# Note VOLUME options must always happen after the chown call above +# RUN commands can not modify existing volumes +VOLUME /var/lib/containers +VOLUME /home/podman/.local/share/containers + +RUN mkdir -p /var/lib/shared/overlay-images \ + /var/lib/shared/overlay-layers \ + /var/lib/shared/vfs-images \ + /var/lib/shared/vfs-layers && \ + touch /var/lib/shared/overlay-images/images.lock && \ + touch /var/lib/shared/overlay-layers/layers.lock && \ + touch /var/lib/shared/vfs-images/images.lock && \ + touch /var/lib/shared/vfs-layers/layers.lock + +ENV _CONTAINERS_USERNS_CONFIGURED="" diff --git a/contrib/podmanimage/stable/Dockerfile b/contrib/podmanimage/stable/Dockerfile deleted file mode 100644 index 78d820458..000000000 --- a/contrib/podmanimage/stable/Dockerfile +++ /dev/null @@ -1,36 +0,0 @@ -# stable/Dockerfile -# -# Build a Podman container image from the latest -# stable version of Podman on the Fedoras Updates System. -# https://bodhi.fedoraproject.org/updates/?search=podman -# This image can be used to create a secured container -# that runs safely with privileges within the container. -# -FROM registry.fedoraproject.org/fedora:latest - -# Don't include container-selinux and remove -# directories used by yum that are just taking -# up space. -RUN dnf -y update; rpm --restore shadow-utils 2>/dev/null; \ -yum -y install podman fuse-overlayfs --exclude container-selinux; \ -rm -rf /var/cache /var/log/dnf* /var/log/yum.* - -RUN useradd podman; \ -echo -e "podman:1:999\npodman:1001:64535" > /etc/subuid; \ -echo -e "podman:1:999\npodman:1001:64535" > /etc/subgid; - -ADD https://raw.githubusercontent.com/containers/podman/main/contrib/podmanimage/stable/containers.conf /etc/containers/containers.conf -ADD https://raw.githubusercontent.com/containers/podman/main/contrib/podmanimage/stable/podman-containers.conf /home/podman/.config/containers/containers.conf - -RUN mkdir -p /home/podman/.local/share/containers; chown podman:podman -R /home/podman - -# Note VOLUME options must always happen after the chown call above -# RUN commands can not modify existing volumes -VOLUME /var/lib/containers -VOLUME /home/podman/.local/share/containers - -# chmod containers.conf and adjust storage.conf to enable Fuse storage. -RUN chmod 644 /etc/containers/containers.conf; sed -i -e 's|^#mount_program|mount_program|g' -e '/additionalimage.*/a "/var/lib/shared",' -e 's|^mountopt[[:space:]]*=.*$|mountopt = "nodev,fsync=0"|g' /etc/containers/storage.conf -RUN mkdir -p /var/lib/shared/overlay-images /var/lib/shared/overlay-layers /var/lib/shared/vfs-images /var/lib/shared/vfs-layers; touch /var/lib/shared/overlay-images/images.lock; touch /var/lib/shared/overlay-layers/layers.lock; touch /var/lib/shared/vfs-images/images.lock; touch /var/lib/shared/vfs-layers/layers.lock - -ENV _CONTAINERS_USERNS_CONFIGURED="" diff --git a/contrib/podmanimage/stable/storage.conf b/contrib/podmanimage/stable/storage.conf new file mode 100644 index 000000000..bc8d8c111 --- /dev/null +++ b/contrib/podmanimage/stable/storage.conf @@ -0,0 +1,6 @@ +[storage.options] +additionalimagestores = ["/var/lib/shared"] + +[storage.options.overlay] +mountopt = "nodev,fsync=0" +mount_program = "/usr/bin/fuse-overlayfs" diff --git a/contrib/podmanimage/testing/Containerfile b/contrib/podmanimage/testing/Containerfile new file mode 100644 index 000000000..5fa794baf --- /dev/null +++ b/contrib/podmanimage/testing/Containerfile @@ -0,0 +1,61 @@ +# testing/Containerfile +# +# Build a Podman container image from the latest +# stable version of Podman on the Fedoras Updates System. +# https://bodhi.fedoraproject.org/updates/?search=podman +# This image can be used to create a secured container +# that runs safely with privileges within the container. +# +FROM registry.fedoraproject.org/fedora:latest + +# Don't include container-selinux and remove +# directories used by dnf that are just taking +# up space. +RUN dnf -y update && \ + rpm --setcaps shadow-utils 2>/dev/null && \ + dnf -y install podman fuse-overlayfs \ + --exclude container-selinux --enablerepo updates-testing && \ + dnf clean all && \ + rm -rf /var/cache /var/log/dnf* /var/log/yum.* + +RUN useradd podman; \ +echo -e "podman:1:999\npodman:1001:64535" > /etc/subuid; \ +echo -e "podman:1:999\npodman:1001:64535" > /etc/subgid; + +ARG _REPO_URL="https://raw.githubusercontent.com/containers/podman/main/contrib/podmanimage/stable" +ADD $_REPO_URL/storage.conf /etc/containers/storage.conf +ADD $_REPO_URL/containers.conf /etc/containers/containers.conf +ADD $_REPO_URL/podman-containers.conf /home/podman/.config/containers/containers.conf + +RUN mkdir -p /home/podman/.local/share/containers && \ + chown podman:podman -R /home/podman + +# Copy & modify the defaults to provide reference if runtime changes needed. +# Changes here are required for running with fuse-overlay storage inside container. +RUN sed -i -e 's|^#mount_program|mount_program|g' \ + -e '/additionalimage.*/a "/var/lib/shared",' \ + -e 's|^mountopt[[:space:]]*=.*$|mountopt = "nodev,fsync=0"|g' \ + /usr/share/containers/storage.conf \ + > /etc/containers/storage.conf + +# Note VOLUME options must always happen after the chown call above +# RUN commands can not modify existing volumes +VOLUME /var/lib/containers +VOLUME /home/podman/.local/share/containers + +# chmod containers.conf and adjust storage.conf to enable Fuse storage. +RUN chmod 644 /etc/containers/containers.conf && \ + sed -i -e 's|^#mount_program|mount_program|g' \ + -e '/additionalimage.*/a "/var/lib/shared",' \ + -e 's|^mountopt[[:space:]]*=.*$|mountopt = "nodev,fsync=0"|g' \ + /etc/containers/storage.conf +RUN mkdir -p /var/lib/shared/overlay-images \ + /var/lib/shared/overlay-layers \ + /var/lib/shared/vfs-images \ + /var/lib/shared/vfs-layers && \ + touch /var/lib/shared/overlay-images/images.lock && \ + touch /var/lib/shared/overlay-layers/layers.lock && \ + touch /var/lib/shared/vfs-images/images.lock && \ + touch /var/lib/shared/vfs-layers/layers.lock + +ENV _CONTAINERS_USERNS_CONFIGURED="" diff --git a/contrib/podmanimage/testing/Dockerfile b/contrib/podmanimage/testing/Dockerfile deleted file mode 100644 index 41af1c849..000000000 --- a/contrib/podmanimage/testing/Dockerfile +++ /dev/null @@ -1,36 +0,0 @@ -# testing/Dockerfile -# -# Build a Podman image using the latest -# version of Podman that is in updates-testing -# on the Fedoras Updates System. At times this -# may be the same the latest stable version. -# https://bodhi.fedoraproject.org/updates/?search=podman -# This image can be used to create a secured container -# that runs safely with privileges within the container. -# -FROM registry.fedoraproject.org/fedora:latest - -# Don't include container-selinux and remove -# directories used by yum that are just taking -# up space. -RUN yum -y update; rpm --restore shadow-utils 2>/dev/null; yum -y install podman fuse-overlayfs --exclude container-selinux --enablerepo updates-testing; rm -rf /var/cache /var/log/dnf* /var/log/yum.* - -RUN useradd podman; \ -echo -e "podman:1:999\npodman:1001:64535" > /etc/subuid; \ -echo -e "podman:1:999\npodman:1001:64535" > /etc/subgid; - -ADD https://raw.githubusercontent.com/containers/podman/main/contrib/podmanimage/stable/containers.conf /etc/containers/containers.conf -ADD https://raw.githubusercontent.com/containers/podman/main/contrib/podmanimage/stable/podman-containers.conf /home/podman/.config/containers/containers.conf - -RUN mkdir -p /home/podman/.local/share/containers; chown podman:podman -R /home/podman - -# Note VOLUME options must always happen after the chown call above -# RUN commands can not modify existing volumes -VOLUME /var/lib/containers -VOLUME /home/podman/.local/share/containers - -# chmod containers.conf and adjust storage.conf to enable Fuse storage. -RUN chmod 644 /etc/containers/containers.conf; sed -i -e 's|^#mount_program|mount_program|g' -e '/additionalimage.*/a "/var/lib/shared",' -e 's|^mountopt[[:space:]]*=.*$|mountopt = "nodev,fsync=0"|g' /etc/containers/storage.conf -RUN mkdir -p /var/lib/shared/overlay-images /var/lib/shared/overlay-layers /var/lib/shared/vfs-images /var/lib/shared/vfs-layers; touch /var/lib/shared/overlay-images/images.lock; touch /var/lib/shared/overlay-layers/layers.lock; touch /var/lib/shared/vfs-images/images.lock; touch /var/lib/shared/vfs-layers/layers.lock - -ENV _CONTAINERS_USERNS_CONFIGURED="" diff --git a/contrib/podmanimage/upstream/Containerfile b/contrib/podmanimage/upstream/Containerfile new file mode 100644 index 000000000..b338a33ae --- /dev/null +++ b/contrib/podmanimage/upstream/Containerfile @@ -0,0 +1,62 @@ +# upstream/Containerfile +# +# Build a Podman container image from the latest +# upstream version of Podman on GitHub. +# https://github.com/containers/podman +# This image can be used to create a secured container +# that runs safely with privileges within the container. +# The containers created by this image also come with a +# Podman development environment in /root/podman. +# +FROM registry.fedoraproject.org/fedora:latest + +# Don't include container-selinux and remove +# directories used by dnf that are just taking +# up space. The latest podman + deps. come from +# https://copr.fedorainfracloud.org/coprs/rhcontainerbot/podman-next/ +RUN dnf -y update && \ + rpm --setcaps shadow-utils 2>/dev/null && \ + dnf -y install 'dnf-command(copr)' --enablerepo=updates-testing && \ + dnf -y copr enable rhcontainerbot/podman-next && \ + dnf -y install podman fuse-overlayfs \ + --exclude container-selinux \ + --enablerepo=updates-testing && \ + dnf clean all && \ + rm -rf /var/cache /var/log/dnf* /var/log/yum.* + +RUN useradd podman; \ +echo -e "podman:1:999\npodman:1001:64535" > /etc/subuid; \ +echo -e "podman:1:999\npodman:1001:64535" > /etc/subgid; + +ARG _REPO_URL="https://raw.githubusercontent.com/containers/podman/main/contrib/podmanimage/stable" +ADD $_REPO_URL/storage.conf /etc/containers/storage.conf +ADD $_REPO_URL/containers.conf /etc/containers/containers.conf +ADD $_REPO_URL/podman-containers.conf /home/podman/.config/containers/containers.conf + +RUN mkdir -p /home/podman/.local/share/containers && \ + chown podman:podman -R /home/podman && \ + chmod 644 /etc/containers/containers.conf + +# Copy & modify the defaults to provide reference if runtime changes needed. +# Changes here are required for running with fuse-overlay storage inside container. +RUN sed -i -e 's|^#mount_program|mount_program|g' \ + -e '/additionalimage.*/a "/var/lib/shared",' \ + -e 's|^mountopt[[:space:]]*=.*$|mountopt = "nodev,fsync=0"|g' \ + /usr/share/containers/storage.conf \ + > /etc/containers/storage.conf + +# Note VOLUME options must always happen after the chown call above +# RUN commands can not modify existing volumes +VOLUME /var/lib/containers +VOLUME /home/podman/.local/share/containers + +RUN mkdir -p /var/lib/shared/overlay-images \ + /var/lib/shared/overlay-layers \ + /var/lib/shared/vfs-images \ + /var/lib/shared/vfs-layers && \ + touch /var/lib/shared/overlay-images/images.lock && \ + touch /var/lib/shared/overlay-layers/layers.lock && \ + touch /var/lib/shared/vfs-images/images.lock && \ + touch /var/lib/shared/vfs-layers/layers.lock + +ENV _CONTAINERS_USERNS_CONFIGURED="" diff --git a/contrib/podmanimage/upstream/Dockerfile b/contrib/podmanimage/upstream/Dockerfile deleted file mode 100644 index 0769a7612..000000000 --- a/contrib/podmanimage/upstream/Dockerfile +++ /dev/null @@ -1,85 +0,0 @@ -# git/Dockerfile -# -# Build a Podman container image from the latest -# upstream version of Podman on GitHub. -# https://github.com/containers/podman -# This image can be used to create a secured container -# that runs safely with privileges within the container. -# The containers created by this image also come with a -# Podman development environment in /root/podman. -# -FROM registry.fedoraproject.org/fedora:latest -ENV GOPATH=/root/podman - -# Install the software required to build Podman. -# Then create a directory and clone from the Podman -# GitHub repository, make and install Podman -# to the container. -# Finally remove the podman directory and a few other packages -# that are needed for building but not running Podman -RUN yum -y update; rpm --restore shadow-utils 2>/dev/null; yum -y install --exclude container-selinux \ - --enablerepo=updates-testing \ - btrfs-progs-devel \ - containernetworking-cni \ - conmon \ - device-mapper-devel \ - git \ - glib2-devel \ - glibc-devel \ - glibc-static \ - go \ - golang-github-cpuguy83-md2man \ - gpgme-devel \ - iptables \ - libassuan-devel \ - libgpg-error-devel \ - libseccomp-devel \ - libselinux-devel \ - make \ - pkgconfig \ - crun \ - fuse-overlayfs \ - fuse3 \ - containers-common \ - podman-plugins; \ - mkdir /root/podman; \ - git clone https://github.com/containers/podman /root/podman/src/github.com/containers/podman; \ - cd /root/podman/src/github.com/containers/podman; \ - make BUILDTAGS="selinux seccomp"; \ - make install PREFIX=/usr; \ - cd /root/podman; \ - git clone https://github.com/containers/conmon /root/podman/conmon; \ - cd conmon; \ - make; \ - install -D -m 755 bin/conmon /usr/libexec/podman/conmon; \ - git clone https://github.com/containernetworking/plugins.git $GOPATH/src/github.com/containernetworking/plugins; \ - cd $GOPATH/src/github.com/containernetworking/plugins; \ - ./build_linux.sh; \ - mkdir -p /usr/libexec/cni; \ - \cp -fR bin/* /usr/libexec/cni; \ - mkdir -p /etc/cni/net.d; \ - curl -qsSL https://raw.githubusercontent.com/containers/podman/main/cni/87-podman-bridge.conflist | tee /etc/cni/net.d/99-loopback.conf; \ - mkdir -p /usr/share/containers; \ - rm -rf /root/podman/*; \ - yum -y remove git golang go-md2man make; \ - yum clean all; - -RUN useradd podman; \ -echo -e "podman:1:999\npodman:1001:64535" > /etc/subuid; \ -echo -e "podman:1:999\npodman:1001:64535" > /etc/subgid; - -ADD https://raw.githubusercontent.com/containers/podman/main/contrib/podmanimage/stable/containers.conf /etc/containers/containers.conf -ADD https://raw.githubusercontent.com/containers/podman/main/contrib/podmanimage/stable/podman-containers.conf /home/podman/.config/containers/containers.conf - -RUN mkdir -p /home/podman/.local/share/containers; chown podman:podman -R /home/podman - -# Note VOLUME options must always happen after the chown call above -# RUN commands can not modify existing volumes -VOLUME /var/lib/containers -VOLUME /home/podman/.local/share/containers - -# chmod containers.conf and adjust storage.conf to enable Fuse storage. -RUN chmod 644 /etc/containers/containers.conf; sed -i -e 's|^#mount_program|mount_program|g' -e '/additionalimage.*/a "/var/lib/shared",' -e 's|^mountopt[[:space:]]*=.*$|mountopt = "nodev,fsync=0"|g' /etc/containers/storage.conf -RUN mkdir -p /var/lib/shared/overlay-images /var/lib/shared/overlay-layers /var/lib/shared/vfs-images /var/lib/shared/vfs-layers; touch /var/lib/shared/overlay-images/images.lock; touch /var/lib/shared/overlay-layers/layers.lock; touch /var/lib/shared/vfs-images/images.lock; touch /var/lib/shared/vfs-layers/layers.lock - -ENV _CONTAINERS_USERNS_CONFIGURED="" diff --git a/docs/source/markdown/podman-build.1.md b/docs/source/markdown/podman-build.1.md index 1cde4fbdf..4d6a74a1f 100644 --- a/docs/source/markdown/podman-build.1.md +++ b/docs/source/markdown/podman-build.1.md @@ -435,8 +435,8 @@ if it does not exist. This option is useful for building multi architecture imag #### **--memory**, **-m**=*LIMIT* -Memory limit (format: `<number>[<unit>]`, where unit = b (bytes), k (kilobytes), -m (megabytes), or g (gigabytes)) +Memory limit (format: `<number>[<unit>]`, where unit = b (bytes), k (kibibytes), +m (mebibytes), or g (gibibytes)) Allows you to constrain the memory available to a container. If the host supports swap memory, then the **-m** memory setting can be larger than physical @@ -453,7 +453,7 @@ A limit value equal to memory plus swap. Must be used with the **-m** the value of --memory. The format of `LIMIT` is `<number>[<unit>]`. Unit can be `b` (bytes), -`k` (kilobytes), `m` (megabytes), or `g` (gigabytes). If you don't specify a +`k` (kibibytes), `m` (mebibytes), or `g` (gibibytes). If you don't specify a unit, `b` is used. Set LIMIT to `-1` to enable unlimited swap. #### **--network**=*mode*, **--net** @@ -631,8 +631,8 @@ as a seccomp filter Size of `/dev/shm`. The format is `<number><unit>`. `number` must be greater than `0`. -Unit is optional and can be `b` (bytes), `k` (kilobytes), `m`(megabytes), or -`g` (gigabytes). If you omit the unit, the system uses bytes. If you omit the +Unit is optional and can be `b` (bytes), `k` (kibibytes), `m`(mebibytes), or +`g` (gibibytes). If you omit the unit, the system uses bytes. If you omit the size entirely, the system uses `64m`. #### **--sign-by**=*fingerprint* diff --git a/docs/source/markdown/podman-container-clone.1.md b/docs/source/markdown/podman-container-clone.1.md index 69423113d..6d552db75 100644 --- a/docs/source/markdown/podman-container-clone.1.md +++ b/docs/source/markdown/podman-container-clone.1.md @@ -131,7 +131,7 @@ Force removal of the original container that we are cloning. Can only be used in #### **--memory**, **-m**=*limit* -Memory limit (format: `<number>[<unit>]`, where unit = b (bytes), k (kilobytes), m (megabytes), or g (gigabytes)) +Memory limit (format: `<number>[<unit>]`, where unit = b (bytes), k (kibibytes), m (mebibytes), or g (gibibytes)) Allows the memory available to a container to be constrained. If the host supports swap memory, then the **-m** memory setting can be larger than physical @@ -143,7 +143,7 @@ If no memory limits are specified, the original container's will be used. #### **--memory-reservation**=*limit* -Memory soft limit (format: `<number>[<unit>]`, where unit = b (bytes), k (kilobytes), m (megabytes), or g (gigabytes)) +Memory soft limit (format: `<number>[<unit>]`, where unit = b (bytes), k (kibibytes), m (mebibytes), or g (gibibytes)) After setting memory reservation, when the system detects memory contention or low memory, containers are forced to restrict their consumption to their @@ -159,7 +159,7 @@ A limit value equal to memory plus swap. Must be used with the **-m** the value of --memory if specified. Otherwise, the container being cloned will be used to derive the swap value. The format of `LIMIT` is `<number>[<unit>]`. Unit can be `b` (bytes), -`k` (kilobytes), `m` (megabytes), or `g` (gigabytes). If you don't specify a +`k` (kibibytes), `m` (mebibytes), or `g` (gibibytes). If you don't specify a unit, `b` is used. Set LIMIT to `-1` to enable unlimited swap. #### **--memory-swappiness**=*number* diff --git a/docs/source/markdown/podman-create.1.md b/docs/source/markdown/podman-create.1.md index 009209343..913183869 100644 --- a/docs/source/markdown/podman-create.1.md +++ b/docs/source/markdown/podman-create.1.md @@ -573,7 +573,7 @@ To specify multiple static MAC addresses per container, set multiple networks us #### **--memory**, **-m**=*limit* -Memory limit (format: `<number>[<unit>]`, where unit = b (bytes), k (kilobytes), m (megabytes), or g (gigabytes)) +Memory limit (format: `<number>[<unit>]`, where unit = b (bytes), k (kibibytes), m (mebibytes), or g (gibibytes)) Allows you to constrain the memory available to a container. If the host supports swap memory, then the **-m** memory setting can be larger than physical @@ -583,7 +583,7 @@ system's page size (the value would be very large, that's millions of trillions) #### **--memory-reservation**=*limit* -Memory soft limit (format: `<number>[<unit>]`, where unit = b (bytes), k (kilobytes), m (megabytes), or g (gigabytes)) +Memory soft limit (format: `<number>[<unit>]`, where unit = b (bytes), k (kibibytes), m (mebibytes), or g (gibibytes)) After setting memory reservation, when the system detects memory contention or low memory, containers are forced to restrict their consumption to their @@ -599,7 +599,7 @@ A limit value equal to memory plus swap. Must be used with the **-m** the value of --memory. The format of `LIMIT` is `<number>[<unit>]`. Unit can be `b` (bytes), -`k` (kilobytes), `m` (megabytes), or `g` (gigabytes). If you don't specify a +`k` (kibibytes), `m` (mebibytes), or `g` (gibibytes). If you don't specify a unit, `b` is used. Set LIMIT to `-1` to enable unlimited swap. #### **--memory-swappiness**=*number* @@ -826,22 +826,27 @@ container. Rootless containers cannot have more privileges than the account that launched them. -#### **--publish**, **-p**=*port* +#### **--publish**, **-p**=[[_ip_:][_hostPort_]:]_containerPort_[/_protocol_] -Publish a container's port, or range of ports, to the host +Publish a container's port, or range of ports, to the host. -Format: `ip:hostPort:containerPort | ip::containerPort | hostPort:containerPort | containerPort` Both hostPort and containerPort can be specified as a range of ports. -When specifying ranges for both, the number of container ports in the range must match the number of host ports in the range. -(e.g., `podman run -p 1234-1236:1222-1224 --name thisWorks -t busybox` -but not `podman run -p 1230-1236:1230-1240 --name RangeContainerPortsBiggerThanRangeHostPorts -t busybox`) -With host IP: `podman run -p 127.0.0.1:$HOSTPORT:$CONTAINERPORT --name CONTAINER -t someimage` +When specifying ranges for both, the number of container ports in the +range must match the number of host ports in the range. + If host IP is set to 0.0.0.0 or not set at all, the port will be bound on all IPs on the host. + +By default, Podman will publish TCP ports. To publish a UDP port instead, give +`udp` as protocol. To publish both TCP and UDP ports, set `--publish` twice, +with `tcp`, and `udp` as protocols respectively. Rootful containers can also +publish ports using the `sctp` protocol. + Host port does not have to be specified (e.g. `podman run -p 127.0.0.1::80`). If it is not, the container port will be randomly assigned a port on the host. -Use `podman port` to see the actual mapping: `podman port CONTAINER $CONTAINERPORT` -**Note:** if a container will be run within a pod, it is not necessary to publish the port for +Use **podman port** to see the actual mapping: `podman port $CONTAINER $CONTAINERPORT`. + +**Note:** If a container will be run within a pod, it is not necessary to publish the port for the containers in the pod. The port must only be published by the pod itself. Pod network stacks act like the network stack on the host - you have a variety of containers in the pod, and programs in the container, all sharing a single interface and IP address, and @@ -1008,7 +1013,7 @@ Note: Labeling can be disabled for all containers by setting label=false in the #### **--shm-size**=*size* -Size of `/dev/shm` (format: `<number>[<unit>]`, where unit = b (bytes), k (kilobytes), m (megabytes), or g (gigabytes)) +Size of `/dev/shm` (format: `<number>[<unit>]`, where unit = b (bytes), k (kibibytes), m (mebibytes), or g (gibibytes)) If you omit the unit, the system uses bytes. If you omit the size entirely, the system uses `64m`. When size is `0`, there is no limit on the amount of memory used for IPC by the container. diff --git a/docs/source/markdown/podman-image-scp.1.md b/docs/source/markdown/podman-image-scp.1.md index 1d902da91..b6b610a7d 100644 --- a/docs/source/markdown/podman-image-scp.1.md +++ b/docs/source/markdown/podman-image-scp.1.md @@ -33,7 +33,7 @@ Suppress the output ``` $ podman image scp alpine -Loaded image(s): docker.io/library/alpine:latest +Loaded image: docker.io/library/alpine:latest ``` ``` @@ -43,12 +43,12 @@ Copying blob 72e830a4dff5 done Copying config 85f9dc67c7 done Writing manifest to image destination Storing signatures -Loaded image(s): docker.io/library/alpine:latest +Loaded image: docker.io/library/alpine:latest ``` ``` $ podman image scp Fedora::alpine RHEL:: -Loaded image(s): docker.io/library/alpine:latest +Loaded image: docker.io/library/alpine:latest ``` ``` @@ -59,7 +59,7 @@ Copying blob 9450ef9feb15 [--------------------------------------] 0.0b / 0.0b Copying config 1f97f0559c done Writing manifest to image destination Storing signatures -Loaded image(s): docker.io/library/alpine:latest +Loaded image: docker.io/library/alpine:latest ``` ``` @@ -73,7 +73,7 @@ Copying blob 5eb901baf107 skipped: already exists Copying config 696d33ca15 done Writing manifest to image destination Storing signatures -Loaded image(s): docker.io/library/alpine:latest +Loaded image: docker.io/library/alpine:latest ``` ``` @@ -87,7 +87,7 @@ Copying blob 5eb901baf107 Copying config 696d33ca15 done Writing manifest to image destination Storing signatures -Loaded image(s): docker.io/library/alpine:latest +Loaded image: docker.io/library/alpine:latest ``` ## SEE ALSO 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-pod-create.1.md b/docs/source/markdown/podman-pod-create.1.md index cf749efda..1a98528bb 100644 --- a/docs/source/markdown/podman-pod-create.1.md +++ b/docs/source/markdown/podman-pod-create.1.md @@ -227,16 +227,30 @@ Set the PID mode for the pod. The default is to create a private PID namespace f Write the pod ID to the file. -#### **--publish**=*port*, **-p** +#### **--publish**, **-p**=[[_ip_:][_hostPort_]:]_containerPort_[/_protocol_] -Publish a port or range of ports from the pod to the host. +Publish a container's port, or range of ports, within this pod to the host. -Format: `ip:hostPort:containerPort | ip::containerPort | hostPort:containerPort | containerPort` Both hostPort and containerPort can be specified as a range of ports. -When specifying ranges for both, the number of container ports in the range must match the number of host ports in the range. -Use `podman port` to see the actual mapping: `podman port CONTAINER $CONTAINERPORT`. +When specifying ranges for both, the number of container ports in the +range must match the number of host ports in the range. -NOTE: This cannot be modified once the pod is created. +If host IP is set to 0.0.0.0 or not set at all, the port will be bound on all IPs on the host. + +By default, Podman will publish TCP ports. To publish a UDP port instead, give +`udp` as protocol. To publish both TCP and UDP ports, set `--publish` twice, +with `tcp`, and `udp` as protocols respectively. Rootful containers can also +publish ports using the `sctp` protocol. + +Host port does not have to be specified (e.g. `podman run -p 127.0.0.1::80`). +If it is not, the container port will be randomly assigned a port on the host. + +Use **podman port** to see the actual mapping: `podman port $CONTAINER $CONTAINERPORT`. + +**Note:** You must not publish ports of containers in the pod individually, +but only by the pod itself. + +**Note:** This cannot be modified once the pod is created. #### **--replace** diff --git a/docs/source/markdown/podman-run.1.md b/docs/source/markdown/podman-run.1.md index a16ee9394..b9d87b5bd 100644 --- a/docs/source/markdown/podman-run.1.md +++ b/docs/source/markdown/podman-run.1.md @@ -597,7 +597,7 @@ To specify multiple static MAC addresses per container, set multiple networks us #### **--memory**, **-m**=_number_[_unit_] -Memory limit. A _unit_ can be **b** (bytes), **k** (kilobytes), **m** (megabytes), or **g** (gigabytes). +Memory limit. A _unit_ can be **b** (bytes), **k** (kibibytes), **m** (mebibytes), or **g** (gibibytes). Allows you to constrain the memory available to a container. If the host supports swap memory, then the **-m** memory setting can be larger than physical @@ -607,7 +607,7 @@ system's page size (the value would be very large, that's millions of trillions) #### **--memory-reservation**=_number_[_unit_] -Memory soft limit. A _unit_ can be **b** (bytes), **k** (kilobytes), **m** (megabytes), or **g** (gigabytes). +Memory soft limit. A _unit_ can be **b** (bytes), **k** (kibibytes), **m** (mebibytes), or **g** (gibibytes). After setting memory reservation, when the system detects memory contention or low memory, containers are forced to restrict their consumption to their @@ -618,7 +618,7 @@ as memory limit. #### **--memory-swap**=_number_[_unit_] A limit value equal to memory plus swap. -A _unit_ can be **b** (bytes), **k** (kilobytes), **m** (megabytes), or **g** (gigabytes). +A _unit_ can be **b** (bytes), **k** (kibibytes), **m** (mebibytes), or **g** (gibibytes). Must be used with the **-m** (**--memory**) flag. The argument value should always be larger than that of @@ -864,22 +864,27 @@ points, Apparmor/SELinux separation, and Seccomp filters are all disabled. Rootless containers cannot have more privileges than the account that launched them. -#### **--publish**, **-p**=_ip_:_hostPort_:_containerPort_ | _ip_::_containerPort_ | _hostPort_:_containerPort_ | _containerPort_ +#### **--publish**, **-p**=[[_ip_:][_hostPort_]:]_containerPort_[/_protocol_] Publish a container's port, or range of ports, to the host. Both hostPort and containerPort can be specified as a range of ports. - -When specifying ranges for both, the number of container ports in the range must match the number of host ports in the range. +When specifying ranges for both, the number of container ports in the +range must match the number of host ports in the range. If host IP is set to 0.0.0.0 or not set at all, the port will be bound on all IPs on the host. +By default, Podman will publish TCP ports. To publish a UDP port instead, give +`udp` as protocol. To publish both TCP and UDP ports, set `--publish` twice, +with `tcp`, and `udp` as protocols respectively. Rootful containers can also +publish ports using the `sctp` protocol. + Host port does not have to be specified (e.g. `podman run -p 127.0.0.1::80`). If it is not, the container port will be randomly assigned a port on the host. -Use **podman port** to see the actual mapping: **podman port $CONTAINER $CONTAINERPORT**. +Use **podman port** to see the actual mapping: `podman port $CONTAINER $CONTAINERPORT`. -**Note:** if a container will be run within a pod, it is not necessary to publish the port for +**Note:** If a container will be run within a pod, it is not necessary to publish the port for the containers in the pod. The port must only be published by the pod itself. Pod network stacks act like the network stack on the host - you have a variety of containers in the pod, and programs in the container, all sharing a single interface and IP address, and @@ -1053,7 +1058,7 @@ Note: Labeling can be disabled for all containers by setting **label=false** in #### **--shm-size**=_number_[_unit_] -Size of _/dev/shm_. A _unit_ can be **b** (bytes), **k** (kilobytes), **m** (megabytes), or **g** (gigabytes). +Size of _/dev/shm_. A _unit_ can be **b** (bytes), **k** (kibibytes), **m** (mebibytes), or **g** (gibibytes). If you omit the unit, the system uses bytes. If you omit the size entirely, the default is **64m**. When _size_ is **0**, there is no limit on the amount of memory used for IPC by the container. @@ -9,7 +9,7 @@ require ( github.com/checkpoint-restore/checkpointctl v0.0.0-20220321135231-33f4a66335f0 github.com/checkpoint-restore/go-criu/v5 v5.3.0 github.com/container-orchestrated-devices/container-device-interface v0.4.0 - github.com/containernetworking/cni v1.1.0 + github.com/containernetworking/cni v1.1.1 github.com/containernetworking/plugins v1.1.1 github.com/containers/buildah v1.26.1-0.20220524184833-5500333c2e06 github.com/containers/common v0.48.1-0.20220523155016-2fd37da97824 @@ -328,8 +328,9 @@ github.com/containernetworking/cni v0.7.1/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ github.com/containernetworking/cni v0.8.0/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ61X79hmU3w8FmsY= github.com/containernetworking/cni v0.8.1/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ61X79hmU3w8FmsY= github.com/containernetworking/cni v1.0.1/go.mod h1:AKuhXbN5EzmD4yTNtfSsX3tPcmtrBI6QcRV0NiNt15Y= -github.com/containernetworking/cni v1.1.0 h1:T00oIz4hef+/p9gpRZa57SnIN+QnbmAHBjbxaOSFo9U= github.com/containernetworking/cni v1.1.0/go.mod h1:sDpYKmGVENF3s6uvMvGgldDWeG8dMxakj/u+i9ht9vw= +github.com/containernetworking/cni v1.1.1 h1:ky20T7c0MvKvbMOwS/FrlbNwjEoqJEUUYfsL4b0mc4k= +github.com/containernetworking/cni v1.1.1/go.mod h1:sDpYKmGVENF3s6uvMvGgldDWeG8dMxakj/u+i9ht9vw= github.com/containernetworking/plugins v0.8.6/go.mod h1:qnw5mN19D8fIwkqW7oHHYDHVlzhJpcY6TQxn/fUyDDM= github.com/containernetworking/plugins v0.9.1/go.mod h1:xP/idU2ldlzN6m4p5LmGiwRDjeJr6FLK6vuiUwoH7P8= github.com/containernetworking/plugins v1.0.1/go.mod h1:QHCfGpaTwYTbbH+nZXKVTxNBDZcxSOplJT5ico8/FLE= diff --git a/libpod/container_api.go b/libpod/container_api.go index d87deb71a..b064d3528 100644 --- a/libpod/container_api.go +++ b/libpod/container_api.go @@ -123,7 +123,18 @@ func (c *Container) StartAndAttach(ctx context.Context, streams *define.AttachSt // Attach to the container before starting it go func() { - if err := c.attach(streams, keys, resize, true, startedChan, nil); err != nil { + // Start resizing + if c.LogDriver() != define.PassthroughLogging { + registerResizeFunc(resize, c.bundlePath()) + } + + opts := new(AttachOptions) + opts.Streams = streams + opts.DetachKeys = &keys + opts.Start = true + opts.Started = startedChan + + if err := c.ociRuntime.Attach(c, opts); err != nil { attachChan <- err } close(attachChan) @@ -260,8 +271,18 @@ func (c *Container) Attach(streams *define.AttachStreams, keys string, resize <- }() } + // Start resizing + if c.LogDriver() != define.PassthroughLogging { + registerResizeFunc(resize, c.bundlePath()) + } + + opts := new(AttachOptions) + opts.Streams = streams + opts.DetachKeys = &keys + opts.AttachReady = attachRdy + c.newContainerEvent(events.Attach) - return c.attach(streams, keys, resize, false, nil, attachRdy) + return c.ociRuntime.Attach(c, opts) } // HTTPAttach forwards an attach session over a hijacked HTTP session. diff --git a/libpod/container_log.go b/libpod/container_log.go index 7a9eb2dbf..da6d51670 100644 --- a/libpod/container_log.go +++ b/libpod/container_log.go @@ -75,7 +75,6 @@ func (c *Container) readFromLogFile(ctx context.Context, options *logs.LogOption go func() { defer options.WaitGroup.Done() - var partial string for line := range t.Lines { select { case <-ctx.Done(): @@ -89,13 +88,6 @@ func (c *Container) readFromLogFile(ctx context.Context, options *logs.LogOption logrus.Errorf("Getting new log line: %v", err) continue } - if nll.Partial() { - partial += nll.Msg - continue - } else if !nll.Partial() && len(partial) > 0 { - nll.Msg = partial + nll.Msg - partial = "" - } nll.CID = c.ID() nll.CName = c.Name() nll.ColorID = colorID diff --git a/libpod/healthcheck_linux.go b/libpod/healthcheck_linux.go index 45b3a0e41..1e03db542 100644 --- a/libpod/healthcheck_linux.go +++ b/libpod/healthcheck_linux.go @@ -7,6 +7,7 @@ import ( "os/exec" "strings" + "github.com/containers/podman/v4/pkg/errorhandling" "github.com/containers/podman/v4/pkg/rootless" "github.com/containers/podman/v4/pkg/systemd" "github.com/pkg/errors" @@ -46,6 +47,17 @@ func (c *Container) createTimer() error { return nil } +// Wait for a message on the channel. Throw an error if the message is not "done". +func systemdOpSuccessful(c chan string) error { + msg := <-c + switch msg { + case "done": + return nil + default: + return fmt.Errorf("expected %q but received %q", "done", msg) + } +} + // startTimer starts a systemd timer for the healthchecks func (c *Container) startTimer() error { if c.disableHealthCheckSystemd() { @@ -56,8 +68,17 @@ func (c *Container) startTimer() error { return errors.Wrapf(err, "unable to get systemd connection to start healthchecks") } defer conn.Close() - _, err = conn.StartUnitContext(context.Background(), fmt.Sprintf("%s.service", c.ID()), "fail", nil) - return err + + startFile := fmt.Sprintf("%s.service", c.ID()) + startChan := make(chan string) + if _, err := conn.StartUnitContext(context.Background(), startFile, "fail", startChan); err != nil { + return err + } + if err := systemdOpSuccessful(startChan); err != nil { + return fmt.Errorf("starting systemd health-check timer %q: %w", startFile, err) + } + + return nil } // removeTransientFiles removes the systemd timer and unit files @@ -71,30 +92,37 @@ func (c *Container) removeTransientFiles(ctx context.Context) error { return errors.Wrapf(err, "unable to get systemd connection to remove healthchecks") } defer conn.Close() + + // Errors are returned at the very end. Let's make sure to stop and + // clean up as much as possible. + stopErrors := []error{} + + // Stop the timer before the service to make sure the timer does not + // fire after the service is stopped. + timerChan := make(chan string) timerFile := fmt.Sprintf("%s.timer", c.ID()) - serviceFile := fmt.Sprintf("%s.service", c.ID()) + if _, err := conn.StopUnitContext(ctx, timerFile, "fail", timerChan); err != nil { + if !strings.HasSuffix(err.Error(), ".timer not loaded.") { + stopErrors = append(stopErrors, fmt.Errorf("removing health-check timer %q: %w", timerFile, err)) + } + } else if err := systemdOpSuccessful(timerChan); err != nil { + stopErrors = append(stopErrors, fmt.Errorf("stopping systemd health-check timer %q: %w", timerFile, err)) + } - // If the service has failed (the healthcheck has failed), then - // the .service file is not removed on stopping the unit file. If - // we check the properties of the service, it will automatically - // reset the state. But checking the state takes msecs vs usecs to - // blindly call reset. + // Reset the service before stopping it to make sure it's being removed + // on stop. + serviceChan := make(chan string) + serviceFile := fmt.Sprintf("%s.service", c.ID()) if err := conn.ResetFailedUnitContext(ctx, serviceFile); err != nil { - logrus.Debugf("failed to reset unit file: %q", err) + logrus.Debugf("Failed to reset unit file: %q", err) } - - // We want to ignore errors where the timer unit and/or service unit has already - // been removed. The error return is generic so we have to check against the - // string in the error - if _, err = conn.StopUnitContext(ctx, serviceFile, "fail", nil); err != nil { + if _, err := conn.StopUnitContext(ctx, serviceFile, "fail", serviceChan); err != nil { if !strings.HasSuffix(err.Error(), ".service not loaded.") { - return errors.Wrapf(err, "unable to remove service file") - } - } - if _, err = conn.StopUnitContext(ctx, timerFile, "fail", nil); err != nil { - if strings.HasSuffix(err.Error(), ".timer not loaded.") { - return nil + stopErrors = append(stopErrors, fmt.Errorf("removing health-check service %q: %w", serviceFile, err)) } + } else if err := systemdOpSuccessful(serviceChan); err != nil { + stopErrors = append(stopErrors, fmt.Errorf("stopping systemd health-check service %q: %w", serviceFile, err)) } - return err + + return errorhandling.JoinErrors(stopErrors) } diff --git a/libpod/networking_linux.go b/libpod/networking_linux.go index 73e64530e..37fa9b5f5 100644 --- a/libpod/networking_linux.go +++ b/libpod/networking_linux.go @@ -930,6 +930,8 @@ func (r *Runtime) reloadContainerNetwork(ctr *Container) (map[string]types.Statu return r.configureNetNS(ctr, ctr.state.NetNS) } +// TODO (5.0): return the statistics per network interface +// This would allow better compat with docker. func getContainerNetIO(ctr *Container) (*netlink.LinkStatistics, error) { var netStats *netlink.LinkStatistics @@ -943,21 +945,39 @@ func getContainerNetIO(ctr *Container) (*netlink.LinkStatistics, error) { return nil, nil } - // FIXME get the interface from the container netstatus - dev := "eth0" netMode := ctr.config.NetMode + netStatus := ctr.getNetworkStatus() if otherCtr != nil { netMode = otherCtr.config.NetMode + netStatus = otherCtr.getNetworkStatus() } if netMode.IsSlirp4netns() { - dev = "tap0" + // create a fake status with correct interface name for the logic below + netStatus = map[string]types.StatusBlock{ + "slirp4netns": { + Interfaces: map[string]types.NetInterface{"tap0": {}}, + }, + } } err := ns.WithNetNSPath(netNSPath, func(_ ns.NetNS) error { - link, err := netlink.LinkByName(dev) - if err != nil { - return err + for _, status := range netStatus { + for dev := range status.Interfaces { + link, err := netlink.LinkByName(dev) + if err != nil { + return err + } + if netStats == nil { + netStats = link.Attrs().Statistics + continue + } + // Currently only Tx/RxBytes are used. + // In the future we should return all stats per interface so that + // api users have a better options. + stats := link.Attrs().Statistics + netStats.TxBytes += stats.TxBytes + netStats.RxBytes += stats.RxBytes + } } - netStats = link.Attrs().Statistics return nil }) return netStats, err diff --git a/libpod/oci.go b/libpod/oci.go index 09f856ac7..90862969c 100644 --- a/libpod/oci.go +++ b/libpod/oci.go @@ -12,9 +12,7 @@ import ( // management logic - e.g., we do not expect it to determine on its own that // calling 'UnpauseContainer()' on a container that is not paused is an error. // The code calling the OCIRuntime will manage this. -// TODO: May want to move the Attach() code under this umbrella. It's highly OCI -// runtime dependent. -// TODO: May want to move the conmon cleanup code here too - it depends on +// TODO: May want to move the conmon cleanup code here - it depends on // Conmon being in use. type OCIRuntime interface { // Name returns the name of the runtime. @@ -52,6 +50,8 @@ type OCIRuntime interface { // UnpauseContainer unpauses the given container. UnpauseContainer(ctr *Container) error + // Attach to a container. + Attach(ctr *Container, params *AttachOptions) error // HTTPAttach performs an attach intended to be transported over HTTP. // For terminal attach, the container's output will be directly streamed // to output; otherwise, STDOUT and STDERR will be multiplexed, with @@ -149,6 +149,30 @@ type OCIRuntime interface { RuntimeInfo() (*define.ConmonInfo, *define.OCIRuntimeInfo, error) } +// AttachOptions are options used when attached to a container or an exec +// session. +type AttachOptions struct { + // Streams are the streams to attach to. + Streams *define.AttachStreams + // DetachKeys containers the key combination that will detach from the + // attach session. Empty string is assumed as no detach keys - user + // detach is impossible. If unset, defaults from containers.conf will be + // used. + DetachKeys *string + // InitialSize is the initial size of the terminal. Set before the + // attach begins. + InitialSize *define.TerminalSize + // AttachReady signals when the attach has successfully completed and + // streaming has begun. + AttachReady chan<- bool + // Start indicates that the container should be started if it is not + // already running. + Start bool + // Started signals when the container has been successfully started. + // Required if Start is true, unused otherwise. + Started chan<- bool +} + // ExecOptions are options passed into ExecContainer. They control the command // that will be executed and how the exec will proceed. type ExecOptions struct { diff --git a/libpod/oci_attach_linux.go b/libpod/oci_conmon_attach_linux.go index 06f8f8719..155a8fbc3 100644 --- a/libpod/oci_attach_linux.go +++ b/libpod/oci_conmon_attach_linux.go @@ -38,19 +38,28 @@ func openUnixSocket(path string) (*net.UnixConn, error) { return net.DialUnix("unixpacket", nil, &net.UnixAddr{Name: fmt.Sprintf("/proc/self/fd/%d", fd), Net: "unixpacket"}) } -// Attach to the given container -// Does not check if state is appropriate -// started is only required if startContainer is true -func (c *Container) attach(streams *define.AttachStreams, keys string, resize <-chan define.TerminalSize, startContainer bool, started chan bool, attachRdy chan<- bool) error { +// Attach to the given container. +// Does not check if state is appropriate. +// started is only required if startContainer is true. +func (r *ConmonOCIRuntime) Attach(c *Container, params *AttachOptions) error { passthrough := c.LogDriver() == define.PassthroughLogging - if !streams.AttachOutput && !streams.AttachError && !streams.AttachInput && !passthrough { + if params == nil || params.Streams == nil { + return errors.Wrapf(define.ErrInternal, "must provide parameters to Attach") + } + + if !params.Streams.AttachOutput && !params.Streams.AttachError && !params.Streams.AttachInput && !passthrough { return errors.Wrapf(define.ErrInvalidArg, "must provide at least one stream to attach to") } - if startContainer && started == nil { + if params.Start && params.Started == nil { return errors.Wrapf(define.ErrInternal, "started chan not passed when startContainer set") } + keys := config.DefaultDetachKeys + if params.DetachKeys != nil { + keys = *params.DetachKeys + } + detachKeys, err := processDetachKeys(keys) if err != nil { return err @@ -60,7 +69,12 @@ func (c *Container) attach(streams *define.AttachStreams, keys string, resize <- if !passthrough { logrus.Debugf("Attaching to container %s", c.ID()) - registerResizeFunc(resize, c.bundlePath()) + // If we have a resize, do it. + if params.InitialSize != nil { + if err := r.AttachResize(c, *params.InitialSize); err != nil { + return err + } + } attachSock, err := c.AttachSocketPath() if err != nil { @@ -80,22 +94,22 @@ func (c *Container) attach(streams *define.AttachStreams, keys string, resize <- // If starting was requested, start the container and notify when that's // done. - if startContainer { + if params.Start { if err := c.start(); err != nil { return err } - started <- true + params.Started <- true } if passthrough { return nil } - receiveStdoutError, stdinDone := setupStdioChannels(streams, conn, detachKeys) - if attachRdy != nil { - attachRdy <- true + receiveStdoutError, stdinDone := setupStdioChannels(params.Streams, conn, detachKeys) + if params.AttachReady != nil { + params.AttachReady <- true } - return readStdio(conn, streams, receiveStdoutError, stdinDone) + return readStdio(conn, params.Streams, receiveStdoutError, stdinDone) } // Attach to the given container's exec session diff --git a/libpod/oci_missing.go b/libpod/oci_missing.go index 86f54c02e..fd8160830 100644 --- a/libpod/oci_missing.go +++ b/libpod/oci_missing.go @@ -108,6 +108,11 @@ func (r *MissingRuntime) UnpauseContainer(ctr *Container) error { return r.printError() } +// Attach is not available as the runtime is missing +func (r *MissingRuntime) Attach(ctr *Container, params *AttachOptions) error { + return r.printError() +} + // HTTPAttach is not available as the runtime is missing func (r *MissingRuntime) HTTPAttach(ctr *Container, req *http.Request, w http.ResponseWriter, streams *HTTPAttachStreams, detachKeys *string, cancel <-chan bool, hijackDone chan<- bool, streamAttach, streamLogs bool) error { return r.printError() diff --git a/libpod/options.go b/libpod/options.go index a02c05537..4b6803c3f 100644 --- a/libpod/options.go +++ b/libpod/options.go @@ -435,6 +435,21 @@ func WithDefaultInfraCommand(cmd string) RuntimeOption { } } +// WithReset instructs libpod to reset all storage to factory defaults. +// All containers, pods, volumes, images, and networks will be removed. +// All directories created by Libpod will be removed. +func WithReset() RuntimeOption { + return func(rt *Runtime) error { + if rt.valid { + return define.ErrRuntimeFinalized + } + + rt.doReset = true + + return nil + } +} + // WithRenumber instructs libpod to perform a lock renumbering while // initializing. This will handle migrations from early versions of libpod with // file locks to newer versions with SHM locking, as well as changes in the diff --git a/libpod/reset.go b/libpod/reset.go index 28d0ee3f6..30eab50fb 100644 --- a/libpod/reset.go +++ b/libpod/reset.go @@ -17,8 +17,78 @@ import ( "github.com/sirupsen/logrus" ) +// removeAllDirs removes all Podman storage directories. It is intended to be +// used as a backup for reset() when that function cannot be used due to +// failures in initializing libpod. +// It does not expect that all the directories match what is in use by Podman, +// as this is a common failure point for `system reset`. As such, our ability to +// interface with containers and pods is somewhat limited. +// This function assumes that we do not have a working c/storage store. +func (r *Runtime) removeAllDirs() error { + var lastErr error + + // Grab the runtime alive lock. + // This ensures that no other Podman process can run while we are doing + // a reset, so no race conditions with containers/pods/etc being created + // while we are resetting storage. + // TODO: maybe want a helper for getting the path? This is duped from + // runtime.go + runtimeAliveLock := filepath.Join(r.config.Engine.TmpDir, "alive.lck") + aliveLock, err := storage.GetLockfile(runtimeAliveLock) + if err != nil { + logrus.Errorf("Lock runtime alive lock %s: %v", runtimeAliveLock, err) + } else { + aliveLock.Lock() + defer aliveLock.Unlock() + } + + // We do not have a store - so we can't really try and remove containers + // or pods or volumes... + // Try and remove the directories, in hopes that they are unmounted. + // This is likely to fail but it's the best we can do. + + // Volume path + if err := os.RemoveAll(r.config.Engine.VolumePath); err != nil { + lastErr = errors.Wrapf(err, "removing volume path") + } + + // Tmpdir + if err := os.RemoveAll(r.config.Engine.TmpDir); err != nil { + if lastErr != nil { + logrus.Errorf("Reset: %v", lastErr) + } + lastErr = errors.Wrapf(err, "removing tmp dir") + } + + // Runroot + if err := os.RemoveAll(r.storageConfig.RunRoot); err != nil { + if lastErr != nil { + logrus.Errorf("Reset: %v", lastErr) + } + lastErr = errors.Wrapf(err, "removing run root") + } + + // Static dir + if err := os.RemoveAll(r.config.Engine.StaticDir); err != nil { + if lastErr != nil { + logrus.Errorf("Reset: %v", lastErr) + } + lastErr = errors.Wrapf(err, "removing static dir") + } + + // Graph root + if err := os.RemoveAll(r.storageConfig.GraphRoot); err != nil { + if lastErr != nil { + logrus.Errorf("Reset: %v", lastErr) + } + lastErr = errors.Wrapf(err, "removing graph root") + } + + return lastErr +} + // Reset removes all storage -func (r *Runtime) Reset(ctx context.Context) error { +func (r *Runtime) reset(ctx context.Context) error { var timeout *uint pods, err := r.GetAllPods() if err != nil { diff --git a/libpod/runtime.go b/libpod/runtime.go index e268c2d17..10c6d21c5 100644 --- a/libpod/runtime.go +++ b/libpod/runtime.go @@ -96,6 +96,10 @@ type Runtime struct { // This bool is just needed so that we can set it for netavark interface. syslog bool + // doReset indicates that the runtime should perform a system reset. + // All Podman files will be removed. + doReset bool + // doRenumber indicates that the runtime should perform a lock renumber // during initialization. // Once the runtime has been initialized and returned, this variable is @@ -235,6 +239,11 @@ func newRuntimeFromConfig(conf *config.Config, options ...RuntimeOption) (*Runti runtime.config.CheckCgroupsAndAdjustConfig() + // If resetting storage, do *not* return a runtime. + if runtime.doReset { + return nil, nil + } + return runtime, nil } @@ -305,6 +314,13 @@ func makeRuntime(runtime *Runtime) (retErr error) { } runtime.conmonPath = cPath + if runtime.noStore && runtime.doReset { + return errors.Wrapf(define.ErrInvalidArg, "cannot perform system reset if runtime is not creating a store") + } + if runtime.doReset && runtime.doRenumber { + return errors.Wrapf(define.ErrInvalidArg, "cannot perform system reset while renumbering locks") + } + // Make the static files directory if it does not exist if err := os.MkdirAll(runtime.config.Engine.StaticDir, 0700); err != nil { // The directory is allowed to exist @@ -339,6 +355,20 @@ func makeRuntime(runtime *Runtime) (retErr error) { // Grab config from the database so we can reset some defaults dbConfig, err := runtime.state.GetDBConfig() if err != nil { + if runtime.doReset { + // We can at least delete the DB and the static files + // directory. + // Can't safely touch anything else because we aren't + // sure of other directories. + if err := runtime.state.Close(); err != nil { + logrus.Errorf("Closing database connection: %v", err) + } else { + if err := os.RemoveAll(runtime.config.Engine.StaticDir); err != nil { + logrus.Errorf("Removing static files directory %v: %v", runtime.config.Engine.StaticDir, err) + } + } + } + return errors.Wrapf(err, "error retrieving runtime configuration from database") } @@ -372,7 +402,13 @@ func makeRuntime(runtime *Runtime) (retErr error) { // Validate our config against the database, now that we've set our // final storage configuration if err := runtime.state.ValidateDBConfig(runtime); err != nil { - return err + // If we are performing a storage reset: continue on with a + // warning. Otherwise we can't `system reset` after a change to + // the core paths. + if !runtime.doReset { + return err + } + logrus.Errorf("Runtime paths differ from those stored in database, storage reset may not remove all files") } if err := runtime.state.SetNamespace(runtime.config.Engine.Namespace); err != nil { @@ -394,6 +430,14 @@ func makeRuntime(runtime *Runtime) (retErr error) { } else if runtime.noStore { logrus.Debug("No store required. Not opening container store.") } else if err := runtime.configureStore(); err != nil { + // Make a best-effort attempt to clean up if performing a + // storage reset. + if runtime.doReset { + if err := runtime.removeAllDirs(); err != nil { + logrus.Errorf("Removing libpod directories: %v", err) + } + } + return err } defer func() { @@ -575,6 +619,18 @@ func makeRuntime(runtime *Runtime) (retErr error) { return err } + // If we're resetting storage, do it now. + // We will not return a valid runtime. + // TODO: Plumb this context out so it can be set. + if runtime.doReset { + // Mark the runtime as valid, so normal functionality "mostly" + // works and we can use regular functions to remove + // ctrs/pods/etc + runtime.valid = true + + return runtime.reset(context.Background()) + } + // If we're renumbering locks, do it now. // It breaks out of normal runtime init, and will not return a valid // runtime. @@ -818,7 +874,7 @@ func (r *Runtime) DeferredShutdown(force bool) { // still containers running or mounted func (r *Runtime) Shutdown(force bool) error { if !r.valid { - return define.ErrRuntimeStopped + return nil } if r.workerChannel != nil { diff --git a/pkg/api/handlers/libpod/play.go b/pkg/api/handlers/libpod/play.go index b71afc28c..36e61c986 100644 --- a/pkg/api/handlers/libpod/play.go +++ b/pkg/api/handlers/libpod/play.go @@ -77,7 +77,7 @@ func PlayKube(w http.ResponseWriter, r *http.Request) { utils.Error(w, http.StatusInternalServerError, err) return } - query.LogDriver = config.Containers.LogDriver + logDriver = config.Containers.LogDriver } containerEngine := abi.ContainerEngine{Libpod: runtime} @@ -89,7 +89,7 @@ func PlayKube(w http.ResponseWriter, r *http.Request) { Networks: query.Network, NoHosts: query.NoHosts, Quiet: true, - LogDriver: query.LogDriver, + LogDriver: logDriver, LogOptions: query.LogOptions, StaticIPs: staticIPs, StaticMACs: staticMACs, diff --git a/pkg/domain/infra/abi/system.go b/pkg/domain/infra/abi/system.go index 2ce190464..762f0d79a 100644 --- a/pkg/domain/infra/abi/system.go +++ b/pkg/domain/infra/abi/system.go @@ -328,7 +328,7 @@ func (ic *ContainerEngine) SystemDf(ctx context.Context, options entities.System } func (se *SystemEngine) Reset(ctx context.Context) error { - return se.Libpod.Reset(ctx) + return nil } func (se *SystemEngine) Renumber(ctx context.Context, flags *pflag.FlagSet, config *entities.PodmanConfig) error { diff --git a/pkg/domain/infra/runtime_abi.go b/pkg/domain/infra/runtime_abi.go index 39989c96b..7b5198d2f 100644 --- a/pkg/domain/infra/runtime_abi.go +++ b/pkg/domain/infra/runtime_abi.go @@ -53,7 +53,7 @@ func NewSystemEngine(setup entities.EngineSetup, facts *entities.PodmanConfig) ( case entities.RenumberMode: r, err = GetRuntimeRenumber(context.Background(), facts.FlagSet, facts) case entities.ResetMode: - r, err = GetRuntimeRenumber(context.Background(), facts.FlagSet, facts) + r, err = GetRuntimeReset(context.Background(), facts.FlagSet, facts) case entities.MigrateMode: name, flagErr := facts.FlagSet.GetString("new-runtime") if flagErr != nil { diff --git a/pkg/domain/infra/runtime_libpod.go b/pkg/domain/infra/runtime_libpod.go index ac557e9de..03e7ffb5d 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" @@ -37,6 +37,7 @@ type engineOpts struct { migrate bool noStore bool withFDS bool + reset bool config *entities.PodmanConfig } @@ -48,6 +49,7 @@ func GetRuntimeMigrate(ctx context.Context, fs *flag.FlagSet, cfg *entities.Podm migrate: true, noStore: false, withFDS: true, + reset: false, config: cfg, }) } @@ -59,6 +61,7 @@ func GetRuntimeDisableFDs(ctx context.Context, fs *flag.FlagSet, cfg *entities.P migrate: false, noStore: false, withFDS: false, + reset: false, config: cfg, }) } @@ -70,6 +73,7 @@ func GetRuntimeRenumber(ctx context.Context, fs *flag.FlagSet, cfg *entities.Pod migrate: false, noStore: false, withFDS: true, + reset: false, config: cfg, }) } @@ -82,6 +86,7 @@ func GetRuntime(ctx context.Context, flags *flag.FlagSet, cfg *entities.PodmanCo migrate: false, noStore: false, withFDS: true, + reset: false, config: cfg, }) }) @@ -95,6 +100,18 @@ func GetRuntimeNoStore(ctx context.Context, fs *flag.FlagSet, cfg *entities.Podm migrate: false, noStore: true, withFDS: true, + reset: false, + config: cfg, + }) +} + +func GetRuntimeReset(ctx context.Context, fs *flag.FlagSet, cfg *entities.PodmanConfig) (*libpod.Runtime, error) { + return getRuntime(ctx, fs, &engineOpts{ + renumber: false, + migrate: false, + noStore: false, + withFDS: true, + reset: true, config: cfg, }) } @@ -161,6 +178,10 @@ func getRuntime(ctx context.Context, fs *flag.FlagSet, opts *engineOpts) (*libpo } } + if opts.reset { + options = append(options, libpod.WithReset()) + } + if opts.renumber { options = append(options, libpod.WithRenumber()) } @@ -375,7 +396,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/qemu/machine.go b/pkg/machine/qemu/machine.go index e3fb3b970..0a85ff5ce 100644 --- a/pkg/machine/qemu/machine.go +++ b/pkg/machine/qemu/machine.go @@ -831,8 +831,14 @@ func (v *MachineVM) Remove(_ string, opts machine.RemoveOptions) (string, func() if err != nil { return "", nil, err } - if state == machine.Running && !opts.Force { - return "", nil, errors.Errorf("running vm %q cannot be destroyed", v.Name) + if state == machine.Running { + if !opts.Force { + return "", nil, errors.Errorf("running vm %q cannot be destroyed", v.Name) + } + err := v.Stop(v.Name, machine.StopOptions{}) + if err != nil { + return "", nil, err + } } // Collect all the files that need to be destroyed @@ -952,7 +958,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 +1478,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/specgen/container_validate.go b/pkg/specgen/container_validate.go index 532a2094f..5616a4511 100644 --- a/pkg/specgen/container_validate.go +++ b/pkg/specgen/container_validate.go @@ -183,10 +183,12 @@ func (s *SpecGenerator) Validate() error { } // Set defaults if network info is not provided - if s.NetNS.NSMode == "" { - s.NetNS.NSMode = Bridge + // when we are rootless we default to slirp4netns + if s.NetNS.IsPrivate() || s.NetNS.IsDefault() { if rootless.IsRootless() { s.NetNS.NSMode = Slirp + } else { + s.NetNS.NSMode = Bridge } } if err := validateNetNS(&s.NetNS); err != nil { diff --git a/pkg/specgen/generate/namespaces.go b/pkg/specgen/generate/namespaces.go index 37d561ec2..4735111c8 100644 --- a/pkg/specgen/generate/namespaces.go +++ b/pkg/specgen/generate/namespaces.go @@ -42,6 +42,9 @@ func GetDefaultNamespaceMode(nsType string, cfg *config.Config, pod *libpod.Pod) podMode = true case nsType == "net" && pod.SharesNet(): podMode = true + case nsType == "net" && pod.NetworkMode() == "host": + toReturn.NSMode = specgen.Host + return toReturn, nil case nsType == "cgroup" && pod.SharesCgroup(): podMode = true } @@ -236,10 +239,12 @@ func namespaceOptions(s *specgen.SpecGenerator, rt *libpod.Runtime, pod *libpod. toReturn = append(toReturn, libpod.WithCgroupsMode(s.CgroupsMode)) } - // Net - // TODO validate CNINetworks, StaticIP, StaticIPv6 are only set if we - // are in bridge mode. postConfigureNetNS := !s.UserNS.IsHost() + // when we are rootless we default to slirp4netns + if rootless.IsRootless() && (s.NetNS.IsPrivate() || s.NetNS.IsDefault()) { + s.NetNS.NSMode = specgen.Slirp + } + switch s.NetNS.NSMode { case specgen.FromPod: if pod == nil || infraCtr == nil { @@ -262,9 +267,7 @@ func namespaceOptions(s *specgen.SpecGenerator, rt *libpod.Runtime, pod *libpod. val = fmt.Sprintf("slirp4netns:%s", s.NetNS.Value) } toReturn = append(toReturn, libpod.WithNetNS(portMappings, expose, postConfigureNetNS, val, nil)) - case specgen.Private: - fallthrough - case specgen.Bridge: + case specgen.Bridge, specgen.Private, specgen.Default: portMappings, expose, err := createPortMappings(s, imageData) if err != nil { return nil, err diff --git a/pkg/specgen/generate/pod_create.go b/pkg/specgen/generate/pod_create.go index 5b7bb2b57..d4f281a11 100644 --- a/pkg/specgen/generate/pod_create.go +++ b/pkg/specgen/generate/pod_create.go @@ -141,6 +141,9 @@ func MapSpec(p *specgen.PodSpecGenerator) (*specgen.SpecGenerator, error) { case specgen.Bridge: p.InfraContainerSpec.NetNS.NSMode = specgen.Bridge logrus.Debugf("Pod using bridge network mode") + case specgen.Private: + p.InfraContainerSpec.NetNS.NSMode = specgen.Private + logrus.Debugf("Pod will use default network mode") case specgen.Host: logrus.Debugf("Pod will use host networking") if len(p.InfraContainerSpec.PortMappings) > 0 || @@ -151,15 +154,15 @@ func MapSpec(p *specgen.PodSpecGenerator) (*specgen.SpecGenerator, error) { p.InfraContainerSpec.NetNS.NSMode = specgen.Host case specgen.Slirp: logrus.Debugf("Pod will use slirp4netns") - if p.InfraContainerSpec.NetNS.NSMode != "host" { + if p.InfraContainerSpec.NetNS.NSMode != specgen.Host { p.InfraContainerSpec.NetworkOptions = p.NetworkOptions - p.InfraContainerSpec.NetNS.NSMode = specgen.NamespaceMode("slirp4netns") + p.InfraContainerSpec.NetNS.NSMode = specgen.Slirp } case specgen.NoNetwork: logrus.Debugf("Pod will not use networking") if len(p.InfraContainerSpec.PortMappings) > 0 || len(p.InfraContainerSpec.Networks) > 0 || - p.InfraContainerSpec.NetNS.NSMode == "host" { + p.InfraContainerSpec.NetNS.NSMode == specgen.Host { return nil, errors.Wrapf(define.ErrInvalidArg, "cannot disable pod network if network-related configuration is specified") } p.InfraContainerSpec.NetNS.NSMode = specgen.NoNetwork diff --git a/pkg/specgen/namespaces.go b/pkg/specgen/namespaces.go index 5a3b94ca4..f1343f6e2 100644 --- a/pkg/specgen/namespaces.go +++ b/pkg/specgen/namespaces.go @@ -10,7 +10,6 @@ import ( "github.com/containers/common/pkg/cgroups" cutil "github.com/containers/common/pkg/util" "github.com/containers/podman/v4/libpod/define" - "github.com/containers/podman/v4/pkg/rootless" "github.com/containers/podman/v4/pkg/util" "github.com/containers/storage" spec "github.com/opencontainers/runtime-spec/specs-go" @@ -319,62 +318,6 @@ func ParseUserNamespace(ns string) (Namespace, error) { return ParseNamespace(ns) } -// ParseNetworkNamespace parses a network namespace specification in string -// form. -// Returns a namespace and (optionally) a list of CNI networks to join. -func ParseNetworkNamespace(ns string, rootlessDefaultCNI bool) (Namespace, map[string]types.PerNetworkOptions, error) { - toReturn := Namespace{} - networks := make(map[string]types.PerNetworkOptions) - // Net defaults to Slirp on rootless - switch { - case ns == string(Slirp), strings.HasPrefix(ns, string(Slirp)+":"): - toReturn.NSMode = Slirp - case ns == string(FromPod): - toReturn.NSMode = FromPod - case ns == "" || ns == string(Default) || ns == string(Private): - if rootless.IsRootless() { - if rootlessDefaultCNI { - toReturn.NSMode = Bridge - } else { - toReturn.NSMode = Slirp - } - } else { - toReturn.NSMode = Bridge - } - case ns == string(Bridge): - toReturn.NSMode = Bridge - case ns == string(NoNetwork): - toReturn.NSMode = NoNetwork - case ns == string(Host): - toReturn.NSMode = Host - case strings.HasPrefix(ns, "ns:"): - split := strings.SplitN(ns, ":", 2) - if len(split) != 2 { - return toReturn, nil, errors.Errorf("must provide a path to a namespace when specifying \"ns:\"") - } - toReturn.NSMode = Path - toReturn.Value = split[1] - case strings.HasPrefix(ns, string(FromContainer)+":"): - split := strings.SplitN(ns, ":", 2) - if len(split) != 2 { - return toReturn, nil, errors.Errorf("must provide name or ID or a container when specifying \"container:\"") - } - toReturn.NSMode = FromContainer - toReturn.Value = split[1] - default: - // Assume we have been given a list of CNI networks. - // Which only works in bridge mode, so set that. - networkList := strings.Split(ns, ",") - for _, net := range networkList { - networks[net] = types.PerNetworkOptions{} - } - - toReturn.NSMode = Bridge - } - - return toReturn, networks, nil -} - // ParseNetworkFlag parses a network string slice into the network options // If the input is nil or empty it will use the default setting from containers.conf func ParseNetworkFlag(networks []string) (Namespace, map[string]types.PerNetworkOptions, map[string][]string, error) { @@ -400,13 +343,7 @@ func ParseNetworkFlag(networks []string) (Namespace, map[string]types.PerNetwork case ns == string(FromPod): toReturn.NSMode = FromPod case ns == "" || ns == string(Default) || ns == string(Private): - // Net defaults to Slirp on rootless - if rootless.IsRootless() { - toReturn.NSMode = Slirp - break - } - // if root we use bridge - fallthrough + toReturn.NSMode = Private case ns == string(Bridge), strings.HasPrefix(ns, string(Bridge)+":"): toReturn.NSMode = Bridge parts := strings.SplitN(ns, ":", 2) diff --git a/pkg/specgen/namespaces_test.go b/pkg/specgen/namespaces_test.go index 368c92bd5..d03a6d032 100644 --- a/pkg/specgen/namespaces_test.go +++ b/pkg/specgen/namespaces_test.go @@ -5,7 +5,6 @@ import ( "testing" "github.com/containers/common/libnetwork/types" - "github.com/containers/podman/v4/pkg/rootless" "github.com/stretchr/testify/assert" ) @@ -17,14 +16,6 @@ func parsMacNoErr(mac string) types.HardwareAddr { func TestParseNetworkFlag(t *testing.T) { // root and rootless have different defaults defaultNetName := "default" - defaultNetworks := map[string]types.PerNetworkOptions{ - defaultNetName: {}, - } - defaultNsMode := Namespace{NSMode: Bridge} - if rootless.IsRootless() { - defaultNsMode = Namespace{NSMode: Slirp} - defaultNetworks = map[string]types.PerNetworkOptions{} - } tests := []struct { name string @@ -37,26 +28,26 @@ func TestParseNetworkFlag(t *testing.T) { { name: "empty input", args: nil, - nsmode: defaultNsMode, - networks: defaultNetworks, + nsmode: Namespace{NSMode: Private}, + networks: map[string]types.PerNetworkOptions{}, }, { name: "empty string as input", args: []string{}, - nsmode: defaultNsMode, - networks: defaultNetworks, + nsmode: Namespace{NSMode: Private}, + networks: map[string]types.PerNetworkOptions{}, }, { name: "default mode", args: []string{"default"}, - nsmode: defaultNsMode, - networks: defaultNetworks, + nsmode: Namespace{NSMode: Private}, + networks: map[string]types.PerNetworkOptions{}, }, { name: "private mode", args: []string{"private"}, - nsmode: defaultNsMode, - networks: defaultNetworks, + nsmode: Namespace{NSMode: Private}, + networks: map[string]types.PerNetworkOptions{}, }, { name: "bridge mode", diff --git a/pkg/specgen/podspecgen.go b/pkg/specgen/podspecgen.go index 603506241..777097ac5 100644 --- a/pkg/specgen/podspecgen.go +++ b/pkg/specgen/podspecgen.go @@ -4,6 +4,7 @@ import ( "net" "github.com/containers/common/libnetwork/types" + storageTypes "github.com/containers/storage/types" spec "github.com/opencontainers/runtime-spec/specs-go" ) @@ -222,6 +223,10 @@ type PodResourceConfig struct { type PodSecurityConfig struct { SecurityOpt []string `json:"security_opt,omitempty"` + // IDMappings are UID and GID mappings that will be used by user + // namespaces. + // Required if UserNS is private. + IDMappings *storageTypes.IDMappingOptions `json:"idmappings,omitempty"` } // NewPodSpecGenerator creates a new pod spec diff --git a/pkg/specgenutil/createparse.go b/pkg/specgenutil/createparse.go index fb5f9c351..132f93771 100644 --- a/pkg/specgenutil/createparse.go +++ b/pkg/specgenutil/createparse.go @@ -18,20 +18,5 @@ func validate(c *entities.ContainerCreateOptions) error { return err } - var imageVolType = map[string]string{ - "bind": "", - "tmpfs": "", - "ignore": "", - } - if _, ok := imageVolType[c.ImageVolume]; !ok { - switch { - case c.IsInfra: - c.ImageVolume = "bind" - case c.IsClone: // the image volume type will be deduced later from the container we are cloning - return nil - default: - return errors.Errorf("invalid image-volume type %q. Pick one of bind, tmpfs, or ignore", c.ImageVolume) - } - } - return nil + return config.ValidateImageVolumeMode(c.ImageVolume) } diff --git a/pkg/specgenutil/specgen.go b/pkg/specgenutil/specgen.go index efaade9cd..6d70af106 100644 --- a/pkg/specgenutil/specgen.go +++ b/pkg/specgenutil/specgen.go @@ -229,9 +229,11 @@ func setNamespaces(s *specgen.SpecGenerator, c *entities.ContainerCreateOptions) } func FillOutSpecGen(s *specgen.SpecGenerator, c *entities.ContainerCreateOptions, args []string) error { - var ( - err error - ) + rtc, err := config.Default() + if err != nil { + return err + } + // validate flags as needed if err := validate(c); err != nil { return err @@ -479,8 +481,13 @@ func FillOutSpecGen(s *specgen.SpecGenerator, c *entities.ContainerCreateOptions if len(s.HostUsers) == 0 || len(c.HostUsers) != 0 { s.HostUsers = c.HostUsers } - if len(s.ImageVolumeMode) == 0 || len(c.ImageVolume) != 0 { - s.ImageVolumeMode = c.ImageVolume + if len(c.ImageVolume) != 0 { + if len(s.ImageVolumeMode) == 0 { + s.ImageVolumeMode = c.ImageVolume + } + } + if len(s.ImageVolumeMode) == 0 { + s.ImageVolumeMode = rtc.Engine.ImageVolumeMode } if s.ImageVolumeMode == "bind" { s.ImageVolumeMode = "anonymous" @@ -550,11 +557,6 @@ func FillOutSpecGen(s *specgen.SpecGenerator, c *entities.ContainerCreateOptions s.CgroupsMode = c.CgroupsMode } if s.CgroupsMode == "" { - rtc, err := config.Default() - if err != nil { - return err - } - s.CgroupsMode = rtc.Cgroups() } diff --git a/test/e2e/logs_test.go b/test/e2e/logs_test.go index 0d24a7e17..14dd6b6b8 100644 --- a/test/e2e/logs_test.go +++ b/test/e2e/logs_test.go @@ -8,6 +8,7 @@ import ( "time" . "github.com/containers/podman/v4/test/utils" + "github.com/containers/storage/pkg/stringid" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" . "github.com/onsi/gomega/gexec" @@ -370,6 +371,26 @@ var _ = Describe("Podman logs", func() { Expect(results.OutputToString()).To(Equal("stdout")) Expect(results.ErrorToString()).To(Equal("stderr")) }) + + It("podman logs partial log lines: "+log, func() { + skipIfJournaldInContainer() + + cname := "log-test" + content := stringid.GenerateNonCryptoID() + // use printf to print no extra newline + logc := podmanTest.Podman([]string{"run", "--log-driver", log, "--name", cname, ALPINE, "printf", content}) + logc.WaitWithDefaultTimeout() + Expect(logc).To(Exit(0)) + // Important: do not use OutputToString(), this will remove the trailing newline from the output. + // However this test must make sure that there is no such extra newline. + Expect(string(logc.Out.Contents())).To(Equal(content)) + + logs := podmanTest.Podman([]string{"logs", cname}) + logs.WaitWithDefaultTimeout() + Expect(logs).To(Exit(0)) + // see comment above + Expect(string(logs.Out.Contents())).To(Equal(content)) + }) } It("using journald for container with container tag", func() { diff --git a/test/e2e/network_connect_disconnect_test.go b/test/e2e/network_connect_disconnect_test.go index a0716c84d..019bb4617 100644 --- a/test/e2e/network_connect_disconnect_test.go +++ b/test/e2e/network_connect_disconnect_test.go @@ -114,6 +114,11 @@ var _ = Describe("Podman network connect and disconnect", func() { exec3.WaitWithDefaultTimeout() Expect(exec3).Should(Exit(0)) Expect(strings.Contains(exec3.OutputToString(), ns)).To(BeFalse()) + + // make sure stats still works https://github.com/containers/podman/issues/13824 + stats := podmanTest.Podman([]string{"stats", "test", "--no-stream"}) + stats.WaitWithDefaultTimeout() + Expect(stats).Should(Exit(0)) }) It("bad network name in connect should result in error", func() { @@ -237,6 +242,11 @@ var _ = Describe("Podman network connect and disconnect", func() { Expect(exec3).Should(Exit(0)) Expect(strings.Contains(exec3.OutputToString(), ns)).To(BeTrue()) + // make sure stats works https://github.com/containers/podman/issues/13824 + stats := podmanTest.Podman([]string{"stats", "test", "--no-stream"}) + stats.WaitWithDefaultTimeout() + Expect(stats).Should(Exit(0)) + // make sure no logrus errors are shown https://github.com/containers/podman/issues/9602 rm := podmanTest.Podman([]string{"rm", "--time=0", "-f", "test"}) rm.WaitWithDefaultTimeout() diff --git a/test/e2e/play_kube_test.go b/test/e2e/play_kube_test.go index 31044f68b..61f2b3a1c 100644 --- a/test/e2e/play_kube_test.go +++ b/test/e2e/play_kube_test.go @@ -3688,7 +3688,7 @@ ENV OPENJ9_JAVA_OPTIONS=%q }) // Check the block devices are exposed inside container - It("ddpodman play kube expose block device inside container", func() { + It("podman play kube expose block device inside container", func() { SkipIfRootless("It needs root access to create devices") // randomize the folder name to avoid error when running tests with multiple nodes @@ -3727,7 +3727,7 @@ ENV OPENJ9_JAVA_OPTIONS=%q }) // Check the char devices are exposed inside container - It("ddpodman play kube expose character device inside container", func() { + It("podman play kube expose character device inside container", func() { SkipIfRootless("It needs root access to create devices") // randomize the folder name to avoid error when running tests with multiple nodes @@ -3781,7 +3781,7 @@ ENV OPENJ9_JAVA_OPTIONS=%q Expect(kube).Should(Exit(125)) }) - It("ddpodman play kube reports error when we try to expose char device as block device", func() { + It("podman play kube reports error when we try to expose char device as block device", func() { SkipIfRootless("It needs root access to create devices") // randomize the folder name to avoid error when running tests with multiple nodes @@ -3807,7 +3807,7 @@ ENV OPENJ9_JAVA_OPTIONS=%q Expect(kube).Should(Exit(125)) }) - It("ddpodman play kube reports error when we try to expose block device as char device", func() { + It("podman play kube reports error when we try to expose block device as char device", func() { SkipIfRootless("It needs root access to create devices") // randomize the folder name to avoid error when running tests with multiple nodes diff --git a/test/e2e/pod_infra_container_test.go b/test/e2e/pod_infra_container_test.go index ab204992c..ad2db2411 100644 --- a/test/e2e/pod_infra_container_test.go +++ b/test/e2e/pod_infra_container_test.go @@ -125,6 +125,19 @@ var _ = Describe("Podman pod create", func() { session = podmanTest.Podman([]string{"run", fedoraMinimal, "curl", "-f", "localhost"}) session.WaitWithDefaultTimeout() Expect(session).To(ExitWithError()) + + session = podmanTest.Podman([]string{"pod", "create", "--network", "host"}) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + + session = podmanTest.Podman([]string{"run", "-dt", "--pod", session.OutputToString(), ALPINE}) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + + session = podmanTest.Podman([]string{"inspect", "--format", "'{{.NetworkSettings.SandboxKey}}'", session.OutputToString()}) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + Expect(session.OutputToString()).Should(ContainSubstring("''")) // no network path... host }) It("podman pod correctly sets up IPCNS", func() { diff --git a/test/e2e/save_test.go b/test/e2e/save_test.go index 897e49ef7..7a1fb0fc2 100644 --- a/test/e2e/save_test.go +++ b/test/e2e/save_test.go @@ -226,13 +226,17 @@ default-docker: }) It("podman save --multi-image-archive (untagged images)", func() { - // Refer to images via ID instead of tag. - session := podmanTest.Podman([]string{"images", "--format", "{{.ID}}"}) + // #14468: to make execution time more predictable, save at + // most three images and sort them by size. + session := podmanTest.Podman([]string{"images", "--sort", "size", "--format", "{{.ID}}"}) session.WaitWithDefaultTimeout() Expect(session).Should(Exit(0)) ids := session.OutputToStringArray() Expect(len(ids)).To(BeNumerically(">", 1), "We need to have *some* images to save") + if len(ids) > 3 { + ids = ids[:3] + } multiImageSave(podmanTest, ids) }) }) diff --git a/test/system/120-load.bats b/test/system/120-load.bats index 45e0b3362..5a7f63b43 100644 --- a/test/system/120-load.bats +++ b/test/system/120-load.bats @@ -121,7 +121,7 @@ verify_iid_and_name() { run_podman untag $IMAGE $newname run_podman image scp -q ${notme}@localhost::$newname - expect="Loaded image(s): $newname" + expect="Loaded image: $newname" is "$output" "$expect" "-q silences output" # Confirm that we have it, and that its digest matches our original diff --git a/test/system/160-volumes.bats b/test/system/160-volumes.bats index 5b0460723..797883ec6 100644 --- a/test/system/160-volumes.bats +++ b/test/system/160-volumes.bats @@ -411,4 +411,43 @@ NeedsChown | true fi } +@test "podman --image-volume" { + tmpdir=$PODMAN_TMPDIR/volume-test + mkdir -p $tmpdir + containerfile=$tmpdir/Containerfile + cat >$containerfile <<EOF +FROM $IMAGE +VOLUME /data +EOF + fs=$(stat -f -c %T .) + run_podman build -t volume_image $tmpdir + + containersconf=$tmpdir/containers.conf + cat >$containersconf <<EOF +[engine] +image_volume_mode="tmpfs" +EOF + + run_podman run --image-volume tmpfs --rm volume_image stat -f -c %T /data + is "$output" "tmpfs" "Should be tmpfs" + + run_podman 1 run --image-volume ignore --rm volume_image stat -f -c %T /data + is "$output" "stat: can't read file system information for '/data': No such file or directory" "Should fail with /data does not exists" + + CONTAINERS_CONF="$containersconf" run_podman run --rm volume_image stat -f -c %T /data + is "$output" "tmpfs" "Should be tmpfs" + + CONTAINERS_CONF="$containersconf" run_podman run --image-volume bind --rm volume_image stat -f -c %T /data + assert "$output" != "tmpfs" "Should match hosts $fs" + + CONTAINERS_CONF="$containersconf" run_podman run --image-volume tmpfs --rm volume_image stat -f -c %T /data + is "$output" "tmpfs" "Should be tmpfs" + + CONTAINERS_CONF="$containersconf" run_podman 1 run --image-volume ignore --rm volume_image stat -f -c %T /data + is "$output" "stat: can't read file system information for '/data': No such file or directory" "Should fail with /data does not exists" + + run_podman rm --all --force -t 0 + run_podman image rm --force localhost/volume_image +} + # vim: filetype=sh diff --git a/test/system/170-run-userns.bats b/test/system/170-run-userns.bats index b80351902..46cb37b9d 100644 --- a/test/system/170-run-userns.bats +++ b/test/system/170-run-userns.bats @@ -38,10 +38,12 @@ function _require_crun() { @test "rootful pod with custom ID mapping" { skip_if_rootless "does not work rootless - rootful feature" - skip_if_remote "remote --uidmap is broken (see #14233)" random_pod_name=$(random_string 30) run_podman pod create --uidmap 0:200000:5000 --name=$random_pod_name run_podman pod start $random_pod_name + run_podman pod inspect --format '{{.InfraContainerID}}' $random_pod_name + run podman inspect --format '{{.HostConfig.IDMappings.UIDMap}}' $output + is "$output" ".*0:200000:5000" "UID Map Successful" # Remove the pod and the pause image run_podman pod rm $random_pod_name diff --git a/test/system/600-completion.bats b/test/system/600-completion.bats index 018e95e78..2de9b1ae1 100644 --- a/test/system/600-completion.bats +++ b/test/system/600-completion.bats @@ -8,6 +8,16 @@ load helpers +function setup() { + # $PODMAN may be a space-separated string, e.g. if we include a --url. + local -a podman_as_array=($PODMAN) + # __completeNoDesc must be the first arg if we running the completion cmd + # set the var for the run_completion function + PODMAN_COMPLETION="${podman_as_array[0]} __completeNoDesc ${podman_as_array[@]:1}" + + basic_setup +} + # Returns true if we are able to podman-pause function _can_pause() { # Even though we're just trying completion, not an actual unpause, @@ -88,8 +98,14 @@ function check_shell_completion() { continue 2 fi + name=$random_container_name + # special case podman cp suggest containers names with a colon + if [[ $cmd = "cp" ]]; then + name="$name:" + fi + run_completion "$@" $cmd "${extra_args[@]}" "" - is "$output" ".*-$random_container_name${nl}" \ + is "$output" ".*-$name${nl}" \ "$* $cmd: actual container listed in suggestions" match=true @@ -175,7 +191,7 @@ function check_shell_completion() { _check_completion_end NoSpace else _check_completion_end Default - assert "${#lines[@]}" -eq 2 "$* $cmd: Suggestions are in the output" + _check_no_suggestions fi ;; @@ -205,16 +221,7 @@ function check_shell_completion() { if [[ ! ${args##* } =~ "..." ]]; then run_completion "$@" $cmd "${extra_args[@]}" "" _check_completion_end NoFileComp - if [ ${#lines[@]} -gt 2 ]; then - # checking for line count is not enough since we may include additional debug output - # lines starting with [Debug] are allowed - i=0 - length=$(( ${#lines[@]} - 2 )) - while [[ i -lt length ]]; do - assert "${lines[$i]:0:7}" == "[Debug]" "Suggestions are in the output" - i=$(( i + 1 )) - done - fi + _check_no_suggestions fi done @@ -231,6 +238,24 @@ function _check_completion_end() { is "${lines[-1]}" "Completion ended with directive: ShellCompDirective$1" "Completion has wrong ShellCompDirective set" } +# Check that there are no suggestions in the output. +# We could only check stdout and not stderr but this is not possible with bats. +# By default we always have two extra lines at the end for the ShellCompDirective. +# Then we could also have other extra lines for debugging, they will always start +# with [Debug], e.g. `[Debug] [Error] no container with name or ID "t12" found: no such container`. +function _check_no_suggestions() { + if [ ${#lines[@]} -gt 2 ]; then + # Checking for line count is not enough since we may include additional debug output. + # Lines starting with [Debug] are allowed. + local i=0 + length=$((${#lines[@]} - 2)) + while [[ i -lt length ]]; do + assert "${lines[$i]:0:7}" == "[Debug]" "Unexpected non-Debug output line: ${lines[$i]}" + i=$((i + 1)) + done + fi +} + @test "podman shell completion test" { @@ -280,11 +305,6 @@ function _check_completion_end() { # create secret run_podman secret create $random_secret_name $secret_file - # $PODMAN may be a space-separated string, e.g. if we include a --url. - local -a podman_as_array=($PODMAN) - # __completeNoDesc must be the first arg if we running the completion cmd - PODMAN_COMPLETION="${podman_as_array[0]} __completeNoDesc ${podman_as_array[@]:1}" - # Called with no args -- start with 'podman --help'. check_shell_completion() will # recurse for any subcommands. check_shell_completion @@ -316,3 +336,41 @@ function _check_completion_end() { done <<<"$output" } + +@test "podman shell completion for paths in container/image" { + skip_if_remote "mounting via remote does not work" + for cmd in create run; do + run_completion $cmd $IMAGE "" + assert "$output" =~ ".*^/etc\$.*^/home\$.*^/root\$.*" "root directories suggested (cmd: podman $cmd)" + + # check completion for subdirectory + run_completion $cmd $IMAGE "/etc" + # It should be safe to assume the os-release file always exists in $IMAGE + assert "$output" =~ ".*^/etc/os-release\$.*" "/etc files suggested (cmd: podman $cmd /etc)" + # check completion for partial file name + run_completion $cmd $IMAGE "/etc/os-" + assert "$output" =~ ".*^/etc/os-release\$.*" "/etc files suggested (cmd: podman $cmd /etc/os-)" + + # check completion with relative path components + # It is important the we will still use the image root and not escape to the host + run_completion $cmd $IMAGE "../../" + assert "$output" =~ ".*^../../etc\$.*^../../home\$.*" "relative root directories suggested (cmd: podman $cmd ../../)" + done + + random_name=$(random_string 30) + random_file=$(random_string 30) + run_podman run --name $random_name $IMAGE touch /tmp/$random_file + + # check completion for podman cp + run_completion cp "" + assert "$output" =~ ".*^$random_name\:\$.*" "podman cp suggest container names" + + run_completion cp "$random_name:" + assert "$output" =~ ".*^$random_name\:/etc\$.*" "podman cp suggest paths in container" + + run_completion cp "$random_name:/tmp" + assert "$output" =~ ".*^$random_name\:/tmp/$random_file\$.*" "podman cp suggest custom file in container" + + # cleanup container + run_podman rm $random_name +} diff --git a/vendor/github.com/containernetworking/cni/pkg/invoke/exec.go b/vendor/github.com/containernetworking/cni/pkg/invoke/exec.go index e79bffe63..55ed392a0 100644 --- a/vendor/github.com/containernetworking/cni/pkg/invoke/exec.go +++ b/vendor/github.com/containernetworking/cni/pkg/invoke/exec.go @@ -16,6 +16,7 @@ package invoke import ( "context" + "encoding/json" "fmt" "os" @@ -33,6 +34,43 @@ type Exec interface { Decode(jsonBytes []byte) (version.PluginInfo, error) } +// Plugin must return result in same version as specified in netconf; but +// for backwards compatibility reasons if the result version is empty use +// config version (rather than technically correct 0.1.0). +// https://github.com/containernetworking/cni/issues/895 +func fixupResultVersion(netconf, result []byte) (string, []byte, error) { + versionDecoder := &version.ConfigDecoder{} + confVersion, err := versionDecoder.Decode(netconf) + if err != nil { + return "", nil, err + } + + var rawResult map[string]interface{} + if err := json.Unmarshal(result, &rawResult); err != nil { + return "", nil, fmt.Errorf("failed to unmarshal raw result: %w", err) + } + + // Manually decode Result version; we need to know whether its cniVersion + // is empty, while built-in decoders (correctly) substitute 0.1.0 for an + // empty version per the CNI spec. + if resultVerRaw, ok := rawResult["cniVersion"]; ok { + resultVer, ok := resultVerRaw.(string) + if ok && resultVer != "" { + return resultVer, result, nil + } + } + + // If the cniVersion is not present or empty, assume the result is + // the same CNI spec version as the config + rawResult["cniVersion"] = confVersion + newBytes, err := json.Marshal(rawResult) + if err != nil { + return "", nil, fmt.Errorf("failed to remarshal fixed result: %w", err) + } + + return confVersion, newBytes, nil +} + // For example, a testcase could pass an instance of the following fakeExec // object to ExecPluginWithResult() to verify the incoming stdin and environment // and provide a tailored response: @@ -84,7 +122,12 @@ func ExecPluginWithResult(ctx context.Context, pluginPath string, netconf []byte return nil, err } - return create.CreateFromBytes(stdoutBytes) + resultVersion, fixedBytes, err := fixupResultVersion(netconf, stdoutBytes) + if err != nil { + return nil, err + } + + return create.Create(resultVersion, fixedBytes) } func ExecPluginWithoutResult(ctx context.Context, pluginPath string, netconf []byte, args CNIArgs, exec Exec) error { diff --git a/vendor/modules.txt b/vendor/modules.txt index 9bd500ee8..e5de2d8d4 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -72,7 +72,7 @@ github.com/containerd/containerd/sys # github.com/containerd/stargz-snapshotter/estargz v0.11.4 github.com/containerd/stargz-snapshotter/estargz github.com/containerd/stargz-snapshotter/estargz/errorutil -# github.com/containernetworking/cni v1.1.0 +# github.com/containernetworking/cni v1.1.1 ## explicit github.com/containernetworking/cni/libcni github.com/containernetworking/cni/pkg/invoke |