diff options
102 files changed, 2363 insertions, 1845 deletions
diff --git a/.cirrus.yml b/.cirrus.yml index fe00974b3..21100388f 100644 --- a/.cirrus.yml +++ b/.cirrus.yml @@ -59,6 +59,7 @@ env: curl --fail --location -O --url https://api.cirrus-ci.com/v1/artifact/build/${CIRRUS_BUILD_ID} + # Default timeout for each task timeout_in: 60m @@ -71,7 +72,9 @@ gcp_credentials: ENCRYPTED[a28959877b2c9c36f151781b0a05407218cda646c7d047fc556e4 ext_svc_check_task: alias: 'ext_svc_check' # int. ref. name - required for depends_on reference name: "Ext. services" # Displayed Title - has no other significance - skip: &tags "$CIRRUS_TAG != ''" # Don't run on tags + # Don't create this task for new tags so release process is more reliable + # Docs: ./contrib/cirrus/CIModes.md + only_if: $CIRRUS_TAG == '' # Default/small container image to execute tasks with container: &smallcontainer image: ${CTR_FQIN} @@ -117,7 +120,9 @@ ext_svc_check_task: automation_task: alias: 'automation' name: "Check Automation" - skip: &branches_and_tags "$CIRRUS_PR == '' || $CIRRUS_TAG != ''" # Don't run on branches/tags + # This task is not needed for branches, tags, or cron runs. + # Docs: ./contrib/cirrus/CIModes.md + only_if: &is_pr "$CIRRUS_PR != ''" container: *smallcontainer env: TEST_FLAVOR: automation @@ -138,6 +143,9 @@ automation_task: build_task: alias: 'build' name: 'Build for $DISTRO_NV' + # Multiarch doesn't depend on buildability in this automation context + # Docs: ./contrib/cirrus/CIModes.md + only_if: "$CIRRUS_CRON != 'multiarch'" gce_instance: &standardvm image_project: libpod-218412 zone: "us-central1-a" @@ -189,7 +197,8 @@ validate_task: # automation reliability/speed in those contexts. Any missed errors due # to nonsequential PR merging practices, will be caught on a future PR, # build or test task failures. - skip: *branches_and_tags + # Docs: ./contrib/cirrus/CIModes.md + only_if: *is_pr depends_on: - ext_svc_check - automation @@ -218,9 +227,12 @@ validate_task: bindings_task: name: "Test Bindings" alias: bindings - # Don't run for [CI:DOCS] or [CI:BUILD] - only_if: ¬_build $CIRRUS_CHANGE_TITLE !=~ '.*CI:DOCS.*' && $CIRRUS_CHANGE_TITLE !=~ '.*CI:BUILD.*' - skip: *branches_and_tags + # Don't create task for PRs using [CI:DOCS] or [CI:BUILD] + # Docs: ./contrib/cirrus/CIModes.md + only_if: >- + $CIRRUS_PR != '' && + $CIRRUS_CHANGE_TITLE !=~ '.*CI:DOCS.*' && + $CIRRUS_CHANGE_TITLE !=~ '.*CI:BUILD.*' depends_on: - build gce_instance: *standardvm @@ -249,6 +261,11 @@ bindings_task: swagger_task: name: "Test Swagger" alias: swagger + # Don't create task for [CI:BUILD] or multiarch builds + # Docs: ./contrib/cirrus/CIModes.md + only_if: >- + $CIRRUS_CHANGE_TITLE !=~ '.*CI:BUILD.*' && + $CIRRUS_CRON != 'multiarch' depends_on: - build gce_instance: *standardvm @@ -276,7 +293,8 @@ swagger_task: consistency_task: name: "Test Code Consistency" alias: consistency - skip: *tags + # Docs: ./contrib/cirrus/CIModes.md + only_if: *is_pr depends_on: - build container: *smallcontainer @@ -297,8 +315,11 @@ consistency_task: alt_build_task: name: "$ALT_NAME" alias: alt_build - # Don't run for [CI:DOCS]; DO run for [CI:BUILD] - only_if: ¬_docs $CIRRUS_CHANGE_TITLE !=~ '.*CI:DOCS.*' + # Don't create task for [CI:DOCS] or multiarch builds + # Docs: ./contrib/cirrus/CIModes.md + only_if: ¬_docs_multiarch >- + $CIRRUS_CHANGE_TITLE !=~ '.*CI:DOCS.*' && + $CIRRUS_CRON != 'multiarch' depends_on: - build env: @@ -330,7 +351,8 @@ alt_build_task: osx_alt_build_task: name: "OSX Cross" alias: osx_alt_build - only_if: *not_docs + # Docs: ./contrib/cirrus/CIModes.md + only_if: *not_docs_multiarch depends_on: - build env: @@ -359,8 +381,14 @@ osx_alt_build_task: docker-py_test_task: name: Docker-py Compat. alias: docker-py_test - skip: *tags - only_if: *not_build + # Don't create task for tags, branches, or PRs w/ [CI:DOCS] or [CI:BUILD] + # N/B: for PRs $CIRRUS_BRANCH == 'pull/<number>' + # Docs: ./contrib/cirrus/CIModes.md + only_if: ¬_tag_branch_build_docs >- + $CIRRUS_PR != '' && + $CIRRUS_CHANGE_TITLE !=~ '.*CI:DOCS.*' && + $CIRRUS_CHANGE_TITLE !=~ '.*CI:BUILD.*' + depends_on: - build gce_instance: *standardvm @@ -379,8 +407,8 @@ docker-py_test_task: unit_test_task: name: "Unit tests on $DISTRO_NV" alias: unit_test - skip: *tags - only_if: *not_build + # Docs: ./contrib/cirrus/CIModes.md + only_if: *not_tag_branch_build_docs depends_on: - validate matrix: @@ -404,8 +432,8 @@ unit_test_task: apiv2_test_task: name: "APIv2 test on $DISTRO_NV" alias: apiv2_test - only_if: *not_build - skip: *tags + # Docs: ./contrib/cirrus/CIModes.md + only_if: *not_tag_branch_build_docs depends_on: - validate gce_instance: *standardvm @@ -424,8 +452,8 @@ apiv2_test_task: compose_test_task: name: "$TEST_FLAVOR test on $DISTRO_NV ($PRIV_NAME)" alias: compose_test - only_if: *not_build - skip: *tags + # Docs: ./contrib/cirrus/CIModes.md + only_if: *not_tag_branch_build_docs depends_on: - validate gce_instance: *standardvm @@ -457,8 +485,8 @@ local_integration_test_task: &local_integration_test_task # <int.|sys.> <podman|remote> <Distro NV> <root|rootless> name: &std_name_fmt "$TEST_FLAVOR $PODBIN_NAME $DISTRO_NV $PRIV_NAME $TEST_ENVIRON" alias: local_integration_test - only_if: *not_build - skip: *branches_and_tags + # Docs: ./contrib/cirrus/CIModes.md + only_if: *not_tag_branch_build_docs depends_on: - unit_test matrix: *platform_axis @@ -492,8 +520,8 @@ remote_integration_test_task: container_integration_test_task: name: *std_name_fmt alias: container_integration_test - only_if: *not_build - skip: *branches_and_tags + # Docs: ./contrib/cirrus/CIModes.md + only_if: *not_tag_branch_build_docs depends_on: - unit_test matrix: &fedora_vm_axis @@ -522,8 +550,8 @@ container_integration_test_task: rootless_integration_test_task: name: *std_name_fmt alias: rootless_integration_test - only_if: *not_build - skip: *branches_and_tags + # Docs: ./contrib/cirrus/CIModes.md + only_if: *not_tag_branch_build_docs depends_on: - unit_test matrix: *platform_axis @@ -545,8 +573,13 @@ rootless_integration_test_task: local_system_test_task: &local_system_test_task name: *std_name_fmt alias: local_system_test - skip: *tags - only_if: *not_build + # Don't create task for tags, or if using [CI:DOCS], [CI:BUILD], multiarch + # Docs: ./contrib/cirrus/CIModes.md + only_if: ¬_tag_build_docs_multiarch >- + $CIRRUS_TAG == '' && + $CIRRUS_CHANGE_TITLE !=~ '.*CI:DOCS.*' && + $CIRRUS_CHANGE_TITLE !=~ '.*CI:BUILD.*' && + $CIRRUS_CRON != 'multiarch' depends_on: - local_integration_test matrix: *platform_axis @@ -590,11 +623,29 @@ rootless_remote_system_test_task: PRIV_NAME: rootless +rootless_system_test_task: + name: *std_name_fmt + alias: rootless_system_test + # Docs: ./contrib/cirrus/CIModes.md + only_if: *not_tag_build_docs_multiarch + depends_on: + - rootless_integration_test + matrix: *platform_axis + gce_instance: *standardvm + env: + TEST_FLAVOR: sys + PRIV_NAME: rootless + clone_script: *get_gosrc + setup_script: *setup + main_script: *main + always: *logs_artifacts + + buildah_bud_test_task: name: *std_name_fmt alias: buildah_bud_test - skip: *tags - only_if: *not_build + # Docs: ./contrib/cirrus/CIModes.md + only_if: *not_tag_branch_build_docs depends_on: - local_integration_test env: @@ -618,29 +669,11 @@ buildah_bud_test_task: always: *int_logs_artifacts -rootless_system_test_task: - name: *std_name_fmt - alias: rootless_system_test - skip: *tags - only_if: *not_build - depends_on: - - rootless_integration_test - matrix: *platform_axis - gce_instance: *standardvm - env: - TEST_FLAVOR: sys - PRIV_NAME: rootless - clone_script: *get_gosrc - setup_script: *setup - main_script: *main - always: *logs_artifacts - - rootless_gitlab_test_task: name: *std_name_fmt alias: rootless_gitlab_test - skip: *tags - only_if: *not_build + # Docs: ./contrib/cirrus/CIModes.md + only_if: *not_tag_branch_build_docs # Community-maintained downstream test may fail unexpectedly. # Ref. repository: https://gitlab.com/gitlab-org/gitlab-runner # If necessary, uncomment the next line and file issue(s) with details. @@ -666,8 +699,8 @@ rootless_gitlab_test_task: upgrade_test_task: name: "Upgrade test: from $PODMAN_UPGRADE_FROM" alias: upgrade_test - skip: *tags - only_if: *not_build + # Docs: ./contrib/cirrus/CIModes.md + only_if: *not_tag_branch_build_docs depends_on: - local_system_test matrix: @@ -695,9 +728,10 @@ image_build_task: &image-build alias: image_build # Some of these container images take > 1h to build, limit # this task to a specific Cirrus-Cron entry with this name. + # Docs: ./contrib/cirrus/CIModes.md only_if: $CIRRUS_CRON == 'multiarch' depends_on: - - build + - ext_svc_check timeout_in: 120m # emulation is sssllllooooowwww gce_instance: <<: *standardvm @@ -728,6 +762,7 @@ test_image_build_task: <<: *image-build alias: test_image_build # Allow this to run inside a PR w/ [CI:BUILD] only. + # Docs: ./contrib/cirrus/CIModes.md only_if: $CIRRUS_PR != '' && $CIRRUS_CHANGE_TITLE =~ '.*CI:BUILD.*' # This takes a LONG time, only run when requested. N/B: Any task # made to depend on this one will block FOREVER unless triggered. @@ -793,9 +828,9 @@ success_task: - remote_system_test - rootless_system_test - rootless_remote_system_test + - buildah_bud_test - rootless_gitlab_test - upgrade_test - - buildah_bud_test - image_build - meta container: *smallcontainer @@ -809,7 +844,8 @@ success_task: artifacts_task: name: "Artifacts" alias: artifacts - only_if: *not_docs + # Docs: ./contrib/cirrus/CIModes.md + only_if: *not_docs_multiarch depends_on: - success # This task is a secondary/convenience for downstream consumers, don't @@ -862,7 +898,9 @@ artifacts_task: release_task: name: "Verify Release" alias: release - only_if: *tags + # This should _only_ run for new tags + # Docs: ./contrib/cirrus/CIModes.md + only_if: $CIRRUS_TAG != '' depends_on: - success gce_instance: *standardvm @@ -884,6 +922,7 @@ release_test_task: name: "Optional Release Test" alias: release_test # Release-PRs always include "release" or "Bump" in the title + # Docs: ./contrib/cirrus/CIModes.md only_if: $CIRRUS_CHANGE_TITLE =~ '.*((release)|(bump)).*' # Allow running manually only as part of release-related builds # see RELEASE_PROCESS.md 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 @@ -3,6 +3,7 @@ # Podman: A tool for managing OCI containers and pods Podman (the POD MANager) is a tool for managing containers and images, volumes mounted into those containers, and pods made from groups of containers. +Podman runs containers on Linux, but can also be used on Mac and Windows systems using a Podman-managed virtual machine. Podman is based on libpod, a library for container lifecycle management that is also contained in this repository. The libpod library provides APIs for managing containers, pods, container images, and volumes. * [Latest Version: 4.1.0](https://github.com/containers/podman/releases/tag/v4.1.0) @@ -24,16 +25,15 @@ At a high level, the scope of Podman and libpod is the following: * Support for pods, groups of containers that share resources and are managed together. * Support for running containers and pods without root or other elevated privileges. * Resource isolation of containers and pods. -* Support for a Docker-compatible CLI interface. +* Support for a Docker-compatible CLI interface, which can both run containers locally and on remote systems. * No manager daemon, for improved security and lower resource utilization at idle. * Support for a REST API providing both a Docker-compatible interface and an improved interface exposing advanced Podman functionality. - -Podman presently only supports running containers on Linux. However, we are building a remote client which can run on Windows and macOS and manage Podman containers on a Linux system via the REST API using SSH tunneling. +* Support for running on Windows and Mac via virtual machines run by `podman machine`. ## Roadmap -1. Further improvements to the REST API, with a focus on bugfixes and implementing missing functionality -1. Improvements on rootless containers, with a focus on improving the user experience and exposing presently-unavailable features when possible +1. A fully-featured GUI frontend for `podman machine` +1. Further improvements to `podman generate kube` and `podman play kube` 1. Improvements to Pods, including the addition of pod-level resource limits ## Communications @@ -111,7 +111,7 @@ includes tables showing Docker commands and their Podman equivalent commands. Tutorials on using Podman. **[Remote Client](https://github.com/containers/podman/blob/main/docs/tutorials/remote_client.md)** -A brief how-to on using the Podman remote-client. +A brief how-to on using the Podman remote client. **[Basic Setup and Use of Podman in a Rootless environment](https://github.com/containers/podman/blob/main/docs/tutorials/rootless_tutorial.md)** A tutorial showing the setup and configuration necessary to run Rootless Podman. diff --git a/cmd/podman/common/completion.go b/cmd/podman/common/completion.go index 3720e9608..07dcc4e6a 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,90 @@ func getNetworks(cmd *cobra.Command, toComplete string, cType completeType) ([]s return suggestions, cobra.ShellCompDirectiveNoFileComp } +func fdIsNotDir(f *os.File) bool { + stat, err := f.Stat() + if err != nil { + cobra.CompErrorln(err.Error()) + return true + } + return !stat.IsDir() +} + +func getPathCompletion(root string, toComplete string) ([]string, cobra.ShellCompDirective) { + 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, cobra.ShellCompDirectiveDefault + } + var base string + f, err := os.Open(userpath) + // when error or file is not dir get the parent path to stat + if err != nil || fdIsNotDir(f) { + // 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, cobra.ShellCompDirectiveDefault + } + } + + if fdIsNotDir(f) { + // nothing to complete since it is no dir + return nil, cobra.ShellCompDirectiveDefault + } + + entries, err := f.ReadDir(-1) + if err != nil { + cobra.CompErrorln(err.Error()) + return nil, cobra.ShellCompDirectiveDefault + } + if len(entries) == 0 { + // path is empty dir, just add the trailing slash and no space + if !strings.HasSuffix(toComplete, "/") { + toComplete += "/" + } + return []string{toComplete}, cobra.ShellCompDirectiveDefault | cobra.ShellCompDirectiveNoSpace + } + completions := make([]string, 0, len(entries)) + count := 0 + for _, e := range entries { + if strings.HasPrefix(e.Name(), base) { + suf := "" + // When the entry is an directory we add the "/" as suffix and do not want to add space + // to match normal shell completion behavior. + // Just inc counter again to fake more than one entry in this case and thus get no space. + if e.IsDir() { + suf = "/" + count++ + } + completions = append(completions, simplePathJoinUnix(toComplete, e.Name()+suf)) + count++ + } + } + directive := cobra.ShellCompDirectiveDefault + if count > 1 { + // when we have more than one match we do not want to add a space after the completion + directive |= cobra.ShellCompDirectiveNoSpace + } + return completions, directive +} + +// 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 +609,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) } // AutocompleteRegistries - Autocomplete registries. @@ -572,14 +682,40 @@ 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 + } + comps, directive := getPathCompletion(resp[0].Path, toComplete[i+1:]) + return prefixSlice(toComplete[:i+1], comps), directive + } + // 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 886c10cb5..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() diff --git a/cmd/podman/common/create_opts.go b/cmd/podman/common/create_opts.go index c40d1ea51..ad535ff59 100644 --- a/cmd/podman/common/create_opts.go +++ b/cmd/podman/common/create_opts.go @@ -1,472 +1,9 @@ package common import ( - "fmt" - "net" - "os" - "path/filepath" - "strconv" - "strings" - - "github.com/containers/common/libnetwork/types" - "github.com/containers/common/pkg/cgroups" - "github.com/containers/common/pkg/config" "github.com/containers/podman/v4/cmd/podman/registry" - "github.com/containers/podman/v4/libpod/define" - "github.com/containers/podman/v4/pkg/api/handlers" - "github.com/containers/podman/v4/pkg/domain/entities" - "github.com/containers/podman/v4/pkg/rootless" - "github.com/containers/podman/v4/pkg/specgen" - "github.com/docker/docker/api/types/mount" - "github.com/pkg/errors" ) -func stringMaptoArray(m map[string]string) []string { - a := make([]string, 0, len(m)) - for k, v := range m { - a = append(a, fmt.Sprintf("%s=%s", k, v)) - } - return a -} - -// ContainerCreateToContainerCLIOpts converts a compat input struct to cliopts so it can be converted to -// a specgen spec. -func ContainerCreateToContainerCLIOpts(cc handlers.CreateContainerConfig, rtc *config.Config) (*entities.ContainerCreateOptions, []string, error) { - var ( - capAdd []string - cappDrop []string - entrypoint *string - init bool - specPorts []types.PortMapping - ) - - if cc.HostConfig.Init != nil { - init = *cc.HostConfig.Init - } - - // Iterate devices and convert back to string - devices := make([]string, 0, len(cc.HostConfig.Devices)) - for _, dev := range cc.HostConfig.Devices { - devices = append(devices, fmt.Sprintf("%s:%s:%s", dev.PathOnHost, dev.PathInContainer, dev.CgroupPermissions)) - } - - // iterate blkreaddevicebps - readBps := make([]string, 0, len(cc.HostConfig.BlkioDeviceReadBps)) - for _, dev := range cc.HostConfig.BlkioDeviceReadBps { - readBps = append(readBps, dev.String()) - } - - // iterate blkreaddeviceiops - readIops := make([]string, 0, len(cc.HostConfig.BlkioDeviceReadIOps)) - for _, dev := range cc.HostConfig.BlkioDeviceReadIOps { - readIops = append(readIops, dev.String()) - } - - // iterate blkwritedevicebps - writeBps := make([]string, 0, len(cc.HostConfig.BlkioDeviceWriteBps)) - for _, dev := range cc.HostConfig.BlkioDeviceWriteBps { - writeBps = append(writeBps, dev.String()) - } - - // iterate blkwritedeviceiops - writeIops := make([]string, 0, len(cc.HostConfig.BlkioDeviceWriteIOps)) - for _, dev := range cc.HostConfig.BlkioDeviceWriteIOps { - writeIops = append(writeIops, dev.String()) - } - - // entrypoint - // can be a string or slice. if it is a slice, we need to - // marshall it to json; otherwise it should just be the string - // value - if len(cc.Config.Entrypoint) > 0 { - entrypoint = &cc.Config.Entrypoint[0] - if len(cc.Config.Entrypoint) > 1 { - b, err := json.Marshal(cc.Config.Entrypoint) - if err != nil { - return nil, nil, err - } - var jsonString = string(b) - entrypoint = &jsonString - } - } - - // expose ports - expose := make([]string, 0, len(cc.Config.ExposedPorts)) - for p := range cc.Config.ExposedPorts { - expose = append(expose, fmt.Sprintf("%s/%s", p.Port(), p.Proto())) - } - - // mounts type=tmpfs/bind,source=...,target=...=,opt=val - volSources := make(map[string]bool) - volDestinations := make(map[string]bool) - mounts := make([]string, 0, len(cc.HostConfig.Mounts)) - var builder strings.Builder - for _, m := range cc.HostConfig.Mounts { - addField(&builder, "type", string(m.Type)) - addField(&builder, "source", m.Source) - addField(&builder, "target", m.Target) - - // Store source/dest so we don't add duplicates if a volume is - // also mentioned in cc.Volumes. - // Which Docker Compose v2.0 does, for unclear reasons... - volSources[m.Source] = true - volDestinations[m.Target] = true - - if m.ReadOnly { - addField(&builder, "ro", "true") - } - addField(&builder, "consistency", string(m.Consistency)) - // Map any specialized mount options that intersect between *Options and cli options - switch m.Type { - case mount.TypeBind: - if m.BindOptions != nil { - addField(&builder, "bind-propagation", string(m.BindOptions.Propagation)) - addField(&builder, "bind-nonrecursive", strconv.FormatBool(m.BindOptions.NonRecursive)) - } - case mount.TypeTmpfs: - if m.TmpfsOptions != nil { - addField(&builder, "tmpfs-size", strconv.FormatInt(m.TmpfsOptions.SizeBytes, 10)) - addField(&builder, "tmpfs-mode", strconv.FormatUint(uint64(m.TmpfsOptions.Mode), 8)) - } - case mount.TypeVolume: - // All current VolumeOpts are handled above - // See vendor/github.com/containers/common/pkg/parse/parse.go:ValidateVolumeOpts() - } - mounts = append(mounts, builder.String()) - builder.Reset() - } - - // dns - dns := make([]net.IP, 0, len(cc.HostConfig.DNS)) - for _, d := range cc.HostConfig.DNS { - dns = append(dns, net.ParseIP(d)) - } - - // publish - for port, pbs := range cc.HostConfig.PortBindings { - for _, pb := range pbs { - var hostport int - var err error - if pb.HostPort != "" { - hostport, err = strconv.Atoi(pb.HostPort) - } - if err != nil { - return nil, nil, err - } - tmpPort := types.PortMapping{ - HostIP: pb.HostIP, - ContainerPort: uint16(port.Int()), - HostPort: uint16(hostport), - Range: 0, - Protocol: port.Proto(), - } - specPorts = append(specPorts, tmpPort) - } - } - - // special case for NetworkMode, the podman default is slirp4netns for - // rootless but for better docker compat we want bridge. - netmode := string(cc.HostConfig.NetworkMode) - if netmode == "" || netmode == "default" { - netmode = "bridge" - } - nsmode, networks, netOpts, err := specgen.ParseNetworkFlag([]string{netmode}) - if err != nil { - return nil, nil, err - } - - // network - // Note: we cannot emulate compat exactly here. we only allow specifics of networks to be - // defined when there is only one network. - netInfo := entities.NetOptions{ - AddHosts: cc.HostConfig.ExtraHosts, - DNSOptions: cc.HostConfig.DNSOptions, - DNSSearch: cc.HostConfig.DNSSearch, - DNSServers: dns, - Network: nsmode, - PublishPorts: specPorts, - NetworkOptions: netOpts, - NoHosts: rtc.Containers.NoHosts, - } - - // network names - switch { - case len(cc.NetworkingConfig.EndpointsConfig) > 0: - endpointsConfig := cc.NetworkingConfig.EndpointsConfig - networks := make(map[string]types.PerNetworkOptions, len(endpointsConfig)) - for netName, endpoint := range endpointsConfig { - netOpts := types.PerNetworkOptions{} - if endpoint != nil { - netOpts.Aliases = endpoint.Aliases - - // if IP address is provided - if len(endpoint.IPAddress) > 0 { - staticIP := net.ParseIP(endpoint.IPAddress) - if staticIP == nil { - return nil, nil, errors.Errorf("failed to parse the ip address %q", endpoint.IPAddress) - } - netOpts.StaticIPs = append(netOpts.StaticIPs, staticIP) - } - - if endpoint.IPAMConfig != nil { - // if IPAMConfig.IPv4Address is provided - if len(endpoint.IPAMConfig.IPv4Address) > 0 { - staticIP := net.ParseIP(endpoint.IPAMConfig.IPv4Address) - if staticIP == nil { - return nil, nil, errors.Errorf("failed to parse the ipv4 address %q", endpoint.IPAMConfig.IPv4Address) - } - netOpts.StaticIPs = append(netOpts.StaticIPs, staticIP) - } - // if IPAMConfig.IPv6Address is provided - if len(endpoint.IPAMConfig.IPv6Address) > 0 { - staticIP := net.ParseIP(endpoint.IPAMConfig.IPv6Address) - if staticIP == nil { - return nil, nil, errors.Errorf("failed to parse the ipv6 address %q", endpoint.IPAMConfig.IPv6Address) - } - netOpts.StaticIPs = append(netOpts.StaticIPs, staticIP) - } - } - // If MAC address is provided - if len(endpoint.MacAddress) > 0 { - staticMac, err := net.ParseMAC(endpoint.MacAddress) - if err != nil { - return nil, nil, errors.Errorf("failed to parse the mac address %q", endpoint.MacAddress) - } - netOpts.StaticMAC = types.HardwareAddr(staticMac) - } - } - - networks[netName] = netOpts - } - - netInfo.Networks = networks - case len(cc.HostConfig.NetworkMode) > 0: - netInfo.Networks = networks - } - - parsedTmp := make([]string, 0, len(cc.HostConfig.Tmpfs)) - for path, options := range cc.HostConfig.Tmpfs { - finalString := path - if options != "" { - finalString += ":" + options - } - parsedTmp = append(parsedTmp, finalString) - } - - // Note: several options here are marked as "don't need". this is based - // on speculation by Matt and I. We think that these come into play later - // like with start. We believe this is just a difference in podman/compat - cliOpts := entities.ContainerCreateOptions{ - // Attach: nil, // don't need? - Authfile: "", - CapAdd: append(capAdd, cc.HostConfig.CapAdd...), - CapDrop: append(cappDrop, cc.HostConfig.CapDrop...), - CgroupParent: cc.HostConfig.CgroupParent, - CIDFile: cc.HostConfig.ContainerIDFile, - CPUPeriod: uint64(cc.HostConfig.CPUPeriod), - CPUQuota: cc.HostConfig.CPUQuota, - CPURTPeriod: uint64(cc.HostConfig.CPURealtimePeriod), - CPURTRuntime: cc.HostConfig.CPURealtimeRuntime, - CPUShares: uint64(cc.HostConfig.CPUShares), - // CPUS: 0, // don't need? - CPUSetCPUs: cc.HostConfig.CpusetCpus, - CPUSetMems: cc.HostConfig.CpusetMems, - // Detach: false, // don't need - // DetachKeys: "", // don't need - Devices: devices, - DeviceCgroupRule: nil, - DeviceReadBPs: readBps, - DeviceReadIOPs: readIops, - DeviceWriteBPs: writeBps, - DeviceWriteIOPs: writeIops, - Entrypoint: entrypoint, - Env: cc.Config.Env, - Expose: expose, - GroupAdd: cc.HostConfig.GroupAdd, - Hostname: cc.Config.Hostname, - ImageVolume: "bind", - Init: init, - Interactive: cc.Config.OpenStdin, - IPC: string(cc.HostConfig.IpcMode), - Label: stringMaptoArray(cc.Config.Labels), - LogDriver: cc.HostConfig.LogConfig.Type, - LogOptions: stringMaptoArray(cc.HostConfig.LogConfig.Config), - Name: cc.Name, - OOMScoreAdj: &cc.HostConfig.OomScoreAdj, - Arch: "", - OS: "", - Variant: "", - PID: string(cc.HostConfig.PidMode), - PIDsLimit: cc.HostConfig.PidsLimit, - Privileged: cc.HostConfig.Privileged, - PublishAll: cc.HostConfig.PublishAllPorts, - Quiet: false, - ReadOnly: cc.HostConfig.ReadonlyRootfs, - ReadOnlyTmpFS: true, // podman default - Rm: cc.HostConfig.AutoRemove, - SecurityOpt: cc.HostConfig.SecurityOpt, - StopSignal: cc.Config.StopSignal, - StorageOpts: stringMaptoArray(cc.HostConfig.StorageOpt), - Sysctl: stringMaptoArray(cc.HostConfig.Sysctls), - Systemd: "true", // podman default - TmpFS: parsedTmp, - TTY: cc.Config.Tty, - UnsetEnv: cc.UnsetEnv, - UnsetEnvAll: cc.UnsetEnvAll, - User: cc.Config.User, - UserNS: string(cc.HostConfig.UsernsMode), - UTS: string(cc.HostConfig.UTSMode), - Mount: mounts, - VolumesFrom: cc.HostConfig.VolumesFrom, - Workdir: cc.Config.WorkingDir, - Net: &netInfo, - HealthInterval: define.DefaultHealthCheckInterval, - HealthRetries: define.DefaultHealthCheckRetries, - HealthTimeout: define.DefaultHealthCheckTimeout, - HealthStartPeriod: define.DefaultHealthCheckStartPeriod, - } - if !rootless.IsRootless() { - var ulimits []string - if len(cc.HostConfig.Ulimits) > 0 { - for _, ul := range cc.HostConfig.Ulimits { - ulimits = append(ulimits, ul.String()) - } - cliOpts.Ulimit = ulimits - } - } - if cc.HostConfig.Resources.NanoCPUs > 0 { - if cliOpts.CPUPeriod != 0 || cliOpts.CPUQuota != 0 { - return nil, nil, errors.Errorf("NanoCpus conflicts with CpuPeriod and CpuQuota") - } - cliOpts.CPUPeriod = 100000 - cliOpts.CPUQuota = cc.HostConfig.Resources.NanoCPUs / 10000 - } - - // volumes - for _, vol := range cc.HostConfig.Binds { - cliOpts.Volume = append(cliOpts.Volume, vol) - // Extract the destination so we don't add duplicate mounts in - // the volumes phase. - splitVol := specgen.SplitVolumeString(vol) - switch len(splitVol) { - case 1: - volDestinations[vol] = true - default: - volSources[splitVol[0]] = true - volDestinations[splitVol[1]] = true - } - } - // Anonymous volumes are added differently from other volumes, in their - // own special field, for reasons known only to Docker. Still use the - // format of `-v` so we can just append them in there. - // Unfortunately, these may be duplicates of existing mounts in Binds. - // So... We need to catch that. - // This also handles volumes duplicated between cc.HostConfig.Mounts and - // cc.Volumes, as seen in compose v2.0. - for vol := range cc.Volumes { - if _, ok := volDestinations[filepath.Clean(vol)]; ok { - continue - } - cliOpts.Volume = append(cliOpts.Volume, vol) - } - // Make mount points for compat volumes - for vol := range volSources { - // This might be a named volume. - // Assume it is if it's not an absolute path. - if !filepath.IsAbs(vol) { - continue - } - // If volume already exists, there is nothing to do - if _, err := os.Stat(vol); err == nil { - continue - } - if err := os.MkdirAll(vol, 0755); err != nil { - if !os.IsExist(err) { - return nil, nil, errors.Wrapf(err, "error making volume mountpoint for volume %s", vol) - } - } - } - if len(cc.HostConfig.BlkioWeightDevice) > 0 { - devices := make([]string, 0, len(cc.HostConfig.BlkioWeightDevice)) - for _, d := range cc.HostConfig.BlkioWeightDevice { - devices = append(devices, d.String()) - } - cliOpts.BlkIOWeightDevice = devices - } - if cc.HostConfig.BlkioWeight > 0 { - cliOpts.BlkIOWeight = strconv.Itoa(int(cc.HostConfig.BlkioWeight)) - } - - if cc.HostConfig.Memory > 0 { - cliOpts.Memory = strconv.Itoa(int(cc.HostConfig.Memory)) - } - - if cc.HostConfig.MemoryReservation > 0 { - cliOpts.MemoryReservation = strconv.Itoa(int(cc.HostConfig.MemoryReservation)) - } - - cgroupsv2, err := cgroups.IsCgroup2UnifiedMode() - if err != nil { - return nil, nil, err - } - if cc.HostConfig.MemorySwap > 0 && (!rootless.IsRootless() || (rootless.IsRootless() && cgroupsv2)) { - cliOpts.MemorySwap = strconv.Itoa(int(cc.HostConfig.MemorySwap)) - } - - if cc.Config.StopTimeout != nil { - cliOpts.StopTimeout = uint(*cc.Config.StopTimeout) - } - - if cc.HostConfig.ShmSize > 0 { - cliOpts.ShmSize = strconv.Itoa(int(cc.HostConfig.ShmSize)) - } - - if len(cc.HostConfig.RestartPolicy.Name) > 0 { - policy := cc.HostConfig.RestartPolicy.Name - // only add restart count on failure - if cc.HostConfig.RestartPolicy.IsOnFailure() { - policy += fmt.Sprintf(":%d", cc.HostConfig.RestartPolicy.MaximumRetryCount) - } - cliOpts.Restart = policy - } - - if cc.HostConfig.MemorySwappiness != nil && (!rootless.IsRootless() || rootless.IsRootless() && cgroupsv2 && rtc.Engine.CgroupManager == "systemd") { - cliOpts.MemorySwappiness = *cc.HostConfig.MemorySwappiness - } else { - cliOpts.MemorySwappiness = -1 - } - if cc.HostConfig.OomKillDisable != nil { - cliOpts.OOMKillDisable = *cc.HostConfig.OomKillDisable - } - if cc.Config.Healthcheck != nil { - finCmd := "" - for _, str := range cc.Config.Healthcheck.Test { - finCmd = finCmd + str + " " - } - if len(finCmd) > 1 { - finCmd = finCmd[:len(finCmd)-1] - } - cliOpts.HealthCmd = finCmd - if cc.Config.Healthcheck.Interval > 0 { - cliOpts.HealthInterval = cc.Config.Healthcheck.Interval.String() - } - if cc.Config.Healthcheck.Retries > 0 { - cliOpts.HealthRetries = uint(cc.Config.Healthcheck.Retries) - } - if cc.Config.Healthcheck.StartPeriod > 0 { - cliOpts.HealthStartPeriod = cc.Config.Healthcheck.StartPeriod.String() - } - if cc.Config.Healthcheck.Timeout > 0 { - cliOpts.HealthTimeout = cc.Config.Healthcheck.Timeout.String() - } - } - - // specgen assumes the image name is arg[0] - cmd := []string{cc.Config.Image} - cmd = append(cmd, cc.Config.Cmd...) - return &cliOpts, cmd, nil -} - func ulimits() []string { if !registry.IsRemote() { return containerConfig.Ulimits() @@ -536,17 +73,3 @@ func LogDriver() string { } return "" } - -// addField is a helper function to populate mount options -func addField(b *strings.Builder, name string, value string) { - if value == "" { - return - } - - if b.Len() > 0 { - b.WriteRune(',') - } - b.WriteString(name) - b.WriteRune('=') - b.WriteString(value) -} 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/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/contrib/cirrus/CIModes.md b/contrib/cirrus/CIModes.md new file mode 100644 index 000000000..8b1e33bb1 --- /dev/null +++ b/contrib/cirrus/CIModes.md @@ -0,0 +1,129 @@ +The following is a list (incomplete) of the primary contexts and runtime +"modes" supported by podman CI. Note that there may be additional checks +done regarding "skipping work" in the `runner.sh` script. This document +only details the controls at the `.cirrus.yml` level. + +## Visualization + +The relationship between tasks can be incredibly hard to understand by +staring at the YAML. +[A tool exists](https://github.com/containers/automation/tree/main/cirrus-task-map) +for producing a graph (flow-chart) of the `.cirrus.yml` file. A (possibly +outdated) example of it's output can be seen below: + +![cirrus-task-map output](https://github.com/containers/podman/wiki/cirrus-map.svg) + +## Implementation notes + ++ The `skip` conditional should never be used for tasks. + While it's arguably easier to read that `only_if`, it leads to a cluttered + status output that's harder to page through when reviewing PRs. As opposed + to `only_if` which will bypass creation of the task (at runtime) completely. + Also, by sticking to one conditional style, it's easer to re-use the YAML + statements across multiple tasks. + ++ The only variables which can be used as part of conditions are defined by + Cirrus-CI. + [The list is documented](https://cirrus-ci.org/guide/writing-tasks/#environment-variables). Reference to any variables defined in YAML will **not** behave how + you expect, don't use them! + +* Somme Cirrus-CI defined variables contain non-empty values outside their + obvious context. For example, when running for a PR a task will have + `$CIRRUS_BRANCH` set to `pull/<number>`. + +* Conditions which use positive or negative regular-expressions have several + "flags" set: "Multi-line" and "Case-insensitive". + +## Testing + +Executing most of the modes can be mocked by forcing values for (otherwise) +Cirrus-CI defined variables. For example `$CIRRUS_TAG`. As of the publishing +of this document, it's not possible to override the behavior of `$CIRRUS_PR`. + +## Cirrus Task contexts and runtime modes + +### Intended general PR Tasks (*italic*: matrix) ++ ext_svc_check ++ automation ++ *build* ++ validate ++ bindings ++ swagger ++ consistency ++ *alt_build* ++ osx_alt_build ++ docker-py_test ++ *unit_test* ++ apiv2_test ++ *compose_test* ++ *local_integration_test* ++ *remote_integration_test* ++ *container_integration_test* ++ *rootless_integration_test* ++ *local_system_test* ++ *remote_system_test* ++ *rootless_remote_system_test* ++ *buildah_bud_test* ++ *rootless_system_test* ++ rootless_gitlab_test ++ *upgrade_test* ++ meta ++ success ++ artifacts + +### Intended for PR w/ "release" or "bump" in title: ++ (All the general PR tasks above) ++ release_test + +### Intended `[CI:DOCS]` PR Tasks: ++ ext_svc_check ++ automation ++ *build* ++ validate ++ swagger ++ consistency ++ meta ++ success + +### Intend `[CI:BUILD]` PR Tasks: ++ ext_svc_check ++ automation ++ *build* ++ validate ++ consistency ++ *alt_build* ++ osx_alt_build ++ test_image_build ++ meta ++ success ++ artifacts + +### Intended Branch tasks (and Cirrus-cron jobs, except "multiarch"): ++ ext_svc_check ++ *build* ++ swagger ++ *alt_build* ++ osx_alt_build ++ *local_system_test* ++ *remote_system_test* ++ *rootless_remote_system_test* ++ *rootless_system_test* ++ meta ++ success ++ artifacts + +### Intended for "multiarch" Cirrus-Cron (always a branch): ++ ext_svc_check ++ image_build ++ meta ++ success + +### Intended for new Tag tasks: ++ *build* ++ swagger ++ *alt_build* ++ osx_alt_build ++ meta ++ success ++ artifacts ++ release 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..b4ef81d84 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. @@ -21,18 +21,18 @@ The container images are: and stable version of podman. For the most recent `<version>` tags (`vX`, `vX.Y`, and `vX.Y.Z`) the image contents will be updated daily to incorporate (especially) security upgrades. For build details, please [see the - configuration file](stable/Dockerfile). + configuration file](stable/Containerfile). * `quay.io/containers/podman:latest` and `quay.io/podman/stable:latest` - - Built daily using the same Dockerfile as above. The Podman version + Built daily using the same Containerfile as above. The Podman version will remain the "latest" available in Fedora, however the other image contents may vary compared to the version-tagged images. * `quay.io/podman/testing:latest` - This image is built daily, using the latest version of Podman that was in the Fedora `updates-testing` repository. - The image is Built with [the testing Dockerfile](testing/Dockerfile). + The image is Built with [the testing Containerfile](testing/Containerfile). * `quay.io/podman/upstream:latest` - This image is built daily using the latest code found in this GitHub repository. Due to the image changing frequently, it's not guaranteed to be stable or even executable. The image is built with - [the upstream Dockerfile](upstream/Dockerfile). + [the upstream Containerfile](upstream/Containerfile). ## Sample Usage diff --git a/contrib/podmanimage/stable/Containerfile b/contrib/podmanimage/stable/Containerfile new file mode 100644 index 000000000..9121c5cde --- /dev/null +++ b/contrib/podmanimage/stable/Containerfile @@ -0,0 +1,55 @@ +# 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/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 -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/testing/Containerfile b/contrib/podmanimage/testing/Containerfile new file mode 100644 index 000000000..16314a633 --- /dev/null +++ b/contrib/podmanimage/testing/Containerfile @@ -0,0 +1,60 @@ +# 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/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 -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..c3a07a8d6 --- /dev/null +++ b/contrib/podmanimage/upstream/Containerfile @@ -0,0 +1,61 @@ +# 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/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 -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 86a7090de..4d6a74a1f 100644 --- a/docs/source/markdown/podman-build.1.md +++ b/docs/source/markdown/podman-build.1.md @@ -99,7 +99,7 @@ different stages in COPY instruction. Valid values could be: -* Local directory – e.g. --build-context project2=../path/to/project2/src +* Local directory – e.g. --build-context project2=../path/to/project2/src (This option is not available with the remote Podman client. On Podman machine setup (i.e macOS and Winows) path must exists on the machine VM) * HTTP URL to a tarball – e.g. --build-context src=https://example.org/releases/src.tar * Container image – specified with a container-image:// prefix, e.g. --build-context alpine=container-image://alpine:3.15, (also accepts docker://, docker-image://) @@ -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 8a1bfcee1..d5e96168d 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* @@ -1013,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. @@ -1261,9 +1261,9 @@ Podman allocates unique ranges of UIDs and GIDs from the `containers` subordinat **host**: run in the user namespace of the caller. The processes running in the container will have the same privileges on the host as any other process launched by the calling user (default). -**keep-id**: creates a user namespace where the current rootless user's UID:GID are mapped to the same values in the container. This option is ignored for containers created by the root user. +**keep-id**: creates a user namespace where the current rootless user's UID:GID are mapped to the same values in the container. This option is not allowed for containers created by the root user. -**nomap**: creates a user namespace where the current rootless user's UID:GID are not mapped into the container. This option is ignored for containers created by the root user. +**nomap**: creates a user namespace where the current rootless user's UID:GID are not mapped into the container. This option is not allowed for containers created by the root user. **ns:**_namespace_: run the container in the given existing user namespace. 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-play-kube.1.md b/docs/source/markdown/podman-play-kube.1.md index 08bb2a5bc..1c7fc99a2 100644 --- a/docs/source/markdown/podman-play-kube.1.md +++ b/docs/source/markdown/podman-play-kube.1.md @@ -276,9 +276,9 @@ Podman allocates unique ranges of UIDs and GIDs from the `containers` subordinat **host**: create a new namespace for the container. -**keep-id**: creates a user namespace where the current rootless user's UID:GID are mapped to the same values in the container. This option is ignored for containers created by the root user. +**keep-id**: creates a user namespace where the current rootless user's UID:GID are mapped to the same values in the container. This option is not allowed for containers created by the root user. -**nomap**: creates a user namespace where the current rootless user's UID:GID are not mapped into the container. This option is ignored for containers created by the root user. +**nomap**: creates a user namespace where the current rootless user's UID:GID are not mapped into the container. This option is not allowed for containers created by the root user. **ns:**_namespace_: run the pod in the given existing user namespace. diff --git a/docs/source/markdown/podman-pod-create.1.md b/docs/source/markdown/podman-pod-create.1.md index 1a98528bb..d5b8232cf 100644 --- a/docs/source/markdown/podman-pod-create.1.md +++ b/docs/source/markdown/podman-pod-create.1.md @@ -360,9 +360,9 @@ Valid _mode_ values are: - *host*: run in the user namespace of the caller. The processes running in the container will have the same privileges on the host as any other process launched by the calling user (default). - - *keep-id*: creates a user namespace where the current rootless user's UID:GID are mapped to the same values in the container. This option is ignored for containers created by the root user. + - *keep-id*: creates a user namespace where the current rootless user's UID:GID are mapped to the same values in the container. This option is not allowed for containers created by the root user. - - *nomap*: creates a user namespace where the current rootless user's UID:GID are not mapped into the container. This option is ignored for containers created by the root user. + - *nomap*: creates a user namespace where the current rootless user's UID:GID are not mapped into the container. This option is not allowed for containers created by the root user. #### **--volume**, **-v**[=*[[SOURCE-VOLUME|HOST-DIR:]CONTAINER-DIR[:OPTIONS]]*] diff --git a/docs/source/markdown/podman-run.1.md b/docs/source/markdown/podman-run.1.md index b64aaf873..6acbec7d5 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 @@ -1058,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. @@ -1329,9 +1329,9 @@ The rootless option `--userns=keep-id` uses all the subuids and subgids of the u **host**: run in the user namespace of the caller. The processes running in the container will have the same privileges on the host as any other process launched by the calling user (default). -**keep-id**: creates a user namespace where the current rootless user's UID:GID are mapped to the same values in the container. This option is ignored for containers created by the root user. +**keep-id**: creates a user namespace where the current rootless user's UID:GID are mapped to the same values in the container. This option is not allowed for containers created by the root user. -**nomap**: creates a user namespace where the current rootless user's UID:GID are not mapped into the container. This option is ignored for containers created by the root user. +**nomap**: creates a user namespace where the current rootless user's UID:GID are not mapped into the container. This option is not allowed for containers created by the root user. **ns:**_namespace_: run the container in the given existing user namespace. diff --git a/docs/tutorials/basic_networking.md b/docs/tutorials/basic_networking.md index 396994596..b6f53175b 100644 --- a/docs/tutorials/basic_networking.md +++ b/docs/tutorials/basic_networking.md @@ -93,6 +93,22 @@ When rootless containers are run, network operations will be executed inside an extra network namespace. To join this namespace, use `podman unshare --rootless-netns`. +#### Default Network + +The default network `podman` with netavark is memory-only. It does not support dns resolution because of backwards compatibility with Docker. To change settings, export the in-memory network and change the file. + +For the default rootful network use +``` +podman network inspect podman | jq .[] > /etc/containers/networks/podman.json +``` + +And for the rootless network use + +``` +podman network inspect podman | jq .[] > ~/.local/share/containers/storage/networks/podman.json +``` + + #### Example By default, rootful containers use the netavark for its default network if @@ -9,10 +9,10 @@ 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 + github.com/containers/common v0.48.1-0.20220528105338-54c8092c69a1 github.com/containers/conmon v2.0.20+incompatible github.com/containers/image/v5 v5.21.2-0.20220520105616-e594853d6471 github.com/containers/ocicrypt v1.1.4-0.20220428134531-566b808bdf6f @@ -24,7 +24,7 @@ require ( github.com/davecgh/go-spew v1.1.1 github.com/digitalocean/go-qemu v0.0.0-20210326154740-ac9e0b687001 github.com/docker/distribution v2.8.1+incompatible - github.com/docker/docker v20.10.16+incompatible + github.com/docker/docker v20.10.17+incompatible github.com/docker/go-connections v0.4.1-0.20210727194412-58542c764a11 github.com/docker/go-plugins-helpers v0.0.0-20211224144127-6eecb7beb651 github.com/docker/go-units v0.4.0 @@ -47,7 +47,7 @@ require ( github.com/onsi/gomega v1.19.0 github.com/opencontainers/go-digest v1.0.0 github.com/opencontainers/image-spec v1.0.3-0.20220114050600-8b9d41f48198 - github.com/opencontainers/runc v1.1.2 + github.com/opencontainers/runc v1.1.3 github.com/opencontainers/runtime-spec v1.0.3-0.20211214071223-8958f93039ab github.com/opencontainers/runtime-tools v0.9.1-0.20220110225228-7e2d60f1e41f github.com/opencontainers/selinux v1.10.1 @@ -57,7 +57,7 @@ require ( github.com/sirupsen/logrus v1.8.1 github.com/spf13/cobra v1.4.0 github.com/spf13/pflag v1.0.5 - github.com/stretchr/testify v1.7.1 + github.com/stretchr/testify v1.7.2 github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635 github.com/uber/jaeger-client-go v2.30.0+incompatible github.com/ulikunitz/xz v0.5.10 @@ -72,5 +72,4 @@ require ( google.golang.org/protobuf v1.28.0 gopkg.in/inf.v0 v0.9.1 gopkg.in/yaml.v2 v2.4.0 - gopkg.in/yaml.v3 v3.0.1 // indirect ) @@ -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= @@ -338,8 +339,8 @@ github.com/containernetworking/plugins v1.1.1/go.mod h1:Sr5TH/eBsGLXK/h71HeLfX19 github.com/containers/buildah v1.26.1-0.20220524184833-5500333c2e06 h1:Tx1IfKch/SnsCk1YrdyR4B2AcS1TKLYxbSMXzmQXafU= github.com/containers/buildah v1.26.1-0.20220524184833-5500333c2e06/go.mod h1:oB0PwsW+rhePNsBimCnEz4YMLx8QxZBjHi/DPnXhUCg= github.com/containers/common v0.48.1-0.20220519181648-280c6f69fa82/go.mod h1:Ru/JjL1CTHzlxghVMhchzcFUwHLvlIeR5/SUMw8VUOI= -github.com/containers/common v0.48.1-0.20220523155016-2fd37da97824 h1:5gMIUUpIK9DvHrrlj1Tik8GfCh5DEuVqm0JnYHWYUDw= -github.com/containers/common v0.48.1-0.20220523155016-2fd37da97824/go.mod h1:Ru/JjL1CTHzlxghVMhchzcFUwHLvlIeR5/SUMw8VUOI= +github.com/containers/common v0.48.1-0.20220528105338-54c8092c69a1 h1:oq9ol4U/HEJfDYCp9aKBFDBaE16Y1RZN0GJ4eIkrJoo= +github.com/containers/common v0.48.1-0.20220528105338-54c8092c69a1/go.mod h1:Ru/JjL1CTHzlxghVMhchzcFUwHLvlIeR5/SUMw8VUOI= github.com/containers/conmon v2.0.20+incompatible h1:YbCVSFSCqFjjVwHTPINGdMX1F6JXHGTUje2ZYobNrkg= github.com/containers/conmon v2.0.20+incompatible/go.mod h1:hgwZ2mtuDrppv78a/cOBNiCm6O0UMWGx1mu7P00nu5I= github.com/containers/image/v5 v5.21.2-0.20220511203756-fe4fd4ed8be4/go.mod h1:OsX9sFexyGF0FCNAjfcVFv3IwMqDyLyV/WQY/roLPcE= @@ -428,8 +429,9 @@ github.com/docker/docker v1.4.2-0.20190924003213-a8608b5b67c7/go.mod h1:eEKB0N0r github.com/docker/docker v20.10.3-0.20220208084023-a5c757555091+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker v20.10.12+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker v20.10.15+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= -github.com/docker/docker v20.10.16+incompatible h1:2Db6ZR/+FUR3hqPMwnogOPHFn405crbpxvWzKovETOQ= github.com/docker/docker v20.10.16+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker v20.10.17+incompatible h1:JYCuMrWaVNophQTOrMMoSwudOVEfcegoZZrleKc1xwE= +github.com/docker/docker v20.10.17+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker-credential-helpers v0.6.3/go.mod h1:WRaJzqw3CTB9bk10avuGsjVBZsD05qeibJ1/TYlvc0Y= github.com/docker/docker-credential-helpers v0.6.4 h1:axCks+yV+2MR3/kZhAmy07yC56WZ2Pwu/fKWtKuZB0o= github.com/docker/docker-credential-helpers v0.6.4/go.mod h1:ofX3UI0Gz1TteYBjtgs07O36Pyasyp66D2uKT7H8W1c= @@ -1052,8 +1054,9 @@ github.com/opencontainers/runc v1.0.2/go.mod h1:aTaHFFwQXuA71CiyxOdFFIorAoemI04s github.com/opencontainers/runc v1.0.3/go.mod h1:aTaHFFwQXuA71CiyxOdFFIorAoemI04suvGRQFzWTD0= github.com/opencontainers/runc v1.1.0/go.mod h1:Tj1hFw6eFWp/o33uxGf5yF2BX5yz2Z6iptFpuvbbKqc= github.com/opencontainers/runc v1.1.1/go.mod h1:Tj1hFw6eFWp/o33uxGf5yF2BX5yz2Z6iptFpuvbbKqc= -github.com/opencontainers/runc v1.1.2 h1:2VSZwLx5k/BfsBxMMipG/LYUnmqOD/BPkIVgQUcTlLw= github.com/opencontainers/runc v1.1.2/go.mod h1:Tj1hFw6eFWp/o33uxGf5yF2BX5yz2Z6iptFpuvbbKqc= +github.com/opencontainers/runc v1.1.3 h1:vIXrkId+0/J2Ymu2m7VjGvbSlAId9XNRPhn2p4b+d8w= +github.com/opencontainers/runc v1.1.3/go.mod h1:1J5XiS+vdZ3wCyZybsuxXZWGrgSr8fFJHLXuG2PsnNg= github.com/opencontainers/runtime-spec v0.1.2-0.20190507144316-5b71a03e2700/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= github.com/opencontainers/runtime-spec v1.0.1/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= github.com/opencontainers/runtime-spec v1.0.2-0.20190207185410-29686dbc5559/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= @@ -1185,8 +1188,9 @@ github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg github.com/sebdah/goldie/v2 v2.5.3 h1:9ES/mNN+HNUbNWpVAlrzuZ7jE+Nrczbj8uFRjM7624Y= github.com/sebdah/goldie/v2 v2.5.3/go.mod h1:oZ9fp0+se1eapSRjfYbsV/0Hqhbuu3bJVvKI/NNtssI= github.com/seccomp/libseccomp-golang v0.9.1/go.mod h1:GbW5+tmTXfcxTToHLXlScSlAvWlF4P2Ca7zGrPiEpWo= -github.com/seccomp/libseccomp-golang v0.9.2-0.20210429002308-3879420cc921 h1:58EBmR2dMNL2n/FnbQewK3D14nXr0V9CObDSvMJLq+Y= github.com/seccomp/libseccomp-golang v0.9.2-0.20210429002308-3879420cc921/go.mod h1:JA8cRccbGaA1s33RQf7Y1+q9gHmZX1yB/z9WDN1C6fg= +github.com/seccomp/libseccomp-golang v0.9.2-0.20220502022130-f33da4d89646 h1:RpforrEYXWkmGwJHIGnLZ3tTWStkjVVstwzNGqxX2Ds= +github.com/seccomp/libseccomp-golang v0.9.2-0.20220502022130-f33da4d89646/go.mod h1:JA8cRccbGaA1s33RQf7Y1+q9gHmZX1yB/z9WDN1C6fg= github.com/securego/gosec/v2 v2.9.1/go.mod h1:oDcDLcatOJxkCGaCaq8lua1jTnYf6Sou4wdiJ1n4iHc= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= @@ -1261,8 +1265,9 @@ github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81P github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.2 h1:4jaiDzPyXQvSd7D0EjG45355tLlV3VOECpq10pLC+8s= +github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/sylabs/sif/v2 v2.7.0 h1:VFzN8alnJ/3n1JA0K9DyUtfSzezWgWrzLDcYGhgBskk= github.com/sylabs/sif/v2 v2.7.0/go.mod h1:TiyBWsgWeh5yBeQFNuQnvROwswqK7YJT8JA1L53bsXQ= 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_config.go b/libpod/container_config.go index 30b84adcf..6558f3c89 100644 --- a/libpod/container_config.go +++ b/libpod/container_config.go @@ -412,6 +412,9 @@ type ContainerMiscConfig struct { InitContainerType string `json:"init_container_type,omitempty"` // PasswdEntry specifies arbitrary data to append to a file. PasswdEntry string `json:"passwd_entry,omitempty"` + // MountAllDevices is an option to indicate whether a privileged container + // will mount all the host's devices + MountAllDevices bool `json:"mountAllDevices"` } // InfraInherit contains the compatible options inheritable from the infra container diff --git a/libpod/container_internal.go b/libpod/container_internal.go index 7494eb3ec..fd451f9ef 100644 --- a/libpod/container_internal.go +++ b/libpod/container_internal.go @@ -17,6 +17,7 @@ import ( "github.com/containers/buildah/pkg/overlay" butil "github.com/containers/buildah/util" "github.com/containers/common/libnetwork/etchosts" + "github.com/containers/common/libnetwork/resolvconf" "github.com/containers/common/pkg/cgroups" "github.com/containers/common/pkg/chown" "github.com/containers/common/pkg/config" @@ -986,7 +987,7 @@ func (c *Container) checkDependenciesRunning() ([]string, error) { } func (c *Container) completeNetworkSetup() error { - var outResolvConf []string + var nameservers []string netDisabled, err := c.NetworkDisabled() if err != nil { return err @@ -1000,11 +1001,14 @@ func (c *Container) completeNetworkSetup() error { if err := c.runtime.setupNetNS(c); err != nil { return err } + if err := c.save(); err != nil { + return err + } state := c.state // collect any dns servers that cni tells us to use (dnsname) for _, status := range c.getNetworkStatus() { for _, server := range status.DNSServerIPs { - outResolvConf = append(outResolvConf, fmt.Sprintf("nameserver %s", server)) + nameservers = append(nameservers, server.String()) } } // check if we have a bindmount for /etc/hosts @@ -1020,24 +1024,12 @@ func (c *Container) completeNetworkSetup() error { } // check if we have a bindmount for resolv.conf - resolvBindMount := state.BindMounts["/etc/resolv.conf"] - if len(outResolvConf) < 1 || resolvBindMount == "" || len(c.config.NetNsCtr) > 0 { + resolvBindMount := state.BindMounts[resolvconf.DefaultResolvConf] + if len(nameservers) < 1 || resolvBindMount == "" || len(c.config.NetNsCtr) > 0 { return nil } - // read the existing resolv.conf - b, err := ioutil.ReadFile(resolvBindMount) - if err != nil { - return err - } - for _, line := range strings.Split(string(b), "\n") { - // only keep things that don't start with nameserver from the old - // resolv.conf file - if !strings.HasPrefix(line, "nameserver") { - outResolvConf = append([]string{line}, outResolvConf...) - } - } // write and return - return ioutil.WriteFile(resolvBindMount, []byte(strings.Join(outResolvConf, "\n")), 0644) + return resolvconf.Add(resolvBindMount, nameservers) } // Initialize a container, creating it in the runtime diff --git a/libpod/container_internal_linux.go b/libpod/container_internal_linux.go index e19d75deb..41c0ac595 100644 --- a/libpod/container_internal_linux.go +++ b/libpod/container_internal_linux.go @@ -9,7 +9,6 @@ import ( "io" "io/ioutil" "math" - "net" "os" "os/user" "path" @@ -29,6 +28,7 @@ import ( "github.com/containers/buildah/pkg/overlay" butil "github.com/containers/buildah/util" "github.com/containers/common/libnetwork/etchosts" + "github.com/containers/common/libnetwork/resolvconf" "github.com/containers/common/libnetwork/types" "github.com/containers/common/pkg/apparmor" "github.com/containers/common/pkg/cgroups" @@ -44,7 +44,6 @@ import ( "github.com/containers/podman/v4/pkg/checkpoint/crutils" "github.com/containers/podman/v4/pkg/criu" "github.com/containers/podman/v4/pkg/lookup" - "github.com/containers/podman/v4/pkg/resolvconf" "github.com/containers/podman/v4/pkg/rootless" "github.com/containers/podman/v4/pkg/util" "github.com/containers/podman/v4/utils" @@ -388,6 +387,37 @@ func lookupHostUser(name string) (*runcuser.ExecUser, error) { return &execUser, nil } +// Internal only function which returns upper and work dir from +// overlay options. +func getOverlayUpperAndWorkDir(options []string) (string, string, error) { + upperDir := "" + workDir := "" + for _, o := range options { + if strings.HasPrefix(o, "upperdir") { + splitOpt := strings.SplitN(o, "=", 2) + if len(splitOpt) > 1 { + upperDir = splitOpt[1] + if upperDir == "" { + return "", "", errors.New("cannot accept empty value for upperdir") + } + } + } + if strings.HasPrefix(o, "workdir") { + splitOpt := strings.SplitN(o, "=", 2) + if len(splitOpt) > 1 { + workDir = splitOpt[1] + if workDir == "" { + return "", "", errors.New("cannot accept empty value for workdir") + } + } + } + } + if (upperDir != "" && workDir == "") || (upperDir == "" && workDir != "") { + return "", "", errors.New("must specify both upperdir and workdir") + } + return upperDir, workDir, nil +} + // Generate spec for a container // Accepts a map of the container's dependencies func (c *Container) generateSpec(ctx context.Context) (*spec.Spec, error) { @@ -407,6 +437,14 @@ func (c *Container) generateSpec(ctx context.Context) (*spec.Spec, error) { //nolint:staticcheck g := generate.NewFromSpec(c.config.Spec) + // If the flag to mount all devices is set for a privileged container, add + // all the devices from the host's machine into the container + if c.config.MountAllDevices { + if err := util.AddPrivilegedDevices(&g); err != nil { + return nil, err + } + } + // If network namespace was requested, add it now if c.config.CreateNetNS { if c.config.PostConfigureNetNS { @@ -460,23 +498,9 @@ func (c *Container) generateSpec(ctx context.Context) (*spec.Spec, error) { for _, o := range namedVol.Options { if o == "O" { overlayFlag = true - } - if overlayFlag && strings.Contains(o, "upperdir") { - splitOpt := strings.SplitN(o, "=", 2) - if len(splitOpt) > 1 { - upperDir = splitOpt[1] - if upperDir == "" { - return nil, errors.New("cannot accept empty value for upperdir") - } - } - } - if overlayFlag && strings.Contains(o, "workdir") { - splitOpt := strings.SplitN(o, "=", 2) - if len(splitOpt) > 1 { - workDir = splitOpt[1] - if workDir == "" { - return nil, errors.New("cannot accept empty value for workdir") - } + upperDir, workDir, err = getOverlayUpperAndWorkDir(namedVol.Options) + if err != nil { + return nil, err } } } @@ -489,10 +513,6 @@ func (c *Container) generateSpec(ctx context.Context) (*spec.Spec, error) { return nil, err } - if (upperDir != "" && workDir == "") || (upperDir == "" && workDir != "") { - return nil, errors.Wrapf(err, "must specify both upperdir and workdir") - } - overlayOpts = &overlay.Options{RootUID: c.RootUID(), RootGID: c.RootGID(), UpperDirOptionFragment: upperDir, @@ -585,11 +605,22 @@ func (c *Container) generateSpec(ctx context.Context) (*spec.Spec, error) { // Add overlay volumes for _, overlayVol := range c.config.OverlayVolumes { + upperDir, workDir, err := getOverlayUpperAndWorkDir(overlayVol.Options) + if err != nil { + return nil, err + } contentDir, err := overlay.TempDir(c.config.StaticDir, c.RootUID(), c.RootGID()) if err != nil { return nil, err } - overlayMount, err := overlay.Mount(contentDir, overlayVol.Source, overlayVol.Dest, c.RootUID(), c.RootGID(), c.runtime.store.GraphOptions()) + overlayOpts := &overlay.Options{RootUID: c.RootUID(), + RootGID: c.RootGID(), + UpperDirOptionFragment: upperDir, + WorkDirOptionFragment: workDir, + GraphOpts: c.runtime.store.GraphOptions(), + } + + overlayMount, err := overlay.MountWithOptions(contentDir, overlayVol.Source, overlayVol.Dest, overlayOpts) if err != nil { return nil, errors.Wrapf(err, "mounting overlay failed %q", overlayVol.Source) } @@ -2284,49 +2315,10 @@ rootless=%d // generateResolvConf generates a containers resolv.conf func (c *Container) generateResolvConf() error { var ( - nameservers []string networkNameServers []string networkSearchDomains []string ) - hostns := true - resolvConf := "/etc/resolv.conf" - for _, namespace := range c.config.Spec.Linux.Namespaces { - if namespace.Type == spec.NetworkNamespace { - hostns = false - if namespace.Path != "" && !strings.HasPrefix(namespace.Path, "/proc/") { - definedPath := filepath.Join("/etc/netns", filepath.Base(namespace.Path), "resolv.conf") - _, err := os.Stat(definedPath) - if err == nil { - resolvConf = definedPath - } else if !os.IsNotExist(err) { - return err - } - } - break - } - } - - contents, err := ioutil.ReadFile(resolvConf) - // resolv.conf doesn't have to exists - if err != nil && !os.IsNotExist(err) { - return err - } - - ns := resolvconf.GetNameservers(contents) - // check if systemd-resolved is used, assume it is used when 127.0.0.53 is the only nameserver - if !hostns && len(ns) == 1 && ns[0] == "127.0.0.53" { - // read the actual resolv.conf file for systemd-resolved - resolvedContents, err := ioutil.ReadFile("/run/systemd/resolve/resolv.conf") - if err != nil { - if !os.IsNotExist(err) { - return errors.Wrapf(err, "detected that systemd-resolved is in use, but could not locate real resolv.conf") - } - } else { - contents = resolvedContents - } - } - netStatus := c.getNetworkStatus() for _, status := range netStatus { if status.DNSServerIPs != nil { @@ -2346,34 +2338,18 @@ func (c *Container) generateResolvConf() error { return err } - // Ensure that the container's /etc/resolv.conf is compatible with its - // network configuration. - resolv, err := resolvconf.FilterResolvDNS(contents, ipv6, !hostns) - if err != nil { - return errors.Wrapf(err, "error parsing host resolv.conf") + nameservers := make([]string, 0, len(c.runtime.config.Containers.DNSServers)+len(c.config.DNSServer)) + nameservers = append(nameservers, c.runtime.config.Containers.DNSServers...) + for _, ip := range c.config.DNSServer { + nameservers = append(nameservers, ip.String()) } - - dns := make([]net.IP, 0, len(c.runtime.config.Containers.DNSServers)+len(c.config.DNSServer)) - for _, i := range c.runtime.config.Containers.DNSServers { - result := net.ParseIP(i) - if result == nil { - return errors.Wrapf(define.ErrInvalidArg, "invalid IP address %s", i) - } - dns = append(dns, result) - } - dns = append(dns, c.config.DNSServer...) // If the user provided dns, it trumps all; then dns masq; then resolv.conf var search []string - switch { - case len(dns) > 0: - // We store DNS servers as net.IP, so need to convert to string - for _, server := range dns { - nameservers = append(nameservers, server.String()) - } - default: - // Make a new resolv.conf + keepHostServers := false + if len(nameservers) == 0 { + keepHostServers = true // first add the nameservers from the networks status - nameservers = append(nameservers, networkNameServers...) + nameservers = networkNameServers // when we add network dns server we also have to add the search domains search = networkSearchDomains // slirp4netns has a built in DNS forwarder. @@ -2385,38 +2361,34 @@ func (c *Container) generateResolvConf() error { nameservers = append(nameservers, slirp4netnsDNS.String()) } } - nameservers = append(nameservers, resolvconf.GetNameservers(resolv.Content)...) } if len(c.config.DNSSearch) > 0 || len(c.runtime.config.Containers.DNSSearches) > 0 { - if !cutil.StringInSlice(".", c.config.DNSSearch) { - search = append(search, c.runtime.config.Containers.DNSSearches...) - search = append(search, c.config.DNSSearch...) - } - } else { - search = append(search, resolvconf.GetSearchDomains(resolv.Content)...) + customSearch := make([]string, 0, len(c.config.DNSSearch)+len(c.runtime.config.Containers.DNSSearches)) + customSearch = append(customSearch, c.runtime.config.Containers.DNSSearches...) + customSearch = append(customSearch, c.config.DNSSearch...) + search = customSearch } - var options []string - if len(c.config.DNSOption) > 0 || len(c.runtime.config.Containers.DNSOptions) > 0 { - options = c.runtime.config.Containers.DNSOptions - options = append(options, c.config.DNSOption...) - } else { - options = resolvconf.GetOptions(resolv.Content) - } + options := make([]string, 0, len(c.config.DNSOption)+len(c.runtime.config.Containers.DNSOptions)) + options = append(options, c.runtime.config.Containers.DNSOptions...) + options = append(options, c.config.DNSOption...) destPath := filepath.Join(c.state.RunDir, "resolv.conf") - if err := os.Remove(destPath); err != nil && !os.IsNotExist(err) { - return errors.Wrapf(err, "container %s", c.ID()) - } - - // Build resolv.conf - if _, err = resolvconf.Build(destPath, nameservers, search, options); err != nil { + if err := resolvconf.New(&resolvconf.Params{ + IPv6Enabled: ipv6, + KeepHostServers: keepHostServers, + Nameservers: nameservers, + Namespaces: c.config.Spec.Linux.Namespaces, + Options: options, + Path: destPath, + Searches: search, + }); err != nil { return errors.Wrapf(err, "error building resolv.conf for container %s", c.ID()) } - return c.bindMountRootFile(destPath, "/etc/resolv.conf") + return c.bindMountRootFile(destPath, resolvconf.DefaultResolvConf) } // Check if a container uses IPv6. @@ -2457,31 +2429,13 @@ func (c *Container) addNameserver(ips []string) error { } // Do we have a resolv.conf at all? - path, ok := c.state.BindMounts["/etc/resolv.conf"] + path, ok := c.state.BindMounts[resolvconf.DefaultResolvConf] if !ok { return nil } - // Read in full contents, parse out existing nameservers - contents, err := ioutil.ReadFile(path) - if err != nil { - return err - } - ns := resolvconf.GetNameservers(contents) - options := resolvconf.GetOptions(contents) - search := resolvconf.GetSearchDomains(contents) - - // We could verify that it doesn't already exist - // but extra nameservers shouldn't harm anything. - // Ensure we are the first entry in resolv.conf though, otherwise we - // might be after user-added servers. - ns = append(ips, ns...) - - // We're rewriting the container's resolv.conf as part of this, but we - // hold the container lock, so there should be no risk of parallel - // modification. - if _, err := resolvconf.Build(path, ns, search, options); err != nil { - return errors.Wrapf(err, "error adding new nameserver to container %s resolv.conf", c.ID()) + if err := resolvconf.Add(path, ips); err != nil { + return fmt.Errorf("adding new nameserver to container %s resolv.conf: %w", c.ID(), err) } return nil @@ -2496,34 +2450,13 @@ func (c *Container) removeNameserver(ips []string) error { } // Do we have a resolv.conf at all? - path, ok := c.state.BindMounts["/etc/resolv.conf"] + path, ok := c.state.BindMounts[resolvconf.DefaultResolvConf] if !ok { return nil } - // Read in full contents, parse out existing nameservers - contents, err := ioutil.ReadFile(path) - if err != nil { - return err - } - ns := resolvconf.GetNameservers(contents) - options := resolvconf.GetOptions(contents) - search := resolvconf.GetSearchDomains(contents) - - toRemove := make(map[string]bool) - for _, ip := range ips { - toRemove[ip] = true - } - - newNS := make([]string, 0, len(ns)) - for _, server := range ns { - if !toRemove[server] { - newNS = append(newNS, server) - } - } - - if _, err := resolvconf.Build(path, newNS, search, options); err != nil { - return errors.Wrapf(err, "error removing nameservers from container %s resolv.conf", c.ID()) + if err := resolvconf.Remove(path, ips); err != nil { + return fmt.Errorf("removing nameservers from container %s resolv.conf: %w", c.ID(), err) } return nil 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/networking_linux.go b/libpod/networking_linux.go index 37fa9b5f5..ee80b00fe 100644 --- a/libpod/networking_linux.go +++ b/libpod/networking_linux.go @@ -21,6 +21,7 @@ import ( "github.com/containernetworking/plugins/pkg/ns" "github.com/containers/common/libnetwork/etchosts" + "github.com/containers/common/libnetwork/resolvconf" "github.com/containers/common/libnetwork/types" "github.com/containers/common/pkg/config" "github.com/containers/common/pkg/machine" @@ -30,11 +31,10 @@ import ( "github.com/containers/podman/v4/libpod/events" "github.com/containers/podman/v4/pkg/errorhandling" "github.com/containers/podman/v4/pkg/namespaces" - "github.com/containers/podman/v4/pkg/resolvconf" "github.com/containers/podman/v4/pkg/rootless" "github.com/containers/podman/v4/utils" "github.com/containers/storage/pkg/lockfile" - spec "github.com/opencontainers/runtime-spec/specs-go" + "github.com/opencontainers/runtime-spec/specs-go" "github.com/opencontainers/selinux/go-selinux/label" "github.com/pkg/errors" "github.com/sirupsen/logrus" @@ -526,23 +526,19 @@ func (r *Runtime) GetRootlessNetNs(new bool) (*RootlessNetNS, error) { return nil, errors.Wrapf(err, "failed to determine slirp4netns DNS address from cidr: %s", cidr.String()) } } - conf, err := resolvconf.Get() - if err != nil { - return nil, err - } - conf, err = resolvconf.FilterResolvDNS(conf.Content, netOptions.enableIPv6, true) - if err != nil { - return nil, err - } - searchDomains := resolvconf.GetSearchDomains(conf.Content) - dnsOptions := resolvconf.GetOptions(conf.Content) - nameServers := resolvconf.GetNameservers(conf.Content) - _, err = resolvconf.Build(filepath.Join(rootlessNetNsDir, "resolv.conf"), append([]string{resolveIP.String()}, nameServers...), searchDomains, dnsOptions) - if err != nil { + if err := resolvconf.New(&resolvconf.Params{ + Path: filepath.Join(rootlessNetNsDir, "resolv.conf"), + // fake the netns since we want to filter localhost + Namespaces: []specs.LinuxNamespace{ + {Type: specs.NetworkNamespace}, + }, + IPv6Enabled: netOptions.enableIPv6, + KeepHostServers: true, + Nameservers: []string{resolveIP.String()}, + }); err != nil { return nil, errors.Wrap(err, "failed to create rootless netns resolv.conf") } - // create cni directories to store files // they will be bind mounted to the correct location in a extra mount ns err = os.MkdirAll(filepath.Join(rootlessNetNsDir, persistentCNIDir), 0700) @@ -1089,7 +1085,7 @@ func (c *Container) getContainerNetworkInfo() (*define.InspectNetworkSettings, e func (c *Container) joinedNetworkNSPath() string { for _, namespace := range c.config.Spec.Linux.Namespaces { - if namespace.Type == spec.NetworkNamespace { + if namespace.Type == specs.NetworkNamespace { return namespace.Path } } 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_conmon_linux.go b/libpod/oci_conmon_linux.go index 6aa7ce6dc..0c1ee61d3 100644 --- a/libpod/oci_conmon_linux.go +++ b/libpod/oci_conmon_linux.go @@ -411,8 +411,8 @@ func (r *ConmonOCIRuntime) KillContainer(ctr *Container, signal uint, all bool) if err2 := r.UpdateContainerStatus(ctr); err2 != nil { logrus.Infof("Error updating status for container %s: %v", ctr.ID(), err2) } - if ctr.state.State == define.ContainerStateExited { - return nil + if ctr.ensureState(define.ContainerStateStopped, define.ContainerStateExited) { + return define.ErrCtrStateInvalid } return errors.Wrapf(err, "error sending signal to container %s", ctr.ID()) } 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..8b3b07efa 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 @@ -2159,3 +2174,17 @@ func WithPasswdEntry(passwdEntry string) CtrCreateOption { return nil } } + +// WithMountAllDevices sets the option to mount all of a privileged container's +// host devices +func WithMountAllDevices() CtrCreateOption { + return func(ctr *Container) error { + if ctr.valid { + return define.ErrCtrFinalized + } + + ctr.config.MountAllDevices = true + + return nil + } +} diff --git a/libpod/pod.go b/libpod/pod.go index 3c8dc43d4..108317637 100644 --- a/libpod/pod.go +++ b/libpod/pod.go @@ -178,8 +178,8 @@ func (p *Pod) NetworkMode() string { return infra.NetworkMode() } -// PidMode returns the PID mode given by the user ex: pod, private... -func (p *Pod) PidMode() string { +// Namespace Mode returns the given NS mode provided by the user ex: host, private... +func (p *Pod) NamespaceMode(kind specs.LinuxNamespaceType) string { infra, err := p.runtime.GetContainer(p.state.InfraContainerID) if err != nil { return "" @@ -187,28 +187,7 @@ func (p *Pod) PidMode() string { ctrSpec := infra.config.Spec if ctrSpec != nil && ctrSpec.Linux != nil { for _, ns := range ctrSpec.Linux.Namespaces { - if ns.Type == specs.PIDNamespace { - if ns.Path != "" { - return fmt.Sprintf("ns:%s", ns.Path) - } - return "private" - } - } - return "host" - } - return "" -} - -// PidMode returns the PID mode given by the user ex: pod, private... -func (p *Pod) UserNSMode() string { - infra, err := p.infraContainer() - if err != nil { - return "" - } - ctrSpec := infra.config.Spec - if ctrSpec != nil && ctrSpec.Linux != nil { - for _, ns := range ctrSpec.Linux.Namespaces { - if ns.Type == specs.UserNamespace { + if ns.Type == kind { if ns.Path != "" { return fmt.Sprintf("ns:%s", ns.Path) } diff --git a/libpod/pod_api.go b/libpod/pod_api.go index 1c1e15984..fefe0e329 100644 --- a/libpod/pod_api.go +++ b/libpod/pod_api.go @@ -9,6 +9,7 @@ import ( "github.com/containers/podman/v4/libpod/events" "github.com/containers/podman/v4/pkg/parallel" "github.com/containers/podman/v4/pkg/rootless" + "github.com/opencontainers/runtime-spec/specs-go" "github.com/pkg/errors" "github.com/sirupsen/logrus" ) @@ -673,8 +674,8 @@ func (p *Pod) Inspect() (*define.InspectPodData, error) { infraConfig.CPUPeriod = p.CPUPeriod() infraConfig.CPUQuota = p.CPUQuota() infraConfig.CPUSetCPUs = p.ResourceLim().CPU.Cpus - infraConfig.PidNS = p.PidMode() - infraConfig.UserNS = p.UserNSMode() + infraConfig.PidNS = p.NamespaceMode(specs.PIDNamespace) + infraConfig.UserNS = p.NamespaceMode(specs.UserNamespace) namedVolumes, mounts := infra.SortUserVolumes(infra.config.Spec) inspectMounts, err = infra.GetMounts(namedVolumes, infra.config.ImageVolumes, mounts) infraSecurity = infra.GetSecurityOptions() 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..6c8a99846 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 { @@ -1067,7 +1123,7 @@ func (r *Runtime) mergeDBConfig(dbConfig *DBConfig) { if !r.storageSet.GraphDriverNameSet && dbConfig.GraphDriver != "" { if r.storageConfig.GraphDriverName != dbConfig.GraphDriver && r.storageConfig.GraphDriverName != "" { - logrus.Errorf("User-selected graph driver %q overwritten by graph driver %q from database - delete libpod local files to resolve", + logrus.Errorf("User-selected graph driver %q overwritten by graph driver %q from database - delete libpod local files to resolve. May prevent use of images created by other tools", r.storageConfig.GraphDriverName, dbConfig.GraphDriver) } r.storageConfig.GraphDriverName = dbConfig.GraphDriver diff --git a/pkg/api/handlers/compat/containers_create.go b/pkg/api/handlers/compat/containers_create.go index b9b7f6708..67ec52047 100644 --- a/pkg/api/handlers/compat/containers_create.go +++ b/pkg/api/handlers/compat/containers_create.go @@ -261,8 +261,13 @@ func cliOpts(cc handlers.CreateContainerConfig, rtc *config.Config) (*entities.C } } - // netMode - nsmode, networks, netOpts, err := specgen.ParseNetworkFlag([]string{string(cc.HostConfig.NetworkMode)}) + // special case for NetworkMode, the podman default is slirp4netns for + // rootless but for better docker compat we want bridge. + netmode := string(cc.HostConfig.NetworkMode) + if netmode == "" || netmode == "default" { + netmode = "bridge" + } + nsmode, networks, netOpts, err := specgen.ParseNetworkFlag([]string{netmode}) if err != nil { return nil, nil, err } @@ -278,6 +283,7 @@ func cliOpts(cc handlers.CreateContainerConfig, rtc *config.Config) (*entities.C Network: nsmode, PublishPorts: specPorts, NetworkOptions: netOpts, + NoHosts: rtc.Containers.NoHosts, } // network names @@ -438,7 +444,7 @@ func cliOpts(cc handlers.CreateContainerConfig, rtc *config.Config) (*entities.C cliOpts.Volume = append(cliOpts.Volume, vol) // Extract the destination so we don't add duplicate mounts in // the volumes phase. - splitVol := strings.SplitN(vol, ":", 3) + splitVol := specgen.SplitVolumeString(vol) switch len(splitVol) { case 1: volDestinations[vol] = true diff --git a/pkg/api/handlers/compat/images_build.go b/pkg/api/handlers/compat/images_build.go index f47aa523e..fe17aa1d4 100644 --- a/pkg/api/handlers/compat/images_build.go +++ b/pkg/api/handlers/compat/images_build.go @@ -70,68 +70,69 @@ func BuildImage(w http.ResponseWriter, r *http.Request) { }() query := struct { - AddHosts string `schema:"extrahosts"` - AdditionalCapabilities string `schema:"addcaps"` - AllPlatforms bool `schema:"allplatforms"` - Annotations string `schema:"annotations"` - AppArmor string `schema:"apparmor"` - BuildArgs string `schema:"buildargs"` - CacheFrom string `schema:"cachefrom"` - CgroupParent string `schema:"cgroupparent"` // nolint - Compression uint64 `schema:"compression"` - ConfigureNetwork string `schema:"networkmode"` - CPPFlags string `schema:"cppflags"` - CpuPeriod uint64 `schema:"cpuperiod"` // nolint - CpuQuota int64 `schema:"cpuquota"` // nolint - CpuSetCpus string `schema:"cpusetcpus"` // nolint - CpuSetMems string `schema:"cpusetmems"` // nolint - CpuShares uint64 `schema:"cpushares"` // nolint - DNSOptions string `schema:"dnsoptions"` - DNSSearch string `schema:"dnssearch"` - DNSServers string `schema:"dnsservers"` - Devices string `schema:"devices"` - Dockerfile string `schema:"dockerfile"` - DropCapabilities string `schema:"dropcaps"` - Envs []string `schema:"setenv"` - Excludes string `schema:"excludes"` - ForceRm bool `schema:"forcerm"` - From string `schema:"from"` - HTTPProxy bool `schema:"httpproxy"` - IdentityLabel bool `schema:"identitylabel"` - Ignore bool `schema:"ignore"` - Isolation string `schema:"isolation"` - Jobs int `schema:"jobs"` // nolint - LabelOpts string `schema:"labelopts"` - Labels string `schema:"labels"` - Layers bool `schema:"layers"` - LogRusage bool `schema:"rusage"` - Manifest string `schema:"manifest"` - MemSwap int64 `schema:"memswap"` - Memory int64 `schema:"memory"` - NamespaceOptions string `schema:"nsoptions"` - NoCache bool `schema:"nocache"` - OSFeatures []string `schema:"osfeature"` - OSVersion string `schema:"osversion"` - OutputFormat string `schema:"outputformat"` - Platform []string `schema:"platform"` - Pull bool `schema:"pull"` - PullPolicy string `schema:"pullpolicy"` - Quiet bool `schema:"q"` - Registry string `schema:"registry"` - Rm bool `schema:"rm"` - RusageLogFile string `schema:"rusagelogfile"` - Remote string `schema:"remote"` - Seccomp string `schema:"seccomp"` - Secrets string `schema:"secrets"` - SecurityOpt string `schema:"securityopt"` - ShmSize int `schema:"shmsize"` - Squash bool `schema:"squash"` - TLSVerify bool `schema:"tlsVerify"` - Tags []string `schema:"t"` - Target string `schema:"target"` - Timestamp int64 `schema:"timestamp"` - Ulimits string `schema:"ulimits"` - UnsetEnvs []string `schema:"unsetenv"` + AddHosts string `schema:"extrahosts"` + AdditionalCapabilities string `schema:"addcaps"` + AdditionalBuildContexts string `schema:"additionalbuildcontexts"` + AllPlatforms bool `schema:"allplatforms"` + Annotations string `schema:"annotations"` + AppArmor string `schema:"apparmor"` + BuildArgs string `schema:"buildargs"` + CacheFrom string `schema:"cachefrom"` + CgroupParent string `schema:"cgroupparent"` // nolint + Compression uint64 `schema:"compression"` + ConfigureNetwork string `schema:"networkmode"` + CPPFlags string `schema:"cppflags"` + CpuPeriod uint64 `schema:"cpuperiod"` // nolint + CpuQuota int64 `schema:"cpuquota"` // nolint + CpuSetCpus string `schema:"cpusetcpus"` // nolint + CpuSetMems string `schema:"cpusetmems"` // nolint + CpuShares uint64 `schema:"cpushares"` // nolint + DNSOptions string `schema:"dnsoptions"` + DNSSearch string `schema:"dnssearch"` + DNSServers string `schema:"dnsservers"` + Devices string `schema:"devices"` + Dockerfile string `schema:"dockerfile"` + DropCapabilities string `schema:"dropcaps"` + Envs []string `schema:"setenv"` + Excludes string `schema:"excludes"` + ForceRm bool `schema:"forcerm"` + From string `schema:"from"` + HTTPProxy bool `schema:"httpproxy"` + IdentityLabel bool `schema:"identitylabel"` + Ignore bool `schema:"ignore"` + Isolation string `schema:"isolation"` + Jobs int `schema:"jobs"` // nolint + LabelOpts string `schema:"labelopts"` + Labels string `schema:"labels"` + Layers bool `schema:"layers"` + LogRusage bool `schema:"rusage"` + Manifest string `schema:"manifest"` + MemSwap int64 `schema:"memswap"` + Memory int64 `schema:"memory"` + NamespaceOptions string `schema:"nsoptions"` + NoCache bool `schema:"nocache"` + OSFeatures []string `schema:"osfeature"` + OSVersion string `schema:"osversion"` + OutputFormat string `schema:"outputformat"` + Platform []string `schema:"platform"` + Pull bool `schema:"pull"` + PullPolicy string `schema:"pullpolicy"` + Quiet bool `schema:"q"` + Registry string `schema:"registry"` + Rm bool `schema:"rm"` + RusageLogFile string `schema:"rusagelogfile"` + Remote string `schema:"remote"` + Seccomp string `schema:"seccomp"` + Secrets string `schema:"secrets"` + SecurityOpt string `schema:"securityopt"` + ShmSize int `schema:"shmsize"` + Squash bool `schema:"squash"` + TLSVerify bool `schema:"tlsVerify"` + Tags []string `schema:"t"` + Target string `schema:"target"` + Timestamp int64 `schema:"timestamp"` + Ulimits string `schema:"ulimits"` + UnsetEnvs []string `schema:"unsetenv"` }{ Dockerfile: "Dockerfile", IdentityLabel: true, @@ -375,6 +376,14 @@ func BuildImage(w http.ResponseWriter, r *http.Request) { additionalTags = append(additionalTags, possiblyNormalizedTag) } + var additionalBuildContexts = map[string]*buildahDefine.AdditionalBuildContext{} + if _, found := r.URL.Query()["additionalbuildcontexts"]; found { + if err := json.Unmarshal([]byte(query.AdditionalBuildContexts), &additionalBuildContexts); err != nil { + utils.BadRequest(w, "additionalbuildcontexts", query.AdditionalBuildContexts, err) + return + } + } + var buildArgs = map[string]string{} if _, found := r.URL.Query()["buildargs"]; found { if err := json.Unmarshal([]byte(query.BuildArgs), &buildArgs); err != nil { @@ -562,12 +571,13 @@ func BuildImage(w http.ResponseWriter, r *http.Request) { runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime) buildOptions := buildahDefine.BuildOptions{ - AddCapabilities: addCaps, - AdditionalTags: additionalTags, - Annotations: annotations, - CPPFlags: cppflags, - Args: buildArgs, - AllPlatforms: query.AllPlatforms, + AddCapabilities: addCaps, + AdditionalBuildContexts: additionalBuildContexts, + AdditionalTags: additionalTags, + Annotations: annotations, + CPPFlags: cppflags, + Args: buildArgs, + AllPlatforms: query.AllPlatforms, CommonBuildOpts: &buildah.CommonBuildOptions{ AddHost: addhosts, ApparmorProfile: apparmor, diff --git a/pkg/api/handlers/libpod/manifests.go b/pkg/api/handlers/libpod/manifests.go index 65b9d6cb5..d9ed1c265 100644 --- a/pkg/api/handlers/libpod/manifests.go +++ b/pkg/api/handlers/libpod/manifests.go @@ -163,7 +163,6 @@ func ManifestAddV3(w http.ResponseWriter, r *http.Request) { // Wrapper to support 3.x with 4.x libpod query := struct { entities.ManifestAddOptions - Images []string TLSVerify bool `schema:"tlsVerify"` }{} if err := json.NewDecoder(r.Body).Decode(&query); err != 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/bindings/images/build.go b/pkg/bindings/images/build.go index b4b7c36f6..fe81dc662 100644 --- a/pkg/bindings/images/build.go +++ b/pkg/bindings/images/build.go @@ -81,6 +81,13 @@ func Build(ctx context.Context, containerFiles []string, options entities.BuildO for _, tag := range options.AdditionalTags { params.Add("t", tag) } + if additionalBuildContexts := options.AdditionalBuildContexts; len(additionalBuildContexts) > 0 { + additionalBuildContextMap, err := jsoniter.Marshal(additionalBuildContexts) + if err != nil { + return nil, err + } + params.Set("additionalbuildcontexts", string(additionalBuildContextMap)) + } if buildArgs := options.Args; len(buildArgs) > 0 { bArgs, err := jsoniter.MarshalToString(buildArgs) if err != nil { diff --git a/pkg/domain/infra/abi/containers.go b/pkg/domain/infra/abi/containers.go index d2fafccb1..8bd84a310 100644 --- a/pkg/domain/infra/abi/containers.go +++ b/pkg/domain/infra/abi/containers.go @@ -616,6 +616,7 @@ func (ic *ContainerEngine) ContainerRestore(ctx context.Context, namesOrIds []st ImportPrevious: options.ImportPrevious, Pod: options.Pod, PrintStats: options.PrintStats, + FileLocks: options.FileLocks, } filterFuncs := []libpod.ContainerFilter{ 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 daa6f0cbf..03e7ffb5d 100644 --- a/pkg/domain/infra/runtime_libpod.go +++ b/pkg/domain/infra/runtime_libpod.go @@ -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()) } diff --git a/pkg/machine/qemu/machine.go b/pkg/machine/qemu/machine.go index 4ae04b77f..1b0d63986 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 diff --git a/pkg/resolvconf/dns/resolvconf.go b/pkg/resolvconf/dns/resolvconf.go deleted file mode 100644 index cb4bd1033..000000000 --- a/pkg/resolvconf/dns/resolvconf.go +++ /dev/null @@ -1,28 +0,0 @@ -// Originally from github.com/docker/libnetwork/resolvconf/dns - -package dns - -import ( - "regexp" -) - -// IPLocalhost is a regex pattern for IPv4 or IPv6 loopback range. -const IPLocalhost = `((127\.([0-9]{1,3}\.){2}[0-9]{1,3})|(::1)$)` - -// IPv4Localhost is a regex pattern for IPv4 localhost address range. -const IPv4Localhost = `(127\.([0-9]{1,3}\.){2}[0-9]{1,3})` - -var localhostIPRegexp = regexp.MustCompile(IPLocalhost) -var localhostIPv4Regexp = regexp.MustCompile(IPv4Localhost) - -// IsLocalhost returns true if ip matches the localhost IP regular expression. -// Used for determining if nameserver settings are being passed which are -// localhost addresses -func IsLocalhost(ip string) bool { - return localhostIPRegexp.MatchString(ip) -} - -// IsIPv4Localhost returns true if ip matches the IPv4 localhost regular expression. -func IsIPv4Localhost(ip string) bool { - return localhostIPv4Regexp.MatchString(ip) -} diff --git a/pkg/specgen/generate/config_linux.go b/pkg/specgen/generate/config_linux.go index ed2e5408d..4c3748e67 100644 --- a/pkg/specgen/generate/config_linux.go +++ b/pkg/specgen/generate/config_linux.go @@ -3,7 +3,6 @@ package generate import ( "fmt" "io/fs" - "io/ioutil" "os" "path" "path/filepath" @@ -11,6 +10,7 @@ import ( "github.com/containers/podman/v4/libpod/define" "github.com/containers/podman/v4/pkg/rootless" + "github.com/containers/podman/v4/pkg/util" spec "github.com/opencontainers/runtime-spec/specs-go" "github.com/opencontainers/runtime-tools/generate" "github.com/pkg/errors" @@ -18,56 +18,6 @@ import ( "golang.org/x/sys/unix" ) -var ( - errNotADevice = errors.New("not a device node") -) - -func addPrivilegedDevices(g *generate.Generator) error { - hostDevices, err := getDevices("/dev") - if err != nil { - return err - } - g.ClearLinuxDevices() - - if rootless.IsRootless() { - mounts := make(map[string]interface{}) - for _, m := range g.Mounts() { - mounts[m.Destination] = true - } - newMounts := []spec.Mount{} - for _, d := range hostDevices { - devMnt := spec.Mount{ - Destination: d.Path, - Type: define.TypeBind, - Source: d.Path, - Options: []string{"slave", "nosuid", "noexec", "rw", "rbind"}, - } - if d.Path == "/dev/ptmx" || strings.HasPrefix(d.Path, "/dev/tty") { - continue - } - if _, found := mounts[d.Path]; found { - continue - } - newMounts = append(newMounts, devMnt) - } - g.Config.Mounts = append(newMounts, g.Config.Mounts...) - if g.Config.Linux.Resources != nil { - g.Config.Linux.Resources.Devices = nil - } - } else { - for _, d := range hostDevices { - g.AddDevice(d) - } - // Add resources device - need to clear the existing one first. - if g.Config.Linux.Resources != nil { - g.Config.Linux.Resources.Devices = nil - } - g.AddLinuxResourcesDevice(true, "", nil, nil, "rwm") - } - - return nil -} - // DevicesFromPath computes a list of devices func DevicesFromPath(g *generate.Generator, devicePath string) error { devs := strings.Split(devicePath, ":") @@ -174,60 +124,12 @@ func BlockAccessToKernelFilesystems(privileged, pidModeIsHost bool, mask, unmask } } -// based on getDevices from runc (libcontainer/devices/devices.go) -func getDevices(path string) ([]spec.LinuxDevice, error) { - files, err := ioutil.ReadDir(path) - if err != nil { - if rootless.IsRootless() && os.IsPermission(err) { - return nil, nil - } - return nil, err - } - out := []spec.LinuxDevice{} - for _, f := range files { - switch { - case f.IsDir(): - switch f.Name() { - // ".lxc" & ".lxd-mounts" added to address https://github.com/lxc/lxd/issues/2825 - case "pts", "shm", "fd", "mqueue", ".lxc", ".lxd-mounts": - continue - default: - sub, err := getDevices(filepath.Join(path, f.Name())) - if err != nil { - return nil, err - } - if sub != nil { - out = append(out, sub...) - } - continue - } - case f.Name() == "console": - continue - case f.Mode()&os.ModeSymlink != 0: - continue - } - - device, err := deviceFromPath(filepath.Join(path, f.Name())) - if err != nil { - if err == errNotADevice { - continue - } - if os.IsNotExist(err) { - continue - } - return nil, err - } - out = append(out, *device) - } - return out, nil -} - func addDevice(g *generate.Generator, device string) error { src, dst, permissions, err := ParseDevice(device) if err != nil { return err } - dev, err := deviceFromPath(src) + dev, err := util.DeviceFromPath(src) if err != nil { return errors.Wrapf(err, "%s is not a valid device", src) } @@ -316,43 +218,6 @@ func IsValidDeviceMode(mode string) bool { return true } -// Copied from github.com/opencontainers/runc/libcontainer/devices -// Given the path to a device look up the information about a linux device -func deviceFromPath(path string) (*spec.LinuxDevice, error) { - var stat unix.Stat_t - err := unix.Lstat(path, &stat) - if err != nil { - return nil, err - } - var ( - devType string - mode = stat.Mode - devNumber = uint64(stat.Rdev) // nolint: unconvert - m = os.FileMode(mode) - ) - - switch { - case mode&unix.S_IFBLK == unix.S_IFBLK: - devType = "b" - case mode&unix.S_IFCHR == unix.S_IFCHR: - devType = "c" - case mode&unix.S_IFIFO == unix.S_IFIFO: - devType = "p" - default: - return nil, errNotADevice - } - - return &spec.LinuxDevice{ - Type: devType, - Path: path, - FileMode: &m, - UID: &stat.Uid, - GID: &stat.Gid, - Major: int64(unix.Major(devNumber)), - Minor: int64(unix.Minor(devNumber)), - }, nil -} - func supportAmbientCapabilities() bool { err := unix.Prctl(unix.PR_CAP_AMBIENT, unix.PR_CAP_AMBIENT_IS_SET, 0, 0, 0) return err == nil diff --git a/pkg/specgen/generate/container_create.go b/pkg/specgen/generate/container_create.go index 04e24d625..7faf13465 100644 --- a/pkg/specgen/generate/container_create.go +++ b/pkg/specgen/generate/container_create.go @@ -278,6 +278,10 @@ func createContainerOptions(rt *libpod.Runtime, s *specgen.SpecGenerator, pod *l options = append(options, libpod.WithPasswdEntry(s.PasswdEntry)) } + if s.Privileged { + options = append(options, libpod.WithMountAllDevices()) + } + useSystemd := false switch s.Systemd { case "always": @@ -542,6 +546,16 @@ func Inherit(infra libpod.Container, s *specgen.SpecGenerator, rt *libpod.Runtim infraConf := infra.Config() infraSpec := infraConf.Spec + // need to set compatOptions to the currently filled specgenOptions so we do not overwrite + compatibleOptions.CapAdd = append(compatibleOptions.CapAdd, s.CapAdd...) + compatibleOptions.CapDrop = append(compatibleOptions.CapDrop, s.CapDrop...) + compatibleOptions.HostDeviceList = append(compatibleOptions.HostDeviceList, s.HostDeviceList...) + compatibleOptions.ImageVolumes = append(compatibleOptions.ImageVolumes, s.ImageVolumes...) + compatibleOptions.Mounts = append(compatibleOptions.Mounts, s.Mounts...) + compatibleOptions.OverlayVolumes = append(compatibleOptions.OverlayVolumes, s.OverlayVolumes...) + compatibleOptions.SelinuxOpts = append(compatibleOptions.SelinuxOpts, s.SelinuxOpts...) + compatibleOptions.Volumes = append(compatibleOptions.Volumes, s.Volumes...) + compatByte, err := json.Marshal(compatibleOptions) if err != nil { return nil, nil, nil, err diff --git a/pkg/specgen/generate/namespaces.go b/pkg/specgen/generate/namespaces.go index 4dd6b3eaf..4224d16ce 100644 --- a/pkg/specgen/generate/namespaces.go +++ b/pkg/specgen/generate/namespaces.go @@ -19,6 +19,8 @@ import ( "github.com/sirupsen/logrus" ) +const host = "host" + // Get the default namespace mode for any given namespace type. func GetDefaultNamespaceMode(nsType string, cfg *config.Config, pod *libpod.Pod) (specgen.Namespace, error) { // The default for most is private @@ -33,16 +35,38 @@ func GetDefaultNamespaceMode(nsType string, cfg *config.Config, pod *libpod.Pod) podMode := false switch { case nsType == "pid" && pod.SharesPID(): + if pod.NamespaceMode(spec.PIDNamespace) == host { + toReturn.NSMode = specgen.Host + return toReturn, nil + } podMode = true case nsType == "ipc" && pod.SharesIPC(): + if pod.NamespaceMode(spec.IPCNamespace) == host { + toReturn.NSMode = specgen.Host + return toReturn, nil + } podMode = true case nsType == "uts" && pod.SharesUTS(): + if pod.NamespaceMode(spec.UTSNamespace) == host { + toReturn.NSMode = specgen.Host + return toReturn, nil + } podMode = true case nsType == "user" && pod.SharesUser(): + // user does not need a special check for host, this is already validated on pod creation + // if --userns=host then pod.SharesUser == false podMode = true case nsType == "net" && pod.SharesNet(): + if pod.NetworkMode() == host { + toReturn.NSMode = specgen.Host + return toReturn, nil + } podMode = true case nsType == "cgroup" && pod.SharesCgroup(): + if pod.NamespaceMode(spec.CgroupNamespace) == host { + toReturn.NSMode = specgen.Host + return toReturn, nil + } podMode = true } if podMode { @@ -488,10 +512,7 @@ func GetNamespaceOptions(ns []string, netnsIsHost bool) ([]libpod.PodCreateOptio case "cgroup": options = append(options, libpod.WithPodCgroup()) case "net": - // share the netns setting with other containers in the pod only when it is not set to host - if !netnsIsHost { - options = append(options, libpod.WithPodNet()) - } + options = append(options, libpod.WithPodNet()) case "mnt": return erroredOptions, errors.Errorf("Mount sharing functionality not supported on pod level") case "pid": diff --git a/pkg/specgen/generate/oci.go b/pkg/specgen/generate/oci.go index dda2de6e4..716960024 100644 --- a/pkg/specgen/generate/oci.go +++ b/pkg/specgen/generate/oci.go @@ -337,14 +337,8 @@ func SpecGenToOCI(ctx context.Context, s *specgen.SpecGenerator, rt *libpod.Runt } var userDevices []spec.LinuxDevice - if s.Privileged { - // If privileged, we need to add all the host devices to the - // spec. We do not add the user provided ones because we are - // already adding them all. - if err := addPrivilegedDevices(&g); err != nil { - return nil, err - } - } else { + + if !s.Privileged { // add default devices from containers.conf for _, device := range rtc.Containers.Devices { if err = DevicesFromPath(&g, device); err != nil { diff --git a/pkg/specgen/volumes.go b/pkg/specgen/volumes.go index b26666df3..a7a1022b0 100644 --- a/pkg/specgen/volumes.go +++ b/pkg/specgen/volumes.go @@ -97,6 +97,8 @@ func GenVolumeMounts(volumeFlag []string) (map[string]spec.Mount, map[string]*Na // This is not a named volume overlayFlag := false chownFlag := false + upperDirFlag := false + workDirFlag := false for _, o := range options { if o == "O" { overlayFlag = true @@ -105,8 +107,16 @@ func GenVolumeMounts(volumeFlag []string) (map[string]spec.Mount, map[string]*Na if strings.Contains(joinedOpts, "U") { chownFlag = true } - - if len(options) > 2 || (len(options) == 2 && !chownFlag) { + if strings.Contains(joinedOpts, "upperdir") { + upperDirFlag = true + } + if strings.Contains(joinedOpts, "workdir") { + workDirFlag = true + } + if (workDirFlag && !upperDirFlag) || (!workDirFlag && upperDirFlag) { + return nil, nil, nil, errors.New("must set both `upperdir` and `workdir`") + } + if len(options) > 2 && !(len(options) == 3 && upperDirFlag && workDirFlag) || (len(options) == 2 && !chownFlag) { return nil, nil, nil, errors.New("can't use 'O' with other options") } } diff --git a/pkg/util/utils_linux.go b/pkg/util/utils_linux.go index 0b21bf3c5..871303f64 100644 --- a/pkg/util/utils_linux.go +++ b/pkg/util/utils_linux.go @@ -3,13 +3,24 @@ package util import ( "fmt" "io/fs" + "io/ioutil" "os" "path/filepath" + "strings" "syscall" + "github.com/containers/podman/v4/libpod/define" + "github.com/containers/podman/v4/pkg/rootless" "github.com/containers/psgo" + spec "github.com/opencontainers/runtime-spec/specs-go" + "github.com/opencontainers/runtime-tools/generate" "github.com/pkg/errors" "github.com/sirupsen/logrus" + "golang.org/x/sys/unix" +) + +var ( + errNotADevice = errors.New("not a device node") ) // GetContainerPidInformationDescriptors returns a string slice of all supported @@ -59,3 +70,134 @@ func FindDeviceNodes() (map[string]string, error) { return nodes, nil } + +func AddPrivilegedDevices(g *generate.Generator) error { + hostDevices, err := getDevices("/dev") + if err != nil { + return err + } + g.ClearLinuxDevices() + + if rootless.IsRootless() { + mounts := make(map[string]interface{}) + for _, m := range g.Mounts() { + mounts[m.Destination] = true + } + newMounts := []spec.Mount{} + for _, d := range hostDevices { + devMnt := spec.Mount{ + Destination: d.Path, + Type: define.TypeBind, + Source: d.Path, + Options: []string{"slave", "nosuid", "noexec", "rw", "rbind"}, + } + if d.Path == "/dev/ptmx" || strings.HasPrefix(d.Path, "/dev/tty") { + continue + } + if _, found := mounts[d.Path]; found { + continue + } + newMounts = append(newMounts, devMnt) + } + g.Config.Mounts = append(newMounts, g.Config.Mounts...) + if g.Config.Linux.Resources != nil { + g.Config.Linux.Resources.Devices = nil + } + } else { + for _, d := range hostDevices { + g.AddDevice(d) + } + // Add resources device - need to clear the existing one first. + if g.Config.Linux.Resources != nil { + g.Config.Linux.Resources.Devices = nil + } + g.AddLinuxResourcesDevice(true, "", nil, nil, "rwm") + } + + return nil +} + +// based on getDevices from runc (libcontainer/devices/devices.go) +func getDevices(path string) ([]spec.LinuxDevice, error) { + files, err := ioutil.ReadDir(path) + if err != nil { + if rootless.IsRootless() && os.IsPermission(err) { + return nil, nil + } + return nil, err + } + out := []spec.LinuxDevice{} + for _, f := range files { + switch { + case f.IsDir(): + switch f.Name() { + // ".lxc" & ".lxd-mounts" added to address https://github.com/lxc/lxd/issues/2825 + case "pts", "shm", "fd", "mqueue", ".lxc", ".lxd-mounts": + continue + default: + sub, err := getDevices(filepath.Join(path, f.Name())) + if err != nil { + return nil, err + } + if sub != nil { + out = append(out, sub...) + } + continue + } + case f.Name() == "console": + continue + case f.Mode()&os.ModeSymlink != 0: + continue + } + + device, err := DeviceFromPath(filepath.Join(path, f.Name())) + if err != nil { + if err == errNotADevice { + continue + } + if os.IsNotExist(err) { + continue + } + return nil, err + } + out = append(out, *device) + } + return out, nil +} + +// Copied from github.com/opencontainers/runc/libcontainer/devices +// Given the path to a device look up the information about a linux device +func DeviceFromPath(path string) (*spec.LinuxDevice, error) { + var stat unix.Stat_t + err := unix.Lstat(path, &stat) + if err != nil { + return nil, err + } + var ( + devType string + mode = stat.Mode + devNumber = uint64(stat.Rdev) // nolint: unconvert + m = os.FileMode(mode) + ) + + switch { + case mode&unix.S_IFBLK == unix.S_IFBLK: + devType = "b" + case mode&unix.S_IFCHR == unix.S_IFCHR: + devType = "c" + case mode&unix.S_IFIFO == unix.S_IFIFO: + devType = "p" + default: + return nil, errNotADevice + } + + return &spec.LinuxDevice{ + Type: devType, + Path: path, + FileMode: &m, + UID: &stat.Uid, + GID: &stat.Gid, + Major: int64(unix.Major(devNumber)), + Minor: int64(unix.Minor(devNumber)), + }, nil +} diff --git a/test/apiv2/10-images.at b/test/apiv2/10-images.at index 13aaea317..f03b95786 100644 --- a/test/apiv2/10-images.at +++ b/test/apiv2/10-images.at @@ -53,7 +53,7 @@ t POST "images/create?fromImage=alpine" 200 .error~null .status~".*Download comp t POST "images/create?fromImage=alpine&tag=latest" 200 # 10977 - handle platform parameter correctly -t POST "images/create?fromImage=testimage:20210610&platform=linux/arm64" 200 +t POST "images/create?fromImage=quay.io/libpod/testimage:20210610&platform=linux/arm64" 200 t GET "images/testimage:20210610/json" 200 \ .Architecture=arm64 diff --git a/test/apiv2/12-imagesMore.at b/test/apiv2/12-imagesMore.at index 67b4f1c79..57d5e114d 100644 --- a/test/apiv2/12-imagesMore.at +++ b/test/apiv2/12-imagesMore.at @@ -6,6 +6,8 @@ red='\e[31m' nc='\e[0m' +start_registry + podman pull -q $IMAGE t GET libpod/images/json 200 \ @@ -20,48 +22,38 @@ t GET libpod/images/$IMAGE/tree 200 \ t POST "libpod/images/nonesuch/tag?repo=myrepo&tag=mytag" 404 # Tag the image -t POST "libpod/images/$IMAGE/tag?repo=localhost:5000/myrepo&tag=mytag" 201 +t POST "libpod/images/$IMAGE/tag?repo=localhost:$REGISTRY_PORT/myrepo&tag=mytag" 201 t GET libpod/images/$IMAGE/json 200 \ - .RepoTags[1]=localhost:5000/myrepo:mytag - -# Run registry container -# FIXME this fails if python tests have been run first... -podman run -d --name registry -p 5000:5000 quay.io/libpod/registry:2.7 /entrypoint.sh /etc/docker/registry/config.yml -wait_for_port localhost 5000 - -# Push to local registry and check output -while read -r LINE -do - if echo "${LINE}" | jq --exit-status 'select( .status != null) | select ( .status | contains("digest: sha256:"))' &>/dev/null; then - GOT_DIGEST="1" - fi -done < <(curl -sL "http://$HOST:$PORT/images/localhost:5000/myrepo/push?tlsVerify=false&tag=mytag" -XPOST) -if [ -z "${GOT_DIGEST}" ] ; then - echo -e "${red}not ok: did not found digest in output${nc}" 1>&2; -fi - -# Push to local registry -t POST "images/localhost:5000/myrepo/push?tlsVerify=false&tag=mytag" 200 + .RepoTags[1]=localhost:$REGISTRY_PORT/myrepo:mytag + +# Push to local registry... +t POST "images/localhost:$REGISTRY_PORT/myrepo/push?tlsVerify=false&tag=mytag" 200 + +# ...and check output. We can't use our built-in checks because this output +# is a sequence of JSON objects, i.e., individual ones, not in a JSON array. +# The lines themselves are valid JSON, but taken together they are not. +readarray lines <<<"$output" +s0=$(jq -r .status <<<"${lines[0]}") +is "$s0" "The push refers to repository [localhost:$REGISTRY_PORT/myrepo:mytag]" \ + "Push to local registry: first status line" + +# FIXME: is there a way to test the actual digest? +s1=$(jq -r .status <<<"${lines[1]}") +like "$s1" "mytag: digest: sha256:[0-9a-f]\{64\} size: [0-9]\+" \ + "Push to local registry: second status line" # Untag the image -t POST "libpod/images/$iid/untag?repo=localhost:5000/myrepo&tag=mytag" 201 +t POST "libpod/images/$iid/untag?repo=localhost:$REGISTRY_PORT/myrepo&tag=mytag" 201 # Try to push non-existing image -t POST "images/localhost:5000/idonotexist/push?tlsVerify=false" 404 +t POST "images/localhost:$REGISTRY_PORT/idonotexist/push?tlsVerify=false" 404 t GET libpod/images/$IMAGE/json 200 \ .RepoTags[-1]=$IMAGE -# Remove the registry container -t DELETE libpod/containers/registry?force=true 200 - -# Remove images +# Remove image t DELETE libpod/images/$IMAGE 200 \ .ExitCode=0 -t DELETE libpod/images/quay.io/libpod/registry:2.7 200 \ - .ExitCode=0 -if [ -z "${GOT_DIGEST}" ] ; then - exit 1; -fi +stop_registry diff --git a/test/apiv2/15-manifest.at b/test/apiv2/15-manifest.at index 0dd7026fa..970bed5a8 100644 --- a/test/apiv2/15-manifest.at +++ b/test/apiv2/15-manifest.at @@ -2,18 +2,40 @@ # # Tests for manifest list endpoints +start_registry + t POST /v3.4.0/libpod/manifests/create?name=abc 200 \ .Id~[0-9a-f]\\{64\\} id_abc=$(jq -r '.Id' <<<"$output") t POST /v4.0.0/libpod/manifests/xyz 201 \ .Id~[0-9a-f]\\{64\\} -echo xyz $output id_xyz=$(jq -r '.Id' <<<"$output") t GET /v3.4.0/libpod/manifests/$id_abc/exists 204 t GET /v4.0.0/libpod/manifests/$id_xyz/exists 204 +id_abc_image=$($PODMAN_BIN --root $WORKDIR/server_root image build -q --format=docker -<<EOF +FROM alpine +RUN >file1 +EOF +) + +id_xyz_image=$($PODMAN_BIN --root $WORKDIR/server_root image build -q --format=docker -<<EOF +FROM alpine +RUN >file2 +EOF +) + +t POST /v3.4.0/libpod/manifests/$id_abc/add images="[\"containers-storage:$id_abc_image\"]" 200 +t PUT /v4.0.0/libpod/manifests/$id_xyz operation='update' images="[\"containers-storage:$id_xyz_image\"]" 200 + +t POST "/v3.4.0/libpod/manifests/abc:latest/push?destination=localhost:$REGISTRY_PORT%2Fabc:latest&tlsVerify=false&all=true" 200 +t POST "/v4.0.0/libpod/manifests/xyz:latest/registry/localhost:$REGISTRY_PORT%2Fxyz:latest?tlsVerify=false&all=true" 200 + # /v3.x cannot delete a manifest list t DELETE /v4.0.0/libpod/manifests/$id_abc 200 t DELETE /v4.0.0/libpod/manifests/$id_xyz 200 + +podman rmi -a +stop_registry diff --git a/test/apiv2/20-containers.at b/test/apiv2/20-containers.at index 4d32a1031..383c527b4 100644 --- a/test/apiv2/20-containers.at +++ b/test/apiv2/20-containers.at @@ -45,16 +45,16 @@ t GET libpod/containers/json?all=true 200 \ .[0].IsInfra=false # Test compat API for Network Settings (.Network is N/A when rootless) -network_expect= +network_expect="Networks=null" if root; then - network_expect='.[0].NetworkSettings.Networks.podman.NetworkID=podman' + network_expect="Networks.podman.NetworkID=podman" fi t GET /containers/json?all=true 200 \ length=1 \ .[0].Id~[0-9a-f]\\{64\\} \ .[0].Image=$IMAGE \ .[0].Mounts~.*/tmp \ - $network_expect + .[0].NetworkSettings.$network_expect # compat API imageid with sha256: prefix t GET containers/json?limit=1 200 \ @@ -239,6 +239,7 @@ t GET containers/$cid/json 200 \ t POST containers/create Image=$IMAGE Entrypoint='["top"]' 201 \ .Id~[0-9a-f]\\{64\\} cid_top=$(jq -r '.Id' <<<"$output") + t GET containers/${cid_top}/json 200 \ .Config.Entrypoint[0]="top" \ .Config.Cmd='[]' \ @@ -477,7 +478,8 @@ for endpoint in containers/create libpod/containers/create; do t POST libpod/containers/$cid/init 204 - t GET libpod/containers/$cid/json 200 + t GET libpod/containers/$cid/json 200 \ + .HostsPath="" t DELETE containers/$cid 204 done diff --git a/test/apiv2/60-auth.at b/test/apiv2/60-auth.at index 1e087d12b..465b0a96d 100644 --- a/test/apiv2/60-auth.at +++ b/test/apiv2/60-auth.at @@ -3,7 +3,7 @@ # registry-related tests # -start_registry +start_registry htpasswd # Test unreachable t POST /v1.40/auth username=$REGISTRY_USERNAME password=WrOnGPassWord serveraddress=does.not.exist.io:1234/ \ @@ -26,3 +26,5 @@ t POST /v1.40/auth username=$REGISTRY_USERNAME password=$REGISTRY_PASSWORD serve 200 \ .Status="Login Succeeded" \ .IdentityToken="" + +stop_registry diff --git a/test/apiv2/70-short-names.at b/test/apiv2/70-short-names.at index dbf816f55..bd7f8e7bd 100644 --- a/test/apiv2/70-short-names.at +++ b/test/apiv2/70-short-names.at @@ -128,7 +128,7 @@ t DELETE "containers/$cid" # disable the docker.io enforcement. stop_service -CONTAINERS_CONF=$(pwd)/test/apiv2/containers.conf start_service +CONTAINERS_CONF=$TESTS_DIR/containers.conf start_service t POST "images/create?fromImage=quay.io/libpod/alpine:latest" 200 .error~null .status~".*Download complete.*" t POST "images/alpine/tag?repo=foo" 201 diff --git a/test/apiv2/test-apiv2 b/test/apiv2/test-apiv2 index c3545522e..8ecc2aa2d 100755 --- a/test/apiv2/test-apiv2 +++ b/test/apiv2/test-apiv2 @@ -62,7 +62,7 @@ clean_up_server() { podman rm -a podman rmi -af - stop_registry + stop_registry --cleanup stop_service fi } @@ -87,6 +87,7 @@ trap err_handler ERR ######### function die() { echo "$ME: $*" >&2 + clean_up_server exit 1 } @@ -219,19 +220,19 @@ function jsonify() { function t() { local method=$1; shift local path=$1; shift - local curl_args + local -a curl_args local content_type="application/json" local testname="$method $path" - # POST requests may be followed by one or more key=value pairs. + # POST and PUT requests may be followed by one or more key=value pairs. # Slurp the command line until we see a 3-digit status code. - if [[ $method = "POST" ]]; then + if [[ $method = "POST" || $method == "PUT" ]]; then local -a post_args for arg; do case "$arg" in *=*) post_args+=("$arg"); shift;; - *.tar) curl_args="--data-binary @$arg" ; + *.tar) curl_args+=(--data-binary @$arg); content_type="application/x-tar"; shift;; application/*) content_type="$arg"; @@ -241,8 +242,8 @@ function t() { esac done if [[ -z "$curl_args" ]]; then - curl_args="-d $(jsonify ${post_args[@]})" - testname="$testname [$curl_args]" + curl_args=(-d $(jsonify ${post_args[@]})) + testname="$testname [${curl_args[@]}]" fi fi @@ -269,7 +270,7 @@ function t() { # curl -X HEAD but without --head seems to wait for output anyway if [[ $method == "HEAD" ]]; then - curl_args="--head" + curl_args+=("--head") fi local expected_code=$1; shift @@ -281,7 +282,7 @@ function t() { # -s = silent, but --write-out 'format' gives us important response data # The hairy "{ ...;rc=$?; } || :" lets us capture curl's exit code and # give a helpful diagnostic if it fails. - { response=$(curl -s -X $method ${curl_args} \ + { response=$(curl -s -X $method "${curl_args[@]}" \ -H "Content-type: $content_type" \ --dump-header $WORKDIR/curl.headers.out \ --write-out '%{http_code}^%{content_type}^%{time_total}' \ @@ -289,8 +290,7 @@ function t() { # Any error from curl is instant bad news, from which we can't recover if [[ $rc -ne 0 ]]; then - echo "FATAL: curl failure ($rc) on $url - cannot continue" >&2 - exit 1 + die "curl failure ($rc) on $url - cannot continue" fi # Show returned headers (without trailing ^M or empty lines) in log file. @@ -380,11 +380,6 @@ function start_service() { die "Cannot start service on non-localhost ($HOST)" fi - echo "rootdir: "$WORKDIR - # Some tests use shortnames; force registry override to work around - # docker.io throttling. -# FIXME esm revisit pulling expected images re: shortnames caused tests to fail -# env CONTAINERS_REGISTRIES_CONF=$TESTS_DIR/../registries.conf $PODMAN_BIN \ --root $WORKDIR/server_root --syslog=true \ system service \ @@ -411,15 +406,17 @@ REGISTRY_PORT= REGISTRY_USERNAME= REGISTRY_PASSWORD= function start_registry() { - # We can be invoked multiple times, e.g. from different subtests, but - # let's assume that once started we only kill it at the end of tests. + # We can be called multiple times, but each time should start a new + # registry container with (possibly) different configuration. That + # means that all callers must be responsible for invoking stop_registry. if [[ -n "$REGISTRY_PORT" ]]; then - return + die "start_registry invoked twice in succession, without stop_registry" fi + # First arg is auth type (default: "none", but can also be "htpasswd") + local auth="${1:-none}" + REGISTRY_PORT=$(random_port) - REGISTRY_USERNAME=u$(random_string 7) - REGISTRY_PASSWORD=p$(random_string 7) local REGDIR=$WORKDIR/registry local AUTHDIR=$REGDIR/auth @@ -433,24 +430,39 @@ function start_registry() { podman ${PODMAN_REGISTRY_ARGS} pull $REGISTRY_IMAGE || podman ${PODMAN_REGISTRY_ARGS} pull $REGISTRY_IMAGE - # Create a local cert and credentials - # FIXME: is there a hidden "--quiet" flag? This is too noisy. - openssl req -newkey rsa:4096 -nodes -sha256 \ - -keyout $AUTHDIR/domain.key -x509 -days 2 \ - -out $AUTHDIR/domain.crt \ - -subj "/C=US/ST=Foo/L=Bar/O=Red Hat, Inc./CN=registry host certificate" \ - -addext subjectAltName=DNS:localhost - htpasswd -Bbn ${REGISTRY_USERNAME} ${REGISTRY_PASSWORD} \ - > $AUTHDIR/htpasswd + # Create a local cert (no need to do this more than once) + if [[ ! -e $AUTHDIR/domain.key ]]; then + # FIXME: is there a hidden "--quiet" flag? This is too noisy. + openssl req -newkey rsa:4096 -nodes -sha256 \ + -keyout $AUTHDIR/domain.key -x509 -days 2 \ + -out $AUTHDIR/domain.crt \ + -subj "/C=US/ST=Foo/L=Bar/O=Red Hat, Inc./CN=registry host certificate" \ + -addext subjectAltName=DNS:localhost + fi + + # If invoked with auth=htpasswd, create credentials + REGISTRY_USERNAME= + REGISTRY_PASSWORD= + declare -a registry_auth_params=(-e "REGISTRY_AUTH=$auth") + if [[ "$auth" = "htpasswd" ]]; then + REGISTRY_USERNAME=u$(random_string 7) + REGISTRY_PASSWORD=p$(random_string 7) + + htpasswd -Bbn ${REGISTRY_USERNAME} ${REGISTRY_PASSWORD} \ + > $AUTHDIR/htpasswd + + registry_auth_params+=( + -e "REGISTRY_AUTH_HTPASSWD_REALM=Registry Realm" + -e "REGISTRY_AUTH_HTPASSWD_PATH=/auth/htpasswd" + ) + fi # Run the registry, and wait for it to come up podman ${PODMAN_REGISTRY_ARGS} run -d \ -p ${REGISTRY_PORT}:5000 \ --name registry \ -v $AUTHDIR:/auth:Z \ - -e "REGISTRY_AUTH=htpasswd" \ - -e "REGISTRY_AUTH_HTPASSWD_REALM=Registry Realm" \ - -e REGISTRY_AUTH_HTPASSWD_PATH=/auth/htpasswd \ + "${registry_auth_params[@]}" \ -e REGISTRY_HTTP_TLS_CERTIFICATE=/auth/domain.crt \ -e REGISTRY_HTTP_TLS_KEY=/auth/domain.key \ ${REGISTRY_IMAGE} @@ -462,13 +474,19 @@ function stop_registry() { local REGDIR=${WORKDIR}/registry if [[ -d $REGDIR ]]; then local OPTS="--root ${REGDIR}/root --runroot ${REGDIR}/runroot" - podman $OPTS stop -f -t 0 -a + podman $OPTS stop -i -t 0 registry # rm/rmi are important when running rootless: without them we # get EPERMS in tmpdir cleanup because files are owned by subuids. - podman $OPTS rm -f -a - podman $OPTS rmi -f -a + podman $OPTS rm -f -i registry + if [[ "$1" = "--cleanup" ]]; then + podman $OPTS rmi -f -a + fi fi + + REGISTRY_PORT= + REGISTRY_USERNAME= + REGISTRY_PASSWORD= } ################# diff --git a/test/buildah-bud/apply-podman-deltas b/test/buildah-bud/apply-podman-deltas index e2ca45728..6ff564aaa 100755 --- a/test/buildah-bud/apply-podman-deltas +++ b/test/buildah-bud/apply-podman-deltas @@ -193,21 +193,12 @@ skip_if_remote "volumes don't work with podman-remote" \ "buildah bud --volume" \ "buildah-bud-policy" -# Most of this should work in podman remote after API implementation other than where context is host. skip_if_remote "--build-context option not implemented in podman-remote" \ - "build-with-additional-build-context and COPY, test pinning image" \ - "build-with-additional-build-context and COPY, stagename and additional-context conflict" \ - "build-with-additional-build-context and COPY, additionalContext and numeric value of stage" \ - "build-with-additional-build-context and COPY, additionalContext and numeric value of stage" \ "build-with-additional-build-context and COPY, additional context from host" \ - "build-with-additional-build-context and COPY, additional context from external URL" \ - "build-with-additional-build-context and RUN --mount=from=, additional-context is URL and mounted from subdir" \ "build-with-additional-build-context and RUN --mount=from=, additional-context not image and also test conflict with stagename" \ - "build-with-additional-build-context and RUN --mount=from=, additional-context and also test conflict with stagename" \ - "bud-multiple-platform for --all-platform with additional-build-context" \ - "build-with-additional-build-context and FROM, stagename and additional-context conflict" \ + +skip_if_remote "env-variable for Containerfile.in pre-processing is not propogated on remote" \ "bud with Containerfile.in, via envariable" \ - "build-with-additional-build-context and FROM, pin busybox to alpine" # Requires a local file outside context dir skip_if_remote "local keyfile not sent to podman-remote" \ diff --git a/test/e2e/build_test.go b/test/e2e/build_test.go index 86dc76116..9ecc2f8c6 100644 --- a/test/e2e/build_test.go +++ b/test/e2e/build_test.go @@ -85,7 +85,7 @@ var _ = Describe("Podman build", func() { }) It("podman build with a secret from file and verify if secret file is not leaked into image", func() { - session := podmanTest.Podman([]string{"build", "-f", "build/secret-verify-leak/Containerfile.with-secret-verify-leak", "-t", "secret-test-leak", "--secret", "id=mysecret,src=build/secret.txt", "build/"}) + session := podmanTest.Podman([]string{"build", "-f", "build/secret-verify-leak/Containerfile.with-secret-verify-leak", "-t", "secret-test-leak", "--secret", "id=mysecret,src=build/secret.txt", "build/secret-verify-leak"}) session.WaitWithDefaultTimeout() Expect(session).Should(Exit(0)) Expect(session.OutputToString()).To(ContainSubstring("somesecret")) @@ -555,7 +555,7 @@ subdir**` dd := exec.Command("dd", "if=/dev/random", "of="+randomFile, "bs=1G", "count=1") ddSession, err := Start(dd, GinkgoWriter, GinkgoWriter) Expect(err).ToNot(HaveOccurred()) - Eventually(ddSession).Should(Exit(0)) + Eventually(ddSession, "10s", "1s").Should(Exit(0)) // make cwd as context root path Expect(os.Chdir(contextDir)).ToNot(HaveOccurred()) 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 019bb4617..c9ffe6a8d 100644 --- a/test/e2e/network_connect_disconnect_test.go +++ b/test/e2e/network_connect_disconnect_test.go @@ -2,7 +2,6 @@ package integration import ( "os" - "strings" . "github.com/containers/podman/v4/test/utils" "github.com/containers/storage/pkg/stringid" @@ -94,7 +93,7 @@ var _ = Describe("Podman network connect and disconnect", func() { exec2 := podmanTest.Podman([]string{"exec", "-it", "test", "cat", "/etc/resolv.conf"}) exec2.WaitWithDefaultTimeout() Expect(exec2).Should(Exit(0)) - Expect(strings.Contains(exec2.OutputToString(), ns)).To(BeTrue()) + Expect(exec2.OutputToString()).To(ContainSubstring(ns)) dis := podmanTest.Podman([]string{"network", "disconnect", netName, "test"}) dis.WaitWithDefaultTimeout() @@ -113,7 +112,7 @@ var _ = Describe("Podman network connect and disconnect", func() { exec3 := podmanTest.Podman([]string{"exec", "-it", "test", "cat", "/etc/resolv.conf"}) exec3.WaitWithDefaultTimeout() Expect(exec3).Should(Exit(0)) - Expect(strings.Contains(exec3.OutputToString(), ns)).To(BeFalse()) + Expect(exec3.OutputToString()).ToNot(ContainSubstring(ns)) // make sure stats still works https://github.com/containers/podman/issues/13824 stats := podmanTest.Podman([]string{"stats", "test", "--no-stream"}) @@ -211,7 +210,7 @@ var _ = Describe("Podman network connect and disconnect", func() { exec2 := podmanTest.Podman([]string{"exec", "-it", "test", "cat", "/etc/resolv.conf"}) exec2.WaitWithDefaultTimeout() Expect(exec2).Should(Exit(0)) - Expect(strings.Contains(exec2.OutputToString(), ns)).To(BeFalse()) + Expect(exec2.OutputToString()).ToNot(ContainSubstring(ns)) ip := "10.11.100.99" mac := "44:11:44:11:44:11" @@ -240,7 +239,7 @@ var _ = Describe("Podman network connect and disconnect", func() { exec3 := podmanTest.Podman([]string{"exec", "-it", "test", "cat", "/etc/resolv.conf"}) exec3.WaitWithDefaultTimeout() Expect(exec3).Should(Exit(0)) - Expect(strings.Contains(exec3.OutputToString(), ns)).To(BeTrue()) + Expect(exec3.OutputToString()).To(ContainSubstring(ns)) // make sure stats works https://github.com/containers/podman/issues/13824 stats := podmanTest.Podman([]string{"stats", "test", "--no-stream"}) 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_create_test.go b/test/e2e/pod_create_test.go index dedb1caeb..4919cc670 100644 --- a/test/e2e/pod_create_test.go +++ b/test/e2e/pod_create_test.go @@ -1112,4 +1112,26 @@ ENTRYPOINT ["sleep","99999"] }) + It("podman pod create infra inheritance test", func() { + volName := "testVol1" + volCreate := podmanTest.Podman([]string{"volume", "create", volName}) + volCreate.WaitWithDefaultTimeout() + Expect(volCreate).Should(Exit(0)) + + session := podmanTest.Podman([]string{"pod", "create", "-v", volName + ":/vol1"}) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + + volName2 := "testVol2" + volCreate = podmanTest.Podman([]string{"volume", "create", volName2}) + volCreate.WaitWithDefaultTimeout() + Expect(volCreate).Should(Exit(0)) + + session = podmanTest.Podman([]string{"run", "--pod", session.OutputToString(), "-v", volName2 + ":/vol2", ALPINE, "mount"}) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + Expect(session.OutputToString()).Should(ContainSubstring("/vol1")) + Expect(session.OutputToString()).Should(ContainSubstring("/vol2")) + }) + }) diff --git a/test/e2e/pod_infra_container_test.go b/test/e2e/pod_infra_container_test.go index ab204992c..20794a29c 100644 --- a/test/e2e/pod_infra_container_test.go +++ b/test/e2e/pod_infra_container_test.go @@ -125,6 +125,29 @@ 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", "--name", "hostCtr", "--pod", session.OutputToString(), ALPINE, "readlink", "/proc/self/ns/net"}) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + + ns := SystemExec("readlink", []string{"/proc/self/ns/net"}) + ns.WaitWithDefaultTimeout() + Expect(ns).Should(Exit(0)) + netns := ns.OutputToString() + Expect(netns).ToNot(BeEmpty()) + + Expect(session.OutputToString()).To(Equal(netns)) + + // Sanity Check for podman inspect + session = podmanTest.Podman([]string{"inspect", "--format", "'{{.NetworkSettings.SandboxKey}}'", "hostCtr"}) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + Expect(session.OutputToString()).Should(Equal("''")) // no network path... host + }) It("podman pod correctly sets up IPCNS", func() { diff --git a/test/e2e/run_privileged_test.go b/test/e2e/run_privileged_test.go index 4f0b512c6..dfaff7e67 100644 --- a/test/e2e/run_privileged_test.go +++ b/test/e2e/run_privileged_test.go @@ -131,6 +131,30 @@ var _ = Describe("Podman privileged container tests", func() { Expect(len(session.OutputToStringArray())).To(BeNumerically(">", 20)) }) + It("podman privileged should restart after host devices change", func() { + containerName := "privileged-restart-test" + SkipIfRootless("Cannot create devices in /dev in rootless mode") + Expect(os.MkdirAll("/dev/foodevdir", os.ModePerm)).To(BeNil()) + + mknod := SystemExec("mknod", []string{"/dev/foodevdir/null", "c", "1", "3"}) + mknod.WaitWithDefaultTimeout() + Expect(mknod).Should(Exit(0)) + + session := podmanTest.Podman([]string{"run", "--name=" + containerName, "--privileged", "-it", fedoraMinimal, "ls", "/dev"}) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + + deviceFiles := session.OutputToStringArray() + + os.RemoveAll("/dev/foodevdir") + session = podmanTest.Podman([]string{"start", "--attach", containerName}) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + + deviceFilesAfterRemoval := session.OutputToStringArray() + Expect(deviceFiles).To(Not(Equal(deviceFilesAfterRemoval))) + }) + It("run no-new-privileges test", func() { // Check if our kernel is new enough k, err := IsKernelNewerThan("4.14") diff --git a/test/e2e/run_volume_test.go b/test/e2e/run_volume_test.go index 3bef889b7..1c0480407 100644 --- a/test/e2e/run_volume_test.go +++ b/test/e2e/run_volume_test.go @@ -325,6 +325,51 @@ var _ = Describe("Podman run with volumes", func() { }) + It("podman support overlay volume with custom upperdir and workdir", func() { + SkipIfRemote("Overlay volumes only work locally") + if os.Getenv("container") != "" { + Skip("Overlay mounts not supported when running in a container") + } + if rootless.IsRootless() { + if _, err := exec.LookPath("fuse-overlayfs"); err != nil { + Skip("Fuse-Overlayfs required for rootless overlay mount test") + } + } + + // Use bindsource instead of named volume + bindSource := filepath.Join(tempdir, "bindsource") + err := os.Mkdir(bindSource, 0755) + Expect(err).To(BeNil(), "mkdir "+bindSource) + + // create persistent upperdir on host + upperDir := filepath.Join(tempdir, "upper") + err = os.Mkdir(upperDir, 0755) + Expect(err).To(BeNil(), "mkdir "+upperDir) + + // create persistent workdir on host + workDir := filepath.Join(tempdir, "work") + err = os.Mkdir(workDir, 0755) + Expect(err).To(BeNil(), "mkdir "+workDir) + + overlayOpts := fmt.Sprintf("upperdir=%s,workdir=%s", upperDir, workDir) + + // create file on overlay volume + session := podmanTest.Podman([]string{"run", "--volume", bindSource + ":/data:O," + overlayOpts, ALPINE, "sh", "-c", "echo hello >> " + "/data/overlay"}) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + + session = podmanTest.Podman([]string{"run", "--volume", bindSource + ":/data:O," + overlayOpts, ALPINE, "sh", "-c", "ls /data"}) + session.WaitWithDefaultTimeout() + // must contain `overlay` file since it should be persistent on specified upper and workdir + Expect(session.OutputToString()).To(ContainSubstring("overlay")) + + session = podmanTest.Podman([]string{"run", "--volume", bindSource + ":/data:O", ALPINE, "sh", "-c", "ls /data"}) + session.WaitWithDefaultTimeout() + // must not contain `overlay` file which was on custom upper and workdir since we have not specified any upper or workdir + Expect(session.OutputToString()).To(Not(ContainSubstring("overlay"))) + + }) + It("podman run with noexec can't exec", func() { session := podmanTest.Podman([]string{"run", "--rm", "-v", "/bin:/hostbin:noexec", ALPINE, "/hostbin/ls", "/"}) session.WaitWithDefaultTimeout() 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/170-run-userns.bats b/test/system/170-run-userns.bats index 46cb37b9d..84788a7f4 100644 --- a/test/system/170-run-userns.bats +++ b/test/system/170-run-userns.bats @@ -111,15 +111,30 @@ EOF } @test "podman userns=nomap" { - skip_if_not_rootless "--userns=nomap only works in rootless mode" - ns_user=$(id -un) - baseuid=$(egrep "${ns_user}:" /etc/subuid | cut -f2 -d:) - test ! -z ${baseuid} || skip "no IDs allocated for user ${ns_user}" + if is_rootless; then + ns_user=$(id -un) + baseuid=$(egrep "${ns_user}:" /etc/subuid | cut -f2 -d:) + test ! -z ${baseuid} || skip "no IDs allocated for user ${ns_user}" + + test_name="test_$(random_string 12)" + run_podman run -d --userns=nomap $IMAGE sleep 100 + cid=${output} + run_podman top ${cid} huser + is "${output}" "HUSER.*${baseuid}" "Container should start with baseuid from /etc/subuid not user UID" + run_podman rm -t 0 --force ${cid} + else + run_podman 125 run -d --userns=nomap $IMAGE sleep 100 + is "${output}" "Error: nomap is only supported in rootless mode" "Container should fail to start since nomap is not suppored in rootful mode" + fi +} - test_name="test_$(random_string 12)" - run_podman run -d --userns=nomap $IMAGE sleep 100 - cid=${output} - run_podman top ${cid} huser - is "${output}" "HUSER.*${baseuid}" "Container should start with baseuid from /etc/subuid not user UID" - run_podman rm -t 0 --force ${cid} +@test "podman userns=keep-id" { + if is_rootless; then + user=$(id -u) + run_podman run --rm --userns=keep-id $IMAGE id -u + is "${output}" "$user" "Container should run as the current user" + else + run_podman 125 run --rm --userns=keep-id $IMAGE id -u + is "${output}" "Error: keep-id is only supported in rootless mode" "Container should fail to start since keep-id is not suppored in rootful mode" + fi } diff --git a/test/system/500-networking.bats b/test/system/500-networking.bats index 3db0804d1..92aabae32 100644 --- a/test/system/500-networking.bats +++ b/test/system/500-networking.bats @@ -111,6 +111,10 @@ load helpers $IMAGE nc -l -n -v -p $myport cid="$output" + # check that podman stores the network info correctly when a userns is used (#14465) + run_podman container inspect --format "{{.NetworkSettings.SandboxKey}}" $cid + assert "$output" =~ ".*/netns/netns-.*" "Netns path should be set" + wait_for_output "listening on .*:$myport .*" $cid # emit random string, and check it diff --git a/test/system/520-checkpoint.bats b/test/system/520-checkpoint.bats index c16a8c35d..7f60f01b3 100644 --- a/test/system/520-checkpoint.bats +++ b/test/system/520-checkpoint.bats @@ -170,4 +170,34 @@ function teardown() { # FIXME: test --leave-running +@test "podman checkpoint --file-locks" { + action='flock test.lock sh -c "while [ -e /wait ];do sleep 0.5;done;for i in 1 2 3;do echo \$i;sleep 0.5;done"' + run_podman run -d $IMAGE sh -c "touch /wait; touch test.lock; echo READY; $action & $action & wait" + local cid="$output" + + # Wait for container to start emitting output + wait_for_ready $cid + + # Checkpoint, and confirm via inspect + run_podman container checkpoint --file-locks $cid + is "$output" "$cid" "podman container checkpoint" + + run_podman container inspect \ + --format '{{.State.Status}}:{{.State.Running}}:{{.State.Paused}}:{{.State.Checkpointed}}' $cid + is "$output" "exited:false:false:true" "State. Status:Running:Pause:Checkpointed" + + # Restart immediately and confirm state + run_podman container restore --file-locks $cid + is "$output" "$cid" "podman container restore" + + # Signal the container to continue; this is where the 1-2-3s will come from + run_podman exec $cid rm /wait + + # Wait for the container to stop + run_podman wait $cid + + run_podman logs $cid + trim=$(sed -z -e 's/[\r\n]\+//g' <<<"$output") + is "$trim" "READY123123" "File lock restored" +} # vim: filetype=sh diff --git a/test/system/600-completion.bats b/test/system/600-completion.bats index 018e95e78..cb4a2c5f8 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,51 @@ 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/\$.*" "etc directory suggested (cmd: podman $cmd)" + assert "$output" =~ ".*^/home/\$.*" "home directory suggested (cmd: podman $cmd)" + assert "$output" =~ ".*^/root/\$.*" "root directory 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/\$.*" "relative etc directory suggested (cmd: podman $cmd ../../)" + assert "$output" =~ ".*^../../home/\$.*" "relative home directory suggested (cmd: podman $cmd ../../)" + done + + random_name=$(random_string 30) + random_file=$(random_string 30) + run_podman run --name $random_name $IMAGE sh -c "touch /tmp/$random_file && touch /tmp/${random_file}2 && mkdir /emptydir" + + # 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" + + run_completion cp "$random_name:/tmp/$random_file" + assert "$output" =~ ".*^$random_name\:/tmp/$random_file\$.*" "podman cp suggest /tmp/$random_file file in container" + assert "$output" =~ ".*^$random_name\:/tmp/${random_file}2\$.*" "podman cp suggest /tmp/${random_file}2 file in container" + + run_completion cp "$random_name:/emptydir" + assert "$output" =~ ".*^$random_name\:/emptydir/\$.*ShellCompDirectiveNoSpace" "podman cp suggest empty dir with no space directive (:2)" + + # 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/github.com/containers/common/libimage/inspect.go b/vendor/github.com/containers/common/libimage/inspect.go index 05d60edfc..ae06acd2c 100644 --- a/vendor/github.com/containers/common/libimage/inspect.go +++ b/vendor/github.com/containers/common/libimage/inspect.go @@ -128,7 +128,7 @@ func (i *Image) Inspect(ctx context.Context, options *InspectOptions) (*ImageDat Config: &ociImage.Config, Version: info.DockerVersion, Size: size, - VirtualSize: size, // TODO: they should be different (inherited from Podman) + VirtualSize: size, // NOTE: same as size. Inherited from Docker where it's scheduled for deprecation. Digest: i.Digest(), Labels: info.Labels, RootFS: &RootFS{ diff --git a/vendor/github.com/containers/common/libimage/pull.go b/vendor/github.com/containers/common/libimage/pull.go index 771756160..d204ef1c4 100644 --- a/vendor/github.com/containers/common/libimage/pull.go +++ b/vendor/github.com/containers/common/libimage/pull.go @@ -533,9 +533,6 @@ func (r *Runtime) copySingleImageFromRegistry(ctx context.Context, imageName str sys := r.systemContextCopy() resolved, err := shortnames.Resolve(sys, imageName) if err != nil { - // TODO: that is a too big of a hammer since we should only - // ignore errors that indicate that there's no alias and no - // USRs. Must be addressed in c/image first. if localImage != nil && pullPolicy == config.PullPolicyNewer { return []string{resolvedImageName}, nil } diff --git a/vendor/github.com/containers/common/libnetwork/resolvconf/resolv.go b/vendor/github.com/containers/common/libnetwork/resolvconf/resolv.go new file mode 100644 index 000000000..c451d3b49 --- /dev/null +++ b/vendor/github.com/containers/common/libnetwork/resolvconf/resolv.go @@ -0,0 +1,182 @@ +package resolvconf + +import ( + "errors" + "fmt" + "os" + "path/filepath" + "strings" + + "github.com/containers/common/pkg/util" + "github.com/opencontainers/runtime-spec/specs-go" + "github.com/sirupsen/logrus" +) + +const ( + localhost = "127.0.0.1" + systemdResolvedIP = "127.0.0.53" +) + +// Params for the New() function. +type Params struct { + // Path is the path to new resolv.conf file which should be created. + Path string + // Namespaces is the list of container namespaces. + // This is required to fist check for a resolv.conf under /etc/netns, + // created by "ip netns". Also used to check if the container has a + // netns in which case localhost nameserver must be filtered. + Namespaces []specs.LinuxNamespace + // IPv6Enabled will filter ipv6 nameservers when not set to true. + IPv6Enabled bool + // KeepHostServers can be set when it is required to still keep the + // original resolv.conf content even when custom Nameserver/Searches/Options + // are set. In this case they will be appended to the given values. + KeepHostServers bool + // Nameservers is a list of nameservers the container should use, + // instead of the default ones from the host. + Nameservers []string + // Searches is a list of dns search domains the container should use, + // instead of the default ones from the host. + Searches []string + // Options is a list of dns options the container should use, + // instead of the default ones from the host. + Options []string + + // resolvConfPath is the path which should be used as base to get the dns + // options. This should only be used for testing purposes. For all other + // callers this defaults to /etc/resolv.conf. + resolvConfPath string +} + +func getDefaultResolvConf(params *Params) ([]byte, bool, error) { + resolveConf := DefaultResolvConf + // this is only used by testing + if params.resolvConfPath != "" { + resolveConf = params.resolvConfPath + } + hostNS := true + for _, ns := range params.Namespaces { + if ns.Type == specs.NetworkNamespace { + hostNS = false + if ns.Path != "" && !strings.HasPrefix(ns.Path, "/proc/") { + // check for netns created by "ip netns" + path := filepath.Join("/etc/netns", filepath.Base(ns.Path), "resolv.conf") + _, err := os.Stat(path) + if err == nil { + resolveConf = path + } + } + break + } + } + + contents, err := os.ReadFile(resolveConf) + if err != nil && !errors.Is(err, os.ErrNotExist) { + return nil, false, err + } + if hostNS { + return contents, hostNS, nil + } + + ns := getNameservers(contents) + // Check for local only resolver, in this case we want to get the real nameservers + // since localhost is not reachable from the netns. + if len(ns) == 1 { + var path string + switch ns[0] { + case systemdResolvedIP: + // used by systemd-resolved + path = "/run/systemd/resolve/resolv.conf" + case localhost: + // used by NetworkManager https://github.com/containers/podman/issues/13599 + path = "/run/NetworkManager/no-stub-resolv.conf" + } + if path != "" { + // read the actual resolv.conf file for + resolvedContents, err := os.ReadFile(path) + if err != nil { + // do not error when the file does not exists, the detection logic is not perfect + if !errors.Is(err, os.ErrNotExist) { + return nil, false, fmt.Errorf("local resolver detected, but could not read real resolv.conf at %q: %w", path, err) + } + } else { + logrus.Debugf("found local resolver, using %q to get the nameservers", path) + contents = resolvedContents + } + } + } + + return contents, hostNS, nil +} + +// unsetSearchDomainsIfNeeded removes the search domain when they contain a single dot as element. +func unsetSearchDomainsIfNeeded(searches []string) []string { + if util.StringInSlice(".", searches) { + return nil + } + return searches +} + +// New creates a new resolv.conf file with the given params. +func New(params *Params) error { + // short path, if everything is given there is no need to actually read the hosts /etc/resolv.conf + if len(params.Nameservers) > 0 && len(params.Options) > 0 && len(params.Searches) > 0 && !params.KeepHostServers { + return build(params.Path, params.Nameservers, unsetSearchDomainsIfNeeded(params.Searches), params.Options) + } + + content, hostNS, err := getDefaultResolvConf(params) + if err != nil { + return fmt.Errorf("failed to get the default /etc/resolv.conf content: %w", err) + } + + content = filterResolvDNS(content, params.IPv6Enabled, !hostNS) + + nameservers := params.Nameservers + if len(nameservers) == 0 || params.KeepHostServers { + nameservers = append(nameservers, getNameservers(content)...) + } + + searches := unsetSearchDomainsIfNeeded(params.Searches) + // if no params.Searches then use host ones + // otherwise make sure that they were no explicitly unset before adding host ones + if len(params.Searches) == 0 || (params.KeepHostServers && len(searches) > 0) { + searches = append(searches, getSearchDomains(content)...) + } + + options := params.Options + if len(options) == 0 || params.KeepHostServers { + options = append(options, getOptions(content)...) + } + + return build(params.Path, nameservers, searches, options) +} + +// Add will add the given nameservers to the given resolv.conf file. +// It will add the nameserver in front of the existing ones. +func Add(path string, nameservers []string) error { + contents, err := os.ReadFile(path) + if err != nil { + return err + } + + nameservers = append(nameservers, getNameservers(contents)...) + return build(path, nameservers, getSearchDomains(contents), getOptions(contents)) +} + +// Remove the given nameserver from the given resolv.conf file. +func Remove(path string, nameservers []string) error { + contents, err := os.ReadFile(path) + if err != nil { + return err + } + + oldNameservers := getNameservers(contents) + newNameserver := make([]string, 0, len(oldNameservers)) + for _, ns := range oldNameservers { + if !util.StringInSlice(ns, nameservers) { + newNameserver = append(newNameserver, ns) + } + } + + return build(path, newNameserver, getSearchDomains(contents), getOptions(contents)) +} diff --git a/pkg/resolvconf/resolvconf.go b/vendor/github.com/containers/common/libnetwork/resolvconf/resolvconf.go index f23cd61b0..54b8c3227 100644 --- a/pkg/resolvconf/resolvconf.go +++ b/vendor/github.com/containers/common/libnetwork/resolvconf/resolvconf.go @@ -1,26 +1,23 @@ // Package resolvconf provides utility code to query and update DNS configuration in /etc/resolv.conf. -// Originally from github.com/docker/libnetwork/resolvconf. +// Originally from github.com/docker/libnetwork/resolvconf but heavily modified to better work with podman. package resolvconf import ( "bytes" - "io/ioutil" + "os" "regexp" "strings" - "sync" - "github.com/containers/podman/v4/pkg/resolvconf/dns" - "github.com/containers/storage/pkg/ioutils" "github.com/sirupsen/logrus" ) const ( - // DefaultResolvConf points to the default file used for dns configuration on a linux machine + // DefaultResolvConf points to the default file used for dns configuration on a linux machine. DefaultResolvConf = "/etc/resolv.conf" ) var ( - // Note: the default IPv4 & IPv6 resolvers are set to Google's Public DNS + // Note: the default IPv4 & IPv6 resolvers are set to Google's Public DNS. defaultIPv4Dns = []string{"nameserver 8.8.8.8", "nameserver 8.8.4.4"} defaultIPv6Dns = []string{"nameserver 2001:4860:4860::8888", "nameserver 2001:4860:4860::8844"} ipv4NumBlock = `(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)` @@ -29,94 +26,30 @@ var ( // will *not match* IPv4-Embedded IPv6 Addresses (RFC6052), but that and other variants // -- e.g. other link-local types -- either won't work in containers or are unnecessary. // For readability and sufficiency for Docker purposes this seemed more reasonable than a - // 1000+ character regexp with exact and complete IPv6 validation + // 1000+ character regexp with exact and complete IPv6 validation. ipv6Address = `([0-9A-Fa-f]{0,4}:){2,7}([0-9A-Fa-f]{0,4})(%\w+)?` - localhostNSRegexp = regexp.MustCompile(`(?m)^nameserver\s+` + dns.IPLocalhost + `\s*\n*`) + // ipLocalhost is a regex pattern for IPv4 or IPv6 loopback range. + ipLocalhost = `((127\.([0-9]{1,3}\.){2}[0-9]{1,3})|(::1)$)` + + localhostNSRegexp = regexp.MustCompile(`(?m)^nameserver\s+` + ipLocalhost + `\s*\n*`) nsIPv6Regexp = regexp.MustCompile(`(?m)^nameserver\s+` + ipv6Address + `\s*\n*`) nsRegexp = regexp.MustCompile(`^\s*nameserver\s*((` + ipv4Address + `)|(` + ipv6Address + `))\s*$`) searchRegexp = regexp.MustCompile(`^\s*search\s*(([^\s]+\s*)*)$`) optionsRegexp = regexp.MustCompile(`^\s*options\s*(([^\s]+\s*)*)$`) ) -var lastModified struct { - sync.Mutex - sha256 string - contents []byte -} - -// File contains the resolv.conf content and its hash -type File struct { - Content []byte - Hash string -} - -// Get returns the contents of /etc/resolv.conf and its hash -func Get() (*File, error) { - return GetSpecific(DefaultResolvConf) -} - -// GetSpecific returns the contents of the user specified resolv.conf file and its hash -func GetSpecific(path string) (*File, error) { - resolv, err := ioutil.ReadFile(path) - if err != nil { - return nil, err - } - hash, err := ioutils.HashData(bytes.NewReader(resolv)) - if err != nil { - return nil, err - } - return &File{Content: resolv, Hash: hash}, nil -} - -// GetIfChanged retrieves the host /etc/resolv.conf file, checks against the last hash -// and, if modified since last check, returns the bytes and new hash. -// This feature is used by the resolv.conf updater for containers -func GetIfChanged() (*File, error) { - lastModified.Lock() - defer lastModified.Unlock() - - resolv, err := ioutil.ReadFile("/etc/resolv.conf") - if err != nil { - return nil, err - } - newHash, err := ioutils.HashData(bytes.NewReader(resolv)) - if err != nil { - return nil, err - } - if lastModified.sha256 != newHash { - lastModified.sha256 = newHash - lastModified.contents = resolv - return &File{Content: resolv, Hash: newHash}, nil - } - // nothing changed, so return no data - return nil, nil -} - -// GetLastModified retrieves the last used contents and hash of the host resolv.conf. -// Used by containers updating on restart -func GetLastModified() *File { - lastModified.Lock() - defer lastModified.Unlock() - - return &File{Content: lastModified.contents, Hash: lastModified.sha256} -} - -// FilterResolvDNS cleans up the config in resolvConf. It has two main jobs: +// filterResolvDNS cleans up the config in resolvConf. It has two main jobs: // 1. If a netns is enabled, it looks for localhost (127.*|::1) entries in the provided // resolv.conf, removing local nameserver entries, and, if the resulting // cleaned config has no defined nameservers left, adds default DNS entries // 2. Given the caller provides the enable/disable state of IPv6, the filter // code will remove all IPv6 nameservers if it is not enabled for containers // -func FilterResolvDNS(resolvConf []byte, ipv6Enabled bool, netnsEnabled bool) (*File, error) { +func filterResolvDNS(resolvConf []byte, ipv6Enabled bool, netnsEnabled bool) []byte { // If we're using the host netns, we have nothing to do besides hash the file. if !netnsEnabled { - hash, err := ioutils.HashData(bytes.NewReader(resolvConf)) - if err != nil { - return nil, err - } - return &File{Content: resolvConf, Hash: hash}, nil + return resolvConf } cleanedResolvConf := localhostNSRegexp.ReplaceAll(resolvConf, []byte{}) // if IPv6 is not enabled, also clean out any IPv6 address nameserver @@ -125,7 +58,7 @@ func FilterResolvDNS(resolvConf []byte, ipv6Enabled bool, netnsEnabled bool) (*F } // if the resulting resolvConf has no more nameservers defined, add appropriate // default DNS servers for IPv4 and (optionally) IPv6 - if len(GetNameservers(cleanedResolvConf)) == 0 { + if len(getNameservers(cleanedResolvConf)) == 0 { logrus.Infof("No non-localhost DNS nameservers are left in resolv.conf. Using default external servers: %v", defaultIPv4Dns) dns := defaultIPv4Dns if ipv6Enabled { @@ -134,19 +67,15 @@ func FilterResolvDNS(resolvConf []byte, ipv6Enabled bool, netnsEnabled bool) (*F } cleanedResolvConf = append(cleanedResolvConf, []byte("\n"+strings.Join(dns, "\n"))...) } - hash, err := ioutils.HashData(bytes.NewReader(cleanedResolvConf)) - if err != nil { - return nil, err - } - return &File{Content: cleanedResolvConf, Hash: hash}, nil + return cleanedResolvConf } // getLines parses input into lines and strips away comments. -func getLines(input []byte, commentMarker []byte) [][]byte { +func getLines(input []byte) [][]byte { lines := bytes.Split(input, []byte("\n")) var output [][]byte for _, currentLine := range lines { - var commentIndex = bytes.Index(currentLine, commentMarker) + commentIndex := bytes.Index(currentLine, []byte("#")) if commentIndex == -1 { output = append(output, currentLine) } else { @@ -156,10 +85,10 @@ func getLines(input []byte, commentMarker []byte) [][]byte { return output } -// GetNameservers returns nameservers (if any) listed in /etc/resolv.conf -func GetNameservers(resolvConf []byte) []string { +// getNameservers returns nameservers (if any) listed in /etc/resolv.conf. +func getNameservers(resolvConf []byte) []string { nameservers := []string{} - for _, line := range getLines(resolvConf, []byte("#")) { + for _, line := range getLines(resolvConf) { ns := nsRegexp.FindSubmatch(line) if len(ns) > 0 { nameservers = append(nameservers, string(ns[1])) @@ -168,30 +97,12 @@ func GetNameservers(resolvConf []byte) []string { return nameservers } -// GetNameserversAsCIDR returns nameservers (if any) listed in -// /etc/resolv.conf as CIDR blocks (e.g., "1.2.3.4/32") -// This function's output is intended for net.ParseCIDR -func GetNameserversAsCIDR(resolvConf []byte) []string { - nameservers := []string{} - for _, nameserver := range GetNameservers(resolvConf) { - var address string - // If IPv6, strip zone if present - if strings.Contains(nameserver, ":") { - address = strings.Split(nameserver, "%")[0] + "/128" - } else { - address = nameserver + "/32" - } - nameservers = append(nameservers, address) - } - return nameservers -} - -// GetSearchDomains returns search domains (if any) listed in /etc/resolv.conf +// getSearchDomains returns search domains (if any) listed in /etc/resolv.conf // If more than one search line is encountered, only the contents of the last // one is returned. -func GetSearchDomains(resolvConf []byte) []string { +func getSearchDomains(resolvConf []byte) []string { domains := []string{} - for _, line := range getLines(resolvConf, []byte("#")) { + for _, line := range getLines(resolvConf) { match := searchRegexp.FindSubmatch(line) if match == nil { continue @@ -201,12 +112,12 @@ func GetSearchDomains(resolvConf []byte) []string { return domains } -// GetOptions returns options (if any) listed in /etc/resolv.conf +// getOptions returns options (if any) listed in /etc/resolv.conf // If more than one options line is encountered, only the contents of the last // one is returned. -func GetOptions(resolvConf []byte) []string { +func getOptions(resolvConf []byte) []string { options := []string{} - for _, line := range getLines(resolvConf, []byte("#")) { + for _, line := range getLines(resolvConf) { match := optionsRegexp.FindSubmatch(line) if match == nil { continue @@ -216,35 +127,30 @@ func GetOptions(resolvConf []byte) []string { return options } -// Build writes a configuration file to path containing a "nameserver" entry +// build writes a configuration file to path containing a "nameserver" entry // for every element in dns, a "search" entry for every element in // dnsSearch, and an "options" entry for every element in dnsOptions. -func Build(path string, dns, dnsSearch, dnsOptions []string) (*File, error) { - content := bytes.NewBuffer(nil) +func build(path string, dns, dnsSearch, dnsOptions []string) error { + content := new(bytes.Buffer) if len(dnsSearch) > 0 { if searchString := strings.Join(dnsSearch, " "); strings.Trim(searchString, " ") != "." { if _, err := content.WriteString("search " + searchString + "\n"); err != nil { - return nil, err + return err } } } for _, dns := range dns { if _, err := content.WriteString("nameserver " + dns + "\n"); err != nil { - return nil, err + return err } } if len(dnsOptions) > 0 { if optsString := strings.Join(dnsOptions, " "); strings.Trim(optsString, " ") != "" { if _, err := content.WriteString("options " + optsString + "\n"); err != nil { - return nil, err + return err } } } - hash, err := ioutils.HashData(bytes.NewReader(content.Bytes())) - if err != nil { - return nil, err - } - - return &File{Content: content.Bytes(), Hash: hash}, ioutil.WriteFile(path, content.Bytes(), 0644) + return os.WriteFile(path, content.Bytes(), 0o644) } diff --git a/vendor/github.com/docker/docker/api/swagger.yaml b/vendor/github.com/docker/docker/api/swagger.yaml index b6bca4cef..0bbe74700 100644 --- a/vendor/github.com/docker/docker/api/swagger.yaml +++ b/vendor/github.com/docker/docker/api/swagger.yaml @@ -3347,7 +3347,7 @@ definitions: Limits: description: "Define resources limits." $ref: "#/definitions/Limit" - Reservation: + Reservations: description: "Define resources reservation." $ref: "#/definitions/ResourceObject" RestartPolicy: diff --git a/vendor/github.com/docker/docker/pkg/system/mknod.go b/vendor/github.com/docker/docker/pkg/system/mknod.go index 5734d47d4..d27152c0f 100644 --- a/vendor/github.com/docker/docker/pkg/system/mknod.go +++ b/vendor/github.com/docker/docker/pkg/system/mknod.go @@ -7,12 +7,6 @@ import ( "golang.org/x/sys/unix" ) -// Mknod creates a filesystem node (file, device special file or named pipe) named path -// with attributes specified by mode and dev. -func Mknod(path string, mode uint32, dev int) error { - return unix.Mknod(path, mode, dev) -} - // Mkdev is used to build the value of linux devices (in /dev/) which specifies major // and minor number of the newly created device special file. // Linux device nodes are a bit weird due to backwards compat with 16 bit device nodes. diff --git a/vendor/github.com/docker/docker/pkg/system/mknod_freebsd.go b/vendor/github.com/docker/docker/pkg/system/mknod_freebsd.go new file mode 100644 index 000000000..c890be116 --- /dev/null +++ b/vendor/github.com/docker/docker/pkg/system/mknod_freebsd.go @@ -0,0 +1,14 @@ +//go:build freebsd +// +build freebsd + +package system // import "github.com/docker/docker/pkg/system" + +import ( + "golang.org/x/sys/unix" +) + +// Mknod creates a filesystem node (file, device special file or named pipe) named path +// with attributes specified by mode and dev. +func Mknod(path string, mode uint32, dev int) error { + return unix.Mknod(path, mode, uint64(dev)) +} diff --git a/vendor/github.com/docker/docker/pkg/system/mknod_unix.go b/vendor/github.com/docker/docker/pkg/system/mknod_unix.go new file mode 100644 index 000000000..4586aad19 --- /dev/null +++ b/vendor/github.com/docker/docker/pkg/system/mknod_unix.go @@ -0,0 +1,14 @@ +//go:build !freebsd && !windows +// +build !freebsd,!windows + +package system // import "github.com/docker/docker/pkg/system" + +import ( + "golang.org/x/sys/unix" +) + +// Mknod creates a filesystem node (file, device special file or named pipe) named path +// with attributes specified by mode and dev. +func Mknod(path string, mode uint32, dev int) error { + return unix.Mknod(path, mode, dev) +} diff --git a/vendor/github.com/seccomp/libseccomp-golang/.golangci.yml b/vendor/github.com/seccomp/libseccomp-golang/.golangci.yml new file mode 100644 index 000000000..7df8aa198 --- /dev/null +++ b/vendor/github.com/seccomp/libseccomp-golang/.golangci.yml @@ -0,0 +1,4 @@ +# For documentation, see https://golangci-lint.run/usage/configuration/ +linters: + enable: + - gofumpt diff --git a/vendor/github.com/seccomp/libseccomp-golang/.travis.yml b/vendor/github.com/seccomp/libseccomp-golang/.travis.yml deleted file mode 100644 index 5240d4622..000000000 --- a/vendor/github.com/seccomp/libseccomp-golang/.travis.yml +++ /dev/null @@ -1,57 +0,0 @@ -# Travis CI configuration for libseccomp-golang - -# https://docs.travis-ci.com/user/reference/bionic -# https://wiki.ubuntu.com/Releases - -dist: bionic -sudo: false - -notifications: - email: - on_success: always - on_failure: always - -arch: - - amd64 - -os: - - linux - -language: go - -jobs: - include: - - name: "last libseccomp 2.5.0" - env: - - SECCOMP_VER=2.5.0 - - SECCOMP_SHA256SUM=1ffa7038d2720ad191919816db3479295a4bcca1ec14e02f672539f4983014f3 - - name: "compat libseccomp 2.4.4" - env: - - SECCOMP_VER=2.4.4 - - SECCOMP_SHA256SUM=4e79738d1ef3c9b7ca9769f1f8b8d84fc17143c2c1c432e53b9c64787e0ff3eb - - name: "compat libseccomp 2.2.1" - env: - - SECCOMP_VER=2.2.1 - - SECCOMP_SHA256SUM=0ba1789f54786c644af54cdffc9fd0dd0a8bb2b2ee153933f658855d2851a740 - -addons: - apt: - packages: - - build-essential - - astyle - - golint - - gperf - -install: - - go get -u golang.org/x/lint/golint - -# run all of the tests independently, fail if any of the tests error -script: - - wget https://github.com/seccomp/libseccomp/releases/download/v$SECCOMP_VER/libseccomp-$SECCOMP_VER.tar.gz - - echo $SECCOMP_SHA256SUM libseccomp-$SECCOMP_VER.tar.gz | sha256sum -c - - tar xf libseccomp-$SECCOMP_VER.tar.gz - - pushd libseccomp-$SECCOMP_VER && ./configure --prefix=/opt/libseccomp-$SECCOMP_VER && make && sudo make install && popd - - make check-syntax - - make lint - - PKG_CONFIG_PATH=/opt/libseccomp-$SECCOMP_VER/lib/pkgconfig LD_LIBRARY_PATH=/opt/libseccomp-$SECCOMP_VER/lib make vet - - PKG_CONFIG_PATH=/opt/libseccomp-$SECCOMP_VER/lib/pkgconfig LD_LIBRARY_PATH=/opt/libseccomp-$SECCOMP_VER/lib make test diff --git a/vendor/github.com/seccomp/libseccomp-golang/CONTRIBUTING.md b/vendor/github.com/seccomp/libseccomp-golang/CONTRIBUTING.md index d6862cbd5..c2fc80d5a 100644 --- a/vendor/github.com/seccomp/libseccomp-golang/CONTRIBUTING.md +++ b/vendor/github.com/seccomp/libseccomp-golang/CONTRIBUTING.md @@ -1,31 +1,23 @@ -How to Submit Patches to the libseccomp Project +How to Submit Patches to the libseccomp-golang Project =============================================================================== https://github.com/seccomp/libseccomp-golang This document is intended to act as a guide to help you contribute to the -libseccomp project. It is not perfect, and there will always be exceptions -to the rules described here, but by following the instructions below you -should have a much easier time getting your work merged with the upstream +libseccomp-golang project. It is not perfect, and there will always be +exceptions to the rules described here, but by following the instructions below +you should have a much easier time getting your work merged with the upstream project. ## Test Your Code Using Existing Tests -There are two possible tests you can run to verify your code. The first -test is used to check the formatting and coding style of your changes, you -can run the test with the following command: - - # make check-syntax - -... if there are any problems with your changes a diff/patch will be shown -which indicates the problems and how to fix them. - -The second possible test is used to ensure the sanity of your code changes -and to test these changes against the included tests. You can run the test -with the following command: +A number of tests and lint related recipes are provided in the Makefile, if +you want to run the standard regression tests, you can execute the following: # make check -... if there are any faults or errors they will be displayed. +In order to use it, the 'golangci-lint' tool is needed, which can be found at: + +* https://github.com/golangci/golangci-lint ## Add New Tests for New Functionality diff --git a/vendor/github.com/seccomp/libseccomp-golang/Makefile b/vendor/github.com/seccomp/libseccomp-golang/Makefile index 38cfa852c..530f5b4ad 100644 --- a/vendor/github.com/seccomp/libseccomp-golang/Makefile +++ b/vendor/github.com/seccomp/libseccomp-golang/Makefile @@ -4,7 +4,7 @@ all: check-build -check: vet test +check: lint test check-build: go build @@ -16,7 +16,7 @@ fix-syntax: gofmt -w . vet: - go vet -v + go vet -v ./... # Previous bugs have made the tests freeze until the timeout. Golang default # timeout for tests is 10 minutes, which is too long, considering current tests @@ -28,5 +28,4 @@ test: go test -v -timeout $(TEST_TIMEOUT) lint: - @$(if $(shell which golint),true,$(error "install golint and include it in your PATH")) - golint -set_exit_status + golangci-lint run . diff --git a/vendor/github.com/seccomp/libseccomp-golang/README.md b/vendor/github.com/seccomp/libseccomp-golang/README.md index 806a5ddf2..6430f1c9e 100644 --- a/vendor/github.com/seccomp/libseccomp-golang/README.md +++ b/vendor/github.com/seccomp/libseccomp-golang/README.md @@ -2,7 +2,9 @@ =============================================================================== https://github.com/seccomp/libseccomp-golang -[![Build Status](https://img.shields.io/travis/seccomp/libseccomp-golang/main.svg)](https://travis-ci.org/seccomp/libseccomp-golang) +[![Go Reference](https://pkg.go.dev/badge/github.com/seccomp/libseccomp-golang.svg)](https://pkg.go.dev/github.com/seccomp/libseccomp-golang) +[![validate](https://github.com/seccomp/libseccomp-golang/actions/workflows/validate.yml/badge.svg)](https://github.com/seccomp/libseccomp-golang/actions/workflows/validate.yml) +[![test](https://github.com/seccomp/libseccomp-golang/actions/workflows/test.yml/badge.svg)](https://github.com/seccomp/libseccomp-golang/actions/workflows/test.yml) The libseccomp library provides an easy to use, platform independent, interface to the Linux Kernel's syscall filtering mechanism. The libseccomp API is @@ -26,26 +28,14 @@ list. * https://groups.google.com/d/forum/libseccomp -Documentation is also available at: +Documentation for this package is also available at: -* https://godoc.org/github.com/seccomp/libseccomp-golang +* https://pkg.go.dev/github.com/seccomp/libseccomp-golang ## Installing the package -The libseccomp-golang bindings require at least Go v1.2.1 and GCC v4.8.4; -earlier versions may yield unpredictable results. If you meet these -requirements you can install this package using the command below: - # go get github.com/seccomp/libseccomp-golang -## Testing the Library - -A number of tests and lint related recipes are provided in the Makefile, if -you want to run the standard regression tests, you can excute the following: - - # make check - -In order to execute the 'make lint' recipe the 'golint' tool is needed, it -can be found at: +## Contributing -* https://github.com/golang/lint +See [CONTRIBUTING.md](CONTRIBUTING.md). diff --git a/vendor/github.com/seccomp/libseccomp-golang/SECURITY.md b/vendor/github.com/seccomp/libseccomp-golang/SECURITY.md new file mode 100644 index 000000000..c448faa8e --- /dev/null +++ b/vendor/github.com/seccomp/libseccomp-golang/SECURITY.md @@ -0,0 +1,47 @@ +The libseccomp-golang Security Vulnerability Handling Process +=============================================================================== +https://github.com/seccomp/libseccomp-golang + +This document document attempts to describe the processes through which +sensitive security relevant bugs can be responsibly disclosed to the +libseccomp-golang project and how the project maintainers should handle these +reports. Just like the other libseccomp-golang process documents, this +document should be treated as a guiding document and not a hard, unyielding set +of regulations; the bug reporters and project maintainers are encouraged to +work together to address the issues as best they can, in a manner which works +best for all parties involved. + +### Reporting Problems + +Problems with the libseccomp-golang library that are not suitable for immediate +public disclosure should be emailed to the current libseccomp-golang +maintainers, the list is below. We typically request at most a 90 day time +period to address the issue before it is made public, but we will make every +effort to address the issue as quickly as possible and shorten the disclosure +window. + +* Paul Moore, paul@paul-moore.com +* Tom Hromatka, tom.hromatka@oracle.com + +### Resolving Sensitive Security Issues + +Upon disclosure of a bug, the maintainers should work together to investigate +the problem and decide on a solution. In order to prevent an early disclosure +of the problem, those working on the solution should do so privately and +outside of the traditional libseccomp-golang development practices. One +possible solution to this is to leverage the GitHub "Security" functionality to +create a private development fork that can be shared among the maintainers, and +optionally the reporter. A placeholder GitHub issue may be created, but +details should remain extremely limited until such time as the problem has been +fixed and responsibly disclosed. If a CVE, or other tag, has been assigned to +the problem, the GitHub issue title should include the vulnerability tag once +the problem has been disclosed. + +### Public Disclosure + +Whenever possible, responsible reporting and patching practices should be +followed, including notification to the linux-distros and oss-security mailing +lists. + +* https://oss-security.openwall.org/wiki/mailing-lists/distros +* https://oss-security.openwall.org/wiki/mailing-lists/oss-security diff --git a/vendor/github.com/seccomp/libseccomp-golang/go.sum b/vendor/github.com/seccomp/libseccomp-golang/go.sum index 72ae16111..e69de29bb 100644 --- a/vendor/github.com/seccomp/libseccomp-golang/go.sum +++ b/vendor/github.com/seccomp/libseccomp-golang/go.sum @@ -1,23 +0,0 @@ -github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/lint v0.0.0-20200302205851-738671d3881b h1:Wh+f8QHJXR411sJR8/vRBTZ7YapZaRvUcLFFJhusH0k= -golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7 h1:EBZoQjiKKPaLbPrbpssUfuHtwM6KV/vb4U85g/cigFY= -golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200313205530-4303120df7d8 h1:gkI/wGGwpcG5W4hLCzZNGxA4wzWBGGDStRI1MrjDl2Q= -golang.org/x/tools v0.0.0-20200313205530-4303120df7d8/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/vendor/github.com/seccomp/libseccomp-golang/seccomp.go b/vendor/github.com/seccomp/libseccomp-golang/seccomp.go index e9b92e221..8dad12fdb 100644 --- a/vendor/github.com/seccomp/libseccomp-golang/seccomp.go +++ b/vendor/github.com/seccomp/libseccomp-golang/seccomp.go @@ -1,5 +1,3 @@ -// +build linux - // Public API specification for libseccomp Go bindings // Contains public API for the bindings @@ -18,48 +16,36 @@ import ( "unsafe" ) -// C wrapping code - -// To compile libseccomp-golang against a specific version of libseccomp: -// cd ../libseccomp && mkdir -p prefix -// ./configure --prefix=$PWD/prefix && make && make install -// cd ../libseccomp-golang -// PKG_CONFIG_PATH=$PWD/../libseccomp/prefix/lib/pkgconfig/ make -// LD_PRELOAD=$PWD/../libseccomp/prefix/lib/libseccomp.so.2.5.0 PKG_CONFIG_PATH=$PWD/../libseccomp/prefix/lib/pkgconfig/ make test - -// #cgo pkg-config: libseccomp // #include <stdlib.h> // #include <seccomp.h> import "C" // Exported types -// VersionError denotes that the system libseccomp version is incompatible -// with this package. +// VersionError represents an error when either the system libseccomp version +// or the kernel version is too old to perform the operation requested. type VersionError struct { - message string - minimum string + op string // operation that failed or would fail + major, minor, micro uint // minimally required libseccomp version + curAPI, minAPI uint // current and minimally required API versions } func init() { // This forces the cgo libseccomp to initialize its internal API support state, // which is necessary on older versions of libseccomp in order to work // correctly. - GetAPI() + _, _ = getAPI() } func (e VersionError) Error() string { - messageStr := "" - if e.message != "" { - messageStr = e.message + ": " + if e.minAPI != 0 { + return fmt.Sprintf("%s requires libseccomp >= %d.%d.%d and API level >= %d "+ + "(current version: %d.%d.%d, API level: %d)", + e.op, e.major, e.minor, e.micro, e.minAPI, + verMajor, verMinor, verMicro, e.curAPI) } - minimumStr := "" - if e.minimum != "" { - minimumStr = e.minimum - } else { - minimumStr = "2.2.0" - } - return fmt.Sprintf("Libseccomp version too low: %sminimum supported is %s: detected %d.%d.%d", messageStr, minimumStr, verMajor, verMinor, verMicro) + return fmt.Sprintf("%s requires libseccomp >= %d.%d.%d (current version: %d.%d.%d)", + e.op, e.major, e.minor, e.micro, verMajor, verMinor, verMicro) } // ScmpArch represents a CPU architecture. Seccomp can restrict syscalls on a @@ -148,44 +134,46 @@ const ( // variables are invalid ArchInvalid ScmpArch = iota // ArchNative is the native architecture of the kernel - ArchNative ScmpArch = iota + ArchNative // ArchX86 represents 32-bit x86 syscalls - ArchX86 ScmpArch = iota + ArchX86 // ArchAMD64 represents 64-bit x86-64 syscalls - ArchAMD64 ScmpArch = iota + ArchAMD64 // ArchX32 represents 64-bit x86-64 syscalls (32-bit pointers) - ArchX32 ScmpArch = iota + ArchX32 // ArchARM represents 32-bit ARM syscalls - ArchARM ScmpArch = iota + ArchARM // ArchARM64 represents 64-bit ARM syscalls - ArchARM64 ScmpArch = iota + ArchARM64 // ArchMIPS represents 32-bit MIPS syscalls - ArchMIPS ScmpArch = iota + ArchMIPS // ArchMIPS64 represents 64-bit MIPS syscalls - ArchMIPS64 ScmpArch = iota + ArchMIPS64 // ArchMIPS64N32 represents 64-bit MIPS syscalls (32-bit pointers) - ArchMIPS64N32 ScmpArch = iota + ArchMIPS64N32 // ArchMIPSEL represents 32-bit MIPS syscalls (little endian) - ArchMIPSEL ScmpArch = iota + ArchMIPSEL // ArchMIPSEL64 represents 64-bit MIPS syscalls (little endian) - ArchMIPSEL64 ScmpArch = iota + ArchMIPSEL64 // ArchMIPSEL64N32 represents 64-bit MIPS syscalls (little endian, // 32-bit pointers) - ArchMIPSEL64N32 ScmpArch = iota + ArchMIPSEL64N32 // ArchPPC represents 32-bit POWERPC syscalls - ArchPPC ScmpArch = iota + ArchPPC // ArchPPC64 represents 64-bit POWER syscalls (big endian) - ArchPPC64 ScmpArch = iota + ArchPPC64 // ArchPPC64LE represents 64-bit POWER syscalls (little endian) - ArchPPC64LE ScmpArch = iota + ArchPPC64LE // ArchS390 represents 31-bit System z/390 syscalls - ArchS390 ScmpArch = iota + ArchS390 // ArchS390X represents 64-bit System z/390 syscalls - ArchS390X ScmpArch = iota + ArchS390X // ArchPARISC represents 32-bit PA-RISC - ArchPARISC ScmpArch = iota + ArchPARISC // ArchPARISC64 represents 64-bit PA-RISC - ArchPARISC64 ScmpArch = iota + ArchPARISC64 + // ArchRISCV64 represents RISCV64 + ArchRISCV64 ) const ( @@ -194,34 +182,36 @@ const ( // ActInvalid is a placeholder to ensure uninitialized ScmpAction // variables are invalid ActInvalid ScmpAction = iota - // ActKill kills the thread that violated the rule. It is the same as ActKillThread. + // ActKillThread kills the thread that violated the rule. // All other threads from the same thread group will continue to execute. - ActKill ScmpAction = iota + ActKillThread // ActTrap throws SIGSYS - ActTrap ScmpAction = iota + ActTrap // ActNotify triggers a userspace notification. This action is only usable when // libseccomp API level 6 or higher is supported. - ActNotify ScmpAction = iota + ActNotify // ActErrno causes the syscall to return a negative error code. This // code can be set with the SetReturnCode method - ActErrno ScmpAction = iota + ActErrno // ActTrace causes the syscall to notify tracing processes with the // given error code. This code can be set with the SetReturnCode method - ActTrace ScmpAction = iota + ActTrace // ActAllow permits the syscall to continue execution - ActAllow ScmpAction = iota + ActAllow // ActLog permits the syscall to continue execution after logging it. // This action is only usable when libseccomp API level 3 or higher is // supported. - ActLog ScmpAction = iota - // ActKillThread kills the thread that violated the rule. It is the same as ActKill. - // All other threads from the same thread group will continue to execute. - ActKillThread ScmpAction = iota + ActLog // ActKillProcess kills the process that violated the rule. // All threads in the thread group are also terminated. // This action is only usable when libseccomp API level 3 or higher is // supported. - ActKillProcess ScmpAction = iota + ActKillProcess + // ActKill kills the thread that violated the rule. + // All other threads from the same thread group will continue to execute. + // + // Deprecated: use ActKillThread + ActKill = ActKillThread ) const ( @@ -234,36 +224,35 @@ const ( CompareInvalid ScmpCompareOp = iota // CompareNotEqual returns true if the argument is not equal to the // given value - CompareNotEqual ScmpCompareOp = iota + CompareNotEqual // CompareLess returns true if the argument is less than the given value - CompareLess ScmpCompareOp = iota + CompareLess // CompareLessOrEqual returns true if the argument is less than or equal // to the given value - CompareLessOrEqual ScmpCompareOp = iota + CompareLessOrEqual // CompareEqual returns true if the argument is equal to the given value - CompareEqual ScmpCompareOp = iota + CompareEqual // CompareGreaterEqual returns true if the argument is greater than or // equal to the given value - CompareGreaterEqual ScmpCompareOp = iota + CompareGreaterEqual // CompareGreater returns true if the argument is greater than the given // value - CompareGreater ScmpCompareOp = iota - // CompareMaskedEqual returns true if the argument is equal to the given - // value, when masked (bitwise &) against the second given value - CompareMaskedEqual ScmpCompareOp = iota + CompareGreater + // CompareMaskedEqual returns true if the masked argument value is + // equal to the masked datum value. Mask is the first argument, and + // datum is the second one. + CompareMaskedEqual ) -var ( - // ErrSyscallDoesNotExist represents an error condition where - // libseccomp is unable to resolve the syscall - ErrSyscallDoesNotExist = fmt.Errorf("could not resolve syscall name") -) +// ErrSyscallDoesNotExist represents an error condition where +// libseccomp is unable to resolve the syscall +var ErrSyscallDoesNotExist = fmt.Errorf("could not resolve syscall name") const ( // Userspace notification response flags // NotifRespFlagContinue tells the kernel to continue executing the system - // call that triggered the notification. Must only be used when the notication + // call that triggered the notification. Must only be used when the notification // response's error is 0. NotifRespFlagContinue uint32 = 1 ) @@ -314,6 +303,8 @@ func GetArchFromString(arch string) (ScmpArch, error) { return ArchPARISC, nil case "parisc64": return ArchPARISC64, nil + case "riscv64": + return ArchRISCV64, nil default: return ArchInvalid, fmt.Errorf("cannot convert unrecognized string %q", arch) } @@ -358,6 +349,8 @@ func (a ScmpArch) String() string { return "parisc" case ArchPARISC64: return "parisc64" + case ArchRISCV64: + return "riscv64" case ArchNative: return "native" case ArchInvalid: @@ -394,7 +387,7 @@ func (a ScmpCompareOp) String() string { // String returns a string representation of a seccomp match action func (a ScmpAction) String() string { switch a & 0xFFFF { - case ActKill, ActKillThread: + case ActKillThread: return "Action: Kill thread" case ActKillProcess: return "Action: Kill process" @@ -556,8 +549,8 @@ func MakeCondition(arg uint, comparison ScmpCompareOp, values ...uint64) (ScmpCo return condStruct, err } - if comparison == CompareInvalid { - return condStruct, fmt.Errorf("invalid comparison operator") + if err := sanitizeCompareOp(comparison); err != nil { + return condStruct, err } else if arg > 5 { return condStruct, fmt.Errorf("syscalls only have up to 6 arguments (%d given)", arg) } else if len(values) > 2 { @@ -874,10 +867,8 @@ func (f *ScmpFilter) GetNoNewPrivsBit() (bool, error) { func (f *ScmpFilter) GetLogBit() (bool, error) { log, err := f.getFilterAttr(filterAttrLog) if err != nil { - // Ignore error, if not supported returns apiLevel == 0 - apiLevel, _ := GetAPI() - if apiLevel < 3 { - return false, fmt.Errorf("getting the log bit is only supported in libseccomp 2.4.0 and newer with API level 3 or higher") + if e := checkAPI("GetLogBit", 3, 2, 4, 0); e != nil { + err = e } return false, err @@ -899,9 +890,8 @@ func (f *ScmpFilter) GetLogBit() (bool, error) { func (f *ScmpFilter) GetSSB() (bool, error) { ssb, err := f.getFilterAttr(filterAttrSSB) if err != nil { - api, apiErr := getAPI() - if (apiErr != nil && api == 0) || (apiErr == nil && api < 4) { - return false, fmt.Errorf("getting the SSB flag is only supported in libseccomp 2.5.0 and newer with API level 4 or higher") + if e := checkAPI("GetSSB", 4, 2, 5, 0); e != nil { + err = e } return false, err @@ -914,6 +904,42 @@ func (f *ScmpFilter) GetSSB() (bool, error) { return true, nil } +// GetOptimize returns the current optimization level of the filter, +// or an error if an issue was encountered retrieving the value. +// See SetOptimize for more details. +func (f *ScmpFilter) GetOptimize() (int, error) { + level, err := f.getFilterAttr(filterAttrOptimize) + if err != nil { + if e := checkAPI("GetOptimize", 4, 2, 5, 0); e != nil { + err = e + } + + return 0, err + } + + return int(level), nil +} + +// GetRawRC returns the current state of RawRC flag, or an error +// if an issue was encountered retrieving the value. +// See SetRawRC for more details. +func (f *ScmpFilter) GetRawRC() (bool, error) { + rawrc, err := f.getFilterAttr(filterAttrRawRC) + if err != nil { + if e := checkAPI("GetRawRC", 4, 2, 5, 0); e != nil { + err = e + } + + return false, err + } + + if rawrc == 0 { + return false, nil + } + + return true, nil +} + // SetBadArchAction sets the default action taken on a syscall for an // architecture not in the filter, or an error if an issue was encountered // setting the value. @@ -953,10 +979,8 @@ func (f *ScmpFilter) SetLogBit(state bool) error { err := f.setFilterAttr(filterAttrLog, toSet) if err != nil { - // Ignore error, if not supported returns apiLevel == 0 - apiLevel, _ := GetAPI() - if apiLevel < 3 { - return fmt.Errorf("setting the log bit is only supported in libseccomp 2.4.0 and newer with API level 3 or higher") + if e := checkAPI("SetLogBit", 3, 2, 4, 0); e != nil { + err = e } } @@ -976,9 +1000,52 @@ func (f *ScmpFilter) SetSSB(state bool) error { err := f.setFilterAttr(filterAttrSSB, toSet) if err != nil { - api, apiErr := getAPI() - if (apiErr != nil && api == 0) || (apiErr == nil && api < 4) { - return fmt.Errorf("setting the SSB flag is only supported in libseccomp 2.5.0 and newer with API level 4 or higher") + if e := checkAPI("SetSSB", 4, 2, 5, 0); e != nil { + err = e + } + } + + return err +} + +// SetOptimize sets optimization level of the seccomp filter. By default +// libseccomp generates a set of sequential "if" statements for each rule in +// the filter. SetSyscallPriority can be used to prioritize the order for the +// default cause. The binary tree optimization sorts by syscall numbers and +// generates consistent O(log n) filter traversal for every rule in the filter. +// The binary tree may be advantageous for large filters. Note that +// SetSyscallPriority is ignored when level == 2. +// +// The different optimization levels are: +// 0: Reserved value, not currently used. +// 1: Rules sorted by priority and complexity (DEFAULT). +// 2: Binary tree sorted by syscall number. +func (f *ScmpFilter) SetOptimize(level int) error { + cLevel := C.uint32_t(level) + + err := f.setFilterAttr(filterAttrOptimize, cLevel) + if err != nil { + if e := checkAPI("SetOptimize", 4, 2, 5, 0); e != nil { + err = e + } + } + + return err +} + +// SetRawRC sets whether libseccomp should pass system error codes back to the +// caller, instead of the default ECANCELED. Defaults to false. +func (f *ScmpFilter) SetRawRC(state bool) error { + var toSet C.uint32_t = 0x0 + + if state { + toSet = 0x1 + } + + err := f.setFilterAttr(filterAttrRawRC, toSet) + if err != nil { + if e := checkAPI("SetRawRC", 4, 2, 5, 0); e != nil { + err = e } } @@ -1029,9 +1096,6 @@ func (f *ScmpFilter) AddRuleExact(call ScmpSyscall, action ScmpAction) error { // AddRuleConditional adds a single rule for a conditional action on a syscall. // Returns an error if an issue was encountered adding the rule. // All conditions must match for the rule to match. -// There is a bug in library versions below v2.2.1 which can, in some cases, -// cause conditions to be lost when more than one are used. Consequently, -// AddRuleConditional is disabled on library versions lower than v2.2.1 func (f *ScmpFilter) AddRuleConditional(call ScmpSyscall, action ScmpAction, conds []ScmpCondition) error { return f.addRuleGeneric(call, action, false, conds) } @@ -1043,9 +1107,6 @@ func (f *ScmpFilter) AddRuleConditional(call ScmpSyscall, action ScmpAction, con // The rule will function exactly as described, but it may not function identically // (or be able to be applied to) all architectures. // Returns an error if an issue was encountered adding the rule. -// There is a bug in library versions below v2.2.1 which can, in some cases, -// cause conditions to be lost when more than one are used. Consequently, -// AddRuleConditionalExact is disabled on library versions lower than v2.2.1 func (f *ScmpFilter) AddRuleConditionalExact(call ScmpSyscall, action ScmpAction, conds []ScmpCondition) error { return f.addRuleGeneric(call, action, true, conds) } diff --git a/vendor/github.com/seccomp/libseccomp-golang/seccomp_internal.go b/vendor/github.com/seccomp/libseccomp-golang/seccomp_internal.go index 8dc7b296f..df4dfb7eb 100644 --- a/vendor/github.com/seccomp/libseccomp-golang/seccomp_internal.go +++ b/vendor/github.com/seccomp/libseccomp-golang/seccomp_internal.go @@ -1,11 +1,10 @@ -// +build linux - // Internal functions for libseccomp Go bindings // No exported functions package seccomp import ( + "errors" "fmt" "syscall" ) @@ -27,10 +26,10 @@ import ( #include <stdlib.h> #include <seccomp.h> -#if SCMP_VER_MAJOR < 2 -#error Minimum supported version of Libseccomp is v2.2.0 -#elif SCMP_VER_MAJOR == 2 && SCMP_VER_MINOR < 2 -#error Minimum supported version of Libseccomp is v2.2.0 +#if (SCMP_VER_MAJOR < 2) || \ + (SCMP_VER_MAJOR == 2 && SCMP_VER_MINOR < 3) || \ + (SCMP_VER_MAJOR == 2 && SCMP_VER_MINOR == 3 && SCMP_VER_MICRO < 1) +#error This package requires libseccomp >= v2.3.1 #endif #define ARCH_BAD ~0 @@ -65,6 +64,10 @@ const uint32_t C_ARCH_BAD = ARCH_BAD; #define SCMP_ARCH_PARISC64 ARCH_BAD #endif +#ifndef SCMP_ARCH_RISCV64 +#define SCMP_ARCH_RISCV64 ARCH_BAD +#endif + const uint32_t C_ARCH_NATIVE = SCMP_ARCH_NATIVE; const uint32_t C_ARCH_X86 = SCMP_ARCH_X86; const uint32_t C_ARCH_X86_64 = SCMP_ARCH_X86_64; @@ -84,6 +87,7 @@ const uint32_t C_ARCH_S390 = SCMP_ARCH_S390; const uint32_t C_ARCH_S390X = SCMP_ARCH_S390X; const uint32_t C_ARCH_PARISC = SCMP_ARCH_PARISC; const uint32_t C_ARCH_PARISC64 = SCMP_ARCH_PARISC64; +const uint32_t C_ARCH_RISCV64 = SCMP_ARCH_RISCV64; #ifndef SCMP_ACT_LOG #define SCMP_ACT_LOG 0x7ffc0000U @@ -113,20 +117,25 @@ const uint32_t C_ACT_NOTIFY = SCMP_ACT_NOTIFY; // The libseccomp SCMP_FLTATR_CTL_LOG member of the scmp_filter_attr enum was // added in v2.4.0 -#if (SCMP_VER_MAJOR < 2) || \ - (SCMP_VER_MAJOR == 2 && SCMP_VER_MINOR < 4) +#if SCMP_VER_MAJOR == 2 && SCMP_VER_MINOR < 4 #define SCMP_FLTATR_CTL_LOG _SCMP_FLTATR_MIN #endif + +// The following SCMP_FLTATR_* were added in libseccomp v2.5.0. #if SCMP_VER_MAJOR == 2 && SCMP_VER_MINOR < 5 -#define SCMP_FLTATR_CTL_SSB _SCMP_FLTATR_MIN +#define SCMP_FLTATR_CTL_SSB _SCMP_FLTATR_MIN +#define SCMP_FLTATR_CTL_OPTIMIZE _SCMP_FLTATR_MIN +#define SCMP_FLTATR_API_SYSRAWRC _SCMP_FLTATR_MIN #endif -const uint32_t C_ATTRIBUTE_DEFAULT = (uint32_t)SCMP_FLTATR_ACT_DEFAULT; -const uint32_t C_ATTRIBUTE_BADARCH = (uint32_t)SCMP_FLTATR_ACT_BADARCH; -const uint32_t C_ATTRIBUTE_NNP = (uint32_t)SCMP_FLTATR_CTL_NNP; -const uint32_t C_ATTRIBUTE_TSYNC = (uint32_t)SCMP_FLTATR_CTL_TSYNC; -const uint32_t C_ATTRIBUTE_LOG = (uint32_t)SCMP_FLTATR_CTL_LOG; -const uint32_t C_ATTRIBUTE_SSB = (uint32_t)SCMP_FLTATR_CTL_SSB; +const uint32_t C_ATTRIBUTE_DEFAULT = (uint32_t)SCMP_FLTATR_ACT_DEFAULT; +const uint32_t C_ATTRIBUTE_BADARCH = (uint32_t)SCMP_FLTATR_ACT_BADARCH; +const uint32_t C_ATTRIBUTE_NNP = (uint32_t)SCMP_FLTATR_CTL_NNP; +const uint32_t C_ATTRIBUTE_TSYNC = (uint32_t)SCMP_FLTATR_CTL_TSYNC; +const uint32_t C_ATTRIBUTE_LOG = (uint32_t)SCMP_FLTATR_CTL_LOG; +const uint32_t C_ATTRIBUTE_SSB = (uint32_t)SCMP_FLTATR_CTL_SSB; +const uint32_t C_ATTRIBUTE_OPTIMIZE = (uint32_t)SCMP_FLTATR_CTL_OPTIMIZE; +const uint32_t C_ATTRIBUTE_SYSRAWRC = (uint32_t)SCMP_FLTATR_API_SYSRAWRC; const int C_CMP_NE = (int)SCMP_CMP_NE; const int C_CMP_LT = (int)SCMP_CMP_LT; @@ -173,8 +182,7 @@ unsigned int get_micro_version() #endif // The libseccomp API level functions were added in v2.4.0 -#if (SCMP_VER_MAJOR < 2) || \ - (SCMP_VER_MAJOR == 2 && SCMP_VER_MINOR < 4) +#if SCMP_VER_MAJOR == 2 && SCMP_VER_MINOR < 4 const unsigned int seccomp_api_get(void) { // libseccomp-golang requires libseccomp v2.2.0, at a minimum, which @@ -217,8 +225,7 @@ void add_struct_arg_cmp( } // The seccomp notify API functions were added in v2.5.0 -#if (SCMP_VER_MAJOR < 2) || \ - (SCMP_VER_MAJOR == 2 && SCMP_VER_MINOR < 5) +#if SCMP_VER_MAJOR == 2 && SCMP_VER_MINOR < 5 struct seccomp_data { int nr; @@ -270,11 +277,13 @@ type scmpFilterAttr uint32 const ( filterAttrActDefault scmpFilterAttr = iota - filterAttrActBadArch scmpFilterAttr = iota - filterAttrNNP scmpFilterAttr = iota - filterAttrTsync scmpFilterAttr = iota - filterAttrLog scmpFilterAttr = iota - filterAttrSSB scmpFilterAttr = iota + filterAttrActBadArch + filterAttrNNP + filterAttrTsync + filterAttrLog + filterAttrSSB + filterAttrOptimize + filterAttrRawRC ) const ( @@ -282,9 +291,9 @@ const ( scmpError C.int = -1 // Comparison boundaries to check for architecture validity archStart ScmpArch = ArchNative - archEnd ScmpArch = ArchPARISC64 + archEnd ScmpArch = ArchRISCV64 // Comparison boundaries to check for action validity - actionStart ScmpAction = ActKill + actionStart ScmpAction = ActKillThread actionEnd ScmpAction = ActKillProcess // Comparison boundaries to check for comparison operator validity compareOpStart ScmpCompareOp = CompareNotEqual @@ -292,8 +301,9 @@ const ( ) var ( - // Error thrown on bad filter context - errBadFilter = fmt.Errorf("filter is invalid or uninitialized") + // errBadFilter is thrown on bad filter context. + errBadFilter = errors.New("filter is invalid or uninitialized") + errDefAction = errors.New("requested action matches default action of filter") // Constants representing library major, minor, and micro versions verMajor = uint(C.get_major_version()) verMinor = uint(C.get_minor_version()) @@ -302,19 +312,28 @@ var ( // Nonexported functions -// Check if library version is greater than or equal to the given one -func checkVersionAbove(major, minor, micro uint) bool { - return (verMajor > major) || +// checkVersion returns an error if the libseccomp version being used +// is less than the one specified by major, minor, and micro arguments. +// Argument op is an arbitrary non-empty operation description, which +// is used as a part of the error message returned. +// +// Most users should use checkAPI instead. +func checkVersion(op string, major, minor, micro uint) error { + if (verMajor > major) || (verMajor == major && verMinor > minor) || - (verMajor == major && verMinor == minor && verMicro >= micro) + (verMajor == major && verMinor == minor && verMicro >= micro) { + return nil + } + return &VersionError{ + op: op, + major: major, + minor: minor, + micro: micro, + } } -// Ensure that the library is supported, i.e. >= 2.2.0. func ensureSupportedVersion() error { - if !checkVersionAbove(2, 2, 0) { - return VersionError{} - } - return nil + return checkVersion("seccomp", 2, 3, 1) } // Get the API level @@ -406,8 +425,10 @@ func (f *ScmpFilter) addRuleWrapper(call ScmpSyscall, action ScmpAction, exact b switch e := errRc(retCode); e { case syscall.EFAULT: return fmt.Errorf("unrecognized syscall %#x", int32(call)) - case syscall.EPERM: - return fmt.Errorf("requested action matches default action of filter") + // libseccomp >= v2.5.0 returns EACCES, older versions return EPERM. + // TODO: remove EPERM once libseccomp < v2.5.0 is not supported. + case syscall.EPERM, syscall.EACCES: + return errDefAction case syscall.EINVAL: return fmt.Errorf("two checks on same syscall argument") default: @@ -432,14 +453,6 @@ func (f *ScmpFilter) addRuleGeneric(call ScmpSyscall, action ScmpAction, exact b return err } } else { - // We don't support conditional filtering in library version v2.1 - if !checkVersionAbove(2, 2, 1) { - return VersionError{ - message: "conditional filtering is not supported", - minimum: "2.2.1", - } - } - argsArr := C.make_arg_cmp_array(C.uint(len(conds))) if argsArr == nil { return fmt.Errorf("error allocating memory for conditions") @@ -536,6 +549,8 @@ func archFromNative(a C.uint32_t) (ScmpArch, error) { return ArchPARISC, nil case C.C_ARCH_PARISC64: return ArchPARISC64, nil + case C.C_ARCH_RISCV64: + return ArchRISCV64, nil default: return 0x0, fmt.Errorf("unrecognized architecture %#x", uint32(a)) } @@ -580,6 +595,8 @@ func (a ScmpArch) toNative() C.uint32_t { return C.C_ARCH_PARISC case ArchPARISC64: return C.C_ARCH_PARISC64 + case ArchRISCV64: + return C.C_ARCH_RISCV64 case ArchNative: return C.C_ARCH_NATIVE default: @@ -612,8 +629,6 @@ func (a ScmpCompareOp) toNative() C.int { func actionFromNative(a C.uint32_t) (ScmpAction, error) { aTmp := a & 0xFFFF switch a & 0xFFFF0000 { - case C.C_ACT_KILL: - return ActKill, nil case C.C_ACT_KILL_PROCESS: return ActKillProcess, nil case C.C_ACT_KILL_THREAD: @@ -638,8 +653,6 @@ func actionFromNative(a C.uint32_t) (ScmpAction, error) { // Only use with sanitized actions, no error handling func (a ScmpAction) toNative() C.uint32_t { switch a & 0xFFFF { - case ActKill: - return C.C_ACT_KILL case ActKillProcess: return C.C_ACT_KILL_PROCESS case ActKillThread: @@ -676,15 +689,15 @@ func (a scmpFilterAttr) toNative() uint32 { return uint32(C.C_ATTRIBUTE_LOG) case filterAttrSSB: return uint32(C.C_ATTRIBUTE_SSB) + case filterAttrOptimize: + return uint32(C.C_ATTRIBUTE_OPTIMIZE) + case filterAttrRawRC: + return uint32(C.C_ATTRIBUTE_SYSRAWRC) default: return 0x0 } } -func (a ScmpSyscall) toNative() C.uint32_t { - return C.uint32_t(a) -} - func syscallFromNative(a C.int) ScmpSyscall { return ScmpSyscall(a) } @@ -724,9 +737,34 @@ func (scmpResp *ScmpNotifResp) toNative(resp *C.struct_seccomp_notif_resp) { resp.flags = C.__u32(scmpResp.Flags) } +// checkAPI checks that both the API level and the seccomp version is equal to +// or greater than the specified minLevel and major, minor, micro, +// respectively, and returns an error otherwise. Argument op is an arbitrary +// non-empty operation description, used as a part of the error message +// returned. +func checkAPI(op string, minLevel uint, major, minor, micro uint) error { + // Ignore error from getAPI, as it returns level == 0 in case of error. + level, _ := getAPI() + if level >= minLevel { + return checkVersion(op, major, minor, micro) + } + return &VersionError{ + op: op, + curAPI: level, + minAPI: minLevel, + major: major, + minor: minor, + micro: micro, + } +} + // Userspace Notification API // Calls to C.seccomp_notify* hidden from seccomp.go +func notifSupported() error { + return checkAPI("seccomp notification", 6, 2, 5, 0) +} + func (f *ScmpFilter) getNotifFd() (ScmpFd, error) { f.lock.Lock() defer f.lock.Unlock() @@ -734,11 +772,8 @@ func (f *ScmpFilter) getNotifFd() (ScmpFd, error) { if !f.valid { return -1, errBadFilter } - - // Ignore error, if not supported returns apiLevel == 0 - apiLevel, _ := GetAPI() - if apiLevel < 6 { - return -1, fmt.Errorf("seccomp notification requires API level >= 6; current level = %d", apiLevel) + if err := notifSupported(); err != nil { + return -1, err } fd := C.seccomp_notify_fd(f.filterCtx) @@ -750,10 +785,8 @@ func notifReceive(fd ScmpFd) (*ScmpNotifReq, error) { var req *C.struct_seccomp_notif var resp *C.struct_seccomp_notif_resp - // Ignore error, if not supported returns apiLevel == 0 - apiLevel, _ := GetAPI() - if apiLevel < 6 { - return nil, fmt.Errorf("seccomp notification requires API level >= 6; current level = %d", apiLevel) + if err := notifSupported(); err != nil { + return nil, err } // we only use the request here; the response is unused @@ -789,13 +822,11 @@ func notifRespond(fd ScmpFd, scmpResp *ScmpNotifResp) error { var req *C.struct_seccomp_notif var resp *C.struct_seccomp_notif_resp - // Ignore error, if not supported returns apiLevel == 0 - apiLevel, _ := GetAPI() - if apiLevel < 6 { - return fmt.Errorf("seccomp notification requires API level >= 6; current level = %d", apiLevel) + if err := notifSupported(); err != nil { + return err } - // we only use the reponse here; the request is discarded + // we only use the response here; the request is discarded if retCode := C.seccomp_notify_alloc(&req, &resp); retCode != 0 { return errRc(retCode) } @@ -827,10 +858,8 @@ func notifRespond(fd ScmpFd, scmpResp *ScmpNotifResp) error { } func notifIDValid(fd ScmpFd, id uint64) error { - // Ignore error, if not supported returns apiLevel == 0 - apiLevel, _ := GetAPI() - if apiLevel < 6 { - return fmt.Errorf("seccomp notification requires API level >= 6; current level = %d", apiLevel) + if err := notifSupported(); err != nil { + return err } for { diff --git a/vendor/github.com/stretchr/testify/assert/assertion_compare_can_convert.go b/vendor/github.com/stretchr/testify/assert/assertion_compare_can_convert.go index df22c47fc..da867903e 100644 --- a/vendor/github.com/stretchr/testify/assert/assertion_compare_can_convert.go +++ b/vendor/github.com/stretchr/testify/assert/assertion_compare_can_convert.go @@ -9,7 +9,7 @@ package assert import "reflect" -// Wrapper around reflect.Value.CanConvert, for compatability +// Wrapper around reflect.Value.CanConvert, for compatibility // reasons. func canConvert(value reflect.Value, to reflect.Type) bool { return value.CanConvert(to) diff --git a/vendor/modules.txt b/vendor/modules.txt index 9bd500ee8..de76aad91 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 @@ -109,7 +109,7 @@ github.com/containers/buildah/pkg/rusage github.com/containers/buildah/pkg/sshagent github.com/containers/buildah/pkg/util github.com/containers/buildah/util -# github.com/containers/common v0.48.1-0.20220523155016-2fd37da97824 +# github.com/containers/common v0.48.1-0.20220528105338-54c8092c69a1 ## explicit github.com/containers/common/libimage github.com/containers/common/libimage/define @@ -119,6 +119,7 @@ github.com/containers/common/libnetwork/etchosts github.com/containers/common/libnetwork/internal/util github.com/containers/common/libnetwork/netavark github.com/containers/common/libnetwork/network +github.com/containers/common/libnetwork/resolvconf github.com/containers/common/libnetwork/types github.com/containers/common/libnetwork/util github.com/containers/common/pkg/apparmor @@ -329,7 +330,7 @@ github.com/docker/distribution/registry/client/auth/challenge github.com/docker/distribution/registry/client/transport github.com/docker/distribution/registry/storage/cache github.com/docker/distribution/registry/storage/cache/memory -# github.com/docker/docker v20.10.16+incompatible +# github.com/docker/docker v20.10.17+incompatible ## explicit github.com/docker/docker/api github.com/docker/docker/api/types @@ -554,7 +555,7 @@ github.com/opencontainers/go-digest ## explicit github.com/opencontainers/image-spec/specs-go github.com/opencontainers/image-spec/specs-go/v1 -# github.com/opencontainers/runc v1.1.2 +# github.com/opencontainers/runc v1.1.3 ## explicit github.com/opencontainers/runc/libcontainer/apparmor github.com/opencontainers/runc/libcontainer/cgroups @@ -627,7 +628,7 @@ github.com/rootless-containers/rootlesskit/pkg/port/builtin/parent/tcp github.com/rootless-containers/rootlesskit/pkg/port/builtin/parent/udp github.com/rootless-containers/rootlesskit/pkg/port/builtin/parent/udp/udpproxy github.com/rootless-containers/rootlesskit/pkg/port/portutil -# github.com/seccomp/libseccomp-golang v0.9.2-0.20210429002308-3879420cc921 +# github.com/seccomp/libseccomp-golang v0.9.2-0.20220502022130-f33da4d89646 github.com/seccomp/libseccomp-golang # github.com/sirupsen/logrus v1.8.1 ## explicit @@ -641,7 +642,7 @@ github.com/spf13/cobra github.com/spf13/pflag # github.com/stefanberger/go-pkcs11uri v0.0.0-20201008174630-78d3cae3a980 github.com/stefanberger/go-pkcs11uri -# github.com/stretchr/testify v1.7.1 +# github.com/stretchr/testify v1.7.2 ## explicit github.com/stretchr/testify/assert github.com/stretchr/testify/require @@ -865,7 +866,6 @@ gopkg.in/tomb.v1 ## explicit gopkg.in/yaml.v2 # gopkg.in/yaml.v3 v3.0.1 -## explicit gopkg.in/yaml.v3 # sigs.k8s.io/yaml v1.3.0 sigs.k8s.io/yaml |