summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Containerfile-testvol10
-rw-r--r--Makefile30
-rw-r--r--cmd/podman-mac-helper/install.go16
-rw-r--r--cmd/podman-mac-helper/main.go4
-rw-r--r--cmd/podman-mac-helper/uninstall.go5
-rw-r--r--cmd/podman/auto-update.go3
-rw-r--r--cmd/podman/common/diffChanges.go3
-rw-r--r--cmd/podman/common/netflags.go27
-rw-r--r--cmd/podman/containers/attach.go4
-rw-r--r--cmd/podman/containers/checkpoint.go12
-rw-r--r--cmd/podman/containers/cleanup.go8
-rw-r--r--cmd/podman/containers/clone.go5
-rw-r--r--cmd/podman/containers/commit.go3
-rw-r--r--cmd/podman/containers/cp.go40
-rw-r--r--cmd/podman/containers/create.go22
-rw-r--r--cmd/podman/containers/diff.go3
-rw-r--r--cmd/podman/containers/exec.go6
-rw-r--r--cmd/podman/containers/export.go4
-rw-r--r--cmd/podman/containers/kill.go4
-rw-r--r--cmd/podman/containers/logs.go7
-rw-r--r--cmd/podman/containers/mount.go6
-rw-r--r--cmd/podman/containers/pause.go4
-rw-r--r--cmd/podman/containers/port.go10
-rw-r--r--cmd/podman/containers/ps.go10
-rw-r--r--cmd/podman/containers/rename.go5
-rw-r--r--cmd/podman/containers/restart.go5
-rw-r--r--cmd/podman/containers/rm.go18
-rw-r--r--cmd/podman/containers/run.go7
-rw-r--r--cmd/podman/containers/start.go16
-rw-r--r--cmd/podman/containers/stats.go4
-rw-r--r--cmd/podman/containers/stop.go3
-rw-r--r--cmd/podman/containers/top.go4
-rw-r--r--cmd/podman/containers/unpause.go4
-rw-r--r--cmd/podman/containers/wait.go4
-rw-r--r--cmd/podman/diff/diff.go6
-rw-r--r--cmd/podman/early_init_linux.go5
-rw-r--r--cmd/podman/generate/kube.go5
-rw-r--r--cmd/podman/generate/systemd.go6
-rw-r--r--cmd/podman/images/build.go31
-rw-r--r--cmd/podman/images/history.go3
-rw-r--r--cmd/podman/images/import.go10
-rw-r--r--cmd/podman/images/list.go4
-rw-r--r--cmd/podman/images/load.go8
-rw-r--r--cmd/podman/images/mount.go4
-rw-r--r--cmd/podman/images/pull.go4
-rw-r--r--cmd/podman/images/rm.go6
-rw-r--r--cmd/podman/images/save.go11
-rw-r--r--cmd/podman/images/scp.go302
-rw-r--r--cmd/podman/images/scp_test.go46
-rw-r--r--cmd/podman/images/scp_utils.go88
-rw-r--r--cmd/podman/images/search.go10
-rw-r--r--cmd/podman/images/sign.go4
-rw-r--r--cmd/podman/images/trust_set.go10
-rw-r--r--cmd/podman/images/unmount.go2
-rw-r--r--cmd/podman/images/utils_linux.go4
-rw-r--r--cmd/podman/inspect/inspect.go25
-rw-r--r--cmd/podman/machine/init.go6
-rw-r--r--cmd/podman/machine/inspect.go1
-rw-r--r--cmd/podman/machine/list.go7
-rw-r--r--cmd/podman/machine/machine.go9
-rw-r--r--cmd/podman/machine/rm.go1
-rw-r--r--cmd/podman/machine/set.go1
-rw-r--r--cmd/podman/machine/ssh.go13
-rw-r--r--cmd/podman/machine/start.go6
-rw-r--r--cmd/podman/machine/stop.go1
-rw-r--r--cmd/podman/manifest/push.go6
-rw-r--r--cmd/podman/manifest/remove.go3
-rw-r--r--cmd/podman/networks/create.go10
-rw-r--r--cmd/podman/networks/rm.go12
-rw-r--r--cmd/podman/parse/filters.go5
-rw-r--r--cmd/podman/parse/net.go14
-rw-r--r--cmd/podman/play/kube.go12
-rw-r--r--cmd/podman/pods/clone.go3
-rw-r--r--cmd/podman/pods/create.go16
-rw-r--r--cmd/podman/pods/inspect.go6
-rw-r--r--cmd/podman/pods/logs.go9
-rw-r--r--cmd/podman/pods/ps.go6
-rw-r--r--cmd/podman/pods/rm.go8
-rw-r--r--cmd/podman/pods/top.go4
-rw-r--r--cmd/podman/registry/config.go5
-rw-r--r--cmd/podman/root.go42
-rw-r--r--cmd/podman/root_test.go4
-rw-r--r--cmd/podman/secrets/create.go4
-rw-r--r--cmd/podman/secrets/inspect.go3
-rw-r--r--cmd/podman/secrets/list.go4
-rw-r--r--cmd/podman/system/connection/add.go125
-rw-r--r--cmd/podman/system/connection/remove.go3
-rw-r--r--cmd/podman/system/connection/shared.go27
-rw-r--r--cmd/podman/system/dial_stdio.go12
-rw-r--r--cmd/podman/system/service_abi.go36
-rw-r--r--cmd/podman/system/unshare.go6
-rw-r--r--cmd/podman/validate/args.go24
-rw-r--r--cmd/podman/volumes/create.go5
-rw-r--r--cmd/podman/volumes/export.go2
-rw-r--r--cmd/podman/volumes/import.go2
-rw-r--r--cmd/podman/volumes/inspect.go3
-rw-r--r--cmd/podman/volumes/list.go4
-rw-r--r--cmd/podman/volumes/reload.go52
-rw-r--r--cmd/podman/volumes/rm.go12
-rw-r--r--cmd/rootlessport/main.go10
-rw-r--r--contrib/cirrus/lib.sh2
-rwxr-xr-xcontrib/cirrus/setup_environment.sh4
-rw-r--r--contrib/podmanimage/stable/Containerfile3
-rw-r--r--contrib/podmanimage/testing/Containerfile3
-rw-r--r--contrib/podmanimage/upstream/Containerfile3
-rw-r--r--docs/source/markdown/podman-create.1.md11
-rw-r--r--docs/source/markdown/podman-machine-init.1.md7
-rw-r--r--docs/source/markdown/podman-machine-inspect.1.md2
-rw-r--r--docs/source/markdown/podman-machine-list.1.md9
-rw-r--r--docs/source/markdown/podman-machine-rm.1.md1
-rw-r--r--docs/source/markdown/podman-machine-set.1.md2
-rw-r--r--docs/source/markdown/podman-machine-ssh.1.md2
-rw-r--r--docs/source/markdown/podman-machine-start.1.md7
-rw-r--r--docs/source/markdown/podman-machine-stop.1.md7
-rw-r--r--docs/source/markdown/podman-machine.1.md9
-rw-r--r--docs/source/markdown/podman-run.1.md11
-rw-r--r--docs/source/markdown/podman-volume-create.1.md3
-rw-r--r--docs/source/markdown/podman-volume-reload.1.md29
-rw-r--r--docs/source/markdown/podman-volume.1.md1
-rw-r--r--go.mod6
-rw-r--r--go.sum31
-rw-r--r--libpod/container_api.go9
-rw-r--r--libpod/container_config.go1
-rw-r--r--libpod/container_exec.go21
-rw-r--r--libpod/container_internal.go35
-rw-r--r--libpod/container_internal_linux.go3
-rw-r--r--libpod/define/volume_inspect.go6
-rw-r--r--libpod/events.go10
-rw-r--r--libpod/events/config.go4
-rw-r--r--libpod/events/events.go4
-rw-r--r--libpod/events/journal_linux.go2
-rw-r--r--libpod/healthcheck.go15
-rw-r--r--libpod/oci_conmon_linux.go201
-rw-r--r--libpod/plugin/volume_api.go18
-rw-r--r--libpod/pod_internal.go2
-rw-r--r--libpod/runtime_ctr.go16
-rw-r--r--libpod/runtime_pod_linux.go35
-rw-r--r--libpod/runtime_volume.go2
-rw-r--r--libpod/runtime_volume_linux.go100
-rw-r--r--libpod/stats.go24
-rw-r--r--libpod/util_linux.go27
-rw-r--r--libpod/volume_internal.go6
-rw-r--r--pkg/api/handlers/compat/containers.go12
-rw-r--r--pkg/api/handlers/compat/containers_stats.go23
-rw-r--r--pkg/api/handlers/libpod/images.go47
-rw-r--r--pkg/api/handlers/swagger/responses.go7
-rw-r--r--pkg/api/server/register_images.go38
-rw-r--r--pkg/bindings/images/images.go20
-rw-r--r--pkg/bindings/images/types.go7
-rw-r--r--pkg/bindings/images/types_remove_options.go15
-rw-r--r--pkg/bindings/images/types_scp_options.go12
-rw-r--r--pkg/bindings/manifests/manifests.go20
-rw-r--r--pkg/bindings/test/manifests_test.go13
-rw-r--r--pkg/criu/criu.go43
-rw-r--r--pkg/criu/criu_linux.go44
-rw-r--r--pkg/criu/criu_unsupported.go8
-rw-r--r--pkg/domain/entities/engine_container.go1
-rw-r--r--pkg/domain/entities/engine_image.go2
-rw-r--r--pkg/domain/entities/events.go10
-rw-r--r--pkg/domain/entities/images.go2
-rw-r--r--pkg/domain/entities/manifest.go15
-rw-r--r--pkg/domain/entities/reports/scp.go5
-rw-r--r--pkg/domain/entities/volumes.go5
-rw-r--r--pkg/domain/filters/volumes.go8
-rw-r--r--pkg/domain/infra/abi/images.go151
-rw-r--r--pkg/domain/infra/abi/pods.go97
-rw-r--r--pkg/domain/infra/abi/system.go4
-rw-r--r--pkg/domain/infra/abi/volumes.go5
-rw-r--r--pkg/domain/infra/tunnel/images.go28
-rw-r--r--pkg/domain/infra/tunnel/volumes.go4
-rw-r--r--pkg/domain/utils/scp.go581
-rw-r--r--pkg/domain/utils/utils_test.go39
-rw-r--r--pkg/machine/qemu/config.go4
-rw-r--r--pkg/machine/qemu/machine.go106
-rw-r--r--pkg/signal/signal_common.go17
-rw-r--r--pkg/signal/signal_linux.go17
-rw-r--r--pkg/signal/signal_linux_mipsx.go17
-rw-r--r--pkg/signal/signal_unix.go11
-rw-r--r--pkg/signal/signal_unsupported.go11
-rw-r--r--pkg/specgen/generate/container_create.go13
-rw-r--r--pkg/specgen/generate/oci.go23
-rw-r--r--pkg/util/mountOpts.go7
-rw-r--r--test/apiv2/12-imagesMore.at13
-rw-r--r--test/apiv2/20-containers.at24
-rw-r--r--test/apiv2/27-containersEvents.at4
-rwxr-xr-xtest/apiv2/test-apiv22
-rw-r--r--test/e2e/checkpoint_test.go87
-rw-r--r--test/e2e/config.go2
-rw-r--r--test/e2e/events_test.go21
-rw-r--r--test/e2e/image_scp_test.go11
-rw-r--r--test/e2e/manifest_test.go28
-rw-r--r--test/e2e/run_volume_test.go8
-rw-r--r--test/e2e/system_df_test.go11
-rw-r--r--test/e2e/system_reset_test.go12
-rw-r--r--test/e2e/volume_ls_test.go31
-rw-r--r--test/e2e/volume_plugin_test.go68
-rw-r--r--test/system/050-stop.bats15
-rw-r--r--test/system/055-rm.bats10
-rw-r--r--test/system/120-load.bats24
-rw-r--r--test/system/150-login.bats2
-rw-r--r--test/system/160-volumes.bats23
-rw-r--r--test/system/200-pod.bats41
-rw-r--r--test/system/500-networking.bats8
-rw-r--r--test/system/helpers.bash31
-rw-r--r--test/testvol/Containerfile9
-rw-r--r--test/testvol/create.go26
-rw-r--r--test/testvol/list.go32
-rw-r--r--test/testvol/main.go23
-rw-r--r--test/testvol/remove.go26
-rw-r--r--test/testvol/util.go29
-rw-r--r--utils/utils.go24
-rw-r--r--utils/utils_supported.go2
-rw-r--r--vendor/github.com/containers/common/libimage/normalize.go38
-rw-r--r--vendor/github.com/containers/common/libimage/platform.go76
-rw-r--r--vendor/github.com/containers/common/libimage/pull.go2
-rw-r--r--vendor/github.com/containers/common/libimage/runtime.go2
-rw-r--r--vendor/github.com/containers/common/pkg/cgroups/blkio.go3
-rw-r--r--vendor/github.com/containers/common/pkg/cgroups/blkio_linux.go161
-rw-r--r--vendor/github.com/containers/common/pkg/cgroups/cgroups.go62
-rw-r--r--vendor/github.com/containers/common/pkg/cgroups/cgroups_linux.go575
-rw-r--r--vendor/github.com/containers/common/pkg/cgroups/cpu.go74
-rw-r--r--vendor/github.com/containers/common/pkg/cgroups/cpu_linux.go100
-rw-r--r--vendor/github.com/containers/common/pkg/cgroups/cpuset.go41
-rw-r--r--vendor/github.com/containers/common/pkg/cgroups/cpuset_linux.go57
-rw-r--r--vendor/github.com/containers/common/pkg/cgroups/memory.go3
-rw-r--r--vendor/github.com/containers/common/pkg/cgroups/memory_linux.go78
-rw-r--r--vendor/github.com/containers/common/pkg/cgroups/pids.go3
-rw-r--r--vendor/github.com/containers/common/pkg/cgroups/pids_linux.go71
-rw-r--r--vendor/github.com/containers/common/pkg/cgroups/systemd.go7
-rw-r--r--vendor/github.com/containers/common/pkg/cgroups/systemd_linux.go167
-rw-r--r--vendor/github.com/containers/common/pkg/cgroups/utils.go176
-rw-r--r--vendor/github.com/containers/common/pkg/cgroups/utils_linux.go146
-rw-r--r--vendor/github.com/containers/common/pkg/parse/parse.go7
-rw-r--r--vendor/github.com/containers/common/pkg/seccomp/filter.go3
-rw-r--r--vendor/github.com/opencontainers/runc/libcontainer/cgroups/cgroups.go15
-rw-r--r--vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/blkio.go311
-rw-r--r--vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/cpu.go129
-rw-r--r--vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/cpuacct.go166
-rw-r--r--vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/cpuset.go245
-rw-r--r--vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/devices.go39
-rw-r--r--vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/error.go15
-rw-r--r--vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/freezer.go158
-rw-r--r--vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/fs.go264
-rw-r--r--vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/hugetlb.go62
-rw-r--r--vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/memory.go348
-rw-r--r--vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/name.go31
-rw-r--r--vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/net_cls.go32
-rw-r--r--vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/net_prio.go30
-rw-r--r--vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/paths.go186
-rw-r--r--vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/perf_event.go24
-rw-r--r--vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/pids.go62
-rw-r--r--vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/rdma.go25
-rw-r--r--vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs2/cpu.go87
-rw-r--r--vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs2/cpuset.go28
-rw-r--r--vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs2/create.go152
-rw-r--r--vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs2/defaultpath.go99
-rw-r--r--vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs2/freezer.go127
-rw-r--r--vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs2/fs2.go271
-rw-r--r--vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs2/hugetlb.go48
-rw-r--r--vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs2/io.go193
-rw-r--r--vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs2/memory.go216
-rw-r--r--vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs2/pids.go72
-rw-r--r--vendor/github.com/opencontainers/runc/libcontainer/cgroups/fscommon/rdma.go121
-rw-r--r--vendor/github.com/opencontainers/runc/libcontainer/cgroups/fscommon/utils.go145
-rw-r--r--vendor/github.com/opencontainers/runc/libcontainer/configs/blkio_device.go8
-rw-r--r--vendor/github.com/opencontainers/runc/libcontainer/configs/config.go3
-rw-r--r--vendor/github.com/opencontainers/runc/libcontainer/configs/mount.go6
-rw-r--r--vendor/github.com/opencontainers/runc/libcontainer/utils/utils.go19
-rw-r--r--vendor/github.com/seccomp/libseccomp-golang/CHANGELOG25
-rw-r--r--vendor/github.com/seccomp/libseccomp-golang/README.md32
-rw-r--r--vendor/github.com/seccomp/libseccomp-golang/SECURITY.md1
-rw-r--r--vendor/github.com/seccomp/libseccomp-golang/seccomp.go15
-rw-r--r--vendor/github.com/seccomp/libseccomp-golang/seccomp_internal.go17
-rw-r--r--vendor/github.com/stretchr/testify/assert/assertion_format.go10
-rw-r--r--vendor/github.com/stretchr/testify/assert/assertion_forward.go20
-rw-r--r--vendor/github.com/stretchr/testify/assert/assertions.go71
-rw-r--r--vendor/github.com/stretchr/testify/require/require.go26
-rw-r--r--vendor/github.com/stretchr/testify/require/require_forward.go20
-rw-r--r--vendor/modules.txt12
279 files changed, 8374 insertions, 1751 deletions
diff --git a/Containerfile-testvol b/Containerfile-testvol
deleted file mode 100644
index 6ff45064b..000000000
--- a/Containerfile-testvol
+++ /dev/null
@@ -1,10 +0,0 @@
-FROM golang:1.15-alpine AS build-img
-COPY ./test/testvol/ /go/src/github.com/containers/podman/cmd/testvol/
-COPY ./vendor /go/src/github.com/containers/podman/vendor/
-WORKDIR /go/src/github.com/containers/podman
-RUN go build -o /testvol ./cmd/testvol
-
-FROM alpine
-COPY --from=build-img /testvol /usr/local/bin
-WORKDIR /
-ENTRYPOINT ["/usr/local/bin/testvol"]
diff --git a/Makefile b/Makefile
index 38ea7a256..b6e143cea 100644
--- a/Makefile
+++ b/Makefile
@@ -51,10 +51,20 @@ BUILDTAGS ?= \
$(shell hack/libsubid_tag.sh) \
exclude_graphdriver_devicemapper \
seccomp
+ifeq ($(shell uname -s),FreeBSD)
+# Use bash for make's shell function - the default shell on FreeBSD
+# has a command builtin is not compatible with the way its used below
+SHELL := $(shell command -v bash)
+endif
PYTHON ?= $(shell command -v python3 python|head -n1)
PKG_MANAGER ?= $(shell command -v dnf yum|head -n1)
# ~/.local/bin is not in PATH on all systems
PRE_COMMIT = $(shell command -v bin/venv/bin/pre-commit ~/.local/bin/pre-commit pre-commit | head -n1)
+ifeq ($(shell uname -s),FreeBSD)
+SED=gsed
+else
+SED=sed
+endif
# This isn't what we actually build; it's a superset, used for target
# dependencies. Basically: all *.go and *.c files, except *_test.go,
@@ -180,7 +190,11 @@ default: all
all: binaries docs
.PHONY: binaries
+ifeq ($(shell uname -s),FreeBSD)
+binaries: podman podman-remote ## Build podman and podman-remote binaries
+else
binaries: podman podman-remote rootlessport ## Build podman, podman-remote and rootlessport binaries
+endif
# Extract text following double-# for targets, as their description for
# the `help` target. Otherwise These simple-substitutions are resolved
@@ -225,11 +239,11 @@ test/checkseccomp/checkseccomp: $(wildcard test/checkseccomp/*.go)
.PHONY: test/testvol/testvol
test/testvol/testvol: $(wildcard test/testvol/*.go)
- $(GOCMD) build $(BUILDFLAGS) $(GO_LDFLAGS) '$(LDFLAGS_PODMAN)' -o $@ ./test/testvol
+ $(GOCMD) build -o $@ ./test/testvol
-.PHONY: volume-plugin-test-image
+.PHONY: volume-plugin-test-img
volume-plugin-test-img:
- podman build -t quay.io/libpod/volume-plugin-test-img -f Containerfile-testvol .
+ ./bin/podman build --network none -t quay.io/libpod/volume-plugin-test-img:$$(date +%Y%m%d) -f ./test/testvol/Containerfile .
.PHONY: test/goecho/goecho
test/goecho/goecho: $(wildcard test/goecho/*.go)
@@ -423,7 +437,7 @@ $(MANPAGES): %: %.md .install.md2man docdir
### replaces "\" at the end of a line with two spaces
### this ensures that manpages are renderd correctly
- @sed -e 's/\((podman[^)]*\.md\(#.*\)\?)\)//g' \
+ @$(SED) -e 's/\((podman[^)]*\.md\(#.*\)\?)\)//g' \
-e 's/\[\(podman[^]]*\)\]/\1/g' \
-e 's/\[\([^]]*\)](http[^)]\+)/\1/g' \
-e 's;<\(/\)\?\(a\|a\s\+[^>]*\|sup\)>;;g' \
@@ -739,7 +753,9 @@ install.bin:
install ${SELINUXOPT} -m 755 bin/podman $(DESTDIR)$(BINDIR)/podman
test -z "${SELINUXOPT}" || chcon --verbose --reference=$(DESTDIR)$(BINDIR)/podman bin/podman
install ${SELINUXOPT} -d -m 755 $(DESTDIR)$(LIBEXECPODMAN)
+ifneq ($(shell uname -s),FreeBSD)
install ${SELINUXOPT} -m 755 bin/rootlessport $(DESTDIR)$(LIBEXECPODMAN)/rootlessport
+endif
test -z "${SELINUXOPT}" || chcon --verbose --reference=$(DESTDIR)$(LIBEXECPODMAN)/rootlessport bin/rootlessport
install ${SELINUXOPT} -m 755 -d ${DESTDIR}${TMPFILESDIR}
install ${SELINUXOPT} -m 644 contrib/tmpfile/podman.conf ${DESTDIR}${TMPFILESDIR}/podman.conf
@@ -753,9 +769,9 @@ install.modules-load: # This should only be used by distros which might use ipta
install.man:
install ${SELINUXOPT} -d -m 755 $(DESTDIR)$(MANDIR)/man1
install ${SELINUXOPT} -d -m 755 $(DESTDIR)$(MANDIR)/man5
- install ${SELINUXOPT} -m 644 $(filter %.1,$(MANPAGES_DEST)) -t $(DESTDIR)$(MANDIR)/man1
- install ${SELINUXOPT} -m 644 $(filter %.5,$(MANPAGES_DEST)) -t $(DESTDIR)$(MANDIR)/man5
- install ${SELINUXOPT} -m 644 docs/source/markdown/links/*1 -t $(DESTDIR)$(MANDIR)/man1
+ install ${SELINUXOPT} -m 644 $(filter %.1,$(MANPAGES_DEST)) $(DESTDIR)$(MANDIR)/man1
+ install ${SELINUXOPT} -m 644 $(filter %.5,$(MANPAGES_DEST)) $(DESTDIR)$(MANDIR)/man5
+ install ${SELINUXOPT} -m 644 docs/source/markdown/links/*1 $(DESTDIR)$(MANDIR)/man1
.PHONY: install.completions
install.completions:
diff --git a/cmd/podman-mac-helper/install.go b/cmd/podman-mac-helper/install.go
index a1b99e66c..7b8753820 100644
--- a/cmd/podman-mac-helper/install.go
+++ b/cmd/podman-mac-helper/install.go
@@ -5,6 +5,7 @@ package main
import (
"bytes"
+ "errors"
"fmt"
"io"
"io/fs"
@@ -14,7 +15,6 @@ import (
"syscall"
"text/template"
- "github.com/pkg/errors"
"github.com/spf13/cobra"
)
@@ -111,7 +111,7 @@ func install(cmd *cobra.Command, args []string) error {
file, err := os.OpenFile(fileName, os.O_WRONLY|os.O_CREATE|os.O_EXCL, rw_r_r)
if err != nil {
- return errors.Wrap(err, "error creating helper plist file")
+ return fmt.Errorf("creating helper plist file: %w", err)
}
defer file.Close()
_, err = buf.WriteTo(file)
@@ -120,7 +120,7 @@ func install(cmd *cobra.Command, args []string) error {
}
if err = runDetectErr("launchctl", "load", fileName); err != nil {
- return errors.Wrap(err, "launchctl failed loading service")
+ return fmt.Errorf("launchctl failed loading service: %w", err)
}
return nil
@@ -133,13 +133,13 @@ func restrictRecursive(targetDir string, until string) error {
return err
}
if info.Mode()&fs.ModeSymlink != 0 {
- return errors.Errorf("symlinks not allowed in helper paths (remove them and rerun): %s", targetDir)
+ return fmt.Errorf("symlinks not allowed in helper paths (remove them and rerun): %s", targetDir)
}
if err = os.Chown(targetDir, 0, 0); err != nil {
- return errors.Wrap(err, "could not update ownership of helper path")
+ return fmt.Errorf("could not update ownership of helper path: %w", err)
}
if err = os.Chmod(targetDir, rwx_rx_rx|fs.ModeSticky); err != nil {
- return errors.Wrap(err, "could not update permissions of helper path")
+ return fmt.Errorf("could not update permissions of helper path: %w", err)
}
targetDir = filepath.Dir(targetDir)
}
@@ -162,7 +162,7 @@ func verifyRootDeep(path string) error {
stat := info.Sys().(*syscall.Stat_t)
if stat.Uid != 0 {
- return errors.Errorf("installation target path must be solely owned by root: %s is not", current)
+ return fmt.Errorf("installation target path must be solely owned by root: %s is not", current)
}
if info.Mode()&fs.ModeSymlink != 0 {
@@ -206,7 +206,7 @@ func installExecutable(user string) (string, error) {
targetDir := filepath.Join(installPrefix, "podman", "helper", user)
if err := os.MkdirAll(targetDir, rwx_rx_rx); err != nil {
- return "", errors.Wrap(err, "could not create helper directory structure")
+ return "", fmt.Errorf("could not create helper directory structure: %w", err)
}
// Correct any incorrect perms on previously existing directories and verify no symlinks
diff --git a/cmd/podman-mac-helper/main.go b/cmd/podman-mac-helper/main.go
index 735d9898f..937cb8433 100644
--- a/cmd/podman-mac-helper/main.go
+++ b/cmd/podman-mac-helper/main.go
@@ -4,6 +4,7 @@
package main
import (
+ "errors"
"fmt"
"io"
"io/ioutil"
@@ -13,7 +14,6 @@ import (
"strconv"
"strings"
- "github.com/pkg/errors"
"github.com/spf13/cobra"
)
@@ -97,7 +97,7 @@ func getUser() (string, string, string, error) {
return "", "", "", fmt.Errorf("invalid uid for user: %s", name)
}
if id == 0 {
- return "", "", "", fmt.Errorf("unexpected root user")
+ return "", "", "", errors.New("unexpected root user")
}
return name, uid, home, nil
diff --git a/cmd/podman-mac-helper/uninstall.go b/cmd/podman-mac-helper/uninstall.go
index f72d0efd1..0c21b27e9 100644
--- a/cmd/podman-mac-helper/uninstall.go
+++ b/cmd/podman-mac-helper/uninstall.go
@@ -9,7 +9,6 @@ import (
"os/exec"
"path/filepath"
- "github.com/pkg/errors"
"github.com/spf13/cobra"
)
@@ -48,13 +47,13 @@ func uninstall(cmd *cobra.Command, args []string) error {
if err := os.Remove(fileName); err != nil {
if !os.IsNotExist(err) {
- return errors.Errorf("could not remove plist file: %s", fileName)
+ return fmt.Errorf("could not remove plist file: %s", fileName)
}
}
helperPath := filepath.Join(installPrefix, "podman", "helper", userName)
if err := os.RemoveAll(helperPath); err != nil {
- return errors.Errorf("could not remove helper binary path: %s", helperPath)
+ return fmt.Errorf("could not remove helper binary path: %s", helperPath)
}
return nil
}
diff --git a/cmd/podman/auto-update.go b/cmd/podman/auto-update.go
index 1dc29530e..88ef0ec88 100644
--- a/cmd/podman/auto-update.go
+++ b/cmd/podman/auto-update.go
@@ -13,7 +13,6 @@ import (
"github.com/containers/podman/v4/cmd/podman/registry"
"github.com/containers/podman/v4/pkg/domain/entities"
"github.com/containers/podman/v4/pkg/errorhandling"
- "github.com/pkg/errors"
"github.com/spf13/cobra"
)
@@ -63,7 +62,7 @@ func init() {
func autoUpdate(cmd *cobra.Command, args []string) error {
if len(args) > 0 {
// Backwards compat. System tests expect this error string.
- return errors.Errorf("`%s` takes no arguments", cmd.CommandPath())
+ return fmt.Errorf("`%s` takes no arguments", cmd.CommandPath())
}
allReports, failures := registry.ContainerEngine().AutoUpdate(registry.GetContext(), autoUpdateOptions.AutoUpdateOptions)
diff --git a/cmd/podman/common/diffChanges.go b/cmd/podman/common/diffChanges.go
index 99b5f1dcd..6d4c7154a 100644
--- a/cmd/podman/common/diffChanges.go
+++ b/cmd/podman/common/diffChanges.go
@@ -6,7 +6,6 @@ import (
"github.com/containers/podman/v4/pkg/domain/entities"
"github.com/containers/storage/pkg/archive"
- "github.com/pkg/errors"
)
type ChangesReportJSON struct {
@@ -26,7 +25,7 @@ func ChangesToJSON(diffs *entities.DiffReport) error {
case archive.ChangeModify:
body.Changed = append(body.Changed, row.Path)
default:
- return errors.Errorf("output kind %q not recognized", row.Kind)
+ return fmt.Errorf("output kind %q not recognized", row.Kind)
}
}
diff --git a/cmd/podman/common/netflags.go b/cmd/podman/common/netflags.go
index 9dfe81d62..e7aa265d1 100644
--- a/cmd/podman/common/netflags.go
+++ b/cmd/podman/common/netflags.go
@@ -1,6 +1,8 @@
package common
import (
+ "errors"
+ "fmt"
"net"
"github.com/containers/common/libnetwork/types"
@@ -10,7 +12,6 @@ import (
"github.com/containers/podman/v4/pkg/domain/entities"
"github.com/containers/podman/v4/pkg/specgen"
"github.com/containers/podman/v4/pkg/specgenutil"
- "github.com/pkg/errors"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
)
@@ -125,13 +126,13 @@ func NetFlagsToNetOptions(opts *entities.NetOptions, flags pflag.FlagSet) (*enti
if d == "none" {
opts.UseImageResolvConf = true
if len(servers) > 1 {
- return nil, errors.Errorf("%s is not allowed to be specified with other DNS ip addresses", d)
+ return nil, fmt.Errorf("%s is not allowed to be specified with other DNS ip addresses", d)
}
break
}
dns := net.ParseIP(d)
if dns == nil {
- return nil, errors.Errorf("%s is not an ip address", d)
+ return nil, fmt.Errorf("%s is not an ip address", d)
}
opts.DNSServers = append(opts.DNSServers, dns)
}
@@ -154,7 +155,7 @@ func NetFlagsToNetOptions(opts *entities.NetOptions, flags pflag.FlagSet) (*enti
for _, dom := range dnsSearches {
if dom == "." {
if len(dnsSearches) > 1 {
- return nil, errors.Errorf("cannot pass additional search domains when also specifying '.'")
+ return nil, errors.New("cannot pass additional search domains when also specifying '.'")
}
continue
}
@@ -218,18 +219,18 @@ func NetFlagsToNetOptions(opts *entities.NetOptions, flags pflag.FlagSet) (*enti
if ip != "" {
// if pod create --infra=false
if infra, err := flags.GetBool("infra"); err == nil && !infra {
- return nil, errors.Wrapf(define.ErrInvalidArg, "cannot set --%s without infra container", ipFlagName)
+ return nil, fmt.Errorf("cannot set --%s without infra container: %w", ipFlagName, define.ErrInvalidArg)
}
staticIP := net.ParseIP(ip)
if staticIP == nil {
- return nil, errors.Errorf("%q is not an ip address", ip)
+ return nil, fmt.Errorf("%q is not an ip address", ip)
}
if !opts.Network.IsBridge() && !opts.Network.IsDefault() {
- return nil, errors.Wrapf(define.ErrInvalidArg, "--%s can only be set when the network mode is bridge", ipFlagName)
+ return nil, fmt.Errorf("--%s can only be set when the network mode is bridge: %w", ipFlagName, define.ErrInvalidArg)
}
if len(opts.Networks) != 1 {
- return nil, errors.Wrapf(define.ErrInvalidArg, "--%s can only be set for a single network", ipFlagName)
+ return nil, fmt.Errorf("--%s can only be set for a single network: %w", ipFlagName, define.ErrInvalidArg)
}
for name, netOpts := range opts.Networks {
netOpts.StaticIPs = append(netOpts.StaticIPs, staticIP)
@@ -245,17 +246,17 @@ func NetFlagsToNetOptions(opts *entities.NetOptions, flags pflag.FlagSet) (*enti
if len(m) > 0 {
// if pod create --infra=false
if infra, err := flags.GetBool("infra"); err == nil && !infra {
- return nil, errors.Wrap(define.ErrInvalidArg, "cannot set --mac without infra container")
+ return nil, fmt.Errorf("cannot set --mac without infra container: %w", define.ErrInvalidArg)
}
mac, err := net.ParseMAC(m)
if err != nil {
return nil, err
}
if !opts.Network.IsBridge() && !opts.Network.IsDefault() {
- return nil, errors.Wrap(define.ErrInvalidArg, "--mac-address can only be set when the network mode is bridge")
+ return nil, fmt.Errorf("--mac-address can only be set when the network mode is bridge: %w", define.ErrInvalidArg)
}
if len(opts.Networks) != 1 {
- return nil, errors.Wrap(define.ErrInvalidArg, "--mac-address can only be set for a single network")
+ return nil, fmt.Errorf("--mac-address can only be set for a single network: %w", define.ErrInvalidArg)
}
for name, netOpts := range opts.Networks {
netOpts.StaticMAC = types.HardwareAddr(mac)
@@ -270,10 +271,10 @@ func NetFlagsToNetOptions(opts *entities.NetOptions, flags pflag.FlagSet) (*enti
if len(aliases) > 0 {
// if pod create --infra=false
if infra, err := flags.GetBool("infra"); err == nil && !infra {
- return nil, errors.Wrap(define.ErrInvalidArg, "cannot set --network-alias without infra container")
+ return nil, fmt.Errorf("cannot set --network-alias without infra container: %w", define.ErrInvalidArg)
}
if !opts.Network.IsBridge() && !opts.Network.IsDefault() {
- return nil, errors.Wrap(define.ErrInvalidArg, "--network-alias can only be set when the network mode is bridge")
+ return nil, fmt.Errorf("--network-alias can only be set when the network mode is bridge: %w", define.ErrInvalidArg)
}
for name, netOpts := range opts.Networks {
netOpts.Aliases = aliases
diff --git a/cmd/podman/containers/attach.go b/cmd/podman/containers/attach.go
index 1a05ea5c3..52adb1fcb 100644
--- a/cmd/podman/containers/attach.go
+++ b/cmd/podman/containers/attach.go
@@ -1,13 +1,13 @@
package containers
import (
+ "errors"
"os"
"github.com/containers/podman/v4/cmd/podman/common"
"github.com/containers/podman/v4/cmd/podman/registry"
"github.com/containers/podman/v4/cmd/podman/validate"
"github.com/containers/podman/v4/pkg/domain/entities"
- "github.com/pkg/errors"
"github.com/spf13/cobra"
)
@@ -70,7 +70,7 @@ func init() {
func attach(cmd *cobra.Command, args []string) error {
if len(args) > 1 || (len(args) == 0 && !attachOpts.Latest) {
- return errors.Errorf("attach requires the name or id of one running container or the latest flag")
+ return errors.New("attach requires the name or id of one running container or the latest flag")
}
var name string
diff --git a/cmd/podman/containers/checkpoint.go b/cmd/podman/containers/checkpoint.go
index e0891f7a1..0eb0db394 100644
--- a/cmd/podman/containers/checkpoint.go
+++ b/cmd/podman/containers/checkpoint.go
@@ -2,6 +2,7 @@ package containers
import (
"context"
+ "errors"
"fmt"
"strings"
"time"
@@ -15,7 +16,6 @@ import (
"github.com/containers/podman/v4/pkg/domain/entities"
"github.com/containers/podman/v4/pkg/rootless"
"github.com/containers/storage/pkg/archive"
- "github.com/pkg/errors"
"github.com/spf13/cobra"
)
@@ -90,7 +90,7 @@ func checkpoint(cmd *cobra.Command, args []string) error {
podmanStart := time.Now()
if cmd.Flags().Changed("compress") {
if checkpointOptions.Export == "" {
- return errors.Errorf("--compress can only be used with --export")
+ return errors.New("--compress can only be used with --export")
}
compress, _ := cmd.Flags().GetString("compress")
switch strings.ToLower(compress) {
@@ -101,7 +101,7 @@ func checkpoint(cmd *cobra.Command, args []string) error {
case "zstd":
checkpointOptions.Compression = archive.Zstd
default:
- return errors.Errorf("Selected compression algorithm (%q) not supported. Please select one from: gzip, none, zstd", compress)
+ return fmt.Errorf("selected compression algorithm (%q) not supported. Please select one from: gzip, none, zstd", compress)
}
} else {
checkpointOptions.Compression = archive.Zstd
@@ -110,13 +110,13 @@ func checkpoint(cmd *cobra.Command, args []string) error {
return errors.New("checkpointing a container requires root")
}
if checkpointOptions.Export == "" && checkpointOptions.IgnoreRootFS {
- return errors.Errorf("--ignore-rootfs can only be used with --export")
+ return errors.New("--ignore-rootfs can only be used with --export")
}
if checkpointOptions.Export == "" && checkpointOptions.IgnoreVolumes {
- return errors.Errorf("--ignore-volumes can only be used with --export")
+ return errors.New("--ignore-volumes can only be used with --export")
}
if checkpointOptions.WithPrevious && checkpointOptions.PreCheckPoint {
- return errors.Errorf("--with-previous can not be used with --pre-checkpoint")
+ return errors.New("--with-previous can not be used with --pre-checkpoint")
}
if (checkpointOptions.WithPrevious || checkpointOptions.PreCheckPoint) && !criu.MemTrack() {
return errors.New("system (architecture/kernel/CRIU) does not support memory tracking")
diff --git a/cmd/podman/containers/cleanup.go b/cmd/podman/containers/cleanup.go
index 18cec097c..c9a5cb28b 100644
--- a/cmd/podman/containers/cleanup.go
+++ b/cmd/podman/containers/cleanup.go
@@ -1,6 +1,7 @@
package containers
import (
+ "errors"
"fmt"
"github.com/containers/common/pkg/completion"
@@ -9,7 +10,6 @@ import (
"github.com/containers/podman/v4/cmd/podman/utils"
"github.com/containers/podman/v4/cmd/podman/validate"
"github.com/containers/podman/v4/pkg/domain/entities"
- "github.com/pkg/errors"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
)
@@ -65,11 +65,11 @@ func cleanup(cmd *cobra.Command, args []string) error {
if cleanupOptions.Exec != "" {
switch {
case cleanupOptions.All:
- return errors.Errorf("exec and all options conflict")
+ return errors.New("exec and all options conflict")
case len(args) > 1:
- return errors.Errorf("cannot use exec option when more than one container is given")
+ return errors.New("cannot use exec option when more than one container is given")
case cleanupOptions.RemoveImage:
- return errors.Errorf("exec and rmi options conflict")
+ return errors.New("exec and rmi options conflict")
}
}
diff --git a/cmd/podman/containers/clone.go b/cmd/podman/containers/clone.go
index 2763adf3c..f8d5a2d80 100644
--- a/cmd/podman/containers/clone.go
+++ b/cmd/podman/containers/clone.go
@@ -7,7 +7,6 @@ import (
"github.com/containers/podman/v4/cmd/podman/registry"
"github.com/containers/podman/v4/libpod/define"
"github.com/containers/podman/v4/pkg/domain/entities"
- "github.com/pkg/errors"
"github.com/spf13/cobra"
)
@@ -56,7 +55,7 @@ func init() {
func clone(cmd *cobra.Command, args []string) error {
switch len(args) {
case 0:
- return errors.Wrapf(define.ErrInvalidArg, "must specify at least 1 argument")
+ return fmt.Errorf("must specify at least 1 argument: %w", define.ErrInvalidArg)
case 2:
ctrClone.CreateOpts.Name = args[1]
case 3:
@@ -73,7 +72,7 @@ func clone(cmd *cobra.Command, args []string) error {
}
}
if ctrClone.Force && !ctrClone.Destroy {
- return errors.Wrapf(define.ErrInvalidArg, "cannot set --force without --destroy")
+ return fmt.Errorf("cannot set --force without --destroy: %w", define.ErrInvalidArg)
}
ctrClone.ID = args[0]
diff --git a/cmd/podman/containers/commit.go b/cmd/podman/containers/commit.go
index e0cadd5b0..fb6dccad4 100644
--- a/cmd/podman/containers/commit.go
+++ b/cmd/podman/containers/commit.go
@@ -11,7 +11,6 @@ import (
"github.com/containers/podman/v4/cmd/podman/common"
"github.com/containers/podman/v4/cmd/podman/registry"
"github.com/containers/podman/v4/pkg/domain/entities"
- "github.com/pkg/errors"
"github.com/spf13/cobra"
)
@@ -109,7 +108,7 @@ func commit(cmd *cobra.Command, args []string) error {
}
if len(iidFile) > 0 {
if err = ioutil.WriteFile(iidFile, []byte(response.Id), 0644); err != nil {
- return errors.Wrap(err, "failed to write image ID")
+ return fmt.Errorf("failed to write image ID: %w", err)
}
}
fmt.Println(response.Id)
diff --git a/cmd/podman/containers/cp.go b/cmd/podman/containers/cp.go
index a5842afc8..c38cba66e 100644
--- a/cmd/podman/containers/cp.go
+++ b/cmd/podman/containers/cp.go
@@ -1,6 +1,7 @@
package containers
import (
+ "fmt"
"io"
"io/ioutil"
"os"
@@ -9,6 +10,8 @@ import (
"strconv"
"strings"
+ "errors"
+
buildahCopiah "github.com/containers/buildah/copier"
"github.com/containers/podman/v4/cmd/podman/common"
"github.com/containers/podman/v4/cmd/podman/registry"
@@ -17,7 +20,6 @@ import (
"github.com/containers/podman/v4/pkg/errorhandling"
"github.com/containers/storage/pkg/archive"
"github.com/containers/storage/pkg/idtools"
- "github.com/pkg/errors"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
)
@@ -102,7 +104,7 @@ func containerMustExist(container string) error {
return err
}
if !exists.Value {
- return errors.Errorf("container %q does not exist", container)
+ return fmt.Errorf("container %q does not exist", container)
}
return nil
}
@@ -131,7 +133,7 @@ func copyContainerToContainer(sourceContainer string, sourcePath string, destCon
sourceContainerInfo, err := registry.ContainerEngine().ContainerStat(registry.GetContext(), sourceContainer, sourcePath)
if err != nil {
- return errors.Wrapf(err, "%q could not be found on container %s", sourcePath, sourceContainer)
+ return fmt.Errorf("%q could not be found on container %s: %w", sourcePath, sourceContainer, err)
}
destContainerBaseName, destContainerInfo, destResolvedToParentDir, err := resolvePathOnDestinationContainer(destContainer, destPath, false)
@@ -170,7 +172,7 @@ func copyContainerToContainer(sourceContainer string, sourcePath string, destCon
return err
}
if err := copyFunc(); err != nil {
- return errors.Wrap(err, "error copying from container")
+ return fmt.Errorf("error copying from container: %w", err)
}
return nil
}
@@ -190,7 +192,7 @@ func copyContainerToContainer(sourceContainer string, sourcePath string, destCon
return err
}
if err := copyFunc(); err != nil {
- return errors.Wrap(err, "error copying to container")
+ return fmt.Errorf("error copying to container: %w", err)
}
return nil
}
@@ -212,7 +214,7 @@ func copyFromContainer(container string, containerPath string, hostPath string)
containerInfo, err := registry.ContainerEngine().ContainerStat(registry.GetContext(), container, containerPath)
if err != nil {
- return errors.Wrapf(err, "%q could not be found on container %s", containerPath, container)
+ return fmt.Errorf("%q could not be found on container %s: %w", containerPath, container, err)
}
var hostBaseName string
@@ -220,13 +222,13 @@ func copyFromContainer(container string, containerPath string, hostPath string)
hostInfo, hostInfoErr := copy.ResolveHostPath(hostPath)
if hostInfoErr != nil {
if strings.HasSuffix(hostPath, "/") {
- return errors.Wrapf(hostInfoErr, "%q could not be found on the host", hostPath)
+ return fmt.Errorf("%q could not be found on the host: %w", hostPath, hostInfoErr)
}
// If it doesn't exist, then let's have a look at the parent dir.
parentDir := filepath.Dir(hostPath)
hostInfo, err = copy.ResolveHostPath(parentDir)
if err != nil {
- return errors.Wrapf(hostInfoErr, "%q could not be found on the host", hostPath)
+ return fmt.Errorf("%q could not be found on the host: %w", hostPath, hostInfoErr)
}
// If the specified path does not exist, we need to assume that
// it'll be created while copying. Hence, we use it as the
@@ -241,7 +243,7 @@ func copyFromContainer(container string, containerPath string, hostPath string)
if !isStdout {
if err := validateFileInfo(hostInfo); err != nil {
- return errors.Wrap(err, "invalid destination")
+ return fmt.Errorf("invalid destination: %w", err)
}
}
@@ -313,7 +315,7 @@ func copyFromContainer(container string, containerPath string, hostPath string)
dir = filepath.Dir(dir)
}
if err := buildahCopiah.Put(dir, "", putOptions, reader); err != nil {
- return errors.Wrap(err, "error copying to host")
+ return fmt.Errorf("error copying to host: %w", err)
}
return nil
}
@@ -325,7 +327,7 @@ func copyFromContainer(container string, containerPath string, hostPath string)
return err
}
if err := copyFunc(); err != nil {
- return errors.Wrap(err, "error copying from container")
+ return fmt.Errorf("error copying from container: %w", err)
}
return nil
}
@@ -347,7 +349,7 @@ func copyToContainer(container string, containerPath string, hostPath string) er
// Make sure that host path exists.
hostInfo, err := copy.ResolveHostPath(hostPath)
if err != nil {
- return errors.Wrapf(err, "%q could not be found on the host", hostPath)
+ return fmt.Errorf("%q could not be found on the host: %w", hostPath, err)
}
containerBaseName, containerInfo, containerResolvedToParentDir, err := resolvePathOnDestinationContainer(container, containerPath, isStdin)
@@ -422,7 +424,7 @@ func copyToContainer(container string, containerPath string, hostPath string) er
getOptions.Rename = map[string]string{filepath.Base(hostTarget): containerBaseName}
}
if err := buildahCopiah.Get("/", "", getOptions, []string{hostTarget}, writer); err != nil {
- return errors.Wrap(err, "error copying from host")
+ return fmt.Errorf("error copying from host: %w", err)
}
return nil
}
@@ -439,7 +441,7 @@ func copyToContainer(container string, containerPath string, hostPath string) er
return err
}
if err := copyFunc(); err != nil {
- return errors.Wrap(err, "error copying to container")
+ return fmt.Errorf("error copying to container: %w", err)
}
return nil
}
@@ -458,7 +460,7 @@ func resolvePathOnDestinationContainer(container string, containerPath string, i
}
if strings.HasSuffix(containerPath, "/") {
- err = errors.Wrapf(err, "%q could not be found on container %s", containerPath, container)
+ err = fmt.Errorf("%q could not be found on container %s: %w", containerPath, container, err)
return
}
if isStdin {
@@ -479,13 +481,13 @@ func resolvePathOnDestinationContainer(container string, containerPath string, i
parentDir, err := containerParentDir(container, path)
if err != nil {
- err = errors.Wrapf(err, "could not determine parent dir of %q on container %s", path, container)
+ err = fmt.Errorf("could not determine parent dir of %q on container %s: %w", path, container, err)
return
}
containerInfo, err = registry.ContainerEngine().ContainerStat(registry.GetContext(), container, parentDir)
if err != nil {
- err = errors.Wrapf(err, "%q could not be found on container %s", containerPath, container)
+ err = fmt.Errorf("%q could not be found on container %s: %w", containerPath, container, err)
return
}
@@ -505,7 +507,7 @@ func containerParentDir(container string, containerPath string) (string, error)
return "", err
}
if len(inspectData) != 1 {
- return "", errors.Errorf("inspecting container %q: expected 1 data item but got %d", container, len(inspectData))
+ return "", fmt.Errorf("inspecting container %q: expected 1 data item but got %d", container, len(inspectData))
}
workDir := filepath.Join("/", inspectData[0].Config.WorkingDir)
workDir = filepath.Join(workDir, containerPath)
@@ -518,5 +520,5 @@ func validateFileInfo(info *copy.FileInfo) error {
if info.Mode.IsDir() || info.Mode.IsRegular() {
return nil
}
- return errors.Errorf("%q must be a directory or a regular file", info.LinkTarget)
+ return fmt.Errorf("%q must be a directory or a regular file", info.LinkTarget)
}
diff --git a/cmd/podman/containers/create.go b/cmd/podman/containers/create.go
index c021aa031..aa7040bcc 100644
--- a/cmd/podman/containers/create.go
+++ b/cmd/podman/containers/create.go
@@ -2,6 +2,7 @@ package containers
import (
"context"
+ "errors"
"fmt"
"os"
"strconv"
@@ -21,7 +22,6 @@ import (
"github.com/containers/podman/v4/pkg/specgenutil"
"github.com/containers/podman/v4/pkg/util"
"github.com/mattn/go-isatty"
- "github.com/pkg/errors"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
)
@@ -128,7 +128,7 @@ func create(cmd *cobra.Command, args []string) error {
return errors.New("must specify pod value with init-ctr")
}
if !cutil.StringInSlice(initctr, []string{define.AlwaysInitContainer, define.OneShotInitContainer}) {
- return errors.Errorf("init-ctr value must be '%s' or '%s'", define.AlwaysInitContainer, define.OneShotInitContainer)
+ return fmt.Errorf("init-ctr value must be '%s' or '%s'", define.AlwaysInitContainer, define.OneShotInitContainer)
}
cliVals.InitContainerType = initctr
}
@@ -218,10 +218,10 @@ func CreateInit(c *cobra.Command, vals entities.ContainerCreateOptions, isInfra
if !isInfra {
if c.Flag("cpu-period").Changed && c.Flag("cpus").Changed {
- return vals, errors.Errorf("--cpu-period and --cpus cannot be set together")
+ return vals, errors.New("--cpu-period and --cpus cannot be set together")
}
if c.Flag("cpu-quota").Changed && c.Flag("cpus").Changed {
- return vals, errors.Errorf("--cpu-quota and --cpus cannot be set together")
+ return vals, errors.New("--cpu-quota and --cpus cannot be set together")
}
vals.IPC = c.Flag("ipc").Value.String()
vals.UTS = c.Flag("uts").Value.String()
@@ -268,30 +268,30 @@ func CreateInit(c *cobra.Command, vals entities.ContainerCreateOptions, isInfra
if c.Flags().Changed("env") {
env, err := c.Flags().GetStringArray("env")
if err != nil {
- return vals, errors.Wrapf(err, "retrieve env flag")
+ return vals, fmt.Errorf("retrieve env flag: %w", err)
}
vals.Env = env
}
if c.Flag("cgroups").Changed && vals.CgroupsMode == "split" && registry.IsRemote() {
- return vals, errors.Errorf("the option --cgroups=%q is not supported in remote mode", vals.CgroupsMode)
+ return vals, fmt.Errorf("the option --cgroups=%q is not supported in remote mode", vals.CgroupsMode)
}
if c.Flag("pod").Changed && !strings.HasPrefix(c.Flag("pod").Value.String(), "new:") && c.Flag("userns").Changed {
- return vals, errors.Errorf("--userns and --pod cannot be set together")
+ return vals, errors.New("--userns and --pod cannot be set together")
}
}
if c.Flag("shm-size").Changed {
vals.ShmSize = c.Flag("shm-size").Value.String()
}
if (c.Flag("dns").Changed || c.Flag("dns-opt").Changed || c.Flag("dns-search").Changed) && vals.Net != nil && (vals.Net.Network.NSMode == specgen.NoNetwork || vals.Net.Network.IsContainer()) {
- return vals, errors.Errorf("conflicting options: dns and the network mode: " + string(vals.Net.Network.NSMode))
+ return vals, fmt.Errorf("conflicting options: dns and the network mode: " + string(vals.Net.Network.NSMode))
}
noHosts, err := c.Flags().GetBool("no-hosts")
if err != nil {
return vals, err
}
if noHosts && c.Flag("add-host").Changed {
- return vals, errors.Errorf("--no-hosts and --add-host cannot be set together")
+ return vals, errors.New("--no-hosts and --add-host cannot be set together")
}
if !isInfra && c.Flag("entrypoint").Changed {
@@ -314,7 +314,7 @@ func PullImage(imageName string, cliVals entities.ContainerCreateOptions) (strin
if cliVals.Platform != "" || cliVals.Arch != "" || cliVals.OS != "" {
if cliVals.Platform != "" {
if cliVals.Arch != "" || cliVals.OS != "" {
- return "", errors.Errorf("--platform option can not be specified with --arch or --os")
+ return "", errors.New("--platform option can not be specified with --arch or --os")
}
split := strings.SplitN(cliVals.Platform, "/", 2)
cliVals.OS = split[0]
@@ -362,7 +362,7 @@ func createPodIfNecessary(cmd *cobra.Command, s *specgen.SpecGenerator, netOpts
}
podName := strings.Replace(s.Pod, "new:", "", 1)
if len(podName) < 1 {
- return errors.Errorf("new pod name must be at least one character")
+ return errors.New("new pod name must be at least one character")
}
var err error
diff --git a/cmd/podman/containers/diff.go b/cmd/podman/containers/diff.go
index 15d3a3eff..ce501f890 100644
--- a/cmd/podman/containers/diff.go
+++ b/cmd/podman/containers/diff.go
@@ -1,13 +1,14 @@
package containers
import (
+ "errors"
+
"github.com/containers/podman/v4/cmd/podman/common"
"github.com/containers/podman/v4/cmd/podman/diff"
"github.com/containers/podman/v4/cmd/podman/registry"
"github.com/containers/podman/v4/cmd/podman/validate"
"github.com/containers/podman/v4/libpod/define"
"github.com/containers/podman/v4/pkg/domain/entities"
- "github.com/pkg/errors"
"github.com/spf13/cobra"
)
diff --git a/cmd/podman/containers/exec.go b/cmd/podman/containers/exec.go
index f5b93a96a..303795489 100644
--- a/cmd/podman/containers/exec.go
+++ b/cmd/podman/containers/exec.go
@@ -2,6 +2,7 @@ package containers
import (
"bufio"
+ "errors"
"fmt"
"os"
@@ -13,7 +14,6 @@ import (
"github.com/containers/podman/v4/pkg/domain/entities"
envLib "github.com/containers/podman/v4/pkg/env"
"github.com/containers/podman/v4/pkg/rootless"
- "github.com/pkg/errors"
"github.com/spf13/cobra"
)
@@ -126,14 +126,14 @@ func exec(_ *cobra.Command, args []string) error {
cliEnv, err := envLib.ParseSlice(envInput)
if err != nil {
- return errors.Wrap(err, "error parsing environment variables")
+ return fmt.Errorf("error parsing environment variables: %w", err)
}
execOpts.Envs = envLib.Join(execOpts.Envs, cliEnv)
for fd := 3; fd < int(3+execOpts.PreserveFDs); fd++ {
if !rootless.IsFdInherited(fd) {
- return errors.Errorf("file descriptor %d is not available - the preserve-fds option requires that file descriptors must be passed", fd)
+ return fmt.Errorf("file descriptor %d is not available - the preserve-fds option requires that file descriptors must be passed", fd)
}
}
diff --git a/cmd/podman/containers/export.go b/cmd/podman/containers/export.go
index 681df93e0..d78bfe960 100644
--- a/cmd/podman/containers/export.go
+++ b/cmd/podman/containers/export.go
@@ -2,6 +2,7 @@ package containers
import (
"context"
+ "errors"
"os"
"github.com/containers/common/pkg/completion"
@@ -9,7 +10,6 @@ import (
"github.com/containers/podman/v4/cmd/podman/parse"
"github.com/containers/podman/v4/cmd/podman/registry"
"github.com/containers/podman/v4/pkg/domain/entities"
- "github.com/pkg/errors"
"github.com/spf13/cobra"
"golang.org/x/term"
)
@@ -70,7 +70,7 @@ func export(cmd *cobra.Command, args []string) error {
if len(exportOpts.Output) == 0 {
file := os.Stdout
if term.IsTerminal(int(file.Fd())) {
- return errors.Errorf("refusing to export to terminal. Use -o flag or redirect")
+ return errors.New("refusing to export to terminal. Use -o flag or redirect")
}
exportOpts.Output = "/dev/stdout"
} else if err := parse.ValidateFileName(exportOpts.Output); err != nil {
diff --git a/cmd/podman/containers/kill.go b/cmd/podman/containers/kill.go
index eddefd196..5a5379389 100644
--- a/cmd/podman/containers/kill.go
+++ b/cmd/podman/containers/kill.go
@@ -2,6 +2,7 @@ package containers
import (
"context"
+ "errors"
"fmt"
"io/ioutil"
"strings"
@@ -13,7 +14,6 @@ import (
"github.com/containers/podman/v4/cmd/podman/validate"
"github.com/containers/podman/v4/pkg/domain/entities"
"github.com/containers/podman/v4/pkg/signal"
- "github.com/pkg/errors"
"github.com/spf13/cobra"
)
@@ -97,7 +97,7 @@ func kill(_ *cobra.Command, args []string) error {
for _, cidFile := range cidFiles {
content, err := ioutil.ReadFile(cidFile)
if err != nil {
- return errors.Wrap(err, "error reading CIDFile")
+ return fmt.Errorf("error reading CIDFile: %w", err)
}
id := strings.Split(string(content), "\n")[0]
args = append(args, id)
diff --git a/cmd/podman/containers/logs.go b/cmd/podman/containers/logs.go
index 374bf6b1c..9a9749210 100644
--- a/cmd/podman/containers/logs.go
+++ b/cmd/podman/containers/logs.go
@@ -1,6 +1,8 @@
package containers
import (
+ "errors"
+ "fmt"
"os"
"github.com/containers/common/pkg/completion"
@@ -9,7 +11,6 @@ import (
"github.com/containers/podman/v4/cmd/podman/validate"
"github.com/containers/podman/v4/pkg/domain/entities"
"github.com/containers/podman/v4/pkg/util"
- "github.com/pkg/errors"
"github.com/spf13/cobra"
)
@@ -125,7 +126,7 @@ func logs(_ *cobra.Command, args []string) error {
// parse time, error out if something is wrong
since, err := util.ParseInputTime(logsOptions.SinceRaw, true)
if err != nil {
- return errors.Wrapf(err, "error parsing --since %q", logsOptions.SinceRaw)
+ return fmt.Errorf("error parsing --since %q: %w", logsOptions.SinceRaw, err)
}
logsOptions.Since = since
}
@@ -133,7 +134,7 @@ func logs(_ *cobra.Command, args []string) error {
// parse time, error out if something is wrong
until, err := util.ParseInputTime(logsOptions.UntilRaw, false)
if err != nil {
- return errors.Wrapf(err, "error parsing --until %q", logsOptions.UntilRaw)
+ return fmt.Errorf("error parsing --until %q: %w", logsOptions.UntilRaw, err)
}
logsOptions.Until = until
}
diff --git a/cmd/podman/containers/mount.go b/cmd/podman/containers/mount.go
index 16eb5d452..a1f0b0cf3 100644
--- a/cmd/podman/containers/mount.go
+++ b/cmd/podman/containers/mount.go
@@ -1,6 +1,7 @@
package containers
import (
+ "errors"
"fmt"
"os"
@@ -10,7 +11,6 @@ import (
"github.com/containers/podman/v4/cmd/podman/utils"
"github.com/containers/podman/v4/cmd/podman/validate"
"github.com/containers/podman/v4/pkg/domain/entities"
- "github.com/pkg/errors"
"github.com/spf13/cobra"
)
@@ -83,7 +83,7 @@ func init() {
func mount(cmd *cobra.Command, args []string) error {
if len(args) > 0 && mountOpts.Latest {
- return errors.Errorf("--latest and containers cannot be used together")
+ return errors.New("--latest and containers cannot be used together")
}
reports, err := registry.ContainerEngine().ContainerMount(registry.GetContext(), args, mountOpts)
if err != nil {
@@ -108,7 +108,7 @@ func mount(cmd *cobra.Command, args []string) error {
case mountOpts.Format == "":
break // print defaults
default:
- return errors.Errorf("unknown --format argument: %q", mountOpts.Format)
+ return fmt.Errorf("unknown --format argument: %q", mountOpts.Format)
}
mrs := make([]mountReporter, 0, len(reports))
diff --git a/cmd/podman/containers/pause.go b/cmd/podman/containers/pause.go
index 9dbe97d11..3c26fd5c8 100644
--- a/cmd/podman/containers/pause.go
+++ b/cmd/podman/containers/pause.go
@@ -2,6 +2,7 @@ package containers
import (
"context"
+ "errors"
"fmt"
"github.com/containers/common/pkg/cgroups"
@@ -10,7 +11,6 @@ import (
"github.com/containers/podman/v4/cmd/podman/utils"
"github.com/containers/podman/v4/pkg/domain/entities"
"github.com/containers/podman/v4/pkg/rootless"
- "github.com/pkg/errors"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
)
@@ -73,7 +73,7 @@ func pause(cmd *cobra.Command, args []string) error {
}
if len(args) < 1 && !pauseOpts.All {
- return errors.Errorf("you must provide at least one container name or id")
+ return errors.New("you must provide at least one container name or id")
}
responses, err := registry.ContainerEngine().ContainerPause(context.Background(), args, pauseOpts)
if err != nil {
diff --git a/cmd/podman/containers/port.go b/cmd/podman/containers/port.go
index fdb2f6c46..74bfdf5c6 100644
--- a/cmd/podman/containers/port.go
+++ b/cmd/podman/containers/port.go
@@ -1,6 +1,7 @@
package containers
import (
+ "errors"
"fmt"
"strconv"
"strings"
@@ -9,7 +10,6 @@ import (
"github.com/containers/podman/v4/cmd/podman/registry"
"github.com/containers/podman/v4/cmd/podman/validate"
"github.com/containers/podman/v4/pkg/domain/entities"
- "github.com/pkg/errors"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
)
@@ -77,14 +77,14 @@ func port(_ *cobra.Command, args []string) error {
)
if len(args) == 0 && !portOpts.Latest && !portOpts.All {
- return errors.Errorf("you must supply a running container name or id")
+ return errors.New("you must supply a running container name or id")
}
if !portOpts.Latest && len(args) >= 1 {
container = args[0]
}
port := ""
if len(args) > 2 {
- return errors.Errorf("`port` accepts at most 2 arguments")
+ return errors.New("`port` accepts at most 2 arguments")
}
if len(args) > 1 && !portOpts.Latest {
port = args[1]
@@ -95,7 +95,7 @@ func port(_ *cobra.Command, args []string) error {
if len(port) > 0 {
fields := strings.Split(port, "/")
if len(fields) > 2 || len(fields) < 1 {
- return errors.Errorf("port formats are port/protocol. '%s' is invalid", port)
+ return fmt.Errorf("port formats are port/protocol. '%s' is invalid", port)
}
if len(fields) == 1 {
fields = append(fields, "tcp")
@@ -149,7 +149,7 @@ func port(_ *cobra.Command, args []string) error {
}
}
if !found && port != "" {
- return errors.Errorf("failed to find published port %q", port)
+ return fmt.Errorf("failed to find published port %q", port)
}
}
return nil
diff --git a/cmd/podman/containers/ps.go b/cmd/podman/containers/ps.go
index a011a8ae6..12b7f5dae 100644
--- a/cmd/podman/containers/ps.go
+++ b/cmd/podman/containers/ps.go
@@ -1,6 +1,7 @@
package containers
import (
+ "errors"
"fmt"
"os"
"strings"
@@ -16,7 +17,6 @@ import (
"github.com/containers/podman/v4/cmd/podman/validate"
"github.com/containers/podman/v4/pkg/domain/entities"
"github.com/docker/go-units"
- "github.com/pkg/errors"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
)
@@ -108,18 +108,18 @@ func listFlagSet(cmd *cobra.Command) {
func checkFlags(c *cobra.Command) error {
// latest, and last are mutually exclusive.
if listOpts.Last >= 0 && listOpts.Latest {
- return errors.Errorf("last and latest are mutually exclusive")
+ return errors.New("last and latest are mutually exclusive")
}
// Quiet conflicts with size and namespace and is overridden by a Go
// template.
if listOpts.Quiet {
if listOpts.Size || listOpts.Namespace {
- return errors.Errorf("quiet conflicts with size and namespace")
+ return errors.New("quiet conflicts with size and namespace")
}
}
// Size and namespace conflict with each other
if listOpts.Size && listOpts.Namespace {
- return errors.Errorf("size and namespace options conflict")
+ return errors.New("size and namespace options conflict")
}
if listOpts.Watch > 0 && listOpts.Latest {
@@ -191,7 +191,7 @@ func ps(cmd *cobra.Command, _ []string) error {
for _, f := range filters {
split := strings.SplitN(f, "=", 2)
if len(split) == 1 {
- return errors.Errorf("invalid filter %q", f)
+ return fmt.Errorf("invalid filter %q", f)
}
listOpts.Filters[split[0]] = append(listOpts.Filters[split[0]], split[1])
}
diff --git a/cmd/podman/containers/rename.go b/cmd/podman/containers/rename.go
index 1c0b28ce3..d78492ae4 100644
--- a/cmd/podman/containers/rename.go
+++ b/cmd/podman/containers/rename.go
@@ -1,10 +1,11 @@
package containers
import (
+ "errors"
+
"github.com/containers/podman/v4/cmd/podman/common"
"github.com/containers/podman/v4/cmd/podman/registry"
"github.com/containers/podman/v4/pkg/domain/entities"
- "github.com/pkg/errors"
"github.com/spf13/cobra"
)
@@ -44,7 +45,7 @@ func init() {
func rename(cmd *cobra.Command, args []string) error {
if len(args) > 2 {
- return errors.Errorf("must provide at least two arguments to rename")
+ return errors.New("must provide at least two arguments to rename")
}
renameOpts := entities.ContainerRenameOptions{
NewName: args[1],
diff --git a/cmd/podman/containers/restart.go b/cmd/podman/containers/restart.go
index 25bbb61e3..9d704d671 100644
--- a/cmd/podman/containers/restart.go
+++ b/cmd/podman/containers/restart.go
@@ -11,7 +11,6 @@ import (
"github.com/containers/podman/v4/cmd/podman/validate"
"github.com/containers/podman/v4/libpod/define"
"github.com/containers/podman/v4/pkg/domain/entities"
- "github.com/pkg/errors"
"github.com/spf13/cobra"
)
@@ -85,10 +84,10 @@ func restart(cmd *cobra.Command, args []string) error {
errs utils.OutputErrors
)
if len(args) < 1 && !restartOptions.Latest && !restartOptions.All {
- return errors.Wrapf(define.ErrInvalidArg, "you must provide at least one container name or ID")
+ return fmt.Errorf("you must provide at least one container name or ID: %w", define.ErrInvalidArg)
}
if len(args) > 0 && restartOptions.Latest {
- return errors.Wrapf(define.ErrInvalidArg, "--latest and containers cannot be used together")
+ return fmt.Errorf("--latest and containers cannot be used together: %w", define.ErrInvalidArg)
}
if cmd.Flag("time").Changed {
diff --git a/cmd/podman/containers/rm.go b/cmd/podman/containers/rm.go
index bcbe86947..9fa688d23 100644
--- a/cmd/podman/containers/rm.go
+++ b/cmd/podman/containers/rm.go
@@ -2,6 +2,7 @@ package containers
import (
"context"
+ "errors"
"fmt"
"io/ioutil"
"strings"
@@ -13,7 +14,6 @@ import (
"github.com/containers/podman/v4/cmd/podman/validate"
"github.com/containers/podman/v4/libpod/define"
"github.com/containers/podman/v4/pkg/domain/entities"
- "github.com/pkg/errors"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
)
@@ -104,7 +104,7 @@ func rm(cmd *cobra.Command, args []string) error {
for _, cidFile := range cidFiles {
content, err := ioutil.ReadFile(cidFile)
if err != nil {
- return errors.Wrap(err, "error reading CIDFile")
+ return fmt.Errorf("error reading CIDFile: %w", err)
}
id := strings.Split(string(content), "\n")[0]
args = append(args, id)
@@ -133,9 +133,7 @@ func removeContainers(namesOrIDs []string, rmOptions entities.RmOptions, setExit
}
for _, r := range responses {
if r.Err != nil {
- // When using the API, errors.Cause(err) will never equal constant define.ErrWillDeadLock
- if errors.Cause(r.Err) == define.ErrWillDeadlock ||
- errors.Cause(r.Err).Error() == define.ErrWillDeadlock.Error() {
+ if errors.Is(r.Err, define.ErrWillDeadlock) {
logrus.Errorf("Potential deadlock detected - please run 'podman system renumber' to resolve")
}
if setExit {
@@ -154,15 +152,9 @@ func setExitCode(err error) {
if registry.GetExitCode() == 1 {
return
}
- cause := errors.Cause(err)
- switch {
- case cause == define.ErrNoSuchCtr:
+ if errors.Is(err, define.ErrNoSuchCtr) || strings.Contains(err.Error(), define.ErrNoSuchCtr.Error()) {
registry.SetExitCode(1)
- case strings.Contains(cause.Error(), define.ErrNoSuchCtr.Error()):
- registry.SetExitCode(1)
- case cause == define.ErrCtrStateInvalid:
- registry.SetExitCode(2)
- case strings.Contains(cause.Error(), define.ErrCtrStateInvalid.Error()):
+ } else if errors.Is(err, define.ErrCtrStateInvalid) || strings.Contains(err.Error(), define.ErrCtrStateInvalid.Error()) {
registry.SetExitCode(2)
}
}
diff --git a/cmd/podman/containers/run.go b/cmd/podman/containers/run.go
index e49bdaee4..1176b866d 100644
--- a/cmd/podman/containers/run.go
+++ b/cmd/podman/containers/run.go
@@ -15,7 +15,6 @@ import (
"github.com/containers/podman/v4/pkg/rootless"
"github.com/containers/podman/v4/pkg/specgen"
"github.com/containers/podman/v4/pkg/specgenutil"
- "github.com/pkg/errors"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"golang.org/x/term"
@@ -134,7 +133,7 @@ func run(cmd *cobra.Command, args []string) error {
for fd := 3; fd < int(3+runOpts.PreserveFDs); fd++ {
if !rootless.IsFdInherited(fd) {
- return errors.Errorf("file descriptor %d is not available - the preserve-fds option requires that file descriptors must be passed", fd)
+ return fmt.Errorf("file descriptor %d is not available - the preserve-fds option requires that file descriptors must be passed", fd)
}
}
@@ -165,7 +164,7 @@ func run(cmd *cobra.Command, args []string) error {
// If attach is set, clear stdin/stdout/stderr and only attach requested
if cmd.Flag("attach").Changed {
if passthrough {
- return errors.Wrapf(define.ErrInvalidArg, "cannot specify --attach with --log-driver=passthrough")
+ return fmt.Errorf("cannot specify --attach with --log-driver=passthrough: %w", define.ErrInvalidArg)
}
runOpts.OutputStream = nil
runOpts.ErrorStream = nil
@@ -182,7 +181,7 @@ func run(cmd *cobra.Command, args []string) error {
case "stdin":
runOpts.InputStream = os.Stdin
default:
- return errors.Wrapf(define.ErrInvalidArg, "invalid stream %q for --attach - must be one of stdin, stdout, or stderr", stream)
+ return fmt.Errorf("invalid stream %q for --attach - must be one of stdin, stdout, or stderr: %w", stream, define.ErrInvalidArg)
}
}
}
diff --git a/cmd/podman/containers/start.go b/cmd/podman/containers/start.go
index b70e975b7..cd4fa17b8 100644
--- a/cmd/podman/containers/start.go
+++ b/cmd/podman/containers/start.go
@@ -1,6 +1,7 @@
package containers
import (
+ "errors"
"fmt"
"os"
"strings"
@@ -11,7 +12,6 @@ import (
"github.com/containers/podman/v4/cmd/podman/validate"
"github.com/containers/podman/v4/libpod/define"
"github.com/containers/podman/v4/pkg/domain/entities"
- "github.com/pkg/errors"
"github.com/spf13/cobra"
)
@@ -88,19 +88,19 @@ func validateStart(cmd *cobra.Command, args []string) error {
return errors.New("start requires at least one argument")
}
if startOptions.All && startOptions.Latest {
- return errors.Errorf("--all and --latest cannot be used together")
+ return errors.New("--all and --latest cannot be used together")
}
if len(args) > 0 && startOptions.Latest {
- return errors.Errorf("--latest and containers cannot be used together")
+ return errors.New("--latest and containers cannot be used together")
}
if len(args) > 1 && startOptions.Attach {
- return errors.Errorf("you cannot start and attach multiple containers at once")
+ return errors.New("you cannot start and attach multiple containers at once")
}
if (len(args) > 0 || startOptions.Latest) && startOptions.All {
- return errors.Errorf("either start all containers or the container(s) provided in the arguments")
+ return errors.New("either start all containers or the container(s) provided in the arguments")
}
if startOptions.Attach && startOptions.All {
- return errors.Errorf("you cannot start and attach all containers at once")
+ return errors.New("you cannot start and attach all containers at once")
}
return nil
}
@@ -114,7 +114,7 @@ func start(cmd *cobra.Command, args []string) error {
startOptions.SigProxy = sigProxy
if sigProxy && !startOptions.Attach {
- return errors.Wrapf(define.ErrInvalidArg, "you cannot use sig-proxy without --attach")
+ return fmt.Errorf("you cannot use sig-proxy without --attach: %w", define.ErrInvalidArg)
}
if startOptions.Attach {
startOptions.Stdin = os.Stdin
@@ -127,7 +127,7 @@ func start(cmd *cobra.Command, args []string) error {
for _, f := range filters {
split := strings.SplitN(f, "=", 2)
if len(split) == 1 {
- return errors.Errorf("invalid filter %q", f)
+ return fmt.Errorf("invalid filter %q", f)
}
startOptions.Filters[split[0]] = append(startOptions.Filters[split[0]], split[1])
}
diff --git a/cmd/podman/containers/stats.go b/cmd/podman/containers/stats.go
index 89c7f2b08..0dd8ce80a 100644
--- a/cmd/podman/containers/stats.go
+++ b/cmd/podman/containers/stats.go
@@ -1,6 +1,7 @@
package containers
import (
+ "errors"
"fmt"
"os"
@@ -14,7 +15,6 @@ import (
"github.com/containers/podman/v4/pkg/domain/entities"
"github.com/containers/podman/v4/utils"
"github.com/docker/go-units"
- "github.com/pkg/errors"
"github.com/spf13/cobra"
)
@@ -105,7 +105,7 @@ func checkStatOptions(cmd *cobra.Command, args []string) error {
opts++
}
if opts > 1 {
- return errors.Errorf("--all, --latest and containers cannot be used together")
+ return errors.New("--all, --latest and containers cannot be used together")
}
return nil
}
diff --git a/cmd/podman/containers/stop.go b/cmd/podman/containers/stop.go
index def608fea..2ddd169a1 100644
--- a/cmd/podman/containers/stop.go
+++ b/cmd/podman/containers/stop.go
@@ -12,7 +12,6 @@ import (
"github.com/containers/podman/v4/cmd/podman/utils"
"github.com/containers/podman/v4/cmd/podman/validate"
"github.com/containers/podman/v4/pkg/domain/entities"
- "github.com/pkg/errors"
"github.com/spf13/cobra"
)
@@ -102,7 +101,7 @@ func stop(cmd *cobra.Command, args []string) error {
for _, cidFile := range cidFiles {
content, err := ioutil.ReadFile(cidFile)
if err != nil {
- return errors.Wrap(err, "error reading CIDFile")
+ return fmt.Errorf("error reading CIDFile: %w", err)
}
id := strings.Split(string(content), "\n")[0]
args = append(args, id)
diff --git a/cmd/podman/containers/top.go b/cmd/podman/containers/top.go
index 389034e37..9340acd9a 100644
--- a/cmd/podman/containers/top.go
+++ b/cmd/podman/containers/top.go
@@ -2,6 +2,7 @@ package containers
import (
"context"
+ "errors"
"fmt"
"os"
"strings"
@@ -12,7 +13,6 @@ import (
"github.com/containers/podman/v4/cmd/podman/validate"
"github.com/containers/podman/v4/pkg/domain/entities"
"github.com/containers/podman/v4/pkg/util"
- "github.com/pkg/errors"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
)
@@ -88,7 +88,7 @@ func top(cmd *cobra.Command, args []string) error {
}
if len(args) < 1 && !topOptions.Latest {
- return errors.Errorf("you must provide the name or id of a running container")
+ return errors.New("you must provide the name or id of a running container")
}
if topOptions.Latest {
diff --git a/cmd/podman/containers/unpause.go b/cmd/podman/containers/unpause.go
index eaf50f2c7..a5375e737 100644
--- a/cmd/podman/containers/unpause.go
+++ b/cmd/podman/containers/unpause.go
@@ -2,6 +2,7 @@ package containers
import (
"context"
+ "errors"
"fmt"
"github.com/containers/common/pkg/cgroups"
@@ -10,7 +11,6 @@ import (
"github.com/containers/podman/v4/cmd/podman/utils"
"github.com/containers/podman/v4/pkg/domain/entities"
"github.com/containers/podman/v4/pkg/rootless"
- "github.com/pkg/errors"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
)
@@ -70,7 +70,7 @@ func unpause(cmd *cobra.Command, args []string) error {
}
}
if len(args) < 1 && !unPauseOptions.All {
- return errors.Errorf("you must provide at least one container name or id")
+ return errors.New("you must provide at least one container name or id")
}
responses, err := registry.ContainerEngine().ContainerUnpause(context.Background(), args, unPauseOptions)
if err != nil {
diff --git a/cmd/podman/containers/wait.go b/cmd/podman/containers/wait.go
index 720a696ce..5b8480781 100644
--- a/cmd/podman/containers/wait.go
+++ b/cmd/podman/containers/wait.go
@@ -2,6 +2,7 @@ package containers
import (
"context"
+ "errors"
"fmt"
"time"
@@ -12,7 +13,6 @@ import (
"github.com/containers/podman/v4/cmd/podman/validate"
"github.com/containers/podman/v4/libpod/define"
"github.com/containers/podman/v4/pkg/domain/entities"
- "github.com/pkg/errors"
"github.com/spf13/cobra"
)
@@ -86,7 +86,7 @@ func wait(cmd *cobra.Command, args []string) error {
}
if !waitOptions.Latest && len(args) == 0 {
- return errors.Errorf("%q requires a name, id, or the \"--latest\" flag", cmd.CommandPath())
+ return fmt.Errorf("%q requires a name, id, or the \"--latest\" flag", cmd.CommandPath())
}
if waitOptions.Latest && len(args) > 0 {
return errors.New("--latest and containers cannot be used together")
diff --git a/cmd/podman/diff/diff.go b/cmd/podman/diff/diff.go
index 15c55852a..06df767d0 100644
--- a/cmd/podman/diff/diff.go
+++ b/cmd/podman/diff/diff.go
@@ -2,6 +2,7 @@ package diff
import (
"encoding/json"
+ "errors"
"fmt"
"os"
@@ -9,7 +10,6 @@ import (
"github.com/containers/podman/v4/cmd/podman/registry"
"github.com/containers/podman/v4/pkg/domain/entities"
"github.com/containers/storage/pkg/archive"
- "github.com/pkg/errors"
"github.com/spf13/cobra"
)
@@ -46,7 +46,7 @@ func changesToJSON(diffs *entities.DiffReport) error {
case archive.ChangeModify:
body.Changed = append(body.Changed, row.Path)
default:
- return errors.Errorf("output kind %q not recognized", row.Kind)
+ return fmt.Errorf("output kind %q not recognized", row.Kind)
}
}
@@ -73,7 +73,7 @@ func ValidateContainerDiffArgs(cmd *cobra.Command, args []string) error {
return errors.New("--latest and containers cannot be used together")
}
if len(args) == 0 && !given {
- return errors.Errorf("%q requires a name, id, or the \"--latest\" flag", cmd.CommandPath())
+ return fmt.Errorf("%q requires a name, id, or the \"--latest\" flag", cmd.CommandPath())
}
return nil
}
diff --git a/cmd/podman/early_init_linux.go b/cmd/podman/early_init_linux.go
index dfd9d1e30..e26c4264e 100644
--- a/cmd/podman/early_init_linux.go
+++ b/cmd/podman/early_init_linux.go
@@ -6,7 +6,6 @@ import (
"syscall"
"github.com/containers/podman/v4/libpod/define"
- "github.com/pkg/errors"
)
func setRLimits() error {
@@ -15,11 +14,11 @@ func setRLimits() error {
rlimits.Max = define.RLimitDefaultValue
if err := syscall.Setrlimit(syscall.RLIMIT_NOFILE, rlimits); err != nil {
if err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, rlimits); err != nil {
- return errors.Wrapf(err, "error getting rlimits")
+ return fmt.Errorf("error getting rlimits: %w", err)
}
rlimits.Cur = rlimits.Max
if err := syscall.Setrlimit(syscall.RLIMIT_NOFILE, rlimits); err != nil {
- return errors.Wrapf(err, "error setting new rlimits")
+ return fmt.Errorf("error setting new rlimits: %w", err)
}
}
return nil
diff --git a/cmd/podman/generate/kube.go b/cmd/podman/generate/kube.go
index c4c92799c..7bfc3dcf7 100644
--- a/cmd/podman/generate/kube.go
+++ b/cmd/podman/generate/kube.go
@@ -11,7 +11,6 @@ import (
"github.com/containers/podman/v4/cmd/podman/registry"
"github.com/containers/podman/v4/cmd/podman/utils"
"github.com/containers/podman/v4/pkg/domain/entities"
- "github.com/pkg/errors"
"github.com/spf13/cobra"
)
@@ -68,10 +67,10 @@ func kube(cmd *cobra.Command, args []string) error {
if cmd.Flags().Changed("filename") {
if _, err := os.Stat(kubeFile); err == nil {
- return errors.Errorf("cannot write to %q; file exists", kubeFile)
+ return fmt.Errorf("cannot write to %q; file exists", kubeFile)
}
if err := ioutil.WriteFile(kubeFile, content, 0644); err != nil {
- return errors.Wrapf(err, "cannot write to %q", kubeFile)
+ return fmt.Errorf("cannot write to %q: %w", kubeFile, err)
}
return nil
}
diff --git a/cmd/podman/generate/systemd.go b/cmd/podman/generate/systemd.go
index 0dab6299d..1ece64a30 100644
--- a/cmd/podman/generate/systemd.go
+++ b/cmd/podman/generate/systemd.go
@@ -2,6 +2,7 @@ package pods
import (
"encoding/json"
+ "errors"
"fmt"
"os"
"path/filepath"
@@ -13,7 +14,6 @@ import (
"github.com/containers/podman/v4/cmd/podman/utils"
"github.com/containers/podman/v4/pkg/domain/entities"
systemDefine "github.com/containers/podman/v4/pkg/systemd/define"
- "github.com/pkg/errors"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
)
@@ -156,7 +156,7 @@ func systemd(cmd *cobra.Command, args []string) error {
if files {
cwd, err := os.Getwd()
if err != nil {
- return errors.Wrap(err, "error getting current working directory")
+ return fmt.Errorf("error getting current working directory: %w", err)
}
for name, content := range reports.Units {
path := filepath.Join(cwd, fmt.Sprintf("%s.service", name))
@@ -189,7 +189,7 @@ func systemd(cmd *cobra.Command, args []string) error {
case format == "":
return printDefault(reports.Units)
default:
- return errors.Errorf("unknown --format argument: %s", format)
+ return fmt.Errorf("unknown --format argument: %s", format)
}
}
diff --git a/cmd/podman/images/build.go b/cmd/podman/images/build.go
index 94b7c43a2..9f1b86eb4 100644
--- a/cmd/podman/images/build.go
+++ b/cmd/podman/images/build.go
@@ -1,6 +1,7 @@
package images
import (
+ "errors"
"fmt"
"io"
"io/ioutil"
@@ -23,7 +24,6 @@ import (
"github.com/containers/podman/v4/cmd/podman/registry"
"github.com/containers/podman/v4/cmd/podman/utils"
"github.com/containers/podman/v4/pkg/domain/entities"
- "github.com/pkg/errors"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
)
@@ -222,7 +222,7 @@ func build(cmd *cobra.Command, args []string) error {
// The context directory could be a URL. Try to handle that.
tempDir, subDir, err := buildahDefine.TempDirForURL("", "buildah", args[0])
if err != nil {
- return errors.Wrapf(err, "error prepping temporary context directory")
+ return fmt.Errorf("error prepping temporary context directory: %w", err)
}
if tempDir != "" {
// We had to download it to a temporary directory.
@@ -237,7 +237,7 @@ func build(cmd *cobra.Command, args []string) error {
// Nope, it was local. Use it as is.
absDir, err := filepath.Abs(args[0])
if err != nil {
- return errors.Wrapf(err, "error determining path to directory %q", args[0])
+ return fmt.Errorf("error determining path to directory %q: %w", args[0], err)
}
contextDir = absDir
}
@@ -253,7 +253,7 @@ func build(cmd *cobra.Command, args []string) error {
}
absFile, err := filepath.Abs(containerFiles[i])
if err != nil {
- return errors.Wrapf(err, "error determining path to file %q", containerFiles[i])
+ return fmt.Errorf("error determining path to file %q: %w", containerFiles[i], err)
}
contextDir = filepath.Dir(absFile)
containerFiles[i] = absFile
@@ -262,10 +262,10 @@ func build(cmd *cobra.Command, args []string) error {
}
if contextDir == "" {
- return errors.Errorf("no context directory and no Containerfile specified")
+ return errors.New("no context directory and no Containerfile specified")
}
if !utils.IsDir(contextDir) {
- return errors.Errorf("context must be a directory: %q", contextDir)
+ return fmt.Errorf("context must be a directory: %q", contextDir)
}
if len(containerFiles) == 0 {
if utils.FileExists(filepath.Join(contextDir, "Containerfile")) {
@@ -296,14 +296,15 @@ func build(cmd *cobra.Command, args []string) error {
if registry.IsRemote() {
// errors from server does not contain ExitCode
// so parse exit code from error message
- remoteExitCode, parseErr := utils.ExitCodeFromBuildError(fmt.Sprint(errors.Cause(err)))
+ remoteExitCode, parseErr := utils.ExitCodeFromBuildError(err.Error())
if parseErr == nil {
exitCode = remoteExitCode
}
}
- if ee, ok := (errors.Cause(err)).(*exec.ExitError); ok {
- exitCode = ee.ExitCode()
+ exitError := &exec.ExitError{}
+ if errors.As(err, &exitError) {
+ exitCode = exitError.ExitCode()
}
registry.SetExitCode(exitCode)
@@ -356,7 +357,7 @@ func buildFlagsWrapperToOptions(c *cobra.Command, contextDir string, flags *buil
}
if pullFlagsCount > 1 {
- return nil, errors.Errorf("can only set one of 'pull' or 'pull-always' or 'pull-never'")
+ return nil, errors.New("can only set one of 'pull' or 'pull-always' or 'pull-never'")
}
// Allow for --pull, --pull=true, --pull=false, --pull=never, --pull=always
@@ -477,7 +478,7 @@ func buildFlagsWrapperToOptions(c *cobra.Command, contextDir string, flags *buil
case strings.HasPrefix(flags.Format, buildahDefine.DOCKER):
format = buildahDefine.Dockerv2ImageManifest
default:
- return nil, errors.Errorf("unrecognized image type %q", flags.Format)
+ return nil, fmt.Errorf("unrecognized image type %q", flags.Format)
}
runtimeFlags := []string{}
@@ -500,7 +501,7 @@ func buildFlagsWrapperToOptions(c *cobra.Command, contextDir string, flags *buil
decConfig, err := getDecryptConfig(flags.DecryptionKeys)
if err != nil {
- return nil, errors.Wrapf(err, "unable to obtain decrypt config")
+ return nil, fmt.Errorf("unable to obtain decrypt config: %w", err)
}
additionalBuildContext := make(map[string]*buildahDefine.AdditionalBuildContext)
@@ -510,7 +511,7 @@ func buildFlagsWrapperToOptions(c *cobra.Command, contextDir string, flags *buil
if len(av) > 1 {
parseAdditionalBuildContext, err := parse.GetAdditionalBuildContext(av[1])
if err != nil {
- return nil, errors.Wrapf(err, "while parsing additional build context")
+ return nil, fmt.Errorf("while parsing additional build context: %w", err)
}
additionalBuildContext[av[0]] = &parseAdditionalBuildContext
} else {
@@ -580,7 +581,7 @@ func buildFlagsWrapperToOptions(c *cobra.Command, contextDir string, flags *buil
if flags.IgnoreFile != "" {
excludes, err := parseDockerignore(flags.IgnoreFile)
if err != nil {
- return nil, errors.Wrapf(err, "unable to obtain decrypt config")
+ return nil, fmt.Errorf("unable to obtain decrypt config: %w", err)
}
opts.Excludes = excludes
}
@@ -599,7 +600,7 @@ func getDecryptConfig(decryptionKeys []string) (*encconfig.DecryptConfig, error)
// decryption
dcc, err := enchelpers.CreateCryptoConfig([]string{}, decryptionKeys)
if err != nil {
- return nil, errors.Wrapf(err, "invalid decryption keys")
+ return nil, fmt.Errorf("invalid decryption keys: %w", err)
}
cc := encconfig.CombineCryptoConfigs([]encconfig.CryptoConfig{dcc})
decConfig = cc.DecryptConfig
diff --git a/cmd/podman/images/history.go b/cmd/podman/images/history.go
index e190941e7..8f910f82d 100644
--- a/cmd/podman/images/history.go
+++ b/cmd/podman/images/history.go
@@ -13,7 +13,6 @@ import (
"github.com/containers/podman/v4/cmd/podman/utils"
"github.com/containers/podman/v4/pkg/domain/entities"
"github.com/docker/go-units"
- "github.com/pkg/errors"
"github.com/spf13/cobra"
)
@@ -132,7 +131,7 @@ func history(cmd *cobra.Command, args []string) error {
})
if err := rpt.Execute(hdrs); err != nil {
- return errors.Wrapf(err, "failed to write report column headers")
+ return fmt.Errorf("failed to write report column headers: %w", err)
}
}
return rpt.Execute(hr)
diff --git a/cmd/podman/images/import.go b/cmd/podman/images/import.go
index 1910fef6d..8343a0bda 100644
--- a/cmd/podman/images/import.go
+++ b/cmd/podman/images/import.go
@@ -2,6 +2,7 @@ package images
import (
"context"
+ "errors"
"fmt"
"io"
"io/ioutil"
@@ -14,7 +15,6 @@ import (
"github.com/containers/podman/v4/cmd/podman/registry"
"github.com/containers/podman/v4/pkg/domain/entities"
"github.com/hashicorp/go-multierror"
- "github.com/pkg/errors"
"github.com/spf13/cobra"
)
@@ -102,7 +102,7 @@ func importCon(cmd *cobra.Command, args []string) error {
)
switch len(args) {
case 0:
- return errors.Errorf("need to give the path to the tarball, or must specify a tarball of '-' for stdin")
+ return errors.New("need to give the path to the tarball, or must specify a tarball of '-' for stdin")
case 1:
source = args[0]
case 2:
@@ -112,20 +112,20 @@ func importCon(cmd *cobra.Command, args []string) error {
// instead of the localhost ones
reference = args[1]
default:
- return errors.Errorf("too many arguments. Usage TARBALL [REFERENCE]")
+ return errors.New("too many arguments. Usage TARBALL [REFERENCE]")
}
if source == "-" {
outFile, err := ioutil.TempFile("", "podman")
if err != nil {
- return errors.Errorf("creating file %v", err)
+ return fmt.Errorf("creating file %v", err)
}
defer os.Remove(outFile.Name())
defer outFile.Close()
_, err = io.Copy(outFile, os.Stdin)
if err != nil {
- return errors.Errorf("copying file %v", err)
+ return fmt.Errorf("copying file %v", err)
}
source = outFile.Name()
}
diff --git a/cmd/podman/images/list.go b/cmd/podman/images/list.go
index 81011f9b1..94d8412e5 100644
--- a/cmd/podman/images/list.go
+++ b/cmd/podman/images/list.go
@@ -1,6 +1,7 @@
package images
import (
+ "errors"
"fmt"
"os"
"sort"
@@ -15,7 +16,6 @@ import (
"github.com/containers/podman/v4/cmd/podman/registry"
"github.com/containers/podman/v4/pkg/domain/entities"
"github.com/docker/go-units"
- "github.com/pkg/errors"
"github.com/spf13/cobra"
)
@@ -225,7 +225,7 @@ func sortImages(imageS []*entities.ImageSummary) ([]imageReporter, error) {
h.ImageSummary = *e
h.Repository, h.Tag, err = tokenRepoTag(tag)
if err != nil {
- return nil, errors.Wrapf(err, "error parsing repository tag: %q", tag)
+ return nil, fmt.Errorf("error parsing repository tag: %q: %w", tag, err)
}
if h.Tag == "<none>" {
untagged = append(untagged, h)
diff --git a/cmd/podman/images/load.go b/cmd/podman/images/load.go
index c18c32387..367b628c7 100644
--- a/cmd/podman/images/load.go
+++ b/cmd/podman/images/load.go
@@ -2,6 +2,7 @@ package images
import (
"context"
+ "errors"
"fmt"
"io"
"io/ioutil"
@@ -14,7 +15,6 @@ import (
"github.com/containers/podman/v4/cmd/podman/validate"
"github.com/containers/podman/v4/pkg/domain/entities"
"github.com/containers/podman/v4/pkg/util"
- "github.com/pkg/errors"
"github.com/spf13/cobra"
"golang.org/x/term"
)
@@ -91,18 +91,18 @@ func load(cmd *cobra.Command, args []string) error {
}
} else {
if term.IsTerminal(int(os.Stdin.Fd())) {
- return errors.Errorf("cannot read from terminal, use command-line redirection or the --input flag")
+ return errors.New("cannot read from terminal, use command-line redirection or the --input flag")
}
outFile, err := ioutil.TempFile(util.Tmpdir(), "podman")
if err != nil {
- return errors.Errorf("creating file %v", err)
+ return fmt.Errorf("creating file %v", err)
}
defer os.Remove(outFile.Name())
defer outFile.Close()
_, err = io.Copy(outFile, os.Stdin)
if err != nil {
- return errors.Errorf("copying file %v", err)
+ return fmt.Errorf("copying file %v", err)
}
loadOpts.Input = outFile.Name()
}
diff --git a/cmd/podman/images/mount.go b/cmd/podman/images/mount.go
index 532d96196..cd54e24ae 100644
--- a/cmd/podman/images/mount.go
+++ b/cmd/podman/images/mount.go
@@ -1,6 +1,7 @@
package images
import (
+ "errors"
"fmt"
"os"
@@ -8,7 +9,6 @@ import (
"github.com/containers/podman/v4/cmd/podman/common"
"github.com/containers/podman/v4/cmd/podman/registry"
"github.com/containers/podman/v4/pkg/domain/entities"
- "github.com/pkg/errors"
"github.com/spf13/cobra"
)
@@ -84,7 +84,7 @@ func mount(cmd *cobra.Command, args []string) error {
case mountOpts.Format == "":
break // see default format below
default:
- return errors.Errorf("unknown --format argument: %q", mountOpts.Format)
+ return fmt.Errorf("unknown --format argument: %q", mountOpts.Format)
}
mrs := make([]mountReporter, 0, len(reports))
diff --git a/cmd/podman/images/pull.go b/cmd/podman/images/pull.go
index a7da5518a..6e3ec1517 100644
--- a/cmd/podman/images/pull.go
+++ b/cmd/podman/images/pull.go
@@ -1,6 +1,7 @@
package images
import (
+ "errors"
"fmt"
"os"
"strings"
@@ -13,7 +14,6 @@ import (
"github.com/containers/podman/v4/cmd/podman/utils"
"github.com/containers/podman/v4/pkg/domain/entities"
"github.com/containers/podman/v4/pkg/util"
- "github.com/pkg/errors"
"github.com/spf13/cobra"
)
@@ -138,7 +138,7 @@ func imagePull(cmd *cobra.Command, args []string) error {
}
if platform != "" {
if pullOptions.Arch != "" || pullOptions.OS != "" {
- return errors.Errorf("--platform option can not be specified with --arch or --os")
+ return errors.New("--platform option can not be specified with --arch or --os")
}
split := strings.SplitN(platform, "/", 2)
pullOptions.OS = split[0]
diff --git a/cmd/podman/images/rm.go b/cmd/podman/images/rm.go
index 13dab62d4..18b22e51d 100644
--- a/cmd/podman/images/rm.go
+++ b/cmd/podman/images/rm.go
@@ -1,13 +1,13 @@
package images
import (
+ "errors"
"fmt"
"github.com/containers/podman/v4/cmd/podman/common"
"github.com/containers/podman/v4/cmd/podman/registry"
"github.com/containers/podman/v4/pkg/domain/entities"
"github.com/containers/podman/v4/pkg/errorhandling"
- "github.com/pkg/errors"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
)
@@ -62,10 +62,10 @@ func imageRemoveFlagSet(flags *pflag.FlagSet) {
func rm(cmd *cobra.Command, args []string) error {
if len(args) < 1 && !imageOpts.All {
- return errors.Errorf("image name or ID must be specified")
+ return errors.New("image name or ID must be specified")
}
if len(args) > 0 && imageOpts.All {
- return errors.Errorf("when using the --all switch, you may not pass any images names or IDs")
+ return errors.New("when using the --all switch, you may not pass any images names or IDs")
}
// Note: certain image-removal errors are non-fatal. Hence, the report
diff --git a/cmd/podman/images/save.go b/cmd/podman/images/save.go
index d85d688ee..43366e1b3 100644
--- a/cmd/podman/images/save.go
+++ b/cmd/podman/images/save.go
@@ -2,6 +2,8 @@ package images
import (
"context"
+ "errors"
+ "fmt"
"os"
"strings"
@@ -12,7 +14,6 @@ import (
"github.com/containers/podman/v4/cmd/podman/registry"
"github.com/containers/podman/v4/libpod/define"
"github.com/containers/podman/v4/pkg/domain/entities"
- "github.com/pkg/errors"
"github.com/spf13/cobra"
"golang.org/x/term"
)
@@ -31,14 +32,14 @@ var (
RunE: save,
Args: func(cmd *cobra.Command, args []string) error {
if len(args) == 0 {
- return errors.Errorf("need at least 1 argument")
+ return errors.New("need at least 1 argument")
}
format, err := cmd.Flags().GetString("format")
if err != nil {
return err
}
if !util.StringInSlice(format, common.ValidSaveFormats) {
- return errors.Errorf("format value must be one of %s", strings.Join(common.ValidSaveFormats, " "))
+ return fmt.Errorf("format value must be one of %s", strings.Join(common.ValidSaveFormats, " "))
}
return nil
},
@@ -103,13 +104,13 @@ func save(cmd *cobra.Command, args []string) (finalErr error) {
succeeded = false
)
if cmd.Flag("compress").Changed && (saveOpts.Format != define.OCIManifestDir && saveOpts.Format != define.V2s2ManifestDir) {
- return errors.Errorf("--compress can only be set when --format is either 'oci-dir' or 'docker-dir'")
+ return errors.New("--compress can only be set when --format is either 'oci-dir' or 'docker-dir'")
}
if len(saveOpts.Output) == 0 {
saveOpts.Quiet = true
fi := os.Stdout
if term.IsTerminal(int(fi.Fd())) {
- return errors.Errorf("refusing to save to terminal. Use -o flag or redirect")
+ return errors.New("refusing to save to terminal. Use -o flag or redirect")
}
pipePath, cleanup, err := setupPipe()
if err != nil {
diff --git a/cmd/podman/images/scp.go b/cmd/podman/images/scp.go
index 3dbc9c331..a7aa43e61 100644
--- a/cmd/podman/images/scp.go
+++ b/cmd/podman/images/scp.go
@@ -1,28 +1,12 @@
package images
import (
- "context"
- "fmt"
- "io/ioutil"
- urlP "net/url"
"os"
- "os/exec"
- "os/user"
- "strconv"
"strings"
- "github.com/containers/common/pkg/config"
"github.com/containers/podman/v4/cmd/podman/common"
"github.com/containers/podman/v4/cmd/podman/registry"
- "github.com/containers/podman/v4/cmd/podman/system/connection"
- "github.com/containers/podman/v4/libpod/define"
- "github.com/containers/podman/v4/pkg/domain/entities"
- "github.com/containers/podman/v4/utils"
- scpD "github.com/dtylman/scp"
- "github.com/pkg/errors"
- "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
- "golang.org/x/crypto/ssh"
)
var (
@@ -32,7 +16,6 @@ var (
Annotations: map[string]string{
registry.UnshareNSRequired: "",
registry.ParentNSRequired: "",
- registry.EngineMode: registry.ABIMode,
},
Long: saveScpDescription,
Short: "securely copy images",
@@ -46,9 +29,6 @@ var (
var (
parentFlags []string
quiet bool
- source entities.ImageScpOptions
- dest entities.ImageScpOptions
- sshInfo entities.ImageScpConnections
)
func init() {
@@ -66,7 +46,6 @@ func scpFlags(cmd *cobra.Command) {
func scp(cmd *cobra.Command, args []string) (finalErr error) {
var (
- // TODO add tag support for images
err error
)
for i, val := range os.Args {
@@ -81,288 +60,17 @@ func scp(cmd *cobra.Command, args []string) (finalErr error) {
}
parentFlags = append(parentFlags, val)
}
- podman, err := os.Executable()
- if err != nil {
- return err
- }
- f, err := ioutil.TempFile("", "podman") // open temp file for load/save output
- if err != nil {
- return err
- }
- confR, err := config.NewConfig("") // create a hand made config for the remote engine since we might use remote and native at once
- if err != nil {
- return errors.Wrapf(err, "could not make config")
- }
-
- abiEng, err := registry.NewImageEngine(cmd, args) // abi native engine
- if err != nil {
- return err
- }
-
- cfg, err := config.ReadCustomConfig() // get ready to set ssh destination if necessary
- if err != nil {
- return err
- }
- locations := []*entities.ImageScpOptions{}
- cliConnections := []string{}
- var flipConnections bool
- for _, arg := range args {
- loc, connect, err := parseImageSCPArg(arg)
- if err != nil {
- return err
- }
- locations = append(locations, loc)
- cliConnections = append(cliConnections, connect...)
- }
- source = *locations[0]
- switch {
- case len(locations) > 1:
- if flipConnections, err = validateSCPArgs(locations); err != nil {
- return err
- }
- if flipConnections { // the order of cliConnections matters, we need to flip both arrays since the args are parsed separately sometimes.
- cliConnections[0], cliConnections[1] = cliConnections[1], cliConnections[0]
- locations[0], locations[1] = locations[1], locations[0]
- }
- dest = *locations[1]
- case len(locations) == 1:
- switch {
- case len(locations[0].Image) == 0:
- return errors.Wrapf(define.ErrInvalidArg, "no source image specified")
- case len(locations[0].Image) > 0 && !locations[0].Remote && len(locations[0].User) == 0: // if we have podman image scp $IMAGE
- return errors.Wrapf(define.ErrInvalidArg, "must specify a destination")
- }
- }
-
- source.Quiet = quiet
- source.File = f.Name() // after parsing the arguments, set the file for the save/load
- dest.File = source.File
- if err = os.Remove(source.File); err != nil { // remove the file and simply use its name so podman creates the file upon save. avoids umask errors
- return err
- }
-
- allLocal := true // if we are all localhost, do not validate connections but if we are using one localhost and one non we need to use sshd
- for _, val := range cliConnections {
- if !strings.Contains(val, "@localhost::") {
- allLocal = false
- break
- }
- }
- if allLocal {
- cliConnections = []string{}
- }
-
- var serv map[string]config.Destination
- serv, err = GetServiceInformation(cliConnections, cfg)
- if err != nil {
- return err
- }
-
- // TODO: Add podman remote support
- confR.Engine = config.EngineConfig{Remote: true, CgroupManager: "cgroupfs", ServiceDestinations: serv} // pass the service dest (either remote or something else) to engine
- saveCmd, loadCmd := createCommands(podman)
- switch {
- case source.Remote: // if we want to load FROM the remote, dest can either be local or remote in this case
- err = saveToRemote(source.Image, source.File, "", sshInfo.URI[0], sshInfo.Identities[0])
- if err != nil {
- return err
- }
- if dest.Remote { // we want to load remote -> remote, both source and dest are remote
- rep, err := loadToRemote(dest.File, "", sshInfo.URI[1], sshInfo.Identities[1])
- if err != nil {
- return err
- }
- fmt.Println(rep)
- break
- }
- err = execPodman(podman, loadCmd)
- if err != nil {
- return err
- }
- case dest.Remote: // remote host load, implies source is local
- err = execPodman(podman, saveCmd)
- if err != nil {
- return err
- }
- rep, err := loadToRemote(source.File, "", sshInfo.URI[0], sshInfo.Identities[0])
- if err != nil {
- return err
- }
- fmt.Println(rep)
- if err = os.Remove(source.File); err != nil {
- return err
- }
- // TODO: Add podman remote support
- default: // else native load, both source and dest are local and transferring between users
- if source.User == "" { // source user has to be set, destination does not
- source.User = os.Getenv("USER")
- if source.User == "" {
- u, err := user.Current()
- if err != nil {
- return errors.Wrapf(err, "could not obtain user, make sure the environmental variable $USER is set")
- }
- source.User = u.Username
- }
- }
- err := abiEng.Transfer(context.Background(), source, dest, parentFlags)
- if err != nil {
- return err
- }
- }
- return nil
-}
-
-// loadToRemote takes image and remote connection information. it connects to the specified client
-// and copies the saved image dir over to the remote host and then loads it onto the machine
-// returns a string containing output or an error
-func loadToRemote(localFile string, tag string, url *urlP.URL, iden string) (string, error) {
- dial, remoteFile, err := createConnection(url, iden)
- if err != nil {
- return "", err
- }
- defer dial.Close()
-
- n, err := scpD.CopyTo(dial, localFile, remoteFile)
- if err != nil {
- errOut := strconv.Itoa(int(n)) + " Bytes copied before error"
- return " ", errors.Wrapf(err, errOut)
- }
- var run string
- if tag != "" {
- return "", errors.Wrapf(define.ErrInvalidArg, "Renaming of an image is currently not supported")
- }
- podman := os.Args[0]
- run = podman + " image load --input=" + remoteFile + ";rm " + remoteFile // run ssh image load of the file copied via scp
- out, err := connection.ExecRemoteCommand(dial, run)
- if err != nil {
- return "", err
+ src := args[0]
+ dst := ""
+ if len(args) > 1 {
+ dst = args[1]
}
- return strings.TrimSuffix(string(out), "\n"), nil
-}
-
-// saveToRemote takes image information and remote connection information. it connects to the specified client
-// and saves the specified image on the remote machine and then copies it to the specified local location
-// returns an error if one occurs.
-func saveToRemote(image, localFile string, tag string, uri *urlP.URL, iden string) error {
- dial, remoteFile, err := createConnection(uri, iden)
+ err = registry.ImageEngine().Scp(registry.Context(), src, dst, parentFlags, quiet)
if err != nil {
return err
}
- defer dial.Close()
- if tag != "" {
- return errors.Wrapf(define.ErrInvalidArg, "Renaming of an image is currently not supported")
- }
- podman := os.Args[0]
- run := podman + " image save " + image + " --format=oci-archive --output=" + remoteFile // run ssh image load of the file copied via scp. Files are reverse in this case...
- _, err = connection.ExecRemoteCommand(dial, run)
- if err != nil {
- return err
- }
- n, err := scpD.CopyFrom(dial, remoteFile, localFile)
- if _, conErr := connection.ExecRemoteCommand(dial, "rm "+remoteFile); conErr != nil {
- logrus.Errorf("Removing file on endpoint: %v", conErr)
- }
- if err != nil {
- errOut := strconv.Itoa(int(n)) + " Bytes copied before error"
- return errors.Wrapf(err, errOut)
- }
return nil
}
-
-// makeRemoteFile creates the necessary remote file on the host to
-// save or load the image to. returns a string with the file name or an error
-func makeRemoteFile(dial *ssh.Client) (string, error) {
- run := "mktemp"
- remoteFile, err := connection.ExecRemoteCommand(dial, run)
- if err != nil {
- return "", err
- }
- return strings.TrimSuffix(string(remoteFile), "\n"), nil
-}
-
-// createConnections takes a boolean determining which ssh client to dial
-// and returns the dials client, its newly opened remote file, and an error if applicable.
-func createConnection(url *urlP.URL, iden string) (*ssh.Client, string, error) {
- cfg, err := connection.ValidateAndConfigure(url, iden)
- if err != nil {
- return nil, "", err
- }
- dialAdd, err := ssh.Dial("tcp", url.Host, cfg) // dial the client
- if err != nil {
- return nil, "", errors.Wrapf(err, "failed to connect")
- }
- file, err := makeRemoteFile(dialAdd)
- if err != nil {
- return nil, "", err
- }
-
- return dialAdd, file, nil
-}
-
-// GetSerivceInformation takes the parsed list of hosts to connect to and validates the information
-func GetServiceInformation(cliConnections []string, cfg *config.Config) (map[string]config.Destination, error) {
- var serv map[string]config.Destination
- var url string
- var iden string
- for i, val := range cliConnections {
- splitEnv := strings.SplitN(val, "::", 2)
- sshInfo.Connections = append(sshInfo.Connections, splitEnv[0])
- if len(splitEnv[1]) != 0 {
- err := validateImageName(splitEnv[1])
- if err != nil {
- return nil, err
- }
- source.Image = splitEnv[1]
- //TODO: actually use the new name given by the user
- }
- conn, found := cfg.Engine.ServiceDestinations[sshInfo.Connections[i]]
- if found {
- url = conn.URI
- iden = conn.Identity
- } else { // no match, warn user and do a manual connection.
- url = "ssh://" + sshInfo.Connections[i]
- iden = ""
- logrus.Warnf("Unknown connection name given. Please use system connection add to specify the default remote socket location")
- }
- urlT, err := urlP.Parse(url) // create an actual url to pass to exec command
- if err != nil {
- return nil, err
- }
- if urlT.User.Username() == "" {
- if urlT.User, err = connection.GetUserInfo(urlT); err != nil {
- return nil, err
- }
- }
- sshInfo.URI = append(sshInfo.URI, urlT)
- sshInfo.Identities = append(sshInfo.Identities, iden)
- }
- return serv, nil
-}
-
-// execPodman executes the podman save/load command given the podman binary
-func execPodman(podman string, command []string) error {
- cmd := exec.Command(podman)
- utils.CreateSCPCommand(cmd, command[1:])
- logrus.Debugf("Executing podman command: %q", cmd)
- return cmd.Run()
-}
-
-// createCommands forms the podman save and load commands used by SCP
-func createCommands(podman string) ([]string, []string) {
- var parentString string
- quiet := ""
- if source.Quiet {
- quiet = "-q "
- }
- if len(parentFlags) > 0 {
- parentString = strings.Join(parentFlags, " ") + " " // if there are parent args, an extra space needs to be added
- } else {
- parentString = strings.Join(parentFlags, " ")
- }
- loadCmd := strings.Split(fmt.Sprintf("%s %sload %s--input %s", podman, parentString, quiet, dest.File), " ")
- saveCmd := strings.Split(fmt.Sprintf("%s %vsave %s--output %s %s", podman, parentString, quiet, source.File, source.Image), " ")
- return saveCmd, loadCmd
-}
diff --git a/cmd/podman/images/scp_test.go b/cmd/podman/images/scp_test.go
deleted file mode 100644
index 315fda2ab..000000000
--- a/cmd/podman/images/scp_test.go
+++ /dev/null
@@ -1,46 +0,0 @@
-package images
-
-import (
- "testing"
-
- "github.com/containers/podman/v4/pkg/domain/entities"
- "github.com/stretchr/testify/assert"
-)
-
-func TestParseSCPArgs(t *testing.T) {
- args := []string{"alpine", "root@localhost::"}
- var source *entities.ImageScpOptions
- var dest *entities.ImageScpOptions
- var err error
- source, _, err = parseImageSCPArg(args[0])
- assert.Nil(t, err)
- assert.Equal(t, source.Image, "alpine")
-
- dest, _, err = parseImageSCPArg(args[1])
- assert.Nil(t, err)
- assert.Equal(t, dest.Image, "")
- assert.Equal(t, dest.User, "root")
-
- args = []string{"root@localhost::alpine"}
- source, _, err = parseImageSCPArg(args[0])
- assert.Nil(t, err)
- assert.Equal(t, source.User, "root")
- assert.Equal(t, source.Image, "alpine")
-
- args = []string{"charliedoern@192.168.68.126::alpine", "foobar@192.168.68.126::"}
- source, _, err = parseImageSCPArg(args[0])
- assert.Nil(t, err)
- assert.True(t, source.Remote)
- assert.Equal(t, source.Image, "alpine")
-
- dest, _, err = parseImageSCPArg(args[1])
- assert.Nil(t, err)
- assert.True(t, dest.Remote)
- assert.Equal(t, dest.Image, "")
-
- args = []string{"charliedoern@192.168.68.126::alpine"}
- source, _, err = parseImageSCPArg(args[0])
- assert.Nil(t, err)
- assert.True(t, source.Remote)
- assert.Equal(t, source.Image, "alpine")
-}
diff --git a/cmd/podman/images/scp_utils.go b/cmd/podman/images/scp_utils.go
deleted file mode 100644
index a85687a42..000000000
--- a/cmd/podman/images/scp_utils.go
+++ /dev/null
@@ -1,88 +0,0 @@
-package images
-
-import (
- "strings"
-
- "github.com/containers/image/v5/docker/reference"
- "github.com/containers/podman/v4/libpod/define"
- "github.com/containers/podman/v4/pkg/domain/entities"
- "github.com/pkg/errors"
-)
-
-// parseImageSCPArg returns the valid connection, and source/destination data based off of the information provided by the user
-// arg is a string containing one of the cli arguments returned is a filled out source/destination options structs as well as a connections array and an error if applicable
-func parseImageSCPArg(arg string) (*entities.ImageScpOptions, []string, error) {
- location := entities.ImageScpOptions{}
- var err error
- cliConnections := []string{}
-
- switch {
- case strings.Contains(arg, "@localhost::"): // image transfer between users
- location.User = strings.Split(arg, "@")[0]
- location, err = validateImagePortion(location, arg)
- if err != nil {
- return nil, nil, err
- }
- cliConnections = append(cliConnections, arg)
- case strings.Contains(arg, "::"):
- location, err = validateImagePortion(location, arg)
- if err != nil {
- return nil, nil, err
- }
- location.Remote = true
- cliConnections = append(cliConnections, arg)
- default:
- location.Image = arg
- }
- return &location, cliConnections, nil
-}
-
-// validateImagePortion is a helper function to validate the image name in an SCP argument
-func validateImagePortion(location entities.ImageScpOptions, arg string) (entities.ImageScpOptions, error) {
- if remoteArgLength(arg, 1) > 0 {
- err := validateImageName(strings.Split(arg, "::")[1])
- if err != nil {
- return location, err
- }
- location.Image = strings.Split(arg, "::")[1] // this will get checked/set again once we validate connections
- }
- return location, nil
-}
-
-// validateSCPArgs takes the array of source and destination options and checks for common errors
-func validateSCPArgs(locations []*entities.ImageScpOptions) (bool, error) {
- if len(locations) > 2 {
- return false, errors.Wrapf(define.ErrInvalidArg, "cannot specify more than two arguments")
- }
- switch {
- case len(locations[0].Image) > 0 && len(locations[1].Image) > 0:
- return false, errors.Wrapf(define.ErrInvalidArg, "cannot specify an image rename")
- case len(locations[0].Image) == 0 && len(locations[1].Image) == 0:
- return false, errors.Wrapf(define.ErrInvalidArg, "a source image must be specified")
- case len(locations[0].Image) == 0 && len(locations[1].Image) != 0:
- if locations[0].Remote && locations[1].Remote {
- return true, nil // we need to flip the cliConnections array so the save/load connections are in the right place
- }
- }
- return false, nil
-}
-
-// validateImageName makes sure that the image given is valid and no injections are occurring
-// we simply use this for error checking, bot setting the image
-func validateImageName(input string) error {
- // ParseNormalizedNamed transforms a shortname image into its
- // full name reference so busybox => docker.io/library/busybox
- // we want to keep our shortnames, so only return an error if
- // we cannot parse what the user has given us
- _, err := reference.ParseNormalizedNamed(input)
- return err
-}
-
-// remoteArgLength is a helper function to simplify the extracting of host argument data
-// returns an int which contains the length of a specified index in a host::image string
-func remoteArgLength(input string, side int) int {
- if strings.Contains(input, "::") {
- return len((strings.Split(input, "::"))[side])
- }
- return -1
-}
diff --git a/cmd/podman/images/search.go b/cmd/podman/images/search.go
index a18f7a11d..eb876d3d4 100644
--- a/cmd/podman/images/search.go
+++ b/cmd/podman/images/search.go
@@ -1,6 +1,7 @@
package images
import (
+ "errors"
"fmt"
"os"
"strings"
@@ -12,7 +13,6 @@ import (
"github.com/containers/podman/v4/cmd/podman/common"
"github.com/containers/podman/v4/cmd/podman/registry"
"github.com/containers/podman/v4/pkg/domain/entities"
- "github.com/pkg/errors"
"github.com/spf13/cobra"
)
@@ -111,11 +111,11 @@ func imageSearch(cmd *cobra.Command, args []string) error {
case 1:
searchTerm = args[0]
default:
- return errors.Errorf("search requires exactly one argument")
+ return errors.New("search requires exactly one argument")
}
if searchOptions.ListTags && len(searchOptions.Filters) != 0 {
- return errors.Errorf("filters are not applicable to list tags result")
+ return errors.New("filters are not applicable to list tags result")
}
// TLS verification in c/image is controlled via a `types.OptionalBool`
@@ -155,7 +155,7 @@ func imageSearch(cmd *cobra.Command, args []string) error {
switch {
case searchOptions.ListTags:
if len(searchOptions.Filters) != 0 {
- return errors.Errorf("filters are not applicable to list tags result")
+ return errors.New("filters are not applicable to list tags result")
}
if isJSON {
listTagsEntries := buildListTagsJSON(searchReport)
@@ -181,7 +181,7 @@ func imageSearch(cmd *cobra.Command, args []string) error {
if rpt.RenderHeaders {
hdrs := report.Headers(entities.ImageSearchReport{}, nil)
if err := rpt.Execute(hdrs); err != nil {
- return errors.Wrapf(err, "failed to write report column headers")
+ return fmt.Errorf("failed to write report column headers: %w", err)
}
}
return rpt.Execute(searchReport)
diff --git a/cmd/podman/images/sign.go b/cmd/podman/images/sign.go
index e4e201894..beea6d2b8 100644
--- a/cmd/podman/images/sign.go
+++ b/cmd/podman/images/sign.go
@@ -1,6 +1,7 @@
package images
import (
+ "errors"
"os"
"github.com/containers/common/pkg/auth"
@@ -8,7 +9,6 @@ import (
"github.com/containers/podman/v4/cmd/podman/common"
"github.com/containers/podman/v4/cmd/podman/registry"
"github.com/containers/podman/v4/pkg/domain/entities"
- "github.com/pkg/errors"
"github.com/spf13/cobra"
)
@@ -57,7 +57,7 @@ func init() {
func sign(cmd *cobra.Command, args []string) error {
if signOptions.SignBy == "" {
- return errors.Errorf("please provide an identity")
+ return errors.New("please provide an identity")
}
var sigStoreDir string
diff --git a/cmd/podman/images/trust_set.go b/cmd/podman/images/trust_set.go
index f4ff0cffc..832e9f724 100644
--- a/cmd/podman/images/trust_set.go
+++ b/cmd/podman/images/trust_set.go
@@ -1,6 +1,7 @@
package images
import (
+ "fmt"
"net/url"
"regexp"
@@ -9,7 +10,6 @@ import (
"github.com/containers/podman/v4/cmd/podman/common"
"github.com/containers/podman/v4/cmd/podman/registry"
"github.com/containers/podman/v4/pkg/domain/entities"
- "github.com/pkg/errors"
"github.com/spf13/cobra"
)
@@ -61,7 +61,7 @@ func setTrust(cmd *cobra.Command, args []string) error {
}
if !util.StringInSlice(setOptions.Type, validTrustTypes) {
- return errors.Errorf("invalid choice: %s (choose from 'accept', 'reject', 'signedBy')", setOptions.Type)
+ return fmt.Errorf("invalid choice: %s (choose from 'accept', 'reject', 'signedBy')", setOptions.Type)
}
return registry.ImageEngine().SetTrust(registry.Context(), args, setOptions)
}
@@ -71,17 +71,17 @@ func isValidImageURI(imguri string) (bool, error) {
uri := "http://" + imguri
u, err := url.Parse(uri)
if err != nil {
- return false, errors.Wrapf(err, "invalid image uri: %s", imguri)
+ return false, fmt.Errorf("invalid image uri: %s: %w", imguri, err)
}
reg := regexp.MustCompile(`^[a-zA-Z0-9-_\.]+\/?:?[0-9]*[a-z0-9-\/:]*$`)
ret := reg.FindAllString(u.Host, -1)
if len(ret) == 0 {
- return false, errors.Wrapf(err, "invalid image uri: %s", imguri)
+ return false, fmt.Errorf("invalid image uri: %s: %w", imguri, err)
}
reg = regexp.MustCompile(`^[a-z0-9-:\./]*$`)
ret = reg.FindAllString(u.Fragment, -1)
if len(ret) == 0 {
- return false, errors.Wrapf(err, "invalid image uri: %s", imguri)
+ return false, fmt.Errorf("invalid image uri: %s: %w", imguri, err)
}
return true, nil
}
diff --git a/cmd/podman/images/unmount.go b/cmd/podman/images/unmount.go
index 3ada09937..2a3df7cbd 100644
--- a/cmd/podman/images/unmount.go
+++ b/cmd/podman/images/unmount.go
@@ -1,13 +1,13 @@
package images
import (
+ "errors"
"fmt"
"github.com/containers/podman/v4/cmd/podman/common"
"github.com/containers/podman/v4/cmd/podman/registry"
"github.com/containers/podman/v4/cmd/podman/utils"
"github.com/containers/podman/v4/pkg/domain/entities"
- "github.com/pkg/errors"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
)
diff --git a/cmd/podman/images/utils_linux.go b/cmd/podman/images/utils_linux.go
index f7c159415..5923716ec 100644
--- a/cmd/podman/images/utils_linux.go
+++ b/cmd/podman/images/utils_linux.go
@@ -1,12 +1,12 @@
package images
import (
+ "fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
- "github.com/pkg/errors"
"github.com/sirupsen/logrus"
"golang.org/x/sys/unix"
)
@@ -26,7 +26,7 @@ func setupPipe() (string, func() <-chan error, error) {
if e := os.RemoveAll(pipeDir); e != nil {
logrus.Errorf("Removing named pipe: %q", e)
}
- return "", nil, errors.Wrapf(err, "error creating named pipe")
+ return "", nil, fmt.Errorf("error creating named pipe: %w", err)
}
go func() {
fpipe, err := os.Open(pipePath)
diff --git a/cmd/podman/inspect/inspect.go b/cmd/podman/inspect/inspect.go
index 05a6de699..b736b3477 100644
--- a/cmd/podman/inspect/inspect.go
+++ b/cmd/podman/inspect/inspect.go
@@ -3,6 +3,7 @@ package inspect
import (
"context"
"encoding/json" // due to a bug in json-iterator it cannot be used here
+ "errors"
"fmt"
"os"
"regexp"
@@ -16,7 +17,6 @@ import (
"github.com/containers/podman/v4/cmd/podman/validate"
"github.com/containers/podman/v4/libpod/define"
"github.com/containers/podman/v4/pkg/domain/entities"
- "github.com/pkg/errors"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
)
@@ -64,19 +64,19 @@ func newInspector(options entities.InspectOptions) (*inspector, error) {
case common.ImageType, common.ContainerType, common.AllType, common.PodType, common.NetworkType, common.VolumeType:
// Valid types.
default:
- return nil, errors.Errorf("invalid type %q: must be %q, %q, %q, %q, %q, or %q", options.Type,
+ return nil, fmt.Errorf("invalid type %q: must be %q, %q, %q, %q, %q, or %q", options.Type,
common.ImageType, common.ContainerType, common.PodType, common.NetworkType, common.VolumeType, common.AllType)
}
if options.Type == common.ImageType {
if options.Latest {
- return nil, errors.Errorf("latest is not supported for type %q", common.ImageType)
+ return nil, fmt.Errorf("latest is not supported for type %q", common.ImageType)
}
if options.Size {
- return nil, errors.Errorf("size is not supported for type %q", common.ImageType)
+ return nil, fmt.Errorf("size is not supported for type %q", common.ImageType)
}
}
if options.Type == common.PodType && options.Size {
- return nil, errors.Errorf("size is not supported for type %q", common.PodType)
+ return nil, fmt.Errorf("size is not supported for type %q", common.PodType)
}
podOpts := entities.PodInspectOptions{
Latest: options.Latest,
@@ -145,8 +145,7 @@ func (i *inspector) inspect(namesOrIDs []string) error {
i.podOptions.NameOrID = pod
podData, err := i.containerEngine.PodInspect(ctx, i.podOptions)
if err != nil {
- cause := errors.Cause(err)
- if !strings.Contains(cause.Error(), define.ErrNoSuchPod.Error()) {
+ if !strings.Contains(err.Error(), define.ErrNoSuchPod.Error()) {
errs = []error{err}
} else {
return err
@@ -159,8 +158,7 @@ func (i *inspector) inspect(namesOrIDs []string) error {
if i.podOptions.Latest { // latest means there are no names in the namesOrID array
podData, err := i.containerEngine.PodInspect(ctx, i.podOptions)
if err != nil {
- cause := errors.Cause(err)
- if !strings.Contains(cause.Error(), define.ErrNoSuchPod.Error()) {
+ if !strings.Contains(err.Error(), define.ErrNoSuchPod.Error()) {
errs = []error{err}
} else {
return err
@@ -189,7 +187,7 @@ func (i *inspector) inspect(namesOrIDs []string) error {
data = append(data, volumeData[i])
}
default:
- return errors.Errorf("invalid type %q: must be %q, %q, %q, %q, %q, or %q", i.options.Type,
+ return fmt.Errorf("invalid type %q: must be %q, %q, %q, %q, %q, or %q", i.options.Type,
common.ImageType, common.ContainerType, common.PodType, common.NetworkType, common.VolumeType, common.AllType)
}
// Always print an empty array
@@ -218,7 +216,7 @@ func (i *inspector) inspect(namesOrIDs []string) error {
fmt.Fprintf(os.Stderr, "error inspecting object: %v\n", err)
}
}
- return errors.Errorf("inspecting object: %v", errs[0])
+ return fmt.Errorf("inspecting object: %w", errs[0])
}
return nil
}
@@ -287,8 +285,7 @@ func (i *inspector) inspectAll(ctx context.Context, namesOrIDs []string) ([]inte
i.podOptions.NameOrID = name
podData, err := i.containerEngine.PodInspect(ctx, i.podOptions)
if err != nil {
- cause := errors.Cause(err)
- if !strings.Contains(cause.Error(), define.ErrNoSuchPod.Error()) {
+ if !strings.Contains(err.Error(), define.ErrNoSuchPod.Error()) {
return nil, nil, err
}
} else {
@@ -296,7 +293,7 @@ func (i *inspector) inspectAll(ctx context.Context, namesOrIDs []string) ([]inte
continue
}
if len(errs) > 0 {
- allErrs = append(allErrs, errors.Errorf("no such object: %q", name))
+ allErrs = append(allErrs, fmt.Errorf("no such object: %q", name))
continue
}
}
diff --git a/cmd/podman/machine/init.go b/cmd/podman/machine/init.go
index 9d464ad37..def3334e8 100644
--- a/cmd/podman/machine/init.go
+++ b/cmd/podman/machine/init.go
@@ -11,7 +11,6 @@ import (
"github.com/containers/podman/v4/cmd/podman/registry"
"github.com/containers/podman/v4/libpod/events"
"github.com/containers/podman/v4/pkg/machine"
- "github.com/pkg/errors"
"github.com/spf13/cobra"
)
@@ -20,6 +19,7 @@ var (
Use: "init [options] [NAME]",
Short: "Initialize a virtual machine",
Long: "initialize a virtual machine ",
+ PersistentPreRunE: rootlessOnly,
RunE: initMachine,
Args: cobra.MaximumNArgs(1),
Example: `podman machine init myvm`,
@@ -119,12 +119,12 @@ func initMachine(cmd *cobra.Command, args []string) error {
initOpts.Name = defaultMachineName
if len(args) > 0 {
if len(args[0]) > maxMachineNameSize {
- return errors.Errorf("machine name %q must be %d characters or less", args[0], maxMachineNameSize)
+ return fmt.Errorf("machine name %q must be %d characters or less", args[0], maxMachineNameSize)
}
initOpts.Name = args[0]
}
if _, err := provider.LoadVMByName(initOpts.Name); err == nil {
- return errors.Wrap(machine.ErrVMAlreadyExists, initOpts.Name)
+ return fmt.Errorf("%s: %w", initOpts.Name, machine.ErrVMAlreadyExists)
}
for idx, vol := range initOpts.Volumes {
initOpts.Volumes[idx] = os.ExpandEnv(vol)
diff --git a/cmd/podman/machine/inspect.go b/cmd/podman/machine/inspect.go
index 4600a2b6d..d69c382f2 100644
--- a/cmd/podman/machine/inspect.go
+++ b/cmd/podman/machine/inspect.go
@@ -20,6 +20,7 @@ var (
Use: "inspect [options] [MACHINE...]",
Short: "Inspect an existing machine",
Long: "Provide details on a managed virtual machine",
+ PersistentPreRunE: rootlessOnly,
RunE: inspect,
Example: `podman machine inspect myvm`,
ValidArgsFunction: autocompleteMachine,
diff --git a/cmd/podman/machine/list.go b/cmd/podman/machine/list.go
index 1ffb8690c..b1e31566f 100644
--- a/cmd/podman/machine/list.go
+++ b/cmd/podman/machine/list.go
@@ -4,6 +4,7 @@
package machine
import (
+ "fmt"
"os"
"sort"
"strconv"
@@ -17,7 +18,6 @@ import (
"github.com/containers/podman/v4/cmd/podman/validate"
"github.com/containers/podman/v4/pkg/machine"
"github.com/docker/go-units"
- "github.com/pkg/errors"
"github.com/spf13/cobra"
)
@@ -27,6 +27,7 @@ var (
Aliases: []string{"ls"},
Short: "List machines",
Long: "List managed virtual machines.",
+ PersistentPreRunE: rootlessOnly,
RunE: list,
Args: validate.NoArgs,
ValidArgsFunction: completion.AutocompleteNone,
@@ -88,7 +89,7 @@ func list(cmd *cobra.Command, args []string) error {
provider := GetSystemDefaultProvider()
listResponse, err = provider.List(opts)
if err != nil {
- return errors.Wrap(err, "error listing vms")
+ return fmt.Errorf("listing vms: %w", err)
}
// Sort by last run
@@ -157,7 +158,7 @@ func outputTemplate(cmd *cobra.Command, responses []*ListReporter) error {
defer w.Flush()
if printHeader {
if err := tmpl.Execute(w, headers); err != nil {
- return errors.Wrapf(err, "failed to write report column headers")
+ return fmt.Errorf("failed to write report column headers: %w", err)
}
}
return tmpl.Execute(w, responses)
diff --git a/cmd/podman/machine/machine.go b/cmd/podman/machine/machine.go
index 5a8a06b9d..d3d44b45e 100644
--- a/cmd/podman/machine/machine.go
+++ b/cmd/podman/machine/machine.go
@@ -5,6 +5,7 @@ package machine
import (
"errors"
+ "fmt"
"net"
"os"
"path/filepath"
@@ -17,6 +18,7 @@ import (
"github.com/containers/podman/v4/cmd/podman/validate"
"github.com/containers/podman/v4/libpod/events"
"github.com/containers/podman/v4/pkg/machine"
+ "github.com/containers/podman/v4/pkg/rootless"
"github.com/containers/podman/v4/pkg/util"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
@@ -162,3 +164,10 @@ func closeMachineEvents(cmd *cobra.Command, _ []string) error {
}
return nil
}
+
+func rootlessOnly(cmd *cobra.Command, args []string) error {
+ if !rootless.IsRootless() {
+ return fmt.Errorf("cannot run command %q as root", cmd.CommandPath())
+ }
+ return nil
+}
diff --git a/cmd/podman/machine/rm.go b/cmd/podman/machine/rm.go
index a6e66265c..362c9a7d3 100644
--- a/cmd/podman/machine/rm.go
+++ b/cmd/podman/machine/rm.go
@@ -20,6 +20,7 @@ var (
Use: "rm [options] [MACHINE]",
Short: "Remove an existing machine",
Long: "Remove a managed virtual machine ",
+ PersistentPreRunE: rootlessOnly,
RunE: rm,
Args: cobra.MaximumNArgs(1),
Example: `podman machine rm myvm`,
diff --git a/cmd/podman/machine/set.go b/cmd/podman/machine/set.go
index 5777882da..1b9e1b2bd 100644
--- a/cmd/podman/machine/set.go
+++ b/cmd/podman/machine/set.go
@@ -18,6 +18,7 @@ var (
Use: "set [options] [NAME]",
Short: "Sets a virtual machine setting",
Long: "Sets an updatable virtual machine setting",
+ PersistentPreRunE: rootlessOnly,
RunE: setMachine,
Args: cobra.MaximumNArgs(1),
Example: `podman machine set --rootful=false`,
diff --git a/cmd/podman/machine/ssh.go b/cmd/podman/machine/ssh.go
index 8261f3607..cb2f62f51 100644
--- a/cmd/podman/machine/ssh.go
+++ b/cmd/podman/machine/ssh.go
@@ -4,6 +4,7 @@
package machine
import (
+ "fmt"
"net/url"
"github.com/containers/common/pkg/completion"
@@ -11,16 +12,16 @@ import (
"github.com/containers/podman/v4/cmd/podman/registry"
"github.com/containers/podman/v4/cmd/podman/utils"
"github.com/containers/podman/v4/pkg/machine"
- "github.com/pkg/errors"
"github.com/spf13/cobra"
)
var (
sshCmd = &cobra.Command{
- Use: "ssh [options] [NAME] [COMMAND [ARG ...]]",
- Short: "SSH into an existing machine",
- Long: "SSH into a managed virtual machine ",
- RunE: ssh,
+ Use: "ssh [options] [NAME] [COMMAND [ARG ...]]",
+ Short: "SSH into an existing machine",
+ Long: "SSH into a managed virtual machine ",
+ PersistentPreRunE: rootlessOnly,
+ RunE: ssh,
Example: `podman machine ssh myvm
podman machine ssh myvm echo hello`,
ValidArgsFunction: autocompleteMachineSSH,
@@ -88,7 +89,7 @@ func ssh(cmd *cobra.Command, args []string) error {
vm, err = provider.LoadVMByName(vmName)
if err != nil {
- return errors.Wrapf(err, "vm %s not found", vmName)
+ return fmt.Errorf("vm %s not found: %w", vmName, err)
}
err = vm.SSH(vmName, sshOpts)
return utils.HandleOSExecError(err)
diff --git a/cmd/podman/machine/start.go b/cmd/podman/machine/start.go
index 3bd7f4a25..15a75522a 100644
--- a/cmd/podman/machine/start.go
+++ b/cmd/podman/machine/start.go
@@ -9,7 +9,6 @@ import (
"github.com/containers/podman/v4/cmd/podman/registry"
"github.com/containers/podman/v4/libpod/events"
"github.com/containers/podman/v4/pkg/machine"
- "github.com/pkg/errors"
"github.com/spf13/cobra"
)
@@ -18,6 +17,7 @@ var (
Use: "start [MACHINE]",
Short: "Start an existing machine",
Long: "Start a managed virtual machine ",
+ PersistentPreRunE: rootlessOnly,
RunE: start,
Args: cobra.MaximumNArgs(1),
Example: `podman machine start myvm`,
@@ -54,9 +54,9 @@ func start(_ *cobra.Command, args []string) error {
}
if active {
if vmName == activeName {
- return errors.Wrapf(machine.ErrVMAlreadyRunning, "cannot start VM %s", vmName)
+ return fmt.Errorf("cannot start VM %s: %w", vmName, machine.ErrVMAlreadyRunning)
}
- return errors.Wrapf(machine.ErrMultipleActiveVM, "cannot start VM %s. VM %s is currently running or starting", vmName, activeName)
+ return fmt.Errorf("cannot start VM %s. VM %s is currently running or starting: %w", vmName, activeName, machine.ErrMultipleActiveVM)
}
fmt.Printf("Starting machine %q\n", vmName)
if err := vm.Start(vmName, machine.StartOptions{}); err != nil {
diff --git a/cmd/podman/machine/stop.go b/cmd/podman/machine/stop.go
index 993662792..ce87a44c4 100644
--- a/cmd/podman/machine/stop.go
+++ b/cmd/podman/machine/stop.go
@@ -17,6 +17,7 @@ var (
Use: "stop [MACHINE]",
Short: "Stop an existing machine",
Long: "Stop a managed virtual machine ",
+ PersistentPreRunE: rootlessOnly,
RunE: stop,
Args: cobra.MaximumNArgs(1),
Example: `podman machine stop myvm`,
diff --git a/cmd/podman/manifest/push.go b/cmd/podman/manifest/push.go
index b96a65c4a..9479e79a3 100644
--- a/cmd/podman/manifest/push.go
+++ b/cmd/podman/manifest/push.go
@@ -1,6 +1,7 @@
package manifest
import (
+ "fmt"
"io/ioutil"
"github.com/containers/common/pkg/auth"
@@ -11,7 +12,6 @@ import (
"github.com/containers/podman/v4/cmd/podman/utils"
"github.com/containers/podman/v4/pkg/domain/entities"
"github.com/containers/podman/v4/pkg/util"
- "github.com/pkg/errors"
"github.com/spf13/cobra"
)
@@ -88,10 +88,10 @@ func push(cmd *cobra.Command, args []string) error {
listImageSpec := args[0]
destSpec := args[1]
if listImageSpec == "" {
- return errors.Errorf(`invalid image name "%s"`, listImageSpec)
+ return fmt.Errorf(`invalid image name "%s"`, listImageSpec)
}
if destSpec == "" {
- return errors.Errorf(`invalid destination "%s"`, destSpec)
+ return fmt.Errorf(`invalid destination "%s"`, destSpec)
}
if manifestPushOpts.CredentialsCLI != "" {
diff --git a/cmd/podman/manifest/remove.go b/cmd/podman/manifest/remove.go
index c32ffad78..4aa3b66b7 100644
--- a/cmd/podman/manifest/remove.go
+++ b/cmd/podman/manifest/remove.go
@@ -5,7 +5,6 @@ import (
"github.com/containers/podman/v4/cmd/podman/common"
"github.com/containers/podman/v4/cmd/podman/registry"
- "github.com/pkg/errors"
"github.com/spf13/cobra"
)
@@ -31,7 +30,7 @@ func init() {
func remove(cmd *cobra.Command, args []string) error {
updatedListID, err := registry.ImageEngine().ManifestRemoveDigest(registry.Context(), args[0], args[1])
if err != nil {
- return errors.Wrapf(err, "error removing from manifest list %s", args[0])
+ return fmt.Errorf("removing from manifest list %s: %w", args[0], err)
}
fmt.Println(updatedListID)
return nil
diff --git a/cmd/podman/networks/create.go b/cmd/podman/networks/create.go
index 84c58d4dc..2cf7023f3 100644
--- a/cmd/podman/networks/create.go
+++ b/cmd/podman/networks/create.go
@@ -1,6 +1,7 @@
package network
import (
+ "errors"
"fmt"
"net"
@@ -11,7 +12,6 @@ import (
"github.com/containers/podman/v4/cmd/podman/parse"
"github.com/containers/podman/v4/cmd/podman/registry"
"github.com/containers/podman/v4/pkg/domain/entities"
- "github.com/pkg/errors"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
)
@@ -97,11 +97,11 @@ func networkCreate(cmd *cobra.Command, args []string) error {
var err error
networkCreateOptions.Labels, err = parse.GetAllLabels([]string{}, labels)
if err != nil {
- return errors.Wrap(err, "failed to parse labels")
+ return fmt.Errorf("failed to parse labels: %w", err)
}
networkCreateOptions.Options, err = parse.GetAllLabels([]string{}, opts)
if err != nil {
- return errors.Wrapf(err, "unable to parse options")
+ return fmt.Errorf("unable to parse options: %w", err)
}
network := types.Network{
@@ -181,11 +181,11 @@ func parseRange(iprange string) (*types.LeaseRange, error) {
startIP, err := util.FirstIPInSubnet(subnet)
if err != nil {
- return nil, errors.Wrap(err, "failed to get first ip in range")
+ return nil, fmt.Errorf("failed to get first ip in range: %w", err)
}
lastIP, err := util.LastIPInSubnet(subnet)
if err != nil {
- return nil, errors.Wrap(err, "failed to get last ip in range")
+ return nil, fmt.Errorf("failed to get last ip in range: %w", err)
}
return &types.LeaseRange{
StartIP: startIP,
diff --git a/cmd/podman/networks/rm.go b/cmd/podman/networks/rm.go
index f71f59eea..c2d3f655f 100644
--- a/cmd/podman/networks/rm.go
+++ b/cmd/podman/networks/rm.go
@@ -1,6 +1,7 @@
package network
import (
+ "errors"
"fmt"
"strings"
@@ -10,7 +11,6 @@ import (
"github.com/containers/podman/v4/cmd/podman/utils"
"github.com/containers/podman/v4/libpod/define"
"github.com/containers/podman/v4/pkg/domain/entities"
- "github.com/pkg/errors"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
)
@@ -78,15 +78,9 @@ func networkRm(cmd *cobra.Command, args []string) error {
}
func setExitCode(err error) {
- cause := errors.Cause(err)
- switch {
- case cause == define.ErrNoSuchNetwork:
+ if errors.Is(err, define.ErrNoSuchNetwork) || strings.Contains(err.Error(), define.ErrNoSuchNetwork.Error()) {
registry.SetExitCode(1)
- case strings.Contains(cause.Error(), define.ErrNoSuchNetwork.Error()):
- registry.SetExitCode(1)
- case cause == define.ErrNetworkInUse:
- registry.SetExitCode(2)
- case strings.Contains(cause.Error(), define.ErrNetworkInUse.Error()):
+ } else if errors.Is(err, define.ErrNetworkInUse) || strings.Contains(err.Error(), define.ErrNetworkInUse.Error()) {
registry.SetExitCode(2)
}
}
diff --git a/cmd/podman/parse/filters.go b/cmd/podman/parse/filters.go
index 8a10f2a97..e4ab942af 100644
--- a/cmd/podman/parse/filters.go
+++ b/cmd/podman/parse/filters.go
@@ -1,10 +1,9 @@
package parse
import (
+ "fmt"
"net/url"
"strings"
-
- "github.com/pkg/errors"
)
func FilterArgumentsIntoFilters(filters []string) (url.Values, error) {
@@ -12,7 +11,7 @@ func FilterArgumentsIntoFilters(filters []string) (url.Values, error) {
for _, f := range filters {
t := strings.SplitN(f, "=", 2)
if len(t) < 2 {
- return parsedFilters, errors.Errorf("filter input must be in the form of filter=value: %s is invalid", f)
+ return parsedFilters, fmt.Errorf("filter input must be in the form of filter=value: %s is invalid", f)
}
parsedFilters.Add(t[0], t[1])
}
diff --git a/cmd/podman/parse/net.go b/cmd/podman/parse/net.go
index ba70c7ba5..9228c7127 100644
--- a/cmd/podman/parse/net.go
+++ b/cmd/podman/parse/net.go
@@ -10,8 +10,6 @@ import (
"os"
"regexp"
"strings"
-
- "github.com/pkg/errors"
)
const (
@@ -81,7 +79,7 @@ func GetAllLabels(labelFile, inputLabels []string) (map[string]string, error) {
for _, label := range inputLabels {
split := strings.SplitN(label, "=", 2)
if split[0] == "" {
- return nil, errors.Errorf("invalid label format: %q", label)
+ return nil, fmt.Errorf("invalid label format: %q", label)
}
value := ""
if len(split) > 1 {
@@ -97,13 +95,13 @@ func parseEnvOrLabel(env map[string]string, line, configType string) error {
// catch invalid variables such as "=" or "=A"
if data[0] == "" {
- return errors.Errorf("invalid environment variable: %q", line)
+ return fmt.Errorf("invalid environment variable: %q", line)
}
// trim the front of a variable, but nothing else
name := strings.TrimLeft(data[0], whiteSpaces)
if strings.ContainsAny(name, whiteSpaces) {
- return errors.Errorf("name %q has white spaces, poorly formatted name", name)
+ return fmt.Errorf("name %q has white spaces, poorly formatted name", name)
}
if len(data) > 1 {
@@ -157,7 +155,7 @@ func parseEnvOrLabelFile(envOrLabel map[string]string, filename, configType stri
// as it is currently not supported
func ValidateFileName(filename string) error {
if strings.Contains(filename, ":") {
- return errors.Errorf("invalid filename (should not contain ':') %q", filename)
+ return fmt.Errorf("invalid filename (should not contain ':') %q", filename)
}
return nil
}
@@ -166,10 +164,10 @@ func ValidateFileName(filename string) error {
func ValidURL(urlStr string) error {
url, err := url.ParseRequestURI(urlStr)
if err != nil {
- return errors.Wrapf(err, "invalid url %q", urlStr)
+ return fmt.Errorf("invalid url %q: %w", urlStr, err)
}
if url.Scheme == "" {
- return errors.Errorf("invalid url %q: missing scheme", urlStr)
+ return fmt.Errorf("invalid url %q: missing scheme", urlStr)
}
return nil
}
diff --git a/cmd/podman/play/kube.go b/cmd/podman/play/kube.go
index f5b121009..8fd12baaf 100644
--- a/cmd/podman/play/kube.go
+++ b/cmd/podman/play/kube.go
@@ -1,6 +1,7 @@
package pods
import (
+ "errors"
"fmt"
"net"
"os"
@@ -16,7 +17,6 @@ import (
"github.com/containers/podman/v4/pkg/domain/entities"
"github.com/containers/podman/v4/pkg/errorhandling"
"github.com/containers/podman/v4/pkg/util"
- "github.com/pkg/errors"
"github.com/spf13/cobra"
)
@@ -188,14 +188,14 @@ func kube(cmd *cobra.Command, args []string) error {
for _, annotation := range annotations {
splitN := strings.SplitN(annotation, "=", 2)
if len(splitN) > 2 {
- return errors.Errorf("annotation %q must include an '=' sign", annotation)
+ return fmt.Errorf("annotation %q must include an '=' sign", annotation)
}
if kubeOptions.Annotations == nil {
kubeOptions.Annotations = make(map[string]string)
}
annotation := splitN[1]
if len(annotation) > define.MaxKubeAnnotation {
- return errors.Errorf("annotation exceeds maximum size, %d, of kubernetes annotation: %s", define.MaxKubeAnnotation, annotation)
+ return fmt.Errorf("annotation exceeds maximum size, %d, of kubernetes annotation: %s", define.MaxKubeAnnotation, annotation)
}
kubeOptions.Annotations[splitN[0]] = annotation
}
@@ -235,7 +235,7 @@ func teardown(yamlfile string) error {
defer f.Close()
reports, err := registry.ContainerEngine().PlayKubeDown(registry.GetContext(), f, *options)
if err != nil {
- return errors.Wrap(err, yamlfile)
+ return fmt.Errorf("%v: %w", yamlfile, err)
}
// Output stopped pods
@@ -273,7 +273,7 @@ func playkube(yamlfile string) error {
defer f.Close()
report, err := registry.ContainerEngine().PlayKube(registry.GetContext(), f, kubeOptions.PlayKubeOptions)
if err != nil {
- return errors.Wrap(err, yamlfile)
+ return fmt.Errorf("%s: %w", yamlfile, err)
}
// Print volumes report
for i, volume := range report.Volumes {
@@ -320,7 +320,7 @@ func playkube(yamlfile string) error {
}
if ctrsFailed > 0 {
- return errors.Errorf("failed to start %d containers", ctrsFailed)
+ return fmt.Errorf("failed to start %d containers", ctrsFailed)
}
return nil
diff --git a/cmd/podman/pods/clone.go b/cmd/podman/pods/clone.go
index 391af1cf7..9558c6aed 100644
--- a/cmd/podman/pods/clone.go
+++ b/cmd/podman/pods/clone.go
@@ -9,7 +9,6 @@ import (
"github.com/containers/podman/v4/cmd/podman/registry"
"github.com/containers/podman/v4/libpod/define"
"github.com/containers/podman/v4/pkg/domain/entities"
- "github.com/pkg/errors"
"github.com/spf13/cobra"
)
@@ -67,7 +66,7 @@ func init() {
func clone(cmd *cobra.Command, args []string) error {
switch len(args) {
case 0:
- return errors.Wrapf(define.ErrInvalidArg, "must specify at least 1 argument")
+ return fmt.Errorf("must specify at least 1 argument: %w", define.ErrInvalidArg)
case 2:
podClone.CreateOpts.Name = args[1]
}
diff --git a/cmd/podman/pods/create.go b/cmd/podman/pods/create.go
index ca9d60174..45ad2dfd0 100644
--- a/cmd/podman/pods/create.go
+++ b/cmd/podman/pods/create.go
@@ -2,6 +2,7 @@ package pods
import (
"context"
+ "errors"
"fmt"
"io/ioutil"
"os"
@@ -23,7 +24,6 @@ import (
"github.com/containers/podman/v4/pkg/specgenutil"
"github.com/containers/podman/v4/pkg/util"
"github.com/docker/docker/pkg/parsers"
- "github.com/pkg/errors"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
@@ -127,7 +127,7 @@ func create(cmd *cobra.Command, args []string) error {
labels = infraOptions.Label
createOptions.Labels, err = parse.GetAllLabels(labelFile, labels)
if err != nil {
- return errors.Wrapf(err, "unable to process labels")
+ return fmt.Errorf("unable to process labels: %w", err)
}
if cmd.Flag("infra-image").Changed {
@@ -165,7 +165,7 @@ func create(cmd *cobra.Command, args []string) error {
return err
}
if strings.Contains(share, "cgroup") && shareParent {
- return errors.Wrapf(define.ErrInvalidArg, "cannot define the pod as the cgroup parent at the same time as joining the infra container's cgroupNS")
+ return fmt.Errorf("cannot define the pod as the cgroup parent at the same time as joining the infra container's cgroupNS: %w", define.ErrInvalidArg)
}
if strings.HasPrefix(share, "+") {
@@ -193,10 +193,10 @@ func create(cmd *cobra.Command, args []string) error {
if cmd.Flag("pod-id-file").Changed {
podIDFD, err = util.OpenExclusiveFile(podIDFile)
if err != nil && os.IsExist(err) {
- return errors.Errorf("pod id file exists. Ensure another pod is not using it or delete %s", podIDFile)
+ return fmt.Errorf("pod id file exists. Ensure another pod is not using it or delete %s", podIDFile)
}
if err != nil {
- return errors.Errorf("opening pod-id-file %s", podIDFile)
+ return fmt.Errorf("opening pod-id-file %s", podIDFile)
}
defer errorhandling.CloseQuiet(podIDFD)
defer errorhandling.SyncQuiet(podIDFD)
@@ -204,7 +204,7 @@ func create(cmd *cobra.Command, args []string) error {
if len(createOptions.Net.PublishPorts) > 0 {
if !createOptions.Infra {
- return errors.Errorf("you must have an infra container to publish port bindings to the host")
+ return fmt.Errorf("you must have an infra container to publish port bindings to the host")
}
}
@@ -231,7 +231,7 @@ func create(cmd *cobra.Command, args []string) error {
ret, err := parsers.ParseUintList(copy)
copy = ""
if err != nil {
- return errors.Wrapf(err, "could not parse list")
+ return fmt.Errorf("could not parse list: %w", err)
}
var vals []int
for ind, val := range ret {
@@ -301,7 +301,7 @@ func create(cmd *cobra.Command, args []string) error {
if len(podIDFile) > 0 {
if err = ioutil.WriteFile(podIDFile, []byte(response.Id), 0644); err != nil {
- return errors.Wrapf(err, "failed to write pod ID to file")
+ return fmt.Errorf("failed to write pod ID to file: %w", err)
}
}
fmt.Println(response.Id)
diff --git a/cmd/podman/pods/inspect.go b/cmd/podman/pods/inspect.go
index bb30fe6e6..082e8d9a1 100644
--- a/cmd/podman/pods/inspect.go
+++ b/cmd/podman/pods/inspect.go
@@ -2,6 +2,7 @@ package pods
import (
"context"
+ "errors"
"os"
"text/template"
@@ -10,7 +11,6 @@ import (
"github.com/containers/podman/v4/cmd/podman/registry"
"github.com/containers/podman/v4/cmd/podman/validate"
"github.com/containers/podman/v4/pkg/domain/entities"
- "github.com/pkg/errors"
"github.com/spf13/cobra"
)
@@ -49,10 +49,10 @@ func init() {
func inspect(cmd *cobra.Command, args []string) error {
if len(args) < 1 && !inspectOptions.Latest {
- return errors.Errorf("you must provide the name or id of a running pod")
+ return errors.New("you must provide the name or id of a running pod")
}
if len(args) > 0 && inspectOptions.Latest {
- return errors.Errorf("--latest and containers cannot be used together")
+ return errors.New("--latest and containers cannot be used together")
}
if !inspectOptions.Latest {
diff --git a/cmd/podman/pods/logs.go b/cmd/podman/pods/logs.go
index 28e7b7a43..0102d4b71 100644
--- a/cmd/podman/pods/logs.go
+++ b/cmd/podman/pods/logs.go
@@ -1,6 +1,8 @@
package pods
import (
+ "errors"
+ "fmt"
"os"
"github.com/containers/common/pkg/completion"
@@ -10,7 +12,6 @@ import (
"github.com/containers/podman/v4/libpod/define"
"github.com/containers/podman/v4/pkg/domain/entities"
"github.com/containers/podman/v4/pkg/util"
- "github.com/pkg/errors"
"github.com/spf13/cobra"
)
@@ -100,7 +101,7 @@ func logs(_ *cobra.Command, args []string) error {
// parse time, error out if something is wrong
since, err := util.ParseInputTime(logsPodOptions.SinceRaw, true)
if err != nil {
- return errors.Wrapf(err, "error parsing --since %q", logsPodOptions.SinceRaw)
+ return fmt.Errorf("error parsing --since %q: %w", logsPodOptions.SinceRaw, err)
}
logsPodOptions.Since = since
}
@@ -108,14 +109,14 @@ func logs(_ *cobra.Command, args []string) error {
// parse time, error out if something is wrong
until, err := util.ParseInputTime(logsPodOptions.UntilRaw, false)
if err != nil {
- return errors.Wrapf(err, "error parsing --until %q", logsPodOptions.UntilRaw)
+ return fmt.Errorf("error parsing --until %q: %w", logsPodOptions.UntilRaw, err)
}
logsPodOptions.Until = until
}
// Remote can only process one container at a time
if registry.IsRemote() && logsPodOptions.ContainerName == "" {
- return errors.Wrapf(define.ErrInvalidArg, "-c or --container cannot be empty")
+ return fmt.Errorf("-c or --container cannot be empty: %w", define.ErrInvalidArg)
}
logsPodOptions.StdoutWriter = os.Stdout
diff --git a/cmd/podman/pods/ps.go b/cmd/podman/pods/ps.go
index c98b4ef4e..681c9c42e 100644
--- a/cmd/podman/pods/ps.go
+++ b/cmd/podman/pods/ps.go
@@ -2,6 +2,7 @@ package pods
import (
"context"
+ "errors"
"fmt"
"os"
"sort"
@@ -15,7 +16,6 @@ import (
"github.com/containers/podman/v4/cmd/podman/validate"
"github.com/containers/podman/v4/pkg/domain/entities"
"github.com/docker/go-units"
- "github.com/pkg/errors"
"github.com/spf13/cobra"
)
@@ -80,7 +80,7 @@ func pods(cmd *cobra.Command, _ []string) error {
for _, f := range inputFilters {
split := strings.SplitN(f, "=", 2)
if len(split) < 2 {
- return errors.Errorf("filter input must be in the form of filter=value: %s is invalid", f)
+ return fmt.Errorf("filter input must be in the form of filter=value: %s is invalid", f)
}
psInput.Filters[split[0]] = append(psInput.Filters[split[0]], split[1])
}
@@ -276,7 +276,7 @@ func sortPodPsOutput(sortBy string, lprs []*entities.ListPodsReport) error {
case "status":
sort.Sort(podPsSortedStatus{lprs})
default:
- return errors.Errorf("invalid option for --sort, options are: id, names, or number")
+ return errors.New("invalid option for --sort, options are: id, names, or number")
}
return nil
}
diff --git a/cmd/podman/pods/rm.go b/cmd/podman/pods/rm.go
index 16b7191c9..2ffd968f9 100644
--- a/cmd/podman/pods/rm.go
+++ b/cmd/podman/pods/rm.go
@@ -2,6 +2,7 @@ package pods
import (
"context"
+ "errors"
"fmt"
"strings"
@@ -13,7 +14,6 @@ import (
"github.com/containers/podman/v4/libpod/define"
"github.com/containers/podman/v4/pkg/domain/entities"
"github.com/containers/podman/v4/pkg/specgenutil"
- "github.com/pkg/errors"
"github.com/spf13/cobra"
)
@@ -112,11 +112,7 @@ func removePods(namesOrIDs []string, rmOptions entities.PodRmOptions, printIDs b
}
func setExitCode(err error) {
- cause := errors.Cause(err)
- switch {
- case cause == define.ErrNoSuchPod:
- registry.SetExitCode(1)
- case strings.Contains(cause.Error(), define.ErrNoSuchPod.Error()):
+ if errors.Is(err, define.ErrNoSuchPod) || strings.Contains(err.Error(), define.ErrNoSuchPod.Error()) {
registry.SetExitCode(1)
}
}
diff --git a/cmd/podman/pods/top.go b/cmd/podman/pods/top.go
index 4e9c7a3ee..34f3d1c33 100644
--- a/cmd/podman/pods/top.go
+++ b/cmd/podman/pods/top.go
@@ -2,6 +2,7 @@ package pods
import (
"context"
+ "errors"
"fmt"
"os"
"strings"
@@ -12,7 +13,6 @@ import (
"github.com/containers/podman/v4/cmd/podman/validate"
"github.com/containers/podman/v4/pkg/domain/entities"
"github.com/containers/podman/v4/pkg/util"
- "github.com/pkg/errors"
"github.com/spf13/cobra"
)
@@ -67,7 +67,7 @@ func top(_ *cobra.Command, args []string) error {
}
if len(args) < 1 && !topOptions.Latest {
- return errors.Errorf("you must provide the name or id of a running pod")
+ return errors.New("you must provide the name or id of a running pod")
}
if topOptions.Latest {
diff --git a/cmd/podman/registry/config.go b/cmd/podman/registry/config.go
index e06de034d..cae618b44 100644
--- a/cmd/podman/registry/config.go
+++ b/cmd/podman/registry/config.go
@@ -11,7 +11,6 @@ import (
"github.com/containers/podman/v4/pkg/domain/entities"
"github.com/containers/podman/v4/pkg/rootless"
"github.com/containers/podman/v4/pkg/util"
- "github.com/pkg/errors"
)
const (
@@ -99,7 +98,7 @@ func setXdgDirs() error {
return err
}
if err := os.Setenv("XDG_RUNTIME_DIR", dir); err != nil {
- return errors.Wrapf(err, "cannot set XDG_RUNTIME_DIR="+dir)
+ return fmt.Errorf("cannot set XDG_RUNTIME_DIR=%s: %w", dir, err)
}
}
@@ -117,7 +116,7 @@ func setXdgDirs() error {
return err
}
if err := os.Setenv("XDG_CONFIG_HOME", cfgHomeDir); err != nil {
- return errors.Wrapf(err, "cannot set XDG_CONFIG_HOME="+cfgHomeDir)
+ return fmt.Errorf("cannot set XDG_CONFIG_HOME=%s: %w", cfgHomeDir, err)
}
}
return nil
diff --git a/cmd/podman/root.go b/cmd/podman/root.go
index 1892ff9f7..f28d92e2f 100644
--- a/cmd/podman/root.go
+++ b/cmd/podman/root.go
@@ -1,6 +1,7 @@
package main
import (
+ "errors"
"fmt"
"os"
"path/filepath"
@@ -20,7 +21,6 @@ import (
"github.com/containers/podman/v4/pkg/parallel"
"github.com/containers/podman/v4/pkg/rootless"
"github.com/containers/podman/v4/version"
- "github.com/pkg/errors"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
@@ -137,22 +137,20 @@ func persistentPreRunE(cmd *cobra.Command, args []string) error {
if cmd.Flag("import").Changed {
runtime, err := crutils.CRGetRuntimeFromArchive(cmd.Flag("import").Value.String())
if err != nil {
- return errors.Wrapf(
- err,
- "failed extracting runtime information from %s",
- cmd.Flag("import").Value.String(),
+ return fmt.Errorf(
+ "failed extracting runtime information from %s: %w",
+ cmd.Flag("import").Value.String(), err,
)
}
- if cfg.RuntimePath == "" {
+
+ runtimeFlag := cmd.Root().Flag("runtime")
+ if runtimeFlag == nil {
+ return errors.New("failed to load --runtime flag")
+ }
+
+ if !runtimeFlag.Changed {
// If the user did not select a runtime, this takes the one from
// the checkpoint archives and tells Podman to use it for the restore.
- runtimeFlag := cmd.Root().Flags().Lookup("runtime")
- if runtimeFlag == nil {
- return errors.Errorf(
- "setting runtime to '%s' for restore",
- *runtime,
- )
- }
if err := runtimeFlag.Value.Set(*runtime); err != nil {
return err
}
@@ -161,7 +159,7 @@ func persistentPreRunE(cmd *cobra.Command, args []string) error {
} else if cfg.RuntimePath != *runtime {
// If the user selected a runtime on the command-line this checks if
// it is the same then during checkpointing and errors out if not.
- return errors.Errorf(
+ return fmt.Errorf(
"checkpoint archive %s was created with runtime '%s' and cannot be restored with runtime '%s'",
cmd.Flag("import").Value.String(),
*runtime,
@@ -179,15 +177,15 @@ func persistentPreRunE(cmd *cobra.Command, args []string) error {
var err error
cfg.URI, cfg.Identity, err = cfg.ActiveDestination()
if err != nil {
- return errors.Wrap(err, "failed to resolve active destination")
+ return fmt.Errorf("failed to resolve active destination: %w", err)
}
if err := cmd.Root().LocalFlags().Set("url", cfg.URI); err != nil {
- return errors.Wrap(err, "failed to override --url flag")
+ return fmt.Errorf("failed to override --url flag: %w", err)
}
if err := cmd.Root().LocalFlags().Set("identity", cfg.Identity); err != nil {
- return errors.Wrap(err, "failed to override --identity flag")
+ return fmt.Errorf("failed to override --identity flag: %w", err)
}
}
@@ -256,7 +254,7 @@ func persistentPreRunE(cmd *cobra.Command, args []string) error {
}
if cfg.MaxWorks <= 0 {
- return errors.Errorf("maximum workers must be set to a positive number (got %d)", cfg.MaxWorks)
+ return fmt.Errorf("maximum workers must be set to a positive number (got %d)", cfg.MaxWorks)
}
if err := parallel.SetMaxThreads(uint(cfg.MaxWorks)); err != nil {
return err
@@ -298,12 +296,12 @@ func persistentPostRunE(cmd *cobra.Command, args []string) error {
if cmd.Flag("memory-profile").Changed {
f, err := os.Create(registry.PodmanConfig().MemoryProfile)
if err != nil {
- return errors.Wrap(err, "creating memory profile")
+ return fmt.Errorf("creating memory profile: %w", err)
}
defer f.Close()
runtime.GC() // get up-to-date GC statistics
if err := pprof.WriteHeapProfile(f); err != nil {
- return errors.Wrap(err, "writing memory profile")
+ return fmt.Errorf("writing memory profile: %w", err)
}
}
@@ -482,7 +480,7 @@ func resolveDestination() (string, string, string) {
cfg, err := config.ReadCustomConfig()
if err != nil {
- logrus.Warning(errors.Wrap(err, "unable to read local containers.conf"))
+ logrus.Warning(fmt.Errorf("unable to read local containers.conf: %w", err))
return "", registry.DefaultAPIAddress(), ""
}
@@ -495,7 +493,7 @@ func resolveDestination() (string, string, string) {
func formatError(err error) string {
var message string
- if errors.Cause(err) == define.ErrOCIRuntime {
+ if errors.Is(err, define.ErrOCIRuntime) {
// OCIRuntimeErrors include the reason for the failure in the
// second to last message in the error chain.
message = fmt.Sprintf(
diff --git a/cmd/podman/root_test.go b/cmd/podman/root_test.go
index 0a73afdc4..98a3de79d 100644
--- a/cmd/podman/root_test.go
+++ b/cmd/podman/root_test.go
@@ -1,12 +1,12 @@
package main
import (
+ "errors"
"fmt"
"strings"
"testing"
"github.com/containers/podman/v4/libpod/define"
- "github.com/pkg/errors"
)
func TestFormatError(t *testing.T) {
@@ -22,7 +22,7 @@ func TestFormatError(t *testing.T) {
func TestFormatOCIError(t *testing.T) {
expectedPrefix := "Error: "
expectedSuffix := "OCI runtime output"
- err := errors.Wrap(define.ErrOCIRuntime, expectedSuffix)
+ err := fmt.Errorf("%s: %w", expectedSuffix, define.ErrOCIRuntime)
output := formatError(err)
if !strings.HasPrefix(output, expectedPrefix) {
diff --git a/cmd/podman/secrets/create.go b/cmd/podman/secrets/create.go
index 01ee3d256..8ecfecf69 100644
--- a/cmd/podman/secrets/create.go
+++ b/cmd/podman/secrets/create.go
@@ -2,6 +2,7 @@ package secrets
import (
"context"
+ "errors"
"fmt"
"io"
"os"
@@ -11,7 +12,6 @@ import (
"github.com/containers/podman/v4/cmd/podman/common"
"github.com/containers/podman/v4/cmd/podman/registry"
"github.com/containers/podman/v4/pkg/domain/entities"
- "github.com/pkg/errors"
"github.com/spf13/cobra"
)
@@ -66,7 +66,7 @@ func create(cmd *cobra.Command, args []string) error {
case env:
envValue := os.Getenv(path)
if envValue == "" {
- return errors.Errorf("cannot create store secret data: environment variable %s is not set", path)
+ return fmt.Errorf("cannot create store secret data: environment variable %s is not set", path)
}
reader = strings.NewReader(envValue)
case path == "-" || path == "/dev/stdin":
diff --git a/cmd/podman/secrets/inspect.go b/cmd/podman/secrets/inspect.go
index 473d5620c..1fcc676b4 100644
--- a/cmd/podman/secrets/inspect.go
+++ b/cmd/podman/secrets/inspect.go
@@ -10,7 +10,6 @@ import (
"github.com/containers/podman/v4/cmd/podman/common"
"github.com/containers/podman/v4/cmd/podman/registry"
"github.com/containers/podman/v4/pkg/domain/entities"
- "github.com/pkg/errors"
"github.com/spf13/cobra"
)
@@ -78,7 +77,7 @@ func inspect(cmd *cobra.Command, args []string) error {
fmt.Fprintf(os.Stderr, "error inspecting secret: %v\n", err)
}
}
- return errors.Errorf("inspecting secret: %v", errs[0])
+ return fmt.Errorf("inspecting secret: %w", errs[0])
}
return nil
}
diff --git a/cmd/podman/secrets/list.go b/cmd/podman/secrets/list.go
index 558a16ccf..8b1956eab 100644
--- a/cmd/podman/secrets/list.go
+++ b/cmd/podman/secrets/list.go
@@ -2,6 +2,7 @@ package secrets
import (
"context"
+ "fmt"
"os"
"time"
@@ -13,7 +14,6 @@ import (
"github.com/containers/podman/v4/cmd/podman/validate"
"github.com/containers/podman/v4/pkg/domain/entities"
"github.com/docker/go-units"
- "github.com/pkg/errors"
"github.com/spf13/cobra"
)
@@ -108,7 +108,7 @@ func outputTemplate(cmd *cobra.Command, responses []*entities.SecretListReport)
if !listFlag.noHeading {
if err := tmpl.Execute(w, headers); err != nil {
- return errors.Wrapf(err, "failed to write report column headers")
+ return fmt.Errorf("failed to write report column headers: %w", err)
}
}
return tmpl.Execute(w, responses)
diff --git a/cmd/podman/system/connection/add.go b/cmd/podman/system/connection/add.go
index d77a39bcc..191603718 100644
--- a/cmd/podman/system/connection/add.go
+++ b/cmd/podman/system/connection/add.go
@@ -2,25 +2,22 @@ package connection
import (
"encoding/json"
+ "errors"
"fmt"
"net"
"net/url"
"os"
- "os/user"
"regexp"
- "time"
"github.com/containers/common/pkg/completion"
"github.com/containers/common/pkg/config"
"github.com/containers/podman/v4/cmd/podman/registry"
"github.com/containers/podman/v4/cmd/podman/system"
"github.com/containers/podman/v4/libpod/define"
- "github.com/containers/podman/v4/pkg/terminal"
- "github.com/pkg/errors"
+ "github.com/containers/podman/v4/pkg/domain/utils"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"golang.org/x/crypto/ssh"
- "golang.org/x/crypto/ssh/agent"
)
var (
@@ -79,7 +76,7 @@ func add(cmd *cobra.Command, args []string) error {
// Default to ssh schema if none given
dest := args[1]
if match, err := regexp.Match("^[A-Za-z][A-Za-z0-9+.-]*://", []byte(dest)); err != nil {
- return errors.Wrapf(err, "invalid destination")
+ return fmt.Errorf("invalid destination: %w", err)
} else if !match {
dest = "ssh://" + dest
}
@@ -95,7 +92,7 @@ func add(cmd *cobra.Command, args []string) error {
switch uri.Scheme {
case "ssh":
if uri.User.Username() == "" {
- if uri.User, err = GetUserInfo(uri); err != nil {
+ if uri.User, err = utils.GetUserInfo(uri); err != nil {
return err
}
}
@@ -180,44 +177,20 @@ func add(cmd *cobra.Command, args []string) error {
return cfg.Write()
}
-func GetUserInfo(uri *url.URL) (*url.Userinfo, error) {
- var (
- usr *user.User
- err error
- )
- if u, found := os.LookupEnv("_CONTAINERS_ROOTLESS_UID"); found {
- usr, err = user.LookupId(u)
- if err != nil {
- return nil, errors.Wrapf(err, "failed to look up rootless user")
- }
- } else {
- usr, err = user.Current()
- if err != nil {
- return nil, errors.Wrapf(err, "failed to obtain current user")
- }
- }
-
- pw, set := uri.User.Password()
- if set {
- return url.UserPassword(usr.Username, pw), nil
- }
- return url.User(usr.Username), nil
-}
-
func getUDS(uri *url.URL, iden string) (string, error) {
- cfg, err := ValidateAndConfigure(uri, iden)
+ cfg, err := utils.ValidateAndConfigure(uri, iden)
if err != nil {
- return "", errors.Wrapf(err, "failed to validate")
+ return "", fmt.Errorf("failed to validate: %w", err)
}
dial, err := ssh.Dial("tcp", uri.Host, cfg)
if err != nil {
- return "", errors.Wrapf(err, "failed to connect")
+ return "", fmt.Errorf("failed to connect: %w", err)
}
defer dial.Close()
session, err := dial.NewSession()
if err != nil {
- return "", errors.Wrapf(err, "failed to create new ssh session on %q", uri.Host)
+ return "", fmt.Errorf("failed to create new ssh session on %q: %w", uri.Host, err)
}
defer session.Close()
@@ -226,94 +199,18 @@ func getUDS(uri *url.URL, iden string) (string, error) {
if v, found := os.LookupEnv("PODMAN_BINARY"); found {
podman = v
}
- infoJSON, err := ExecRemoteCommand(dial, podman+" info --format=json")
+ infoJSON, err := utils.ExecRemoteCommand(dial, podman+" info --format=json")
if err != nil {
return "", err
}
var info define.Info
if err := json.Unmarshal(infoJSON, &info); err != nil {
- return "", errors.Wrapf(err, "failed to parse 'podman info' results")
+ return "", fmt.Errorf("failed to parse 'podman info' results: %w", err)
}
if info.Host.RemoteSocket == nil || len(info.Host.RemoteSocket.Path) == 0 {
- return "", errors.Errorf("remote podman %q failed to report its UDS socket", uri.Host)
+ return "", fmt.Errorf("remote podman %q failed to report its UDS socket", uri.Host)
}
return info.Host.RemoteSocket.Path, nil
}
-
-// ValidateAndConfigure will take a ssh url and an identity key (rsa and the like) and ensure the information given is valid
-// iden iden can be blank to mean no identity key
-// once the function validates the information it creates and returns an ssh.ClientConfig.
-func ValidateAndConfigure(uri *url.URL, iden string) (*ssh.ClientConfig, error) {
- var signers []ssh.Signer
- passwd, passwdSet := uri.User.Password()
- if iden != "" { // iden might be blank if coming from image scp or if no validation is needed
- value := iden
- s, err := terminal.PublicKey(value, []byte(passwd))
- if err != nil {
- return nil, errors.Wrapf(err, "failed to read identity %q", value)
- }
- signers = append(signers, s)
- logrus.Debugf("SSH Ident Key %q %s %s", value, ssh.FingerprintSHA256(s.PublicKey()), s.PublicKey().Type())
- }
- if sock, found := os.LookupEnv("SSH_AUTH_SOCK"); found { // validate ssh information, specifically the unix file socket used by the ssh agent.
- logrus.Debugf("Found SSH_AUTH_SOCK %q, ssh-agent signer enabled", sock)
-
- c, err := net.Dial("unix", sock)
- if err != nil {
- return nil, err
- }
- agentSigners, err := agent.NewClient(c).Signers()
- if err != nil {
- return nil, err
- }
-
- signers = append(signers, agentSigners...)
-
- if logrus.IsLevelEnabled(logrus.DebugLevel) {
- for _, s := range agentSigners {
- logrus.Debugf("SSH Agent Key %s %s", ssh.FingerprintSHA256(s.PublicKey()), s.PublicKey().Type())
- }
- }
- }
- var authMethods []ssh.AuthMethod // now we validate and check for the authorization methods, most notaibly public key authorization
- if len(signers) > 0 {
- var dedup = make(map[string]ssh.Signer)
- for _, s := range signers {
- fp := ssh.FingerprintSHA256(s.PublicKey())
- if _, found := dedup[fp]; found {
- logrus.Debugf("Dedup SSH Key %s %s", ssh.FingerprintSHA256(s.PublicKey()), s.PublicKey().Type())
- }
- dedup[fp] = s
- }
-
- var uniq []ssh.Signer
- for _, s := range dedup {
- uniq = append(uniq, s)
- }
- authMethods = append(authMethods, ssh.PublicKeysCallback(func() ([]ssh.Signer, error) {
- return uniq, nil
- }))
- }
- if passwdSet { // if password authentication is given and valid, add to the list
- authMethods = append(authMethods, ssh.Password(passwd))
- }
- if len(authMethods) == 0 {
- authMethods = append(authMethods, ssh.PasswordCallback(func() (string, error) {
- pass, err := terminal.ReadPassword(fmt.Sprintf("%s's login password:", uri.User.Username()))
- return string(pass), err
- }))
- }
- tick, err := time.ParseDuration("40s")
- if err != nil {
- return nil, err
- }
- cfg := &ssh.ClientConfig{
- User: uri.User.Username(),
- Auth: authMethods,
- HostKeyCallback: ssh.InsecureIgnoreHostKey(),
- Timeout: tick,
- }
- return cfg, nil
-}
diff --git a/cmd/podman/system/connection/remove.go b/cmd/podman/system/connection/remove.go
index 463eae9fa..29bf98c43 100644
--- a/cmd/podman/system/connection/remove.go
+++ b/cmd/podman/system/connection/remove.go
@@ -1,11 +1,12 @@
package connection
import (
+ "errors"
+
"github.com/containers/common/pkg/config"
"github.com/containers/podman/v4/cmd/podman/common"
"github.com/containers/podman/v4/cmd/podman/registry"
"github.com/containers/podman/v4/cmd/podman/system"
- "github.com/pkg/errors"
"github.com/spf13/cobra"
)
diff --git a/cmd/podman/system/connection/shared.go b/cmd/podman/system/connection/shared.go
deleted file mode 100644
index 714ae827d..000000000
--- a/cmd/podman/system/connection/shared.go
+++ /dev/null
@@ -1,27 +0,0 @@
-package connection
-
-import (
- "bytes"
-
- "github.com/pkg/errors"
- "golang.org/x/crypto/ssh"
-)
-
-// ExecRemoteCommand takes a ssh client connection and a command to run and executes the
-// command on the specified client. The function returns the Stdout from the client or the Stderr
-func ExecRemoteCommand(dial *ssh.Client, run string) ([]byte, error) {
- sess, err := dial.NewSession() // new ssh client session
- if err != nil {
- return nil, err
- }
- defer sess.Close()
-
- var buffer bytes.Buffer
- var bufferErr bytes.Buffer
- sess.Stdout = &buffer // output from client funneled into buffer
- sess.Stderr = &bufferErr // err form client funneled into buffer
- if err := sess.Run(run); err != nil { // run the command on the ssh client
- return nil, errors.Wrapf(err, bufferErr.String())
- }
- return buffer.Bytes(), nil
-}
diff --git a/cmd/podman/system/dial_stdio.go b/cmd/podman/system/dial_stdio.go
index 8b665bedc..42ce65746 100644
--- a/cmd/podman/system/dial_stdio.go
+++ b/cmd/podman/system/dial_stdio.go
@@ -2,13 +2,15 @@ package system
import (
"context"
+ "fmt"
"io"
"os"
+ "errors"
+
"github.com/containers/podman/v4/cmd/podman/registry"
"github.com/containers/podman/v4/cmd/podman/validate"
"github.com/containers/podman/v4/pkg/bindings"
- "github.com/pkg/errors"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
)
@@ -40,15 +42,15 @@ func runDialStdio() error {
defer cancel()
bindCtx, err := bindings.NewConnection(ctx, cfg.URI)
if err != nil {
- return errors.Wrap(err, "failed to open connection to podman")
+ return fmt.Errorf("failed to open connection to podman: %w", err)
}
conn, err := bindings.GetClient(bindCtx)
if err != nil {
- return errors.Wrap(err, "failed to get connection after initialization")
+ return fmt.Errorf("failed to get connection after initialization: %w", err)
}
netConn, err := conn.GetDialer(bindCtx)
if err != nil {
- return errors.Wrap(err, "failed to open the raw stream connection")
+ return fmt.Errorf("failed to open the raw stream connection: %w", err)
}
defer netConn.Close()
@@ -95,7 +97,7 @@ func copier(to halfWriteCloser, from halfReadCloser, debugDescription string) er
}
}()
if _, err := io.Copy(to, from); err != nil {
- return errors.Wrapf(err, "error while Copy (%s)", debugDescription)
+ return fmt.Errorf("error while Copy (%s): %w", debugDescription, err)
}
return nil
}
diff --git a/cmd/podman/system/service_abi.go b/cmd/podman/system/service_abi.go
index 7cb1b8084..6823d77ba 100644
--- a/cmd/podman/system/service_abi.go
+++ b/cmd/podman/system/service_abi.go
@@ -4,24 +4,46 @@
package system
import (
+ "errors"
"fmt"
"net"
"net/url"
"os"
"path/filepath"
+ "github.com/containers/common/pkg/cgroups"
"github.com/containers/podman/v4/cmd/podman/registry"
api "github.com/containers/podman/v4/pkg/api/server"
"github.com/containers/podman/v4/pkg/domain/entities"
"github.com/containers/podman/v4/pkg/domain/infra"
"github.com/containers/podman/v4/pkg/servicereaper"
+ "github.com/containers/podman/v4/utils"
"github.com/coreos/go-systemd/v22/activation"
- "github.com/pkg/errors"
"github.com/sirupsen/logrus"
"github.com/spf13/pflag"
"golang.org/x/sys/unix"
)
+// maybeMoveToSubCgroup moves the current process in a sub cgroup when
+// it is running in the root cgroup on a system that uses cgroupv2.
+func maybeMoveToSubCgroup() error {
+ unifiedMode, err := cgroups.IsCgroup2UnifiedMode()
+ if err != nil {
+ return err
+ }
+ if !unifiedMode {
+ return nil
+ }
+ cgroup, err := utils.GetOwnCgroup()
+ if err != nil {
+ return err
+ }
+ if cgroup == "/" {
+ return utils.MoveUnderCgroupSubtree("init")
+ }
+ return nil
+}
+
func restService(flags *pflag.FlagSet, cfg *entities.PodmanConfig, opts entities.ServiceOptions) error {
var (
listener net.Listener
@@ -48,13 +70,13 @@ func restService(flags *pflag.FlagSet, cfg *entities.PodmanConfig, opts entities
listener = listeners[0]
// note that activation.Listeners() returns nil when it cannot listen on the fd (i.e. udp connection)
if listener == nil {
- return fmt.Errorf("unexpected fd received from systemd: cannot listen on it")
+ return errors.New("unexpected fd received from systemd: cannot listen on it")
}
libpodRuntime.SetRemoteURI(listeners[0].Addr().String())
} else {
uri, err := url.Parse(opts.URI)
if err != nil {
- return errors.Errorf("%s is an invalid socket destination", opts.URI)
+ return fmt.Errorf("%s is an invalid socket destination", opts.URI)
}
switch uri.Scheme {
@@ -74,7 +96,7 @@ func restService(flags *pflag.FlagSet, cfg *entities.PodmanConfig, opts entities
} else {
listener, err = net.Listen(uri.Scheme, path)
if err != nil {
- return errors.Wrapf(err, "unable to create socket")
+ return fmt.Errorf("unable to create socket: %w", err)
}
}
case "tcp":
@@ -85,7 +107,7 @@ func restService(flags *pflag.FlagSet, cfg *entities.PodmanConfig, opts entities
}
listener, err = net.Listen(uri.Scheme, host)
if err != nil {
- return errors.Wrapf(err, "unable to create socket %v", host)
+ return fmt.Errorf("unable to create socket %v: %w", host, err)
}
default:
logrus.Debugf("Attempting API Service endpoint scheme %q", uri.Scheme)
@@ -103,6 +125,10 @@ func restService(flags *pflag.FlagSet, cfg *entities.PodmanConfig, opts entities
return err
}
+ if err := maybeMoveToSubCgroup(); err != nil {
+ return err
+ }
+
servicereaper.Start()
infra.StartWatcher(libpodRuntime)
server, err := api.NewServerWithSettings(libpodRuntime, listener, opts)
diff --git a/cmd/podman/system/unshare.go b/cmd/podman/system/unshare.go
index 1ed08eac3..6d9c33b64 100644
--- a/cmd/podman/system/unshare.go
+++ b/cmd/podman/system/unshare.go
@@ -1,6 +1,7 @@
package system
import (
+ "errors"
"os"
"github.com/containers/common/pkg/completion"
@@ -8,7 +9,6 @@ import (
"github.com/containers/podman/v4/cmd/podman/utils"
"github.com/containers/podman/v4/pkg/domain/entities"
"github.com/containers/podman/v4/pkg/rootless"
- "github.com/pkg/errors"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
)
@@ -47,14 +47,14 @@ func init() {
func unshare(cmd *cobra.Command, args []string) error {
if isRootless := rootless.IsRootless(); !isRootless {
- return errors.Errorf("please use unshare with rootless")
+ return errors.New("please use unshare with rootless")
}
// exec the specified command, if there is one
if len(args) < 1 {
// try to exec the shell, if one's set
shell, shellSet := os.LookupEnv("SHELL")
if !shellSet {
- return errors.Errorf("no command specified and no $SHELL specified")
+ return errors.New("no command specified and no $SHELL specified")
}
args = []string{shell}
}
diff --git a/cmd/podman/validate/args.go b/cmd/podman/validate/args.go
index ae405e0e5..39eedca64 100644
--- a/cmd/podman/validate/args.go
+++ b/cmd/podman/validate/args.go
@@ -1,12 +1,12 @@
package validate
import (
+ "errors"
"fmt"
"strconv"
"strings"
"github.com/containers/podman/v4/cmd/podman/registry"
- "github.com/pkg/errors"
"github.com/spf13/cobra"
)
@@ -23,12 +23,12 @@ func SubCommandExists(cmd *cobra.Command, args []string) error {
if len(args) > 0 {
suggestions := cmd.SuggestionsFor(args[0])
if len(suggestions) == 0 {
- return errors.Errorf("unrecognized command `%[1]s %[2]s`\nTry '%[1]s --help' for more information", cmd.CommandPath(), args[0])
+ return fmt.Errorf("unrecognized command `%[1]s %[2]s`\nTry '%[1]s --help' for more information", cmd.CommandPath(), args[0])
}
- return errors.Errorf("unrecognized command `%[1]s %[2]s`\n\nDid you mean this?\n\t%[3]s\n\nTry '%[1]s --help' for more information", cmd.CommandPath(), args[0], strings.Join(suggestions, "\n\t"))
+ return fmt.Errorf("unrecognized command `%[1]s %[2]s`\n\nDid you mean this?\n\t%[3]s\n\nTry '%[1]s --help' for more information", cmd.CommandPath(), args[0], strings.Join(suggestions, "\n\t"))
}
cmd.Help() //nolint: errcheck
- return errors.Errorf("missing command '%[1]s COMMAND'", cmd.CommandPath())
+ return fmt.Errorf("missing command '%[1]s COMMAND'", cmd.CommandPath())
}
// IDOrLatestArgs used to validate a nameOrId was provided or the "--latest" flag
@@ -44,7 +44,7 @@ func IDOrLatestArgs(cmd *cobra.Command, args []string) error {
return fmt.Errorf("%q requires a name, id, or the \"--latest\" flag", cmd.CommandPath())
}
if len(args) > 0 && given {
- return fmt.Errorf("--latest and containers cannot be used together")
+ return errors.New("--latest and containers cannot be used together")
}
}
return nil
@@ -75,7 +75,7 @@ func CheckAllLatestAndIDFile(c *cobra.Command, args []string, ignoreArgLen bool,
if idFileFlag == "" {
return errors.New("unable to look up values for 'latest' or 'all'")
} else if c.Flags().Lookup(idFileFlag) == nil {
- return errors.Errorf("unable to look up values for 'latest', 'all', or '%s'", idFileFlag)
+ return fmt.Errorf("unable to look up values for 'latest', 'all', or '%s'", idFileFlag)
}
}
}
@@ -87,13 +87,13 @@ func CheckAllLatestAndIDFile(c *cobra.Command, args []string, ignoreArgLen bool,
}
if specifiedIDFile && (specifiedAll || specifiedLatest) {
- return errors.Errorf("--all, --latest, and --%s cannot be used together", idFileFlag)
+ return fmt.Errorf("--all, --latest, and --%s cannot be used together", idFileFlag)
} else if specifiedAll && specifiedLatest {
- return errors.Errorf("--all and --latest cannot be used together")
+ return errors.New("--all and --latest cannot be used together")
}
if (argLen > 0) && specifiedAll {
- return errors.Errorf("no arguments are needed with --all")
+ return errors.New("no arguments are needed with --all")
}
if ignoreArgLen {
@@ -102,9 +102,9 @@ func CheckAllLatestAndIDFile(c *cobra.Command, args []string, ignoreArgLen bool,
if argLen > 0 {
if specifiedLatest {
- return errors.Errorf("--latest and containers cannot be used together")
+ return errors.New("--latest and containers cannot be used together")
} else if idFileFlag != "" && (specifiedLatest || specifiedIDFile) {
- return errors.Errorf("no arguments are needed with --latest or --%s", idFileFlag)
+ return fmt.Errorf("no arguments are needed with --latest or --%s", idFileFlag)
}
}
@@ -113,7 +113,7 @@ func CheckAllLatestAndIDFile(c *cobra.Command, args []string, ignoreArgLen bool,
}
if argLen < 1 && !specifiedAll && !specifiedLatest && !specifiedIDFile {
- return errors.Errorf("you must provide at least one name or id")
+ return errors.New("you must provide at least one name or id")
}
return nil
}
diff --git a/cmd/podman/volumes/create.go b/cmd/podman/volumes/create.go
index b47ae16ce..0d19fab47 100644
--- a/cmd/podman/volumes/create.go
+++ b/cmd/podman/volumes/create.go
@@ -8,7 +8,6 @@ import (
"github.com/containers/podman/v4/cmd/podman/parse"
"github.com/containers/podman/v4/cmd/podman/registry"
"github.com/containers/podman/v4/pkg/domain/entities"
- "github.com/pkg/errors"
"github.com/spf13/cobra"
)
@@ -65,11 +64,11 @@ func create(cmd *cobra.Command, args []string) error {
}
createOpts.Label, err = parse.GetAllLabels([]string{}, opts.Label)
if err != nil {
- return errors.Wrapf(err, "unable to process labels")
+ return fmt.Errorf("unable to process labels: %w", err)
}
createOpts.Options, err = parse.GetAllLabels([]string{}, opts.Opts)
if err != nil {
- return errors.Wrapf(err, "unable to process options")
+ return fmt.Errorf("unable to process options: %w", err)
}
response, err := registry.ContainerEngine().VolumeCreate(context.Background(), createOpts)
if err != nil {
diff --git a/cmd/podman/volumes/export.go b/cmd/podman/volumes/export.go
index 113f79a0b..f9e08be87 100644
--- a/cmd/podman/volumes/export.go
+++ b/cmd/podman/volumes/export.go
@@ -2,6 +2,7 @@ package volumes
import (
"context"
+ "errors"
"fmt"
"github.com/containers/common/pkg/completion"
@@ -10,7 +11,6 @@ import (
"github.com/containers/podman/v4/pkg/domain/entities"
"github.com/containers/podman/v4/pkg/errorhandling"
"github.com/containers/podman/v4/utils"
- "github.com/pkg/errors"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
)
diff --git a/cmd/podman/volumes/import.go b/cmd/podman/volumes/import.go
index 76a311643..8f3c7f27e 100644
--- a/cmd/podman/volumes/import.go
+++ b/cmd/podman/volumes/import.go
@@ -1,6 +1,7 @@
package volumes
import (
+ "errors"
"fmt"
"os"
@@ -10,7 +11,6 @@ import (
"github.com/containers/podman/v4/pkg/domain/entities"
"github.com/containers/podman/v4/pkg/errorhandling"
"github.com/containers/podman/v4/utils"
- "github.com/pkg/errors"
"github.com/spf13/cobra"
)
diff --git a/cmd/podman/volumes/inspect.go b/cmd/podman/volumes/inspect.go
index 7cf363f36..68ba2976a 100644
--- a/cmd/podman/volumes/inspect.go
+++ b/cmd/podman/volumes/inspect.go
@@ -1,12 +1,13 @@
package volumes
import (
+ "errors"
+
"github.com/containers/podman/v4/cmd/podman/common"
"github.com/containers/podman/v4/cmd/podman/inspect"
"github.com/containers/podman/v4/cmd/podman/registry"
"github.com/containers/podman/v4/libpod/define"
"github.com/containers/podman/v4/pkg/domain/entities"
- "github.com/pkg/errors"
"github.com/spf13/cobra"
)
diff --git a/cmd/podman/volumes/list.go b/cmd/podman/volumes/list.go
index c14cf08bd..06118513d 100644
--- a/cmd/podman/volumes/list.go
+++ b/cmd/podman/volumes/list.go
@@ -2,6 +2,7 @@ package volumes
import (
"context"
+ "errors"
"fmt"
"os"
@@ -12,7 +13,6 @@ import (
"github.com/containers/podman/v4/cmd/podman/registry"
"github.com/containers/podman/v4/cmd/podman/validate"
"github.com/containers/podman/v4/pkg/domain/entities"
- "github.com/pkg/errors"
"github.com/spf13/cobra"
)
@@ -119,7 +119,7 @@ func outputTemplate(cmd *cobra.Command, responses []*entities.VolumeListReport)
if !(noHeading || cliOpts.Quiet || cmd.Flag("format").Changed) {
if err := tmpl.Execute(w, headers); err != nil {
- return errors.Wrapf(err, "failed to write report column headers")
+ return fmt.Errorf("failed to write report column headers: %w", err)
}
}
return tmpl.Execute(w, responses)
diff --git a/cmd/podman/volumes/reload.go b/cmd/podman/volumes/reload.go
new file mode 100644
index 000000000..d0d76fb88
--- /dev/null
+++ b/cmd/podman/volumes/reload.go
@@ -0,0 +1,52 @@
+package volumes
+
+import (
+ "fmt"
+
+ "github.com/containers/common/pkg/completion"
+ "github.com/containers/podman/v4/cmd/podman/registry"
+ "github.com/containers/podman/v4/cmd/podman/utils"
+ "github.com/containers/podman/v4/cmd/podman/validate"
+ "github.com/spf13/cobra"
+)
+
+var (
+ reloadDescription = `Check all configured volume plugins and update the libpod database with all available volumes.
+
+ Existing volumes are also removed from the database when they are no longer present in the plugin.`
+ reloadCommand = &cobra.Command{
+ Use: "reload",
+ Args: validate.NoArgs,
+ Short: "reload all volumes from volume plugins",
+ Long: reloadDescription,
+ RunE: reload,
+ ValidArgsFunction: completion.AutocompleteNone,
+ }
+)
+
+func init() {
+ registry.Commands = append(registry.Commands, registry.CliCommand{
+ Command: reloadCommand,
+ Parent: volumeCmd,
+ })
+}
+
+func reload(cmd *cobra.Command, args []string) error {
+ report, err := registry.ContainerEngine().VolumeReload(registry.Context())
+ if err != nil {
+ return err
+ }
+ printReload("Added", report.Added)
+ printReload("Removed", report.Removed)
+ errs := (utils.OutputErrors)(report.Errors)
+ return errs.PrintErrors()
+}
+
+func printReload(typ string, values []string) {
+ if len(values) > 0 {
+ fmt.Println(typ + ":")
+ for _, name := range values {
+ fmt.Println(name)
+ }
+ }
+}
diff --git a/cmd/podman/volumes/rm.go b/cmd/podman/volumes/rm.go
index 2012b7d3a..c160b8623 100644
--- a/cmd/podman/volumes/rm.go
+++ b/cmd/podman/volumes/rm.go
@@ -2,6 +2,7 @@ package volumes
import (
"context"
+ "errors"
"fmt"
"strings"
@@ -11,7 +12,6 @@ import (
"github.com/containers/podman/v4/cmd/podman/utils"
"github.com/containers/podman/v4/libpod/define"
"github.com/containers/podman/v4/pkg/domain/entities"
- "github.com/pkg/errors"
"github.com/spf13/cobra"
)
@@ -80,15 +80,9 @@ func rm(cmd *cobra.Command, args []string) error {
}
func setExitCode(err error) {
- cause := errors.Cause(err)
- switch {
- case cause == define.ErrNoSuchVolume:
+ if errors.Is(err, define.ErrNoSuchVolume) || strings.Contains(err.Error(), define.ErrNoSuchVolume.Error()) {
registry.SetExitCode(1)
- case strings.Contains(cause.Error(), define.ErrNoSuchVolume.Error()):
- registry.SetExitCode(1)
- case cause == define.ErrVolumeBeingUsed:
- registry.SetExitCode(2)
- case strings.Contains(cause.Error(), define.ErrVolumeBeingUsed.Error()):
+ } else if errors.Is(err, define.ErrVolumeBeingUsed) || strings.Contains(err.Error(), define.ErrVolumeBeingUsed.Error()) {
registry.SetExitCode(2)
}
}
diff --git a/cmd/rootlessport/main.go b/cmd/rootlessport/main.go
index f01b9e4a6..5410cd14a 100644
--- a/cmd/rootlessport/main.go
+++ b/cmd/rootlessport/main.go
@@ -6,6 +6,7 @@ package main
import (
"context"
"encoding/json"
+ "errors"
"fmt"
"io"
"io/ioutil"
@@ -18,7 +19,6 @@ import (
"github.com/containernetworking/plugins/pkg/ns"
"github.com/containers/common/libnetwork/types"
"github.com/containers/podman/v4/pkg/rootlessport"
- "github.com/pkg/errors"
rkport "github.com/rootless-containers/rootlesskit/pkg/port"
rkbuiltin "github.com/rootless-containers/rootlesskit/pkg/port/builtin"
rkportutil "github.com/rootless-containers/rootlesskit/pkg/port/portutil"
@@ -269,16 +269,16 @@ func handler(ctx context.Context, conn io.Reader, pm rkport.Manager) error {
dec := json.NewDecoder(conn)
err := dec.Decode(&childIP)
if err != nil {
- return errors.Wrap(err, "rootless port failed to decode ports")
+ return fmt.Errorf("rootless port failed to decode ports: %w", err)
}
portStatus, err := pm.ListPorts(ctx)
if err != nil {
- return errors.Wrap(err, "rootless port failed to list ports")
+ return fmt.Errorf("rootless port failed to list ports: %w", err)
}
for _, status := range portStatus {
err = pm.RemovePort(ctx, status.ID)
if err != nil {
- return errors.Wrap(err, "rootless port failed to remove port")
+ return fmt.Errorf("rootless port failed to remove port: %w", err)
}
}
// add the ports with the new child IP
@@ -287,7 +287,7 @@ func handler(ctx context.Context, conn io.Reader, pm rkport.Manager) error {
status.Spec.ChildIP = childIP
_, err = pm.AddPort(ctx, status.Spec)
if err != nil {
- return errors.Wrap(err, "rootless port failed to add port")
+ return fmt.Errorf("rootless port failed to add port: %w", err)
}
}
return nil
diff --git a/contrib/cirrus/lib.sh b/contrib/cirrus/lib.sh
index 2624af385..e7ea05867 100644
--- a/contrib/cirrus/lib.sh
+++ b/contrib/cirrus/lib.sh
@@ -135,6 +135,7 @@ setup_rootless() {
req_env_vars GOPATH GOSRC SECRET_ENV_RE
ROOTLESS_USER="${ROOTLESS_USER:-some${RANDOM}dude}"
+ ROOTLESS_UID=""
local rootless_uid
local rootless_gid
@@ -158,6 +159,7 @@ setup_rootless() {
cd $GOSRC || exit 1
# Guarantee independence from specific values
rootless_uid=$[RANDOM+1000]
+ ROOTLESS_UID=$rootless_uid
rootless_gid=$[RANDOM+1000]
msg "creating $rootless_uid:$rootless_gid $ROOTLESS_USER user"
groupadd -g $rootless_gid $ROOTLESS_USER
diff --git a/contrib/cirrus/setup_environment.sh b/contrib/cirrus/setup_environment.sh
index f31cd6eeb..9bd35bd06 100755
--- a/contrib/cirrus/setup_environment.sh
+++ b/contrib/cirrus/setup_environment.sh
@@ -186,10 +186,11 @@ esac
# Required to be defined by caller: Are we testing as root or a regular user
case "$PRIV_NAME" in
root)
- if [[ "$TEST_FLAVOR" = "sys" ]]; then
+ if [[ "$TEST_FLAVOR" = "sys" || "$TEST_FLAVOR" = "apiv2" ]]; then
# Used in local image-scp testing
setup_rootless
echo "PODMAN_ROOTLESS_USER=$ROOTLESS_USER" >> /etc/ci_environment
+ echo "PODMAN_ROOTLESS_UID=$ROOTLESS_UID" >> /etc/ci_environment
fi
;;
rootless)
@@ -203,6 +204,7 @@ esac
if [[ -n "$ROOTLESS_USER" ]]; then
echo "ROOTLESS_USER=$ROOTLESS_USER" >> /etc/ci_environment
+ echo "ROOTLESS_UID=$ROOTLESS_UID" >> /etc/ci_environment
fi
# Required to be defined by caller: Are we testing podman or podman-remote client
diff --git a/contrib/podmanimage/stable/Containerfile b/contrib/podmanimage/stable/Containerfile
index 9121c5cde..70ff439d9 100644
--- a/contrib/podmanimage/stable/Containerfile
+++ b/contrib/podmanimage/stable/Containerfile
@@ -11,6 +11,9 @@ FROM registry.fedoraproject.org/fedora:latest
# Don't include container-selinux and remove
# directories used by dnf that are just taking
# up space.
+# TODO: rpm --setcaps... needed due to Fedora (base) image builds
+# being (maybe still?) affected by
+# https://bugzilla.redhat.com/show_bug.cgi?id=1995337#c3
RUN dnf -y update && \
rpm --setcaps shadow-utils 2>/dev/null && \
dnf -y install podman fuse-overlayfs \
diff --git a/contrib/podmanimage/testing/Containerfile b/contrib/podmanimage/testing/Containerfile
index 16314a633..65c06f98c 100644
--- a/contrib/podmanimage/testing/Containerfile
+++ b/contrib/podmanimage/testing/Containerfile
@@ -11,6 +11,9 @@ FROM registry.fedoraproject.org/fedora:latest
# Don't include container-selinux and remove
# directories used by dnf that are just taking
# up space.
+# TODO: rpm --setcaps... needed due to Fedora (base) image builds
+# being (maybe still?) affected by
+# https://bugzilla.redhat.com/show_bug.cgi?id=1995337#c3
RUN dnf -y update && \
rpm --setcaps shadow-utils 2>/dev/null && \
dnf -y install podman fuse-overlayfs \
diff --git a/contrib/podmanimage/upstream/Containerfile b/contrib/podmanimage/upstream/Containerfile
index c3a07a8d6..96e39c949 100644
--- a/contrib/podmanimage/upstream/Containerfile
+++ b/contrib/podmanimage/upstream/Containerfile
@@ -14,6 +14,9 @@ FROM registry.fedoraproject.org/fedora:latest
# directories used by dnf that are just taking
# up space. The latest podman + deps. come from
# https://copr.fedorainfracloud.org/coprs/rhcontainerbot/podman-next/
+# TODO: rpm --setcaps... needed due to Fedora (base) image builds
+# being (maybe still?) affected by
+# https://bugzilla.redhat.com/show_bug.cgi?id=1995337#c3
RUN dnf -y update && \
rpm --setcaps shadow-utils 2>/dev/null && \
dnf -y install 'dnf-command(copr)' --enablerepo=updates-testing && \
diff --git a/docs/source/markdown/podman-create.1.md b/docs/source/markdown/podman-create.1.md
index 40fca0f3a..425ce7bcc 100644
--- a/docs/source/markdown/podman-create.1.md
+++ b/docs/source/markdown/podman-create.1.md
@@ -1295,13 +1295,14 @@ The _options_ is a comma-separated list and can be:
* **rw**|**ro**
* **z**|**Z**
-* [**r**]**shared**|[**r**]**slave**|[**r**]**private**[**r**]**unbindable**
-* [**r**]**bind**
-* [**no**]**exec**
-* [**no**]**dev**
-* [**no**]**suid**
* [**O**]
* [**U**]
+* [**no**]**copy**
+* [**no**]**dev**
+* [**no**]**exec**
+* [**no**]**suid**
+* [**r**]**bind**
+* [**r**]**shared**|[**r**]**slave**|[**r**]**private**[**r**]**unbindable**
The `CONTAINER-DIR` must be an absolute path such as `/src/docs`. The volume
will be mounted into the container at this directory.
diff --git a/docs/source/markdown/podman-machine-init.1.md b/docs/source/markdown/podman-machine-init.1.md
index 33947bbba..2adb15e6a 100644
--- a/docs/source/markdown/podman-machine-init.1.md
+++ b/docs/source/markdown/podman-machine-init.1.md
@@ -10,9 +10,12 @@ podman\-machine\-init - Initialize a new virtual machine
Initialize a new virtual machine for Podman.
-Podman on macOS requires a virtual machine. This is because containers are Linux -
+Rootless only.
+
+Podman on MacOS and Windows requires a virtual machine. This is because containers are Linux -
containers do not run on any other OS because containers' core functionality are
-tied to the Linux kernel.
+tied to the Linux kernel. Podman machine must be used to manage MacOS and Windows machines,
+but can be optionally used on Linux.
**podman machine init** initializes a new Linux virtual machine where containers are run.
SSH keys are automatically generated to access the VM, and system connections to the root account
diff --git a/docs/source/markdown/podman-machine-inspect.1.md b/docs/source/markdown/podman-machine-inspect.1.md
index 38eb66b0d..29cd775c2 100644
--- a/docs/source/markdown/podman-machine-inspect.1.md
+++ b/docs/source/markdown/podman-machine-inspect.1.md
@@ -13,6 +13,8 @@ Inspect one or more virtual machines
Obtain greater detail about Podman virtual machines. More than one virtual machine can be
inspected at once.
+Rootless only.
+
## OPTIONS
#### **--format**
diff --git a/docs/source/markdown/podman-machine-list.1.md b/docs/source/markdown/podman-machine-list.1.md
index 0c5310463..a25aae090 100644
--- a/docs/source/markdown/podman-machine-list.1.md
+++ b/docs/source/markdown/podman-machine-list.1.md
@@ -12,9 +12,12 @@ podman\-machine\-list - List virtual machines
List Podman managed virtual machines.
-Podman on macOS requires a virtual machine. This is because containers are Linux -
-containers do not run on any other OS because containers' core functionality is
-tied to the Linux kernel.
+Podman on MacOS and Windows requires a virtual machine. This is because containers are Linux -
+containers do not run on any other OS because containers' core functionality are
+tied to the Linux kernel. Podman machine must be used to manage MacOS and Windows machines,
+but can be optionally used on Linux.
+
+Rootless only.
## OPTIONS
diff --git a/docs/source/markdown/podman-machine-rm.1.md b/docs/source/markdown/podman-machine-rm.1.md
index 4a2c59173..d90b615ce 100644
--- a/docs/source/markdown/podman-machine-rm.1.md
+++ b/docs/source/markdown/podman-machine-rm.1.md
@@ -16,6 +16,7 @@ generated for that VM are also removed as is its image file on the filesystem.
Users get a display of what will be deleted and are required to confirm unless the option `--force`
is used.
+Rootless only.
## OPTIONS
diff --git a/docs/source/markdown/podman-machine-set.1.md b/docs/source/markdown/podman-machine-set.1.md
index de90ee4b0..1daf97a61 100644
--- a/docs/source/markdown/podman-machine-set.1.md
+++ b/docs/source/markdown/podman-machine-set.1.md
@@ -10,6 +10,8 @@ podman\-machine\-set - Sets a virtual machine setting
Change a machine setting.
+Rootless only.
+
## OPTIONS
#### **--cpus**=*number*
diff --git a/docs/source/markdown/podman-machine-ssh.1.md b/docs/source/markdown/podman-machine-ssh.1.md
index 6a1455df1..5432f0e9f 100644
--- a/docs/source/markdown/podman-machine-ssh.1.md
+++ b/docs/source/markdown/podman-machine-ssh.1.md
@@ -16,6 +16,8 @@ with the virtual machine is established.
The exit code from ssh command will be forwarded to the podman machine ssh caller, see [Exit Codes](#Exit-Codes).
+Rootless only.
+
## OPTIONS
#### **--help**
diff --git a/docs/source/markdown/podman-machine-start.1.md b/docs/source/markdown/podman-machine-start.1.md
index e55dcab13..b92494dda 100644
--- a/docs/source/markdown/podman-machine-start.1.md
+++ b/docs/source/markdown/podman-machine-start.1.md
@@ -10,9 +10,12 @@ podman\-machine\-start - Start a virtual machine
Starts a virtual machine for Podman.
-Podman on macOS requires a virtual machine. This is because containers are Linux -
+Rootless only.
+
+Podman on MacOS and Windows requires a virtual machine. This is because containers are Linux -
containers do not run on any other OS because containers' core functionality are
-tied to the Linux kernel.
+tied to the Linux kernel. Podman machine must be used to manage MacOS and Windows machines,
+but can be optionally used on Linux.
Only one Podman managed VM can be active at a time. If a VM is already running,
`podman machine start` will return an error.
diff --git a/docs/source/markdown/podman-machine-stop.1.md b/docs/source/markdown/podman-machine-stop.1.md
index 9aa781561..29f3e81f4 100644
--- a/docs/source/markdown/podman-machine-stop.1.md
+++ b/docs/source/markdown/podman-machine-stop.1.md
@@ -10,9 +10,12 @@ podman\-machine\-stop - Stop a virtual machine
Stops a virtual machine.
-Podman on macOS requires a virtual machine. This is because containers are Linux -
+Rootless only.
+
+Podman on MacOS and Windows requires a virtual machine. This is because containers are Linux -
containers do not run on any other OS because containers' core functionality are
-tied to the Linux kernel.
+tied to the Linux kernel. Podman machine must be used to manage MacOS and Windows machines,
+but can be optionally used on Linux.
**podman machine stop** stops a Linux virtual machine where containers are run.
diff --git a/docs/source/markdown/podman-machine.1.md b/docs/source/markdown/podman-machine.1.md
index e9f6c7d20..c55226e02 100644
--- a/docs/source/markdown/podman-machine.1.md
+++ b/docs/source/markdown/podman-machine.1.md
@@ -7,7 +7,14 @@ podman\-machine - Manage Podman's virtual machine
**podman machine** *subcommand*
## DESCRIPTION
-`podman machine` is a set of subcommands that manage Podman's virtual machine on macOS.
+`podman machine` is a set of subcommands that manage Podman's virtual machine.
+
+Podman on MacOS and Windows requires a virtual machine. This is because containers are Linux -
+containers do not run on any other OS because containers' core functionality are
+tied to the Linux kernel. Podman machine must be used to manage MacOS and Windows machines,
+but can be optionally used on Linux.
+
+All `podman machine` commands are rootless only.
## SUBCOMMANDS
diff --git a/docs/source/markdown/podman-run.1.md b/docs/source/markdown/podman-run.1.md
index 488bf6777..5b45c3350 100644
--- a/docs/source/markdown/podman-run.1.md
+++ b/docs/source/markdown/podman-run.1.md
@@ -1362,13 +1362,14 @@ The _options_ is a comma-separated list and can be: <sup>[[1]](#Footnote1)</sup>
* **rw**|**ro**
* **z**|**Z**
-* [**r**]**shared**|[**r**]**slave**|[**r**]**private**[**r**]**unbindable**
-* [**r**]**bind**
-* [**no**]**exec**
-* [**no**]**dev**
-* [**no**]**suid**
* [**O**]
* [**U**]
+* [**no**]**copy**
+* [**no**]**dev**
+* [**no**]**exec**
+* [**no**]**suid**
+* [**r**]**bind**
+* [**r**]**shared**|[**r**]**slave**|[**r**]**private**[**r**]**unbindable**
The `CONTAINER-DIR` must be an absolute path such as `/src/docs`. The volume
will be mounted into the container at this directory.
diff --git a/docs/source/markdown/podman-volume-create.1.md b/docs/source/markdown/podman-volume-create.1.md
index 31e109791..32b10da84 100644
--- a/docs/source/markdown/podman-volume-create.1.md
+++ b/docs/source/markdown/podman-volume-create.1.md
@@ -31,9 +31,10 @@ Set metadata for a volume (e.g., --label mykey=value).
Set driver specific options.
For the default driver, **local**, this allows a volume to be configured to mount a filesystem on the host.
-For the `local` driver the following options are supported: `type`, `device`, and `o`.
+For the `local` driver the following options are supported: `type`, `device`, `o`, and `[no]copy`.
The `type` option sets the type of the filesystem to be mounted, and is equivalent to the `-t` flag to **mount(8)**.
The `device` option sets the device to be mounted, and is equivalent to the `device` argument to **mount(8)**.
+The `copy` option enables copying files from the container image path where the mount is created to the newly created volume on the first run. `copy` is the default.
The `o` option sets options for the mount, and is equivalent to the `-o` flag to **mount(8)** with these exceptions:
diff --git a/docs/source/markdown/podman-volume-reload.1.md b/docs/source/markdown/podman-volume-reload.1.md
new file mode 100644
index 000000000..5b9e9b9ac
--- /dev/null
+++ b/docs/source/markdown/podman-volume-reload.1.md
@@ -0,0 +1,29 @@
+% podman-volume-reload(1)
+
+## NAME
+podman\-volume\-reload - Reload all volumes from volumes plugins
+
+## SYNOPSIS
+**podman volume reload**
+
+## DESCRIPTION
+
+**podman volume reload** checks all configured volume plugins and updates the libpod database with all available volumes.
+Existing volumes are also removed from the database when they are no longer present in the plugin.
+
+This command it is best effort and cannot guarantee a perfect state because plugins can be modified from the outside at any time.
+
+Note: This command is not supported with podman-remote.
+
+## EXAMPLES
+
+```
+$ podman volume reload
+Added:
+vol6
+Removed:
+t3
+```
+
+## SEE ALSO
+**[podman(1)](podman.1.md)**, **[podman-volume(1)](podman-volume.1.md)**
diff --git a/docs/source/markdown/podman-volume.1.md b/docs/source/markdown/podman-volume.1.md
index 476d58591..a437590b3 100644
--- a/docs/source/markdown/podman-volume.1.md
+++ b/docs/source/markdown/podman-volume.1.md
@@ -21,6 +21,7 @@ podman volume is a set of subcommands that manage volumes.
| ls | [podman-volume-ls(1)](podman-volume-ls.1.md) | List all the available volumes. |
| mount | [podman-volume-mount(1)](podman-volume-mount.1.md) | Mount a volume filesystem. |
| prune | [podman-volume-prune(1)](podman-volume-prune.1.md) | Remove all unused volumes. |
+| reload | [podman-volume-reload(1)](podman-volume-reload.1.md) | Reload all volumes from volumes plugins. |
| rm | [podman-volume-rm(1)](podman-volume-rm.1.md) | Remove one or more volumes. |
| unmount | [podman-volume-unmount(1)](podman-volume-unmount.1.md) | Unmount a volume. |
diff --git a/go.mod b/go.mod
index 2f196d9b9..35d4b931f 100644
--- a/go.mod
+++ b/go.mod
@@ -12,7 +12,7 @@ require (
github.com/containernetworking/cni v1.1.1
github.com/containernetworking/plugins v1.1.1
github.com/containers/buildah v1.26.1-0.20220609225314-e66309ebde8c
- github.com/containers/common v0.48.1-0.20220608111710-dbecabbe82c9
+ github.com/containers/common v0.48.1-0.20220628131511-a8336c1613fe
github.com/containers/conmon v2.0.20+incompatible
github.com/containers/image/v5 v5.21.2-0.20220617075545-929f14a56f5c
github.com/containers/ocicrypt v1.1.5
@@ -57,7 +57,7 @@ require (
github.com/sirupsen/logrus v1.8.1
github.com/spf13/cobra v1.5.0
github.com/spf13/pflag v1.0.5
- github.com/stretchr/testify v1.7.5
+ github.com/stretchr/testify v1.8.0
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
@@ -75,3 +75,5 @@ require (
)
require github.com/docker/libnetwork v0.8.0-dev.2.0.20190625141545-5a177b73e316 // indirect
+
+replace github.com/opencontainers/runc => github.com/opencontainers/runc v1.1.1-0.20220617142545-8b9452f75cbc
diff --git a/go.sum b/go.sum
index 012d8cd20..c34b5dfbb 100644
--- a/go.sum
+++ b/go.sum
@@ -198,8 +198,6 @@ github.com/charithe/durationcheck v0.0.9/go.mod h1:SSbRIBVfMjCi/kEB6K65XEA83D6pr
github.com/chavacava/garif v0.0.0-20210405164556-e8a0a408d6af/go.mod h1:Qjyv4H3//PWVzTeCezG2b9IRn6myJxJSr4TD/xo6ojU=
github.com/checkpoint-restore/checkpointctl v0.0.0-20220321135231-33f4a66335f0 h1:txB5jvhzUCSiiQmqmMWpo5CEB7Gj/Hq5Xqi7eaPl8ko=
github.com/checkpoint-restore/checkpointctl v0.0.0-20220321135231-33f4a66335f0/go.mod h1:67kWC1PXQLR3lM/mmNnu3Kzn7K4TSWZAGUuQP1JSngk=
-github.com/checkpoint-restore/go-criu/v4 v4.1.0/go.mod h1:xUQBLp4RLc5zJtWY++yjOoMoB5lihDt7fai+75m+rGw=
-github.com/checkpoint-restore/go-criu/v5 v5.0.0/go.mod h1:cfwC0EG7HMUenopBsUf9d89JlCLQIfgVcNsNN0t6T2M=
github.com/checkpoint-restore/go-criu/v5 v5.2.0/go.mod h1:E/eQpaFtUKGOOSEBZgmKAcn+zUUwWxqcaKZlF54wK8E=
github.com/checkpoint-restore/go-criu/v5 v5.3.0 h1:wpFFOoomK3389ue2lAb0Boag6XPht5QYpipxmSNL4d8=
github.com/checkpoint-restore/go-criu/v5 v5.3.0/go.mod h1:E/eQpaFtUKGOOSEBZgmKAcn+zUUwWxqcaKZlF54wK8E=
@@ -213,7 +211,6 @@ github.com/cilium/ebpf v0.0.0-20200110133405-4032b1d8aae3/go.mod h1:MA5e5Lr8slmE
github.com/cilium/ebpf v0.0.0-20200702112145-1c8d4c9ef775/go.mod h1:7cR51M8ViRLIdUjrmSXlK9pkrsDlLHbO8jiB8X8JnOc=
github.com/cilium/ebpf v0.2.0/go.mod h1:To2CFviqOWL/M0gIMsvSMlqe7em/l1ALkX1PyjrX2Qs=
github.com/cilium/ebpf v0.4.0/go.mod h1:4tRaxcgiL706VnOzHOdBlY8IEAIdxINsQBcU4xJJXRs=
-github.com/cilium/ebpf v0.6.2/go.mod h1:4tRaxcgiL706VnOzHOdBlY8IEAIdxINsQBcU4xJJXRs=
github.com/cilium/ebpf v0.7.0/go.mod h1:/oI2+1shJiTGAMgl6/RgJr36Eo1jzrRcAWbcXO2usCA=
github.com/cilium/ebpf v0.9.0/go.mod h1:+OhNOIXx/Fnu1IE8bJz2dzOA+VSfyTfdNUVdlQnxUFY=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
@@ -340,12 +337,14 @@ github.com/containernetworking/plugins v1.1.1 h1:+AGfFigZ5TiQH00vhR8qPeSatj53eNG
github.com/containernetworking/plugins v1.1.1/go.mod h1:Sr5TH/eBsGLXK/h71HeLfX19sZPp3ry5uHSkI4LPxV8=
github.com/containers/buildah v1.26.1-0.20220609225314-e66309ebde8c h1:/fKyiLFFuceBPZGJ0Lig7ElURhfsslAOw1BOcItD+X8=
github.com/containers/buildah v1.26.1-0.20220609225314-e66309ebde8c/go.mod h1:b0L+u2Dam7soWGn5sVTK31L++Xrf80AbGvK5z9D2+lw=
-github.com/containers/common v0.48.1-0.20220608111710-dbecabbe82c9 h1:sK+TNC8oUBkruZTIqwYJrENetSLQnk+goBVyLiqsJq8=
github.com/containers/common v0.48.1-0.20220608111710-dbecabbe82c9/go.mod h1:WBLwq+i7bicCpH54V70HM6s7jqDAESTlYnd05XXp0ac=
+github.com/containers/common v0.48.1-0.20220628131511-a8336c1613fe h1:H5YI9PXhDB974IkSCUaha+AF60TunRdHaGElZroYx7M=
+github.com/containers/common v0.48.1-0.20220628131511-a8336c1613fe/go.mod h1:UDe7OTpNdtJA2T80Sp7yB0yTaj79f4kMNQbTsNxsqoY=
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=
github.com/containers/image/v5 v5.21.2-0.20220520105616-e594853d6471/go.mod h1:KntCBNQn3qOuZmQuJ38ORyTozmWXiuo05Vef2S0Sm5M=
+github.com/containers/image/v5 v5.21.2-0.20220615100411-a78a00792916/go.mod h1:hEf8L08Hrh/3fK4vLf5l7988MJmij2swfCBUzqgnhF4=
github.com/containers/image/v5 v5.21.2-0.20220617075545-929f14a56f5c h1:U2F/FaMt8gPP8sIpBfvXMCP5gAZfyxoYZ7lmu0dwsXc=
github.com/containers/image/v5 v5.21.2-0.20220617075545-929f14a56f5c/go.mod h1:hEf8L08Hrh/3fK4vLf5l7988MJmij2swfCBUzqgnhF4=
github.com/containers/libtrust v0.0.0-20200511145503-9c3a6c22cd9a h1:spAGlqziZjCJL25C6F1zsQY05tfCKE9F5YwtEWWe6hU=
@@ -365,6 +364,7 @@ github.com/containers/storage v1.38.0/go.mod h1:lBzt28gAk5ADZuRtwdndRJyqX22vnRaX
github.com/containers/storage v1.40.2/go.mod h1:zUyPC3CFIGR1OhY1CKkffxgw9+LuH76PGvVcFj38dgs=
github.com/containers/storage v1.41.0/go.mod h1:Pb0l5Sm/89kolX3o2KolKQ5cCHk5vPNpJrhNaLcdS5s=
github.com/containers/storage v1.41.1-0.20220607143333-8951d0153bf6/go.mod h1:6XQ68cEG8ojfP/m3HIupFV1rZsnqeFmaE8N1ctBP94Y=
+github.com/containers/storage v1.41.1-0.20220614214904-388efef4bf7e/go.mod h1:6XQ68cEG8ojfP/m3HIupFV1rZsnqeFmaE8N1ctBP94Y=
github.com/containers/storage v1.41.1-0.20220616120034-7df64288ef35 h1:6o+kw2z0BrdJ1wNYUbwLVzlb/65KPmZSy+32GNfppzM=
github.com/containers/storage v1.41.1-0.20220616120034-7df64288ef35/go.mod h1:6XQ68cEG8ojfP/m3HIupFV1rZsnqeFmaE8N1ctBP94Y=
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
@@ -399,7 +399,6 @@ github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7Do
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/creack/pty v1.1.11 h1:07n33Z8lZxZ2qwegKbObQohDhXDQxiMMz1NOUGYlesw=
github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
-github.com/cyphar/filepath-securejoin v0.2.2/go.mod h1:FpkQEhXnPnOthhzymB7CGsFk2G9VLXONKD9G7QGMM+4=
github.com/cyphar/filepath-securejoin v0.2.3 h1:YX6ebbZCZP7VkM3scTTokDgBL2TY741X51MTk3ycuNI=
github.com/cyphar/filepath-securejoin v0.2.3/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4=
github.com/d2g/dhcp4 v0.0.0-20170904100407-a1d1b6c41b1c/go.mod h1:Ct2BUK8SB0YC1SMSibvLzxjeJLnrYEVLULFNiHY9YfQ=
@@ -1053,19 +1052,8 @@ github.com/opencontainers/image-spec v1.0.3-0.20211202183452-c5a74bcca799/go.mod
github.com/opencontainers/image-spec v1.0.3-0.20211202193544-a5463b7f9c84/go.mod h1:Qnt1q4cjDNQI9bT832ziho5Iw2BhK8o1KwLOwW56VP4=
github.com/opencontainers/image-spec v1.0.3-0.20220114050600-8b9d41f48198 h1:+czc/J8SlhPKLOtVLMQc+xDCFBT73ZStMsRhSsUhsSg=
github.com/opencontainers/image-spec v1.0.3-0.20220114050600-8b9d41f48198/go.mod h1:j4h1pJW6ZcJTgMZWP3+7RlG3zTaP02aDZ/Qw0sppK7Q=
-github.com/opencontainers/runc v0.0.0-20190115041553-12f6a991201f/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U=
-github.com/opencontainers/runc v0.1.1/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U=
-github.com/opencontainers/runc v1.0.0-rc8.0.20190926000215-3e425f80a8c9/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U=
-github.com/opencontainers/runc v1.0.0-rc9/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U=
-github.com/opencontainers/runc v1.0.0-rc93/go.mod h1:3NOsor4w32B2tC0Zbl8Knk4Wg84SM2ImC1fxBuqJ/H0=
-github.com/opencontainers/runc v1.0.2/go.mod h1:aTaHFFwQXuA71CiyxOdFFIorAoemI04suvGRQFzWTD0=
-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-0.20220607072441-a7a45d7d2721/go.mod h1:QvA0UNe48mC1JxcXq0sENIR38+/LdJMLNxuAvtFBhxA=
-github.com/opencontainers/runc v1.1.1/go.mod h1:Tj1hFw6eFWp/o33uxGf5yF2BX5yz2Z6iptFpuvbbKqc=
-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/runc v1.1.1-0.20220617142545-8b9452f75cbc h1:qjkUzmFsOFbQyjObybk40mRida83j5IHRaKzLGdBbEU=
+github.com/opencontainers/runc v1.1.1-0.20220617142545-8b9452f75cbc/go.mod h1:wUOQGsiKae6VzA/UvlCK3cO+pHk8F2VQHlIoITEfMM8=
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=
@@ -1198,10 +1186,10 @@ github.com/sclevine/spec v1.2.0/go.mod h1:W4J29eT/Kzv7/b9IWLB055Z+qvVC9vt0Arko24
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
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/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/seccomp/libseccomp-golang v0.10.0 h1:aA4bp+/Zzi0BnWZ2F1wgNBs5gTpm+na2rWM6M9YjLpY=
+github.com/seccomp/libseccomp-golang v0.10.0/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=
@@ -1280,8 +1268,9 @@ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals=
-github.com/stretchr/testify v1.7.5 h1:s5PTfem8p8EbKQOctVV53k6jCJt3UX4IEJzwh+C324Q=
github.com/stretchr/testify v1.7.5/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
+github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
+github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
github.com/sylabs/sif/v2 v2.7.0/go.mod h1:TiyBWsgWeh5yBeQFNuQnvROwswqK7YJT8JA1L53bsXQ=
github.com/sylabs/sif/v2 v2.7.1 h1:XXt9AP39sQfsMCGOGQ/XP9H47yqZOvAonalkaCaNIYM=
diff --git a/libpod/container_api.go b/libpod/container_api.go
index c14fe95b0..f35cce772 100644
--- a/libpod/container_api.go
+++ b/libpod/container_api.go
@@ -666,6 +666,15 @@ func (c *Container) Cleanup(ctx context.Context) error {
defer c.lock.Unlock()
if err := c.syncContainer(); err != nil {
+ switch errors.Cause(err) {
+ // When the container has already been removed, the OCI runtime directory remain.
+ case define.ErrNoSuchCtr, define.ErrCtrRemoved:
+ if err := c.cleanupRuntime(ctx); err != nil {
+ return errors.Wrapf(err, "error cleaning up container %s from OCI runtime", c.ID())
+ }
+ default:
+ logrus.Errorf("Syncing container %s status: %v", c.ID(), err)
+ }
return err
}
}
diff --git a/libpod/container_config.go b/libpod/container_config.go
index 45ff03d58..544c45a8c 100644
--- a/libpod/container_config.go
+++ b/libpod/container_config.go
@@ -424,7 +424,6 @@ type InfraInherit struct {
CapDrop []string `json:"cap_drop,omitempty"`
HostDeviceList []spec.LinuxDevice `json:"host_device_list,omitempty"`
ImageVolumes []*specgen.ImageVolume `json:"image_volumes,omitempty"`
- InfraResources *spec.LinuxResources `json:"resource_limits,omitempty"`
Mounts []spec.Mount `json:"mounts,omitempty"`
NoNewPrivileges bool `json:"no_new_privileges,omitempty"`
OverlayVolumes []*specgen.OverlayVolume `json:"overlay_volumes,omitempty"`
diff --git a/libpod/container_exec.go b/libpod/container_exec.go
index be00c6fbe..b112273d0 100644
--- a/libpod/container_exec.go
+++ b/libpod/container_exec.go
@@ -277,9 +277,13 @@ func (c *Container) ExecStart(sessionID string) error {
return c.save()
}
+func (c *Container) ExecStartAndAttach(sessionID string, streams *define.AttachStreams, newSize *define.TerminalSize) error {
+ return c.execStartAndAttach(sessionID, streams, newSize, false)
+}
+
// ExecStartAndAttach starts and attaches to an exec session in a container.
// newSize resizes the tty to this size before the process is started, must be nil if the exec session has no tty
-func (c *Container) ExecStartAndAttach(sessionID string, streams *define.AttachStreams, newSize *define.TerminalSize) error {
+func (c *Container) execStartAndAttach(sessionID string, streams *define.AttachStreams, newSize *define.TerminalSize, isHealthcheck bool) error {
if !c.batched {
c.lock.Lock()
defer c.lock.Unlock()
@@ -315,7 +319,12 @@ func (c *Container) ExecStartAndAttach(sessionID string, streams *define.AttachS
return err
}
- c.newContainerEvent(events.Exec)
+ if isHealthcheck {
+ c.newContainerEvent(events.HealthStatus)
+ } else {
+ c.newContainerEvent(events.Exec)
+ }
+
logrus.Debugf("Successfully started exec session %s in container %s", session.ID(), c.ID())
var lastErr error
@@ -743,10 +752,14 @@ func (c *Container) ExecResize(sessionID string, newSize define.TerminalSize) er
return c.ociRuntime.ExecAttachResize(c, sessionID, newSize)
}
+func (c *Container) Exec(config *ExecConfig, streams *define.AttachStreams, resize <-chan define.TerminalSize) (int, error) {
+ return c.exec(config, streams, resize, false)
+}
+
// Exec emulates the old Libpod exec API, providing a single call to create,
// run, and remove an exec session. Returns exit code and error. Exit code is
// not guaranteed to be set sanely if error is not nil.
-func (c *Container) Exec(config *ExecConfig, streams *define.AttachStreams, resize <-chan define.TerminalSize) (int, error) {
+func (c *Container) exec(config *ExecConfig, streams *define.AttachStreams, resize <-chan define.TerminalSize, isHealthcheck bool) (int, error) {
sessionID, err := c.ExecCreate(config)
if err != nil {
return -1, err
@@ -780,7 +793,7 @@ func (c *Container) Exec(config *ExecConfig, streams *define.AttachStreams, resi
}()
}
- if err := c.ExecStartAndAttach(sessionID, streams, size); err != nil {
+ if err := c.execStartAndAttach(sessionID, streams, size, isHealthcheck); err != nil {
return -1, err
}
diff --git a/libpod/container_internal.go b/libpod/container_internal.go
index ae61298f3..3b01ee6c8 100644
--- a/libpod/container_internal.go
+++ b/libpod/container_internal.go
@@ -21,6 +21,7 @@ import (
"github.com/containers/common/pkg/cgroups"
"github.com/containers/common/pkg/chown"
"github.com/containers/common/pkg/config"
+ cutil "github.com/containers/common/pkg/util"
"github.com/containers/podman/v4/libpod/define"
"github.com/containers/podman/v4/libpod/events"
"github.com/containers/podman/v4/pkg/ctime"
@@ -1288,8 +1289,9 @@ func (c *Container) stop(timeout uint) error {
if err := c.syncContainer(); err != nil {
switch errors.Cause(err) {
// If the container has already been removed (e.g., via
- // the cleanup process), there's nothing left to do.
+ // the cleanup process), set the container state to "stopped".
case define.ErrNoSuchCtr, define.ErrCtrRemoved:
+ c.state.State = define.ContainerStateStopped
return stopErr
default:
if stopErr != nil {
@@ -1639,30 +1641,16 @@ func (c *Container) mountNamedVolume(v *ContainerNamedVolume, mountpoint string)
if err := vol.update(); err != nil {
return nil, err
}
- if vol.state.NeedsCopyUp {
+ _, hasNoCopy := vol.config.Options["nocopy"]
+ if vol.state.NeedsCopyUp && !cutil.StringInSlice("nocopy", v.Options) && !hasNoCopy {
logrus.Debugf("Copying up contents from container %s to volume %s", c.ID(), vol.Name())
- // If the volume is not empty, we should not copy up.
- volMount := vol.mountPoint()
- contents, err := ioutil.ReadDir(volMount)
- if err != nil {
- return nil, errors.Wrapf(err, "error listing contents of volume %s mountpoint when copying up from container %s", vol.Name(), c.ID())
- }
- if len(contents) > 0 {
- // The volume is not empty. It was likely modified
- // outside of Podman. For safety, let's not copy up into
- // it. Fixes CVE-2020-1726.
- return vol, nil
- }
-
srcDir, err := securejoin.SecureJoin(mountpoint, v.Dest)
if err != nil {
return nil, errors.Wrapf(err, "error calculating destination path to copy up container %s volume %s", c.ID(), vol.Name())
}
// Do a manual stat on the source directory to verify existence.
// Skip the rest if it exists.
- // TODO: Should this be stat or lstat? I'm using lstat because I
- // think copy-up doesn't happen when the source is a link.
srcStat, err := os.Lstat(srcDir)
if err != nil {
if os.IsNotExist(err) {
@@ -1688,6 +1676,19 @@ func (c *Container) mountNamedVolume(v *ContainerNamedVolume, mountpoint string)
return vol, nil
}
+ // If the volume is not empty, we should not copy up.
+ volMount := vol.mountPoint()
+ contents, err := ioutil.ReadDir(volMount)
+ if err != nil {
+ return nil, errors.Wrapf(err, "error listing contents of volume %s mountpoint when copying up from container %s", vol.Name(), c.ID())
+ }
+ if len(contents) > 0 {
+ // The volume is not empty. It was likely modified
+ // outside of Podman. For safety, let's not copy up into
+ // it. Fixes CVE-2020-1726.
+ return vol, nil
+ }
+
// Set NeedsCopyUp to false since we are about to do first copy
// Do not copy second time.
vol.state.NeedsCopyUp = false
diff --git a/libpod/container_internal_linux.go b/libpod/container_internal_linux.go
index 77b598b16..0f4bf0f55 100644
--- a/libpod/container_internal_linux.go
+++ b/libpod/container_internal_linux.go
@@ -870,6 +870,7 @@ func (c *Container) generateSpec(ctx context.Context) (*spec.Spec, error) {
if err != nil {
return nil, err
}
+
g.SetLinuxCgroupsPath(cgroupPath)
// Warning: CDI may alter g.Config in place.
@@ -1141,7 +1142,7 @@ func (c *Container) addCheckpointImageMetadata(importBuilder *buildah.Builder) e
return fmt.Errorf("getting host info: %v", err)
}
- criuVersion, err := criu.GetCriuVestion()
+ criuVersion, err := criu.GetCriuVersion()
if err != nil {
return fmt.Errorf("getting criu version: %v", err)
}
diff --git a/libpod/define/volume_inspect.go b/libpod/define/volume_inspect.go
index fac179176..aaa23b4fc 100644
--- a/libpod/define/volume_inspect.go
+++ b/libpod/define/volume_inspect.go
@@ -57,3 +57,9 @@ type InspectVolumeData struct {
// UID/GID.
NeedsChown bool `json:"NeedsChown,omitempty"`
}
+
+type VolumeReload struct {
+ Added []string
+ Removed []string
+ Errors []error
+}
diff --git a/libpod/events.go b/libpod/events.go
index 021b3b53c..bb50df92d 100644
--- a/libpod/events.go
+++ b/libpod/events.go
@@ -33,6 +33,16 @@ func (c *Container) newContainerEvent(status events.Status) {
Attributes: c.Labels(),
}
+ // if the current event is a HealthStatus event, we need to get the current
+ // status of the container to pass to the event
+ if status == events.HealthStatus {
+ containerHealthStatus, err := c.healthCheckStatus()
+ if err != nil {
+ e.HealthStatus = fmt.Sprintf("%v", err)
+ }
+ e.HealthStatus = containerHealthStatus
+ }
+
if err := c.runtime.eventer.Write(e); err != nil {
logrus.Errorf("Unable to write pod event: %q", err)
}
diff --git a/libpod/events/config.go b/libpod/events/config.go
index 2e7016136..a678baa2d 100644
--- a/libpod/events/config.go
+++ b/libpod/events/config.go
@@ -40,6 +40,8 @@ type Event struct {
Time time.Time
// Type of event that occurred
Type Type
+ // Health status of the current container
+ HealthStatus string `json:"health_status,omitempty"`
Details
}
@@ -141,6 +143,8 @@ const (
Exited Status = "died"
// Export ...
Export Status = "export"
+ // HealthStatus ...
+ HealthStatus Status = "health_status"
// History ...
History Status = "history"
// Import ...
diff --git a/libpod/events/events.go b/libpod/events/events.go
index a30e0f1ca..a8001ab95 100644
--- a/libpod/events/events.go
+++ b/libpod/events/events.go
@@ -76,7 +76,7 @@ func (e *Event) ToHumanReadable(truncate bool) string {
}
switch e.Type {
case Container, Pod:
- humanFormat = fmt.Sprintf("%s %s %s %s (image=%s, name=%s", e.Time, e.Type, e.Status, id, e.Image, e.Name)
+ humanFormat = fmt.Sprintf("%s %s %s %s (image=%s, name=%s, health_status=%s", e.Time, e.Type, e.Status, id, e.Image, e.Name, e.HealthStatus)
// check if the container has labels and add it to the output
if len(e.Attributes) > 0 {
for k, v := range e.Attributes {
@@ -168,6 +168,8 @@ func StringToStatus(name string) (Status, error) {
return Exited, nil
case Export.String():
return Export, nil
+ case HealthStatus.String():
+ return HealthStatus, nil
case History.String():
return History, nil
case Import.String():
diff --git a/libpod/events/journal_linux.go b/libpod/events/journal_linux.go
index d21b60c68..036638d34 100644
--- a/libpod/events/journal_linux.go
+++ b/libpod/events/journal_linux.go
@@ -58,6 +58,7 @@ func (e EventJournalD) Write(ee Event) error {
}
m["PODMAN_LABELS"] = string(b)
}
+ m["PODMAN_HEALTH_STATUS"] = ee.HealthStatus
case Network:
m["PODMAN_ID"] = ee.ID
m["PODMAN_NETWORK_NAME"] = ee.Network
@@ -213,6 +214,7 @@ func newEventFromJournalEntry(entry *sdjournal.JournalEntry) (*Event, error) {
newEvent.Details = Details{Attributes: labels}
}
}
+ newEvent.HealthStatus = entry.Fields["PODMAN_HEALTH_STATUS"]
case Network:
newEvent.ID = entry.Fields["PODMAN_ID"]
newEvent.Network = entry.Fields["PODMAN_NETWORK_NAME"]
diff --git a/libpod/healthcheck.go b/libpod/healthcheck.go
index bd77e98c6..95c70b60e 100644
--- a/libpod/healthcheck.go
+++ b/libpod/healthcheck.go
@@ -90,7 +90,7 @@ func (c *Container) runHealthCheck() (define.HealthCheckStatus, error) {
hcResult := define.HealthCheckSuccess
config := new(ExecConfig)
config.Command = newCommand
- exitCode, hcErr := c.Exec(config, streams, nil)
+ exitCode, hcErr := c.exec(config, streams, nil, true)
if hcErr != nil {
errCause := errors.Cause(hcErr)
hcResult = define.HealthCheckFailure
@@ -232,18 +232,27 @@ func (c *Container) getHealthCheckLog() (define.HealthCheckResults, error) {
// HealthCheckStatus returns the current state of a container with a healthcheck
func (c *Container) HealthCheckStatus() (string, error) {
+ c.lock.Lock()
+ defer c.lock.Unlock()
+ return c.healthCheckStatus()
+}
+
+// Internal function to return the current state of a container with a healthcheck.
+// This function does not lock the container.
+func (c *Container) healthCheckStatus() (string, error) {
if !c.HasHealthCheck() {
return "", errors.Errorf("container %s has no defined healthcheck", c.ID())
}
- c.lock.Lock()
- defer c.lock.Unlock()
+
if err := c.syncContainer(); err != nil {
return "", err
}
+
results, err := c.getHealthCheckLog()
if err != nil {
return "", errors.Wrapf(err, "unable to get healthcheck log for %s", c.ID())
}
+
return results.Status, nil
}
diff --git a/libpod/oci_conmon_linux.go b/libpod/oci_conmon_linux.go
index d417626dc..7a9ae7ee5 100644
--- a/libpod/oci_conmon_linux.go
+++ b/libpod/oci_conmon_linux.go
@@ -23,6 +23,9 @@ import (
"text/template"
"time"
+ runcconfig "github.com/opencontainers/runc/libcontainer/configs"
+ "github.com/opencontainers/runc/libcontainer/devices"
+
"github.com/containers/common/pkg/cgroups"
"github.com/containers/common/pkg/config"
conmonConfig "github.com/containers/conmon/runner/config"
@@ -1433,9 +1436,14 @@ func (r *ConmonOCIRuntime) moveConmonToCgroupAndSignal(ctr *Container, cmd *exec
// TODO: This should be a switch - we are not guaranteed that
// there are only 2 valid cgroup managers
cgroupParent := ctr.CgroupParent()
+ cgroupPath := filepath.Join(ctr.config.CgroupParent, "conmon")
+ Resource := ctr.Spec().Linux.Resources
+ cgroupResources, err := GetLimits(Resource)
+ if err != nil {
+ logrus.StandardLogger().Log(logLevel, "Could not get ctr resources")
+ }
if ctr.CgroupManager() == config.SystemdCgroupsManager {
unitName := createUnitName("libpod-conmon", ctr.ID())
-
realCgroupParent := cgroupParent
splitParent := strings.Split(cgroupParent, "/")
if strings.HasSuffix(cgroupParent, ".slice") && len(splitParent) > 1 {
@@ -1447,8 +1455,7 @@ func (r *ConmonOCIRuntime) moveConmonToCgroupAndSignal(ctr *Container, cmd *exec
logrus.StandardLogger().Logf(logLevel, "Failed to add conmon to systemd sandbox cgroup: %v", err)
}
} else {
- cgroupPath := filepath.Join(ctr.config.CgroupParent, "conmon")
- control, err := cgroups.New(cgroupPath, &spec.LinuxResources{})
+ control, err := cgroups.New(cgroupPath, &cgroupResources)
if err != nil {
logrus.StandardLogger().Logf(logLevel, "Failed to add conmon to cgroupfs sandbox cgroup: %v", err)
} else if err := control.AddPid(cmd.Process.Pid); err != nil {
@@ -1730,3 +1737,191 @@ func httpAttachNonTerminalCopy(container *net.UnixConn, http *bufio.ReadWriter,
}
}
}
+
+// GetLimits converts spec resource limits to cgroup consumable limits
+func GetLimits(resource *spec.LinuxResources) (runcconfig.Resources, error) {
+ if resource == nil {
+ resource = &spec.LinuxResources{}
+ }
+ final := &runcconfig.Resources{}
+ devs := []*devices.Rule{}
+
+ // Devices
+ for _, entry := range resource.Devices {
+ if entry.Major == nil || entry.Minor == nil {
+ continue
+ }
+ runeType := 'a'
+ switch entry.Type {
+ case "b":
+ runeType = 'b'
+ case "c":
+ runeType = 'c'
+ }
+
+ devs = append(devs, &devices.Rule{
+ Type: devices.Type(runeType),
+ Major: *entry.Major,
+ Minor: *entry.Minor,
+ Permissions: devices.Permissions(entry.Access),
+ Allow: entry.Allow,
+ })
+ }
+ final.Devices = devs
+
+ // HugepageLimits
+ pageLimits := []*runcconfig.HugepageLimit{}
+ for _, entry := range resource.HugepageLimits {
+ pageLimits = append(pageLimits, &runcconfig.HugepageLimit{
+ Pagesize: entry.Pagesize,
+ Limit: entry.Limit,
+ })
+ }
+ final.HugetlbLimit = pageLimits
+
+ // Networking
+ netPriorities := []*runcconfig.IfPrioMap{}
+ if resource.Network != nil {
+ for _, entry := range resource.Network.Priorities {
+ netPriorities = append(netPriorities, &runcconfig.IfPrioMap{
+ Interface: entry.Name,
+ Priority: int64(entry.Priority),
+ })
+ }
+ }
+ final.NetPrioIfpriomap = netPriorities
+ rdma := make(map[string]runcconfig.LinuxRdma)
+ for name, entry := range resource.Rdma {
+ rdma[name] = runcconfig.LinuxRdma{HcaHandles: entry.HcaHandles, HcaObjects: entry.HcaObjects}
+ }
+ final.Rdma = rdma
+
+ // Memory
+ if resource.Memory != nil {
+ if resource.Memory.Limit != nil {
+ final.Memory = *resource.Memory.Limit
+ }
+ if resource.Memory.Reservation != nil {
+ final.MemoryReservation = *resource.Memory.Reservation
+ }
+ if resource.Memory.Swap != nil {
+ final.MemorySwap = *resource.Memory.Swap
+ }
+ if resource.Memory.Swappiness != nil {
+ final.MemorySwappiness = resource.Memory.Swappiness
+ }
+ }
+
+ // CPU
+ if resource.CPU != nil {
+ if resource.CPU.Period != nil {
+ final.CpuPeriod = *resource.CPU.Period
+ }
+ if resource.CPU.Quota != nil {
+ final.CpuQuota = *resource.CPU.Quota
+ }
+ if resource.CPU.RealtimePeriod != nil {
+ final.CpuRtPeriod = *resource.CPU.RealtimePeriod
+ }
+ if resource.CPU.RealtimeRuntime != nil {
+ final.CpuRtRuntime = *resource.CPU.RealtimeRuntime
+ }
+ if resource.CPU.Shares != nil {
+ final.CpuShares = *resource.CPU.Shares
+ }
+ final.CpusetCpus = resource.CPU.Cpus
+ final.CpusetMems = resource.CPU.Mems
+ }
+
+ // BlkIO
+ if resource.BlockIO != nil {
+ if len(resource.BlockIO.ThrottleReadBpsDevice) > 0 {
+ for _, entry := range resource.BlockIO.ThrottleReadBpsDevice {
+ throttle := &runcconfig.ThrottleDevice{}
+ dev := &runcconfig.BlockIODevice{
+ Major: entry.Major,
+ Minor: entry.Minor,
+ }
+ throttle.BlockIODevice = *dev
+ throttle.Rate = entry.Rate
+ final.BlkioThrottleReadBpsDevice = append(final.BlkioThrottleReadBpsDevice, throttle)
+ }
+ }
+ if len(resource.BlockIO.ThrottleWriteBpsDevice) > 0 {
+ for _, entry := range resource.BlockIO.ThrottleWriteBpsDevice {
+ throttle := &runcconfig.ThrottleDevice{}
+ dev := &runcconfig.BlockIODevice{
+ Major: entry.Major,
+ Minor: entry.Minor,
+ }
+ throttle.BlockIODevice = *dev
+ throttle.Rate = entry.Rate
+ final.BlkioThrottleWriteBpsDevice = append(final.BlkioThrottleWriteBpsDevice, throttle)
+ }
+ }
+ if len(resource.BlockIO.ThrottleReadIOPSDevice) > 0 {
+ for _, entry := range resource.BlockIO.ThrottleReadIOPSDevice {
+ throttle := &runcconfig.ThrottleDevice{}
+ dev := &runcconfig.BlockIODevice{
+ Major: entry.Major,
+ Minor: entry.Minor,
+ }
+ throttle.BlockIODevice = *dev
+ throttle.Rate = entry.Rate
+ final.BlkioThrottleReadIOPSDevice = append(final.BlkioThrottleReadIOPSDevice, throttle)
+ }
+ }
+ if len(resource.BlockIO.ThrottleWriteIOPSDevice) > 0 {
+ for _, entry := range resource.BlockIO.ThrottleWriteIOPSDevice {
+ throttle := &runcconfig.ThrottleDevice{}
+ dev := &runcconfig.BlockIODevice{
+ Major: entry.Major,
+ Minor: entry.Minor,
+ }
+ throttle.BlockIODevice = *dev
+ throttle.Rate = entry.Rate
+ final.BlkioThrottleWriteIOPSDevice = append(final.BlkioThrottleWriteIOPSDevice, throttle)
+ }
+ }
+ if resource.BlockIO.LeafWeight != nil {
+ final.BlkioLeafWeight = *resource.BlockIO.LeafWeight
+ }
+ if resource.BlockIO.Weight != nil {
+ final.BlkioWeight = *resource.BlockIO.Weight
+ }
+ if len(resource.BlockIO.WeightDevice) > 0 {
+ for _, entry := range resource.BlockIO.WeightDevice {
+ weight := &runcconfig.WeightDevice{}
+ dev := &runcconfig.BlockIODevice{
+ Major: entry.Major,
+ Minor: entry.Minor,
+ }
+ if entry.Weight != nil {
+ weight.Weight = *entry.Weight
+ }
+ if entry.LeafWeight != nil {
+ weight.LeafWeight = *entry.LeafWeight
+ }
+ weight.BlockIODevice = *dev
+ final.BlkioWeightDevice = append(final.BlkioWeightDevice, weight)
+ }
+ }
+ }
+
+ // Pids
+ if resource.Pids != nil {
+ final.PidsLimit = resource.Pids.Limit
+ }
+
+ // Networking
+ if resource.Network != nil {
+ if resource.Network.ClassID != nil {
+ final.NetClsClassid = *resource.Network.ClassID
+ }
+ }
+
+ // Unified state
+ final.Unified = resource.Unified
+
+ return *final, nil
+}
diff --git a/libpod/plugin/volume_api.go b/libpod/plugin/volume_api.go
index f997ccf22..2de7db32c 100644
--- a/libpod/plugin/volume_api.go
+++ b/libpod/plugin/volume_api.go
@@ -197,13 +197,13 @@ func (p *VolumePlugin) verifyReachable() error {
// Send a request to the volume plugin for handling.
// Callers *MUST* close the response when they are done.
-func (p *VolumePlugin) sendRequest(toJSON interface{}, hasBody bool, endpoint string) (*http.Response, error) {
+func (p *VolumePlugin) sendRequest(toJSON interface{}, endpoint string) (*http.Response, error) {
var (
reqJSON []byte
err error
)
- if hasBody {
+ if toJSON != nil {
reqJSON, err = json.Marshal(toJSON)
if err != nil {
return nil, errors.Wrapf(err, "error marshalling request JSON for volume plugin %s endpoint %s", p.Name, endpoint)
@@ -274,7 +274,7 @@ func (p *VolumePlugin) CreateVolume(req *volume.CreateRequest) error {
logrus.Infof("Creating volume %s using plugin %s", req.Name, p.Name)
- resp, err := p.sendRequest(req, true, createPath)
+ resp, err := p.sendRequest(req, createPath)
if err != nil {
return err
}
@@ -291,7 +291,7 @@ func (p *VolumePlugin) ListVolumes() ([]*volume.Volume, error) {
logrus.Infof("Listing volumes using plugin %s", p.Name)
- resp, err := p.sendRequest(nil, false, listPath)
+ resp, err := p.sendRequest(nil, listPath)
if err != nil {
return nil, err
}
@@ -326,7 +326,7 @@ func (p *VolumePlugin) GetVolume(req *volume.GetRequest) (*volume.Volume, error)
logrus.Infof("Getting volume %s using plugin %s", req.Name, p.Name)
- resp, err := p.sendRequest(req, true, getPath)
+ resp, err := p.sendRequest(req, getPath)
if err != nil {
return nil, err
}
@@ -361,7 +361,7 @@ func (p *VolumePlugin) RemoveVolume(req *volume.RemoveRequest) error {
logrus.Infof("Removing volume %s using plugin %s", req.Name, p.Name)
- resp, err := p.sendRequest(req, true, removePath)
+ resp, err := p.sendRequest(req, removePath)
if err != nil {
return err
}
@@ -382,7 +382,7 @@ func (p *VolumePlugin) GetVolumePath(req *volume.PathRequest) (string, error) {
logrus.Infof("Getting volume %s path using plugin %s", req.Name, p.Name)
- resp, err := p.sendRequest(req, true, hostVirtualPath)
+ resp, err := p.sendRequest(req, hostVirtualPath)
if err != nil {
return "", err
}
@@ -419,7 +419,7 @@ func (p *VolumePlugin) MountVolume(req *volume.MountRequest) (string, error) {
logrus.Infof("Mounting volume %s using plugin %s for container %s", req.Name, p.Name, req.ID)
- resp, err := p.sendRequest(req, true, mountPath)
+ resp, err := p.sendRequest(req, mountPath)
if err != nil {
return "", err
}
@@ -455,7 +455,7 @@ func (p *VolumePlugin) UnmountVolume(req *volume.UnmountRequest) error {
logrus.Infof("Unmounting volume %s using plugin %s for container %s", req.Name, p.Name, req.ID)
- resp, err := p.sendRequest(req, true, unmountPath)
+ resp, err := p.sendRequest(req, unmountPath)
if err != nil {
return err
}
diff --git a/libpod/pod_internal.go b/libpod/pod_internal.go
index 41f745e6c..1502bcb06 100644
--- a/libpod/pod_internal.go
+++ b/libpod/pod_internal.go
@@ -69,7 +69,7 @@ func (p *Pod) refresh() error {
if p.config.UsePodCgroup {
switch p.runtime.config.Engine.CgroupManager {
case config.SystemdCgroupsManager:
- cgroupPath, err := systemdSliceFromPath(p.config.CgroupParent, fmt.Sprintf("libpod_pod_%s", p.ID()))
+ cgroupPath, err := systemdSliceFromPath(p.config.CgroupParent, fmt.Sprintf("libpod_pod_%s", p.ID()), p.ResourceLim())
if err != nil {
logrus.Errorf("Creating Cgroup for pod %s: %v", p.ID(), err)
}
diff --git a/libpod/runtime_ctr.go b/libpod/runtime_ctr.go
index a9ae9d1db..4d34c6a08 100644
--- a/libpod/runtime_ctr.go
+++ b/libpod/runtime_ctr.go
@@ -502,7 +502,7 @@ func (r *Runtime) setupContainer(ctx context.Context, ctr *Container) (_ *Contai
volOptions = append(volOptions, parsedOptions...)
}
}
- newVol, err := r.newVolume(volOptions...)
+ newVol, err := r.newVolume(false, volOptions...)
if err != nil {
return nil, errors.Wrapf(err, "error creating named volume %q", vol.Name)
}
@@ -664,9 +664,6 @@ func (r *Runtime) removeContainer(ctx context.Context, c *Container, force, remo
}
if c.state.State == define.ContainerStatePaused {
- if err := c.ociRuntime.KillContainer(c, 9, false); err != nil {
- return err
- }
isV2, err := cgroups.IsCgroup2UnifiedMode()
if err != nil {
return err
@@ -677,6 +674,9 @@ func (r *Runtime) removeContainer(ctx context.Context, c *Container, force, remo
return err
}
}
+ if err := c.ociRuntime.KillContainer(c, 9, false); err != nil {
+ return err
+ }
// Need to update container state to make sure we know it's stopped
if err := c.waitForExitFileAndSync(); err != nil {
return err
@@ -715,6 +715,10 @@ func (r *Runtime) removeContainer(ctx context.Context, c *Container, force, remo
// Do a quick ping of the database to check if the container
// still exists.
if ok, _ := r.state.HasContainer(c.ID()); !ok {
+ // When the container has already been removed, the OCI runtime directory remain.
+ if err := c.cleanupRuntime(ctx); err != nil {
+ return errors.Wrapf(err, "error cleaning up container %s from OCI runtime", c.ID())
+ }
return nil
}
}
@@ -805,7 +809,7 @@ func (r *Runtime) removeContainer(ctx context.Context, c *Container, force, remo
if !volume.Anonymous() {
continue
}
- if err := runtime.removeVolume(ctx, volume, false, timeout); err != nil && errors.Cause(err) != define.ErrNoSuchVolume {
+ if err := runtime.removeVolume(ctx, volume, false, timeout, false); err != nil && errors.Cause(err) != define.ErrNoSuchVolume {
if errors.Cause(err) == define.ErrVolumeBeingUsed {
// Ignore error, since podman will report original error
volumesFrom, _ := c.volumesFrom()
@@ -963,7 +967,7 @@ func (r *Runtime) evictContainer(ctx context.Context, idOrName string, removeVol
if !volume.Anonymous() {
continue
}
- if err := r.removeVolume(ctx, volume, false, timeout); err != nil && err != define.ErrNoSuchVolume && err != define.ErrVolumeBeingUsed {
+ if err := r.removeVolume(ctx, volume, false, timeout, false); err != nil && err != define.ErrNoSuchVolume && err != define.ErrVolumeBeingUsed {
logrus.Errorf("Cleaning up volume (%s): %v", v, err)
}
}
diff --git a/libpod/runtime_pod_linux.go b/libpod/runtime_pod_linux.go
index dcc3a044f..1f9ebe724 100644
--- a/libpod/runtime_pod_linux.go
+++ b/libpod/runtime_pod_linux.go
@@ -17,7 +17,7 @@ import (
"github.com/containers/podman/v4/libpod/events"
"github.com/containers/podman/v4/pkg/rootless"
"github.com/containers/podman/v4/pkg/specgen"
- spec "github.com/opencontainers/runtime-spec/specs-go"
+ runcconfig "github.com/opencontainers/runc/libcontainer/configs"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
)
@@ -66,6 +66,7 @@ func (r *Runtime) NewPod(ctx context.Context, p specgen.PodSpecGenerator, option
case config.CgroupfsCgroupsManager:
canUseCgroup := !rootless.IsRootless() || isRootlessCgroupSet(pod.config.CgroupParent)
if canUseCgroup {
+ // need to actually create parent here
if pod.config.CgroupParent == "" {
pod.config.CgroupParent = CgroupfsDefaultCgroupParent
} else if strings.HasSuffix(path.Base(pod.config.CgroupParent), ".slice") {
@@ -73,12 +74,29 @@ func (r *Runtime) NewPod(ctx context.Context, p specgen.PodSpecGenerator, option
}
// If we are set to use pod cgroups, set the cgroup parent that
// all containers in the pod will share
- // No need to create it with cgroupfs - the first container to
- // launch should do it for us
if pod.config.UsePodCgroup {
pod.state.CgroupPath = filepath.Join(pod.config.CgroupParent, pod.ID())
if p.InfraContainerSpec != nil {
p.InfraContainerSpec.CgroupParent = pod.state.CgroupPath
+ // cgroupfs + rootless = permission denied when creating the cgroup.
+ if !rootless.IsRootless() {
+ res, err := GetLimits(p.InfraContainerSpec.ResourceLimits)
+ if err != nil {
+ return nil, err
+ }
+ // Need to both create and update the cgroup
+ // rather than create a new path in c/common for pod cgroup creation
+ // just create as if it is a ctr and then update figures out that we need to
+ // populate the resource limits on the pod level
+ cgc, err := cgroups.New(pod.state.CgroupPath, &res)
+ if err != nil {
+ return nil, err
+ }
+ err = cgc.Update(&res)
+ if err != nil {
+ return nil, err
+ }
+ }
}
}
}
@@ -95,7 +113,7 @@ func (r *Runtime) NewPod(ctx context.Context, p specgen.PodSpecGenerator, option
// If we are set to use pod cgroups, set the cgroup parent that
// all containers in the pod will share
if pod.config.UsePodCgroup {
- cgroupPath, err := systemdSliceFromPath(pod.config.CgroupParent, fmt.Sprintf("libpod_pod_%s", pod.ID()))
+ cgroupPath, err := systemdSliceFromPath(pod.config.CgroupParent, fmt.Sprintf("libpod_pod_%s", pod.ID()), p.InfraContainerSpec.ResourceLimits)
if err != nil {
return nil, errors.Wrapf(err, "unable to create pod cgroup for pod %s", pod.ID())
}
@@ -239,9 +257,8 @@ func (r *Runtime) removePod(ctx context.Context, p *Pod, removeCtrs, force bool,
}
// New resource limits
- resLimits := new(spec.LinuxResources)
- resLimits.Pids = new(spec.LinuxPids)
- resLimits.Pids.Limit = 1 // Inhibit forks with very low pids limit
+ resLimits := new(runcconfig.Resources)
+ resLimits.PidsLimit = 1 // Inhibit forks with very low pids limit
// Don't try if we failed to retrieve the cgroup
if err == nil {
@@ -301,7 +318,7 @@ func (r *Runtime) removePod(ctx context.Context, p *Pod, removeCtrs, force bool,
if !volume.Anonymous() {
continue
}
- if err := r.removeVolume(ctx, volume, false, timeout); err != nil {
+ if err := r.removeVolume(ctx, volume, false, timeout, false); err != nil {
if errors.Cause(err) == define.ErrNoSuchVolume || errors.Cause(err) == define.ErrVolumeRemoved {
continue
}
@@ -321,7 +338,7 @@ func (r *Runtime) removePod(ctx context.Context, p *Pod, removeCtrs, force bool,
switch p.runtime.config.Engine.CgroupManager {
case config.SystemdCgroupsManager:
- if err := deleteSystemdCgroup(p.state.CgroupPath); err != nil {
+ if err := deleteSystemdCgroup(p.state.CgroupPath, p.ResourceLim()); err != nil {
if removalErr == nil {
removalErr = errors.Wrapf(err, "error removing pod %s cgroup", p.ID())
} else {
diff --git a/libpod/runtime_volume.go b/libpod/runtime_volume.go
index 21bf8aefc..6872db21d 100644
--- a/libpod/runtime_volume.go
+++ b/libpod/runtime_volume.go
@@ -33,7 +33,7 @@ func (r *Runtime) RemoveVolume(ctx context.Context, v *Volume, force bool, timeo
return nil
}
}
- return r.removeVolume(ctx, v, force, timeout)
+ return r.removeVolume(ctx, v, force, timeout, false)
}
// GetVolume retrieves a volume given its full name.
diff --git a/libpod/runtime_volume_linux.go b/libpod/runtime_volume_linux.go
index f8788e183..17a48be86 100644
--- a/libpod/runtime_volume_linux.go
+++ b/libpod/runtime_volume_linux.go
@@ -5,6 +5,7 @@ package libpod
import (
"context"
+ "fmt"
"os"
"path/filepath"
"strings"
@@ -25,11 +26,13 @@ func (r *Runtime) NewVolume(ctx context.Context, options ...VolumeCreateOption)
if !r.valid {
return nil, define.ErrRuntimeStopped
}
- return r.newVolume(options...)
+ return r.newVolume(false, options...)
}
-// newVolume creates a new empty volume
-func (r *Runtime) newVolume(options ...VolumeCreateOption) (_ *Volume, deferredErr error) {
+// newVolume creates a new empty volume with the given options.
+// The createPluginVolume can be set to true to make it not create the volume in the volume plugin,
+// this is required for the UpdateVolumePlugins() function. If you are not sure set this to false.
+func (r *Runtime) newVolume(noCreatePluginVolume bool, options ...VolumeCreateOption) (_ *Volume, deferredErr error) {
volume := newVolume(r)
for _, option := range options {
if err := option(volume); err != nil {
@@ -73,7 +76,7 @@ func (r *Runtime) newVolume(options ...VolumeCreateOption) (_ *Volume, deferredE
return nil, errors.Wrapf(err, "invalid volume option %s for driver 'local'", key)
}
}
- case "o", "type", "uid", "gid", "size", "inodes", "noquota":
+ case "o", "type", "uid", "gid", "size", "inodes", "noquota", "copy", "nocopy":
// Do nothing, valid keys
default:
return nil, errors.Wrapf(define.ErrInvalidArg, "invalid mount option %s for driver 'local'", key)
@@ -83,7 +86,7 @@ func (r *Runtime) newVolume(options ...VolumeCreateOption) (_ *Volume, deferredE
// Now we get conditional: we either need to make the volume in the
// volume plugin, or on disk if not using a plugin.
- if volume.plugin != nil {
+ if volume.plugin != nil && !noCreatePluginVolume {
// We can't chown, or relabel, or similar the path the volume is
// using, because it's not managed by us.
// TODO: reevaluate this once we actually have volume plugins in
@@ -164,6 +167,85 @@ func (r *Runtime) newVolume(options ...VolumeCreateOption) (_ *Volume, deferredE
return volume, nil
}
+// UpdateVolumePlugins reads all volumes from all configured volume plugins and
+// imports them into the libpod db. It also checks if existing libpod volumes
+// are removed in the plugin, in this case we try to remove it from libpod.
+// On errors we continue and try to do as much as possible. all errors are
+// returned as array in the returned struct.
+// This function has many race conditions, it is best effort but cannot guarantee
+// a perfect state since plugins can be modified from the outside at any time.
+func (r *Runtime) UpdateVolumePlugins(ctx context.Context) *define.VolumeReload {
+ var (
+ added []string
+ removed []string
+ errs []error
+ allPluginVolumes = map[string]struct{}{}
+ )
+
+ for driverName, socket := range r.config.Engine.VolumePlugins {
+ driver, err := volplugin.GetVolumePlugin(driverName, socket)
+ if err != nil {
+ errs = append(errs, err)
+ continue
+ }
+ vols, err := driver.ListVolumes()
+ if err != nil {
+ errs = append(errs, fmt.Errorf("failed to read volumes from plugin %q: %w", driverName, err))
+ continue
+ }
+ for _, vol := range vols {
+ allPluginVolumes[vol.Name] = struct{}{}
+ if _, err := r.newVolume(true, WithVolumeName(vol.Name), WithVolumeDriver(driverName)); err != nil {
+ // If the volume exists this is not an error, just ignore it and log. It is very likely
+ // that the volume from the plugin was already in our db.
+ if !errors.Is(err, define.ErrVolumeExists) {
+ errs = append(errs, err)
+ continue
+ }
+ logrus.Infof("Volume %q already exists: %v", vol.Name, err)
+ continue
+ }
+ added = append(added, vol.Name)
+ }
+ }
+
+ libpodVolumes, err := r.state.AllVolumes()
+ if err != nil {
+ errs = append(errs, fmt.Errorf("cannot delete dangling plugin volumes: failed to read libpod volumes: %w", err))
+ }
+ for _, vol := range libpodVolumes {
+ if vol.UsesVolumeDriver() {
+ if _, ok := allPluginVolumes[vol.Name()]; !ok {
+ // The volume is no longer in the plugin, lets remove it from the libpod db.
+ if err := r.removeVolume(ctx, vol, false, nil, true); err != nil {
+ if errors.Is(err, define.ErrVolumeBeingUsed) {
+ // Volume is still used by at least one container. This is very bad,
+ // the plugin no longer has this but we still need it.
+ errs = append(errs, fmt.Errorf("volume was removed from the plugin %q but containers still require it: %w", vol.config.Driver, err))
+ continue
+ }
+ if errors.Is(err, define.ErrNoSuchVolume) || errors.Is(err, define.ErrVolumeRemoved) || errors.Is(err, define.ErrMissingPlugin) {
+ // Volume was already removed, no problem just ignore it and continue.
+ continue
+ }
+
+ // some other error
+ errs = append(errs, err)
+ continue
+ }
+ // Volume was successfully removed
+ removed = append(removed, vol.Name())
+ }
+ }
+ }
+
+ return &define.VolumeReload{
+ Added: added,
+ Removed: removed,
+ Errors: errs,
+ }
+}
+
// makeVolumeInPluginIfNotExist makes a volume in the given volume plugin if it
// does not already exist.
func makeVolumeInPluginIfNotExist(name string, options map[string]string, plugin *volplugin.VolumePlugin) error {
@@ -197,8 +279,10 @@ func makeVolumeInPluginIfNotExist(name string, options map[string]string, plugin
return nil
}
-// removeVolume removes the specified volume from state as well tears down its mountpoint and storage
-func (r *Runtime) removeVolume(ctx context.Context, v *Volume, force bool, timeout *uint) error {
+// removeVolume removes the specified volume from state as well tears down its mountpoint and storage.
+// ignoreVolumePlugin is used to only remove the volume from the db and not the plugin,
+// this is required when the volume was already removed from the plugin, i.e. in UpdateVolumePlugins().
+func (r *Runtime) removeVolume(ctx context.Context, v *Volume, force bool, timeout *uint, ignoreVolumePlugin bool) error {
if !v.valid {
if ok, _ := r.state.HasVolume(v.Name()); !ok {
return nil
@@ -263,7 +347,7 @@ func (r *Runtime) removeVolume(ctx context.Context, v *Volume, force bool, timeo
var removalErr error
// If we use a volume plugin, we need to remove from the plugin.
- if v.UsesVolumeDriver() {
+ if v.UsesVolumeDriver() && !ignoreVolumePlugin {
canRemove := true
// Do we have a volume driver?
diff --git a/libpod/stats.go b/libpod/stats.go
index d2ffc3b32..eaac9d7d0 100644
--- a/libpod/stats.go
+++ b/libpod/stats.go
@@ -9,6 +9,8 @@ import (
"syscall"
"time"
+ runccgroup "github.com/opencontainers/runc/libcontainer/cgroups"
+
"github.com/containers/common/pkg/cgroups"
"github.com/containers/podman/v4/libpod/define"
"github.com/pkg/errors"
@@ -69,29 +71,29 @@ func (c *Container) GetContainerStats(previousStats *define.ContainerStats) (*de
// If the current total usage in the cgroup is less than what was previously
// recorded then it means the container was restarted and runs in a new cgroup
- if previousStats.Duration > cgroupStats.CPU.Usage.Total {
+ if previousStats.Duration > cgroupStats.CpuStats.CpuUsage.TotalUsage {
previousStats = &define.ContainerStats{}
}
previousCPU := previousStats.CPUNano
now := uint64(time.Now().UnixNano())
- stats.Duration = cgroupStats.CPU.Usage.Total
+ stats.Duration = cgroupStats.CpuStats.CpuUsage.TotalUsage
stats.UpTime = time.Duration(stats.Duration)
stats.CPU = calculateCPUPercent(cgroupStats, previousCPU, now, previousStats.SystemNano)
// calc the average cpu usage for the time the container is running
stats.AvgCPU = calculateCPUPercent(cgroupStats, 0, now, uint64(c.state.StartedTime.UnixNano()))
- stats.MemUsage = cgroupStats.Memory.Usage.Usage
+ stats.MemUsage = cgroupStats.MemoryStats.Usage.Usage
stats.MemLimit = c.getMemLimit()
stats.MemPerc = (float64(stats.MemUsage) / float64(stats.MemLimit)) * 100
stats.PIDs = 0
if conState == define.ContainerStateRunning || conState == define.ContainerStatePaused {
- stats.PIDs = cgroupStats.Pids.Current
+ stats.PIDs = cgroupStats.PidsStats.Current
}
stats.BlockInput, stats.BlockOutput = calculateBlockIO(cgroupStats)
- stats.CPUNano = cgroupStats.CPU.Usage.Total
- stats.CPUSystemNano = cgroupStats.CPU.Usage.Kernel
+ stats.CPUNano = cgroupStats.CpuStats.CpuUsage.TotalUsage
+ stats.CPUSystemNano = cgroupStats.CpuStats.CpuUsage.UsageInKernelmode
stats.SystemNano = now
- stats.PerCPU = cgroupStats.CPU.Usage.PerCPU
+ stats.PerCPU = cgroupStats.CpuStats.CpuUsage.PercpuUsage
// Handle case where the container is not in a network namespace
if netStats != nil {
stats.NetInput = netStats.TxBytes
@@ -133,10 +135,10 @@ func (c *Container) getMemLimit() uint64 {
// previousCPU is the last value of stats.CPU.Usage.Total measured at the time previousSystem.
// (now - previousSystem) is the time delta in nanoseconds, between the measurement in previousCPU
// and the updated value in stats.
-func calculateCPUPercent(stats *cgroups.Metrics, previousCPU, now, previousSystem uint64) float64 {
+func calculateCPUPercent(stats *runccgroup.Stats, previousCPU, now, previousSystem uint64) float64 {
var (
cpuPercent = 0.0
- cpuDelta = float64(stats.CPU.Usage.Total - previousCPU)
+ cpuDelta = float64(stats.CpuStats.CpuUsage.TotalUsage - previousCPU)
systemDelta = float64(now - previousSystem)
)
if systemDelta > 0.0 && cpuDelta > 0.0 {
@@ -146,8 +148,8 @@ func calculateCPUPercent(stats *cgroups.Metrics, previousCPU, now, previousSyste
return cpuPercent
}
-func calculateBlockIO(stats *cgroups.Metrics) (read uint64, write uint64) {
- for _, blkIOEntry := range stats.Blkio.IoServiceBytesRecursive {
+func calculateBlockIO(stats *runccgroup.Stats) (read uint64, write uint64) {
+ for _, blkIOEntry := range stats.BlkioStats.IoServiceBytesRecursive {
switch strings.ToLower(blkIOEntry.Op) {
case "read":
read += blkIOEntry.Value
diff --git a/libpod/util_linux.go b/libpod/util_linux.go
index fe98056dc..414d1bff9 100644
--- a/libpod/util_linux.go
+++ b/libpod/util_linux.go
@@ -11,6 +11,7 @@ import (
"github.com/containers/common/pkg/cgroups"
"github.com/containers/podman/v4/libpod/define"
"github.com/containers/podman/v4/pkg/rootless"
+ spec "github.com/opencontainers/runtime-spec/specs-go"
"github.com/opencontainers/selinux/go-selinux/label"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
@@ -20,7 +21,7 @@ import (
// systemdSliceFromPath makes a new systemd slice under the given parent with
// the given name.
// The parent must be a slice. The name must NOT include ".slice"
-func systemdSliceFromPath(parent, name string) (string, error) {
+func systemdSliceFromPath(parent, name string, resources *spec.LinuxResources) (string, error) {
cgroupPath, err := assembleSystemdCgroupName(parent, name)
if err != nil {
return "", err
@@ -28,7 +29,7 @@ func systemdSliceFromPath(parent, name string) (string, error) {
logrus.Debugf("Created cgroup path %s for parent %s and name %s", cgroupPath, parent, name)
- if err := makeSystemdCgroup(cgroupPath); err != nil {
+ if err := makeSystemdCgroup(cgroupPath, resources); err != nil {
return "", errors.Wrapf(err, "error creating cgroup %s", cgroupPath)
}
@@ -45,8 +46,12 @@ func getDefaultSystemdCgroup() string {
}
// makeSystemdCgroup creates a systemd Cgroup at the given location.
-func makeSystemdCgroup(path string) error {
- controller, err := cgroups.NewSystemd(getDefaultSystemdCgroup())
+func makeSystemdCgroup(path string, resources *spec.LinuxResources) error {
+ res, err := GetLimits(resources)
+ if err != nil {
+ return err
+ }
+ controller, err := cgroups.NewSystemd(getDefaultSystemdCgroup(), &res)
if err != nil {
return err
}
@@ -54,12 +59,20 @@ func makeSystemdCgroup(path string) error {
if rootless.IsRootless() {
return controller.CreateSystemdUserUnit(path, rootless.GetRootlessUID())
}
- return controller.CreateSystemdUnit(path)
+ err = controller.CreateSystemdUnit(path)
+ if err != nil {
+ return err
+ }
+ return nil
}
// deleteSystemdCgroup deletes the systemd cgroup at the given location
-func deleteSystemdCgroup(path string) error {
- controller, err := cgroups.NewSystemd(getDefaultSystemdCgroup())
+func deleteSystemdCgroup(path string, resources *spec.LinuxResources) error {
+ res, err := GetLimits(resources)
+ if err != nil {
+ return err
+ }
+ controller, err := cgroups.NewSystemd(getDefaultSystemdCgroup(), &res)
if err != nil {
return err
}
diff --git a/libpod/volume_internal.go b/libpod/volume_internal.go
index e0ebb729d..24522c0f9 100644
--- a/libpod/volume_internal.go
+++ b/libpod/volume_internal.go
@@ -55,6 +55,12 @@ func (v *Volume) needsMount() bool {
if _, ok := v.config.Options["NOQUOTA"]; ok {
index++
}
+ if _, ok := v.config.Options["nocopy"]; ok {
+ index++
+ }
+ if _, ok := v.config.Options["copy"]; ok {
+ index++
+ }
// when uid or gid is set there is also the "o" option
// set so we have to ignore this one as well
if index > 0 {
diff --git a/pkg/api/handlers/compat/containers.go b/pkg/api/handlers/compat/containers.go
index 616f0a138..411b0efe9 100644
--- a/pkg/api/handlers/compat/containers.go
+++ b/pkg/api/handlers/compat/containers.go
@@ -289,8 +289,10 @@ func LibpodToContainer(l *libpod.Container, sz bool) (*handlers.Container, error
return nil, err
}
stateStr := state.String()
- if stateStr == "configured" {
- stateStr = "created"
+
+ // Some docker states are not the same as ours. This makes sure the state string stays true to the Docker API
+ if state == define.ContainerStateCreated {
+ stateStr = define.ContainerStateConfigured.String()
}
switch state {
@@ -420,9 +422,9 @@ func LibpodToContainerJSON(l *libpod.Container, sz bool) (*types.ContainerJSON,
state.Running = true
}
- // docker calls the configured state "created"
- if state.Status == define.ContainerStateConfigured.String() {
- state.Status = define.ContainerStateCreated.String()
+ // Dockers created state is our configured state
+ if state.Status == define.ContainerStateCreated.String() {
+ state.Status = define.ContainerStateConfigured.String()
}
if l.HasHealthCheck() && state.Status != "created" {
diff --git a/pkg/api/handlers/compat/containers_stats.go b/pkg/api/handlers/compat/containers_stats.go
index d6bc26416..12c5283fc 100644
--- a/pkg/api/handlers/compat/containers_stats.go
+++ b/pkg/api/handlers/compat/containers_stats.go
@@ -12,6 +12,7 @@ import (
api "github.com/containers/podman/v4/pkg/api/types"
docker "github.com/docker/docker/api/types"
"github.com/gorilla/schema"
+ runccgroups "github.com/opencontainers/runc/libcontainer/cgroups"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
)
@@ -133,7 +134,7 @@ streamLabel: // A label to flatten the scope
}
cfg := ctnr.Config()
- memoryLimit := cgroupStat.Memory.Usage.Limit
+ memoryLimit := cgroupStat.MemoryStats.Usage.Limit
if cfg.Spec.Linux != nil && cfg.Spec.Linux.Resources != nil && cfg.Spec.Linux.Resources.Memory != nil && *cfg.Spec.Linux.Resources.Memory.Limit > 0 {
memoryLimit = uint64(*cfg.Spec.Linux.Resources.Memory.Limit)
}
@@ -144,11 +145,11 @@ streamLabel: // A label to flatten the scope
Read: time.Now(),
PreRead: preRead,
PidsStats: docker.PidsStats{
- Current: cgroupStat.Pids.Current,
+ Current: cgroupStat.PidsStats.Current,
Limit: 0,
},
BlkioStats: docker.BlkioStats{
- IoServiceBytesRecursive: toBlkioStatEntry(cgroupStat.Blkio.IoServiceBytesRecursive),
+ IoServiceBytesRecursive: toBlkioStatEntry(cgroupStat.BlkioStats.IoServiceBytesRecursive),
IoServicedRecursive: nil,
IoQueuedRecursive: nil,
IoServiceTimeRecursive: nil,
@@ -159,14 +160,14 @@ streamLabel: // A label to flatten the scope
},
CPUStats: CPUStats{
CPUUsage: docker.CPUUsage{
- TotalUsage: cgroupStat.CPU.Usage.Total,
- PercpuUsage: cgroupStat.CPU.Usage.PerCPU,
- UsageInKernelmode: cgroupStat.CPU.Usage.Kernel,
- UsageInUsermode: cgroupStat.CPU.Usage.Total - cgroupStat.CPU.Usage.Kernel,
+ TotalUsage: cgroupStat.CpuStats.CpuUsage.TotalUsage,
+ PercpuUsage: cgroupStat.CpuStats.CpuUsage.PercpuUsage,
+ UsageInKernelmode: cgroupStat.CpuStats.CpuUsage.UsageInKernelmode,
+ UsageInUsermode: cgroupStat.CpuStats.CpuUsage.TotalUsage - cgroupStat.CpuStats.CpuUsage.UsageInKernelmode,
},
CPU: stats.CPU,
SystemUsage: systemUsage,
- OnlineCPUs: uint32(len(cgroupStat.CPU.Usage.PerCPU)),
+ OnlineCPUs: uint32(len(cgroupStat.CpuStats.CpuUsage.PercpuUsage)),
ThrottlingData: docker.ThrottlingData{
Periods: 0,
ThrottledPeriods: 0,
@@ -175,8 +176,8 @@ streamLabel: // A label to flatten the scope
},
PreCPUStats: preCPUStats,
MemoryStats: docker.MemoryStats{
- Usage: cgroupStat.Memory.Usage.Usage,
- MaxUsage: cgroupStat.Memory.Usage.Limit,
+ Usage: cgroupStat.MemoryStats.Usage.Usage,
+ MaxUsage: cgroupStat.MemoryStats.Usage.Limit,
Stats: nil,
Failcnt: 0,
Limit: memoryLimit,
@@ -216,7 +217,7 @@ streamLabel: // A label to flatten the scope
}
}
-func toBlkioStatEntry(entries []cgroups.BlkIOEntry) []docker.BlkioStatEntry {
+func toBlkioStatEntry(entries []runccgroups.BlkioStatEntry) []docker.BlkioStatEntry {
results := make([]docker.BlkioStatEntry, len(entries))
for i, e := range entries {
bits, err := json.Marshal(e)
diff --git a/pkg/api/handlers/libpod/images.go b/pkg/api/handlers/libpod/images.go
index a8a50ae58..b71217efa 100644
--- a/pkg/api/handlers/libpod/images.go
+++ b/pkg/api/handlers/libpod/images.go
@@ -21,7 +21,9 @@ import (
api "github.com/containers/podman/v4/pkg/api/types"
"github.com/containers/podman/v4/pkg/auth"
"github.com/containers/podman/v4/pkg/domain/entities"
+ "github.com/containers/podman/v4/pkg/domain/entities/reports"
"github.com/containers/podman/v4/pkg/domain/infra/abi"
+ domainUtils "github.com/containers/podman/v4/pkg/domain/utils"
"github.com/containers/podman/v4/pkg/errorhandling"
"github.com/containers/podman/v4/pkg/util"
utils2 "github.com/containers/podman/v4/utils"
@@ -613,10 +615,11 @@ func ImagesBatchRemove(w http.ResponseWriter, r *http.Request) {
runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime)
decoder := r.Context().Value(api.DecoderKey).(*schema.Decoder)
query := struct {
- All bool `schema:"all"`
- Force bool `schema:"force"`
- Ignore bool `schema:"ignore"`
- Images []string `schema:"images"`
+ All bool `schema:"all"`
+ Force bool `schema:"force"`
+ Ignore bool `schema:"ignore"`
+ LookupManifest bool `schema:"lookupManifest"`
+ Images []string `schema:"images"`
}{}
if err := decoder.Decode(&query, r.URL.Query()); err != nil {
@@ -624,7 +627,7 @@ func ImagesBatchRemove(w http.ResponseWriter, r *http.Request) {
return
}
- opts := entities.ImageRemoveOptions{All: query.All, Force: query.Force, Ignore: query.Ignore}
+ opts := entities.ImageRemoveOptions{All: query.All, Force: query.Force, Ignore: query.Ignore, LookupManifest: query.LookupManifest}
imageEngine := abi.ImageEngine{Libpod: runtime}
rmReport, rmErrors := imageEngine.Remove(r.Context(), query.Images, opts)
strErrs := errorhandling.ErrorsToStrings(rmErrors)
@@ -637,7 +640,8 @@ func ImagesRemove(w http.ResponseWriter, r *http.Request) {
runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime)
decoder := r.Context().Value(api.DecoderKey).(*schema.Decoder)
query := struct {
- Force bool `schema:"force"`
+ Force bool `schema:"force"`
+ LookupManifest bool `schema:"lookupManifest"`
}{
Force: false,
}
@@ -647,7 +651,7 @@ func ImagesRemove(w http.ResponseWriter, r *http.Request) {
return
}
- opts := entities.ImageRemoveOptions{Force: query.Force}
+ opts := entities.ImageRemoveOptions{Force: query.Force, LookupManifest: query.LookupManifest}
imageEngine := abi.ImageEngine{Libpod: runtime}
rmReport, rmErrors := imageEngine.Remove(r.Context(), []string{utils.GetName(r)}, opts)
@@ -670,3 +674,32 @@ func ImagesRemove(w http.ResponseWriter, r *http.Request) {
utils.Error(w, http.StatusInternalServerError, errorhandling.JoinErrors(rmErrors))
}
}
+
+func ImageScp(w http.ResponseWriter, r *http.Request) {
+ decoder := r.Context().Value(api.DecoderKey).(*schema.Decoder)
+ query := struct {
+ Destination string `schema:"destination"`
+ Quiet bool `schema:"quiet"`
+ }{
+ // This is where you can override the golang default value for one of fields
+ }
+ if err := decoder.Decode(&query, r.URL.Query()); err != nil {
+ utils.Error(w, http.StatusBadRequest, errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String()))
+ return
+ }
+
+ sourceArg := utils.GetName(r)
+
+ rep, source, dest, _, err := domainUtils.ExecuteTransfer(sourceArg, query.Destination, []string{}, query.Quiet)
+ if err != nil {
+ utils.Error(w, http.StatusInternalServerError, err)
+ return
+ }
+
+ if source != nil || dest != nil {
+ utils.Error(w, http.StatusBadRequest, errors.Wrapf(define.ErrInvalidArg, "cannot use the user transfer function on the remote client"))
+ return
+ }
+
+ utils.WriteResponse(w, http.StatusOK, &reports.ScpReport{Id: rep.Names[0]})
+}
diff --git a/pkg/api/handlers/swagger/responses.go b/pkg/api/handlers/swagger/responses.go
index 55fc1a77f..93a508b39 100644
--- a/pkg/api/handlers/swagger/responses.go
+++ b/pkg/api/handlers/swagger/responses.go
@@ -41,6 +41,13 @@ type imagesLoadResponseLibpod struct {
Body entities.ImageLoadReport
}
+// Image Scp
+// swagger:response
+type imagesScpResponseLibpod struct {
+ // in:body
+ Body reports.ScpReport
+}
+
// Image Import
// swagger:response
type imagesImportResponseLibpod struct {
diff --git a/pkg/api/server/register_images.go b/pkg/api/server/register_images.go
index 1617a5dd7..a2f46cb35 100644
--- a/pkg/api/server/register_images.go
+++ b/pkg/api/server/register_images.go
@@ -948,6 +948,10 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error {
// name: ignore
// description: Ignore if a specified image does not exist and do not throw an error.
// type: boolean
+ // - in: query
+ // name: lookupManifest
+ // description: Resolves to manifest list instead of image.
+ // type: boolean
// produces:
// - application/json
// responses:
@@ -1615,5 +1619,39 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error {
// 500:
// $ref: "#/responses/internalError"
r.Handle(VersionedPath("/libpod/build"), s.APIHandler(compat.BuildImage)).Methods(http.MethodPost)
+
+ // swagger:operation POST /libpod/images/scp/{name} libpod ImageScpLibpod
+ // ---
+ // tags:
+ // - images
+ // summary: Copy an image from one host to another
+ // description: Copy an image from one host to another
+ // parameters:
+ // - in: path
+ // name: name
+ // required: true
+ // description: source connection/image
+ // type: string
+ // - in: query
+ // name: destination
+ // required: false
+ // description: dest connection/image
+ // type: string
+ // - in: query
+ // name: quiet
+ // required: false
+ // description: quiet output
+ // type: boolean
+ // default: false
+ // produces:
+ // - application/json
+ // responses:
+ // 200:
+ // $ref: "#/responses/imagesScpResponseLibpod"
+ // 400:
+ // $ref: "#/responses/badParamError"
+ // 500:
+ // $ref: '#/responses/internalError'
+ r.Handle(VersionedPath("/libpod/images/scp/{name:.*}"), s.APIHandler(libpod.ImageScp)).Methods(http.MethodPost)
return nil
}
diff --git a/pkg/bindings/images/images.go b/pkg/bindings/images/images.go
index 32372019b..57c8bd597 100644
--- a/pkg/bindings/images/images.go
+++ b/pkg/bindings/images/images.go
@@ -346,3 +346,23 @@ func Search(ctx context.Context, term string, options *SearchOptions) ([]entitie
return results, nil
}
+
+func Scp(ctx context.Context, source, destination *string, options ScpOptions) (reports.ScpReport, error) {
+ rep := reports.ScpReport{}
+
+ conn, err := bindings.GetClient(ctx)
+ if err != nil {
+ return rep, err
+ }
+ params, err := options.ToParams()
+ if err != nil {
+ return rep, err
+ }
+ response, err := conn.DoRequest(ctx, nil, http.MethodPost, fmt.Sprintf("/images/scp/%s", *source), params, nil)
+ if err != nil {
+ return rep, err
+ }
+ defer response.Body.Close()
+
+ return rep, response.Process(&rep)
+}
diff --git a/pkg/bindings/images/types.go b/pkg/bindings/images/types.go
index 16dbad380..3728ae5c0 100644
--- a/pkg/bindings/images/types.go
+++ b/pkg/bindings/images/types.go
@@ -13,6 +13,8 @@ type RemoveOptions struct {
Force *bool
// Ignore if a specified image does not exist and do not throw an error.
Ignore *bool
+ // Confirms if given name is a manifest list and removes it, otherwise returns error.
+ LookupManifest *bool
}
//go:generate go run ../generator/generator.go DiffOptions
@@ -188,3 +190,8 @@ type BuildOptions struct {
// ExistsOptions are optional options for checking if an image exists
type ExistsOptions struct {
}
+
+type ScpOptions struct {
+ Quiet *bool
+ Destination *string
+}
diff --git a/pkg/bindings/images/types_remove_options.go b/pkg/bindings/images/types_remove_options.go
index 613a33183..559ebcfd5 100644
--- a/pkg/bindings/images/types_remove_options.go
+++ b/pkg/bindings/images/types_remove_options.go
@@ -61,3 +61,18 @@ func (o *RemoveOptions) GetIgnore() bool {
}
return *o.Ignore
}
+
+// WithLookupManifest set field LookupManifest to given value
+func (o *RemoveOptions) WithLookupManifest(value bool) *RemoveOptions {
+ o.LookupManifest = &value
+ return o
+}
+
+// GetLookupManifest returns value of field LookupManifest
+func (o *RemoveOptions) GetLookupManifest() bool {
+ if o.LookupManifest == nil {
+ var z bool
+ return z
+ }
+ return *o.LookupManifest
+}
diff --git a/pkg/bindings/images/types_scp_options.go b/pkg/bindings/images/types_scp_options.go
new file mode 100644
index 000000000..5a1178cb1
--- /dev/null
+++ b/pkg/bindings/images/types_scp_options.go
@@ -0,0 +1,12 @@
+package images
+
+import (
+ "net/url"
+
+ "github.com/containers/podman/v4/pkg/bindings/internal/util"
+)
+
+// ToParams formats struct fields to be passed to API service
+func (o *ScpOptions) ToParams() (url.Values, error) {
+ return util.ToParams(o)
+}
diff --git a/pkg/bindings/manifests/manifests.go b/pkg/bindings/manifests/manifests.go
index feff5d6e8..aaa26d7e1 100644
--- a/pkg/bindings/manifests/manifests.go
+++ b/pkg/bindings/manifests/manifests.go
@@ -117,6 +117,26 @@ func Remove(ctx context.Context, name, digest string, _ *RemoveOptions) (string,
return Modify(ctx, name, []string{digest}, optionsv4)
}
+// Delete removes specified manifest from local storage.
+func Delete(ctx context.Context, name string) (*entities.ManifestRemoveReport, error) {
+ var report entities.ManifestRemoveReport
+ conn, err := bindings.GetClient(ctx)
+ if err != nil {
+ return nil, err
+ }
+ response, err := conn.DoRequest(ctx, nil, http.MethodDelete, "/manifests/%s", nil, nil, name)
+ if err != nil {
+ return nil, err
+ }
+ defer response.Body.Close()
+
+ if err := response.Process(&report); err != nil {
+ return nil, err
+ }
+
+ return &report, errorhandling.JoinErrors(errorhandling.StringsToErrors(report.Errors))
+}
+
// Push takes a manifest list and pushes to a destination. If the destination is not specified,
// the name will be used instead. If the optional all boolean is specified, all images specified
// in the list will be pushed as well.
diff --git a/pkg/bindings/test/manifests_test.go b/pkg/bindings/test/manifests_test.go
index e6c93817d..6a34ef5a6 100644
--- a/pkg/bindings/test/manifests_test.go
+++ b/pkg/bindings/test/manifests_test.go
@@ -62,6 +62,19 @@ var _ = Describe("podman manifest", func() {
Expect(len(list.Manifests)).To(BeNumerically("==", 1))
})
+ It("delete manifest", func() {
+ id, err := manifests.Create(bt.conn, "quay.io/libpod/foobar:latest", []string{}, nil)
+ Expect(err).ToNot(HaveOccurred(), err)
+ list, err := manifests.Inspect(bt.conn, id, nil)
+ Expect(err).ToNot(HaveOccurred())
+
+ Expect(len(list.Manifests)).To(BeZero())
+
+ removeReport, err := manifests.Delete(bt.conn, "quay.io/libpod/foobar:latest")
+ Expect(err).ToNot(HaveOccurred())
+ Expect(len(removeReport.Deleted)).To(BeNumerically("==", 1))
+ })
+
It("inspect", func() {
_, err := manifests.Inspect(bt.conn, "larry", nil)
Expect(err).To(HaveOccurred())
diff --git a/pkg/criu/criu.go b/pkg/criu/criu.go
index 6570159d7..0b0bbff5d 100644
--- a/pkg/criu/criu.go
+++ b/pkg/criu/criu.go
@@ -1,51 +1,8 @@
-//go:build linux
-// +build linux
-
package criu
-import (
- "github.com/checkpoint-restore/go-criu/v5"
- "github.com/checkpoint-restore/go-criu/v5/rpc"
-
- "google.golang.org/protobuf/proto"
-)
-
// MinCriuVersion for Podman at least CRIU 3.11 is required
const MinCriuVersion = 31100
// PodCriuVersion is the version of CRIU needed for
// checkpointing and restoring containers out of and into Pods.
const PodCriuVersion = 31600
-
-// CheckForCriu uses CRIU's go bindings to check if the CRIU
-// binary exists and if it at least the version Podman needs.
-func CheckForCriu(version int) bool {
- c := criu.MakeCriu()
- result, err := c.IsCriuAtLeast(version)
- if err != nil {
- return false
- }
- return result
-}
-
-func GetCriuVestion() (int, error) {
- c := criu.MakeCriu()
- return c.GetCriuVersion()
-}
-
-func MemTrack() bool {
- features, err := criu.MakeCriu().FeatureCheck(
- &rpc.CriuFeatures{
- MemTrack: proto.Bool(true),
- },
- )
- if err != nil {
- return false
- }
-
- if features == nil || features.MemTrack == nil {
- return false
- }
-
- return *features.MemTrack
-}
diff --git a/pkg/criu/criu_linux.go b/pkg/criu/criu_linux.go
new file mode 100644
index 000000000..c28e23fd7
--- /dev/null
+++ b/pkg/criu/criu_linux.go
@@ -0,0 +1,44 @@
+//go:build linux
+// +build linux
+
+package criu
+
+import (
+ "github.com/checkpoint-restore/go-criu/v5"
+ "github.com/checkpoint-restore/go-criu/v5/rpc"
+
+ "google.golang.org/protobuf/proto"
+)
+
+// CheckForCriu uses CRIU's go bindings to check if the CRIU
+// binary exists and if it at least the version Podman needs.
+func CheckForCriu(version int) bool {
+ c := criu.MakeCriu()
+ result, err := c.IsCriuAtLeast(version)
+ if err != nil {
+ return false
+ }
+ return result
+}
+
+func MemTrack() bool {
+ features, err := criu.MakeCriu().FeatureCheck(
+ &rpc.CriuFeatures{
+ MemTrack: proto.Bool(true),
+ },
+ )
+ if err != nil {
+ return false
+ }
+
+ if features == nil || features.MemTrack == nil {
+ return false
+ }
+
+ return *features.MemTrack
+}
+
+func GetCriuVersion() (int, error) {
+ c := criu.MakeCriu()
+ return c.GetCriuVersion()
+}
diff --git a/pkg/criu/criu_unsupported.go b/pkg/criu/criu_unsupported.go
index 3e3ed9c6c..437482a0e 100644
--- a/pkg/criu/criu_unsupported.go
+++ b/pkg/criu/criu_unsupported.go
@@ -3,6 +3,14 @@
package criu
+func CheckForCriu(version int) bool {
+ return false
+}
+
func MemTrack() bool {
return false
}
+
+func GetCriuVersion() (int, error) {
+ return MinCriuVersion, nil
+}
diff --git a/pkg/domain/entities/engine_container.go b/pkg/domain/entities/engine_container.go
index df42876f6..e4eb808b4 100644
--- a/pkg/domain/entities/engine_container.go
+++ b/pkg/domain/entities/engine_container.go
@@ -104,4 +104,5 @@ type ContainerEngine interface {
VolumePrune(ctx context.Context, options VolumePruneOptions) ([]*reports.PruneReport, error)
VolumeRm(ctx context.Context, namesOrIds []string, opts VolumeRmOptions) ([]*VolumeRmReport, error)
VolumeUnmount(ctx context.Context, namesOrIds []string) ([]*VolumeUnmountReport, error)
+ VolumeReload(ctx context.Context) (*VolumeReloadReport, error)
}
diff --git a/pkg/domain/entities/engine_image.go b/pkg/domain/entities/engine_image.go
index 5011d82aa..5f76ae50b 100644
--- a/pkg/domain/entities/engine_image.go
+++ b/pkg/domain/entities/engine_image.go
@@ -22,12 +22,12 @@ type ImageEngine interface {
Push(ctx context.Context, source string, destination string, opts ImagePushOptions) error
Remove(ctx context.Context, images []string, opts ImageRemoveOptions) (*ImageRemoveReport, []error)
Save(ctx context.Context, nameOrID string, tags []string, options ImageSaveOptions) error
+ Scp(ctx context.Context, src, dst string, parentFlags []string, quiet bool) error
Search(ctx context.Context, term string, opts ImageSearchOptions) ([]ImageSearchReport, error)
SetTrust(ctx context.Context, args []string, options SetTrustOptions) error
ShowTrust(ctx context.Context, args []string, options ShowTrustOptions) (*ShowTrustReport, error)
Shutdown(ctx context.Context)
Tag(ctx context.Context, nameOrID string, tags []string, options ImageTagOptions) error
- Transfer(ctx context.Context, source ImageScpOptions, dest ImageScpOptions, parentFlags []string) error
Tree(ctx context.Context, nameOrID string, options ImageTreeOptions) (*ImageTreeReport, error)
Unmount(ctx context.Context, images []string, options ImageUnmountOptions) ([]*ImageUnmountReport, error)
Untag(ctx context.Context, nameOrID string, tags []string, options ImageUntagOptions) error
diff --git a/pkg/domain/entities/events.go b/pkg/domain/entities/events.go
index d8ba0f1d3..de218b285 100644
--- a/pkg/domain/entities/events.go
+++ b/pkg/domain/entities/events.go
@@ -14,6 +14,7 @@ type Event struct {
// TODO: it would be nice to have full control over the types at some
// point and fork such Docker types.
dockerEvents.Message
+ HealthStatus string
}
// ConvertToLibpodEvent converts an entities event to a libpod one.
@@ -44,6 +45,7 @@ func ConvertToLibpodEvent(e Event) *libpodEvents.Event {
Status: status,
Time: time.Unix(0, e.TimeNano),
Type: t,
+ HealthStatus: e.HealthStatus,
Details: libpodEvents.Details{
Attributes: details,
},
@@ -59,7 +61,7 @@ func ConvertToEntitiesEvent(e libpodEvents.Event) *Event {
attributes["image"] = e.Image
attributes["name"] = e.Name
attributes["containerExitCode"] = strconv.Itoa(e.ContainerExitCode)
- return &Event{dockerEvents.Message{
+ message := dockerEvents.Message{
// Compatibility with clients that still look for deprecated API elements
Status: e.Status.String(),
ID: e.ID,
@@ -73,5 +75,9 @@ func ConvertToEntitiesEvent(e libpodEvents.Event) *Event {
Scope: "local",
Time: e.Time.Unix(),
TimeNano: e.Time.UnixNano(),
- }}
+ }
+ return &Event{
+ message,
+ e.HealthStatus,
+ }
}
diff --git a/pkg/domain/entities/images.go b/pkg/domain/entities/images.go
index 11f6e8687..da317cfad 100644
--- a/pkg/domain/entities/images.go
+++ b/pkg/domain/entities/images.go
@@ -325,6 +325,8 @@ type ImageScpOptions struct {
Image string `json:"image,omitempty"`
// User is used in conjunction with Transfer to determine if a valid user was given to save from/load into
User string `json:"user,omitempty"`
+ // Tag is the name to be used for the image on the destination
+ Tag string `json:"tag,omitempty"`
}
// ImageScpConnections provides the ssh related information used in remote image transfer
diff --git a/pkg/domain/entities/manifest.go b/pkg/domain/entities/manifest.go
index 81f3e837b..e88c5f854 100644
--- a/pkg/domain/entities/manifest.go
+++ b/pkg/domain/entities/manifest.go
@@ -67,6 +67,21 @@ type ManifestModifyOptions struct {
type ManifestRemoveOptions struct {
}
+// ManifestRemoveReport provides the model for the removed manifest
+//
+// swagger:model
+type ManifestRemoveReport struct {
+ // Deleted manifest list.
+ Deleted []string `json:",omitempty"`
+ // Untagged images. Can be longer than Deleted.
+ Untagged []string `json:",omitempty"`
+ // Errors associated with operation
+ Errors []string `json:",omitempty"`
+ // ExitCode describes the exit codes as described in the `podman rmi`
+ // man page.
+ ExitCode int
+}
+
// ManifestModifyReport provides the model for removed digests and changed manifest
//
// swagger:model
diff --git a/pkg/domain/entities/reports/scp.go b/pkg/domain/entities/reports/scp.go
new file mode 100644
index 000000000..1e102bab3
--- /dev/null
+++ b/pkg/domain/entities/reports/scp.go
@@ -0,0 +1,5 @@
+package reports
+
+type ScpReport struct {
+ Id string `json:"Id"` //nolint:revive,stylecheck
+}
diff --git a/pkg/domain/entities/volumes.go b/pkg/domain/entities/volumes.go
index 556df16c1..9a06b2238 100644
--- a/pkg/domain/entities/volumes.go
+++ b/pkg/domain/entities/volumes.go
@@ -54,6 +54,11 @@ type VolumeListReport struct {
VolumeConfigResponse
}
+// VolumeReloadReport describes the response from reload volume plugins
+type VolumeReloadReport struct {
+ define.VolumeReload
+}
+
/*
* Docker API compatibility types
*/
diff --git a/pkg/domain/filters/volumes.go b/pkg/domain/filters/volumes.go
index e88bd4228..a18e6332c 100644
--- a/pkg/domain/filters/volumes.go
+++ b/pkg/domain/filters/volumes.go
@@ -2,6 +2,7 @@ package filters
import (
"net/url"
+ "regexp"
"strings"
"github.com/containers/podman/v4/libpod"
@@ -15,9 +16,12 @@ func GenerateVolumeFilters(filters url.Values) ([]libpod.VolumeFilter, error) {
for _, val := range v {
switch filter {
case "name":
- nameVal := val
+ nameRegexp, err := regexp.Compile(val)
+ if err != nil {
+ return nil, err
+ }
vf = append(vf, func(v *libpod.Volume) bool {
- return nameVal == v.Name()
+ return nameRegexp.MatchString(v.Name())
})
case "driver":
driverVal := val
diff --git a/pkg/domain/infra/abi/images.go b/pkg/domain/infra/abi/images.go
index d63de2424..02aa5923d 100644
--- a/pkg/domain/infra/abi/images.go
+++ b/pkg/domain/infra/abi/images.go
@@ -3,6 +3,7 @@ package abi
import (
"context"
"fmt"
+ "io/fs"
"io/ioutil"
"net/url"
"os"
@@ -29,7 +30,6 @@ import (
domainUtils "github.com/containers/podman/v4/pkg/domain/utils"
"github.com/containers/podman/v4/pkg/errorhandling"
"github.com/containers/podman/v4/pkg/rootless"
- "github.com/containers/podman/v4/utils"
"github.com/containers/storage"
dockerRef "github.com/docker/distribution/reference"
"github.com/opencontainers/go-digest"
@@ -350,22 +350,6 @@ func (ir *ImageEngine) Push(ctx context.Context, source string, destination stri
}
return pushError
}
-
-// Transfer moves images between root and rootless storage so the user specified in the scp call can access and use the image modified by root
-func (ir *ImageEngine) Transfer(ctx context.Context, source entities.ImageScpOptions, dest entities.ImageScpOptions, parentFlags []string) error {
- if source.User == "" {
- return errors.Wrapf(define.ErrInvalidArg, "you must define a user when transferring from root to rootless storage")
- }
- podman, err := os.Executable()
- if err != nil {
- return err
- }
- if rootless.IsRootless() && (len(dest.User) == 0 || dest.User == "root") { // if we are rootless and do not have a destination user we can just use sudo
- return transferRootless(source, dest, podman, parentFlags)
- }
- return transferRootful(source, dest, podman, parentFlags)
-}
-
func (ir *ImageEngine) Tag(ctx context.Context, nameOrID string, tags []string, options entities.ImageTagOptions) error {
// Allow tagging manifest list instead of resolving instances from manifest
lookupOptions := &libimage.LookupImageOptions{ManifestList: true}
@@ -694,53 +678,32 @@ func (ir *ImageEngine) Sign(ctx context.Context, names []string, options entitie
return nil, nil
}
-func getSigFilename(sigStoreDirPath string) (string, error) {
- sigFileSuffix := 1
- sigFiles, err := ioutil.ReadDir(sigStoreDirPath)
+func (ir *ImageEngine) Scp(ctx context.Context, src, dst string, parentFlags []string, quiet bool) error {
+ rep, source, dest, flags, err := domainUtils.ExecuteTransfer(src, dst, parentFlags, quiet)
if err != nil {
- return "", err
- }
- sigFilenames := make(map[string]bool)
- for _, file := range sigFiles {
- sigFilenames[file.Name()] = true
+ return err
}
- for {
- sigFilename := "signature-" + strconv.Itoa(sigFileSuffix)
- if _, exists := sigFilenames[sigFilename]; !exists {
- return sigFilename, nil
+ if (rep == nil && err == nil) && (source != nil && dest != nil) { // we need to execute the transfer
+ err := Transfer(ctx, *source, *dest, flags)
+ if err != nil {
+ return err
}
- sigFileSuffix++
- }
-}
-
-func localPathFromURI(url *url.URL) (string, error) {
- if url.Scheme != "file" {
- return "", errors.Errorf("writing to %s is not supported. Use a supported scheme", url.String())
}
- return url.Path, nil
+ return nil
}
-// putSignature creates signature and saves it to the signstore file
-func putSignature(manifestBlob []byte, mech signature.SigningMechanism, sigStoreDir string, instanceDigest digest.Digest, dockerReference dockerRef.Reference, options entities.SignOptions) error {
- newSig, err := signature.SignDockerManifest(manifestBlob, dockerReference.String(), mech, options.SignBy)
- if err != nil {
- return err
- }
- signatureDir := fmt.Sprintf("%s@%s=%s", sigStoreDir, instanceDigest.Algorithm(), instanceDigest.Hex())
- if err := os.MkdirAll(signatureDir, 0751); err != nil {
- // The directory is allowed to exist
- if !os.IsExist(err) {
- return err
- }
+func Transfer(ctx context.Context, source entities.ImageScpOptions, dest entities.ImageScpOptions, parentFlags []string) error {
+ if source.User == "" {
+ return errors.Wrapf(define.ErrInvalidArg, "you must define a user when transferring from root to rootless storage")
}
- sigFilename, err := getSigFilename(signatureDir)
+ podman, err := os.Executable()
if err != nil {
return err
}
- if err = ioutil.WriteFile(filepath.Join(signatureDir, sigFilename), newSig, 0644); err != nil {
- return err
+ if rootless.IsRootless() && (len(dest.User) == 0 || dest.User == "root") { // if we are rootless and do not have a destination user we can just use sudo
+ return transferRootless(source, dest, podman, parentFlags)
}
- return nil
+ return transferRootful(source, dest, podman, parentFlags)
}
// TransferRootless creates new podman processes using exec.Command and sudo, transferring images between the given source and destination users
@@ -763,7 +726,7 @@ func transferRootless(source entities.ImageScpOptions, dest entities.ImageScpOpt
} else {
cmdSave = exec.Command(podman)
}
- cmdSave = utils.CreateSCPCommand(cmdSave, saveCommand)
+ cmdSave = domainUtils.CreateSCPCommand(cmdSave, saveCommand)
logrus.Debugf("Executing save command: %q", cmdSave)
err := cmdSave.Run()
if err != nil {
@@ -776,8 +739,11 @@ func transferRootless(source entities.ImageScpOptions, dest entities.ImageScpOpt
} else {
cmdLoad = exec.Command(podman)
}
- cmdLoad = utils.CreateSCPCommand(cmdLoad, loadCommand)
+ cmdLoad = domainUtils.CreateSCPCommand(cmdLoad, loadCommand)
logrus.Debugf("Executing load command: %q", cmdLoad)
+ if len(dest.Tag) > 0 {
+ return domainUtils.ScpTag(cmdLoad, podman, dest)
+ }
return cmdLoad.Run()
}
@@ -833,11 +799,20 @@ func transferRootful(source entities.ImageScpOptions, dest entities.ImageScpOpti
return err
}
}
- err = execPodman(uSave, saveCommand)
+ _, err = execTransferPodman(uSave, saveCommand, false)
+ if err != nil {
+ return err
+ }
+ out, err := execTransferPodman(uLoad, loadCommand, (len(dest.Tag) > 0))
if err != nil {
return err
}
- return execPodman(uLoad, loadCommand)
+ if out != nil {
+ image := domainUtils.ExtractImage(out)
+ _, err := execTransferPodman(uLoad, []string{podman, "tag", image, dest.Tag}, false)
+ return err
+ }
+ return nil
}
func lookupUser(u string) (*user.User, error) {
@@ -847,10 +822,10 @@ func lookupUser(u string) (*user.User, error) {
return user.Lookup(u)
}
-func execPodman(execUser *user.User, command []string) error {
- cmdLogin, err := utils.LoginUser(execUser.Username)
+func execTransferPodman(execUser *user.User, command []string, needToTag bool) ([]byte, error) {
+ cmdLogin, err := domainUtils.LoginUser(execUser.Username)
if err != nil {
- return err
+ return nil, err
}
defer func() {
@@ -864,11 +839,11 @@ func execPodman(execUser *user.User, command []string) error {
cmd.Stdout = os.Stdout
uid, err := strconv.ParseInt(execUser.Uid, 10, 32)
if err != nil {
- return err
+ return nil, err
}
gid, err := strconv.ParseInt(execUser.Gid, 10, 32)
if err != nil {
- return err
+ return nil, err
}
cmd.SysProcAttr = &syscall.SysProcAttr{
Credential: &syscall.Credential{
@@ -878,5 +853,55 @@ func execPodman(execUser *user.User, command []string) error {
NoSetGroups: false,
},
}
- return cmd.Run()
+ if needToTag {
+ cmd.Stdout = nil
+ return cmd.Output()
+ }
+ return nil, cmd.Run()
+}
+
+func getSigFilename(sigStoreDirPath string) (string, error) {
+ sigFileSuffix := 1
+ sigFiles, err := ioutil.ReadDir(sigStoreDirPath)
+ if err != nil {
+ return "", err
+ }
+ sigFilenames := make(map[string]bool)
+ for _, file := range sigFiles {
+ sigFilenames[file.Name()] = true
+ }
+ for {
+ sigFilename := "signature-" + strconv.Itoa(sigFileSuffix)
+ if _, exists := sigFilenames[sigFilename]; !exists {
+ return sigFilename, nil
+ }
+ sigFileSuffix++
+ }
+}
+
+func localPathFromURI(url *url.URL) (string, error) {
+ if url.Scheme != "file" {
+ return "", errors.Errorf("writing to %s is not supported. Use a supported scheme", url.String())
+ }
+ return url.Path, nil
+}
+
+// putSignature creates signature and saves it to the signstore file
+func putSignature(manifestBlob []byte, mech signature.SigningMechanism, sigStoreDir string, instanceDigest digest.Digest, dockerReference dockerRef.Reference, options entities.SignOptions) error {
+ newSig, err := signature.SignDockerManifest(manifestBlob, dockerReference.String(), mech, options.SignBy)
+ if err != nil {
+ return err
+ }
+ signatureDir := fmt.Sprintf("%s@%s=%s", sigStoreDir, instanceDigest.Algorithm(), instanceDigest.Hex())
+ if err := os.MkdirAll(signatureDir, 0751); err != nil {
+ // The directory is allowed to exist
+ if !errors.Is(err, fs.ErrExist) {
+ return err
+ }
+ }
+ sigFilename, err := getSigFilename(signatureDir)
+ if err != nil {
+ return err
+ }
+ return ioutil.WriteFile(filepath.Join(signatureDir, sigFilename), newSig, 0644)
}
diff --git a/pkg/domain/infra/abi/pods.go b/pkg/domain/infra/abi/pods.go
index 3e9cb7f5e..1dca8c580 100644
--- a/pkg/domain/infra/abi/pods.go
+++ b/pkg/domain/infra/abi/pods.go
@@ -402,6 +402,56 @@ func (ic *ContainerEngine) PodTop(ctx context.Context, options entities.PodTopOp
return report, err
}
+func (ic *ContainerEngine) listPodReportFromPod(p *libpod.Pod) (*entities.ListPodsReport, error) {
+ status, err := p.GetPodStatus()
+ if err != nil {
+ return nil, err
+ }
+ cons, err := p.AllContainers()
+ if err != nil {
+ return nil, err
+ }
+ lpcs := make([]*entities.ListPodContainer, len(cons))
+ for i, c := range cons {
+ state, err := c.State()
+ if err != nil {
+ return nil, err
+ }
+ lpcs[i] = &entities.ListPodContainer{
+ Id: c.ID(),
+ Names: c.Name(),
+ Status: state.String(),
+ }
+ }
+ infraID, err := p.InfraContainerID()
+ if err != nil {
+ return nil, err
+ }
+ networks := []string{}
+ if len(infraID) > 0 {
+ infra, err := p.InfraContainer()
+ if err != nil {
+ return nil, err
+ }
+ networks, err = infra.Networks()
+ if err != nil {
+ return nil, err
+ }
+ }
+ return &entities.ListPodsReport{
+ Cgroup: p.CgroupParent(),
+ Containers: lpcs,
+ Created: p.CreatedTime(),
+ Id: p.ID(),
+ InfraId: infraID,
+ Name: p.Name(),
+ Namespace: p.Namespace(),
+ Networks: networks,
+ Status: status,
+ Labels: p.Labels(),
+ }, nil
+}
+
func (ic *ContainerEngine) PodPs(ctx context.Context, options entities.PodPSOptions) ([]*entities.ListPodsReport, error) {
var (
err error
@@ -431,53 +481,14 @@ func (ic *ContainerEngine) PodPs(ctx context.Context, options entities.PodPSOpti
reports := make([]*entities.ListPodsReport, 0, len(pds))
for _, p := range pds {
- var lpcs []*entities.ListPodContainer
- status, err := p.GetPodStatus()
- if err != nil {
- return nil, err
- }
- cons, err := p.AllContainers()
+ r, err := ic.listPodReportFromPod(p)
if err != nil {
- return nil, err
- }
- for _, c := range cons {
- state, err := c.State()
- if err != nil {
- return nil, err
+ if errors.Is(err, define.ErrNoSuchPod) || errors.Is(err, define.ErrNoSuchCtr) {
+ continue
}
- lpcs = append(lpcs, &entities.ListPodContainer{
- Id: c.ID(),
- Names: c.Name(),
- Status: state.String(),
- })
- }
- infraID, err := p.InfraContainerID()
- if err != nil {
return nil, err
}
- networks := []string{}
- if len(infraID) > 0 {
- infra, err := p.InfraContainer()
- if err != nil {
- return nil, err
- }
- networks, err = infra.Networks()
- if err != nil {
- return nil, err
- }
- }
- reports = append(reports, &entities.ListPodsReport{
- Cgroup: p.CgroupParent(),
- Containers: lpcs,
- Created: p.CreatedTime(),
- Id: p.ID(),
- InfraId: infraID,
- Name: p.Name(),
- Namespace: p.Namespace(),
- Networks: networks,
- Status: status,
- Labels: p.Labels(),
- })
+ reports = append(reports, r)
}
return reports, nil
}
diff --git a/pkg/domain/infra/abi/system.go b/pkg/domain/infra/abi/system.go
index 6e26026d4..2bd88ed85 100644
--- a/pkg/domain/infra/abi/system.go
+++ b/pkg/domain/infra/abi/system.go
@@ -319,8 +319,8 @@ func (ic *ContainerEngine) SystemDf(ctx context.Context, options entities.System
}
dfVolumes := make([]*entities.SystemDfVolumeReport, 0, len(vols))
- var reclaimableSize uint64
for _, v := range vols {
+ var reclaimableSize uint64
var consInUse int
mountPoint, err := v.MountPoint()
if err != nil {
@@ -341,7 +341,7 @@ func (ic *ContainerEngine) SystemDf(ctx context.Context, options entities.System
return nil, err
}
if len(inUse) == 0 {
- reclaimableSize += volSize
+ reclaimableSize = volSize
}
for _, viu := range inUse {
if cutil.StringInSlice(viu, runningContainers) {
diff --git a/pkg/domain/infra/abi/volumes.go b/pkg/domain/infra/abi/volumes.go
index a9c53c140..1186d8e81 100644
--- a/pkg/domain/infra/abi/volumes.go
+++ b/pkg/domain/infra/abi/volumes.go
@@ -211,3 +211,8 @@ func (ic *ContainerEngine) VolumeUnmount(ctx context.Context, nameOrIDs []string
return reports, nil
}
+
+func (ic *ContainerEngine) VolumeReload(ctx context.Context) (*entities.VolumeReloadReport, error) {
+ report := ic.Libpod.UpdateVolumePlugins(ctx)
+ return &entities.VolumeReloadReport{VolumeReload: *report}, nil
+}
diff --git a/pkg/domain/infra/tunnel/images.go b/pkg/domain/infra/tunnel/images.go
index 97838d596..09f8ac4c3 100644
--- a/pkg/domain/infra/tunnel/images.go
+++ b/pkg/domain/infra/tunnel/images.go
@@ -2,6 +2,7 @@ package tunnel
import (
"context"
+ "fmt"
"io/ioutil"
"os"
"strconv"
@@ -12,7 +13,6 @@ import (
"github.com/containers/common/pkg/config"
"github.com/containers/image/v5/docker/reference"
"github.com/containers/image/v5/types"
- "github.com/containers/podman/v4/libpod/define"
"github.com/containers/podman/v4/pkg/bindings/images"
"github.com/containers/podman/v4/pkg/domain/entities"
"github.com/containers/podman/v4/pkg/domain/entities/reports"
@@ -28,7 +28,7 @@ func (ir *ImageEngine) Exists(_ context.Context, nameOrID string) (*entities.Boo
}
func (ir *ImageEngine) Remove(ctx context.Context, imagesArg []string, opts entities.ImageRemoveOptions) (*entities.ImageRemoveReport, []error) {
- options := new(images.RemoveOptions).WithForce(opts.Force).WithIgnore(opts.Ignore).WithAll(opts.All)
+ options := new(images.RemoveOptions).WithForce(opts.Force).WithIgnore(opts.Ignore).WithAll(opts.All).WithLookupManifest(opts.LookupManifest)
return images.Remove(ir.ClientCtx, imagesArg, options)
}
@@ -123,10 +123,6 @@ func (ir *ImageEngine) Pull(ctx context.Context, rawImage string, opts entities.
return &entities.ImagePullReport{Images: pulledImages}, nil
}
-func (ir *ImageEngine) Transfer(ctx context.Context, source entities.ImageScpOptions, dest entities.ImageScpOptions, parentFlags []string) error {
- return errors.Wrapf(define.ErrNotImplemented, "cannot use the remote client to transfer images between root and rootless storage")
-}
-
func (ir *ImageEngine) Tag(ctx context.Context, nameOrID string, tags []string, opt entities.ImageTagOptions) error {
options := new(images.TagOptions)
for _, newTag := range tags {
@@ -367,3 +363,23 @@ func (ir *ImageEngine) Shutdown(_ context.Context) {
func (ir *ImageEngine) Sign(ctx context.Context, names []string, options entities.SignOptions) (*entities.SignReport, error) {
return nil, errors.New("not implemented yet")
}
+
+func (ir *ImageEngine) Scp(ctx context.Context, src, dst string, parentFlags []string, quiet bool) error {
+ options := new(images.ScpOptions)
+
+ var destination *string
+ if len(dst) > 1 {
+ destination = &dst
+ }
+ options.Quiet = &quiet
+ options.Destination = destination
+
+ rep, err := images.Scp(ir.ClientCtx, &src, destination, *options)
+ if err != nil {
+ return err
+ }
+
+ fmt.Println("Loaded Image(s):", rep.Id)
+
+ return nil
+}
diff --git a/pkg/domain/infra/tunnel/volumes.go b/pkg/domain/infra/tunnel/volumes.go
index 33e090148..6ec35e836 100644
--- a/pkg/domain/infra/tunnel/volumes.go
+++ b/pkg/domain/infra/tunnel/volumes.go
@@ -108,3 +108,7 @@ func (ic *ContainerEngine) VolumeMount(ctx context.Context, nameOrIDs []string)
func (ic *ContainerEngine) VolumeUnmount(ctx context.Context, nameOrIDs []string) ([]*entities.VolumeUnmountReport, error) {
return nil, errors.New("unmounting volumes is not supported for remote clients")
}
+
+func (ic *ContainerEngine) VolumeReload(ctx context.Context) (*entities.VolumeReloadReport, error) {
+ return nil, errors.New("volume reload is not supported for remote clients")
+}
diff --git a/pkg/domain/utils/scp.go b/pkg/domain/utils/scp.go
new file mode 100644
index 000000000..a4ff6b950
--- /dev/null
+++ b/pkg/domain/utils/scp.go
@@ -0,0 +1,581 @@
+package utils
+
+import (
+ "bytes"
+ "fmt"
+ "io/ioutil"
+ "net"
+ "net/url"
+ "os"
+ "os/exec"
+ "os/user"
+ "strconv"
+ "strings"
+ "time"
+
+ scpD "github.com/dtylman/scp"
+
+ "github.com/containers/common/pkg/config"
+ "github.com/containers/podman/v4/libpod/define"
+ "github.com/containers/podman/v4/pkg/domain/entities"
+ "github.com/containers/podman/v4/pkg/terminal"
+ "github.com/docker/distribution/reference"
+ "github.com/pkg/errors"
+ "github.com/sirupsen/logrus"
+ "golang.org/x/crypto/ssh"
+ "golang.org/x/crypto/ssh/agent"
+)
+
+func ExecuteTransfer(src, dst string, parentFlags []string, quiet bool) (*entities.ImageLoadReport, *entities.ImageScpOptions, *entities.ImageScpOptions, []string, error) {
+ source := entities.ImageScpOptions{}
+ dest := entities.ImageScpOptions{}
+ sshInfo := entities.ImageScpConnections{}
+ report := entities.ImageLoadReport{Names: []string{}}
+
+ podman, err := os.Executable()
+ if err != nil {
+ return nil, nil, nil, nil, err
+ }
+
+ f, err := ioutil.TempFile("", "podman") // open temp file for load/save output
+ if err != nil {
+ return nil, nil, nil, nil, err
+ }
+
+ confR, err := config.NewConfig("") // create a hand made config for the remote engine since we might use remote and native at once
+ if err != nil {
+ return nil, nil, nil, nil, errors.Wrapf(err, "could not make config")
+ }
+
+ cfg, err := config.ReadCustomConfig() // get ready to set ssh destination if necessary
+ if err != nil {
+ return nil, nil, nil, nil, err
+ }
+ locations := []*entities.ImageScpOptions{}
+ cliConnections := []string{}
+ args := []string{src}
+ if len(dst) > 0 {
+ args = append(args, dst)
+ }
+ for _, arg := range args {
+ loc, connect, err := ParseImageSCPArg(arg)
+ if err != nil {
+ return nil, nil, nil, nil, err
+ }
+ locations = append(locations, loc)
+ cliConnections = append(cliConnections, connect...)
+ }
+ source = *locations[0]
+ switch {
+ case len(locations) > 1:
+ if err = ValidateSCPArgs(locations); err != nil {
+ return nil, nil, nil, nil, err
+ }
+ dest = *locations[1]
+ case len(locations) == 1:
+ switch {
+ case len(locations[0].Image) == 0:
+ return nil, nil, nil, nil, errors.Wrapf(define.ErrInvalidArg, "no source image specified")
+ case len(locations[0].Image) > 0 && !locations[0].Remote && len(locations[0].User) == 0: // if we have podman image scp $IMAGE
+ return nil, nil, nil, nil, errors.Wrapf(define.ErrInvalidArg, "must specify a destination")
+ }
+ }
+
+ source.Quiet = quiet
+ source.File = f.Name() // after parsing the arguments, set the file for the save/load
+ dest.File = source.File
+ if err = os.Remove(source.File); err != nil { // remove the file and simply use its name so podman creates the file upon save. avoids umask errors
+ return nil, nil, nil, nil, err
+ }
+
+ allLocal := true // if we are all localhost, do not validate connections but if we are using one localhost and one non we need to use sshd
+ for _, val := range cliConnections {
+ if !strings.Contains(val, "@localhost::") {
+ allLocal = false
+ break
+ }
+ }
+ if allLocal {
+ cliConnections = []string{}
+ }
+
+ var serv map[string]config.Destination
+ serv, err = GetServiceInformation(&sshInfo, cliConnections, cfg)
+ if err != nil {
+ return nil, nil, nil, nil, err
+ }
+
+ confR.Engine = config.EngineConfig{Remote: true, CgroupManager: "cgroupfs", ServiceDestinations: serv} // pass the service dest (either remote or something else) to engine
+ saveCmd, loadCmd := CreateCommands(source, dest, parentFlags, podman)
+
+ switch {
+ case source.Remote: // if we want to load FROM the remote, dest can either be local or remote in this case
+ err = SaveToRemote(source.Image, source.File, "", sshInfo.URI[0], sshInfo.Identities[0])
+ if err != nil {
+ return nil, nil, nil, nil, err
+ }
+ if dest.Remote { // we want to load remote -> remote, both source and dest are remote
+ rep, id, err := LoadToRemote(dest, dest.File, "", sshInfo.URI[1], sshInfo.Identities[1])
+ if err != nil {
+ return nil, nil, nil, nil, err
+ }
+ if len(rep) > 0 {
+ fmt.Println(rep)
+ }
+ if len(id) > 0 {
+ report.Names = append(report.Names, id)
+ }
+ break
+ }
+ id, err := ExecPodman(dest, podman, loadCmd)
+ if err != nil {
+ return nil, nil, nil, nil, err
+ }
+ if len(id) > 0 {
+ report.Names = append(report.Names, id)
+ }
+ case dest.Remote: // remote host load, implies source is local
+ _, err = ExecPodman(dest, podman, saveCmd)
+ if err != nil {
+ return nil, nil, nil, nil, err
+ }
+ rep, id, err := LoadToRemote(dest, source.File, "", sshInfo.URI[0], sshInfo.Identities[0])
+ if err != nil {
+ return nil, nil, nil, nil, err
+ }
+ if len(rep) > 0 {
+ fmt.Println(rep)
+ }
+ if len(id) > 0 {
+ report.Names = append(report.Names, id)
+ }
+ if err = os.Remove(source.File); err != nil {
+ return nil, nil, nil, nil, err
+ }
+ default: // else native load, both source and dest are local and transferring between users
+ if source.User == "" { // source user has to be set, destination does not
+ source.User = os.Getenv("USER")
+ if source.User == "" {
+ u, err := user.Current()
+ if err != nil {
+ return nil, nil, nil, nil, errors.Wrapf(err, "could not obtain user, make sure the environmental variable $USER is set")
+ }
+ source.User = u.Username
+ }
+ }
+ return nil, &source, &dest, parentFlags, nil // transfer needs to be done in ABI due to cross issues
+ }
+
+ return &report, nil, nil, nil, nil
+}
+
+// CreateSCPCommand takes an existing command, appends the given arguments and returns a configured podman command for image scp
+func CreateSCPCommand(cmd *exec.Cmd, command []string) *exec.Cmd {
+ cmd.Args = append(cmd.Args, command...)
+ cmd.Env = os.Environ()
+ cmd.Stderr = os.Stderr
+ cmd.Stdout = os.Stdout
+ return cmd
+}
+
+// ScpTag is a helper function for native podman to tag an image after a local load from image SCP
+func ScpTag(cmd *exec.Cmd, podman string, dest entities.ImageScpOptions) error {
+ cmd.Stdout = nil
+ out, err := cmd.Output() // this function captures the output temporarily in order to execute the next command
+ if err != nil {
+ return err
+ }
+ image := ExtractImage(out)
+ if cmd.Args[0] == "sudo" { // transferRootless will need the sudo since we are loading to sudo from a user acct
+ cmd = exec.Command("sudo", podman, "tag", image, dest.Tag)
+ } else {
+ cmd = exec.Command(podman, "tag", image, dest.Tag)
+ }
+ cmd.Stdout = os.Stdout
+ return cmd.Run()
+}
+
+// ExtractImage pulls out the last line of output from save/load (image id)
+func ExtractImage(out []byte) string {
+ fmt.Println(strings.TrimSuffix(string(out), "\n")) // print output
+ stringOut := string(out) // get all output
+ arrOut := strings.Split(stringOut, " ") // split it into an array
+ return strings.ReplaceAll(arrOut[len(arrOut)-1], "\n", "") // replace the trailing \n
+}
+
+// LoginUser starts the user process on the host so that image scp can use systemd-run
+func LoginUser(user string) (*exec.Cmd, error) {
+ sleep, err := exec.LookPath("sleep")
+ if err != nil {
+ return nil, err
+ }
+ machinectl, err := exec.LookPath("machinectl")
+ if err != nil {
+ return nil, err
+ }
+ cmd := exec.Command(machinectl, "shell", "-q", user+"@.host", sleep, "inf")
+ err = cmd.Start()
+ return cmd, err
+}
+
+// loadToRemote takes image and remote connection information. it connects to the specified client
+// and copies the saved image dir over to the remote host and then loads it onto the machine
+// returns a string containing output or an error
+func LoadToRemote(dest entities.ImageScpOptions, localFile string, tag string, url *url.URL, iden string) (string, string, error) {
+ dial, remoteFile, err := CreateConnection(url, iden)
+ if err != nil {
+ return "", "", err
+ }
+ defer dial.Close()
+
+ n, err := scpD.CopyTo(dial, localFile, remoteFile)
+ if err != nil {
+ errOut := strconv.Itoa(int(n)) + " Bytes copied before error"
+ return " ", "", errors.Wrapf(err, errOut)
+ }
+ var run string
+ if tag != "" {
+ return "", "", errors.Wrapf(define.ErrInvalidArg, "Renaming of an image is currently not supported")
+ }
+ podman := os.Args[0]
+ run = podman + " image load --input=" + remoteFile + ";rm " + remoteFile // run ssh image load of the file copied via scp
+ out, err := ExecRemoteCommand(dial, run)
+ if err != nil {
+ return "", "", err
+ }
+ rep := strings.TrimSuffix(string(out), "\n")
+ outArr := strings.Split(rep, " ")
+ id := outArr[len(outArr)-1]
+ if len(dest.Tag) > 0 { // tag the remote image using the output ID
+ run = podman + " tag " + id + " " + dest.Tag
+ _, err = ExecRemoteCommand(dial, run)
+ if err != nil {
+ return "", "", err
+ }
+ }
+ return rep, id, nil
+}
+
+// saveToRemote takes image information and remote connection information. it connects to the specified client
+// and saves the specified image on the remote machine and then copies it to the specified local location
+// returns an error if one occurs.
+func SaveToRemote(image, localFile string, tag string, uri *url.URL, iden string) error {
+ dial, remoteFile, err := CreateConnection(uri, iden)
+
+ if err != nil {
+ return err
+ }
+ defer dial.Close()
+
+ if tag != "" {
+ return errors.Wrapf(define.ErrInvalidArg, "Renaming of an image is currently not supported")
+ }
+ podman := os.Args[0]
+ run := podman + " image save " + image + " --format=oci-archive --output=" + remoteFile // run ssh image load of the file copied via scp. Files are reverse in this case...
+ _, err = ExecRemoteCommand(dial, run)
+ if err != nil {
+ return err
+ }
+ n, err := scpD.CopyFrom(dial, remoteFile, localFile)
+ if _, conErr := ExecRemoteCommand(dial, "rm "+remoteFile); conErr != nil {
+ logrus.Errorf("Removing file on endpoint: %v", conErr)
+ }
+ if err != nil {
+ errOut := strconv.Itoa(int(n)) + " Bytes copied before error"
+ return errors.Wrapf(err, errOut)
+ }
+ return nil
+}
+
+// makeRemoteFile creates the necessary remote file on the host to
+// save or load the image to. returns a string with the file name or an error
+func MakeRemoteFile(dial *ssh.Client) (string, error) {
+ run := "mktemp"
+ remoteFile, err := ExecRemoteCommand(dial, run)
+ if err != nil {
+ return "", err
+ }
+ return strings.TrimSuffix(string(remoteFile), "\n"), nil
+}
+
+// createConnections takes a boolean determining which ssh client to dial
+// and returns the dials client, its newly opened remote file, and an error if applicable.
+func CreateConnection(url *url.URL, iden string) (*ssh.Client, string, error) {
+ cfg, err := ValidateAndConfigure(url, iden)
+ if err != nil {
+ return nil, "", err
+ }
+ dialAdd, err := ssh.Dial("tcp", url.Host, cfg) // dial the client
+ if err != nil {
+ return nil, "", errors.Wrapf(err, "failed to connect")
+ }
+ file, err := MakeRemoteFile(dialAdd)
+ if err != nil {
+ return nil, "", err
+ }
+
+ return dialAdd, file, nil
+}
+
+// GetSerivceInformation takes the parsed list of hosts to connect to and validates the information
+func GetServiceInformation(sshInfo *entities.ImageScpConnections, cliConnections []string, cfg *config.Config) (map[string]config.Destination, error) {
+ var serv map[string]config.Destination
+ var urlS string
+ var iden string
+ for i, val := range cliConnections {
+ splitEnv := strings.SplitN(val, "::", 2)
+ sshInfo.Connections = append(sshInfo.Connections, splitEnv[0])
+ conn, found := cfg.Engine.ServiceDestinations[sshInfo.Connections[i]]
+ if found {
+ urlS = conn.URI
+ iden = conn.Identity
+ } else { // no match, warn user and do a manual connection.
+ urlS = "ssh://" + sshInfo.Connections[i]
+ iden = ""
+ logrus.Warnf("Unknown connection name given. Please use system connection add to specify the default remote socket location")
+ }
+ urlFinal, err := url.Parse(urlS) // create an actual url to pass to exec command
+ if err != nil {
+ return nil, err
+ }
+ if urlFinal.User.Username() == "" {
+ if urlFinal.User, err = GetUserInfo(urlFinal); err != nil {
+ return nil, err
+ }
+ }
+ sshInfo.URI = append(sshInfo.URI, urlFinal)
+ sshInfo.Identities = append(sshInfo.Identities, iden)
+ }
+ return serv, nil
+}
+
+// execPodman executes the podman save/load command given the podman binary
+func ExecPodman(dest entities.ImageScpOptions, podman string, command []string) (string, error) {
+ cmd := exec.Command(podman)
+ CreateSCPCommand(cmd, command[1:])
+ logrus.Debugf("Executing podman command: %q", cmd)
+ if strings.Contains(strings.Join(command, " "), "load") { // need to tag
+ if len(dest.Tag) > 0 {
+ return "", ScpTag(cmd, podman, dest)
+ }
+ cmd.Stdout = nil
+ out, err := cmd.Output()
+ if err != nil {
+ return "", err
+ }
+ img := ExtractImage(out)
+ return img, nil
+ }
+ return "", cmd.Run()
+}
+
+// createCommands forms the podman save and load commands used by SCP
+func CreateCommands(source entities.ImageScpOptions, dest entities.ImageScpOptions, parentFlags []string, podman string) ([]string, []string) {
+ var parentString string
+ quiet := ""
+ if source.Quiet {
+ quiet = "-q "
+ }
+ if len(parentFlags) > 0 {
+ parentString = strings.Join(parentFlags, " ") + " " // if there are parent args, an extra space needs to be added
+ } else {
+ parentString = strings.Join(parentFlags, " ")
+ }
+ loadCmd := strings.Split(fmt.Sprintf("%s %sload %s--input %s", podman, parentString, quiet, dest.File), " ")
+ saveCmd := strings.Split(fmt.Sprintf("%s %vsave %s--output %s %s", podman, parentString, quiet, source.File, source.Image), " ")
+ return saveCmd, loadCmd
+}
+
+// parseImageSCPArg returns the valid connection, and source/destination data based off of the information provided by the user
+// arg is a string containing one of the cli arguments returned is a filled out source/destination options structs as well as a connections array and an error if applicable
+func ParseImageSCPArg(arg string) (*entities.ImageScpOptions, []string, error) {
+ location := entities.ImageScpOptions{}
+ var err error
+ cliConnections := []string{}
+
+ switch {
+ case strings.Contains(arg, "@localhost::"): // image transfer between users
+ location.User = strings.Split(arg, "@")[0]
+ location, err = ValidateImagePortion(location, arg)
+ if err != nil {
+ return nil, nil, err
+ }
+ cliConnections = append(cliConnections, arg)
+ case strings.Contains(arg, "::"):
+ location, err = ValidateImagePortion(location, arg)
+ if err != nil {
+ return nil, nil, err
+ }
+ location.Remote = true
+ cliConnections = append(cliConnections, arg)
+ default:
+ location.Image = arg
+ }
+ return &location, cliConnections, nil
+}
+
+// validateImagePortion is a helper function to validate the image name in an SCP argument
+func ValidateImagePortion(location entities.ImageScpOptions, arg string) (entities.ImageScpOptions, error) {
+ if RemoteArgLength(arg, 1) > 0 {
+ err := ValidateImageName(strings.Split(arg, "::")[1])
+ if err != nil {
+ return location, err
+ }
+ location.Image = strings.Split(arg, "::")[1] // this will get checked/set again once we validate connections
+ }
+ return location, nil
+}
+
+// validateSCPArgs takes the array of source and destination options and checks for common errors
+func ValidateSCPArgs(locations []*entities.ImageScpOptions) error {
+ if len(locations) > 2 {
+ return errors.Wrapf(define.ErrInvalidArg, "cannot specify more than two arguments")
+ }
+ switch {
+ case len(locations[0].Image) > 0 && len(locations[1].Image) > 0:
+ locations[1].Tag = locations[1].Image
+ locations[1].Image = ""
+ case len(locations[0].Image) == 0 && len(locations[1].Image) == 0:
+ return errors.Wrapf(define.ErrInvalidArg, "a source image must be specified")
+ }
+ return nil
+}
+
+// validateImageName makes sure that the image given is valid and no injections are occurring
+// we simply use this for error checking, bot setting the image
+func ValidateImageName(input string) error {
+ // ParseNormalizedNamed transforms a shortname image into its
+ // full name reference so busybox => docker.io/library/busybox
+ // we want to keep our shortnames, so only return an error if
+ // we cannot parse what the user has given us
+ _, err := reference.ParseNormalizedNamed(input)
+ return err
+}
+
+// remoteArgLength is a helper function to simplify the extracting of host argument data
+// returns an int which contains the length of a specified index in a host::image string
+func RemoteArgLength(input string, side int) int {
+ if strings.Contains(input, "::") {
+ return len((strings.Split(input, "::"))[side])
+ }
+ return -1
+}
+
+// ExecRemoteCommand takes a ssh client connection and a command to run and executes the
+// command on the specified client. The function returns the Stdout from the client or the Stderr
+func ExecRemoteCommand(dial *ssh.Client, run string) ([]byte, error) {
+ sess, err := dial.NewSession() // new ssh client session
+ if err != nil {
+ return nil, err
+ }
+ defer sess.Close()
+
+ var buffer bytes.Buffer
+ var bufferErr bytes.Buffer
+ sess.Stdout = &buffer // output from client funneled into buffer
+ sess.Stderr = &bufferErr // err form client funneled into buffer
+ if err := sess.Run(run); err != nil { // run the command on the ssh client
+ return nil, errors.Wrapf(err, bufferErr.String())
+ }
+ return buffer.Bytes(), nil
+}
+
+func GetUserInfo(uri *url.URL) (*url.Userinfo, error) {
+ var (
+ usr *user.User
+ err error
+ )
+ if u, found := os.LookupEnv("_CONTAINERS_ROOTLESS_UID"); found {
+ usr, err = user.LookupId(u)
+ if err != nil {
+ return nil, errors.Wrapf(err, "failed to lookup rootless user")
+ }
+ } else {
+ usr, err = user.Current()
+ if err != nil {
+ return nil, errors.Wrapf(err, "failed to obtain current user")
+ }
+ }
+
+ pw, set := uri.User.Password()
+ if set {
+ return url.UserPassword(usr.Username, pw), nil
+ }
+ return url.User(usr.Username), nil
+}
+
+// ValidateAndConfigure will take a ssh url and an identity key (rsa and the like) and ensure the information given is valid
+// iden iden can be blank to mean no identity key
+// once the function validates the information it creates and returns an ssh.ClientConfig.
+func ValidateAndConfigure(uri *url.URL, iden string) (*ssh.ClientConfig, error) {
+ var signers []ssh.Signer
+ passwd, passwdSet := uri.User.Password()
+ if iden != "" { // iden might be blank if coming from image scp or if no validation is needed
+ value := iden
+ s, err := terminal.PublicKey(value, []byte(passwd))
+ if err != nil {
+ return nil, errors.Wrapf(err, "failed to read identity %q", value)
+ }
+ signers = append(signers, s)
+ logrus.Debugf("SSH Ident Key %q %s %s", value, ssh.FingerprintSHA256(s.PublicKey()), s.PublicKey().Type())
+ }
+ if sock, found := os.LookupEnv("SSH_AUTH_SOCK"); found { // validate ssh information, specifically the unix file socket used by the ssh agent.
+ logrus.Debugf("Found SSH_AUTH_SOCK %q, ssh-agent signer enabled", sock)
+
+ c, err := net.Dial("unix", sock)
+ if err != nil {
+ return nil, err
+ }
+ agentSigners, err := agent.NewClient(c).Signers()
+ if err != nil {
+ return nil, err
+ }
+
+ signers = append(signers, agentSigners...)
+
+ if logrus.IsLevelEnabled(logrus.DebugLevel) {
+ for _, s := range agentSigners {
+ logrus.Debugf("SSH Agent Key %s %s", ssh.FingerprintSHA256(s.PublicKey()), s.PublicKey().Type())
+ }
+ }
+ }
+ var authMethods []ssh.AuthMethod // now we validate and check for the authorization methods, most notaibly public key authorization
+ if len(signers) > 0 {
+ var dedup = make(map[string]ssh.Signer)
+ for _, s := range signers {
+ fp := ssh.FingerprintSHA256(s.PublicKey())
+ if _, found := dedup[fp]; found {
+ logrus.Debugf("Dedup SSH Key %s %s", ssh.FingerprintSHA256(s.PublicKey()), s.PublicKey().Type())
+ }
+ dedup[fp] = s
+ }
+
+ var uniq []ssh.Signer
+ for _, s := range dedup {
+ uniq = append(uniq, s)
+ }
+ authMethods = append(authMethods, ssh.PublicKeysCallback(func() ([]ssh.Signer, error) {
+ return uniq, nil
+ }))
+ }
+ if passwdSet { // if password authentication is given and valid, add to the list
+ authMethods = append(authMethods, ssh.Password(passwd))
+ }
+ if len(authMethods) == 0 {
+ authMethods = append(authMethods, ssh.PasswordCallback(func() (string, error) {
+ pass, err := terminal.ReadPassword(fmt.Sprintf("%s's login password:", uri.User.Username()))
+ return string(pass), err
+ }))
+ }
+ tick, err := time.ParseDuration("40s")
+ if err != nil {
+ return nil, err
+ }
+ cfg := &ssh.ClientConfig{
+ User: uri.User.Username(),
+ Auth: authMethods,
+ HostKeyCallback: ssh.InsecureIgnoreHostKey(),
+ Timeout: tick,
+ }
+ return cfg, nil
+}
diff --git a/pkg/domain/utils/utils_test.go b/pkg/domain/utils/utils_test.go
index 952a4b5be..291567f6b 100644
--- a/pkg/domain/utils/utils_test.go
+++ b/pkg/domain/utils/utils_test.go
@@ -5,6 +5,7 @@ import (
"sort"
"testing"
+ "github.com/containers/podman/v4/pkg/domain/entities"
"github.com/stretchr/testify/assert"
)
@@ -74,3 +75,41 @@ func TestToURLValues(t *testing.T) {
})
}
}
+
+func TestParseSCPArgs(t *testing.T) {
+ args := []string{"alpine", "root@localhost::"}
+ var source *entities.ImageScpOptions
+ var dest *entities.ImageScpOptions
+ var err error
+ source, _, err = ParseImageSCPArg(args[0])
+ assert.Nil(t, err)
+ assert.Equal(t, source.Image, "alpine")
+
+ dest, _, err = ParseImageSCPArg(args[1])
+ assert.Nil(t, err)
+ assert.Equal(t, dest.Image, "")
+ assert.Equal(t, dest.User, "root")
+
+ args = []string{"root@localhost::alpine"}
+ source, _, err = ParseImageSCPArg(args[0])
+ assert.Nil(t, err)
+ assert.Equal(t, source.User, "root")
+ assert.Equal(t, source.Image, "alpine")
+
+ args = []string{"charliedoern@192.168.68.126::alpine", "foobar@192.168.68.126::"}
+ source, _, err = ParseImageSCPArg(args[0])
+ assert.Nil(t, err)
+ assert.True(t, source.Remote)
+ assert.Equal(t, source.Image, "alpine")
+
+ dest, _, err = ParseImageSCPArg(args[1])
+ assert.Nil(t, err)
+ assert.True(t, dest.Remote)
+ assert.Equal(t, dest.Image, "")
+
+ args = []string{"charliedoern@192.168.68.126::alpine"}
+ source, _, err = ParseImageSCPArg(args[0])
+ assert.Nil(t, err)
+ assert.True(t, source.Remote)
+ assert.Equal(t, source.Image, "alpine")
+}
diff --git a/pkg/machine/qemu/config.go b/pkg/machine/qemu/config.go
index 56c95e3b3..bada1af9b 100644
--- a/pkg/machine/qemu/config.go
+++ b/pkg/machine/qemu/config.go
@@ -72,8 +72,10 @@ type MachineVM struct {
Mounts []machine.Mount
// Name of VM
Name string
- // PidFilePath is the where the PID file lives
+ // PidFilePath is the where the Proxy PID file lives
PidFilePath machine.VMFile
+ // VMPidFilePath is the where the VM PID file lives
+ VMPidFilePath machine.VMFile
// QMPMonitor is the qemu monitor object for sending commands
QMPMonitor Monitor
// ReadySocket tells host when vm is booted
diff --git a/pkg/machine/qemu/machine.go b/pkg/machine/qemu/machine.go
index f7ef52573..2fe0230cf 100644
--- a/pkg/machine/qemu/machine.go
+++ b/pkg/machine/qemu/machine.go
@@ -32,6 +32,7 @@ import (
"github.com/docker/go-units"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
+ "golang.org/x/sys/unix"
)
var (
@@ -107,6 +108,9 @@ func (p *Provider) NewMachine(opts machine.InitOptions) (machine.VM, error) {
if err != nil {
return nil, err
}
+ if err := vm.setPIDSocket(); err != nil {
+ return nil, err
+ }
cmd := []string{execPath}
// Add memory
cmd = append(cmd, []string{"-m", strconv.Itoa(int(vm.Memory))}...)
@@ -135,11 +139,9 @@ func (p *Provider) NewMachine(opts machine.InitOptions) (machine.VM, error) {
"-device", "virtio-serial",
// qemu needs to establish the long name; other connections can use the symlink'd
"-chardev", "socket,path=" + vm.ReadySocket.Path + ",server=on,wait=off,id=" + vm.Name + "_ready",
- "-device", "virtserialport,chardev=" + vm.Name + "_ready" + ",name=org.fedoraproject.port.0"}...)
+ "-device", "virtserialport,chardev=" + vm.Name + "_ready" + ",name=org.fedoraproject.port.0",
+ "-pidfile", vm.VMPidFilePath.GetPath()}...)
vm.CmdLine = cmd
- if err := vm.setPIDSocket(); err != nil {
- return nil, err
- }
return vm, nil
}
@@ -753,17 +755,17 @@ func (v *MachineVM) Stop(_ string, _ machine.StopOptions) error {
if _, err := os.Stat(v.PidFilePath.GetPath()); os.IsNotExist(err) {
return nil
}
- pidString, err := v.PidFilePath.Read()
+ proxyPidString, err := v.PidFilePath.Read()
if err != nil {
return err
}
- pidNum, err := strconv.Atoi(string(pidString))
+ proxyPid, err := strconv.Atoi(string(proxyPidString))
if err != nil {
return err
}
- p, err := os.FindProcess(pidNum)
- if p == nil && err != nil {
+ proxyProc, err := os.FindProcess(proxyPid)
+ if proxyProc == nil && err != nil {
return err
}
@@ -772,7 +774,7 @@ func (v *MachineVM) Stop(_ string, _ machine.StopOptions) error {
return err
}
// Kill the process
- if err := p.Kill(); err != nil {
+ if err := proxyProc.Kill(); err != nil {
return err
}
// Remove the pidfile
@@ -788,22 +790,50 @@ func (v *MachineVM) Stop(_ string, _ machine.StopOptions) error {
// FIXME: this error should probably be returned
return nil //nolint: nilerr
}
-
disconnected = true
- waitInternal := 250 * time.Millisecond
- for i := 0; i < 5; i++ {
- state, err := v.State(false)
- if err != nil {
- return err
- }
- if state != machine.Running {
- break
+
+ if err := v.ReadySocket.Delete(); err != nil {
+ return err
+ }
+
+ if v.VMPidFilePath.GetPath() == "" {
+ // no vm pid file path means it's probably a machine created before we
+ // started using it, so we revert to the old way of waiting for the
+ // machine to stop
+ fmt.Println("Waiting for VM to stop running...")
+ waitInternal := 250 * time.Millisecond
+ for i := 0; i < 5; i++ {
+ state, err := v.State(false)
+ if err != nil {
+ return err
+ }
+ if state != machine.Running {
+ break
+ }
+ time.Sleep(waitInternal)
+ waitInternal *= 2
}
- time.Sleep(waitInternal)
- waitInternal *= 2
+ // after the machine stops running it normally takes about 1 second for the
+ // qemu VM to exit so we wait a bit to try to avoid issues
+ time.Sleep(2 * time.Second)
+ return nil
}
- return v.ReadySocket.Delete()
+ vmPidString, err := v.VMPidFilePath.Read()
+ if err != nil {
+ return err
+ }
+ vmPid, err := strconv.Atoi(strings.TrimSpace(string(vmPidString)))
+ if err != nil {
+ return err
+ }
+
+ fmt.Println("Waiting for VM to exit...")
+ for isProcessAlive(vmPid) {
+ time.Sleep(500 * time.Millisecond)
+ }
+
+ return nil
}
// NewQMPMonitor creates the monitor subsection of our vm
@@ -896,8 +926,11 @@ func (v *MachineVM) Remove(_ string, opts machine.RemoveOptions) (string, func()
// remove socket and pid file if any: warn at low priority if things fail
// Remove the pidfile
+ if err := v.VMPidFilePath.Delete(); err != nil {
+ logrus.Debugf("Error while removing VM pidfile: %v", err)
+ }
if err := v.PidFilePath.Delete(); err != nil {
- logrus.Debugf("Error while removing pidfile: %v", err)
+ logrus.Debugf("Error while removing proxy pidfile: %v", err)
}
// Remove socket
if err := v.QMPMonitor.Address.Delete(); err != nil {
@@ -1079,6 +1112,7 @@ func getVMInfos() ([]*machine.ListResponse, error) {
listEntry.RemoteUsername = vm.RemoteUsername
listEntry.IdentityPath = vm.IdentityPath
listEntry.CreatedAt = vm.Created
+ listEntry.Starting = vm.Starting
if listEntry.CreatedAt.IsZero() {
listEntry.CreatedAt = time.Now()
@@ -1092,6 +1126,7 @@ func getVMInfos() ([]*machine.ListResponse, error) {
if err != nil {
return err
}
+ listEntry.Running = state == machine.Running
if !vm.LastUp.IsZero() { // this means we have already written a time to the config
listEntry.LastUp = vm.LastUp
@@ -1102,12 +1137,6 @@ func getVMInfos() ([]*machine.ListResponse, error) {
return err
}
}
- switch state {
- case machine.Running:
- listEntry.Running = true
- case machine.Starting:
- listEntry.Starting = true
- }
listed = append(listed, listEntry)
}
@@ -1314,13 +1343,19 @@ func (v *MachineVM) setPIDSocket() error {
if !rootless.IsRootless() {
rtPath = "/run"
}
- pidFileName := fmt.Sprintf("%s.pid", v.Name)
socketDir := filepath.Join(rtPath, "podman")
- pidFilePath, err := machine.NewMachineFile(filepath.Join(socketDir, pidFileName), &pidFileName)
+ vmPidFileName := fmt.Sprintf("%s_vm.pid", v.Name)
+ proxyPidFileName := fmt.Sprintf("%s_proxy.pid", v.Name)
+ vmPidFilePath, err := machine.NewMachineFile(filepath.Join(socketDir, vmPidFileName), &vmPidFileName)
+ if err != nil {
+ return err
+ }
+ proxyPidFilePath, err := machine.NewMachineFile(filepath.Join(socketDir, proxyPidFileName), &proxyPidFileName)
if err != nil {
return err
}
- v.PidFilePath = *pidFilePath
+ v.VMPidFilePath = *vmPidFilePath
+ v.PidFilePath = *proxyPidFilePath
return nil
}
@@ -1657,3 +1692,12 @@ func (p *Provider) RemoveAndCleanMachines() error {
}
return prevErr
}
+
+func isProcessAlive(pid int) bool {
+ err := unix.Kill(pid, syscall.Signal(0))
+ if err == nil || err == unix.EPERM {
+ return true
+ }
+
+ return false
+}
diff --git a/pkg/signal/signal_common.go b/pkg/signal/signal_common.go
index fe5a76dae..fc1ecc04d 100644
--- a/pkg/signal/signal_common.go
+++ b/pkg/signal/signal_common.go
@@ -2,6 +2,8 @@ package signal
import (
"fmt"
+ "os"
+ "os/signal"
"strconv"
"strings"
"syscall"
@@ -39,3 +41,18 @@ func ParseSignalNameOrNumber(rawSignal string) (syscall.Signal, error) {
}
return -1, fmt.Errorf("invalid signal: %s", basename)
}
+
+// CatchAll catches all signals and relays them to the specified channel.
+func CatchAll(sigc chan os.Signal) {
+ handledSigs := make([]os.Signal, 0, len(SignalMap))
+ for _, s := range SignalMap {
+ handledSigs = append(handledSigs, s)
+ }
+ signal.Notify(sigc, handledSigs...)
+}
+
+// StopCatch stops catching the signals and closes the specified channel.
+func StopCatch(sigc chan os.Signal) {
+ signal.Stop(sigc)
+ close(sigc)
+}
diff --git a/pkg/signal/signal_linux.go b/pkg/signal/signal_linux.go
index a114ea019..5103b6033 100644
--- a/pkg/signal/signal_linux.go
+++ b/pkg/signal/signal_linux.go
@@ -9,8 +9,6 @@ package signal
// NOTE: this package has originally been copied from github.com/docker/docker.
import (
- "os"
- "os/signal"
"syscall"
"golang.org/x/sys/unix"
@@ -91,18 +89,3 @@ var SignalMap = map[string]syscall.Signal{
"RTMAX-1": sigrtmax - 1,
"RTMAX": sigrtmax,
}
-
-// CatchAll catches all signals and relays them to the specified channel.
-func CatchAll(sigc chan os.Signal) {
- handledSigs := make([]os.Signal, 0, len(SignalMap))
- for _, s := range SignalMap {
- handledSigs = append(handledSigs, s)
- }
- signal.Notify(sigc, handledSigs...)
-}
-
-// StopCatch stops catching the signals and closes the specified channel.
-func StopCatch(sigc chan os.Signal) {
- signal.Stop(sigc)
- close(sigc)
-}
diff --git a/pkg/signal/signal_linux_mipsx.go b/pkg/signal/signal_linux_mipsx.go
index 9021a10e7..cdf9ad4c5 100644
--- a/pkg/signal/signal_linux_mipsx.go
+++ b/pkg/signal/signal_linux_mipsx.go
@@ -10,8 +10,6 @@ package signal
// NOTE: this package has originally been copied from github.com/docker/docker.
import (
- "os"
- "os/signal"
"syscall"
"golang.org/x/sys/unix"
@@ -92,18 +90,3 @@ var SignalMap = map[string]syscall.Signal{
"RTMAX-1": sigrtmax - 1,
"RTMAX": sigrtmax,
}
-
-// CatchAll catches all signals and relays them to the specified channel.
-func CatchAll(sigc chan os.Signal) {
- handledSigs := make([]os.Signal, 0, len(SignalMap))
- for _, s := range SignalMap {
- handledSigs = append(handledSigs, s)
- }
- signal.Notify(sigc, handledSigs...)
-}
-
-// StopCatch stops catching the signals and closes the specified channel.
-func StopCatch(sigc chan os.Signal) {
- signal.Stop(sigc)
- close(sigc)
-}
diff --git a/pkg/signal/signal_unix.go b/pkg/signal/signal_unix.go
index 0f43e21b7..7919e3670 100644
--- a/pkg/signal/signal_unix.go
+++ b/pkg/signal/signal_unix.go
@@ -5,7 +5,6 @@
package signal
import (
- "os"
"syscall"
)
@@ -88,13 +87,3 @@ var SignalMap = map[string]syscall.Signal{
"RTMAX-1": sigrtmax - 1,
"RTMAX": sigrtmax,
}
-
-// CatchAll catches all signals and relays them to the specified channel.
-func CatchAll(sigc chan os.Signal) {
- panic("Unsupported on non-linux platforms")
-}
-
-// StopCatch stops catching the signals and closes the specified channel.
-func StopCatch(sigc chan os.Signal) {
- panic("Unsupported on non-linux platforms")
-}
diff --git a/pkg/signal/signal_unsupported.go b/pkg/signal/signal_unsupported.go
index 9d0cee317..19ae93a61 100644
--- a/pkg/signal/signal_unsupported.go
+++ b/pkg/signal/signal_unsupported.go
@@ -5,7 +5,6 @@
package signal
import (
- "os"
"syscall"
)
@@ -88,13 +87,3 @@ var SignalMap = map[string]syscall.Signal{
"RTMAX-1": sigrtmax - 1,
"RTMAX": sigrtmax,
}
-
-// CatchAll catches all signals and relays them to the specified channel.
-func CatchAll(sigc chan os.Signal) {
- panic("Unsupported on non-linux platforms")
-}
-
-// StopCatch stops catching the signals and closes the specified channel.
-func StopCatch(sigc chan os.Signal) {
- panic("Unsupported on non-linux platforms")
-}
diff --git a/pkg/specgen/generate/container_create.go b/pkg/specgen/generate/container_create.go
index 0dec943d1..6b2e90b22 100644
--- a/pkg/specgen/generate/container_create.go
+++ b/pkg/specgen/generate/container_create.go
@@ -180,10 +180,23 @@ func MakeContainer(ctx context.Context, rt *libpod.Runtime, s *specgen.SpecGener
if err != nil {
return nil, nil, nil, err
}
+ resources := runtimeSpec.Linux.Resources
+
+ // resources get overwrritten similarly to pod inheritance, manually assign here if there is a new value
+ marshalRes, err := json.Marshal(resources)
+ if err != nil {
+ return nil, nil, nil, err
+ }
+
err = json.Unmarshal(out, runtimeSpec.Linux)
if err != nil {
return nil, nil, nil, err
}
+
+ err = json.Unmarshal(marshalRes, runtimeSpec.Linux.Resources)
+ if err != nil {
+ return nil, nil, nil, err
+ }
}
if s.ResourceLimits != nil {
switch {
diff --git a/pkg/specgen/generate/oci.go b/pkg/specgen/generate/oci.go
index 19f55c9d8..1044854f4 100644
--- a/pkg/specgen/generate/oci.go
+++ b/pkg/specgen/generate/oci.go
@@ -298,8 +298,7 @@ func SpecGenToOCI(ctx context.Context, s *specgen.SpecGenerator, rt *libpod.Runt
g.AddAnnotation(key, val)
}
- switch {
- case compatibleOptions.InfraResources == nil && s.ResourceLimits != nil:
+ if s.ResourceLimits != nil {
out, err := json.Marshal(s.ResourceLimits)
if err != nil {
return nil, err
@@ -308,29 +307,9 @@ func SpecGenToOCI(ctx context.Context, s *specgen.SpecGenerator, rt *libpod.Runt
if err != nil {
return nil, err
}
- case s.ResourceLimits != nil: // if we have predefined resource limits we need to make sure we keep the infra and container limits
- originalResources, err := json.Marshal(s.ResourceLimits)
- if err != nil {
- return nil, err
- }
- infraResources, err := json.Marshal(compatibleOptions.InfraResources)
- if err != nil {
- return nil, err
- }
- err = json.Unmarshal(infraResources, s.ResourceLimits) // put infra's resource limits in the container
- if err != nil {
- return nil, err
- }
- err = json.Unmarshal(originalResources, s.ResourceLimits) // make sure we did not override anything
- if err != nil {
- return nil, err
- }
g.Config.Linux.Resources = s.ResourceLimits
- default:
- g.Config.Linux.Resources = compatibleOptions.InfraResources
}
// Devices
-
// set the default rule at the beginning of device configuration
if !inUserNS && !s.Privileged {
g.AddLinuxResourcesDevice(false, "", nil, nil, "rwm")
diff --git a/pkg/util/mountOpts.go b/pkg/util/mountOpts.go
index e37394619..d1dd75a82 100644
--- a/pkg/util/mountOpts.go
+++ b/pkg/util/mountOpts.go
@@ -25,7 +25,7 @@ type defaultMountOptions struct {
// The sourcePath variable, if not empty, contains a bind mount source.
func ProcessOptions(options []string, isTmpfs bool, sourcePath string) ([]string, error) {
var (
- foundWrite, foundSize, foundProp, foundMode, foundExec, foundSuid, foundDev, foundCopyUp, foundBind, foundZ, foundU, foundOverlay, foundIdmap bool
+ foundWrite, foundSize, foundProp, foundMode, foundExec, foundSuid, foundDev, foundCopyUp, foundBind, foundZ, foundU, foundOverlay, foundIdmap, foundCopy bool
)
newOptions := make([]string, 0, len(options))
@@ -55,6 +55,11 @@ func ProcessOptions(options []string, isTmpfs bool, sourcePath string) ([]string
}
switch splitOpt[0] {
+ case "copy", "nocopy":
+ if foundCopy {
+ return nil, errors.Wrapf(ErrDupeMntOption, "only one of 'nocopy' and 'copy' can be used")
+ }
+ foundCopy = true
case "O":
foundOverlay = true
case "volume-opt":
diff --git a/test/apiv2/12-imagesMore.at b/test/apiv2/12-imagesMore.at
index 57d5e114d..fc18dd2d7 100644
--- a/test/apiv2/12-imagesMore.at
+++ b/test/apiv2/12-imagesMore.at
@@ -56,4 +56,17 @@ t GET libpod/images/$IMAGE/json 200 \
t DELETE libpod/images/$IMAGE 200 \
.ExitCode=0
+podman pull -q $IMAGE
+
+# test podman image SCP
+# ssh needs to work so we can validate that the failure is past argument parsing
+podman system connection add --default test ssh://$USER@localhost/run/user/$UID/podman/podman.sock
+# should fail but need to check the output...
+# status 125 here means that the save/load fails due to
+# cirrus weirdness with exec.Command. All of the args have been parsed sucessfully.
+t POST "libpod/images/scp/$IMAGE?destination=QA::" 500 \
+ .cause="exit status 125"
+t DELETE libpod/images/$IMAGE 200 \
+ .ExitCode=0
+
stop_registry
diff --git a/test/apiv2/20-containers.at b/test/apiv2/20-containers.at
index cfd6aab33..6ef4ef917 100644
--- a/test/apiv2/20-containers.at
+++ b/test/apiv2/20-containers.at
@@ -502,3 +502,27 @@ done
stop_service
start_service
+
+# Our states are different from Docker's.
+# Regression test for #14700 (Docker compat returning unknown "initialized" for status.status) to ensure the stay compatible
+podman create --name status-test $IMAGE sh -c "sleep 3"
+t GET containers/status-test/json 200 .State.Status="created"
+
+podman init status-test
+t GET containers/status-test/json 200 .State.Status="created"
+
+podman start status-test
+t GET containers/status-test/json 200 .State.Status="running"
+
+podman pause status-test
+t GET containers/status-test/json 200 .State.Status="paused"
+
+podman unpause status-test
+t GET containers/status-test/json 200 .State.Status="running"
+
+podman stop status-test &
+sleep 1
+t GET containers/status-test/json 200 .State.Status="stopping"
+
+sleep 3
+t GET containers/status-test/json 200 .State.Status="exited"
diff --git a/test/apiv2/27-containersEvents.at b/test/apiv2/27-containersEvents.at
index a86f2e353..e0a66e0ac 100644
--- a/test/apiv2/27-containersEvents.at
+++ b/test/apiv2/27-containersEvents.at
@@ -18,6 +18,10 @@ t GET "libpod/events?stream=false&since=$START" 200 \
'select(.status | contains("died")).Action=died' \
'select(.status | contains("died")).Actor.Attributes.containerExitCode=1'
+t GET "libpod/events?stream=false&since=$START" 200 \
+ 'select(.status | contains("start")).Action=start' \
+ 'select(.status | contains("start")).HealthStatus='\
+
# compat api, uses status=die (#12643)
t GET "events?stream=false&since=$START" 200 \
'select(.status | contains("start")).Action=start' \
diff --git a/test/apiv2/test-apiv2 b/test/apiv2/test-apiv2
index 25f648d93..8548d84e5 100755
--- a/test/apiv2/test-apiv2
+++ b/test/apiv2/test-apiv2
@@ -23,6 +23,8 @@ REGISTRY_IMAGE="${PODMAN_TEST_IMAGE_REGISTRY}/${PODMAN_TEST_IMAGE_USER}/registry
###############################################################################
# BEGIN setup
+USER=$PODMAN_ROOTLESS_USER
+UID=$PODMAN_ROOTLESS_UID
TMPDIR=${TMPDIR:-/tmp}
WORKDIR=$(mktemp --tmpdir -d $ME.tmp.XXXXXX)
diff --git a/test/e2e/checkpoint_test.go b/test/e2e/checkpoint_test.go
index 1fa67e9ba..be976207e 100644
--- a/test/e2e/checkpoint_test.go
+++ b/test/e2e/checkpoint_test.go
@@ -1555,6 +1555,93 @@ var _ = Describe("Podman checkpoint", func() {
os.Remove(fileName)
})
+ It("podman checkpoint container with export and verify non-default runtime", func() {
+ SkipIfRemote("podman-remote does not support --runtime flag")
+ // This test triggers the edge case where:
+ // 1. Default runtime is crun
+ // 2. Container is created with runc
+ // 3. Checkpoint without setting --runtime into archive
+ // 4. Restore without setting --runtime from archive
+ // It should be expected that podman identifies runtime
+ // from the checkpoint archive.
+
+ // Prevent --runtime arg from being set to force using default
+ // runtime unless explicitly set through passed args.
+ preservedMakeOptions := podmanTest.PodmanMakeOptions
+ podmanTest.PodmanMakeOptions = func(args []string, noEvents, noCache bool) []string {
+ defaultArgs := preservedMakeOptions(args, noEvents, noCache)
+ for i := range args {
+ // Runtime is set explicitly, so we should keep --runtime arg.
+ if args[i] == "--runtime" {
+ return defaultArgs
+ }
+ }
+ updatedArgs := make([]string, 0)
+ for i := 0; i < len(defaultArgs); i++ {
+ // Remove --runtime arg, letting podman fall back to its default
+ if defaultArgs[i] == "--runtime" {
+ i++
+ } else {
+ updatedArgs = append(updatedArgs, defaultArgs[i])
+ }
+ }
+ return updatedArgs
+ }
+
+ for _, runtime := range []string{"runc", "crun"} {
+ if err := exec.Command(runtime, "--help").Run(); err != nil {
+ Skip(fmt.Sprintf("%s not found in PATH; this test requires both runc and crun", runtime))
+ }
+ }
+
+ // Detect default runtime
+ session := podmanTest.Podman([]string{"info", "--format", "{{.Host.OCIRuntime.Name}}"})
+ session.WaitWithDefaultTimeout()
+ Expect(session).Should(Exit(0))
+ if defaultRuntime := session.OutputToString(); defaultRuntime != "crun" {
+ Skip(fmt.Sprintf("Default runtime is %q; this test requires crun to be default", defaultRuntime))
+ }
+
+ // Force non-default runtime "runc"
+ localRunString := getRunString([]string{"--runtime", "runc", "--rm", ALPINE, "top"})
+ session = podmanTest.Podman(localRunString)
+ session.WaitWithDefaultTimeout()
+ Expect(session).Should(Exit(0))
+ Expect(podmanTest.NumberOfContainersRunning()).To(Equal(1))
+ cid := session.OutputToString()
+
+ session = podmanTest.Podman([]string{"inspect", "--format", "{{.OCIRuntime}}", cid})
+ session.WaitWithDefaultTimeout()
+ Expect(session).Should(Exit(0))
+ Expect(session.OutputToString()).To(Equal("runc"))
+
+ checkpointExportPath := "/tmp/checkpoint-" + cid + ".tar.gz"
+
+ session = podmanTest.Podman([]string{"container", "checkpoint", cid, "-e", checkpointExportPath})
+ session.WaitWithDefaultTimeout()
+ // As the container has been started with '--rm' it will be completely
+ // cleaned up after checkpointing.
+ Expect(session).Should(Exit(0))
+ fixmeFixme14653(podmanTest, cid)
+ Expect(podmanTest.NumberOfContainersRunning()).To(Equal(0))
+ Expect(podmanTest.NumberOfContainers()).To(Equal(0))
+
+ session = podmanTest.Podman([]string{"container", "restore", "-i", checkpointExportPath})
+ session.WaitWithDefaultTimeout()
+ Expect(session).Should(Exit(0))
+ Expect(podmanTest.NumberOfContainersRunning()).To(Equal(1))
+ Expect(podmanTest.GetContainerStatus()).To(ContainSubstring("Up"))
+
+ // The restored container should have the same runtime as the original container
+ session = podmanTest.Podman([]string{"inspect", "--format", "{{.OCIRuntime}}", cid})
+ session.WaitWithDefaultTimeout()
+ Expect(session).Should(Exit(0))
+ Expect(session.OutputToString()).To(Equal("runc"))
+
+ // Remove exported checkpoint
+ os.Remove(checkpointExportPath)
+ })
+
It("podman checkpoint container with export and try to change the runtime", func() {
SkipIfRemote("podman-remote does not support --runtime flag")
// This test will only run if runc and crun both exist
diff --git a/test/e2e/config.go b/test/e2e/config.go
index 2ca8e2a15..fbcc9dfff 100644
--- a/test/e2e/config.go
+++ b/test/e2e/config.go
@@ -15,7 +15,7 @@ var (
healthcheck = "quay.io/libpod/alpine_healthcheck:latest"
ImageCacheDir = "/tmp/podman/imagecachedir"
fedoraToolbox = "registry.fedoraproject.org/fedora-toolbox:36"
- volumeTest = "quay.io/libpod/volume-plugin-test-img:latest"
+ volumeTest = "quay.io/libpod/volume-plugin-test-img:20220623"
// This image has seccomp profiles that blocks all syscalls.
// The intention behind blocking all syscalls is to prevent
diff --git a/test/e2e/events_test.go b/test/e2e/events_test.go
index 725118ab0..528fa143d 100644
--- a/test/e2e/events_test.go
+++ b/test/e2e/events_test.go
@@ -216,4 +216,25 @@ var _ = Describe("Podman events", func() {
Expect(result.OutputToString()).To(ContainSubstring("create"))
})
+ It("podman events health_status generated", func() {
+ session := podmanTest.Podman([]string{"run", "--name", "test-hc", "-dt", "--health-cmd", "echo working", "busybox"})
+ session.WaitWithDefaultTimeout()
+ Expect(session).Should(Exit(0))
+
+ for i := 0; i < 5; i++ {
+ hc := podmanTest.Podman([]string{"healthcheck", "run", "test-hc"})
+ hc.WaitWithDefaultTimeout()
+ exitCode := hc.ExitCode()
+ if exitCode == 0 || i == 4 {
+ break
+ }
+ time.Sleep(1 * time.Second)
+ }
+
+ result := podmanTest.Podman([]string{"events", "--stream=false", "--filter", "event=health_status"})
+ result.WaitWithDefaultTimeout()
+ Expect(result).Should(Exit(0))
+ Expect(len(result.OutputToStringArray())).To(BeNumerically(">=", 1), "Number of health_status events")
+ })
+
})
diff --git a/test/e2e/image_scp_test.go b/test/e2e/image_scp_test.go
index 53681f05b..77fe810bd 100644
--- a/test/e2e/image_scp_test.go
+++ b/test/e2e/image_scp_test.go
@@ -50,18 +50,12 @@ var _ = Describe("podman image scp", func() {
})
It("podman image scp bogus image", func() {
- if IsRemote() {
- Skip("this test is only for non-remote")
- }
scp := podmanTest.Podman([]string{"image", "scp", "FOOBAR"})
scp.WaitWithDefaultTimeout()
Expect(scp).Should(ExitWithError())
})
It("podman image scp with proper connection", func() {
- if IsRemote() {
- Skip("this test is only for non-remote")
- }
cmd := []string{"system", "connection", "add",
"--default",
"QA",
@@ -86,7 +80,10 @@ var _ = Describe("podman image scp", func() {
// This tests that the input we are given is validated and prepared correctly
// The error given should either be a missing image (due to testing suite complications) or a no such host timeout on ssh
Expect(scp).Should(ExitWithError())
- Expect(scp.ErrorToString()).Should(ContainSubstring("no such host"))
+ // podman-remote exits with a different error
+ if !IsRemote() {
+ Expect(scp.ErrorToString()).Should(ContainSubstring("no such host"))
+ }
})
diff --git a/test/e2e/manifest_test.go b/test/e2e/manifest_test.go
index 2fffc9118..06dbbb539 100644
--- a/test/e2e/manifest_test.go
+++ b/test/e2e/manifest_test.go
@@ -405,4 +405,32 @@ var _ = Describe("Podman manifest", func() {
session.WaitWithDefaultTimeout()
Expect(session).Should(Exit(0))
})
+
+ It("manifest rm should not remove image and should be able to remove tagged manifest list", func() {
+ // manifest rm should fail with `image is not a manifest list`
+ session := podmanTest.Podman([]string{"manifest", "rm", ALPINE})
+ session.WaitWithDefaultTimeout()
+ Expect(session).Should(Exit(125))
+ Expect(session.ErrorToString()).To(ContainSubstring("image is not a manifest list"))
+
+ manifestName := "testmanifest:sometag"
+ session = podmanTest.Podman([]string{"manifest", "create", manifestName})
+ session.WaitWithDefaultTimeout()
+ Expect(session).Should(Exit(0))
+
+ // verify if manifest exists
+ session = podmanTest.Podman([]string{"manifest", "exists", manifestName})
+ session.WaitWithDefaultTimeout()
+ Expect(session).Should(Exit(0))
+
+ // manifest rm should be able to remove tagged manifest list
+ session = podmanTest.Podman([]string{"manifest", "rm", manifestName})
+ session.WaitWithDefaultTimeout()
+ Expect(session).Should(Exit(0))
+
+ // verify that manifest should not exist
+ session = podmanTest.Podman([]string{"manifest", "exists", manifestName})
+ session.WaitWithDefaultTimeout()
+ Expect(session).Should(Exit(1))
+ })
})
diff --git a/test/e2e/run_volume_test.go b/test/e2e/run_volume_test.go
index edb657695..8cc2a68de 100644
--- a/test/e2e/run_volume_test.go
+++ b/test/e2e/run_volume_test.go
@@ -452,6 +452,14 @@ var _ = Describe("Podman run with volumes", func() {
separateVolumeSession.WaitWithDefaultTimeout()
Expect(separateVolumeSession).Should(Exit(0))
Expect(separateVolumeSession.OutputToString()).To(Equal(baselineOutput))
+
+ copySession := podmanTest.Podman([]string{"run", "--rm", "-v", "testvol3:/etc/apk:copy", ALPINE, "stat", "-c", "%h", "/etc/apk/arch"})
+ copySession.WaitWithDefaultTimeout()
+ Expect(copySession).Should(Exit(0))
+
+ noCopySession := podmanTest.Podman([]string{"run", "--rm", "-v", "testvol4:/etc/apk:nocopy", ALPINE, "stat", "-c", "%h", "/etc/apk/arch"})
+ noCopySession.WaitWithDefaultTimeout()
+ Expect(noCopySession).Should(Exit(1))
})
It("podman named volume copyup symlink", func() {
diff --git a/test/e2e/system_df_test.go b/test/e2e/system_df_test.go
index 5a23fc0bb..712d16a6a 100644
--- a/test/e2e/system_df_test.go
+++ b/test/e2e/system_df_test.go
@@ -70,6 +70,17 @@ var _ = Describe("podman system df", func() {
Expect(containers[1]).To(Equal("2"), "total containers expected")
Expect(volumes[2]).To(Equal("2"), "total volumes expected")
Expect(volumes[6]).To(Equal("(50%)"), "percentage usage expected")
+
+ session = podmanTest.Podman([]string{"rm", "container1"})
+ session.WaitWithDefaultTimeout()
+ Expect(session).Should(Exit(0))
+ session = podmanTest.Podman([]string{"system", "df"})
+ session.WaitWithDefaultTimeout()
+ Expect(session).Should(Exit(0))
+ volumes = strings.Fields(session.OutputToStringArray()[3])
+ // percentages on volumes were being calculated incorrectly. Make sure we only report 100% and not above
+ Expect(volumes[6]).To(Equal("(100%)"), "percentage usage expected")
+
})
It("podman system df image with no tag", func() {
diff --git a/test/e2e/system_reset_test.go b/test/e2e/system_reset_test.go
index 28f2e25ca..075ea435c 100644
--- a/test/e2e/system_reset_test.go
+++ b/test/e2e/system_reset_test.go
@@ -4,6 +4,7 @@ import (
"fmt"
"os"
+ "github.com/containers/podman/v4/pkg/rootless"
. "github.com/containers/podman/v4/test/utils"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
@@ -92,9 +93,12 @@ var _ = Describe("podman system reset", func() {
// TODO: machine tests currently don't run outside of the machine test pkg
// no machines are created here to cleanup
- session = podmanTest.Podman([]string{"machine", "list", "-q"})
- session.WaitWithDefaultTimeout()
- Expect(session).Should(Exit(0))
- Expect(session.OutputToStringArray()).To(BeEmpty())
+ // machine commands are rootless only
+ if rootless.IsRootless() {
+ session = podmanTest.Podman([]string{"machine", "list", "-q"})
+ session.WaitWithDefaultTimeout()
+ Expect(session).Should(Exit(0))
+ Expect(session.OutputToStringArray()).To(BeEmpty())
+ }
})
})
diff --git a/test/e2e/volume_ls_test.go b/test/e2e/volume_ls_test.go
index 19f87fb8a..dcfb13f4e 100644
--- a/test/e2e/volume_ls_test.go
+++ b/test/e2e/volume_ls_test.go
@@ -152,6 +152,37 @@ var _ = Describe("Podman volume ls", func() {
Expect(lsDangling).Should(Exit(0))
Expect(lsDangling.OutputToString()).To(ContainSubstring(volName1))
})
+
+ It("podman ls volume with --filter name", func() {
+ volName1 := "volume1"
+ session := podmanTest.Podman([]string{"volume", "create", volName1})
+ session.WaitWithDefaultTimeout()
+ Expect(session).Should(Exit(0))
+
+ volName2 := "volume2"
+ session2 := podmanTest.Podman([]string{"volume", "create", volName2})
+ session2.WaitWithDefaultTimeout()
+ Expect(session2).Should(Exit(0))
+
+ session = podmanTest.Podman([]string{"volume", "ls", "--filter", "name=volume1*"})
+ session.WaitWithDefaultTimeout()
+ Expect(session).Should(Exit(0))
+ Expect(session.OutputToStringArray()).To(HaveLen(3))
+ Expect(session.OutputToStringArray()[1]).To(ContainSubstring(volName1))
+ Expect(session.OutputToStringArray()[2]).To(ContainSubstring(volName2))
+
+ session = podmanTest.Podman([]string{"volume", "ls", "--filter", "name=volumex"})
+ session.WaitWithDefaultTimeout()
+ Expect(session).Should(Exit(0))
+ Expect(session.OutputToStringArray()).To(BeEmpty())
+
+ session = podmanTest.Podman([]string{"volume", "ls", "--filter", "name=volume1"})
+ session.WaitWithDefaultTimeout()
+ Expect(session).Should(Exit(0))
+ Expect(session.OutputToStringArray()).To(HaveLen(2))
+ Expect(session.OutputToStringArray()[1]).To(ContainSubstring(volName1))
+ })
+
It("podman ls volume with multiple --filter flag", func() {
session := podmanTest.Podman([]string{"volume", "create", "--label", "foo=bar", "myvol"})
volName := session.OutputToString()
diff --git a/test/e2e/volume_plugin_test.go b/test/e2e/volume_plugin_test.go
index 4700afdb5..b585f8dd8 100644
--- a/test/e2e/volume_plugin_test.go
+++ b/test/e2e/volume_plugin_test.go
@@ -6,6 +6,7 @@ import (
"path/filepath"
. "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"
@@ -188,4 +189,71 @@ var _ = Describe("Podman volume plugins", func() {
rmAll.WaitWithDefaultTimeout()
Expect(rmAll).Should(Exit(0))
})
+
+ It("podman volume reload", func() {
+ podmanTest.AddImageToRWStore(volumeTest)
+
+ confFile := filepath.Join(podmanTest.TempDir, "containers.conf")
+ err := os.WriteFile(confFile, []byte(`[engine]
+[engine.volume_plugins]
+testvol5 = "/run/docker/plugins/testvol5.sock"`), 0o644)
+ Expect(err).ToNot(HaveOccurred())
+ os.Setenv("CONTAINERS_CONF", confFile)
+
+ pluginStatePath := filepath.Join(podmanTest.TempDir, "volumes")
+ err = os.Mkdir(pluginStatePath, 0755)
+ Expect(err).ToNot(HaveOccurred())
+
+ // Keep this distinct within tests to avoid multiple tests using the same plugin.
+ pluginName := "testvol5"
+ ctrName := "pluginCtr"
+ plugin := podmanTest.Podman([]string{"run", "--name", ctrName, "--security-opt", "label=disable", "-v", "/run/docker/plugins:/run/docker/plugins",
+ "-v", fmt.Sprintf("%v:%v", pluginStatePath, pluginStatePath), "-d", volumeTest, "--sock-name", pluginName, "--path", pluginStatePath})
+ plugin.WaitWithDefaultTimeout()
+ Expect(plugin).Should(Exit(0))
+
+ localvol := "local-" + stringid.GenerateNonCryptoID()
+ // create local volume
+ session := podmanTest.Podman([]string{"volume", "create", localvol})
+ session.WaitWithDefaultTimeout()
+ Expect(session).To(Exit(0))
+
+ vol1 := "vol1-" + stringid.GenerateNonCryptoID()
+ session = podmanTest.Podman([]string{"volume", "create", "--driver", pluginName, vol1})
+ session.WaitWithDefaultTimeout()
+ Expect(session).To(Exit(0))
+
+ // now create volume in plugin without podman
+ vol2 := "vol2-" + stringid.GenerateNonCryptoID()
+ plugin = podmanTest.Podman([]string{"exec", ctrName, "/usr/local/bin/testvol", "--sock-name", pluginName, "create", vol2})
+ plugin.WaitWithDefaultTimeout()
+ Expect(plugin).Should(Exit(0))
+
+ session = podmanTest.Podman([]string{"volume", "ls", "-q"})
+ session.WaitWithDefaultTimeout()
+ Expect(session).To(Exit(0))
+ Expect(session.OutputToStringArray()).To(ContainElements(localvol, vol1))
+ Expect(session.ErrorToString()).To(Equal("")) // make sure no errors are shown
+
+ plugin = podmanTest.Podman([]string{"exec", ctrName, "/usr/local/bin/testvol", "--sock-name", pluginName, "remove", vol1})
+ plugin.WaitWithDefaultTimeout()
+ Expect(plugin).Should(Exit(0))
+
+ // now reload volumes from plugins
+ session = podmanTest.Podman([]string{"volume", "reload"})
+ session.WaitWithDefaultTimeout()
+ Expect(session).To(Exit(0))
+ Expect(string(session.Out.Contents())).To(Equal(fmt.Sprintf(`Added:
+%s
+Removed:
+%s
+`, vol2, vol1)))
+ Expect(session.ErrorToString()).To(Equal("")) // make sure no errors are shown
+
+ session = podmanTest.Podman([]string{"volume", "ls", "-q"})
+ session.WaitWithDefaultTimeout()
+ Expect(session).To(Exit(0))
+ Expect(session.OutputToStringArray()).To(ContainElements(localvol, vol2))
+ Expect(session.ErrorToString()).To(Equal("")) // make no errors are shown
+ })
})
diff --git a/test/system/050-stop.bats b/test/system/050-stop.bats
index c2dfba84d..39002512b 100644
--- a/test/system/050-stop.bats
+++ b/test/system/050-stop.bats
@@ -171,4 +171,19 @@ load helpers
run_podman --noout stop -t 0 stopme
is "$output" "" "output should be empty"
}
+
+@test "podman stop, with --rm container" {
+ OCIDir=/run/$(podman_runtime)
+
+ if is_rootless; then
+ OCIDir=/run/user/$(id -u)/$(podman_runtime)
+ fi
+
+ run_podman run --rm -d --name rmstop $IMAGE sleep infinity
+ local cid="$output"
+ run_podman stop rmstop
+
+ # Check the OCI runtime directory has removed.
+ is "$(ls $OCIDir | grep $cid)" "" "The OCI runtime directory should have been removed"
+}
# vim: filetype=sh
diff --git a/test/system/055-rm.bats b/test/system/055-rm.bats
index 69663fafa..0ef2216b8 100644
--- a/test/system/055-rm.bats
+++ b/test/system/055-rm.bats
@@ -52,10 +52,20 @@ load helpers
}
@test "podman rm <-> run --rm race" {
+ OCIDir=/run/$(podman_runtime)
+
+ if is_rootless; then
+ OCIDir=/run/user/$(id -u)/$(podman_runtime)
+ fi
+
# A container's lock is released before attempting to stop it. This opens
# the window for race conditions that led to #9479.
run_podman run --rm -d $IMAGE sleep infinity
+ local cid="$output"
run_podman rm -af
+
+ # Check the OCI runtime directory has removed.
+ is "$(ls $OCIDir | grep $cid)" "" "The OCI runtime directory should have been removed"
}
@test "podman rm --depend" {
diff --git a/test/system/120-load.bats b/test/system/120-load.bats
index 5a7f63b43..7f0bcfd95 100644
--- a/test/system/120-load.bats
+++ b/test/system/120-load.bats
@@ -128,8 +128,24 @@ verify_iid_and_name() {
run_podman image inspect --format '{{.Digest}}' $newname
is "$output" "$src_digest" "Digest of re-fetched image matches original"
- # Clean up
+ # test tagging capability
+ run_podman untag $IMAGE $newname
+ run_podman image scp ${notme}@localhost::$newname foobar:123
+
+ run_podman image inspect --format '{{.Digest}}' foobar:123
+ is "$output" "$src_digest" "Digest of re-fetched image matches original"
+
+ # remove root img for transfer back with another name
_sudo $PODMAN image rm $newname
+
+ # get foobar's ID, for an ID transfer test
+ run_podman image inspect --format '{{.ID}}' foobar:123
+ run_podman image scp $output ${notme}@localhost::foobartwo
+
+ _sudo $PODMAN image exists foobartwo
+
+ # Clean up
+ _sudo $PODMAN image rm foobartwo
run_podman untag $IMAGE $newname
# Negative test for nonexistent image.
@@ -142,12 +158,6 @@ verify_iid_and_name() {
run_podman 125 image scp $nope ${notme}@localhost::
is "$output" "Error: $nope: image not known.*" "Pushing nonexistent image"
- # Negative test for copying to a different name
- run_podman 125 image scp $IMAGE ${notme}@localhost::newname:newtag
- is "$output" "Error: cannot specify an image rename: invalid argument" \
- "Pushing with a different name: not allowed"
-
- # FIXME: any point in copying by image ID? What else should we test?
}
diff --git a/test/system/150-login.bats b/test/system/150-login.bats
index 33b8438bf..dc902d5fe 100644
--- a/test/system/150-login.bats
+++ b/test/system/150-login.bats
@@ -314,7 +314,7 @@ function _test_skopeo_credential_sharing() {
fi
# Make sure socket is closed
- if { exec 3<> /dev/tcp/127.0.0.1/${PODMAN_LOGIN_REGISTRY_PORT}; } &>/dev/null; then
+ if ! port_is_free $PODMAN_LOGIN_REGISTRY_PORT; then
die "Socket still seems open"
fi
}
diff --git a/test/system/160-volumes.bats b/test/system/160-volumes.bats
index 797883ec6..da60112a0 100644
--- a/test/system/160-volumes.bats
+++ b/test/system/160-volumes.bats
@@ -64,6 +64,29 @@ function teardown() {
}
+# Filter volumes by name
+@test "podman volume filter --name" {
+ suffix=$(random_string)
+ prefix="volume"
+
+ for i in 1 2; do
+ myvolume=${prefix}_${i}_${suffix}
+ run_podman volume create $myvolume
+ is "$output" "$myvolume" "output from volume create $i"
+ done
+
+ run_podman volume ls --filter name=${prefix}_1.+ --format "{{.Name}}"
+ is "$output" "${prefix}_1_${suffix}" "--filter name=${prefix}_1.+ shows only one volume"
+
+ # The _1* is intentional as asterisk has different meaning in glob and regexp. Make sure this is regexp
+ run_podman volume ls --filter name=${prefix}_1* --format "{{.Name}}"
+ is "$output" "${prefix}_1_${suffix}.*${prefix}_2_${suffix}.*" "--filter name=${prefix}_1* shows ${prefix}_1_${suffix} and ${prefix}_2_${suffix}"
+
+ for i in 1 2; do
+ run_podman volume rm ${prefix}_${i}_${suffix}
+ done
+}
+
# Named volumes
@test "podman volume create / run" {
myvolume=myvol$(random_string)
diff --git a/test/system/200-pod.bats b/test/system/200-pod.bats
index 404ad67ec..92d3966be 100644
--- a/test/system/200-pod.bats
+++ b/test/system/200-pod.bats
@@ -472,4 +472,45 @@ spec:
run_podman pod rm $name-pod
}
+@test "pod resource limits" {
+ skip_if_remote "resource limits only implemented on non-remote"
+ if is_rootless; then
+ skip "only meaningful for rootful"
+ fi
+
+ local name1="resources1"
+ run_podman --cgroup-manager=systemd pod create --name=$name1 --cpus=5
+ run_podman --cgroup-manager=systemd pod start $name1
+ run_podman pod inspect --format '{{.CgroupPath}}' $name1
+ local path1="$output"
+ local actual1=$(< /sys/fs/cgroup/$path1/cpu.max)
+ is "$actual1" "500000 100000" "resource limits set properly"
+ run_podman pod --cgroup-manager=systemd rm -f $name1
+
+ local name2="resources2"
+ run_podman --cgroup-manager=cgroupfs pod create --cpus=5 --name=$name2
+ run_podman --cgroup-manager=cgroupfs pod start $name2
+ run_podman pod inspect --format '{{.CgroupPath}}' $name2
+ local path2="$output"
+ local actual2=$(< /sys/fs/cgroup/$path2/cpu.max)
+ is "$actual2" "500000 100000" "resource limits set properly"
+ run_podman --cgroup-manager=cgroupfs pod rm $name2
+}
+
+@test "podman pod ps doesn't race with pod rm" {
+ # create a few pods
+ for i in {0..10}; do
+ run_podman pod create
+ done
+
+ # and delete them
+ $PODMAN pod rm -a &
+
+ # pod ps should not fail while pods are deleted
+ run_podman pod ps -q
+
+ # wait for pod rm -a
+ wait
+}
+
# vim: filetype=sh
diff --git a/test/system/500-networking.bats b/test/system/500-networking.bats
index fb785177c..0d724985e 100644
--- a/test/system/500-networking.bats
+++ b/test/system/500-networking.bats
@@ -676,12 +676,12 @@ EOF
@test "podman run port forward range" {
for netmode in bridge slirp4netns:port_handler=slirp4netns slirp4netns:port_handler=rootlesskit; do
- local port=$(random_free_port)
- local end_port=$(( $port + 2 ))
- local range="$port-$end_port:$port-$end_port"
+ local range=$(random_free_port_range 3)
+ local port="${test%-*}"
+ local end_port="${test#-*}"
local random=$(random_string)
- run_podman run --network $netmode -p "$range" -d $IMAGE sleep inf
+ run_podman run --network $netmode -p "$range:$range" -d $IMAGE sleep inf
cid="$output"
for port in $(seq $port $end_port); do
run_podman exec -d $cid nc -l -p $port -e /bin/cat
diff --git a/test/system/helpers.bash b/test/system/helpers.bash
index 74b5ddc4b..273e8d2f5 100644
--- a/test/system/helpers.bash
+++ b/test/system/helpers.bash
@@ -284,7 +284,7 @@ function random_free_port() {
local port
for port in $(shuf -i ${range}); do
- if ! { exec {unused_fd}<> /dev/tcp/127.0.0.1/$port; } &>/dev/null; then
+ if port_is_free $port; then
echo $port
return
fi
@@ -293,6 +293,35 @@ function random_free_port() {
die "Could not find open port in range $range"
}
+function random_free_port_range() {
+ local size=${1?Usage: random_free_port_range SIZE (as in, number of ports)}
+
+ local maxtries=10
+ while [[ $maxtries -gt 0 ]]; do
+ local firstport=$(random_free_port)
+ local all_ports_free=1
+ for i in $(seq 2 $size); do
+ if ! port_is_free $((firstport + $i)); then
+ all_ports_free=
+ break
+ fi
+ done
+ if [[ -n "$all_ports_free" ]]; then
+ echo "$firstport-$((firstport + $size - 1))"
+ return
+ fi
+
+ maxtries=$((maxtries - 1))
+ done
+
+ die "Could not find free port range with size $size"
+}
+
+function port_is_free() {
+ local port=${1?Usage: port_is_free PORT}
+ ! { exec {unused_fd}<> /dev/tcp/127.0.0.1/$port; } &>/dev/null
+}
+
###################
# wait_for_port # Returns once port is available on host
###################
diff --git a/test/testvol/Containerfile b/test/testvol/Containerfile
new file mode 100644
index 000000000..32448f5a9
--- /dev/null
+++ b/test/testvol/Containerfile
@@ -0,0 +1,9 @@
+FROM docker.io/library/golang:1.18-alpine AS build-img
+COPY ./ /go/src/github.com/containers/podman/
+WORKDIR /go/src/github.com/containers/podman
+RUN GO111MODULE=off go build -o /testvol ./test/testvol
+
+FROM alpine
+COPY --from=build-img /testvol /usr/local/bin
+WORKDIR /
+ENTRYPOINT ["/usr/local/bin/testvol", "serve"]
diff --git a/test/testvol/create.go b/test/testvol/create.go
new file mode 100644
index 000000000..d29300f0b
--- /dev/null
+++ b/test/testvol/create.go
@@ -0,0 +1,26 @@
+package main
+
+import (
+ pluginapi "github.com/docker/go-plugins-helpers/volume"
+ "github.com/spf13/cobra"
+)
+
+var createCmd = &cobra.Command{
+ Use: "create NAME",
+ Short: "create a volume",
+ Long: `Create a volume in the volume plugin listening on --sock-name`,
+ Args: cobra.ExactArgs(1),
+ RunE: func(cmd *cobra.Command, args []string) error {
+ return createVol(config.sockName, args[0])
+ },
+}
+
+func createVol(sockName, volName string) error {
+ plugin, err := getPlugin(sockName)
+ if err != nil {
+ return err
+ }
+ createReq := new(pluginapi.CreateRequest)
+ createReq.Name = volName
+ return plugin.CreateVolume(createReq)
+}
diff --git a/test/testvol/list.go b/test/testvol/list.go
new file mode 100644
index 000000000..fea615a70
--- /dev/null
+++ b/test/testvol/list.go
@@ -0,0 +1,32 @@
+package main
+
+import (
+ "fmt"
+
+ "github.com/spf13/cobra"
+)
+
+var listCmd = &cobra.Command{
+ Use: "list",
+ Short: "list all volumes",
+ Long: `List all volumes from the volume plugin listening on --sock-name`,
+ Args: cobra.NoArgs,
+ RunE: func(cmd *cobra.Command, args []string) error {
+ return listVol(config.sockName)
+ },
+}
+
+func listVol(sockName string) error {
+ plugin, err := getPlugin(sockName)
+ if err != nil {
+ return err
+ }
+ vols, err := plugin.ListVolumes()
+ if err != nil {
+ return err
+ }
+ for _, vol := range vols {
+ fmt.Println(vol.Name)
+ }
+ return nil
+}
diff --git a/test/testvol/main.go b/test/testvol/main.go
index 30ab365b3..99c6fb694 100644
--- a/test/testvol/main.go
+++ b/test/testvol/main.go
@@ -14,13 +14,20 @@ import (
)
var rootCmd = &cobra.Command{
- Use: "testvol",
- Short: "testvol - volume plugin for Podman",
+ Use: "testvol",
+ Short: "testvol - volume plugin for Podman testing",
+ PersistentPreRunE: before,
+ SilenceUsage: true,
+}
+
+var serveCmd = &cobra.Command{
+ Use: "serve",
+ Short: "serve the volume plugin on the unix socket",
Long: `Creates simple directory volumes using the Volume Plugin API for testing volume plugin functionality`,
+ Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error {
return startServer(config.sockName)
},
- PersistentPreRunE: before,
}
// Configuration for the volume plugin
@@ -37,9 +44,12 @@ var config = cliConfig{
}
func init() {
- rootCmd.Flags().StringVar(&config.sockName, "sock-name", config.sockName, "Name of unix socket for plugin")
- rootCmd.Flags().StringVar(&config.path, "path", "", "Path to initialize state and mount points")
+ rootCmd.PersistentFlags().StringVar(&config.sockName, "sock-name", config.sockName, "Name of unix socket for plugin")
rootCmd.PersistentFlags().StringVar(&config.logLevel, "log-level", config.logLevel, "Log messages including and over the specified level: debug, info, warn, error, fatal, panic")
+
+ serveCmd.Flags().StringVar(&config.path, "path", "", "Path to initialize state and mount points")
+
+ rootCmd.AddCommand(serveCmd, createCmd, removeCmd, listCmd)
}
func before(cmd *cobra.Command, args []string) error {
@@ -59,11 +69,8 @@ func before(cmd *cobra.Command, args []string) error {
func main() {
if err := rootCmd.Execute(); err != nil {
- logrus.Errorf("Running volume plugin: %v", err)
os.Exit(1)
}
-
- os.Exit(0)
}
// startServer runs the HTTP server and responds to requests
diff --git a/test/testvol/remove.go b/test/testvol/remove.go
new file mode 100644
index 000000000..2839b0b50
--- /dev/null
+++ b/test/testvol/remove.go
@@ -0,0 +1,26 @@
+package main
+
+import (
+ pluginapi "github.com/docker/go-plugins-helpers/volume"
+ "github.com/spf13/cobra"
+)
+
+var removeCmd = &cobra.Command{
+ Use: "remove NAME",
+ Short: "remove a volume",
+ Long: `Remove a volume in the volume plugin listening on --sock-name`,
+ Args: cobra.ExactArgs(1),
+ RunE: func(cmd *cobra.Command, args []string) error {
+ return removeVol(config.sockName, args[0])
+ },
+}
+
+func removeVol(sockName, volName string) error {
+ plugin, err := getPlugin(sockName)
+ if err != nil {
+ return err
+ }
+ removeReq := new(pluginapi.RemoveRequest)
+ removeReq.Name = volName
+ return plugin.RemoveVolume(removeReq)
+}
diff --git a/test/testvol/util.go b/test/testvol/util.go
new file mode 100644
index 000000000..7a0aeba86
--- /dev/null
+++ b/test/testvol/util.go
@@ -0,0 +1,29 @@
+package main
+
+import (
+ "path/filepath"
+ "strings"
+
+ "github.com/containers/podman/v4/libpod/plugin"
+)
+
+const pluginSockDir = "/run/docker/plugins"
+
+func getSocketPath(pathOrName string) string {
+ if filepath.IsAbs(pathOrName) {
+ return pathOrName
+ }
+
+ // only a name join it with the default path
+ return filepath.Join(pluginSockDir, pathOrName+".sock")
+}
+
+func getPluginName(pathOrName string) string {
+ return strings.TrimSuffix(filepath.Base(pathOrName), ".sock")
+}
+
+func getPlugin(sockNameOrPath string) (*plugin.VolumePlugin, error) {
+ path := getSocketPath(sockNameOrPath)
+ name := getPluginName(sockNameOrPath)
+ return plugin.GetVolumePlugin(name, path)
+}
diff --git a/utils/utils.go b/utils/utils.go
index fd66ac2ed..9239cf907 100644
--- a/utils/utils.go
+++ b/utils/utils.go
@@ -243,27 +243,3 @@ func MovePauseProcessToScope(pausePidPath string) {
}
}
}
-
-// CreateSCPCommand takes an existing command, appends the given arguments and returns a configured podman command for image scp
-func CreateSCPCommand(cmd *exec.Cmd, command []string) *exec.Cmd {
- cmd.Args = append(cmd.Args, command...)
- cmd.Env = os.Environ()
- cmd.Stderr = os.Stderr
- cmd.Stdout = os.Stdout
- return cmd
-}
-
-// LoginUser starts the user process on the host so that image scp can use systemd-run
-func LoginUser(user string) (*exec.Cmd, error) {
- sleep, err := exec.LookPath("sleep")
- if err != nil {
- return nil, err
- }
- machinectl, err := exec.LookPath("machinectl")
- if err != nil {
- return nil, err
- }
- cmd := exec.Command(machinectl, "shell", "-q", user+"@.host", sleep, "inf")
- err = cmd.Start()
- return cmd, err
-}
diff --git a/utils/utils_supported.go b/utils/utils_supported.go
index c2dcc4631..6378212b6 100644
--- a/utils/utils_supported.go
+++ b/utils/utils_supported.go
@@ -137,7 +137,7 @@ func moveUnderCgroup(cgroup, subtree string, processes []uint32) error {
}
// root cgroup, skip it
- if parts[2] == "/" {
+ if parts[2] == "/" && !(unifiedMode && parts[1] == "") {
continue
}
diff --git a/vendor/github.com/containers/common/libimage/normalize.go b/vendor/github.com/containers/common/libimage/normalize.go
index b36bbf396..7af125283 100644
--- a/vendor/github.com/containers/common/libimage/normalize.go
+++ b/vendor/github.com/containers/common/libimage/normalize.go
@@ -1,51 +1,13 @@
package libimage
import (
- "runtime"
"strings"
- "github.com/containerd/containerd/platforms"
"github.com/containers/image/v5/docker/reference"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
)
-// NormalizePlatform normalizes (according to the OCI spec) the specified os,
-// arch and variant. If left empty, the individual item will not be normalized.
-func NormalizePlatform(rawOS, rawArch, rawVariant string) (os, arch, variant string) {
- os, arch, variant = rawOS, rawArch, rawVariant
- if os == "" {
- os = runtime.GOOS
- }
- if arch == "" {
- arch = runtime.GOARCH
- }
- rawPlatform := os + "/" + arch
- if variant != "" {
- rawPlatform += "/" + variant
- }
-
- normalizedPlatform, err := platforms.Parse(rawPlatform)
- if err != nil {
- logrus.Debugf("Error normalizing platform: %v", err)
- return rawOS, rawArch, rawVariant
- }
- logrus.Debugf("Normalized platform %s to %s", rawPlatform, normalizedPlatform)
- os = rawOS
- if rawOS != "" {
- os = normalizedPlatform.OS
- }
- arch = rawArch
- if rawArch != "" {
- arch = normalizedPlatform.Architecture
- }
- variant = rawVariant
- if rawVariant != "" {
- variant = normalizedPlatform.Variant
- }
- return os, arch, variant
-}
-
// NormalizeName normalizes the provided name according to the conventions by
// Podman and Buildah. If tag and digest are missing, the "latest" tag will be
// used. If it's a short name, it will be prefixed with "localhost/".
diff --git a/vendor/github.com/containers/common/libimage/platform.go b/vendor/github.com/containers/common/libimage/platform.go
index 8b78bce24..736a193f6 100644
--- a/vendor/github.com/containers/common/libimage/platform.go
+++ b/vendor/github.com/containers/common/libimage/platform.go
@@ -4,6 +4,9 @@ import (
"context"
"fmt"
"runtime"
+
+ "github.com/containerd/containerd/platforms"
+ "github.com/sirupsen/logrus"
)
// PlatformPolicy controls the behavior of image-platform matching.
@@ -16,11 +19,42 @@ const (
PlatformPolicyWarn
)
-func toPlatformString(architecture, os, variant string) string {
+// NormalizePlatform normalizes (according to the OCI spec) the specified os,
+// arch and variant. If left empty, the individual item will not be normalized.
+func NormalizePlatform(rawOS, rawArch, rawVariant string) (os, arch, variant string) {
+ rawPlatform := toPlatformString(rawOS, rawArch, rawVariant)
+ normalizedPlatform, err := platforms.Parse(rawPlatform)
+ if err != nil {
+ logrus.Debugf("Error normalizing platform: %v", err)
+ return rawOS, rawArch, rawVariant
+ }
+ logrus.Debugf("Normalized platform %s to %s", rawPlatform, normalizedPlatform)
+ os = rawOS
+ if rawOS != "" {
+ os = normalizedPlatform.OS
+ }
+ arch = rawArch
+ if rawArch != "" {
+ arch = normalizedPlatform.Architecture
+ }
+ variant = rawVariant
+ if rawVariant != "" {
+ variant = normalizedPlatform.Variant
+ }
+ return os, arch, variant
+}
+
+func toPlatformString(os, arch, variant string) string {
+ if os == "" {
+ os = runtime.GOOS
+ }
+ if arch == "" {
+ arch = runtime.GOARCH
+ }
if variant == "" {
- return fmt.Sprintf("%s/%s", os, architecture)
+ return fmt.Sprintf("%s/%s", os, arch)
}
- return fmt.Sprintf("%s/%s/%s", os, architecture, variant)
+ return fmt.Sprintf("%s/%s/%s", os, arch, variant)
}
// Checks whether the image matches the specified platform.
@@ -28,36 +62,26 @@ func toPlatformString(architecture, os, variant string) string {
// * 1) a matching error that can be used for logging (or returning) what does not match
// * 2) a bool indicating whether architecture, os or variant were set (some callers need that to decide whether they need to throw an error)
// * 3) a fatal error that occurred prior to check for matches (e.g., storage errors etc.)
-func (i *Image) matchesPlatform(ctx context.Context, architecture, os, variant string) (error, bool, error) {
- customPlatform := len(architecture)+len(os)+len(variant) != 0
-
- if len(architecture) == 0 {
- architecture = runtime.GOARCH
- }
- if len(os) == 0 {
- os = runtime.GOOS
- }
-
+func (i *Image) matchesPlatform(ctx context.Context, os, arch, variant string) (error, bool, error) {
inspectInfo, err := i.inspectInfo(ctx)
if err != nil {
- return nil, customPlatform, fmt.Errorf("inspecting image: %w", err)
+ return nil, false, fmt.Errorf("inspecting image: %w", err)
}
- matches := true
- switch {
- case architecture != inspectInfo.Architecture:
- matches = false
- case os != inspectInfo.Os:
- matches = false
- case variant != "" && variant != inspectInfo.Variant:
- matches = false
+ customPlatform := len(os)+len(arch)+len(variant) != 0
+
+ expected, err := platforms.Parse(toPlatformString(os, arch, variant))
+ if err != nil {
+ return nil, false, fmt.Errorf("parsing host platform: %v", err)
+ }
+ fromImage, err := platforms.Parse(toPlatformString(inspectInfo.Os, inspectInfo.Architecture, inspectInfo.Variant))
+ if err != nil {
+ return nil, false, fmt.Errorf("parsing image platform: %v", err)
}
- if matches {
+ if platforms.NewMatcher(expected).Match(fromImage) {
return nil, customPlatform, nil
}
- imagePlatform := toPlatformString(inspectInfo.Architecture, inspectInfo.Os, inspectInfo.Variant)
- expectedPlatform := toPlatformString(architecture, os, variant)
- return fmt.Errorf("image platform (%s) does not match the expected platform (%s)", imagePlatform, expectedPlatform), customPlatform, nil
+ return fmt.Errorf("image platform (%s) does not match the expected platform (%s)", fromImage, expected), customPlatform, nil
}
diff --git a/vendor/github.com/containers/common/libimage/pull.go b/vendor/github.com/containers/common/libimage/pull.go
index 5e743574c..2071cceca 100644
--- a/vendor/github.com/containers/common/libimage/pull.go
+++ b/vendor/github.com/containers/common/libimage/pull.go
@@ -169,7 +169,7 @@ func (r *Runtime) Pull(ctx context.Context, name string, pullPolicy config.PullP
// Note that we can ignore the 2nd return value here. Some
// images may ship with "wrong" platform, but we already warn
// about it. Throwing an error is not (yet) the plan.
- matchError, _, err := image.matchesPlatform(ctx, options.Architecture, options.OS, options.Variant)
+ matchError, _, err := image.matchesPlatform(ctx, options.OS, options.Architecture, options.Variant)
if err != nil {
return nil, fmt.Errorf("checking platform of image %s: %w", name, err)
}
diff --git a/vendor/github.com/containers/common/libimage/runtime.go b/vendor/github.com/containers/common/libimage/runtime.go
index efae2238d..7e975b81d 100644
--- a/vendor/github.com/containers/common/libimage/runtime.go
+++ b/vendor/github.com/containers/common/libimage/runtime.go
@@ -396,7 +396,7 @@ func (r *Runtime) lookupImageInLocalStorage(name, candidate string, options *Loo
// Ignore the (fatal) error since the image may be corrupted, which
// will bubble up at other places. During lookup, we just return it as
// is.
- if matchError, customPlatform, _ := image.matchesPlatform(context.Background(), options.Architecture, options.OS, options.Variant); matchError != nil {
+ if matchError, customPlatform, _ := image.matchesPlatform(context.Background(), options.OS, options.Architecture, options.Variant); matchError != nil {
if customPlatform {
logrus.Debugf("%v", matchError)
// Return nil if the user clearly requested a custom
diff --git a/vendor/github.com/containers/common/pkg/cgroups/blkio.go b/vendor/github.com/containers/common/pkg/cgroups/blkio.go
index 0fb61c757..a72a641c8 100644
--- a/vendor/github.com/containers/common/pkg/cgroups/blkio.go
+++ b/vendor/github.com/containers/common/pkg/cgroups/blkio.go
@@ -1,3 +1,6 @@
+//go:build !linux
+// +build !linux
+
package cgroups
import (
diff --git a/vendor/github.com/containers/common/pkg/cgroups/blkio_linux.go b/vendor/github.com/containers/common/pkg/cgroups/blkio_linux.go
new file mode 100644
index 000000000..98b8ae541
--- /dev/null
+++ b/vendor/github.com/containers/common/pkg/cgroups/blkio_linux.go
@@ -0,0 +1,161 @@
+//go:build linux
+// +build linux
+
+package cgroups
+
+import (
+ "bufio"
+ "os"
+ "path/filepath"
+ "strconv"
+ "strings"
+
+ "github.com/opencontainers/runc/libcontainer/cgroups"
+ "github.com/opencontainers/runc/libcontainer/cgroups/fs"
+ "github.com/opencontainers/runc/libcontainer/cgroups/fs2"
+ "github.com/opencontainers/runc/libcontainer/configs"
+ "github.com/pkg/errors"
+)
+
+type linuxBlkioHandler struct {
+ Blkio fs.BlkioGroup
+}
+
+func getBlkioHandler() *linuxBlkioHandler {
+ return &linuxBlkioHandler{}
+}
+
+// Apply set the specified constraints
+func (c *linuxBlkioHandler) Apply(ctr *CgroupControl, res *configs.Resources) error {
+ if ctr.cgroup2 {
+ man, err := fs2.NewManager(ctr.config, filepath.Join(cgroupRoot, ctr.config.Path))
+ if err != nil {
+ return err
+ }
+ return man.Set(res)
+
+ }
+ path := filepath.Join(cgroupRoot, Blkio, ctr.config.Path)
+ return c.Blkio.Set(path, res)
+}
+
+// Create the cgroup
+func (c *linuxBlkioHandler) Create(ctr *CgroupControl) (bool, error) {
+ if ctr.cgroup2 {
+ return false, nil
+ }
+ return ctr.createCgroupDirectory(Blkio)
+}
+
+// Destroy the cgroup
+func (c *linuxBlkioHandler) Destroy(ctr *CgroupControl) error {
+ return rmDirRecursively(ctr.getCgroupv1Path(Blkio))
+}
+
+// Stat fills a metrics structure with usage stats for the controller
+func (c *linuxBlkioHandler) Stat(ctr *CgroupControl, m *cgroups.Stats) error {
+ var ioServiceBytesRecursive []cgroups.BlkioStatEntry
+
+ if ctr.cgroup2 {
+ // more details on the io.stat file format:X https://facebookmicrosites.github.io/cgroup2/docs/io-controller.html
+ values, err := readCgroup2MapFile(ctr, "io.stat")
+ if err != nil {
+ return err
+ }
+ for k, v := range values {
+ d := strings.Split(k, ":")
+ if len(d) != 2 {
+ continue
+ }
+ minor, err := strconv.ParseUint(d[0], 10, 0)
+ if err != nil {
+ return err
+ }
+ major, err := strconv.ParseUint(d[1], 10, 0)
+ if err != nil {
+ return err
+ }
+
+ for _, item := range v {
+ d := strings.Split(item, "=")
+ if len(d) != 2 {
+ continue
+ }
+ op := d[0]
+
+ // Accommodate the cgroup v1 naming
+ switch op {
+ case "rbytes":
+ op = "read"
+ case "wbytes":
+ op = "write"
+ }
+
+ value, err := strconv.ParseUint(d[1], 10, 0)
+ if err != nil {
+ return err
+ }
+
+ entry := cgroups.BlkioStatEntry{
+ Op: op,
+ Major: major,
+ Minor: minor,
+ Value: value,
+ }
+ ioServiceBytesRecursive = append(ioServiceBytesRecursive, entry)
+ }
+ }
+ } else {
+ BlkioRoot := ctr.getCgroupv1Path(Blkio)
+
+ p := filepath.Join(BlkioRoot, "blkio.throttle.io_service_bytes_recursive")
+ f, err := os.Open(p)
+ if err != nil {
+ if os.IsNotExist(err) {
+ return nil
+ }
+ return errors.Wrapf(err, "open %s", p)
+ }
+ defer f.Close()
+
+ scanner := bufio.NewScanner(f)
+ for scanner.Scan() {
+ line := scanner.Text()
+ parts := strings.Fields(line)
+ if len(parts) < 3 {
+ continue
+ }
+ d := strings.Split(parts[0], ":")
+ if len(d) != 2 {
+ continue
+ }
+ minor, err := strconv.ParseUint(d[0], 10, 0)
+ if err != nil {
+ return err
+ }
+ major, err := strconv.ParseUint(d[1], 10, 0)
+ if err != nil {
+ return err
+ }
+
+ op := parts[1]
+
+ value, err := strconv.ParseUint(parts[2], 10, 0)
+ if err != nil {
+ return err
+ }
+ entry := cgroups.BlkioStatEntry{
+ Op: op,
+ Major: major,
+ Minor: minor,
+ Value: value,
+ }
+ ioServiceBytesRecursive = append(ioServiceBytesRecursive, entry)
+ }
+ if err := scanner.Err(); err != nil {
+ return errors.Wrapf(err, "parse %s", p)
+ }
+ }
+ m.BlkioStats.IoServiceBytesRecursive = ioServiceBytesRecursive
+ return nil
+}
diff --git a/vendor/github.com/containers/common/pkg/cgroups/cgroups.go b/vendor/github.com/containers/common/pkg/cgroups/cgroups.go
index 57997d652..eb903d3c3 100644
--- a/vendor/github.com/containers/common/pkg/cgroups/cgroups.go
+++ b/vendor/github.com/containers/common/pkg/cgroups/cgroups.go
@@ -1,8 +1,10 @@
+//go:build !linux
+// +build !linux
+
package cgroups
import (
"bufio"
- "bytes"
"context"
"fmt"
"io/ioutil"
@@ -248,47 +250,6 @@ func (c *CgroupControl) getCgroupv1Path(name string) string {
return filepath.Join(cgroupRoot, name, c.path)
}
-// createCgroupv2Path creates the cgroupv2 path and enables all the available controllers
-func createCgroupv2Path(path string) (deferredError error) {
- if !strings.HasPrefix(path, cgroupRoot+"/") {
- return fmt.Errorf("invalid cgroup path %s", path)
- }
- content, err := ioutil.ReadFile(cgroupRoot + "/cgroup.controllers")
- if err != nil {
- return err
- }
- ctrs := bytes.Fields(content)
- res := append([]byte("+"), bytes.Join(ctrs, []byte(" +"))...)
-
- current := "/sys/fs"
- elements := strings.Split(path, "/")
- for i, e := range elements[3:] {
- current = filepath.Join(current, e)
- if i > 0 {
- if err := os.Mkdir(current, 0o755); err != nil {
- if !os.IsExist(err) {
- return err
- }
- } else {
- // If the directory was created, be sure it is not left around on errors.
- defer func() {
- if deferredError != nil {
- os.Remove(current)
- }
- }()
- }
- }
- // We enable the controllers for all the path components except the last one. It is not allowed to add
- // PIDs if there are already enabled controllers.
- if i < len(elements[3:])-1 {
- if err := ioutil.WriteFile(filepath.Join(current, "cgroup.subtree_control"), res, 0o755); err != nil {
- return err
- }
- }
- }
- return nil
-}
-
// initialize initializes the specified hierarchy
func (c *CgroupControl) initialize() (err error) {
createdSoFar := map[string]controllerHandler{}
@@ -332,23 +293,6 @@ func (c *CgroupControl) initialize() (err error) {
return nil
}
-func (c *CgroupControl) createCgroupDirectory(controller string) (bool, error) {
- cPath := c.getCgroupv1Path(controller)
- _, err := os.Stat(cPath)
- if err == nil {
- return false, nil
- }
-
- if !os.IsNotExist(err) {
- return false, err
- }
-
- if err := os.MkdirAll(cPath, 0o755); err != nil {
- return false, errors.Wrapf(err, "error creating cgroup for %s", controller)
- }
- return true, nil
-}
-
func readFileAsUint64(path string) (uint64, error) {
data, err := ioutil.ReadFile(path)
if err != nil {
diff --git a/vendor/github.com/containers/common/pkg/cgroups/cgroups_linux.go b/vendor/github.com/containers/common/pkg/cgroups/cgroups_linux.go
new file mode 100644
index 000000000..4b72014bf
--- /dev/null
+++ b/vendor/github.com/containers/common/pkg/cgroups/cgroups_linux.go
@@ -0,0 +1,575 @@
+//go:build linux
+// +build linux
+
+package cgroups
+
+import (
+ "bufio"
+ "context"
+ "fmt"
+ "io/ioutil"
+ "math"
+ "os"
+ "path/filepath"
+ "strconv"
+ "strings"
+
+ "github.com/containers/storage/pkg/unshare"
+ systemdDbus "github.com/coreos/go-systemd/v22/dbus"
+ "github.com/godbus/dbus/v5"
+ "github.com/opencontainers/runc/libcontainer/cgroups"
+ "github.com/opencontainers/runc/libcontainer/cgroups/fs2"
+ "github.com/opencontainers/runc/libcontainer/configs"
+ "github.com/pkg/errors"
+ "github.com/sirupsen/logrus"
+)
+
+var (
+ // ErrCgroupDeleted means the cgroup was deleted
+ ErrCgroupDeleted = errors.New("cgroup deleted")
+ // ErrCgroupV1Rootless means the cgroup v1 were attempted to be used in rootless environment
+ ErrCgroupV1Rootless = errors.New("no support for CGroups V1 in rootless environments")
+ ErrStatCgroup = errors.New("no cgroup available for gathering user statistics")
+)
+
+// CgroupControl controls a cgroup hierarchy
+type CgroupControl struct {
+ cgroup2 bool
+ config *configs.Cgroup
+ systemd bool
+ // List of additional cgroup subsystems joined that
+ // do not have a custom handler.
+ additionalControllers []controller
+}
+
+type controller struct {
+ name string
+ symlink bool
+}
+
+type controllerHandler interface {
+ Create(*CgroupControl) (bool, error)
+ Apply(*CgroupControl, *configs.Resources) error
+ Destroy(*CgroupControl) error
+ Stat(*CgroupControl, *cgroups.Stats) error
+}
+
+const (
+ cgroupRoot = "/sys/fs/cgroup"
+ // CPU is the cpu controller
+ CPU = "cpu"
+ // CPUAcct is the cpuacct controller
+ CPUAcct = "cpuacct"
+ // CPUset is the cpuset controller
+ CPUset = "cpuset"
+ // Memory is the memory controller
+ Memory = "memory"
+ // Pids is the pids controller
+ Pids = "pids"
+ // Blkio is the blkio controller
+ Blkio = "blkio"
+)
+
+var handlers map[string]controllerHandler
+
+func init() {
+ handlers = make(map[string]controllerHandler)
+ handlers[CPU] = getCPUHandler()
+ handlers[CPUset] = getCpusetHandler()
+ handlers[Memory] = getMemoryHandler()
+ handlers[Pids] = getPidsHandler()
+ handlers[Blkio] = getBlkioHandler()
+}
+
+// getAvailableControllers get the available controllers
+func getAvailableControllers(exclude map[string]controllerHandler, cgroup2 bool) ([]controller, error) {
+ if cgroup2 {
+ controllers := []controller{}
+ controllersFile := cgroupRoot + "/cgroup.controllers"
+ // rootless cgroupv2: check available controllers for current user, systemd or servicescope will inherit
+ if unshare.IsRootless() {
+ userSlice, err := getCgroupPathForCurrentProcess()
+ if err != nil {
+ return controllers, err
+ }
+ // userSlice already contains '/' so not adding here
+ basePath := cgroupRoot + userSlice
+ controllersFile = fmt.Sprintf("%s/cgroup.controllers", basePath)
+ }
+ controllersFileBytes, err := ioutil.ReadFile(controllersFile)
+ if err != nil {
+ return nil, errors.Wrapf(err, "failed while reading controllers for cgroup v2 from %q", controllersFile)
+ }
+ for _, controllerName := range strings.Fields(string(controllersFileBytes)) {
+ c := controller{
+ name: controllerName,
+ symlink: false,
+ }
+ controllers = append(controllers, c)
+ }
+ return controllers, nil
+ }
+
+ subsystems, _ := cgroupV1GetAllSubsystems()
+ controllers := []controller{}
+ // cgroupv1 and rootless: No subsystem is available: delegation is unsafe.
+ if unshare.IsRootless() {
+ return controllers, nil
+ }
+
+ for _, name := range subsystems {
+ if _, found := exclude[name]; found {
+ continue
+ }
+ fileInfo, err := os.Stat(cgroupRoot + "/" + name)
+ if err != nil {
+ continue
+ }
+ c := controller{
+ name: name,
+ symlink: !fileInfo.IsDir(),
+ }
+ controllers = append(controllers, c)
+ }
+
+ return controllers, nil
+}
+
+// GetAvailableControllers get string:bool map of all the available controllers
+func GetAvailableControllers(exclude map[string]controllerHandler, cgroup2 bool) ([]string, error) {
+ availableControllers, err := getAvailableControllers(exclude, cgroup2)
+ if err != nil {
+ return nil, err
+ }
+ controllerList := []string{}
+ for _, controller := range availableControllers {
+ controllerList = append(controllerList, controller.name)
+ }
+
+ return controllerList, nil
+}
+
+func cgroupV1GetAllSubsystems() ([]string, error) {
+ f, err := os.Open("/proc/cgroups")
+ if err != nil {
+ return nil, err
+ }
+ defer f.Close()
+
+ subsystems := []string{}
+
+ s := bufio.NewScanner(f)
+ for s.Scan() {
+ text := s.Text()
+ if text[0] != '#' {
+ parts := strings.Fields(text)
+ if len(parts) >= 4 && parts[3] != "0" {
+ subsystems = append(subsystems, parts[0])
+ }
+ }
+ }
+ if err := s.Err(); err != nil {
+ return nil, err
+ }
+ return subsystems, nil
+}
+
+func getCgroupPathForCurrentProcess() (string, error) {
+ path := fmt.Sprintf("/proc/%d/cgroup", os.Getpid())
+ f, err := os.Open(path)
+ if err != nil {
+ return "", err
+ }
+ defer f.Close()
+
+ cgroupPath := ""
+ s := bufio.NewScanner(f)
+ for s.Scan() {
+ text := s.Text()
+ procEntries := strings.SplitN(text, "::", 2)
+ // set process cgroupPath only if entry is valid
+ if len(procEntries) > 1 {
+ cgroupPath = procEntries[1]
+ }
+ }
+ if err := s.Err(); err != nil {
+ return cgroupPath, err
+ }
+ return cgroupPath, nil
+}
+
+// getCgroupv1Path is a helper function to get the cgroup v1 path
+func (c *CgroupControl) getCgroupv1Path(name string) string {
+ return filepath.Join(cgroupRoot, name, c.config.Path)
+}
+
+// initialize initializes the specified hierarchy
+func (c *CgroupControl) initialize() (err error) {
+ createdSoFar := map[string]controllerHandler{}
+ defer func() {
+ if err != nil {
+ for name, ctr := range createdSoFar {
+ if err := ctr.Destroy(c); err != nil {
+ logrus.Warningf("error cleaning up controller %s for %s", name, c.config.Path)
+ }
+ }
+ }
+ }()
+ if c.cgroup2 {
+ if err := createCgroupv2Path(filepath.Join(cgroupRoot, c.config.Path)); err != nil {
+ return errors.Wrapf(err, "error creating cgroup path %s", c.config.Path)
+ }
+ }
+ for name, handler := range handlers {
+ created, err := handler.Create(c)
+ if err != nil {
+ return err
+ }
+ if created {
+ createdSoFar[name] = handler
+ }
+ }
+
+ if !c.cgroup2 {
+ // We won't need to do this for cgroup v2
+ for _, ctr := range c.additionalControllers {
+ if ctr.symlink {
+ continue
+ }
+ path := c.getCgroupv1Path(ctr.name)
+ if err := os.MkdirAll(path, 0o755); err != nil {
+ return errors.Wrapf(err, "error creating cgroup path for %s", ctr.name)
+ }
+ }
+ }
+
+ return nil
+}
+
+func readFileAsUint64(path string) (uint64, error) {
+ data, err := ioutil.ReadFile(path)
+ if err != nil {
+ return 0, err
+ }
+ v := cleanString(string(data))
+ if v == "max" {
+ return math.MaxUint64, nil
+ }
+ ret, err := strconv.ParseUint(v, 10, 64)
+ if err != nil {
+ return ret, errors.Wrapf(err, "parse %s from %s", v, path)
+ }
+ return ret, nil
+}
+
+func readFileByKeyAsUint64(path, key string) (uint64, error) {
+ content, err := ioutil.ReadFile(path)
+ if err != nil {
+ return 0, err
+ }
+ for _, line := range strings.Split(string(content), "\n") {
+ fields := strings.SplitN(line, " ", 2)
+ if fields[0] == key {
+ v := cleanString(string(fields[1]))
+ if v == "max" {
+ return math.MaxUint64, nil
+ }
+ ret, err := strconv.ParseUint(v, 10, 64)
+ if err != nil {
+ return ret, errors.Wrapf(err, "parse %s from %s", v, path)
+ }
+ return ret, nil
+ }
+ }
+
+ return 0, fmt.Errorf("no key named %s from %s", key, path)
+}
+
+// New creates a new cgroup control
+func New(path string, resources *configs.Resources) (*CgroupControl, error) {
+ cgroup2, err := IsCgroup2UnifiedMode()
+ if err != nil {
+ return nil, err
+ }
+ control := &CgroupControl{
+ cgroup2: cgroup2,
+ config: &configs.Cgroup{
+ Path: path,
+ Resources: resources,
+ },
+ }
+
+ if !cgroup2 {
+ controllers, err := getAvailableControllers(handlers, false)
+ if err != nil {
+ return nil, err
+ }
+ control.additionalControllers = controllers
+ }
+
+ if err := control.initialize(); err != nil {
+ return nil, err
+ }
+
+ return control, nil
+}
+
+// NewSystemd creates a new cgroup control
+func NewSystemd(path string, resources *configs.Resources) (*CgroupControl, error) {
+ cgroup2, err := IsCgroup2UnifiedMode()
+ if err != nil {
+ return nil, err
+ }
+ control := &CgroupControl{
+ cgroup2: cgroup2,
+ systemd: true,
+ config: &configs.Cgroup{
+ Path: path,
+ Resources: resources,
+ Rootless: unshare.IsRootless(),
+ },
+ }
+
+ return control, nil
+}
+
+// Load loads an existing cgroup control
+func Load(path string) (*CgroupControl, error) {
+ cgroup2, err := IsCgroup2UnifiedMode()
+ if err != nil {
+ return nil, err
+ }
+ control := &CgroupControl{
+ cgroup2: cgroup2,
+ systemd: false,
+ config: &configs.Cgroup{
+ Path: path,
+ },
+ }
+ if !cgroup2 {
+ controllers, err := getAvailableControllers(handlers, false)
+ if err != nil {
+ return nil, err
+ }
+ control.additionalControllers = controllers
+ }
+ if !cgroup2 {
+ oneExists := false
+ // check that the cgroup exists at least under one controller
+ for name := range handlers {
+ p := control.getCgroupv1Path(name)
+ if _, err := os.Stat(p); err == nil {
+ oneExists = true
+ break
+ }
+ }
+
+ // if there is no controller at all, raise an error
+ if !oneExists {
+ if unshare.IsRootless() {
+ return nil, ErrCgroupV1Rootless
+ }
+ // compatible with the error code
+ // used by containerd/cgroups
+ return nil, ErrCgroupDeleted
+ }
+ }
+ return control, nil
+}
+
+// CreateSystemdUnit creates the systemd cgroup
+func (c *CgroupControl) CreateSystemdUnit(path string) error {
+ if !c.systemd {
+ return fmt.Errorf("the cgroup controller is not using systemd")
+ }
+
+ conn, err := systemdDbus.NewWithContext(context.TODO())
+ if err != nil {
+ return err
+ }
+ defer conn.Close()
+
+ return systemdCreate(c.config.Resources, path, conn)
+}
+
+// GetUserConnection returns an user connection to D-BUS
+func GetUserConnection(uid int) (*systemdDbus.Conn, error) {
+ return systemdDbus.NewConnection(func() (*dbus.Conn, error) {
+ return dbusAuthConnection(uid, dbus.SessionBusPrivate)
+ })
+}
+
+// CreateSystemdUserUnit creates the systemd cgroup for the specified user
+func (c *CgroupControl) CreateSystemdUserUnit(path string, uid int) error {
+ if !c.systemd {
+ return fmt.Errorf("the cgroup controller is not using systemd")
+ }
+
+ conn, err := GetUserConnection(uid)
+ if err != nil {
+ return err
+ }
+ defer conn.Close()
+
+ return systemdCreate(c.config.Resources, path, conn)
+}
+
+func dbusAuthConnection(uid int, createBus func(opts ...dbus.ConnOption) (*dbus.Conn, error)) (*dbus.Conn, error) {
+ conn, err := createBus()
+ if err != nil {
+ return nil, err
+ }
+
+ methods := []dbus.Auth{dbus.AuthExternal(strconv.Itoa(uid))}
+
+ err = conn.Auth(methods)
+ if err != nil {
+ conn.Close()
+ return nil, err
+ }
+ if err := conn.Hello(); err != nil {
+ return nil, err
+ }
+
+ return conn, nil
+}
+
+// Delete cleans a cgroup
+func (c *CgroupControl) Delete() error {
+ return c.DeleteByPath(c.config.Path)
+}
+
+// DeleteByPathConn deletes the specified cgroup path using the specified
+// dbus connection if needed.
+func (c *CgroupControl) DeleteByPathConn(path string, conn *systemdDbus.Conn) error {
+ if c.systemd {
+ return systemdDestroyConn(path, conn)
+ }
+ if c.cgroup2 {
+ return rmDirRecursively(filepath.Join(cgroupRoot, c.config.Path))
+ }
+ var lastError error
+ for _, h := range handlers {
+ if err := h.Destroy(c); err != nil {
+ lastError = err
+ }
+ }
+
+ for _, ctr := range c.additionalControllers {
+ if ctr.symlink {
+ continue
+ }
+ p := c.getCgroupv1Path(ctr.name)
+ if err := rmDirRecursively(p); err != nil {
+ lastError = errors.Wrapf(err, "remove %s", p)
+ }
+ }
+ return lastError
+}
+
+// DeleteByPath deletes the specified cgroup path
+func (c *CgroupControl) DeleteByPath(path string) error {
+ if c.systemd {
+ conn, err := systemdDbus.NewWithContext(context.TODO())
+ if err != nil {
+ return err
+ }
+ defer conn.Close()
+ return c.DeleteByPathConn(path, conn)
+ }
+ return c.DeleteByPathConn(path, nil)
+}
+
+// Update updates the cgroups
+func (c *CgroupControl) Update(resources *configs.Resources) error {
+ for _, h := range handlers {
+ if err := h.Apply(c, resources); err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+// AddPid moves the specified pid to the cgroup
+func (c *CgroupControl) AddPid(pid int) error {
+ pidString := []byte(fmt.Sprintf("%d\n", pid))
+
+ if c.cgroup2 {
+ path := filepath.Join(cgroupRoot, c.config.Path)
+ return fs2.CreateCgroupPath(path, c.config)
+ }
+
+ names := make([]string, 0, len(handlers))
+ for n := range handlers {
+ names = append(names, n)
+ }
+
+ for _, c := range c.additionalControllers {
+ if !c.symlink {
+ names = append(names, c.name)
+ }
+ }
+
+ for _, n := range names {
+ // If we aren't using cgroup2, we won't write correctly to unified hierarchy
+ if !c.cgroup2 && n == "unified" {
+ continue
+ }
+ p := filepath.Join(c.getCgroupv1Path(n), "tasks")
+ if err := ioutil.WriteFile(p, pidString, 0o644); err != nil {
+ return errors.Wrapf(err, "write %s", p)
+ }
+ }
+ return nil
+}
+
+// Stat returns usage statistics for the cgroup
+func (c *CgroupControl) Stat() (*cgroups.Stats, error) {
+ m := cgroups.Stats{}
+ found := false
+ for _, h := range handlers {
+ if err := h.Stat(c, &m); err != nil {
+ if !os.IsNotExist(errors.Cause(err)) {
+ return nil, err
+ }
+ logrus.Warningf("Failed to retrieve cgroup stats: %v", err)
+ continue
+ }
+ found = true
+ }
+ if !found {
+ return nil, ErrStatCgroup
+ }
+ return &m, nil
+}
+
+func readCgroup2MapPath(path string) (map[string][]string, error) {
+ ret := map[string][]string{}
+ f, err := os.Open(path)
+ if err != nil {
+ if os.IsNotExist(err) {
+ return ret, nil
+ }
+ return nil, errors.Wrapf(err, "open file %s", path)
+ }
+ defer f.Close()
+ scanner := bufio.NewScanner(f)
+ for scanner.Scan() {
+ line := scanner.Text()
+ parts := strings.Fields(line)
+ if len(parts) < 2 {
+ continue
+ }
+ ret[parts[0]] = parts[1:]
+ }
+ if err := scanner.Err(); err != nil {
+ return nil, errors.Wrapf(err, "parsing file %s", path)
+ }
+ return ret, nil
+}
+
+func readCgroup2MapFile(ctr *CgroupControl, name string) (map[string][]string, error) {
+ p := filepath.Join(cgroupRoot, ctr.config.Path, name)
+
+ return readCgroup2MapPath(p)
+}
diff --git a/vendor/github.com/containers/common/pkg/cgroups/cpu.go b/vendor/github.com/containers/common/pkg/cgroups/cpu.go
index c9e94f269..fff76b9e2 100644
--- a/vendor/github.com/containers/common/pkg/cgroups/cpu.go
+++ b/vendor/github.com/containers/common/pkg/cgroups/cpu.go
@@ -1,12 +1,12 @@
+//go:build !linux
+// +build !linux
+
package cgroups
import (
"fmt"
- "io/ioutil"
"os"
- "path/filepath"
"strconv"
- "strings"
spec "github.com/opencontainers/runtime-spec/specs-go"
"github.com/pkg/errors"
@@ -18,36 +18,6 @@ func getCPUHandler() *cpuHandler {
return &cpuHandler{}
}
-func cleanString(s string) string {
- return strings.Trim(s, "\n")
-}
-
-func readAcct(ctr *CgroupControl, name string) (uint64, error) {
- p := filepath.Join(ctr.getCgroupv1Path(CPUAcct), name)
- return readFileAsUint64(p)
-}
-
-func readAcctList(ctr *CgroupControl, name string) ([]uint64, error) {
- p := filepath.Join(ctr.getCgroupv1Path(CPUAcct), name)
- data, err := ioutil.ReadFile(p)
- if err != nil {
- return nil, errors.Wrapf(err, "reading %s", p)
- }
- r := []uint64{}
- for _, s := range strings.Split(string(data), " ") {
- s = cleanString(s)
- if s == "" {
- break
- }
- v, err := strconv.ParseUint(s, 10, 64)
- if err != nil {
- return nil, errors.Wrapf(err, "parsing %s", s)
- }
- r = append(r, v)
- }
- return r, nil
-}
-
// Apply set the specified constraints
func (c *cpuHandler) Apply(ctr *CgroupControl, res *spec.LinuxResources) error {
if res.CPU == nil {
@@ -119,41 +89,3 @@ func (c *cpuHandler) Stat(ctr *CgroupControl, m *Metrics) error {
m.CPU = CPUMetrics{Usage: usage}
return nil
}
-
-// GetSystemCPUUsage returns the system usage for all the cgroups
-func GetSystemCPUUsage() (uint64, error) {
- cgroupv2, err := IsCgroup2UnifiedMode()
- if err != nil {
- return 0, err
- }
- if !cgroupv2 {
- p := filepath.Join(cgroupRoot, CPUAcct, "cpuacct.usage")
- return readFileAsUint64(p)
- }
-
- files, err := ioutil.ReadDir(cgroupRoot)
- if err != nil {
- return 0, err
- }
- var total uint64
- for _, file := range files {
- if !file.IsDir() {
- continue
- }
- p := filepath.Join(cgroupRoot, file.Name(), "cpu.stat")
-
- values, err := readCgroup2MapPath(p)
- if err != nil {
- return 0, err
- }
-
- if val, found := values["usage_usec"]; found {
- v, err := strconv.ParseUint(cleanString(val[0]), 10, 64)
- if err != nil {
- return 0, err
- }
- total += v * 1000
- }
- }
- return total, nil
-}
diff --git a/vendor/github.com/containers/common/pkg/cgroups/cpu_linux.go b/vendor/github.com/containers/common/pkg/cgroups/cpu_linux.go
new file mode 100644
index 000000000..bca55575c
--- /dev/null
+++ b/vendor/github.com/containers/common/pkg/cgroups/cpu_linux.go
@@ -0,0 +1,100 @@
+//go:build linux
+// +build linux
+
+package cgroups
+
+import (
+ "os"
+ "path/filepath"
+ "strconv"
+
+ "github.com/opencontainers/runc/libcontainer/cgroups"
+ "github.com/opencontainers/runc/libcontainer/cgroups/fs"
+ "github.com/opencontainers/runc/libcontainer/cgroups/fs2"
+ "github.com/opencontainers/runc/libcontainer/configs"
+ "github.com/pkg/errors"
+)
+
+type linuxCPUHandler struct {
+ CPU fs.CpuGroup
+}
+
+func getCPUHandler() *linuxCPUHandler {
+ return &linuxCPUHandler{}
+}
+
+// Apply set the specified constraints
+func (c *linuxCPUHandler) Apply(ctr *CgroupControl, res *configs.Resources) error {
+ if ctr.cgroup2 {
+ man, err := fs2.NewManager(ctr.config, filepath.Join(cgroupRoot, ctr.config.Path))
+ if err != nil {
+ return err
+ }
+ return man.Set(res)
+ }
+ path := filepath.Join(cgroupRoot, CPU, ctr.config.Path)
+ return c.CPU.Set(path, res)
+}
+
+// Create the cgroup
+func (c *linuxCPUHandler) Create(ctr *CgroupControl) (bool, error) {
+ if ctr.cgroup2 {
+ return false, nil
+ }
+ return ctr.createCgroupDirectory(CPU)
+}
+
+// Destroy the cgroup
+func (c *linuxCPUHandler) Destroy(ctr *CgroupControl) error {
+ return rmDirRecursively(ctr.getCgroupv1Path(CPU))
+}
+
+// Stat fills a metrics structure with usage stats for the controller
+func (c *linuxCPUHandler) Stat(ctr *CgroupControl, m *cgroups.Stats) error {
+ var err error
+ cpu := cgroups.CpuStats{}
+ if ctr.cgroup2 {
+ values, err := readCgroup2MapFile(ctr, "cpu.stat")
+ if err != nil {
+ return err
+ }
+ if val, found := values["usage_usec"]; found {
+ cpu.CpuUsage.TotalUsage, err = strconv.ParseUint(cleanString(val[0]), 10, 64)
+ if err != nil {
+ return err
+ }
+ cpu.CpuUsage.UsageInKernelmode *= 1000
+ }
+ if val, found := values["system_usec"]; found {
+ cpu.CpuUsage.UsageInKernelmode, err = strconv.ParseUint(cleanString(val[0]), 10, 64)
+ if err != nil {
+ return err
+ }
+ cpu.CpuUsage.TotalUsage *= 1000
+ }
+ } else {
+ cpu.CpuUsage.TotalUsage, err = readAcct(ctr, "cpuacct.usage")
+ if err != nil {
+ if !os.IsNotExist(errors.Cause(err)) {
+ return err
+ }
+ cpu.CpuUsage.TotalUsage = 0
+ }
+ cpu.CpuUsage.UsageInKernelmode, err = readAcct(ctr, "cpuacct.usage_sys")
+ if err != nil {
+ if !os.IsNotExist(errors.Cause(err)) {
+ return err
+ }
+ cpu.CpuUsage.UsageInKernelmode = 0
+ }
+ cpu.CpuUsage.PercpuUsage, err = readAcctList(ctr, "cpuacct.usage_percpu")
+ if err != nil {
+ if !os.IsNotExist(errors.Cause(err)) {
+ return err
+ }
+ cpu.CpuUsage.PercpuUsage = nil
+ }
+ }
+ m.CpuStats = cpu
+ return nil
+}
diff --git a/vendor/github.com/containers/common/pkg/cgroups/cpuset.go b/vendor/github.com/containers/common/pkg/cgroups/cpuset.go
index 2bfeb80db..f7ec9a33b 100644
--- a/vendor/github.com/containers/common/pkg/cgroups/cpuset.go
+++ b/vendor/github.com/containers/common/pkg/cgroups/cpuset.go
@@ -1,52 +1,17 @@
+//go:build !linux
+// +build !linux
+
package cgroups
import (
"fmt"
- "io/ioutil"
"path/filepath"
- "strings"
spec "github.com/opencontainers/runtime-spec/specs-go"
- "github.com/pkg/errors"
)
type cpusetHandler struct{}
-func cpusetCopyFileFromParent(dir, file string, cgroupv2 bool) ([]byte, error) {
- if dir == cgroupRoot {
- return nil, fmt.Errorf("could not find parent to initialize cpuset %s", file)
- }
- path := filepath.Join(dir, file)
- parentPath := path
- if cgroupv2 {
- parentPath = fmt.Sprintf("%s.effective", parentPath)
- }
- data, err := ioutil.ReadFile(parentPath)
- if err != nil {
- return nil, errors.Wrapf(err, "open %s", path)
- }
- if strings.Trim(string(data), "\n") != "" {
- return data, nil
- }
- data, err = cpusetCopyFileFromParent(filepath.Dir(dir), file, cgroupv2)
- if err != nil {
- return nil, err
- }
- if err := ioutil.WriteFile(path, data, 0o644); err != nil {
- return nil, errors.Wrapf(err, "write %s", path)
- }
- return data, nil
-}
-
-func cpusetCopyFromParent(path string, cgroupv2 bool) error {
- for _, file := range []string{"cpuset.cpus", "cpuset.mems"} {
- if _, err := cpusetCopyFileFromParent(path, file, cgroupv2); err != nil {
- return err
- }
- }
- return nil
-}
-
func getCpusetHandler() *cpusetHandler {
return &cpusetHandler{}
}
diff --git a/vendor/github.com/containers/common/pkg/cgroups/cpuset_linux.go b/vendor/github.com/containers/common/pkg/cgroups/cpuset_linux.go
new file mode 100644
index 000000000..a4cc2acaf
--- /dev/null
+++ b/vendor/github.com/containers/common/pkg/cgroups/cpuset_linux.go
@@ -0,0 +1,57 @@
+//go:build linux
+// +build linux
+
+package cgroups
+
+import (
+ "path/filepath"
+
+ "github.com/opencontainers/runc/libcontainer/cgroups"
+ "github.com/opencontainers/runc/libcontainer/cgroups/fs"
+ "github.com/opencontainers/runc/libcontainer/cgroups/fs2"
+ "github.com/opencontainers/runc/libcontainer/configs"
+)
+
+type linuxCpusetHandler struct {
+ CPUSet fs.CpusetGroup
+}
+
+func getCpusetHandler() *linuxCpusetHandler {
+ return &linuxCpusetHandler{}
+}
+
+// Apply set the specified constraints
+func (c *linuxCpusetHandler) Apply(ctr *CgroupControl, res *configs.Resources) error {
+ if ctr.cgroup2 {
+ man, err := fs2.NewManager(ctr.config, filepath.Join(cgroupRoot, ctr.config.Path))
+ if err != nil {
+ return err
+ }
+ return man.Set(res)
+ }
+ path := filepath.Join(cgroupRoot, CPUset, ctr.config.Path)
+ return c.CPUSet.Set(path, res)
+}
+
+// Create the cgroup
+func (c *linuxCpusetHandler) Create(ctr *CgroupControl) (bool, error) {
+ if ctr.cgroup2 {
+ path := filepath.Join(cgroupRoot, ctr.config.Path)
+ return true, cpusetCopyFromParent(path, true)
+ }
+ created, err := ctr.createCgroupDirectory(CPUset)
+ if !created || err != nil {
+ return created, err
+ }
+ return true, cpusetCopyFromParent(ctr.getCgroupv1Path(CPUset), false)
+}
+
+// Destroy the cgroup
+func (c *linuxCpusetHandler) Destroy(ctr *CgroupControl) error {
+ return rmDirRecursively(ctr.getCgroupv1Path(CPUset))
+}
+
+// Stat fills a metrics structure with usage stats for the controller
+func (c *linuxCpusetHandler) Stat(ctr *CgroupControl, m *cgroups.Stats) error {
+ return nil
+}
diff --git a/vendor/github.com/containers/common/pkg/cgroups/memory.go b/vendor/github.com/containers/common/pkg/cgroups/memory.go
index 10d65893c..b597b85bf 100644
--- a/vendor/github.com/containers/common/pkg/cgroups/memory.go
+++ b/vendor/github.com/containers/common/pkg/cgroups/memory.go
@@ -1,3 +1,6 @@
+//go:build !linux
+// +build !linux
+
package cgroups
import (
diff --git a/vendor/github.com/containers/common/pkg/cgroups/memory_linux.go b/vendor/github.com/containers/common/pkg/cgroups/memory_linux.go
new file mode 100644
index 000000000..5d2bf5d0e
--- /dev/null
+++ b/vendor/github.com/containers/common/pkg/cgroups/memory_linux.go
@@ -0,0 +1,78 @@
+//go:build linux
+// +build linux
+
+package cgroups
+
+import (
+ "path/filepath"
+
+ "github.com/opencontainers/runc/libcontainer/cgroups"
+ "github.com/opencontainers/runc/libcontainer/cgroups/fs"
+ "github.com/opencontainers/runc/libcontainer/cgroups/fs2"
+ "github.com/opencontainers/runc/libcontainer/configs"
+)
+
+type linuxMemHandler struct {
+ Mem fs.MemoryGroup
+}
+
+func getMemoryHandler() *linuxMemHandler {
+ return &linuxMemHandler{}
+}
+
+// Apply set the specified constraints
+func (c *linuxMemHandler) Apply(ctr *CgroupControl, res *configs.Resources) error {
+ if ctr.cgroup2 {
+ man, err := fs2.NewManager(ctr.config, filepath.Join(cgroupRoot, ctr.config.Path))
+ if err != nil {
+ return err
+ }
+ return man.Set(res)
+ }
+ path := filepath.Join(cgroupRoot, Memory, ctr.config.Path)
+ return c.Mem.Set(path, res)
+}
+
+// Create the cgroup
+func (c *linuxMemHandler) Create(ctr *CgroupControl) (bool, error) {
+ if ctr.cgroup2 {
+ return false, nil
+ }
+ return ctr.createCgroupDirectory(Memory)
+}
+
+// Destroy the cgroup
+func (c *linuxMemHandler) Destroy(ctr *CgroupControl) error {
+ return rmDirRecursively(ctr.getCgroupv1Path(Memory))
+}
+
+// Stat fills a metrics structure with usage stats for the controller
+func (c *linuxMemHandler) Stat(ctr *CgroupControl, m *cgroups.Stats) error {
+ var err error
+ memUsage := cgroups.MemoryStats{}
+
+ var memoryRoot string
+ var limitFilename string
+
+ if ctr.cgroup2 {
+ memoryRoot = filepath.Join(cgroupRoot, ctr.config.Path)
+ limitFilename = "memory.max"
+ if memUsage.Usage.Usage, err = readFileByKeyAsUint64(filepath.Join(memoryRoot, "memory.stat"), "anon"); err != nil {
+ return err
+ }
+ } else {
+ memoryRoot = ctr.getCgroupv1Path(Memory)
+ limitFilename = "memory.limit_in_bytes"
+ if memUsage.Usage.Usage, err = readFileAsUint64(filepath.Join(memoryRoot, "memory.usage_in_bytes")); err != nil {
+ return err
+ }
+ }
+
+ memUsage.Usage.Limit, err = readFileAsUint64(filepath.Join(memoryRoot, limitFilename))
+ if err != nil {
+ return err
+ }
+
+ m.MemoryStats = memUsage
+ return nil
+}
diff --git a/vendor/github.com/containers/common/pkg/cgroups/pids.go b/vendor/github.com/containers/common/pkg/cgroups/pids.go
index 650120a56..1cb7ced82 100644
--- a/vendor/github.com/containers/common/pkg/cgroups/pids.go
+++ b/vendor/github.com/containers/common/pkg/cgroups/pids.go
@@ -1,3 +1,6 @@
+//go:build !linux
+// +build !linux
+
package cgroups
import (
diff --git a/vendor/github.com/containers/common/pkg/cgroups/pids_linux.go b/vendor/github.com/containers/common/pkg/cgroups/pids_linux.go
new file mode 100644
index 000000000..a8163ce46
--- /dev/null
+++ b/vendor/github.com/containers/common/pkg/cgroups/pids_linux.go
@@ -0,0 +1,71 @@
+//go:build linux
+// +build linux
+
+package cgroups
+
+import (
+ "path/filepath"
+
+ "github.com/opencontainers/runc/libcontainer/cgroups"
+ "github.com/opencontainers/runc/libcontainer/cgroups/fs"
+ "github.com/opencontainers/runc/libcontainer/cgroups/fs2"
+ "github.com/opencontainers/runc/libcontainer/configs"
+)
+
+type linuxPidHandler struct {
+ Pid fs.PidsGroup
+}
+
+func getPidsHandler() *linuxPidHandler {
+ return &linuxPidHandler{}
+}
+
+// Apply set the specified constraints
+func (c *linuxPidHandler) Apply(ctr *CgroupControl, res *configs.Resources) error {
+ if ctr.cgroup2 {
+ man, err := fs2.NewManager(ctr.config, filepath.Join(cgroupRoot, ctr.config.Path))
+ if err != nil {
+ return err
+ }
+ return man.Set(res)
+ }
+
+ path := filepath.Join(cgroupRoot, Pids, ctr.config.Path)
+ return c.Pid.Set(path, res)
+}
+
+// Create the cgroup
+func (c *linuxPidHandler) Create(ctr *CgroupControl) (bool, error) {
+ if ctr.cgroup2 {
+ return false, nil
+ }
+ return ctr.createCgroupDirectory(Pids)
+}
+
+// Destroy the cgroup
+func (c *linuxPidHandler) Destroy(ctr *CgroupControl) error {
+ return rmDirRecursively(ctr.getCgroupv1Path(Pids))
+}
+
+// Stat fills a metrics structure with usage stats for the controller
+func (c *linuxPidHandler) Stat(ctr *CgroupControl, m *cgroups.Stats) error {
+ if ctr.config.Path == "" {
+ // nothing we can do to retrieve the pids.current path
+ return nil
+ }
+
+ var PIDRoot string
+ if ctr.cgroup2 {
+ PIDRoot = filepath.Join(cgroupRoot, ctr.config.Path)
+ } else {
+ PIDRoot = ctr.getCgroupv1Path(Pids)
+ }
+
+ current, err := readFileAsUint64(filepath.Join(PIDRoot, "pids.current"))
+ if err != nil {
+ return err
+ }
+
+ m.PidsStats.Current = current
+ return nil
+}
diff --git a/vendor/github.com/containers/common/pkg/cgroups/systemd.go b/vendor/github.com/containers/common/pkg/cgroups/systemd.go
index 92065a2d7..118fa97a1 100644
--- a/vendor/github.com/containers/common/pkg/cgroups/systemd.go
+++ b/vendor/github.com/containers/common/pkg/cgroups/systemd.go
@@ -1,3 +1,6 @@
+//go:build !linux
+// +build !linux
+
package cgroups
import (
@@ -52,15 +55,11 @@ func systemdCreate(path string, c *systemdDbus.Conn) error {
/*
systemdDestroyConn is copied from containerd/cgroups/systemd.go file, that
has the following license:
-
Copyright The containerd Authors.
-
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
-
https://www.apache.org/licenses/LICENSE-2.0
-
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
diff --git a/vendor/github.com/containers/common/pkg/cgroups/systemd_linux.go b/vendor/github.com/containers/common/pkg/cgroups/systemd_linux.go
new file mode 100644
index 000000000..a45358f9b
--- /dev/null
+++ b/vendor/github.com/containers/common/pkg/cgroups/systemd_linux.go
@@ -0,0 +1,167 @@
+//go:build linux
+// +build linux
+
+package cgroups
+
+import (
+ "context"
+ "fmt"
+ "path/filepath"
+ "strings"
+
+ systemdDbus "github.com/coreos/go-systemd/v22/dbus"
+ "github.com/godbus/dbus/v5"
+ "github.com/opencontainers/runc/libcontainer/configs"
+)
+
+func systemdCreate(resources *configs.Resources, path string, c *systemdDbus.Conn) error {
+ slice, name := filepath.Split(path)
+ slice = strings.TrimSuffix(slice, "/")
+
+ var lastError error
+ for i := 0; i < 2; i++ {
+ properties := []systemdDbus.Property{
+ systemdDbus.PropDescription(fmt.Sprintf("cgroup %s", name)),
+ systemdDbus.PropWants(slice),
+ }
+ pMap := map[string]bool{
+ "DefaultDependencies": false,
+ "MemoryAccounting": true,
+ "CPUAccounting": true,
+ "BlockIOAccounting": true,
+ }
+ if i == 0 {
+ pMap["Delegate"] = true
+ }
+
+ for k, v := range pMap {
+ p := systemdDbus.Property{
+ Name: k,
+ Value: dbus.MakeVariant(v),
+ }
+ properties = append(properties, p)
+ }
+
+ uMap, sMap, bMap, iMap := resourcesToProps(resources)
+ for k, v := range uMap {
+ p := systemdDbus.Property{
+ Name: k,
+ Value: dbus.MakeVariant(v),
+ }
+ properties = append(properties, p)
+ }
+
+ for k, v := range sMap {
+ p := systemdDbus.Property{
+ Name: k,
+ Value: dbus.MakeVariant(v),
+ }
+ properties = append(properties, p)
+ }
+
+ for k, v := range bMap {
+ p := systemdDbus.Property{
+ Name: k,
+ Value: dbus.MakeVariant(v),
+ }
+ properties = append(properties, p)
+ }
+
+ for k, v := range iMap {
+ p := systemdDbus.Property{
+ Name: k,
+ Value: dbus.MakeVariant(v),
+ }
+ properties = append(properties, p)
+ }
+
+ ch := make(chan string)
+ _, err := c.StartTransientUnitContext(context.TODO(), name, "replace", properties, ch)
+ if err != nil {
+ lastError = err
+ continue
+ }
+ <-ch
+ return nil
+ }
+ return lastError
+}
+
+/*
+ systemdDestroyConn is copied from containerd/cgroups/systemd.go file, that
+ has the following license:
+
+ Copyright The containerd Authors.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ https://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+*/
+func systemdDestroyConn(path string, c *systemdDbus.Conn) error {
+ name := filepath.Base(path)
+
+ ch := make(chan string)
+ _, err := c.StopUnitContext(context.TODO(), name, "replace", ch)
+ if err != nil {
+ return err
+ }
+ <-ch
+ return nil
+}
+
+func resourcesToProps(res *configs.Resources) (map[string]uint64, map[string]string, map[string][]byte, map[string]int64) {
+ bMap := make(map[string][]byte)
+ // this array is not used but will be once more resource limits are added
+ sMap := make(map[string]string)
+ iMap := make(map[string]int64)
+ uMap := make(map[string]uint64)
+
+ // CPU
+ if res.CpuPeriod != 0 {
+ uMap["CPUQuotaPeriodUSec"] = res.CpuPeriod
+ }
+ if res.CpuQuota != 0 {
+ period := res.CpuPeriod
+ if period == 0 {
+ period = uint64(100000)
+ }
+ cpuQuotaPerSecUSec := uint64(res.CpuQuota*1000000) / period
+ if cpuQuotaPerSecUSec%10000 != 0 {
+ cpuQuotaPerSecUSec = ((cpuQuotaPerSecUSec / 10000) + 1) * 10000
+ }
+ uMap["CPUQuotaPerSecUSec"] = cpuQuotaPerSecUSec
+ }
+
+ // CPUSet
+ if res.CpusetCpus != "" {
+ bits := []byte(res.CpusetCpus)
+ bMap["AllowedCPUs"] = bits
+ }
+ if res.CpusetMems != "" {
+ bits := []byte(res.CpusetMems)
+ bMap["AllowedMemoryNodes"] = bits
+ }
+
+ // Mem
+ if res.Memory != 0 {
+ iMap["MemoryMax"] = res.Memory
+ }
+ if res.MemorySwap != 0 {
+ iMap["MemorySwapMax"] = res.MemorySwap
+ }
+
+ // Blkio
+ if res.BlkioWeight > 0 {
+ uMap["BlockIOWeight"] = uint64(res.BlkioWeight)
+ }
+
+ return uMap, sMap, bMap, iMap
+}
diff --git a/vendor/github.com/containers/common/pkg/cgroups/utils.go b/vendor/github.com/containers/common/pkg/cgroups/utils.go
new file mode 100644
index 000000000..1fd45f40b
--- /dev/null
+++ b/vendor/github.com/containers/common/pkg/cgroups/utils.go
@@ -0,0 +1,176 @@
+package cgroups
+
+import (
+ "bytes"
+ "fmt"
+ "io/ioutil"
+ "os"
+ "path/filepath"
+ "strconv"
+ "strings"
+
+ "github.com/pkg/errors"
+)
+
+var TestMode bool
+
+func cleanString(s string) string {
+ return strings.Trim(s, "\n")
+}
+
+func readAcct(ctr *CgroupControl, name string) (uint64, error) {
+ p := filepath.Join(ctr.getCgroupv1Path(CPUAcct), name)
+ return readFileAsUint64(p)
+}
+
+func readAcctList(ctr *CgroupControl, name string) ([]uint64, error) {
+ p := filepath.Join(ctr.getCgroupv1Path(CPUAcct), name)
+ data, err := ioutil.ReadFile(p)
+ if err != nil {
+ return nil, errors.Wrapf(err, "reading %s", p)
+ }
+ r := []uint64{}
+ for _, s := range strings.Split(string(data), " ") {
+ s = cleanString(s)
+ if s == "" {
+ break
+ }
+ v, err := strconv.ParseUint(s, 10, 64)
+ if err != nil {
+ return nil, errors.Wrapf(err, "parsing %s", s)
+ }
+ r = append(r, v)
+ }
+ return r, nil
+}
+
+// GetSystemCPUUsage returns the system usage for all the cgroups
+func GetSystemCPUUsage() (uint64, error) {
+ cgroupv2, err := IsCgroup2UnifiedMode()
+ if err != nil {
+ return 0, err
+ }
+ if !cgroupv2 {
+ p := filepath.Join(cgroupRoot, CPUAcct, "cpuacct.usage")
+ return readFileAsUint64(p)
+ }
+
+ files, err := ioutil.ReadDir(cgroupRoot)
+ if err != nil {
+ return 0, err
+ }
+ var total uint64
+ for _, file := range files {
+ if !file.IsDir() {
+ continue
+ }
+ p := filepath.Join(cgroupRoot, file.Name(), "cpu.stat")
+
+ values, err := readCgroup2MapPath(p)
+ if err != nil {
+ return 0, err
+ }
+
+ if val, found := values["usage_usec"]; found {
+ v, err := strconv.ParseUint(cleanString(val[0]), 10, 64)
+ if err != nil {
+ return 0, err
+ }
+ total += v * 1000
+ }
+ }
+ return total, nil
+}
+
+func cpusetCopyFileFromParent(dir, file string, cgroupv2 bool) ([]byte, error) {
+ if dir == cgroupRoot {
+ return nil, fmt.Errorf("could not find parent to initialize cpuset %s", file)
+ }
+ path := filepath.Join(dir, file)
+ parentPath := path
+ if cgroupv2 {
+ parentPath = fmt.Sprintf("%s.effective", parentPath)
+ }
+ data, err := ioutil.ReadFile(parentPath)
+ if err != nil {
+ return nil, errors.Wrapf(err, "open %s", path)
+ }
+ if strings.Trim(string(data), "\n") != "" {
+ return data, nil
+ }
+ data, err = cpusetCopyFileFromParent(filepath.Dir(dir), file, cgroupv2)
+ if err != nil {
+ return nil, err
+ }
+ if err := ioutil.WriteFile(path, data, 0o644); err != nil {
+ return nil, errors.Wrapf(err, "write %s", path)
+ }
+ return data, nil
+}
+
+func cpusetCopyFromParent(path string, cgroupv2 bool) error {
+ for _, file := range []string{"cpuset.cpus", "cpuset.mems"} {
+ if _, err := cpusetCopyFileFromParent(path, file, cgroupv2); err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+// createCgroupv2Path creates the cgroupv2 path and enables all the available controllers
+func createCgroupv2Path(path string) (deferredError error) {
+ if !strings.HasPrefix(path, cgroupRoot+"/") {
+ return fmt.Errorf("invalid cgroup path %s", path)
+ }
+ content, err := ioutil.ReadFile(cgroupRoot + "/cgroup.controllers")
+ if err != nil {
+ return err
+ }
+ ctrs := bytes.Fields(content)
+ res := append([]byte("+"), bytes.Join(ctrs, []byte(" +"))...)
+
+ current := "/sys/fs"
+ elements := strings.Split(path, "/")
+ for i, e := range elements[3:] {
+ current = filepath.Join(current, e)
+ if i > 0 {
+ if err := os.Mkdir(current, 0o755); err != nil {
+ if !os.IsExist(err) {
+ return err
+ }
+ } else {
+ // If the directory was created, be sure it is not left around on errors.
+ defer func() {
+ if deferredError != nil {
+ os.Remove(current)
+ }
+ }()
+ }
+ }
+ // We enable the controllers for all the path components except the last one. It is not allowed to add
+ // PIDs if there are already enabled controllers.
+ if i < len(elements[3:])-1 {
+ if err := ioutil.WriteFile(filepath.Join(current, "cgroup.subtree_control"), res, 0o755); err != nil {
+ return err
+ }
+ }
+ }
+ return nil
+}
+
+func (c *CgroupControl) createCgroupDirectory(controller string) (bool, error) {
+ cPath := c.getCgroupv1Path(controller)
+ _, err := os.Stat(cPath)
+ if err == nil {
+ return false, nil
+ }
+
+ if !os.IsNotExist(err) {
+ return false, err
+ }
+
+ if err := os.MkdirAll(cPath, 0o755); err != nil {
+ return false, errors.Wrapf(err, "error creating cgroup for %s", controller)
+ }
+ return true, nil
+}
diff --git a/vendor/github.com/containers/common/pkg/cgroups/utils_linux.go b/vendor/github.com/containers/common/pkg/cgroups/utils_linux.go
new file mode 100644
index 000000000..bd37042cd
--- /dev/null
+++ b/vendor/github.com/containers/common/pkg/cgroups/utils_linux.go
@@ -0,0 +1,146 @@
+//go:build linux
+// +build linux
+
+package cgroups
+
+import (
+ "bytes"
+ "fmt"
+ "os"
+ "path"
+ "path/filepath"
+ "strings"
+
+ "github.com/opencontainers/runc/libcontainer/cgroups"
+ "github.com/opencontainers/runc/libcontainer/configs"
+ "github.com/pkg/errors"
+ "github.com/sirupsen/logrus"
+ "golang.org/x/sys/unix"
+)
+
+// WriteFile writes to a cgroup file
+func WriteFile(dir, file, data string) error {
+ fd, err := OpenFile(dir, file, unix.O_WRONLY)
+ if err != nil {
+ return err
+ }
+ defer fd.Close()
+ for {
+ _, err := fd.Write([]byte(data))
+ if errors.Is(err, unix.EINTR) {
+ logrus.Infof("interrupted while writing %s to %s", data, fd.Name())
+ continue
+ }
+ return err
+ }
+}
+
+// OpenFile opens a cgroup file with the given flags
+func OpenFile(dir, file string, flags int) (*os.File, error) {
+ var resolveFlags uint64
+ mode := os.FileMode(0)
+ if TestMode && flags&os.O_WRONLY != 0 {
+ flags |= os.O_TRUNC | os.O_CREATE
+ mode = 0o600
+ }
+ cgroupPath := path.Join(dir, file)
+ relPath := strings.TrimPrefix(cgroupPath, cgroupRoot+"/")
+
+ var stats unix.Statfs_t
+ fdTest, errOpen := unix.Openat2(-1, cgroupRoot, &unix.OpenHow{
+ Flags: unix.O_DIRECTORY | unix.O_PATH,
+ })
+ errStat := unix.Fstatfs(fdTest, &stats)
+ cgroupFd := fdTest
+
+ resolveFlags = unix.RESOLVE_BENEATH | unix.RESOLVE_NO_MAGICLINKS
+ if stats.Type == unix.CGROUP2_SUPER_MAGIC {
+ // cgroupv2 has a single mountpoint and no "cpu,cpuacct" symlinks
+ resolveFlags |= unix.RESOLVE_NO_XDEV | unix.RESOLVE_NO_SYMLINKS
+ }
+
+ if errOpen != nil || errStat != nil || (len(relPath) == len(cgroupPath)) { // openat2 not available, use os
+ fdTest, err := os.OpenFile(cgroupPath, flags, mode)
+ if err != nil {
+ return nil, err
+ }
+ if TestMode {
+ return fdTest, nil
+ }
+ if err := unix.Fstatfs(int(fdTest.Fd()), &stats); err != nil {
+ _ = fdTest.Close()
+ return nil, &os.PathError{Op: "statfs", Path: cgroupPath, Err: err}
+ }
+ if stats.Type != unix.CGROUP_SUPER_MAGIC && stats.Type != unix.CGROUP2_SUPER_MAGIC {
+ _ = fdTest.Close()
+ return nil, &os.PathError{Op: "open", Path: cgroupPath, Err: errors.New("not a cgroup file")}
+ }
+ return fdTest, nil
+ }
+
+ fd, err := unix.Openat2(cgroupFd, relPath,
+ &unix.OpenHow{
+ Resolve: resolveFlags,
+ Flags: uint64(flags) | unix.O_CLOEXEC,
+ Mode: uint64(mode),
+ })
+ if err != nil {
+ fmt.Println("Error in openat")
+ return nil, err
+ }
+
+ return os.NewFile(uintptr(fd), cgroupPath), nil
+}
+
+// ReadFile reads from a cgroup file, opening it with the read only flag
+func ReadFile(dir, file string) (string, error) {
+ fd, err := OpenFile(dir, file, unix.O_RDONLY)
+ if err != nil {
+ return "", err
+ }
+ defer fd.Close()
+ var buf bytes.Buffer
+
+ _, err = buf.ReadFrom(fd)
+ return buf.String(), err
+}
+
+// GetBlkioFiles gets the proper files for blkio weights
+func GetBlkioFiles(cgroupPath string) (wtFile, wtDevFile string) {
+ var weightFile string
+ var weightDeviceFile string
+ // in this important since runc keeps these variables private, they won't be set
+ if cgroups.PathExists(filepath.Join(cgroupPath, "blkio.weight")) {
+ weightFile = "blkio.weight"
+ weightDeviceFile = "blkio.weight_device"
+ } else {
+ weightFile = "blkio.bfq.weight"
+ weightDeviceFile = "blkio.bfq.weight_device"
+ }
+ return weightFile, weightDeviceFile
+}
+
+// SetBlkioThrottle sets the throttle limits for the cgroup
+func SetBlkioThrottle(res *configs.Resources, cgroupPath string) error {
+ for _, td := range res.BlkioThrottleReadBpsDevice {
+ if err := WriteFile(cgroupPath, "blkio.throttle.read_bps_device", fmt.Sprintf("%d:%d %d", td.Major, td.Minor, td.Rate)); err != nil {
+ return err
+ }
+ }
+ for _, td := range res.BlkioThrottleWriteBpsDevice {
+ if err := WriteFile(cgroupPath, "blkio.throttle.write_bps_device", fmt.Sprintf("%d:%d %d", td.Major, td.Minor, td.Rate)); err != nil {
+ return err
+ }
+ }
+ for _, td := range res.BlkioThrottleReadIOPSDevice {
+ if err := WriteFile(cgroupPath, "blkio.throttle.read_iops_device", td.String()); err != nil {
+ return err
+ }
+ }
+ for _, td := range res.BlkioThrottleWriteIOPSDevice {
+ if err := WriteFile(cgroupPath, "blkio.throttle.write_iops_device", td.String()); err != nil {
+ return err
+ }
+ }
+ return nil
+}
diff --git a/vendor/github.com/containers/common/pkg/parse/parse.go b/vendor/github.com/containers/common/pkg/parse/parse.go
index 6c4958cc2..43b783e0c 100644
--- a/vendor/github.com/containers/common/pkg/parse/parse.go
+++ b/vendor/github.com/containers/common/pkg/parse/parse.go
@@ -14,7 +14,7 @@ import (
// ValidateVolumeOpts validates a volume's options
func ValidateVolumeOpts(options []string) ([]string, error) {
- var foundRootPropagation, foundRWRO, foundLabelChange, bindType, foundExec, foundDev, foundSuid, foundChown, foundUpperDir, foundWorkDir int
+ var foundRootPropagation, foundRWRO, foundLabelChange, bindType, foundExec, foundDev, foundSuid, foundChown, foundUpperDir, foundWorkDir, foundCopy int
finalOpts := make([]string, 0, len(options))
for _, opt := range options {
// support advanced options like upperdir=/path, workdir=/path
@@ -88,6 +88,11 @@ func ValidateVolumeOpts(options []string) ([]string, error) {
// are intended to be always safe to use, even not on OS
// X).
continue
+ case "copy", "nocopy":
+ foundCopy++
+ if foundCopy > 1 {
+ return nil, errors.Errorf("invalid options %q, can only specify 1 'copy' or 'nocopy' option", strings.Join(options, ", "))
+ }
default:
return nil, errors.Errorf("invalid option type %q", opt)
}
diff --git a/vendor/github.com/containers/common/pkg/seccomp/filter.go b/vendor/github.com/containers/common/pkg/seccomp/filter.go
index 609036c82..7f1783efb 100644
--- a/vendor/github.com/containers/common/pkg/seccomp/filter.go
+++ b/vendor/github.com/containers/common/pkg/seccomp/filter.go
@@ -168,7 +168,8 @@ func matchSyscall(filter *libseccomp.ScmpFilter, call *Syscall) error {
func toAction(act Action, errnoRet *uint) (libseccomp.ScmpAction, error) {
switch act {
case ActKill:
- return libseccomp.ActKill, nil
+ // lint was not passing until this was changed from ActKill to ActKilThread.
+ return libseccomp.ActKillThread, nil
case ActKillProcess:
return libseccomp.ActKillProcess, nil
case ActErrno:
diff --git a/vendor/github.com/opencontainers/runc/libcontainer/cgroups/cgroups.go b/vendor/github.com/opencontainers/runc/libcontainer/cgroups/cgroups.go
index ba2b2266c..b9ba889b7 100644
--- a/vendor/github.com/opencontainers/runc/libcontainer/cgroups/cgroups.go
+++ b/vendor/github.com/opencontainers/runc/libcontainer/cgroups/cgroups.go
@@ -1,9 +1,24 @@
package cgroups
import (
+ "errors"
+
"github.com/opencontainers/runc/libcontainer/configs"
)
+var (
+ // ErrDevicesUnsupported is an error returned when a cgroup manager
+ // is not configured to set device rules.
+ ErrDevicesUnsupported = errors.New("cgroup manager is not configured to set device rules")
+
+ // DevicesSetV1 and DevicesSetV2 are functions to set devices for
+ // cgroup v1 and v2, respectively. Unless libcontainer/cgroups/devices
+ // package is imported, it is set to nil, so cgroup managers can't
+ // manage devices.
+ DevicesSetV1 func(path string, r *configs.Resources) error
+ DevicesSetV2 func(path string, r *configs.Resources) error
+)
+
type Manager interface {
// Apply creates a cgroup, if not yet created, and adds a process
// with the specified pid into that cgroup. A special value of -1
diff --git a/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/blkio.go b/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/blkio.go
new file mode 100644
index 000000000..c81b6562a
--- /dev/null
+++ b/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/blkio.go
@@ -0,0 +1,311 @@
+package fs
+
+import (
+ "bufio"
+ "os"
+ "path/filepath"
+ "strconv"
+ "strings"
+
+ "github.com/opencontainers/runc/libcontainer/cgroups"
+ "github.com/opencontainers/runc/libcontainer/configs"
+)
+
+type BlkioGroup struct {
+ weightFilename string
+ weightDeviceFilename string
+}
+
+func (s *BlkioGroup) Name() string {
+ return "blkio"
+}
+
+func (s *BlkioGroup) Apply(path string, _ *configs.Resources, pid int) error {
+ return apply(path, pid)
+}
+
+func (s *BlkioGroup) Set(path string, r *configs.Resources) error {
+ s.detectWeightFilenames(path)
+ if r.BlkioWeight != 0 {
+ if err := cgroups.WriteFile(path, s.weightFilename, strconv.FormatUint(uint64(r.BlkioWeight), 10)); err != nil {
+ return err
+ }
+ }
+
+ if r.BlkioLeafWeight != 0 {
+ if err := cgroups.WriteFile(path, "blkio.leaf_weight", strconv.FormatUint(uint64(r.BlkioLeafWeight), 10)); err != nil {
+ return err
+ }
+ }
+ for _, wd := range r.BlkioWeightDevice {
+ if wd.Weight != 0 {
+ if err := cgroups.WriteFile(path, s.weightDeviceFilename, wd.WeightString()); err != nil {
+ return err
+ }
+ }
+ if wd.LeafWeight != 0 {
+ if err := cgroups.WriteFile(path, "blkio.leaf_weight_device", wd.LeafWeightString()); err != nil {
+ return err
+ }
+ }
+ }
+ for _, td := range r.BlkioThrottleReadBpsDevice {
+ if err := cgroups.WriteFile(path, "blkio.throttle.read_bps_device", td.String()); err != nil {
+ return err
+ }
+ }
+ for _, td := range r.BlkioThrottleWriteBpsDevice {
+ if err := cgroups.WriteFile(path, "blkio.throttle.write_bps_device", td.String()); err != nil {
+ return err
+ }
+ }
+ for _, td := range r.BlkioThrottleReadIOPSDevice {
+ if err := cgroups.WriteFile(path, "blkio.throttle.read_iops_device", td.String()); err != nil {
+ return err
+ }
+ }
+ for _, td := range r.BlkioThrottleWriteIOPSDevice {
+ if err := cgroups.WriteFile(path, "blkio.throttle.write_iops_device", td.String()); err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
+
+/*
+examples:
+
+ blkio.sectors
+ 8:0 6792
+
+ blkio.io_service_bytes
+ 8:0 Read 1282048
+ 8:0 Write 2195456
+ 8:0 Sync 2195456
+ 8:0 Async 1282048
+ 8:0 Total 3477504
+ Total 3477504
+
+ blkio.io_serviced
+ 8:0 Read 124
+ 8:0 Write 104
+ 8:0 Sync 104
+ 8:0 Async 124
+ 8:0 Total 228
+ Total 228
+
+ blkio.io_queued
+ 8:0 Read 0
+ 8:0 Write 0
+ 8:0 Sync 0
+ 8:0 Async 0
+ 8:0 Total 0
+ Total 0
+*/
+
+func splitBlkioStatLine(r rune) bool {
+ return r == ' ' || r == ':'
+}
+
+func getBlkioStat(dir, file string) ([]cgroups.BlkioStatEntry, error) {
+ var blkioStats []cgroups.BlkioStatEntry
+ f, err := cgroups.OpenFile(dir, file, os.O_RDONLY)
+ if err != nil {
+ if os.IsNotExist(err) {
+ return blkioStats, nil
+ }
+ return nil, err
+ }
+ defer f.Close()
+
+ sc := bufio.NewScanner(f)
+ for sc.Scan() {
+ // format: dev type amount
+ fields := strings.FieldsFunc(sc.Text(), splitBlkioStatLine)
+ if len(fields) < 3 {
+ if len(fields) == 2 && fields[0] == "Total" {
+ // skip total line
+ continue
+ } else {
+ return nil, malformedLine(dir, file, sc.Text())
+ }
+ }
+
+ v, err := strconv.ParseUint(fields[0], 10, 64)
+ if err != nil {
+ return nil, &parseError{Path: dir, File: file, Err: err}
+ }
+ major := v
+
+ v, err = strconv.ParseUint(fields[1], 10, 64)
+ if err != nil {
+ return nil, &parseError{Path: dir, File: file, Err: err}
+ }
+ minor := v
+
+ op := ""
+ valueField := 2
+ if len(fields) == 4 {
+ op = fields[2]
+ valueField = 3
+ }
+ v, err = strconv.ParseUint(fields[valueField], 10, 64)
+ if err != nil {
+ return nil, &parseError{Path: dir, File: file, Err: err}
+ }
+ blkioStats = append(blkioStats, cgroups.BlkioStatEntry{Major: major, Minor: minor, Op: op, Value: v})
+ }
+ if err := sc.Err(); err != nil {
+ return nil, &parseError{Path: dir, File: file, Err: err}
+ }
+
+ return blkioStats, nil
+}
+
+func (s *BlkioGroup) GetStats(path string, stats *cgroups.Stats) error {
+ type blkioStatInfo struct {
+ filename string
+ blkioStatEntriesPtr *[]cgroups.BlkioStatEntry
+ }
+ bfqDebugStats := []blkioStatInfo{
+ {
+ filename: "blkio.bfq.sectors_recursive",
+ blkioStatEntriesPtr: &stats.BlkioStats.SectorsRecursive,
+ },
+ {
+ filename: "blkio.bfq.io_service_time_recursive",
+ blkioStatEntriesPtr: &stats.BlkioStats.IoServiceTimeRecursive,
+ },
+ {
+ filename: "blkio.bfq.io_wait_time_recursive",
+ blkioStatEntriesPtr: &stats.BlkioStats.IoWaitTimeRecursive,
+ },
+ {
+ filename: "blkio.bfq.io_merged_recursive",
+ blkioStatEntriesPtr: &stats.BlkioStats.IoMergedRecursive,
+ },
+ {
+ filename: "blkio.bfq.io_queued_recursive",
+ blkioStatEntriesPtr: &stats.BlkioStats.IoQueuedRecursive,
+ },
+ {
+ filename: "blkio.bfq.time_recursive",
+ blkioStatEntriesPtr: &stats.BlkioStats.IoTimeRecursive,
+ },
+ {
+ filename: "blkio.bfq.io_serviced_recursive",
+ blkioStatEntriesPtr: &stats.BlkioStats.IoServicedRecursive,
+ },
+ {
+ filename: "blkio.bfq.io_service_bytes_recursive",
+ blkioStatEntriesPtr: &stats.BlkioStats.IoServiceBytesRecursive,
+ },
+ }
+ bfqStats := []blkioStatInfo{
+ {
+ filename: "blkio.bfq.io_serviced_recursive",
+ blkioStatEntriesPtr: &stats.BlkioStats.IoServicedRecursive,
+ },
+ {
+ filename: "blkio.bfq.io_service_bytes_recursive",
+ blkioStatEntriesPtr: &stats.BlkioStats.IoServiceBytesRecursive,
+ },
+ }
+ cfqStats := []blkioStatInfo{
+ {
+ filename: "blkio.sectors_recursive",
+ blkioStatEntriesPtr: &stats.BlkioStats.SectorsRecursive,
+ },
+ {
+ filename: "blkio.io_service_time_recursive",
+ blkioStatEntriesPtr: &stats.BlkioStats.IoServiceTimeRecursive,
+ },
+ {
+ filename: "blkio.io_wait_time_recursive",
+ blkioStatEntriesPtr: &stats.BlkioStats.IoWaitTimeRecursive,
+ },
+ {
+ filename: "blkio.io_merged_recursive",
+ blkioStatEntriesPtr: &stats.BlkioStats.IoMergedRecursive,
+ },
+ {
+ filename: "blkio.io_queued_recursive",
+ blkioStatEntriesPtr: &stats.BlkioStats.IoQueuedRecursive,
+ },
+ {
+ filename: "blkio.time_recursive",
+ blkioStatEntriesPtr: &stats.BlkioStats.IoTimeRecursive,
+ },
+ {
+ filename: "blkio.io_serviced_recursive",
+ blkioStatEntriesPtr: &stats.BlkioStats.IoServicedRecursive,
+ },
+ {
+ filename: "blkio.io_service_bytes_recursive",
+ blkioStatEntriesPtr: &stats.BlkioStats.IoServiceBytesRecursive,
+ },
+ }
+ throttleRecursiveStats := []blkioStatInfo{
+ {
+ filename: "blkio.throttle.io_serviced_recursive",
+ blkioStatEntriesPtr: &stats.BlkioStats.IoServicedRecursive,
+ },
+ {
+ filename: "blkio.throttle.io_service_bytes_recursive",
+ blkioStatEntriesPtr: &stats.BlkioStats.IoServiceBytesRecursive,
+ },
+ }
+ baseStats := []blkioStatInfo{
+ {
+ filename: "blkio.throttle.io_serviced",
+ blkioStatEntriesPtr: &stats.BlkioStats.IoServicedRecursive,
+ },
+ {
+ filename: "blkio.throttle.io_service_bytes",
+ blkioStatEntriesPtr: &stats.BlkioStats.IoServiceBytesRecursive,
+ },
+ }
+ orderedStats := [][]blkioStatInfo{
+ bfqDebugStats,
+ bfqStats,
+ cfqStats,
+ throttleRecursiveStats,
+ baseStats,
+ }
+
+ var blkioStats []cgroups.BlkioStatEntry
+ var err error
+
+ for _, statGroup := range orderedStats {
+ for i, statInfo := range statGroup {
+ if blkioStats, err = getBlkioStat(path, statInfo.filename); err != nil || blkioStats == nil {
+ // if error occurs on first file, move to next group
+ if i == 0 {
+ break
+ }
+ return err
+ }
+ *statInfo.blkioStatEntriesPtr = blkioStats
+ // finish if all stats are gathered
+ if i == len(statGroup)-1 {
+ return nil
+ }
+ }
+ }
+ return nil
+}
+
+func (s *BlkioGroup) detectWeightFilenames(path string) {
+ if s.weightFilename != "" {
+ // Already detected.
+ return
+ }
+ if cgroups.PathExists(filepath.Join(path, "blkio.weight")) {
+ s.weightFilename = "blkio.weight"
+ s.weightDeviceFilename = "blkio.weight_device"
+ } else {
+ s.weightFilename = "blkio.bfq.weight"
+ s.weightDeviceFilename = "blkio.bfq.weight_device"
+ }
+}
diff --git a/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/cpu.go b/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/cpu.go
new file mode 100644
index 000000000..6c79f899b
--- /dev/null
+++ b/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/cpu.go
@@ -0,0 +1,129 @@
+package fs
+
+import (
+ "bufio"
+ "errors"
+ "fmt"
+ "os"
+ "strconv"
+
+ "github.com/opencontainers/runc/libcontainer/cgroups"
+ "github.com/opencontainers/runc/libcontainer/cgroups/fscommon"
+ "github.com/opencontainers/runc/libcontainer/configs"
+ "golang.org/x/sys/unix"
+)
+
+type CpuGroup struct{}
+
+func (s *CpuGroup) Name() string {
+ return "cpu"
+}
+
+func (s *CpuGroup) Apply(path string, r *configs.Resources, pid int) error {
+ if err := os.MkdirAll(path, 0o755); err != nil {
+ return err
+ }
+ // We should set the real-Time group scheduling settings before moving
+ // in the process because if the process is already in SCHED_RR mode
+ // and no RT bandwidth is set, adding it will fail.
+ if err := s.SetRtSched(path, r); err != nil {
+ return err
+ }
+ // Since we are not using apply(), we need to place the pid
+ // into the procs file.
+ return cgroups.WriteCgroupProc(path, pid)
+}
+
+func (s *CpuGroup) SetRtSched(path string, r *configs.Resources) error {
+ if r.CpuRtPeriod != 0 {
+ if err := cgroups.WriteFile(path, "cpu.rt_period_us", strconv.FormatUint(r.CpuRtPeriod, 10)); err != nil {
+ return err
+ }
+ }
+ if r.CpuRtRuntime != 0 {
+ if err := cgroups.WriteFile(path, "cpu.rt_runtime_us", strconv.FormatInt(r.CpuRtRuntime, 10)); err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+func (s *CpuGroup) Set(path string, r *configs.Resources) error {
+ if r.CpuShares != 0 {
+ shares := r.CpuShares
+ if err := cgroups.WriteFile(path, "cpu.shares", strconv.FormatUint(shares, 10)); err != nil {
+ return err
+ }
+ // read it back
+ sharesRead, err := fscommon.GetCgroupParamUint(path, "cpu.shares")
+ if err != nil {
+ return err
+ }
+ // ... and check
+ if shares > sharesRead {
+ return fmt.Errorf("the maximum allowed cpu-shares is %d", sharesRead)
+ } else if shares < sharesRead {
+ return fmt.Errorf("the minimum allowed cpu-shares is %d", sharesRead)
+ }
+ }
+
+ var period string
+ if r.CpuPeriod != 0 {
+ period = strconv.FormatUint(r.CpuPeriod, 10)
+ if err := cgroups.WriteFile(path, "cpu.cfs_period_us", period); err != nil {
+ // Sometimes when the period to be set is smaller
+ // than the current one, it is rejected by the kernel
+ // (EINVAL) as old_quota/new_period exceeds the parent
+ // cgroup quota limit. If this happens and the quota is
+ // going to be set, ignore the error for now and retry
+ // after setting the quota.
+ if !errors.Is(err, unix.EINVAL) || r.CpuQuota == 0 {
+ return err
+ }
+ } else {
+ period = ""
+ }
+ }
+ if r.CpuQuota != 0 {
+ if err := cgroups.WriteFile(path, "cpu.cfs_quota_us", strconv.FormatInt(r.CpuQuota, 10)); err != nil {
+ return err
+ }
+ if period != "" {
+ if err := cgroups.WriteFile(path, "cpu.cfs_period_us", period); err != nil {
+ return err
+ }
+ }
+ }
+ return s.SetRtSched(path, r)
+}
+
+func (s *CpuGroup) GetStats(path string, stats *cgroups.Stats) error {
+ const file = "cpu.stat"
+ f, err := cgroups.OpenFile(path, file, os.O_RDONLY)
+ if err != nil {
+ if os.IsNotExist(err) {
+ return nil
+ }
+ return err
+ }
+ defer f.Close()
+
+ sc := bufio.NewScanner(f)
+ for sc.Scan() {
+ t, v, err := fscommon.ParseKeyValue(sc.Text())
+ if err != nil {
+ return &parseError{Path: path, File: file, Err: err}
+ }
+ switch t {
+ case "nr_periods":
+ stats.CpuStats.ThrottlingData.Periods = v
+
+ case "nr_throttled":
+ stats.CpuStats.ThrottlingData.ThrottledPeriods = v
+
+ case "throttled_time":
+ stats.CpuStats.ThrottlingData.ThrottledTime = v
+ }
+ }
+ return nil
+}
diff --git a/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/cpuacct.go b/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/cpuacct.go
new file mode 100644
index 000000000..d3bd7e111
--- /dev/null
+++ b/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/cpuacct.go
@@ -0,0 +1,166 @@
+package fs
+
+import (
+ "bufio"
+ "os"
+ "strconv"
+ "strings"
+
+ "github.com/opencontainers/runc/libcontainer/cgroups"
+ "github.com/opencontainers/runc/libcontainer/cgroups/fscommon"
+ "github.com/opencontainers/runc/libcontainer/configs"
+)
+
+const (
+ cgroupCpuacctStat = "cpuacct.stat"
+ cgroupCpuacctUsageAll = "cpuacct.usage_all"
+
+ nanosecondsInSecond = 1000000000
+
+ userModeColumn = 1
+ kernelModeColumn = 2
+ cuacctUsageAllColumnsNumber = 3
+
+ // The value comes from `C.sysconf(C._SC_CLK_TCK)`, and
+ // on Linux it's a constant which is safe to be hard coded,
+ // so we can avoid using cgo here. For details, see:
+ // https://github.com/containerd/cgroups/pull/12
+ clockTicks uint64 = 100
+)
+
+type CpuacctGroup struct{}
+
+func (s *CpuacctGroup) Name() string {
+ return "cpuacct"
+}
+
+func (s *CpuacctGroup) Apply(path string, _ *configs.Resources, pid int) error {
+ return apply(path, pid)
+}
+
+func (s *CpuacctGroup) Set(_ string, _ *configs.Resources) error {
+ return nil
+}
+
+func (s *CpuacctGroup) GetStats(path string, stats *cgroups.Stats) error {
+ if !cgroups.PathExists(path) {
+ return nil
+ }
+ userModeUsage, kernelModeUsage, err := getCpuUsageBreakdown(path)
+ if err != nil {
+ return err
+ }
+
+ totalUsage, err := fscommon.GetCgroupParamUint(path, "cpuacct.usage")
+ if err != nil {
+ return err
+ }
+
+ percpuUsage, err := getPercpuUsage(path)
+ if err != nil {
+ return err
+ }
+
+ percpuUsageInKernelmode, percpuUsageInUsermode, err := getPercpuUsageInModes(path)
+ if err != nil {
+ return err
+ }
+
+ stats.CpuStats.CpuUsage.TotalUsage = totalUsage
+ stats.CpuStats.CpuUsage.PercpuUsage = percpuUsage
+ stats.CpuStats.CpuUsage.PercpuUsageInKernelmode = percpuUsageInKernelmode
+ stats.CpuStats.CpuUsage.PercpuUsageInUsermode = percpuUsageInUsermode
+ stats.CpuStats.CpuUsage.UsageInUsermode = userModeUsage
+ stats.CpuStats.CpuUsage.UsageInKernelmode = kernelModeUsage
+ return nil
+}
+
+// Returns user and kernel usage breakdown in nanoseconds.
+func getCpuUsageBreakdown(path string) (uint64, uint64, error) {
+ var userModeUsage, kernelModeUsage uint64
+ const (
+ userField = "user"
+ systemField = "system"
+ file = cgroupCpuacctStat
+ )
+
+ // Expected format:
+ // user <usage in ticks>
+ // system <usage in ticks>
+ data, err := cgroups.ReadFile(path, file)
+ if err != nil {
+ return 0, 0, err
+ }
+ // TODO: use strings.SplitN instead.
+ fields := strings.Fields(data)
+ if len(fields) < 4 || fields[0] != userField || fields[2] != systemField {
+ return 0, 0, malformedLine(path, file, data)
+ }
+ if userModeUsage, err = strconv.ParseUint(fields[1], 10, 64); err != nil {
+ return 0, 0, &parseError{Path: path, File: file, Err: err}
+ }
+ if kernelModeUsage, err = strconv.ParseUint(fields[3], 10, 64); err != nil {
+ return 0, 0, &parseError{Path: path, File: file, Err: err}
+ }
+
+ return (userModeUsage * nanosecondsInSecond) / clockTicks, (kernelModeUsage * nanosecondsInSecond) / clockTicks, nil
+}
+
+func getPercpuUsage(path string) ([]uint64, error) {
+ const file = "cpuacct.usage_percpu"
+ percpuUsage := []uint64{}
+ data, err := cgroups.ReadFile(path, file)
+ if err != nil {
+ return percpuUsage, err
+ }
+ // TODO: use strings.SplitN instead.
+ for _, value := range strings.Fields(data) {
+ value, err := strconv.ParseUint(value, 10, 64)
+ if err != nil {
+ return percpuUsage, &parseError{Path: path, File: file, Err: err}
+ }
+ percpuUsage = append(percpuUsage, value)
+ }
+ return percpuUsage, nil
+}
+
+func getPercpuUsageInModes(path string) ([]uint64, []uint64, error) {
+ usageKernelMode := []uint64{}
+ usageUserMode := []uint64{}
+ const file = cgroupCpuacctUsageAll
+
+ fd, err := cgroups.OpenFile(path, file, os.O_RDONLY)
+ if os.IsNotExist(err) {
+ return usageKernelMode, usageUserMode, nil
+ } else if err != nil {
+ return nil, nil, err
+ }
+ defer fd.Close()
+
+ scanner := bufio.NewScanner(fd)
+ scanner.Scan() // skipping header line
+
+ for scanner.Scan() {
+ lineFields := strings.SplitN(scanner.Text(), " ", cuacctUsageAllColumnsNumber+1)
+ if len(lineFields) != cuacctUsageAllColumnsNumber {
+ continue
+ }
+
+ usageInKernelMode, err := strconv.ParseUint(lineFields[kernelModeColumn], 10, 64)
+ if err != nil {
+ return nil, nil, &parseError{Path: path, File: file, Err: err}
+ }
+ usageKernelMode = append(usageKernelMode, usageInKernelMode)
+
+ usageInUserMode, err := strconv.ParseUint(lineFields[userModeColumn], 10, 64)
+ if err != nil {
+ return nil, nil, &parseError{Path: path, File: file, Err: err}
+ }
+ usageUserMode = append(usageUserMode, usageInUserMode)
+ }
+ if err := scanner.Err(); err != nil {
+ return nil, nil, &parseError{Path: path, File: file, Err: err}
+ }
+
+ return usageKernelMode, usageUserMode, nil
+}
diff --git a/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/cpuset.go b/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/cpuset.go
new file mode 100644
index 000000000..550baa427
--- /dev/null
+++ b/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/cpuset.go
@@ -0,0 +1,245 @@
+package fs
+
+import (
+ "errors"
+ "os"
+ "path/filepath"
+ "strconv"
+ "strings"
+
+ "golang.org/x/sys/unix"
+
+ "github.com/opencontainers/runc/libcontainer/cgroups"
+ "github.com/opencontainers/runc/libcontainer/cgroups/fscommon"
+ "github.com/opencontainers/runc/libcontainer/configs"
+)
+
+type CpusetGroup struct{}
+
+func (s *CpusetGroup) Name() string {
+ return "cpuset"
+}
+
+func (s *CpusetGroup) Apply(path string, r *configs.Resources, pid int) error {
+ return s.ApplyDir(path, r, pid)
+}
+
+func (s *CpusetGroup) Set(path string, r *configs.Resources) error {
+ if r.CpusetCpus != "" {
+ if err := cgroups.WriteFile(path, "cpuset.cpus", r.CpusetCpus); err != nil {
+ return err
+ }
+ }
+ if r.CpusetMems != "" {
+ if err := cgroups.WriteFile(path, "cpuset.mems", r.CpusetMems); err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+func getCpusetStat(path string, file string) ([]uint16, error) {
+ var extracted []uint16
+ fileContent, err := fscommon.GetCgroupParamString(path, file)
+ if err != nil {
+ return extracted, err
+ }
+ if len(fileContent) == 0 {
+ return extracted, &parseError{Path: path, File: file, Err: errors.New("empty file")}
+ }
+
+ for _, s := range strings.Split(fileContent, ",") {
+ sp := strings.SplitN(s, "-", 3)
+ switch len(sp) {
+ case 3:
+ return extracted, &parseError{Path: path, File: file, Err: errors.New("extra dash")}
+ case 2:
+ min, err := strconv.ParseUint(sp[0], 10, 16)
+ if err != nil {
+ return extracted, &parseError{Path: path, File: file, Err: err}
+ }
+ max, err := strconv.ParseUint(sp[1], 10, 16)
+ if err != nil {
+ return extracted, &parseError{Path: path, File: file, Err: err}
+ }
+ if min > max {
+ return extracted, &parseError{Path: path, File: file, Err: errors.New("invalid values, min > max")}
+ }
+ for i := min; i <= max; i++ {
+ extracted = append(extracted, uint16(i))
+ }
+ case 1:
+ value, err := strconv.ParseUint(s, 10, 16)
+ if err != nil {
+ return extracted, &parseError{Path: path, File: file, Err: err}
+ }
+ extracted = append(extracted, uint16(value))
+ }
+ }
+
+ return extracted, nil
+}
+
+func (s *CpusetGroup) GetStats(path string, stats *cgroups.Stats) error {
+ var err error
+
+ stats.CPUSetStats.CPUs, err = getCpusetStat(path, "cpuset.cpus")
+ if err != nil && !errors.Is(err, os.ErrNotExist) {
+ return err
+ }
+
+ stats.CPUSetStats.CPUExclusive, err = fscommon.GetCgroupParamUint(path, "cpuset.cpu_exclusive")
+ if err != nil && !errors.Is(err, os.ErrNotExist) {
+ return err
+ }
+
+ stats.CPUSetStats.Mems, err = getCpusetStat(path, "cpuset.mems")
+ if err != nil && !errors.Is(err, os.ErrNotExist) {
+ return err
+ }
+
+ stats.CPUSetStats.MemHardwall, err = fscommon.GetCgroupParamUint(path, "cpuset.mem_hardwall")
+ if err != nil && !errors.Is(err, os.ErrNotExist) {
+ return err
+ }
+
+ stats.CPUSetStats.MemExclusive, err = fscommon.GetCgroupParamUint(path, "cpuset.mem_exclusive")
+ if err != nil && !errors.Is(err, os.ErrNotExist) {
+ return err
+ }
+
+ stats.CPUSetStats.MemoryMigrate, err = fscommon.GetCgroupParamUint(path, "cpuset.memory_migrate")
+ if err != nil && !errors.Is(err, os.ErrNotExist) {
+ return err
+ }
+
+ stats.CPUSetStats.MemorySpreadPage, err = fscommon.GetCgroupParamUint(path, "cpuset.memory_spread_page")
+ if err != nil && !errors.Is(err, os.ErrNotExist) {
+ return err
+ }
+
+ stats.CPUSetStats.MemorySpreadSlab, err = fscommon.GetCgroupParamUint(path, "cpuset.memory_spread_slab")
+ if err != nil && !errors.Is(err, os.ErrNotExist) {
+ return err
+ }
+
+ stats.CPUSetStats.MemoryPressure, err = fscommon.GetCgroupParamUint(path, "cpuset.memory_pressure")
+ if err != nil && !errors.Is(err, os.ErrNotExist) {
+ return err
+ }
+
+ stats.CPUSetStats.SchedLoadBalance, err = fscommon.GetCgroupParamUint(path, "cpuset.sched_load_balance")
+ if err != nil && !errors.Is(err, os.ErrNotExist) {
+ return err
+ }
+
+ stats.CPUSetStats.SchedRelaxDomainLevel, err = fscommon.GetCgroupParamInt(path, "cpuset.sched_relax_domain_level")
+ if err != nil && !errors.Is(err, os.ErrNotExist) {
+ return err
+ }
+
+ return nil
+}
+
+func (s *CpusetGroup) ApplyDir(dir string, r *configs.Resources, pid int) error {
+ // This might happen if we have no cpuset cgroup mounted.
+ // Just do nothing and don't fail.
+ if dir == "" {
+ return nil
+ }
+ // 'ensureParent' start with parent because we don't want to
+ // explicitly inherit from parent, it could conflict with
+ // 'cpuset.cpu_exclusive'.
+ if err := cpusetEnsureParent(filepath.Dir(dir)); err != nil {
+ return err
+ }
+ if err := os.Mkdir(dir, 0o755); err != nil && !os.IsExist(err) {
+ return err
+ }
+ // We didn't inherit cpuset configs from parent, but we have
+ // to ensure cpuset configs are set before moving task into the
+ // cgroup.
+ // The logic is, if user specified cpuset configs, use these
+ // specified configs, otherwise, inherit from parent. This makes
+ // cpuset configs work correctly with 'cpuset.cpu_exclusive', and
+ // keep backward compatibility.
+ if err := s.ensureCpusAndMems(dir, r); err != nil {
+ return err
+ }
+ // Since we are not using apply(), we need to place the pid
+ // into the procs file.
+ return cgroups.WriteCgroupProc(dir, pid)
+}
+
+func getCpusetSubsystemSettings(parent string) (cpus, mems string, err error) {
+ if cpus, err = cgroups.ReadFile(parent, "cpuset.cpus"); err != nil {
+ return
+ }
+ if mems, err = cgroups.ReadFile(parent, "cpuset.mems"); err != nil {
+ return
+ }
+ return cpus, mems, nil
+}
+
+// cpusetEnsureParent makes sure that the parent directories of current
+// are created and populated with the proper cpus and mems files copied
+// from their respective parent. It does that recursively, starting from
+// the top of the cpuset hierarchy (i.e. cpuset cgroup mount point).
+func cpusetEnsureParent(current string) error {
+ var st unix.Statfs_t
+
+ parent := filepath.Dir(current)
+ err := unix.Statfs(parent, &st)
+ if err == nil && st.Type != unix.CGROUP_SUPER_MAGIC {
+ return nil
+ }
+ // Treat non-existing directory as cgroupfs as it will be created,
+ // and the root cpuset directory obviously exists.
+ if err != nil && err != unix.ENOENT { //nolint:errorlint // unix errors are bare
+ return &os.PathError{Op: "statfs", Path: parent, Err: err}
+ }
+
+ if err := cpusetEnsureParent(parent); err != nil {
+ return err
+ }
+ if err := os.Mkdir(current, 0o755); err != nil && !os.IsExist(err) {
+ return err
+ }
+ return cpusetCopyIfNeeded(current, parent)
+}
+
+// cpusetCopyIfNeeded copies the cpuset.cpus and cpuset.mems from the parent
+// directory to the current directory if the file's contents are 0
+func cpusetCopyIfNeeded(current, parent string) error {
+ currentCpus, currentMems, err := getCpusetSubsystemSettings(current)
+ if err != nil {
+ return err
+ }
+ parentCpus, parentMems, err := getCpusetSubsystemSettings(parent)
+ if err != nil {
+ return err
+ }
+
+ if isEmptyCpuset(currentCpus) {
+ if err := cgroups.WriteFile(current, "cpuset.cpus", parentCpus); err != nil {
+ return err
+ }
+ }
+ if isEmptyCpuset(currentMems) {
+ if err := cgroups.WriteFile(current, "cpuset.mems", parentMems); err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+func isEmptyCpuset(str string) bool {
+ return str == "" || str == "\n"
+}
+
+func (s *CpusetGroup) ensureCpusAndMems(path string, r *configs.Resources) error {
+ if err := s.Set(path, r); err != nil {
+ return err
+ }
+ return cpusetCopyIfNeeded(path, filepath.Dir(path))
+}
diff --git a/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/devices.go b/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/devices.go
new file mode 100644
index 000000000..0bf3d9deb
--- /dev/null
+++ b/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/devices.go
@@ -0,0 +1,39 @@
+package fs
+
+import (
+ "github.com/opencontainers/runc/libcontainer/cgroups"
+ "github.com/opencontainers/runc/libcontainer/configs"
+)
+
+type DevicesGroup struct{}
+
+func (s *DevicesGroup) Name() string {
+ return "devices"
+}
+
+func (s *DevicesGroup) Apply(path string, r *configs.Resources, pid int) error {
+ if r.SkipDevices {
+ return nil
+ }
+ if path == "" {
+ // Return error here, since devices cgroup
+ // is a hard requirement for container's security.
+ return errSubsystemDoesNotExist
+ }
+
+ return apply(path, pid)
+}
+
+func (s *DevicesGroup) Set(path string, r *configs.Resources) error {
+ if cgroups.DevicesSetV1 == nil {
+ if len(r.Devices) == 0 {
+ return nil
+ }
+ return cgroups.ErrDevicesUnsupported
+ }
+ return cgroups.DevicesSetV1(path, r)
+}
+
+func (s *DevicesGroup) GetStats(path string, stats *cgroups.Stats) error {
+ return nil
+}
diff --git a/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/error.go b/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/error.go
new file mode 100644
index 000000000..f2ab6f130
--- /dev/null
+++ b/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/error.go
@@ -0,0 +1,15 @@
+package fs
+
+import (
+ "fmt"
+
+ "github.com/opencontainers/runc/libcontainer/cgroups/fscommon"
+)
+
+type parseError = fscommon.ParseError
+
+// malformedLine is used by all cgroupfs file parsers that expect a line
+// in a particular format but get some garbage instead.
+func malformedLine(path, file, line string) error {
+ return &parseError{Path: path, File: file, Err: fmt.Errorf("malformed line: %s", line)}
+}
diff --git a/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/freezer.go b/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/freezer.go
new file mode 100644
index 000000000..987f1bf5e
--- /dev/null
+++ b/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/freezer.go
@@ -0,0 +1,158 @@
+package fs
+
+import (
+ "errors"
+ "fmt"
+ "os"
+ "strings"
+ "time"
+
+ "github.com/opencontainers/runc/libcontainer/cgroups"
+ "github.com/opencontainers/runc/libcontainer/configs"
+ "github.com/sirupsen/logrus"
+ "golang.org/x/sys/unix"
+)
+
+type FreezerGroup struct{}
+
+func (s *FreezerGroup) Name() string {
+ return "freezer"
+}
+
+func (s *FreezerGroup) Apply(path string, _ *configs.Resources, pid int) error {
+ return apply(path, pid)
+}
+
+func (s *FreezerGroup) Set(path string, r *configs.Resources) (Err error) {
+ switch r.Freezer {
+ case configs.Frozen:
+ defer func() {
+ if Err != nil {
+ // Freezing failed, and it is bad and dangerous
+ // to leave the cgroup in FROZEN or FREEZING
+ // state, so (try to) thaw it back.
+ _ = cgroups.WriteFile(path, "freezer.state", string(configs.Thawed))
+ }
+ }()
+
+ // As per older kernel docs (freezer-subsystem.txt before
+ // kernel commit ef9fe980c6fcc1821), if FREEZING is seen,
+ // userspace should either retry or thaw. While current
+ // kernel cgroup v1 docs no longer mention a need to retry,
+ // even a recent kernel (v5.4, Ubuntu 20.04) can't reliably
+ // freeze a cgroup v1 while new processes keep appearing in it
+ // (either via fork/clone or by writing new PIDs to
+ // cgroup.procs).
+ //
+ // The numbers below are empirically chosen to have a decent
+ // chance to succeed in various scenarios ("runc pause/unpause
+ // with parallel runc exec" and "bare freeze/unfreeze on a very
+ // slow system"), tested on RHEL7 and Ubuntu 20.04 kernels.
+ //
+ // Adding any amount of sleep in between retries did not
+ // increase the chances of successful freeze in "pause/unpause
+ // with parallel exec" reproducer. OTOH, adding an occasional
+ // sleep helped for the case where the system is extremely slow
+ // (CentOS 7 VM on GHA CI).
+ //
+ // Alas, this is still a game of chances, since the real fix
+ // belong to the kernel (cgroup v2 do not have this bug).
+
+ for i := 0; i < 1000; i++ {
+ if i%50 == 49 {
+ // Occasional thaw and sleep improves
+ // the chances to succeed in freezing
+ // in case new processes keep appearing
+ // in the cgroup.
+ _ = cgroups.WriteFile(path, "freezer.state", string(configs.Thawed))
+ time.Sleep(10 * time.Millisecond)
+ }
+
+ if err := cgroups.WriteFile(path, "freezer.state", string(configs.Frozen)); err != nil {
+ return err
+ }
+
+ if i%25 == 24 {
+ // Occasional short sleep before reading
+ // the state back also improves the chances to
+ // succeed in freezing in case of a very slow
+ // system.
+ time.Sleep(10 * time.Microsecond)
+ }
+ state, err := cgroups.ReadFile(path, "freezer.state")
+ if err != nil {
+ return err
+ }
+ state = strings.TrimSpace(state)
+ switch state {
+ case "FREEZING":
+ continue
+ case string(configs.Frozen):
+ if i > 1 {
+ logrus.Debugf("frozen after %d retries", i)
+ }
+ return nil
+ default:
+ // should never happen
+ return fmt.Errorf("unexpected state %s while freezing", strings.TrimSpace(state))
+ }
+ }
+ // Despite our best efforts, it got stuck in FREEZING.
+ return errors.New("unable to freeze")
+ case configs.Thawed:
+ return cgroups.WriteFile(path, "freezer.state", string(configs.Thawed))
+ case configs.Undefined:
+ return nil
+ default:
+ return fmt.Errorf("Invalid argument '%s' to freezer.state", string(r.Freezer))
+ }
+}
+
+func (s *FreezerGroup) GetStats(path string, stats *cgroups.Stats) error {
+ return nil
+}
+
+func (s *FreezerGroup) GetState(path string) (configs.FreezerState, error) {
+ for {
+ state, err := cgroups.ReadFile(path, "freezer.state")
+ if err != nil {
+ // If the kernel is too old, then we just treat the freezer as
+ // being in an "undefined" state.
+ if os.IsNotExist(err) || errors.Is(err, unix.ENODEV) {
+ err = nil
+ }
+ return configs.Undefined, err
+ }
+ switch strings.TrimSpace(state) {
+ case "THAWED":
+ return configs.Thawed, nil
+ case "FROZEN":
+ // Find out whether the cgroup is frozen directly,
+ // or indirectly via an ancestor.
+ self, err := cgroups.ReadFile(path, "freezer.self_freezing")
+ if err != nil {
+ // If the kernel is too old, then we just treat
+ // it as being frozen.
+ if errors.Is(err, os.ErrNotExist) || errors.Is(err, unix.ENODEV) {
+ err = nil
+ }
+ return configs.Frozen, err
+ }
+ switch self {
+ case "0\n":
+ return configs.Thawed, nil
+ case "1\n":
+ return configs.Frozen, nil
+ default:
+ return configs.Undefined, fmt.Errorf(`unknown "freezer.self_freezing" state: %q`, self)
+ }
+ case "FREEZING":
+ // Make sure we get a stable freezer state, so retry if the cgroup
+ // is still undergoing freezing. This should be a temporary delay.
+ time.Sleep(1 * time.Millisecond)
+ continue
+ default:
+ return configs.Undefined, fmt.Errorf("unknown freezer.state %q", state)
+ }
+ }
+}
diff --git a/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/fs.go b/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/fs.go
new file mode 100644
index 000000000..be4dcc341
--- /dev/null
+++ b/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/fs.go
@@ -0,0 +1,264 @@
+package fs
+
+import (
+ "errors"
+ "fmt"
+ "os"
+ "sync"
+
+ "golang.org/x/sys/unix"
+
+ "github.com/opencontainers/runc/libcontainer/cgroups"
+ "github.com/opencontainers/runc/libcontainer/cgroups/fscommon"
+ "github.com/opencontainers/runc/libcontainer/configs"
+)
+
+var subsystems = []subsystem{
+ &CpusetGroup{},
+ &DevicesGroup{},
+ &MemoryGroup{},
+ &CpuGroup{},
+ &CpuacctGroup{},
+ &PidsGroup{},
+ &BlkioGroup{},
+ &HugetlbGroup{},
+ &NetClsGroup{},
+ &NetPrioGroup{},
+ &PerfEventGroup{},
+ &FreezerGroup{},
+ &RdmaGroup{},
+ &NameGroup{GroupName: "name=systemd", Join: true},
+}
+
+var errSubsystemDoesNotExist = errors.New("cgroup: subsystem does not exist")
+
+func init() {
+ // If using cgroups-hybrid mode then add a "" controller indicating
+ // it should join the cgroups v2.
+ if cgroups.IsCgroup2HybridMode() {
+ subsystems = append(subsystems, &NameGroup{GroupName: "", Join: true})
+ }
+}
+
+type subsystem interface {
+ // Name returns the name of the subsystem.
+ Name() string
+ // GetStats fills in the stats for the subsystem.
+ GetStats(path string, stats *cgroups.Stats) error
+ // Apply creates and joins a cgroup, adding pid into it. Some
+ // subsystems use resources to pre-configure the cgroup parents
+ // before creating or joining it.
+ Apply(path string, r *configs.Resources, pid int) error
+ // Set sets the cgroup resources.
+ Set(path string, r *configs.Resources) error
+}
+
+type manager struct {
+ mu sync.Mutex
+ cgroups *configs.Cgroup
+ paths map[string]string
+}
+
+func NewManager(cg *configs.Cgroup, paths map[string]string) (cgroups.Manager, error) {
+ // Some v1 controllers (cpu, cpuset, and devices) expect
+ // cgroups.Resources to not be nil in Apply.
+ if cg.Resources == nil {
+ return nil, errors.New("cgroup v1 manager needs configs.Resources to be set during manager creation")
+ }
+ if cg.Resources.Unified != nil {
+ return nil, cgroups.ErrV1NoUnified
+ }
+
+ if paths == nil {
+ var err error
+ paths, err = initPaths(cg)
+ if err != nil {
+ return nil, err
+ }
+ }
+
+ return &manager{
+ cgroups: cg,
+ paths: paths,
+ }, nil
+}
+
+// isIgnorableError returns whether err is a permission error (in the loose
+// sense of the word). This includes EROFS (which for an unprivileged user is
+// basically a permission error) and EACCES (for similar reasons) as well as
+// the normal EPERM.
+func isIgnorableError(rootless bool, err error) bool {
+ // We do not ignore errors if we are root.
+ if !rootless {
+ return false
+ }
+ // Is it an ordinary EPERM?
+ if errors.Is(err, os.ErrPermission) {
+ return true
+ }
+ // Handle some specific syscall errors.
+ var errno unix.Errno
+ if errors.As(err, &errno) {
+ return errno == unix.EROFS || errno == unix.EPERM || errno == unix.EACCES
+ }
+ return false
+}
+
+func (m *manager) Apply(pid int) (err error) {
+ m.mu.Lock()
+ defer m.mu.Unlock()
+
+ c := m.cgroups
+
+ for _, sys := range subsystems {
+ name := sys.Name()
+ p, ok := m.paths[name]
+ if !ok {
+ continue
+ }
+
+ if err := sys.Apply(p, c.Resources, pid); err != nil {
+ // In the case of rootless (including euid=0 in userns), where an
+ // explicit cgroup path hasn't been set, we don't bail on error in
+ // case of permission problems here, but do delete the path from
+ // the m.paths map, since it is either non-existent and could not
+ // be created, or the pid could not be added to it.
+ //
+ // Cases where limits for the subsystem have been set are handled
+ // later by Set, which fails with a friendly error (see
+ // if path == "" in Set).
+ if isIgnorableError(c.Rootless, err) && c.Path == "" {
+ delete(m.paths, name)
+ continue
+ }
+ return err
+ }
+
+ }
+ return nil
+}
+
+func (m *manager) Destroy() error {
+ m.mu.Lock()
+ defer m.mu.Unlock()
+ return cgroups.RemovePaths(m.paths)
+}
+
+func (m *manager) Path(subsys string) string {
+ m.mu.Lock()
+ defer m.mu.Unlock()
+ return m.paths[subsys]
+}
+
+func (m *manager) GetStats() (*cgroups.Stats, error) {
+ m.mu.Lock()
+ defer m.mu.Unlock()
+ stats := cgroups.NewStats()
+ for _, sys := range subsystems {
+ path := m.paths[sys.Name()]
+ if path == "" {
+ continue
+ }
+ if err := sys.GetStats(path, stats); err != nil {
+ return nil, err
+ }
+ }
+ return stats, nil
+}
+
+func (m *manager) Set(r *configs.Resources) error {
+ if r == nil {
+ return nil
+ }
+
+ if r.Unified != nil {
+ return cgroups.ErrV1NoUnified
+ }
+
+ m.mu.Lock()
+ defer m.mu.Unlock()
+ for _, sys := range subsystems {
+ path := m.paths[sys.Name()]
+ if err := sys.Set(path, r); err != nil {
+ // When rootless is true, errors from the device subsystem
+ // are ignored, as it is really not expected to work.
+ if m.cgroups.Rootless && sys.Name() == "devices" && !errors.Is(err, cgroups.ErrDevicesUnsupported) {
+ continue
+ }
+ // However, errors from other subsystems are not ignored.
+ // see @test "runc create (rootless + limits + no cgrouppath + no permission) fails with informative error"
+ if path == "" {
+ // We never created a path for this cgroup, so we cannot set
+ // limits for it (though we have already tried at this point).
+ return fmt.Errorf("cannot set %s limit: container could not join or create cgroup", sys.Name())
+ }
+ return err
+ }
+ }
+
+ return nil
+}
+
+// Freeze toggles the container's freezer cgroup depending on the state
+// provided
+func (m *manager) Freeze(state configs.FreezerState) error {
+ path := m.Path("freezer")
+ if path == "" {
+ return errors.New("cannot toggle freezer: cgroups not configured for container")
+ }
+
+ prevState := m.cgroups.Resources.Freezer
+ m.cgroups.Resources.Freezer = state
+ freezer := &FreezerGroup{}
+ if err := freezer.Set(path, m.cgroups.Resources); err != nil {
+ m.cgroups.Resources.Freezer = prevState
+ return err
+ }
+ return nil
+}
+
+func (m *manager) GetPids() ([]int, error) {
+ return cgroups.GetPids(m.Path("devices"))
+}
+
+func (m *manager) GetAllPids() ([]int, error) {
+ return cgroups.GetAllPids(m.Path("devices"))
+}
+
+func (m *manager) GetPaths() map[string]string {
+ m.mu.Lock()
+ defer m.mu.Unlock()
+ return m.paths
+}
+
+func (m *manager) GetCgroups() (*configs.Cgroup, error) {
+ return m.cgroups, nil
+}
+
+func (m *manager) GetFreezerState() (configs.FreezerState, error) {
+ dir := m.Path("freezer")
+ // If the container doesn't have the freezer cgroup, say it's undefined.
+ if dir == "" {
+ return configs.Undefined, nil
+ }
+ freezer := &FreezerGroup{}
+ return freezer.GetState(dir)
+}
+
+func (m *manager) Exists() bool {
+ return cgroups.PathExists(m.Path("devices"))
+}
+
+func OOMKillCount(path string) (uint64, error) {
+ return fscommon.GetValueByKey(path, "memory.oom_control", "oom_kill")
+}
+
+func (m *manager) OOMKillCount() (uint64, error) {
+ c, err := OOMKillCount(m.Path("memory"))
+ // Ignore ENOENT when rootless as it couldn't create cgroup.
+ if err != nil && m.cgroups.Rootless && os.IsNotExist(err) {
+ err = nil
+ }
+
+ return c, err
+}
diff --git a/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/hugetlb.go b/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/hugetlb.go
new file mode 100644
index 000000000..8ddd6fdd8
--- /dev/null
+++ b/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/hugetlb.go
@@ -0,0 +1,62 @@
+package fs
+
+import (
+ "strconv"
+
+ "github.com/opencontainers/runc/libcontainer/cgroups"
+ "github.com/opencontainers/runc/libcontainer/cgroups/fscommon"
+ "github.com/opencontainers/runc/libcontainer/configs"
+)
+
+type HugetlbGroup struct{}
+
+func (s *HugetlbGroup) Name() string {
+ return "hugetlb"
+}
+
+func (s *HugetlbGroup) Apply(path string, _ *configs.Resources, pid int) error {
+ return apply(path, pid)
+}
+
+func (s *HugetlbGroup) Set(path string, r *configs.Resources) error {
+ for _, hugetlb := range r.HugetlbLimit {
+ if err := cgroups.WriteFile(path, "hugetlb."+hugetlb.Pagesize+".limit_in_bytes", strconv.FormatUint(hugetlb.Limit, 10)); err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
+
+func (s *HugetlbGroup) GetStats(path string, stats *cgroups.Stats) error {
+ if !cgroups.PathExists(path) {
+ return nil
+ }
+ hugetlbStats := cgroups.HugetlbStats{}
+ for _, pageSize := range cgroups.HugePageSizes() {
+ usage := "hugetlb." + pageSize + ".usage_in_bytes"
+ value, err := fscommon.GetCgroupParamUint(path, usage)
+ if err != nil {
+ return err
+ }
+ hugetlbStats.Usage = value
+
+ maxUsage := "hugetlb." + pageSize + ".max_usage_in_bytes"
+ value, err = fscommon.GetCgroupParamUint(path, maxUsage)
+ if err != nil {
+ return err
+ }
+ hugetlbStats.MaxUsage = value
+
+ failcnt := "hugetlb." + pageSize + ".failcnt"
+ value, err = fscommon.GetCgroupParamUint(path, failcnt)
+ if err != nil {
+ return err
+ }
+ hugetlbStats.Failcnt = value
+
+ stats.HugetlbStats[pageSize] = hugetlbStats
+ }
+
+ return nil
+}
diff --git a/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/memory.go b/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/memory.go
new file mode 100644
index 000000000..b7c75f941
--- /dev/null
+++ b/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/memory.go
@@ -0,0 +1,348 @@
+package fs
+
+import (
+ "bufio"
+ "errors"
+ "fmt"
+ "math"
+ "os"
+ "path/filepath"
+ "strconv"
+ "strings"
+
+ "golang.org/x/sys/unix"
+
+ "github.com/opencontainers/runc/libcontainer/cgroups"
+ "github.com/opencontainers/runc/libcontainer/cgroups/fscommon"
+ "github.com/opencontainers/runc/libcontainer/configs"
+)
+
+const (
+ cgroupMemorySwapLimit = "memory.memsw.limit_in_bytes"
+ cgroupMemoryLimit = "memory.limit_in_bytes"
+ cgroupMemoryUsage = "memory.usage_in_bytes"
+ cgroupMemoryMaxUsage = "memory.max_usage_in_bytes"
+)
+
+type MemoryGroup struct{}
+
+func (s *MemoryGroup) Name() string {
+ return "memory"
+}
+
+func (s *MemoryGroup) Apply(path string, _ *configs.Resources, pid int) error {
+ return apply(path, pid)
+}
+
+func setMemory(path string, val int64) error {
+ if val == 0 {
+ return nil
+ }
+
+ err := cgroups.WriteFile(path, cgroupMemoryLimit, strconv.FormatInt(val, 10))
+ if !errors.Is(err, unix.EBUSY) {
+ return err
+ }
+
+ // EBUSY means the kernel can't set new limit as it's too low
+ // (lower than the current usage). Return more specific error.
+ usage, err := fscommon.GetCgroupParamUint(path, cgroupMemoryUsage)
+ if err != nil {
+ return err
+ }
+ max, err := fscommon.GetCgroupParamUint(path, cgroupMemoryMaxUsage)
+ if err != nil {
+ return err
+ }
+
+ return fmt.Errorf("unable to set memory limit to %d (current usage: %d, peak usage: %d)", val, usage, max)
+}
+
+func setSwap(path string, val int64) error {
+ if val == 0 {
+ return nil
+ }
+
+ return cgroups.WriteFile(path, cgroupMemorySwapLimit, strconv.FormatInt(val, 10))
+}
+
+func setMemoryAndSwap(path string, r *configs.Resources) error {
+ // If the memory update is set to -1 and the swap is not explicitly
+ // set, we should also set swap to -1, it means unlimited memory.
+ if r.Memory == -1 && r.MemorySwap == 0 {
+ // Only set swap if it's enabled in kernel
+ if cgroups.PathExists(filepath.Join(path, cgroupMemorySwapLimit)) {
+ r.MemorySwap = -1
+ }
+ }
+
+ // When memory and swap memory are both set, we need to handle the cases
+ // for updating container.
+ if r.Memory != 0 && r.MemorySwap != 0 {
+ curLimit, err := fscommon.GetCgroupParamUint(path, cgroupMemoryLimit)
+ if err != nil {
+ return err
+ }
+
+ // When update memory limit, we should adapt the write sequence
+ // for memory and swap memory, so it won't fail because the new
+ // value and the old value don't fit kernel's validation.
+ if r.MemorySwap == -1 || curLimit < uint64(r.MemorySwap) {
+ if err := setSwap(path, r.MemorySwap); err != nil {
+ return err
+ }
+ if err := setMemory(path, r.Memory); err != nil {
+ return err
+ }
+ return nil
+ }
+ }
+
+ if err := setMemory(path, r.Memory); err != nil {
+ return err
+ }
+ if err := setSwap(path, r.MemorySwap); err != nil {
+ return err
+ }
+
+ return nil
+}
+
+func (s *MemoryGroup) Set(path string, r *configs.Resources) error {
+ if err := setMemoryAndSwap(path, r); err != nil {
+ return err
+ }
+
+ // ignore KernelMemory and KernelMemoryTCP
+
+ if r.MemoryReservation != 0 {
+ if err := cgroups.WriteFile(path, "memory.soft_limit_in_bytes", strconv.FormatInt(r.MemoryReservation, 10)); err != nil {
+ return err
+ }
+ }
+
+ if r.OomKillDisable {
+ if err := cgroups.WriteFile(path, "memory.oom_control", "1"); err != nil {
+ return err
+ }
+ }
+ if r.MemorySwappiness == nil || int64(*r.MemorySwappiness) == -1 {
+ return nil
+ } else if *r.MemorySwappiness <= 100 {
+ if err := cgroups.WriteFile(path, "memory.swappiness", strconv.FormatUint(*r.MemorySwappiness, 10)); err != nil {
+ return err
+ }
+ } else {
+ return fmt.Errorf("invalid memory swappiness value: %d (valid range is 0-100)", *r.MemorySwappiness)
+ }
+
+ return nil
+}
+
+func (s *MemoryGroup) GetStats(path string, stats *cgroups.Stats) error {
+ const file = "memory.stat"
+ statsFile, err := cgroups.OpenFile(path, file, os.O_RDONLY)
+ if err != nil {
+ if os.IsNotExist(err) {
+ return nil
+ }
+ return err
+ }
+ defer statsFile.Close()
+
+ sc := bufio.NewScanner(statsFile)
+ for sc.Scan() {
+ t, v, err := fscommon.ParseKeyValue(sc.Text())
+ if err != nil {
+ return &parseError{Path: path, File: file, Err: err}
+ }
+ stats.MemoryStats.Stats[t] = v
+ }
+ stats.MemoryStats.Cache = stats.MemoryStats.Stats["cache"]
+
+ memoryUsage, err := getMemoryData(path, "")
+ if err != nil {
+ return err
+ }
+ stats.MemoryStats.Usage = memoryUsage
+ swapUsage, err := getMemoryData(path, "memsw")
+ if err != nil {
+ return err
+ }
+ stats.MemoryStats.SwapUsage = swapUsage
+ kernelUsage, err := getMemoryData(path, "kmem")
+ if err != nil {
+ return err
+ }
+ stats.MemoryStats.KernelUsage = kernelUsage
+ kernelTCPUsage, err := getMemoryData(path, "kmem.tcp")
+ if err != nil {
+ return err
+ }
+ stats.MemoryStats.KernelTCPUsage = kernelTCPUsage
+
+ value, err := fscommon.GetCgroupParamUint(path, "memory.use_hierarchy")
+ if err != nil {
+ return err
+ }
+ if value == 1 {
+ stats.MemoryStats.UseHierarchy = true
+ }
+
+ pagesByNUMA, err := getPageUsageByNUMA(path)
+ if err != nil {
+ return err
+ }
+ stats.MemoryStats.PageUsageByNUMA = pagesByNUMA
+
+ return nil
+}
+
+func getMemoryData(path, name string) (cgroups.MemoryData, error) {
+ memoryData := cgroups.MemoryData{}
+
+ moduleName := "memory"
+ if name != "" {
+ moduleName = "memory." + name
+ }
+ var (
+ usage = moduleName + ".usage_in_bytes"
+ maxUsage = moduleName + ".max_usage_in_bytes"
+ failcnt = moduleName + ".failcnt"
+ limit = moduleName + ".limit_in_bytes"
+ )
+
+ value, err := fscommon.GetCgroupParamUint(path, usage)
+ if err != nil {
+ if name != "" && os.IsNotExist(err) {
+ // Ignore ENOENT as swap and kmem controllers
+ // are optional in the kernel.
+ return cgroups.MemoryData{}, nil
+ }
+ return cgroups.MemoryData{}, err
+ }
+ memoryData.Usage = value
+ value, err = fscommon.GetCgroupParamUint(path, maxUsage)
+ if err != nil {
+ return cgroups.MemoryData{}, err
+ }
+ memoryData.MaxUsage = value
+ value, err = fscommon.GetCgroupParamUint(path, failcnt)
+ if err != nil {
+ return cgroups.MemoryData{}, err
+ }
+ memoryData.Failcnt = value
+ value, err = fscommon.GetCgroupParamUint(path, limit)
+ if err != nil {
+ return cgroups.MemoryData{}, err
+ }
+ memoryData.Limit = value
+
+ return memoryData, nil
+}
+
+func getPageUsageByNUMA(path string) (cgroups.PageUsageByNUMA, error) {
+ const (
+ maxColumns = math.MaxUint8 + 1
+ file = "memory.numa_stat"
+ )
+ stats := cgroups.PageUsageByNUMA{}
+
+ fd, err := cgroups.OpenFile(path, file, os.O_RDONLY)
+ if os.IsNotExist(err) {
+ return stats, nil
+ } else if err != nil {
+ return stats, err
+ }
+ defer fd.Close()
+
+ // File format is documented in linux/Documentation/cgroup-v1/memory.txt
+ // and it looks like this:
+ //
+ // total=<total pages> N0=<node 0 pages> N1=<node 1 pages> ...
+ // file=<total file pages> N0=<node 0 pages> N1=<node 1 pages> ...
+ // anon=<total anon pages> N0=<node 0 pages> N1=<node 1 pages> ...
+ // unevictable=<total anon pages> N0=<node 0 pages> N1=<node 1 pages> ...
+ // hierarchical_<counter>=<counter pages> N0=<node 0 pages> N1=<node 1 pages> ...
+
+ scanner := bufio.NewScanner(fd)
+ for scanner.Scan() {
+ var field *cgroups.PageStats
+
+ line := scanner.Text()
+ columns := strings.SplitN(line, " ", maxColumns)
+ for i, column := range columns {
+ byNode := strings.SplitN(column, "=", 2)
+ // Some custom kernels have non-standard fields, like
+ // numa_locality 0 0 0 0 0 0 0 0 0 0
+ // numa_exectime 0
+ if len(byNode) < 2 {
+ if i == 0 {
+ // Ignore/skip those.
+ break
+ } else {
+ // The first column was already validated,
+ // so be strict to the rest.
+ return stats, malformedLine(path, file, line)
+ }
+ }
+ key, val := byNode[0], byNode[1]
+ if i == 0 { // First column: key is name, val is total.
+ field = getNUMAField(&stats, key)
+ if field == nil { // unknown field (new kernel?)
+ break
+ }
+ field.Total, err = strconv.ParseUint(val, 0, 64)
+ if err != nil {
+ return stats, &parseError{Path: path, File: file, Err: err}
+ }
+ field.Nodes = map[uint8]uint64{}
+ } else { // Subsequent columns: key is N<id>, val is usage.
+ if len(key) < 2 || key[0] != 'N' {
+ // This is definitely an error.
+ return stats, malformedLine(path, file, line)
+ }
+
+ n, err := strconv.ParseUint(key[1:], 10, 8)
+ if err != nil {
+ return stats, &parseError{Path: path, File: file, Err: err}
+ }
+
+ usage, err := strconv.ParseUint(val, 10, 64)
+ if err != nil {
+ return stats, &parseError{Path: path, File: file, Err: err}
+ }
+
+ field.Nodes[uint8(n)] = usage
+ }
+
+ }
+ }
+ if err := scanner.Err(); err != nil {
+ return cgroups.PageUsageByNUMA{}, &parseError{Path: path, File: file, Err: err}
+ }
+
+ return stats, nil
+}
+
+func getNUMAField(stats *cgroups.PageUsageByNUMA, name string) *cgroups.PageStats {
+ switch name {
+ case "total":
+ return &stats.Total
+ case "file":
+ return &stats.File
+ case "anon":
+ return &stats.Anon
+ case "unevictable":
+ return &stats.Unevictable
+ case "hierarchical_total":
+ return &stats.Hierarchical.Total
+ case "hierarchical_file":
+ return &stats.Hierarchical.File
+ case "hierarchical_anon":
+ return &stats.Hierarchical.Anon
+ case "hierarchical_unevictable":
+ return &stats.Hierarchical.Unevictable
+ }
+ return nil
+}
diff --git a/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/name.go b/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/name.go
new file mode 100644
index 000000000..b8d5d849c
--- /dev/null
+++ b/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/name.go
@@ -0,0 +1,31 @@
+package fs
+
+import (
+ "github.com/opencontainers/runc/libcontainer/cgroups"
+ "github.com/opencontainers/runc/libcontainer/configs"
+)
+
+type NameGroup struct {
+ GroupName string
+ Join bool
+}
+
+func (s *NameGroup) Name() string {
+ return s.GroupName
+}
+
+func (s *NameGroup) Apply(path string, _ *configs.Resources, pid int) error {
+ if s.Join {
+ // Ignore errors if the named cgroup does not exist.
+ _ = apply(path, pid)
+ }
+ return nil
+}
+
+func (s *NameGroup) Set(_ string, _ *configs.Resources) error {
+ return nil
+}
+
+func (s *NameGroup) GetStats(path string, stats *cgroups.Stats) error {
+ return nil
+}
diff --git a/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/net_cls.go b/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/net_cls.go
new file mode 100644
index 000000000..abfd09ce8
--- /dev/null
+++ b/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/net_cls.go
@@ -0,0 +1,32 @@
+package fs
+
+import (
+ "strconv"
+
+ "github.com/opencontainers/runc/libcontainer/cgroups"
+ "github.com/opencontainers/runc/libcontainer/configs"
+)
+
+type NetClsGroup struct{}
+
+func (s *NetClsGroup) Name() string {
+ return "net_cls"
+}
+
+func (s *NetClsGroup) Apply(path string, _ *configs.Resources, pid int) error {
+ return apply(path, pid)
+}
+
+func (s *NetClsGroup) Set(path string, r *configs.Resources) error {
+ if r.NetClsClassid != 0 {
+ if err := cgroups.WriteFile(path, "net_cls.classid", strconv.FormatUint(uint64(r.NetClsClassid), 10)); err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
+
+func (s *NetClsGroup) GetStats(path string, stats *cgroups.Stats) error {
+ return nil
+}
diff --git a/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/net_prio.go b/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/net_prio.go
new file mode 100644
index 000000000..da74d3779
--- /dev/null
+++ b/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/net_prio.go
@@ -0,0 +1,30 @@
+package fs
+
+import (
+ "github.com/opencontainers/runc/libcontainer/cgroups"
+ "github.com/opencontainers/runc/libcontainer/configs"
+)
+
+type NetPrioGroup struct{}
+
+func (s *NetPrioGroup) Name() string {
+ return "net_prio"
+}
+
+func (s *NetPrioGroup) Apply(path string, _ *configs.Resources, pid int) error {
+ return apply(path, pid)
+}
+
+func (s *NetPrioGroup) Set(path string, r *configs.Resources) error {
+ for _, prioMap := range r.NetPrioIfpriomap {
+ if err := cgroups.WriteFile(path, "net_prio.ifpriomap", prioMap.CgroupString()); err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
+
+func (s *NetPrioGroup) GetStats(path string, stats *cgroups.Stats) error {
+ return nil
+}
diff --git a/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/paths.go b/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/paths.go
new file mode 100644
index 000000000..1092331b2
--- /dev/null
+++ b/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/paths.go
@@ -0,0 +1,186 @@
+package fs
+
+import (
+ "errors"
+ "os"
+ "path/filepath"
+ "sync"
+
+ "golang.org/x/sys/unix"
+
+ "github.com/opencontainers/runc/libcontainer/cgroups"
+ "github.com/opencontainers/runc/libcontainer/configs"
+ "github.com/opencontainers/runc/libcontainer/utils"
+)
+
+// The absolute path to the root of the cgroup hierarchies.
+var (
+ cgroupRootLock sync.Mutex
+ cgroupRoot string
+)
+
+const defaultCgroupRoot = "/sys/fs/cgroup"
+
+func initPaths(cg *configs.Cgroup) (map[string]string, error) {
+ root, err := rootPath()
+ if err != nil {
+ return nil, err
+ }
+
+ inner, err := innerPath(cg)
+ if err != nil {
+ return nil, err
+ }
+
+ paths := make(map[string]string)
+ for _, sys := range subsystems {
+ name := sys.Name()
+ path, err := subsysPath(root, inner, name)
+ if err != nil {
+ // The non-presence of the devices subsystem
+ // is considered fatal for security reasons.
+ if cgroups.IsNotFound(err) && (cg.SkipDevices || name != "devices") {
+ continue
+ }
+
+ return nil, err
+ }
+ paths[name] = path
+ }
+
+ return paths, nil
+}
+
+func tryDefaultCgroupRoot() string {
+ var st, pst unix.Stat_t
+
+ // (1) it should be a directory...
+ err := unix.Lstat(defaultCgroupRoot, &st)
+ if err != nil || st.Mode&unix.S_IFDIR == 0 {
+ return ""
+ }
+
+ // (2) ... and a mount point ...
+ err = unix.Lstat(filepath.Dir(defaultCgroupRoot), &pst)
+ if err != nil {
+ return ""
+ }
+
+ if st.Dev == pst.Dev {
+ // parent dir has the same dev -- not a mount point
+ return ""
+ }
+
+ // (3) ... of 'tmpfs' fs type.
+ var fst unix.Statfs_t
+ err = unix.Statfs(defaultCgroupRoot, &fst)
+ if err != nil || fst.Type != unix.TMPFS_MAGIC {
+ return ""
+ }
+
+ // (4) it should have at least 1 entry ...
+ dir, err := os.Open(defaultCgroupRoot)
+ if err != nil {
+ return ""
+ }
+ names, err := dir.Readdirnames(1)
+ if err != nil {
+ return ""
+ }
+ if len(names) < 1 {
+ return ""
+ }
+ // ... which is a cgroup mount point.
+ err = unix.Statfs(filepath.Join(defaultCgroupRoot, names[0]), &fst)
+ if err != nil || fst.Type != unix.CGROUP_SUPER_MAGIC {
+ return ""
+ }
+
+ return defaultCgroupRoot
+}
+
+// rootPath finds and returns path to the root of the cgroup hierarchies.
+func rootPath() (string, error) {
+ cgroupRootLock.Lock()
+ defer cgroupRootLock.Unlock()
+
+ if cgroupRoot != "" {
+ return cgroupRoot, nil
+ }
+
+ // fast path
+ cgroupRoot = tryDefaultCgroupRoot()
+ if cgroupRoot != "" {
+ return cgroupRoot, nil
+ }
+
+ // slow path: parse mountinfo
+ mi, err := cgroups.GetCgroupMounts(false)
+ if err != nil {
+ return "", err
+ }
+ if len(mi) < 1 {
+ return "", errors.New("no cgroup mount found in mountinfo")
+ }
+
+ // Get the first cgroup mount (e.g. "/sys/fs/cgroup/memory"),
+ // use its parent directory.
+ root := filepath.Dir(mi[0].Mountpoint)
+
+ if _, err := os.Stat(root); err != nil {
+ return "", err
+ }
+
+ cgroupRoot = root
+ return cgroupRoot, nil
+}
+
+func innerPath(c *configs.Cgroup) (string, error) {
+ if (c.Name != "" || c.Parent != "") && c.Path != "" {
+ return "", errors.New("cgroup: either Path or Name and Parent should be used")
+ }
+
+ // XXX: Do not remove CleanPath. Path safety is important! -- cyphar
+ innerPath := utils.CleanPath(c.Path)
+ if innerPath == "" {
+ cgParent := utils.CleanPath(c.Parent)
+ cgName := utils.CleanPath(c.Name)
+ innerPath = filepath.Join(cgParent, cgName)
+ }
+
+ return innerPath, nil
+}
+
+func subsysPath(root, inner, subsystem string) (string, error) {
+ // If the cgroup name/path is absolute do not look relative to the cgroup of the init process.
+ if filepath.IsAbs(inner) {
+ mnt, err := cgroups.FindCgroupMountpoint(root, subsystem)
+ // If we didn't mount the subsystem, there is no point we make the path.
+ if err != nil {
+ return "", err
+ }
+
+ // Sometimes subsystems can be mounted together as 'cpu,cpuacct'.
+ return filepath.Join(root, filepath.Base(mnt), inner), nil
+ }
+
+ // Use GetOwnCgroupPath instead of GetInitCgroupPath, because the creating
+ // process could in container and shared pid namespace with host, and
+ // /proc/1/cgroup could point to whole other world of cgroups.
+ parentPath, err := cgroups.GetOwnCgroupPath(subsystem)
+ if err != nil {
+ return "", err
+ }
+
+ return filepath.Join(parentPath, inner), nil
+}
+
+func apply(path string, pid int) error {
+ if path == "" {
+ return nil
+ }
+ if err := os.MkdirAll(path, 0o755); err != nil {
+ return err
+ }
+ return cgroups.WriteCgroupProc(path, pid)
+}
diff --git a/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/perf_event.go b/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/perf_event.go
new file mode 100644
index 000000000..b86955c8f
--- /dev/null
+++ b/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/perf_event.go
@@ -0,0 +1,24 @@
+package fs
+
+import (
+ "github.com/opencontainers/runc/libcontainer/cgroups"
+ "github.com/opencontainers/runc/libcontainer/configs"
+)
+
+type PerfEventGroup struct{}
+
+func (s *PerfEventGroup) Name() string {
+ return "perf_event"
+}
+
+func (s *PerfEventGroup) Apply(path string, _ *configs.Resources, pid int) error {
+ return apply(path, pid)
+}
+
+func (s *PerfEventGroup) Set(_ string, _ *configs.Resources) error {
+ return nil
+}
+
+func (s *PerfEventGroup) GetStats(path string, stats *cgroups.Stats) error {
+ return nil
+}
diff --git a/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/pids.go b/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/pids.go
new file mode 100644
index 000000000..1f13532a5
--- /dev/null
+++ b/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/pids.go
@@ -0,0 +1,62 @@
+package fs
+
+import (
+ "math"
+ "strconv"
+
+ "github.com/opencontainers/runc/libcontainer/cgroups"
+ "github.com/opencontainers/runc/libcontainer/cgroups/fscommon"
+ "github.com/opencontainers/runc/libcontainer/configs"
+)
+
+type PidsGroup struct{}
+
+func (s *PidsGroup) Name() string {
+ return "pids"
+}
+
+func (s *PidsGroup) Apply(path string, _ *configs.Resources, pid int) error {
+ return apply(path, pid)
+}
+
+func (s *PidsGroup) Set(path string, r *configs.Resources) error {
+ if r.PidsLimit != 0 {
+ // "max" is the fallback value.
+ limit := "max"
+
+ if r.PidsLimit > 0 {
+ limit = strconv.FormatInt(r.PidsLimit, 10)
+ }
+
+ if err := cgroups.WriteFile(path, "pids.max", limit); err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
+
+func (s *PidsGroup) GetStats(path string, stats *cgroups.Stats) error {
+ if !cgroups.PathExists(path) {
+ return nil
+ }
+ current, err := fscommon.GetCgroupParamUint(path, "pids.current")
+ if err != nil {
+ return err
+ }
+
+ max, err := fscommon.GetCgroupParamUint(path, "pids.max")
+ if err != nil {
+ return err
+ }
+ // If no limit is set, read from pids.max returns "max", which is
+ // converted to MaxUint64 by GetCgroupParamUint. Historically, we
+ // represent "no limit" for pids as 0, thus this conversion.
+ if max == math.MaxUint64 {
+ max = 0
+ }
+
+ stats.PidsStats.Current = current
+ stats.PidsStats.Limit = max
+ return nil
+}
diff --git a/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/rdma.go b/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/rdma.go
new file mode 100644
index 000000000..5bbe0f35f
--- /dev/null
+++ b/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/rdma.go
@@ -0,0 +1,25 @@
+package fs
+
+import (
+ "github.com/opencontainers/runc/libcontainer/cgroups"
+ "github.com/opencontainers/runc/libcontainer/cgroups/fscommon"
+ "github.com/opencontainers/runc/libcontainer/configs"
+)
+
+type RdmaGroup struct{}
+
+func (s *RdmaGroup) Name() string {
+ return "rdma"
+}
+
+func (s *RdmaGroup) Apply(path string, _ *configs.Resources, pid int) error {
+ return apply(path, pid)
+}
+
+func (s *RdmaGroup) Set(path string, r *configs.Resources) error {
+ return fscommon.RdmaSet(path, r)
+}
+
+func (s *RdmaGroup) GetStats(path string, stats *cgroups.Stats) error {
+ return fscommon.RdmaGetStats(path, stats)
+}
diff --git a/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs2/cpu.go b/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs2/cpu.go
new file mode 100644
index 000000000..bbbae4d58
--- /dev/null
+++ b/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs2/cpu.go
@@ -0,0 +1,87 @@
+package fs2
+
+import (
+ "bufio"
+ "os"
+ "strconv"
+
+ "github.com/opencontainers/runc/libcontainer/cgroups"
+ "github.com/opencontainers/runc/libcontainer/cgroups/fscommon"
+ "github.com/opencontainers/runc/libcontainer/configs"
+)
+
+func isCpuSet(r *configs.Resources) bool {
+ return r.CpuWeight != 0 || r.CpuQuota != 0 || r.CpuPeriod != 0
+}
+
+func setCpu(dirPath string, r *configs.Resources) error {
+ if !isCpuSet(r) {
+ return nil
+ }
+
+ // NOTE: .CpuShares is not used here. Conversion is the caller's responsibility.
+ if r.CpuWeight != 0 {
+ if err := cgroups.WriteFile(dirPath, "cpu.weight", strconv.FormatUint(r.CpuWeight, 10)); err != nil {
+ return err
+ }
+ }
+
+ if r.CpuQuota != 0 || r.CpuPeriod != 0 {
+ str := "max"
+ if r.CpuQuota > 0 {
+ str = strconv.FormatInt(r.CpuQuota, 10)
+ }
+ period := r.CpuPeriod
+ if period == 0 {
+ // This default value is documented in
+ // https://www.kernel.org/doc/html/latest/admin-guide/cgroup-v2.html
+ period = 100000
+ }
+ str += " " + strconv.FormatUint(period, 10)
+ if err := cgroups.WriteFile(dirPath, "cpu.max", str); err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
+
+func statCpu(dirPath string, stats *cgroups.Stats) error {
+ const file = "cpu.stat"
+ f, err := cgroups.OpenFile(dirPath, file, os.O_RDONLY)
+ if err != nil {
+ return err
+ }
+ defer f.Close()
+
+ sc := bufio.NewScanner(f)
+ for sc.Scan() {
+ t, v, err := fscommon.ParseKeyValue(sc.Text())
+ if err != nil {
+ return &parseError{Path: dirPath, File: file, Err: err}
+ }
+ switch t {
+ case "usage_usec":
+ stats.CpuStats.CpuUsage.TotalUsage = v * 1000
+
+ case "user_usec":
+ stats.CpuStats.CpuUsage.UsageInUsermode = v * 1000
+
+ case "system_usec":
+ stats.CpuStats.CpuUsage.UsageInKernelmode = v * 1000
+
+ case "nr_periods":
+ stats.CpuStats.ThrottlingData.Periods = v
+
+ case "nr_throttled":
+ stats.CpuStats.ThrottlingData.ThrottledPeriods = v
+
+ case "throttled_usec":
+ stats.CpuStats.ThrottlingData.ThrottledTime = v * 1000
+ }
+ }
+ if err := sc.Err(); err != nil {
+ return &parseError{Path: dirPath, File: file, Err: err}
+ }
+ return nil
+}
diff --git a/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs2/cpuset.go b/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs2/cpuset.go
new file mode 100644
index 000000000..16c45bad8
--- /dev/null
+++ b/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs2/cpuset.go
@@ -0,0 +1,28 @@
+package fs2
+
+import (
+ "github.com/opencontainers/runc/libcontainer/cgroups"
+ "github.com/opencontainers/runc/libcontainer/configs"
+)
+
+func isCpusetSet(r *configs.Resources) bool {
+ return r.CpusetCpus != "" || r.CpusetMems != ""
+}
+
+func setCpuset(dirPath string, r *configs.Resources) error {
+ if !isCpusetSet(r) {
+ return nil
+ }
+
+ if r.CpusetCpus != "" {
+ if err := cgroups.WriteFile(dirPath, "cpuset.cpus", r.CpusetCpus); err != nil {
+ return err
+ }
+ }
+ if r.CpusetMems != "" {
+ if err := cgroups.WriteFile(dirPath, "cpuset.mems", r.CpusetMems); err != nil {
+ return err
+ }
+ }
+ return nil
+}
diff --git a/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs2/create.go b/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs2/create.go
new file mode 100644
index 000000000..641123a4d
--- /dev/null
+++ b/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs2/create.go
@@ -0,0 +1,152 @@
+package fs2
+
+import (
+ "fmt"
+ "os"
+ "path/filepath"
+ "strings"
+
+ "github.com/opencontainers/runc/libcontainer/cgroups"
+ "github.com/opencontainers/runc/libcontainer/configs"
+)
+
+func supportedControllers() (string, error) {
+ return cgroups.ReadFile(UnifiedMountpoint, "/cgroup.controllers")
+}
+
+// needAnyControllers returns whether we enable some supported controllers or not,
+// based on (1) controllers available and (2) resources that are being set.
+// We don't check "pseudo" controllers such as
+// "freezer" and "devices".
+func needAnyControllers(r *configs.Resources) (bool, error) {
+ if r == nil {
+ return false, nil
+ }
+
+ // list of all available controllers
+ content, err := supportedControllers()
+ if err != nil {
+ return false, err
+ }
+ avail := make(map[string]struct{})
+ for _, ctr := range strings.Fields(content) {
+ avail[ctr] = struct{}{}
+ }
+
+ // check whether the controller if available or not
+ have := func(controller string) bool {
+ _, ok := avail[controller]
+ return ok
+ }
+
+ if isPidsSet(r) && have("pids") {
+ return true, nil
+ }
+ if isMemorySet(r) && have("memory") {
+ return true, nil
+ }
+ if isIoSet(r) && have("io") {
+ return true, nil
+ }
+ if isCpuSet(r) && have("cpu") {
+ return true, nil
+ }
+ if isCpusetSet(r) && have("cpuset") {
+ return true, nil
+ }
+ if isHugeTlbSet(r) && have("hugetlb") {
+ return true, nil
+ }
+
+ return false, nil
+}
+
+// containsDomainController returns whether the current config contains domain controller or not.
+// Refer to: http://man7.org/linux/man-pages/man7/cgroups.7.html
+// As at Linux 4.19, the following controllers are threaded: cpu, perf_event, and pids.
+func containsDomainController(r *configs.Resources) bool {
+ return isMemorySet(r) || isIoSet(r) || isCpuSet(r) || isHugeTlbSet(r)
+}
+
+// CreateCgroupPath creates cgroupv2 path, enabling all the supported controllers.
+func CreateCgroupPath(path string, c *configs.Cgroup) (Err error) {
+ if !strings.HasPrefix(path, UnifiedMountpoint) {
+ return fmt.Errorf("invalid cgroup path %s", path)
+ }
+
+ content, err := supportedControllers()
+ if err != nil {
+ return err
+ }
+
+ const (
+ cgTypeFile = "cgroup.type"
+ cgStCtlFile = "cgroup.subtree_control"
+ )
+ ctrs := strings.Fields(content)
+ res := "+" + strings.Join(ctrs, " +")
+
+ elements := strings.Split(path, "/")
+ elements = elements[3:]
+ current := "/sys/fs"
+ for i, e := range elements {
+ current = filepath.Join(current, e)
+ if i > 0 {
+ if err := os.Mkdir(current, 0o755); err != nil {
+ if !os.IsExist(err) {
+ return err
+ }
+ } else {
+ // If the directory was created, be sure it is not left around on errors.
+ current := current
+ defer func() {
+ if Err != nil {
+ os.Remove(current)
+ }
+ }()
+ }
+ cgType, _ := cgroups.ReadFile(current, cgTypeFile)
+ cgType = strings.TrimSpace(cgType)
+ switch cgType {
+ // If the cgroup is in an invalid mode (usually this means there's an internal
+ // process in the cgroup tree, because we created a cgroup under an
+ // already-populated-by-other-processes cgroup), then we have to error out if
+ // the user requested controllers which are not thread-aware. However, if all
+ // the controllers requested are thread-aware we can simply put the cgroup into
+ // threaded mode.
+ case "domain invalid":
+ if containsDomainController(c.Resources) {
+ return fmt.Errorf("cannot enter cgroupv2 %q with domain controllers -- it is in an invalid state", current)
+ } else {
+ // Not entirely correct (in theory we'd always want to be a domain --
+ // since that means we're a properly delegated cgroup subtree) but in
+ // this case there's not much we can do and it's better than giving an
+ // error.
+ _ = cgroups.WriteFile(current, cgTypeFile, "threaded")
+ }
+ // If the cgroup is in (threaded) or (domain threaded) mode, we can only use thread-aware controllers
+ // (and you cannot usually take a cgroup out of threaded mode).
+ case "domain threaded":
+ fallthrough
+ case "threaded":
+ if containsDomainController(c.Resources) {
+ return fmt.Errorf("cannot enter cgroupv2 %q with domain controllers -- it is in %s mode", current, cgType)
+ }
+ }
+ }
+ // enable all supported controllers
+ if i < len(elements)-1 {
+ if err := cgroups.WriteFile(current, cgStCtlFile, res); err != nil {
+ // try write one by one
+ allCtrs := strings.Split(res, " ")
+ for _, ctr := range allCtrs {
+ _ = cgroups.WriteFile(current, cgStCtlFile, ctr)
+ }
+ }
+ // Some controllers might not be enabled when rootless or containerized,
+ // but we don't catch the error here. (Caught in setXXX() functions.)
+ }
+ }
+
+ return nil
+}
diff --git a/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs2/defaultpath.go b/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs2/defaultpath.go
new file mode 100644
index 000000000..9c949c91f
--- /dev/null
+++ b/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs2/defaultpath.go
@@ -0,0 +1,99 @@
+/*
+ Copyright The containerd Authors.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+*/
+
+package fs2
+
+import (
+ "bufio"
+ "errors"
+ "fmt"
+ "io"
+ "os"
+ "path/filepath"
+ "strings"
+
+ "github.com/opencontainers/runc/libcontainer/configs"
+ "github.com/opencontainers/runc/libcontainer/utils"
+)
+
+const UnifiedMountpoint = "/sys/fs/cgroup"
+
+func defaultDirPath(c *configs.Cgroup) (string, error) {
+ if (c.Name != "" || c.Parent != "") && c.Path != "" {
+ return "", fmt.Errorf("cgroup: either Path or Name and Parent should be used, got %+v", c)
+ }
+
+ return _defaultDirPath(UnifiedMountpoint, c.Path, c.Parent, c.Name)
+}
+
+func _defaultDirPath(root, cgPath, cgParent, cgName string) (string, error) {
+ if (cgName != "" || cgParent != "") && cgPath != "" {
+ return "", errors.New("cgroup: either Path or Name and Parent should be used")
+ }
+
+ // XXX: Do not remove CleanPath. Path safety is important! -- cyphar
+ innerPath := utils.CleanPath(cgPath)
+ if innerPath == "" {
+ cgParent := utils.CleanPath(cgParent)
+ cgName := utils.CleanPath(cgName)
+ innerPath = filepath.Join(cgParent, cgName)
+ }
+ if filepath.IsAbs(innerPath) {
+ return filepath.Join(root, innerPath), nil
+ }
+
+ ownCgroup, err := parseCgroupFile("/proc/self/cgroup")
+ if err != nil {
+ return "", err
+ }
+ // The current user scope most probably has tasks in it already,
+ // making it impossible to enable controllers for its sub-cgroup.
+ // A parent cgroup (with no tasks in it) is what we need.
+ ownCgroup = filepath.Dir(ownCgroup)
+
+ return filepath.Join(root, ownCgroup, innerPath), nil
+}
+
+// parseCgroupFile parses /proc/PID/cgroup file and return string
+func parseCgroupFile(path string) (string, error) {
+ f, err := os.Open(path)
+ if err != nil {
+ return "", err
+ }
+ defer f.Close()
+ return parseCgroupFromReader(f)
+}
+
+func parseCgroupFromReader(r io.Reader) (string, error) {
+ s := bufio.NewScanner(r)
+ for s.Scan() {
+ var (
+ text = s.Text()
+ parts = strings.SplitN(text, ":", 3)
+ )
+ if len(parts) < 3 {
+ return "", fmt.Errorf("invalid cgroup entry: %q", text)
+ }
+ // text is like "0::/user.slice/user-1001.slice/session-1.scope"
+ if parts[0] == "0" && parts[1] == "" {
+ return parts[2], nil
+ }
+ }
+ if err := s.Err(); err != nil {
+ return "", err
+ }
+ return "", errors.New("cgroup path not found")
+}
diff --git a/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs2/freezer.go b/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs2/freezer.go
new file mode 100644
index 000000000..8917a6411
--- /dev/null
+++ b/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs2/freezer.go
@@ -0,0 +1,127 @@
+package fs2
+
+import (
+ "bufio"
+ "errors"
+ "fmt"
+ "os"
+ "strings"
+ "time"
+
+ "golang.org/x/sys/unix"
+
+ "github.com/opencontainers/runc/libcontainer/cgroups"
+ "github.com/opencontainers/runc/libcontainer/configs"
+)
+
+func setFreezer(dirPath string, state configs.FreezerState) error {
+ var stateStr string
+ switch state {
+ case configs.Undefined:
+ return nil
+ case configs.Frozen:
+ stateStr = "1"
+ case configs.Thawed:
+ stateStr = "0"
+ default:
+ return fmt.Errorf("invalid freezer state %q requested", state)
+ }
+
+ fd, err := cgroups.OpenFile(dirPath, "cgroup.freeze", unix.O_RDWR)
+ if err != nil {
+ // We can ignore this request as long as the user didn't ask us to
+ // freeze the container (since without the freezer cgroup, that's a
+ // no-op).
+ if state != configs.Frozen {
+ return nil
+ }
+ return fmt.Errorf("freezer not supported: %w", err)
+ }
+ defer fd.Close()
+
+ if _, err := fd.WriteString(stateStr); err != nil {
+ return err
+ }
+ // Confirm that the cgroup did actually change states.
+ if actualState, err := readFreezer(dirPath, fd); err != nil {
+ return err
+ } else if actualState != state {
+ return fmt.Errorf(`expected "cgroup.freeze" to be in state %q but was in %q`, state, actualState)
+ }
+ return nil
+}
+
+func getFreezer(dirPath string) (configs.FreezerState, error) {
+ fd, err := cgroups.OpenFile(dirPath, "cgroup.freeze", unix.O_RDONLY)
+ if err != nil {
+ // If the kernel is too old, then we just treat the freezer as being in
+ // an "undefined" state.
+ if os.IsNotExist(err) || errors.Is(err, unix.ENODEV) {
+ err = nil
+ }
+ return configs.Undefined, err
+ }
+ defer fd.Close()
+
+ return readFreezer(dirPath, fd)
+}
+
+func readFreezer(dirPath string, fd *os.File) (configs.FreezerState, error) {
+ if _, err := fd.Seek(0, 0); err != nil {
+ return configs.Undefined, err
+ }
+ state := make([]byte, 2)
+ if _, err := fd.Read(state); err != nil {
+ return configs.Undefined, err
+ }
+ switch string(state) {
+ case "0\n":
+ return configs.Thawed, nil
+ case "1\n":
+ return waitFrozen(dirPath)
+ default:
+ return configs.Undefined, fmt.Errorf(`unknown "cgroup.freeze" state: %q`, state)
+ }
+}
+
+// waitFrozen polls cgroup.events until it sees "frozen 1" in it.
+func waitFrozen(dirPath string) (configs.FreezerState, error) {
+ fd, err := cgroups.OpenFile(dirPath, "cgroup.events", unix.O_RDONLY)
+ if err != nil {
+ return configs.Undefined, err
+ }
+ defer fd.Close()
+
+ // XXX: Simple wait/read/retry is used here. An implementation
+ // based on poll(2) or inotify(7) is possible, but it makes the code
+ // much more complicated. Maybe address this later.
+ const (
+ // Perform maxIter with waitTime in between iterations.
+ waitTime = 10 * time.Millisecond
+ maxIter = 1000
+ )
+ scanner := bufio.NewScanner(fd)
+ for i := 0; scanner.Scan(); {
+ if i == maxIter {
+ return configs.Undefined, fmt.Errorf("timeout of %s reached waiting for the cgroup to freeze", waitTime*maxIter)
+ }
+ line := scanner.Text()
+ val := strings.TrimPrefix(line, "frozen ")
+ if val != line { // got prefix
+ if val[0] == '1' {
+ return configs.Frozen, nil
+ }
+
+ i++
+ // wait, then re-read
+ time.Sleep(waitTime)
+ _, err := fd.Seek(0, 0)
+ if err != nil {
+ return configs.Undefined, err
+ }
+ }
+ }
+ // Should only reach here either on read error,
+ // or if the file does not contain "frozen " line.
+ return configs.Undefined, scanner.Err()
+}
diff --git a/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs2/fs2.go b/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs2/fs2.go
new file mode 100644
index 000000000..d5208d778
--- /dev/null
+++ b/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs2/fs2.go
@@ -0,0 +1,271 @@
+package fs2
+
+import (
+ "errors"
+ "fmt"
+ "os"
+ "strings"
+
+ "github.com/opencontainers/runc/libcontainer/cgroups"
+ "github.com/opencontainers/runc/libcontainer/cgroups/fscommon"
+ "github.com/opencontainers/runc/libcontainer/configs"
+)
+
+type parseError = fscommon.ParseError
+
+type manager struct {
+ config *configs.Cgroup
+ // dirPath is like "/sys/fs/cgroup/user.slice/user-1001.slice/session-1.scope"
+ dirPath string
+ // controllers is content of "cgroup.controllers" file.
+ // excludes pseudo-controllers ("devices" and "freezer").
+ controllers map[string]struct{}
+}
+
+// NewManager creates a manager for cgroup v2 unified hierarchy.
+// dirPath is like "/sys/fs/cgroup/user.slice/user-1001.slice/session-1.scope".
+// If dirPath is empty, it is automatically set using config.
+func NewManager(config *configs.Cgroup, dirPath string) (cgroups.Manager, error) {
+ if dirPath == "" {
+ var err error
+ dirPath, err = defaultDirPath(config)
+ if err != nil {
+ return nil, err
+ }
+ }
+
+ m := &manager{
+ config: config,
+ dirPath: dirPath,
+ }
+ return m, nil
+}
+
+func (m *manager) getControllers() error {
+ if m.controllers != nil {
+ return nil
+ }
+
+ data, err := cgroups.ReadFile(m.dirPath, "cgroup.controllers")
+ if err != nil {
+ if m.config.Rootless && m.config.Path == "" {
+ return nil
+ }
+ return err
+ }
+ fields := strings.Fields(data)
+ m.controllers = make(map[string]struct{}, len(fields))
+ for _, c := range fields {
+ m.controllers[c] = struct{}{}
+ }
+
+ return nil
+}
+
+func (m *manager) Apply(pid int) error {
+ if err := CreateCgroupPath(m.dirPath, m.config); err != nil {
+ // Related tests:
+ // - "runc create (no limits + no cgrouppath + no permission) succeeds"
+ // - "runc create (rootless + no limits + cgrouppath + no permission) fails with permission error"
+ // - "runc create (rootless + limits + no cgrouppath + no permission) fails with informative error"
+ if m.config.Rootless {
+ if m.config.Path == "" {
+ if blNeed, nErr := needAnyControllers(m.config.Resources); nErr == nil && !blNeed {
+ return nil
+ }
+ return fmt.Errorf("rootless needs no limits + no cgrouppath when no permission is granted for cgroups: %w", err)
+ }
+ }
+ return err
+ }
+ if err := cgroups.WriteCgroupProc(m.dirPath, pid); err != nil {
+ return err
+ }
+ return nil
+}
+
+func (m *manager) GetPids() ([]int, error) {
+ return cgroups.GetPids(m.dirPath)
+}
+
+func (m *manager) GetAllPids() ([]int, error) {
+ return cgroups.GetAllPids(m.dirPath)
+}
+
+func (m *manager) GetStats() (*cgroups.Stats, error) {
+ var errs []error
+
+ st := cgroups.NewStats()
+
+ // pids (since kernel 4.5)
+ if err := statPids(m.dirPath, st); err != nil {
+ errs = append(errs, err)
+ }
+ // memory (since kernel 4.5)
+ if err := statMemory(m.dirPath, st); err != nil && !os.IsNotExist(err) {
+ errs = append(errs, err)
+ }
+ // io (since kernel 4.5)
+ if err := statIo(m.dirPath, st); err != nil && !os.IsNotExist(err) {
+ errs = append(errs, err)
+ }
+ // cpu (since kernel 4.15)
+ // Note cpu.stat is available even if the controller is not enabled.
+ if err := statCpu(m.dirPath, st); err != nil && !os.IsNotExist(err) {
+ errs = append(errs, err)
+ }
+ // hugetlb (since kernel 5.6)
+ if err := statHugeTlb(m.dirPath, st); err != nil && !os.IsNotExist(err) {
+ errs = append(errs, err)
+ }
+ // rdma (since kernel 4.11)
+ if err := fscommon.RdmaGetStats(m.dirPath, st); err != nil && !os.IsNotExist(err) {
+ errs = append(errs, err)
+ }
+ if len(errs) > 0 && !m.config.Rootless {
+ return st, fmt.Errorf("error while statting cgroup v2: %+v", errs)
+ }
+ return st, nil
+}
+
+func (m *manager) Freeze(state configs.FreezerState) error {
+ if m.config.Resources == nil {
+ return errors.New("cannot toggle freezer: cgroups not configured for container")
+ }
+ if err := setFreezer(m.dirPath, state); err != nil {
+ return err
+ }
+ m.config.Resources.Freezer = state
+ return nil
+}
+
+func (m *manager) Destroy() error {
+ return cgroups.RemovePath(m.dirPath)
+}
+
+func (m *manager) Path(_ string) string {
+ return m.dirPath
+}
+
+func (m *manager) Set(r *configs.Resources) error {
+ if r == nil {
+ return nil
+ }
+ if err := m.getControllers(); err != nil {
+ return err
+ }
+ // pids (since kernel 4.5)
+ if err := setPids(m.dirPath, r); err != nil {
+ return err
+ }
+ // memory (since kernel 4.5)
+ if err := setMemory(m.dirPath, r); err != nil {
+ return err
+ }
+ // io (since kernel 4.5)
+ if err := setIo(m.dirPath, r); err != nil {
+ return err
+ }
+ // cpu (since kernel 4.15)
+ if err := setCpu(m.dirPath, r); err != nil {
+ return err
+ }
+ // devices (since kernel 4.15, pseudo-controller)
+ //
+ // When rootless is true, errors from the device subsystem are ignored because it is really not expected to work.
+ // However, errors from other subsystems are not ignored.
+ // see @test "runc create (rootless + limits + no cgrouppath + no permission) fails with informative error"
+ if err := setDevices(m.dirPath, r); err != nil {
+ if !m.config.Rootless || errors.Is(err, cgroups.ErrDevicesUnsupported) {
+ return err
+ }
+ }
+ // cpuset (since kernel 5.0)
+ if err := setCpuset(m.dirPath, r); err != nil {
+ return err
+ }
+ // hugetlb (since kernel 5.6)
+ if err := setHugeTlb(m.dirPath, r); err != nil {
+ return err
+ }
+ // rdma (since kernel 4.11)
+ if err := fscommon.RdmaSet(m.dirPath, r); err != nil {
+ return err
+ }
+ // freezer (since kernel 5.2, pseudo-controller)
+ if err := setFreezer(m.dirPath, r.Freezer); err != nil {
+ return err
+ }
+ if err := m.setUnified(r.Unified); err != nil {
+ return err
+ }
+ m.config.Resources = r
+ return nil
+}
+
+func setDevices(dirPath string, r *configs.Resources) error {
+ if cgroups.DevicesSetV2 == nil {
+ if len(r.Devices) > 0 {
+ return cgroups.ErrDevicesUnsupported
+ }
+ return nil
+ }
+ return cgroups.DevicesSetV2(dirPath, r)
+}
+
+func (m *manager) setUnified(res map[string]string) error {
+ for k, v := range res {
+ if strings.Contains(k, "/") {
+ return fmt.Errorf("unified resource %q must be a file name (no slashes)", k)
+ }
+ if err := cgroups.WriteFile(m.dirPath, k, v); err != nil {
+ // Check for both EPERM and ENOENT since O_CREAT is used by WriteFile.
+ if errors.Is(err, os.ErrPermission) || errors.Is(err, os.ErrNotExist) {
+ // Check if a controller is available,
+ // to give more specific error if not.
+ sk := strings.SplitN(k, ".", 2)
+ if len(sk) != 2 {
+ return fmt.Errorf("unified resource %q must be in the form CONTROLLER.PARAMETER", k)
+ }
+ c := sk[0]
+ if _, ok := m.controllers[c]; !ok && c != "cgroup" {
+ return fmt.Errorf("unified resource %q can't be set: controller %q not available", k, c)
+ }
+ }
+ return fmt.Errorf("unable to set unified resource %q: %w", k, err)
+ }
+ }
+
+ return nil
+}
+
+func (m *manager) GetPaths() map[string]string {
+ paths := make(map[string]string, 1)
+ paths[""] = m.dirPath
+ return paths
+}
+
+func (m *manager) GetCgroups() (*configs.Cgroup, error) {
+ return m.config, nil
+}
+
+func (m *manager) GetFreezerState() (configs.FreezerState, error) {
+ return getFreezer(m.dirPath)
+}
+
+func (m *manager) Exists() bool {
+ return cgroups.PathExists(m.dirPath)
+}
+
+func OOMKillCount(path string) (uint64, error) {
+ return fscommon.GetValueByKey(path, "memory.events", "oom_kill")
+}
+
+func (m *manager) OOMKillCount() (uint64, error) {
+ c, err := OOMKillCount(m.dirPath)
+ if err != nil && m.config.Rootless && os.IsNotExist(err) {
+ err = nil
+ }
+
+ return c, err
+}
diff --git a/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs2/hugetlb.go b/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs2/hugetlb.go
new file mode 100644
index 000000000..c92a7e64a
--- /dev/null
+++ b/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs2/hugetlb.go
@@ -0,0 +1,48 @@
+package fs2
+
+import (
+ "strconv"
+
+ "github.com/opencontainers/runc/libcontainer/cgroups"
+ "github.com/opencontainers/runc/libcontainer/cgroups/fscommon"
+ "github.com/opencontainers/runc/libcontainer/configs"
+)
+
+func isHugeTlbSet(r *configs.Resources) bool {
+ return len(r.HugetlbLimit) > 0
+}
+
+func setHugeTlb(dirPath string, r *configs.Resources) error {
+ if !isHugeTlbSet(r) {
+ return nil
+ }
+ for _, hugetlb := range r.HugetlbLimit {
+ if err := cgroups.WriteFile(dirPath, "hugetlb."+hugetlb.Pagesize+".max", strconv.FormatUint(hugetlb.Limit, 10)); err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
+
+func statHugeTlb(dirPath string, stats *cgroups.Stats) error {
+ hugetlbStats := cgroups.HugetlbStats{}
+ for _, pagesize := range cgroups.HugePageSizes() {
+ value, err := fscommon.GetCgroupParamUint(dirPath, "hugetlb."+pagesize+".current")
+ if err != nil {
+ return err
+ }
+ hugetlbStats.Usage = value
+
+ fileName := "hugetlb." + pagesize + ".events"
+ value, err = fscommon.GetValueByKey(dirPath, fileName, "max")
+ if err != nil {
+ return err
+ }
+ hugetlbStats.Failcnt = value
+
+ stats.HugetlbStats[pagesize] = hugetlbStats
+ }
+
+ return nil
+}
diff --git a/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs2/io.go b/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs2/io.go
new file mode 100644
index 000000000..b2ff7d340
--- /dev/null
+++ b/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs2/io.go
@@ -0,0 +1,193 @@
+package fs2
+
+import (
+ "bufio"
+ "bytes"
+ "fmt"
+ "os"
+ "strconv"
+ "strings"
+
+ "github.com/sirupsen/logrus"
+
+ "github.com/opencontainers/runc/libcontainer/cgroups"
+ "github.com/opencontainers/runc/libcontainer/configs"
+)
+
+func isIoSet(r *configs.Resources) bool {
+ return r.BlkioWeight != 0 ||
+ len(r.BlkioWeightDevice) > 0 ||
+ len(r.BlkioThrottleReadBpsDevice) > 0 ||
+ len(r.BlkioThrottleWriteBpsDevice) > 0 ||
+ len(r.BlkioThrottleReadIOPSDevice) > 0 ||
+ len(r.BlkioThrottleWriteIOPSDevice) > 0
+}
+
+// bfqDeviceWeightSupported checks for per-device BFQ weight support (added
+// in kernel v5.4, commit 795fe54c2a8) by reading from "io.bfq.weight".
+func bfqDeviceWeightSupported(bfq *os.File) bool {
+ if bfq == nil {
+ return false
+ }
+ _, _ = bfq.Seek(0, 0)
+ buf := make([]byte, 32)
+ _, _ = bfq.Read(buf)
+ // If only a single number (default weight) if read back, we have older kernel.
+ _, err := strconv.ParseInt(string(bytes.TrimSpace(buf)), 10, 64)
+ return err != nil
+}
+
+func setIo(dirPath string, r *configs.Resources) error {
+ if !isIoSet(r) {
+ return nil
+ }
+
+ // If BFQ IO scheduler is available, use it.
+ var bfq *os.File
+ if r.BlkioWeight != 0 || len(r.BlkioWeightDevice) > 0 {
+ var err error
+ bfq, err = cgroups.OpenFile(dirPath, "io.bfq.weight", os.O_RDWR)
+ if err == nil {
+ defer bfq.Close()
+ } else if !os.IsNotExist(err) {
+ return err
+ }
+ }
+
+ if r.BlkioWeight != 0 {
+ if bfq != nil { // Use BFQ.
+ if _, err := bfq.WriteString(strconv.FormatUint(uint64(r.BlkioWeight), 10)); err != nil {
+ return err
+ }
+ } else {
+ // Fallback to io.weight with a conversion scheme.
+ v := cgroups.ConvertBlkIOToIOWeightValue(r.BlkioWeight)
+ if err := cgroups.WriteFile(dirPath, "io.weight", strconv.FormatUint(v, 10)); err != nil {
+ return err
+ }
+ }
+ }
+ if bfqDeviceWeightSupported(bfq) {
+ for _, wd := range r.BlkioWeightDevice {
+ if _, err := bfq.WriteString(wd.WeightString() + "\n"); err != nil {
+ return fmt.Errorf("setting device weight %q: %w", wd.WeightString(), err)
+ }
+ }
+ }
+ for _, td := range r.BlkioThrottleReadBpsDevice {
+ if err := cgroups.WriteFile(dirPath, "io.max", td.StringName("rbps")); err != nil {
+ return err
+ }
+ }
+ for _, td := range r.BlkioThrottleWriteBpsDevice {
+ if err := cgroups.WriteFile(dirPath, "io.max", td.StringName("wbps")); err != nil {
+ return err
+ }
+ }
+ for _, td := range r.BlkioThrottleReadIOPSDevice {
+ if err := cgroups.WriteFile(dirPath, "io.max", td.StringName("riops")); err != nil {
+ return err
+ }
+ }
+ for _, td := range r.BlkioThrottleWriteIOPSDevice {
+ if err := cgroups.WriteFile(dirPath, "io.max", td.StringName("wiops")); err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
+
+func readCgroup2MapFile(dirPath string, name string) (map[string][]string, error) {
+ ret := map[string][]string{}
+ f, err := cgroups.OpenFile(dirPath, name, os.O_RDONLY)
+ if err != nil {
+ return nil, err
+ }
+ defer f.Close()
+ scanner := bufio.NewScanner(f)
+ for scanner.Scan() {
+ line := scanner.Text()
+ parts := strings.Fields(line)
+ if len(parts) < 2 {
+ continue
+ }
+ ret[parts[0]] = parts[1:]
+ }
+ if err := scanner.Err(); err != nil {
+ return nil, &parseError{Path: dirPath, File: name, Err: err}
+ }
+ return ret, nil
+}
+
+func statIo(dirPath string, stats *cgroups.Stats) error {
+ const file = "io.stat"
+ values, err := readCgroup2MapFile(dirPath, file)
+ if err != nil {
+ return err
+ }
+ // more details on the io.stat file format: https://www.kernel.org/doc/Documentation/cgroup-v2.txt
+ var parsedStats cgroups.BlkioStats
+ for k, v := range values {
+ d := strings.Split(k, ":")
+ if len(d) != 2 {
+ continue
+ }
+ major, err := strconv.ParseUint(d[0], 10, 64)
+ if err != nil {
+ return &parseError{Path: dirPath, File: file, Err: err}
+ }
+ minor, err := strconv.ParseUint(d[1], 10, 64)
+ if err != nil {
+ return &parseError{Path: dirPath, File: file, Err: err}
+ }
+
+ for _, item := range v {
+ d := strings.Split(item, "=")
+ if len(d) != 2 {
+ continue
+ }
+ op := d[0]
+
+ // Map to the cgroupv1 naming and layout (in separate tables).
+ var targetTable *[]cgroups.BlkioStatEntry
+ switch op {
+ // Equivalent to cgroupv1's blkio.io_service_bytes.
+ case "rbytes":
+ op = "Read"
+ targetTable = &parsedStats.IoServiceBytesRecursive
+ case "wbytes":
+ op = "Write"
+ targetTable = &parsedStats.IoServiceBytesRecursive
+ // Equivalent to cgroupv1's blkio.io_serviced.
+ case "rios":
+ op = "Read"
+ targetTable = &parsedStats.IoServicedRecursive
+ case "wios":
+ op = "Write"
+ targetTable = &parsedStats.IoServicedRecursive
+ default:
+ // Skip over entries we cannot map to cgroupv1 stats for now.
+ // In the future we should expand the stats struct to include
+ // them.
+ logrus.Debugf("cgroupv2 io stats: skipping over unmappable %s entry", item)
+ continue
+ }
+
+ value, err := strconv.ParseUint(d[1], 10, 64)
+ if err != nil {
+ return &parseError{Path: dirPath, File: file, Err: err}
+ }
+
+ entry := cgroups.BlkioStatEntry{
+ Op: op,
+ Major: major,
+ Minor: minor,
+ Value: value,
+ }
+ *targetTable = append(*targetTable, entry)
+ }
+ }
+ stats.BlkioStats = parsedStats
+ return nil
+}
diff --git a/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs2/memory.go b/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs2/memory.go
new file mode 100644
index 000000000..adbc4b230
--- /dev/null
+++ b/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs2/memory.go
@@ -0,0 +1,216 @@
+package fs2
+
+import (
+ "bufio"
+ "errors"
+ "math"
+ "os"
+ "strconv"
+ "strings"
+
+ "golang.org/x/sys/unix"
+
+ "github.com/opencontainers/runc/libcontainer/cgroups"
+ "github.com/opencontainers/runc/libcontainer/cgroups/fscommon"
+ "github.com/opencontainers/runc/libcontainer/configs"
+)
+
+// numToStr converts an int64 value to a string for writing to a
+// cgroupv2 files with .min, .max, .low, or .high suffix.
+// The value of -1 is converted to "max" for cgroupv1 compatibility
+// (which used to write -1 to remove the limit).
+func numToStr(value int64) (ret string) {
+ switch {
+ case value == 0:
+ ret = ""
+ case value == -1:
+ ret = "max"
+ default:
+ ret = strconv.FormatInt(value, 10)
+ }
+
+ return ret
+}
+
+func isMemorySet(r *configs.Resources) bool {
+ return r.MemoryReservation != 0 || r.Memory != 0 || r.MemorySwap != 0
+}
+
+func setMemory(dirPath string, r *configs.Resources) error {
+ if !isMemorySet(r) {
+ return nil
+ }
+ swap, err := cgroups.ConvertMemorySwapToCgroupV2Value(r.MemorySwap, r.Memory)
+ if err != nil {
+ return err
+ }
+ swapStr := numToStr(swap)
+ if swapStr == "" && swap == 0 && r.MemorySwap > 0 {
+ // memory and memorySwap set to the same value -- disable swap
+ swapStr = "0"
+ }
+ // never write empty string to `memory.swap.max`, it means set to 0.
+ if swapStr != "" {
+ if err := cgroups.WriteFile(dirPath, "memory.swap.max", swapStr); err != nil {
+ return err
+ }
+ }
+
+ if val := numToStr(r.Memory); val != "" {
+ if err := cgroups.WriteFile(dirPath, "memory.max", val); err != nil {
+ return err
+ }
+ }
+
+ // cgroup.Resources.KernelMemory is ignored
+
+ if val := numToStr(r.MemoryReservation); val != "" {
+ if err := cgroups.WriteFile(dirPath, "memory.low", val); err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
+
+func statMemory(dirPath string, stats *cgroups.Stats) error {
+ const file = "memory.stat"
+ statsFile, err := cgroups.OpenFile(dirPath, file, os.O_RDONLY)
+ if err != nil {
+ return err
+ }
+ defer statsFile.Close()
+
+ sc := bufio.NewScanner(statsFile)
+ for sc.Scan() {
+ t, v, err := fscommon.ParseKeyValue(sc.Text())
+ if err != nil {
+ return &parseError{Path: dirPath, File: file, Err: err}
+ }
+ stats.MemoryStats.Stats[t] = v
+ }
+ if err := sc.Err(); err != nil {
+ return &parseError{Path: dirPath, File: file, Err: err}
+ }
+ stats.MemoryStats.Cache = stats.MemoryStats.Stats["file"]
+ // Unlike cgroup v1 which has memory.use_hierarchy binary knob,
+ // cgroup v2 is always hierarchical.
+ stats.MemoryStats.UseHierarchy = true
+
+ memoryUsage, err := getMemoryDataV2(dirPath, "")
+ if err != nil {
+ if errors.Is(err, unix.ENOENT) && dirPath == UnifiedMountpoint {
+ // The root cgroup does not have memory.{current,max}
+ // so emulate those using data from /proc/meminfo.
+ return statsFromMeminfo(stats)
+ }
+ return err
+ }
+ stats.MemoryStats.Usage = memoryUsage
+ swapUsage, err := getMemoryDataV2(dirPath, "swap")
+ if err != nil {
+ return err
+ }
+ // As cgroup v1 reports SwapUsage values as mem+swap combined,
+ // while in cgroup v2 swap values do not include memory,
+ // report combined mem+swap for v1 compatibility.
+ swapUsage.Usage += memoryUsage.Usage
+ if swapUsage.Limit != math.MaxUint64 {
+ swapUsage.Limit += memoryUsage.Limit
+ }
+ stats.MemoryStats.SwapUsage = swapUsage
+
+ return nil
+}
+
+func getMemoryDataV2(path, name string) (cgroups.MemoryData, error) {
+ memoryData := cgroups.MemoryData{}
+
+ moduleName := "memory"
+ if name != "" {
+ moduleName = "memory." + name
+ }
+ usage := moduleName + ".current"
+ limit := moduleName + ".max"
+
+ value, err := fscommon.GetCgroupParamUint(path, usage)
+ if err != nil {
+ if name != "" && os.IsNotExist(err) {
+ // Ignore EEXIST as there's no swap accounting
+ // if kernel CONFIG_MEMCG_SWAP is not set or
+ // swapaccount=0 kernel boot parameter is given.
+ return cgroups.MemoryData{}, nil
+ }
+ return cgroups.MemoryData{}, err
+ }
+ memoryData.Usage = value
+
+ value, err = fscommon.GetCgroupParamUint(path, limit)
+ if err != nil {
+ return cgroups.MemoryData{}, err
+ }
+ memoryData.Limit = value
+
+ return memoryData, nil
+}
+
+func statsFromMeminfo(stats *cgroups.Stats) error {
+ const file = "/proc/meminfo"
+ f, err := os.Open(file)
+ if err != nil {
+ return err
+ }
+ defer f.Close()
+
+ // Fields we are interested in.
+ var (
+ swap_free uint64
+ swap_total uint64
+ main_total uint64
+ main_free uint64
+ )
+ mem := map[string]*uint64{
+ "SwapFree": &swap_free,
+ "SwapTotal": &swap_total,
+ "MemTotal": &main_total,
+ "MemFree": &main_free,
+ }
+
+ found := 0
+ sc := bufio.NewScanner(f)
+ for sc.Scan() {
+ parts := strings.SplitN(sc.Text(), ":", 3)
+ if len(parts) != 2 {
+ // Should not happen.
+ continue
+ }
+ k := parts[0]
+ p, ok := mem[k]
+ if !ok {
+ // Unknown field -- not interested.
+ continue
+ }
+ vStr := strings.TrimSpace(strings.TrimSuffix(parts[1], " kB"))
+ *p, err = strconv.ParseUint(vStr, 10, 64)
+ if err != nil {
+ return &parseError{File: file, Err: errors.New("bad value for " + k)}
+ }
+
+ found++
+ if found == len(mem) {
+ // Got everything we need -- skip the rest.
+ break
+ }
+ }
+ if err := sc.Err(); err != nil {
+ return &parseError{Path: "", File: file, Err: err}
+ }
+
+ stats.MemoryStats.SwapUsage.Usage = (swap_total - swap_free) * 1024
+ stats.MemoryStats.SwapUsage.Limit = math.MaxUint64
+
+ stats.MemoryStats.Usage.Usage = (main_total - main_free) * 1024
+ stats.MemoryStats.Usage.Limit = math.MaxUint64
+
+ return nil
+}
diff --git a/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs2/pids.go b/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs2/pids.go
new file mode 100644
index 000000000..c8c4a3658
--- /dev/null
+++ b/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs2/pids.go
@@ -0,0 +1,72 @@
+package fs2
+
+import (
+ "errors"
+ "math"
+ "os"
+ "strings"
+
+ "golang.org/x/sys/unix"
+
+ "github.com/opencontainers/runc/libcontainer/cgroups"
+ "github.com/opencontainers/runc/libcontainer/cgroups/fscommon"
+ "github.com/opencontainers/runc/libcontainer/configs"
+)
+
+func isPidsSet(r *configs.Resources) bool {
+ return r.PidsLimit != 0
+}
+
+func setPids(dirPath string, r *configs.Resources) error {
+ if !isPidsSet(r) {
+ return nil
+ }
+ if val := numToStr(r.PidsLimit); val != "" {
+ if err := cgroups.WriteFile(dirPath, "pids.max", val); err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
+
+func statPidsFromCgroupProcs(dirPath string, stats *cgroups.Stats) error {
+ // if the controller is not enabled, let's read PIDS from cgroups.procs
+ // (or threads if cgroup.threads is enabled)
+ contents, err := cgroups.ReadFile(dirPath, "cgroup.procs")
+ if errors.Is(err, unix.ENOTSUP) {
+ contents, err = cgroups.ReadFile(dirPath, "cgroup.threads")
+ }
+ if err != nil {
+ return err
+ }
+ pids := strings.Count(contents, "\n")
+ stats.PidsStats.Current = uint64(pids)
+ stats.PidsStats.Limit = 0
+ return nil
+}
+
+func statPids(dirPath string, stats *cgroups.Stats) error {
+ current, err := fscommon.GetCgroupParamUint(dirPath, "pids.current")
+ if err != nil {
+ if os.IsNotExist(err) {
+ return statPidsFromCgroupProcs(dirPath, stats)
+ }
+ return err
+ }
+
+ max, err := fscommon.GetCgroupParamUint(dirPath, "pids.max")
+ if err != nil {
+ return err
+ }
+ // If no limit is set, read from pids.max returns "max", which is
+ // converted to MaxUint64 by GetCgroupParamUint. Historically, we
+ // represent "no limit" for pids as 0, thus this conversion.
+ if max == math.MaxUint64 {
+ max = 0
+ }
+
+ stats.PidsStats.Current = current
+ stats.PidsStats.Limit = max
+ return nil
+}
diff --git a/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fscommon/rdma.go b/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fscommon/rdma.go
new file mode 100644
index 000000000..d463d15ee
--- /dev/null
+++ b/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fscommon/rdma.go
@@ -0,0 +1,121 @@
+package fscommon
+
+import (
+ "bufio"
+ "errors"
+ "math"
+ "os"
+ "strconv"
+ "strings"
+
+ "github.com/opencontainers/runc/libcontainer/cgroups"
+ "github.com/opencontainers/runc/libcontainer/configs"
+ "golang.org/x/sys/unix"
+)
+
+// parseRdmaKV parses raw string to RdmaEntry.
+func parseRdmaKV(raw string, entry *cgroups.RdmaEntry) error {
+ var value uint32
+
+ parts := strings.SplitN(raw, "=", 3)
+
+ if len(parts) != 2 {
+ return errors.New("Unable to parse RDMA entry")
+ }
+
+ k, v := parts[0], parts[1]
+
+ if v == "max" {
+ value = math.MaxUint32
+ } else {
+ val64, err := strconv.ParseUint(v, 10, 32)
+ if err != nil {
+ return err
+ }
+ value = uint32(val64)
+ }
+ if k == "hca_handle" {
+ entry.HcaHandles = value
+ } else if k == "hca_object" {
+ entry.HcaObjects = value
+ }
+
+ return nil
+}
+
+// readRdmaEntries reads and converts array of rawstrings to RdmaEntries from file.
+// example entry: mlx4_0 hca_handle=2 hca_object=2000
+func readRdmaEntries(dir, file string) ([]cgroups.RdmaEntry, error) {
+ rdmaEntries := make([]cgroups.RdmaEntry, 0)
+ fd, err := cgroups.OpenFile(dir, file, unix.O_RDONLY)
+ if err != nil {
+ return nil, err
+ }
+ defer fd.Close() //nolint:errorlint
+ scanner := bufio.NewScanner(fd)
+ for scanner.Scan() {
+ parts := strings.SplitN(scanner.Text(), " ", 4)
+ if len(parts) == 3 {
+ entry := new(cgroups.RdmaEntry)
+ entry.Device = parts[0]
+ err = parseRdmaKV(parts[1], entry)
+ if err != nil {
+ continue
+ }
+ err = parseRdmaKV(parts[2], entry)
+ if err != nil {
+ continue
+ }
+
+ rdmaEntries = append(rdmaEntries, *entry)
+ }
+ }
+ return rdmaEntries, scanner.Err()
+}
+
+// RdmaGetStats returns rdma stats such as totalLimit and current entries.
+func RdmaGetStats(path string, stats *cgroups.Stats) error {
+ currentEntries, err := readRdmaEntries(path, "rdma.current")
+ if err != nil {
+ if errors.Is(err, os.ErrNotExist) {
+ err = nil
+ }
+ return err
+ }
+ maxEntries, err := readRdmaEntries(path, "rdma.max")
+ if err != nil {
+ return err
+ }
+ // If device got removed between reading two files, ignore returning stats.
+ if len(currentEntries) != len(maxEntries) {
+ return nil
+ }
+
+ stats.RdmaStats = cgroups.RdmaStats{
+ RdmaLimit: maxEntries,
+ RdmaCurrent: currentEntries,
+ }
+
+ return nil
+}
+
+func createCmdString(device string, limits configs.LinuxRdma) string {
+ cmdString := device
+ if limits.HcaHandles != nil {
+ cmdString += " hca_handle=" + strconv.FormatUint(uint64(*limits.HcaHandles), 10)
+ }
+ if limits.HcaObjects != nil {
+ cmdString += " hca_object=" + strconv.FormatUint(uint64(*limits.HcaObjects), 10)
+ }
+ return cmdString
+}
+
+// RdmaSet sets RDMA resources.
+func RdmaSet(path string, r *configs.Resources) error {
+ for device, limits := range r.Rdma {
+ if err := cgroups.WriteFile(path, "rdma.max", createCmdString(device, limits)); err != nil {
+ return err
+ }
+ }
+ return nil
+}
diff --git a/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fscommon/utils.go b/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fscommon/utils.go
new file mode 100644
index 000000000..f4a51c9e5
--- /dev/null
+++ b/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fscommon/utils.go
@@ -0,0 +1,145 @@
+package fscommon
+
+import (
+ "errors"
+ "fmt"
+ "math"
+ "path"
+ "strconv"
+ "strings"
+
+ "github.com/opencontainers/runc/libcontainer/cgroups"
+)
+
+var (
+ // Deprecated: use cgroups.OpenFile instead.
+ OpenFile = cgroups.OpenFile
+ // Deprecated: use cgroups.ReadFile instead.
+ ReadFile = cgroups.ReadFile
+ // Deprecated: use cgroups.WriteFile instead.
+ WriteFile = cgroups.WriteFile
+)
+
+// ParseError records a parse error details, including the file path.
+type ParseError struct {
+ Path string
+ File string
+ Err error
+}
+
+func (e *ParseError) Error() string {
+ return "unable to parse " + path.Join(e.Path, e.File) + ": " + e.Err.Error()
+}
+
+func (e *ParseError) Unwrap() error { return e.Err }
+
+// ParseUint converts a string to an uint64 integer.
+// Negative values are returned at zero as, due to kernel bugs,
+// some of the memory cgroup stats can be negative.
+func ParseUint(s string, base, bitSize int) (uint64, error) {
+ value, err := strconv.ParseUint(s, base, bitSize)
+ if err != nil {
+ intValue, intErr := strconv.ParseInt(s, base, bitSize)
+ // 1. Handle negative values greater than MinInt64 (and)
+ // 2. Handle negative values lesser than MinInt64
+ if intErr == nil && intValue < 0 {
+ return 0, nil
+ } else if errors.Is(intErr, strconv.ErrRange) && intValue < 0 {
+ return 0, nil
+ }
+
+ return value, err
+ }
+
+ return value, nil
+}
+
+// ParseKeyValue parses a space-separated "name value" kind of cgroup
+// parameter and returns its key as a string, and its value as uint64
+// (ParseUint is used to convert the value). For example,
+// "io_service_bytes 1234" will be returned as "io_service_bytes", 1234.
+func ParseKeyValue(t string) (string, uint64, error) {
+ parts := strings.SplitN(t, " ", 3)
+ if len(parts) != 2 {
+ return "", 0, fmt.Errorf("line %q is not in key value format", t)
+ }
+
+ value, err := ParseUint(parts[1], 10, 64)
+ if err != nil {
+ return "", 0, err
+ }
+
+ return parts[0], value, nil
+}
+
+// GetValueByKey reads a key-value pairs from the specified cgroup file,
+// and returns a value of the specified key. ParseUint is used for value
+// conversion.
+func GetValueByKey(path, file, key string) (uint64, error) {
+ content, err := cgroups.ReadFile(path, file)
+ if err != nil {
+ return 0, err
+ }
+
+ lines := strings.Split(content, "\n")
+ for _, line := range lines {
+ arr := strings.Split(line, " ")
+ if len(arr) == 2 && arr[0] == key {
+ val, err := ParseUint(arr[1], 10, 64)
+ if err != nil {
+ err = &ParseError{Path: path, File: file, Err: err}
+ }
+ return val, err
+ }
+ }
+
+ return 0, nil
+}
+
+// GetCgroupParamUint reads a single uint64 value from the specified cgroup file.
+// If the value read is "max", the math.MaxUint64 is returned.
+func GetCgroupParamUint(path, file string) (uint64, error) {
+ contents, err := GetCgroupParamString(path, file)
+ if err != nil {
+ return 0, err
+ }
+ contents = strings.TrimSpace(contents)
+ if contents == "max" {
+ return math.MaxUint64, nil
+ }
+
+ res, err := ParseUint(contents, 10, 64)
+ if err != nil {
+ return res, &ParseError{Path: path, File: file, Err: err}
+ }
+ return res, nil
+}
+
+// GetCgroupParamInt reads a single int64 value from specified cgroup file.
+// If the value read is "max", the math.MaxInt64 is returned.
+func GetCgroupParamInt(path, file string) (int64, error) {
+ contents, err := cgroups.ReadFile(path, file)
+ if err != nil {
+ return 0, err
+ }
+ contents = strings.TrimSpace(contents)
+ if contents == "max" {
+ return math.MaxInt64, nil
+ }
+
+ res, err := strconv.ParseInt(contents, 10, 64)
+ if err != nil {
+ return res, &ParseError{Path: path, File: file, Err: err}
+ }
+ return res, nil
+}
+
+// GetCgroupParamString reads a string from the specified cgroup file.
+func GetCgroupParamString(path, file string) (string, error) {
+ contents, err := cgroups.ReadFile(path, file)
+ if err != nil {
+ return "", err
+ }
+
+ return strings.TrimSpace(contents), nil
+}
diff --git a/vendor/github.com/opencontainers/runc/libcontainer/configs/blkio_device.go b/vendor/github.com/opencontainers/runc/libcontainer/configs/blkio_device.go
index fa195bf90..865344f99 100644
--- a/vendor/github.com/opencontainers/runc/libcontainer/configs/blkio_device.go
+++ b/vendor/github.com/opencontainers/runc/libcontainer/configs/blkio_device.go
@@ -2,8 +2,8 @@ package configs
import "fmt"
-// blockIODevice holds major:minor format supported in blkio cgroup
-type blockIODevice struct {
+// BlockIODevice holds major:minor format supported in blkio cgroup.
+type BlockIODevice struct {
// Major is the device's major number
Major int64 `json:"major"`
// Minor is the device's minor number
@@ -12,7 +12,7 @@ type blockIODevice struct {
// WeightDevice struct holds a `major:minor weight`|`major:minor leaf_weight` pair
type WeightDevice struct {
- blockIODevice
+ BlockIODevice
// Weight is the bandwidth rate for the device, range is from 10 to 1000
Weight uint16 `json:"weight"`
// LeafWeight is the bandwidth rate for the device while competing with the cgroup's child cgroups, range is from 10 to 1000, cfq scheduler only
@@ -41,7 +41,7 @@ func (wd *WeightDevice) LeafWeightString() string {
// ThrottleDevice struct holds a `major:minor rate_per_second` pair
type ThrottleDevice struct {
- blockIODevice
+ BlockIODevice
// Rate is the IO rate limit per cgroup per device
Rate uint64 `json:"rate"`
}
diff --git a/vendor/github.com/opencontainers/runc/libcontainer/configs/config.go b/vendor/github.com/opencontainers/runc/libcontainer/configs/config.go
index c1b4a0041..7cf2fb657 100644
--- a/vendor/github.com/opencontainers/runc/libcontainer/configs/config.go
+++ b/vendor/github.com/opencontainers/runc/libcontainer/configs/config.go
@@ -83,9 +83,6 @@ type Syscall struct {
Args []*Arg `json:"args"`
}
-// TODO Windows. Many of these fields should be factored out into those parts
-// which are common across platforms, and those which are platform specific.
-
// Config defines configuration options for executing a process inside a contained environment.
type Config struct {
// NoPivotRoot will use MS_MOVE and a chroot to jail the process into the container's rootfs
diff --git a/vendor/github.com/opencontainers/runc/libcontainer/configs/mount.go b/vendor/github.com/opencontainers/runc/libcontainer/configs/mount.go
index 784c61820..b4c616d55 100644
--- a/vendor/github.com/opencontainers/runc/libcontainer/configs/mount.go
+++ b/vendor/github.com/opencontainers/runc/libcontainer/configs/mount.go
@@ -35,12 +35,6 @@ type Mount struct {
// Extensions are additional flags that are specific to runc.
Extensions int `json:"extensions"`
-
- // Optional Command to be run before Source is mounted.
- PremountCmds []Command `json:"premount_cmds"`
-
- // Optional Command to be run after Source is mounted.
- PostmountCmds []Command `json:"postmount_cmds"`
}
func (m *Mount) IsBind() bool {
diff --git a/vendor/github.com/opencontainers/runc/libcontainer/utils/utils.go b/vendor/github.com/opencontainers/runc/libcontainer/utils/utils.go
index 6b9fc3435..dbd435341 100644
--- a/vendor/github.com/opencontainers/runc/libcontainer/utils/utils.go
+++ b/vendor/github.com/opencontainers/runc/libcontainer/utils/utils.go
@@ -132,19 +132,16 @@ func WithProcfd(root, unsafePath string, fn func(procfd string) error) error {
return fn(procfd)
}
-// SearchLabels searches a list of key-value pairs for the provided key and
-// returns the corresponding value. The pairs must be separated with '='.
-func SearchLabels(labels []string, query string) string {
- for _, l := range labels {
- parts := strings.SplitN(l, "=", 2)
- if len(parts) < 2 {
- continue
- }
- if parts[0] == query {
- return parts[1]
+// SearchLabels searches through a list of key=value pairs for a given key,
+// returning its value, and the binary flag telling whether the key exist.
+func SearchLabels(labels []string, key string) (string, bool) {
+ key += "="
+ for _, s := range labels {
+ if strings.HasPrefix(s, key) {
+ return s[len(key):], true
}
}
- return ""
+ return "", false
}
// Annotations returns the bundle path and user defined annotations from the
diff --git a/vendor/github.com/seccomp/libseccomp-golang/CHANGELOG b/vendor/github.com/seccomp/libseccomp-golang/CHANGELOG
index a01d9a722..905a9b5cd 100644
--- a/vendor/github.com/seccomp/libseccomp-golang/CHANGELOG
+++ b/vendor/github.com/seccomp/libseccomp-golang/CHANGELOG
@@ -2,6 +2,31 @@ libseccomp-golang: Releases
===============================================================================
https://github.com/seccomp/libseccomp-golang
+* Version 0.10.0 - June 9, 2022
+- Minimum supported version of libseccomp bumped to v2.3.1
+- Add seccomp userspace notification API (ActNotify, filter.*Notif*)
+- Add filter.{Get,Set}SSB (to support SCMP_FLTATR_CTL_SSB)
+- Add filter.{Get,Set}Optimize (to support SCMP_FLTATR_CTL_OPTIMIZE)
+- Add filter.{Get,Set}RawRC (to support SCMP_FLTATR_API_SYSRAWRC)
+- Add ArchPARISC, ArchPARISC64, ArchRISCV64
+- Add ActKillProcess and ActKillThread; deprecate ActKill
+- Add go module support
+- Return ErrSyscallDoesNotExist when unable to resolve a syscall
+- Fix some functions to check for both kernel level API and libseccomp version
+- Fix MakeCondition to use sanitizeCompareOp
+- Fix AddRule to handle EACCES (from libseccomp >= 2.5.0)
+- Updated the main docs and converted to README.md
+- Added CONTRIBUTING.md, SECURITY.md, and administrative docs under doc/admin
+- Add GitHub action CI, enable more linters
+- test: test against various libseccomp versions
+- test: fix and simplify execInSubprocess
+- test: fix APILevelIsSupported
+- Refactor the Errno(-1 * retCode) pattern
+- Refactor/unify libseccomp version / API level checks
+- Code cleanups (linter, formatting, spelling fixes)
+- Cleanup: use errors.New instead of fmt.Errorf where appropriate
+- Cleanup: remove duplicated cgo stuff, redundant linux build tag
+
* Version 0.9.1 - May 21, 2019
- Minimum supported version of libseccomp bumped to v2.2.0
- Use Libseccomp's `seccomp_version` API to retrieve library version
diff --git a/vendor/github.com/seccomp/libseccomp-golang/README.md b/vendor/github.com/seccomp/libseccomp-golang/README.md
index 6430f1c9e..312135ee5 100644
--- a/vendor/github.com/seccomp/libseccomp-golang/README.md
+++ b/vendor/github.com/seccomp/libseccomp-golang/README.md
@@ -22,19 +22,37 @@ The library source repository currently lives on GitHub at the following URLs:
* https://github.com/seccomp/libseccomp-golang
* https://github.com/seccomp/libseccomp
-The project mailing list is currently hosted on Google Groups at the URL below,
-please note that a Google account is not required to subscribe to the mailing
-list.
-
-* https://groups.google.com/d/forum/libseccomp
-
Documentation for this package is also available at:
* https://pkg.go.dev/github.com/seccomp/libseccomp-golang
+## Verifying Releases
+
+Starting with libseccomp-golang v0.10.0, the git tag corresponding to each
+release should be signed by one of the libseccomp-golang maintainers. It is
+recommended that before use you verify the release tags using the following
+command:
+
+ % git tag -v <tag>
+
+At present, only the following keys, specified via the fingerprints below, are
+authorized to sign official libseccomp-golang release tags:
+
+ Paul Moore <paul@paul-moore.com>
+ 7100 AADF AE6E 6E94 0D2E 0AD6 55E4 5A5A E8CA 7C8A
+
+ Tom Hromatka <tom.hromatka@oracle.com>
+ 47A6 8FCE 37C7 D702 4FD6 5E11 356C E62C 2B52 4099
+
+ Kir Kolyshkin <kolyshkin@gmail.com>
+ C242 8CD7 5720 FACD CF76 B6EA 17DE 5ECB 75A1 100E
+
+More information on GnuPG and git tag verification can be found at their
+respective websites: https://git-scm.com/docs/git and https://gnupg.org.
+
## Installing the package
- # go get github.com/seccomp/libseccomp-golang
+ % go get github.com/seccomp/libseccomp-golang
## Contributing
diff --git a/vendor/github.com/seccomp/libseccomp-golang/SECURITY.md b/vendor/github.com/seccomp/libseccomp-golang/SECURITY.md
index c448faa8e..f645d4efe 100644
--- a/vendor/github.com/seccomp/libseccomp-golang/SECURITY.md
+++ b/vendor/github.com/seccomp/libseccomp-golang/SECURITY.md
@@ -22,6 +22,7 @@ window.
* Paul Moore, paul@paul-moore.com
* Tom Hromatka, tom.hromatka@oracle.com
+* Kir Kolyshkin, kolyshkin@gmail.com
### Resolving Sensitive Security Issues
diff --git a/vendor/github.com/seccomp/libseccomp-golang/seccomp.go b/vendor/github.com/seccomp/libseccomp-golang/seccomp.go
index 8dad12fdb..c23406754 100644
--- a/vendor/github.com/seccomp/libseccomp-golang/seccomp.go
+++ b/vendor/github.com/seccomp/libseccomp-golang/seccomp.go
@@ -7,6 +7,7 @@
package seccomp
import (
+ "errors"
"fmt"
"os"
"runtime"
@@ -245,8 +246,8 @@ const (
)
// ErrSyscallDoesNotExist represents an error condition where
-// libseccomp is unable to resolve the syscall
-var ErrSyscallDoesNotExist = fmt.Errorf("could not resolve syscall name")
+// libseccomp is unable to resolve the syscall.
+var ErrSyscallDoesNotExist = errors.New("could not resolve syscall name")
const (
// Userspace notification response flags
@@ -556,7 +557,7 @@ func MakeCondition(arg uint, comparison ScmpCompareOp, values ...uint64) (ScmpCo
} else if len(values) > 2 {
return condStruct, fmt.Errorf("conditions can have at most 2 arguments (%d given)", len(values))
} else if len(values) == 0 {
- return condStruct, fmt.Errorf("must provide at least one value to compare against")
+ return condStruct, errors.New("must provide at least one value to compare against")
}
condStruct.Argument = arg
@@ -611,7 +612,7 @@ func NewFilter(defaultAction ScmpAction) (*ScmpFilter, error) {
fPtr := C.seccomp_init(defaultAction.toNative())
if fPtr == nil {
- return nil, fmt.Errorf("could not create filter")
+ return nil, errors.New("could not create filter")
}
filter := new(ScmpFilter)
@@ -623,7 +624,7 @@ func NewFilter(defaultAction ScmpAction) (*ScmpFilter, error) {
// If the kernel does not support TSYNC, allow us to continue without error.
if err := filter.setFilterAttr(filterAttrTsync, 0x1); err != nil && err != syscall.ENOTSUP {
filter.Release()
- return nil, fmt.Errorf("could not create filter - error setting tsync bit: %v", err)
+ return nil, fmt.Errorf("could not create filter: error setting tsync bit: %w", err)
}
return filter, nil
@@ -695,14 +696,14 @@ func (f *ScmpFilter) Merge(src *ScmpFilter) error {
defer src.lock.Unlock()
if !src.valid || !f.valid {
- return fmt.Errorf("one or more of the filter contexts is invalid or uninitialized")
+ return errors.New("one or more of the filter contexts is invalid or uninitialized")
}
// Merge the filters
if retCode := C.seccomp_merge(f.filterCtx, src.filterCtx); retCode != 0 {
e := errRc(retCode)
if e == syscall.EINVAL {
- return fmt.Errorf("filters could not be merged due to a mismatch in attributes or invalid filter")
+ return fmt.Errorf("filters could not be merged due to a mismatch in attributes or invalid filter: %w", e)
}
return e
}
diff --git a/vendor/github.com/seccomp/libseccomp-golang/seccomp_internal.go b/vendor/github.com/seccomp/libseccomp-golang/seccomp_internal.go
index df4dfb7eb..0a7fd34f5 100644
--- a/vendor/github.com/seccomp/libseccomp-golang/seccomp_internal.go
+++ b/vendor/github.com/seccomp/libseccomp-golang/seccomp_internal.go
@@ -340,7 +340,7 @@ func ensureSupportedVersion() error {
func getAPI() (uint, error) {
api := C.seccomp_api_get()
if api == 0 {
- return 0, fmt.Errorf("API level operations are not supported")
+ return 0, errors.New("API level operations are not supported")
}
return uint(api), nil
@@ -349,11 +349,12 @@ func getAPI() (uint, error) {
// Set the API level
func setAPI(api uint) error {
if retCode := C.seccomp_api_set(C.uint(api)); retCode != 0 {
- if errRc(retCode) == syscall.EOPNOTSUPP {
- return fmt.Errorf("API level operations are not supported")
+ e := errRc(retCode)
+ if e == syscall.EOPNOTSUPP {
+ return errors.New("API level operations are not supported")
}
- return fmt.Errorf("could not set API level: %v", retCode)
+ return fmt.Errorf("could not set API level: %w", e)
}
return nil
@@ -411,7 +412,7 @@ func (f *ScmpFilter) setFilterAttr(attr scmpFilterAttr, value C.uint32_t) error
// Wrapper for seccomp_rule_add_... functions
func (f *ScmpFilter) addRuleWrapper(call ScmpSyscall, action ScmpAction, exact bool, length C.uint, cond C.scmp_cast_t) error {
if length != 0 && cond == nil {
- return fmt.Errorf("null conditions list, but length is nonzero")
+ return errors.New("null conditions list, but length is nonzero")
}
var retCode C.int
@@ -430,7 +431,7 @@ func (f *ScmpFilter) addRuleWrapper(call ScmpSyscall, action ScmpAction, exact b
case syscall.EPERM, syscall.EACCES:
return errDefAction
case syscall.EINVAL:
- return fmt.Errorf("two checks on same syscall argument")
+ return errors.New("two checks on same syscall argument")
default:
return e
}
@@ -455,7 +456,7 @@ func (f *ScmpFilter) addRuleGeneric(call ScmpSyscall, action ScmpAction, exact b
} else {
argsArr := C.make_arg_cmp_array(C.uint(len(conds)))
if argsArr == nil {
- return fmt.Errorf("error allocating memory for conditions")
+ return errors.New("error allocating memory for conditions")
}
defer C.free(argsArr)
@@ -495,7 +496,7 @@ func sanitizeAction(in ScmpAction) error {
}
if inTmp != ActTrace && inTmp != ActErrno && (in&0xFFFF0000) != 0 {
- return fmt.Errorf("highest 16 bits must be zeroed except for Trace and Errno")
+ return errors.New("highest 16 bits must be zeroed except for Trace and Errno")
}
return nil
diff --git a/vendor/github.com/stretchr/testify/assert/assertion_format.go b/vendor/github.com/stretchr/testify/assert/assertion_format.go
index 27e2420ed..7880b8f94 100644
--- a/vendor/github.com/stretchr/testify/assert/assertion_format.go
+++ b/vendor/github.com/stretchr/testify/assert/assertion_format.go
@@ -736,6 +736,16 @@ func WithinDurationf(t TestingT, expected time.Time, actual time.Time, delta tim
return WithinDuration(t, expected, actual, delta, append([]interface{}{msg}, args...)...)
}
+// WithinRangef asserts that a time is within a time range (inclusive).
+//
+// assert.WithinRangef(t, time.Now(), time.Now().Add(-time.Second), time.Now().Add(time.Second), "error message %s", "formatted")
+func WithinRangef(t TestingT, actual time.Time, start time.Time, end time.Time, msg string, args ...interface{}) bool {
+ if h, ok := t.(tHelper); ok {
+ h.Helper()
+ }
+ return WithinRange(t, actual, start, end, append([]interface{}{msg}, args...)...)
+}
+
// YAMLEqf asserts that two YAML strings are equivalent.
func YAMLEqf(t TestingT, expected string, actual string, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
diff --git a/vendor/github.com/stretchr/testify/assert/assertion_forward.go b/vendor/github.com/stretchr/testify/assert/assertion_forward.go
index d9ea368d0..339515b8b 100644
--- a/vendor/github.com/stretchr/testify/assert/assertion_forward.go
+++ b/vendor/github.com/stretchr/testify/assert/assertion_forward.go
@@ -1461,6 +1461,26 @@ func (a *Assertions) WithinDurationf(expected time.Time, actual time.Time, delta
return WithinDurationf(a.t, expected, actual, delta, msg, args...)
}
+// WithinRange asserts that a time is within a time range (inclusive).
+//
+// a.WithinRange(time.Now(), time.Now().Add(-time.Second), time.Now().Add(time.Second))
+func (a *Assertions) WithinRange(actual time.Time, start time.Time, end time.Time, msgAndArgs ...interface{}) bool {
+ if h, ok := a.t.(tHelper); ok {
+ h.Helper()
+ }
+ return WithinRange(a.t, actual, start, end, msgAndArgs...)
+}
+
+// WithinRangef asserts that a time is within a time range (inclusive).
+//
+// a.WithinRangef(time.Now(), time.Now().Add(-time.Second), time.Now().Add(time.Second), "error message %s", "formatted")
+func (a *Assertions) WithinRangef(actual time.Time, start time.Time, end time.Time, msg string, args ...interface{}) bool {
+ if h, ok := a.t.(tHelper); ok {
+ h.Helper()
+ }
+ return WithinRangef(a.t, actual, start, end, msg, args...)
+}
+
// YAMLEq asserts that two YAML strings are equivalent.
func (a *Assertions) YAMLEq(expected string, actual string, msgAndArgs ...interface{}) bool {
if h, ok := a.t.(tHelper); ok {
diff --git a/vendor/github.com/stretchr/testify/assert/assertions.go b/vendor/github.com/stretchr/testify/assert/assertions.go
index 580fdea4c..fa1245b18 100644
--- a/vendor/github.com/stretchr/testify/assert/assertions.go
+++ b/vendor/github.com/stretchr/testify/assert/assertions.go
@@ -8,6 +8,7 @@ import (
"fmt"
"math"
"os"
+ "path/filepath"
"reflect"
"regexp"
"runtime"
@@ -144,7 +145,8 @@ func CallerInfo() []string {
if len(parts) > 1 {
dir := parts[len(parts)-2]
if (dir != "assert" && dir != "mock" && dir != "require") || file == "mock_test.go" {
- callers = append(callers, fmt.Sprintf("%s:%d", file, line))
+ path, _ := filepath.Abs(file)
+ callers = append(callers, fmt.Sprintf("%s:%d", path, line))
}
}
@@ -816,7 +818,6 @@ func Subset(t TestingT, list, subset interface{}, msgAndArgs ...interface{}) (ok
return true // we consider nil to be equal to the nil set
}
- subsetValue := reflect.ValueOf(subset)
defer func() {
if e := recover(); e != nil {
ok = false
@@ -826,14 +827,32 @@ func Subset(t TestingT, list, subset interface{}, msgAndArgs ...interface{}) (ok
listKind := reflect.TypeOf(list).Kind()
subsetKind := reflect.TypeOf(subset).Kind()
- if listKind != reflect.Array && listKind != reflect.Slice {
+ if listKind != reflect.Array && listKind != reflect.Slice && listKind != reflect.Map {
return Fail(t, fmt.Sprintf("%q has an unsupported type %s", list, listKind), msgAndArgs...)
}
- if subsetKind != reflect.Array && subsetKind != reflect.Slice {
+ if subsetKind != reflect.Array && subsetKind != reflect.Slice && listKind != reflect.Map {
return Fail(t, fmt.Sprintf("%q has an unsupported type %s", subset, subsetKind), msgAndArgs...)
}
+ subsetValue := reflect.ValueOf(subset)
+ if subsetKind == reflect.Map && listKind == reflect.Map {
+ listValue := reflect.ValueOf(list)
+ subsetKeys := subsetValue.MapKeys()
+
+ for i := 0; i < len(subsetKeys); i++ {
+ subsetKey := subsetKeys[i]
+ subsetElement := subsetValue.MapIndex(subsetKey).Interface()
+ listElement := listValue.MapIndex(subsetKey).Interface()
+
+ if !ObjectsAreEqual(subsetElement, listElement) {
+ return Fail(t, fmt.Sprintf("\"%s\" does not contain \"%s\"", list, subsetElement), msgAndArgs...)
+ }
+ }
+
+ return true
+ }
+
for i := 0; i < subsetValue.Len(); i++ {
element := subsetValue.Index(i).Interface()
ok, found := containsElement(list, element)
@@ -860,7 +879,6 @@ func NotSubset(t TestingT, list, subset interface{}, msgAndArgs ...interface{})
return Fail(t, "nil is the empty set which is a subset of every set", msgAndArgs...)
}
- subsetValue := reflect.ValueOf(subset)
defer func() {
if e := recover(); e != nil {
ok = false
@@ -870,14 +888,32 @@ func NotSubset(t TestingT, list, subset interface{}, msgAndArgs ...interface{})
listKind := reflect.TypeOf(list).Kind()
subsetKind := reflect.TypeOf(subset).Kind()
- if listKind != reflect.Array && listKind != reflect.Slice {
+ if listKind != reflect.Array && listKind != reflect.Slice && listKind != reflect.Map {
return Fail(t, fmt.Sprintf("%q has an unsupported type %s", list, listKind), msgAndArgs...)
}
- if subsetKind != reflect.Array && subsetKind != reflect.Slice {
+ if subsetKind != reflect.Array && subsetKind != reflect.Slice && listKind != reflect.Map {
return Fail(t, fmt.Sprintf("%q has an unsupported type %s", subset, subsetKind), msgAndArgs...)
}
+ subsetValue := reflect.ValueOf(subset)
+ if subsetKind == reflect.Map && listKind == reflect.Map {
+ listValue := reflect.ValueOf(list)
+ subsetKeys := subsetValue.MapKeys()
+
+ for i := 0; i < len(subsetKeys); i++ {
+ subsetKey := subsetKeys[i]
+ subsetElement := subsetValue.MapIndex(subsetKey).Interface()
+ listElement := listValue.MapIndex(subsetKey).Interface()
+
+ if !ObjectsAreEqual(subsetElement, listElement) {
+ return true
+ }
+ }
+
+ return Fail(t, fmt.Sprintf("%q is a subset of %q", subset, list), msgAndArgs...)
+ }
+
for i := 0; i < subsetValue.Len(); i++ {
element := subsetValue.Index(i).Interface()
ok, found := containsElement(list, element)
@@ -1110,6 +1146,27 @@ func WithinDuration(t TestingT, expected, actual time.Time, delta time.Duration,
return true
}
+// WithinRange asserts that a time is within a time range (inclusive).
+//
+// assert.WithinRange(t, time.Now(), time.Now().Add(-time.Second), time.Now().Add(time.Second))
+func WithinRange(t TestingT, actual, start, end time.Time, msgAndArgs ...interface{}) bool {
+ if h, ok := t.(tHelper); ok {
+ h.Helper()
+ }
+
+ if end.Before(start) {
+ return Fail(t, "Start should be before end", msgAndArgs...)
+ }
+
+ if actual.Before(start) {
+ return Fail(t, fmt.Sprintf("Time %v expected to be in time range %v to %v, but is before the range", actual, start, end), msgAndArgs...)
+ } else if actual.After(end) {
+ return Fail(t, fmt.Sprintf("Time %v expected to be in time range %v to %v, but is after the range", actual, start, end), msgAndArgs...)
+ }
+
+ return true
+}
+
func toFloat(x interface{}) (float64, bool) {
var xf float64
xok := true
diff --git a/vendor/github.com/stretchr/testify/require/require.go b/vendor/github.com/stretchr/testify/require/require.go
index 59c48277a..880853f5a 100644
--- a/vendor/github.com/stretchr/testify/require/require.go
+++ b/vendor/github.com/stretchr/testify/require/require.go
@@ -1864,6 +1864,32 @@ func WithinDurationf(t TestingT, expected time.Time, actual time.Time, delta tim
t.FailNow()
}
+// WithinRange asserts that a time is within a time range (inclusive).
+//
+// assert.WithinRange(t, time.Now(), time.Now().Add(-time.Second), time.Now().Add(time.Second))
+func WithinRange(t TestingT, actual time.Time, start time.Time, end time.Time, msgAndArgs ...interface{}) {
+ if h, ok := t.(tHelper); ok {
+ h.Helper()
+ }
+ if assert.WithinRange(t, actual, start, end, msgAndArgs...) {
+ return
+ }
+ t.FailNow()
+}
+
+// WithinRangef asserts that a time is within a time range (inclusive).
+//
+// assert.WithinRangef(t, time.Now(), time.Now().Add(-time.Second), time.Now().Add(time.Second), "error message %s", "formatted")
+func WithinRangef(t TestingT, actual time.Time, start time.Time, end time.Time, msg string, args ...interface{}) {
+ if h, ok := t.(tHelper); ok {
+ h.Helper()
+ }
+ if assert.WithinRangef(t, actual, start, end, msg, args...) {
+ return
+ }
+ t.FailNow()
+}
+
// YAMLEq asserts that two YAML strings are equivalent.
func YAMLEq(t TestingT, expected string, actual string, msgAndArgs ...interface{}) {
if h, ok := t.(tHelper); ok {
diff --git a/vendor/github.com/stretchr/testify/require/require_forward.go b/vendor/github.com/stretchr/testify/require/require_forward.go
index 5bb07c89c..960bf6f2c 100644
--- a/vendor/github.com/stretchr/testify/require/require_forward.go
+++ b/vendor/github.com/stretchr/testify/require/require_forward.go
@@ -1462,6 +1462,26 @@ func (a *Assertions) WithinDurationf(expected time.Time, actual time.Time, delta
WithinDurationf(a.t, expected, actual, delta, msg, args...)
}
+// WithinRange asserts that a time is within a time range (inclusive).
+//
+// a.WithinRange(time.Now(), time.Now().Add(-time.Second), time.Now().Add(time.Second))
+func (a *Assertions) WithinRange(actual time.Time, start time.Time, end time.Time, msgAndArgs ...interface{}) {
+ if h, ok := a.t.(tHelper); ok {
+ h.Helper()
+ }
+ WithinRange(a.t, actual, start, end, msgAndArgs...)
+}
+
+// WithinRangef asserts that a time is within a time range (inclusive).
+//
+// a.WithinRangef(time.Now(), time.Now().Add(-time.Second), time.Now().Add(time.Second), "error message %s", "formatted")
+func (a *Assertions) WithinRangef(actual time.Time, start time.Time, end time.Time, msg string, args ...interface{}) {
+ if h, ok := a.t.(tHelper); ok {
+ h.Helper()
+ }
+ WithinRangef(a.t, actual, start, end, msg, args...)
+}
+
// YAMLEq asserts that two YAML strings are equivalent.
func (a *Assertions) YAMLEq(expected string, actual string, msgAndArgs ...interface{}) {
if h, ok := a.t.(tHelper); ok {
diff --git a/vendor/modules.txt b/vendor/modules.txt
index cd7e224b3..3e8657bc4 100644
--- a/vendor/modules.txt
+++ b/vendor/modules.txt
@@ -111,7 +111,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.20220608111710-dbecabbe82c9
+# github.com/containers/common v0.48.1-0.20220628131511-a8336c1613fe
## explicit
github.com/containers/common/libimage
github.com/containers/common/libimage/define
@@ -555,10 +555,13 @@ 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.3
+# github.com/opencontainers/runc v1.1.3 => github.com/opencontainers/runc v1.1.1-0.20220617142545-8b9452f75cbc
## explicit
github.com/opencontainers/runc/libcontainer/apparmor
github.com/opencontainers/runc/libcontainer/cgroups
+github.com/opencontainers/runc/libcontainer/cgroups/fs
+github.com/opencontainers/runc/libcontainer/cgroups/fs2
+github.com/opencontainers/runc/libcontainer/cgroups/fscommon
github.com/opencontainers/runc/libcontainer/configs
github.com/opencontainers/runc/libcontainer/devices
github.com/opencontainers/runc/libcontainer/user
@@ -628,7 +631,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.20220502022130-f33da4d89646
+# github.com/seccomp/libseccomp-golang v0.10.0
github.com/seccomp/libseccomp-golang
# github.com/sirupsen/logrus v1.8.1
## explicit
@@ -642,7 +645,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.5
+# github.com/stretchr/testify v1.8.0
## explicit
github.com/stretchr/testify/assert
github.com/stretchr/testify/require
@@ -869,3 +872,4 @@ gopkg.in/yaml.v2
gopkg.in/yaml.v3
# sigs.k8s.io/yaml v1.3.0
sigs.k8s.io/yaml
+# github.com/opencontainers/runc => github.com/opencontainers/runc v1.1.1-0.20220617142545-8b9452f75cbc