summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.cirrus.yml11
-rw-r--r--Makefile4
-rw-r--r--cmd/podman/common/create.go112
-rw-r--r--cmd/podman/machine/info.go29
-rw-r--r--cmd/podman/machine/ssh.go9
-rwxr-xr-xcontrib/cirrus/runner.sh4
-rwxr-xr-xcontrib/cirrus/setup_environment.sh46
-rw-r--r--docs/source/markdown/podman-container-clone.1.md16
-rw-r--r--docs/source/markdown/podman-kube-play.1.md2
-rw-r--r--docs/source/markdown/podman-network-create.1.md3
-rw-r--r--docs/source/markdown/podman-pod-clone.1.md69
-rw-r--r--docs/source/markdown/podman-pod-create.1.md74
-rw-r--r--go.mod4
-rw-r--r--go.sum6
-rwxr-xr-xhack/golangci-lint.sh4
-rw-r--r--libpod/container_internal.go4
-rw-r--r--libpod/container_internal_linux.go41
-rw-r--r--libpod/container_log.go14
-rw-r--r--libpod/container_log_linux.go9
-rw-r--r--libpod/define/annotations.go5
-rw-r--r--libpod/define/pod_inspect.go12
-rw-r--r--libpod/events/journal_linux.go13
-rw-r--r--libpod/events/logfile.go26
-rw-r--r--libpod/options.go12
-rw-r--r--libpod/pod.go108
-rw-r--r--libpod/pod_api.go72
-rw-r--r--libpod/runtime_pod_linux.go4
-rw-r--r--pkg/api/handlers/compat/images.go32
-rw-r--r--pkg/api/handlers/compat/images_push.go1
-rw-r--r--pkg/api/handlers/compat/networks.go54
-rw-r--r--pkg/api/handlers/libpod/images.go71
-rw-r--r--pkg/api/handlers/libpod/images_push.go144
-rw-r--r--pkg/api/server/register_images.go5
-rw-r--r--pkg/bindings/images/images.go40
-rw-r--r--pkg/bindings/images/push.go96
-rw-r--r--pkg/bindings/images/types.go2
-rw-r--r--pkg/bindings/images/types_push_options.go15
-rw-r--r--pkg/domain/entities/images.go15
-rw-r--r--pkg/domain/entities/machine.go22
-rw-r--r--pkg/domain/infra/abi/images.go3
-rw-r--r--pkg/domain/infra/abi/play.go6
-rw-r--r--pkg/domain/infra/tunnel/images.go2
-rw-r--r--pkg/hooks/0.1.0/hook.go88
-rw-r--r--pkg/hooks/0.1.0/hook_test.go182
-rw-r--r--pkg/hooks/1.0.0/hook.go89
-rw-r--r--pkg/hooks/1.0.0/hook_test.go214
-rw-r--r--pkg/hooks/1.0.0/when.go96
-rw-r--r--pkg/hooks/1.0.0/when_test.go329
-rw-r--r--pkg/hooks/README.md22
-rw-r--r--pkg/hooks/docs/.gitignore1
-rw-r--r--pkg/hooks/docs/oci-hooks.5.md182
-rw-r--r--pkg/hooks/exec/exec.go69
-rw-r--r--pkg/hooks/exec/exec_test.go222
-rw-r--r--pkg/hooks/exec/runtimeconfigfilter.go72
-rw-r--r--pkg/hooks/exec/runtimeconfigfilter_test.go265
-rw-r--r--pkg/hooks/hooks.go145
-rw-r--r--pkg/hooks/hooks_test.go218
-rw-r--r--pkg/hooks/monitor.go66
-rw-r--r--pkg/hooks/monitor_test.go324
-rw-r--r--pkg/hooks/read.go101
-rw-r--r--pkg/hooks/read_test.go194
-rw-r--r--pkg/hooks/version.go6
-rw-r--r--pkg/machine/e2e/basic_test.go4
-rw-r--r--pkg/machine/e2e/config_basic_test.go (renamed from pkg/machine/e2e/config_basic.go)3
-rw-r--r--pkg/machine/e2e/config_info_test.go (renamed from pkg/machine/e2e/config_info.go)2
-rw-r--r--pkg/machine/e2e/config_init_test.go (renamed from pkg/machine/e2e/config_init.go)6
-rw-r--r--pkg/machine/e2e/config_inspect_test.go (renamed from pkg/machine/e2e/config_inspect.go)2
-rw-r--r--pkg/machine/e2e/config_list_test.go (renamed from pkg/machine/e2e/config_list.go)2
-rw-r--r--pkg/machine/e2e/config_rm_test.go (renamed from pkg/machine/e2e/config_rm.go)8
-rw-r--r--pkg/machine/e2e/config_set_test.go (renamed from pkg/machine/e2e/config_set.go)2
-rw-r--r--pkg/machine/e2e/config_ssh_test.go (renamed from pkg/machine/e2e/config_ssh.go)8
-rw-r--r--pkg/machine/e2e/config_start_test.go (renamed from pkg/machine/e2e/config_start.go)3
-rw-r--r--pkg/machine/e2e/config_stop_test.go (renamed from pkg/machine/e2e/config_stop.go)3
-rw-r--r--pkg/machine/e2e/config_test.go (renamed from pkg/machine/e2e/config.go)26
-rw-r--r--pkg/machine/e2e/info_test.go6
-rw-r--r--pkg/machine/e2e/init_test.go8
-rw-r--r--pkg/machine/e2e/inspect_test.go8
-rw-r--r--pkg/machine/e2e/list_test.go8
-rw-r--r--pkg/machine/e2e/machine_test.go4
-rw-r--r--pkg/machine/e2e/rm_test.go2
-rw-r--r--pkg/machine/e2e/set_test.go8
-rw-r--r--pkg/machine/e2e/ssh_test.go14
-rw-r--r--pkg/machine/e2e/start_test.go2
-rw-r--r--pkg/machine/e2e/stop_test.go2
-rw-r--r--pkg/machine/fedora.go2
-rw-r--r--pkg/specgen/generate/container.go114
-rw-r--r--pkg/specgen/generate/container_create.go3
-rw-r--r--pkg/specgen/generate/oci.go35
-rw-r--r--pkg/specgen/generate/pod_create.go37
-rw-r--r--pkg/specgenutil/specgen.go12
-rw-r--r--podman.spec.rpkg4
-rw-r--r--test/apiv2/35-networks.at12
-rwxr-xr-xtest/compose/test-compose1
-rw-r--r--test/e2e/build_test.go4
-rw-r--r--test/e2e/checkpoint_image_test.go7
-rw-r--r--test/e2e/checkpoint_test.go5
-rw-r--r--test/e2e/info_test.go15
-rw-r--r--test/e2e/kill_test.go1
-rw-r--r--test/e2e/manifest_test.go3
-rw-r--r--test/e2e/network_connect_disconnect_test.go2
-rw-r--r--test/e2e/play_kube_test.go29
-rw-r--r--test/e2e/pod_create_test.go1
-rw-r--r--test/e2e/push_test.go27
-rw-r--r--test/e2e/run_passwd_test.go7
-rw-r--r--test/e2e/run_test.go7
-rw-r--r--test/system/001-basic.bats11
-rw-r--r--test/system/005-info.bats17
-rw-r--r--test/system/030-run.bats2
-rw-r--r--test/system/160-volumes.bats8
-rw-r--r--test/system/170-run-userns.bats2
-rw-r--r--test/system/200-pod.bats76
-rw-r--r--test/system/251-system-service.bats4
-rw-r--r--test/system/400-unprivileged-access.bats1
-rw-r--r--test/system/500-networking.bats2
-rw-r--r--test/utils/utils.go6
-rw-r--r--vendor/github.com/containers/image/v5/copy/copy.go5
-rw-r--r--vendor/github.com/containers/image/v5/copy/progress_bars.go15
-rw-r--r--vendor/modules.txt6
118 files changed, 1218 insertions, 3502 deletions
diff --git a/.cirrus.yml b/.cirrus.yml
index b585c41f8..c629bcf70 100644
--- a/.cirrus.yml
+++ b/.cirrus.yml
@@ -29,11 +29,11 @@ env:
####
FEDORA_NAME: "fedora-36"
#PRIOR_FEDORA_NAME: "fedora-35"
- UBUNTU_NAME: "ubuntu-2110"
+ UBUNTU_NAME: "ubuntu-2204"
# Image identifiers
- IMAGE_SUFFIX: "c6211193021923328"
- FEDORA_AMI_ID: "ami-06a41d8a81ab56afa"
+ IMAGE_SUFFIX: "c6013173500215296"
+ FEDORA_AMI_ID: "ami-0f116746f31965e41"
# Complete image names
FEDORA_CACHE_IMAGE_NAME: "fedora-${IMAGE_SUFFIX}"
#PRIOR_FEDORA_CACHE_IMAGE_NAME: "prior-fedora-${IMAGE_SUFFIX}"
@@ -178,8 +178,7 @@ build_task:
DISTRO_NV: ${UBUNTU_NAME}
VM_IMAGE_NAME: ${UBUNTU_CACHE_IMAGE_NAME}
CTR_FQIN: ${UBUNTU_CONTAINER_FQIN}
- # FIXME 2022-07-12: change to runc once #14833 is fixed!
- CI_DESIRED_RUNTIME: crun
+ CI_DESIRED_RUNTIME: runc
env:
TEST_FLAVOR: build
clone_script: *full_clone
@@ -550,6 +549,7 @@ container_integration_test_task:
_BUILD_CACHE_HANDLE: ${FEDORA_NAME}-build-${CIRRUS_BUILD_ID}
VM_IMAGE_NAME: ${FEDORA_CACHE_IMAGE_NAME}
CTR_FQIN: ${FEDORA_CONTAINER_FQIN}
+ CI_DESIRED_RUNTIME: crun
#- env:
#DISTRO_NV: ${PRIOR_FEDORA_NAME}
#_BUILD_CACHE_HANDLE: ${PRIOR_FEDORA_NAME}-build-${CIRRUS_BUILD_ID}
@@ -667,6 +667,7 @@ rootless_remote_system_test_task:
CTR_FQIN: ${FEDORA_CONTAINER_FQIN}
# ID for re-use of build output
_BUILD_CACHE_HANDLE: ${FEDORA_NAME}-build-${CIRRUS_BUILD_ID}
+ CI_DESIRED_RUNTIME: crun
<<: *local_system_test_task
alias: rootless_remote_system_test
depends_on:
diff --git a/Makefile b/Makefile
index 4dc173400..0f933ed40 100644
--- a/Makefile
+++ b/Makefile
@@ -77,7 +77,7 @@ BUILDTAGS_CROSS ?= containers_image_openpgp exclude_graphdriver_btrfs exclude_gr
CONTAINER_RUNTIME := $(shell command -v podman 2> /dev/null || echo docker)
OCI_RUNTIME ?= ""
-MANPAGES_MD ?= $(wildcard docs/source/markdown/*.md pkg/*/docs/*.md)
+MANPAGES_MD ?= $(wildcard docs/source/markdown/*.md)
MANPAGES ?= $(MANPAGES_MD:%.md=%)
MANPAGES_DEST ?= $(subst markdown,man, $(subst source,build,$(MANPAGES)))
@@ -776,9 +776,7 @@ install.modules-load: # This should only be used by distros which might use ipta
.PHONY: install.man
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)) $(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
diff --git a/cmd/podman/common/create.go b/cmd/podman/common/create.go
index d2646aa43..00873b95b 100644
--- a/cmd/podman/common/create.go
+++ b/cmd/podman/common/create.go
@@ -56,22 +56,6 @@ func DefineCreateFlags(cmd *cobra.Command, cf *entities.ContainerCreateOptions,
)
_ = cmd.RegisterFlagCompletionFunc(authfileFlagName, completion.AutocompleteDefault)
- blkioWeightFlagName := "blkio-weight"
- createFlags.StringVar(
- &cf.BlkIOWeight,
- blkioWeightFlagName, "",
- "Block IO weight (relative weight) accepts a weight value between 10 and 1000.",
- )
- _ = cmd.RegisterFlagCompletionFunc(blkioWeightFlagName, completion.AutocompleteNone)
-
- blkioWeightDeviceFlagName := "blkio-weight-device"
- createFlags.StringSliceVar(
- &cf.BlkIOWeightDevice,
- blkioWeightDeviceFlagName, []string{},
- "Block IO weight (relative device weight, format: `DEVICE_NAME:WEIGHT`)",
- )
- _ = cmd.RegisterFlagCompletionFunc(blkioWeightDeviceFlagName, completion.AutocompleteDefault)
-
capAddFlagName := "cap-add"
createFlags.StringSliceVar(
&cf.CapAdd,
@@ -127,14 +111,6 @@ func DefineCreateFlags(cmd *cobra.Command, cf *entities.ContainerCreateOptions,
)
_ = cmd.RegisterFlagCompletionFunc(deviceReadIopsFlagName, completion.AutocompleteDefault)
- deviceWriteBpsFlagName := "device-write-bps"
- createFlags.StringSliceVar(
- &cf.DeviceWriteBPs,
- deviceWriteBpsFlagName, []string{},
- "Limit write rate (bytes per second) to a device (e.g. --device-write-bps=/dev/sda:1mb)",
- )
- _ = cmd.RegisterFlagCompletionFunc(deviceWriteBpsFlagName, completion.AutocompleteDefault)
-
deviceWriteIopsFlagName := "device-write-iops"
createFlags.StringSliceVar(
&cf.DeviceWriteIOPs,
@@ -783,14 +759,6 @@ func DefineCreateFlags(cmd *cobra.Command, cf *entities.ContainerCreateOptions,
)
_ = cmd.RegisterFlagCompletionFunc(deviceFlagName, completion.AutocompleteDefault)
- deviceReadBpsFlagName := "device-read-bps"
- createFlags.StringSliceVar(
- &cf.DeviceReadBPs,
- deviceReadBpsFlagName, []string{},
- "Limit read rate (bytes per second) from a device (e.g. --device-read-bps=/dev/sda:1mb)",
- )
- _ = cmd.RegisterFlagCompletionFunc(deviceReadBpsFlagName, completion.AutocompleteDefault)
-
volumesFromFlagName := "volumes-from"
createFlags.StringArrayVar(
&cf.VolumesFrom,
@@ -848,22 +816,6 @@ func DefineCreateFlags(cmd *cobra.Command, cf *entities.ContainerCreateOptions,
)
_ = cmd.RegisterFlagCompletionFunc(cpuRtRuntimeFlagName, completion.AutocompleteNone)
- cpuSharesFlagName := "cpu-shares"
- createFlags.Uint64VarP(
- &cf.CPUShares,
- cpuSharesFlagName, "c", 0,
- "CPU shares (relative weight)",
- )
- _ = cmd.RegisterFlagCompletionFunc(cpuSharesFlagName, completion.AutocompleteNone)
-
- cpusetMemsFlagName := "cpuset-mems"
- createFlags.StringVar(
- &cf.CPUSetMems,
- cpusetMemsFlagName, "",
- "Memory nodes (MEMs) in which to allow execution (0-3, 0,1). Only effective on NUMA systems.",
- )
- _ = cmd.RegisterFlagCompletionFunc(cpusetMemsFlagName, completion.AutocompleteNone)
-
memoryReservationFlagName := "memory-reservation"
createFlags.StringVar(
&cf.MemoryReservation,
@@ -872,14 +824,6 @@ func DefineCreateFlags(cmd *cobra.Command, cf *entities.ContainerCreateOptions,
)
_ = cmd.RegisterFlagCompletionFunc(memoryReservationFlagName, completion.AutocompleteNone)
- memorySwapFlagName := "memory-swap"
- createFlags.StringVar(
- &cf.MemorySwap,
- memorySwapFlagName, "",
- "Swap limit equal to memory plus swap: '-1' to enable unlimited swap",
- )
- _ = cmd.RegisterFlagCompletionFunc(memorySwapFlagName, completion.AutocompleteNone)
-
memorySwappinessFlagName := "memory-swappiness"
createFlags.Int64Var(
&cf.MemorySwappiness,
@@ -913,4 +857,60 @@ func DefineCreateFlags(cmd *cobra.Command, cf *entities.ContainerCreateOptions,
"Memory limit "+sizeWithUnitFormat,
)
_ = cmd.RegisterFlagCompletionFunc(memoryFlagName, completion.AutocompleteNone)
+
+ cpuSharesFlagName := "cpu-shares"
+ createFlags.Uint64VarP(
+ &cf.CPUShares,
+ cpuSharesFlagName, "c", 0,
+ "CPU shares (relative weight)",
+ )
+ _ = cmd.RegisterFlagCompletionFunc(cpuSharesFlagName, completion.AutocompleteNone)
+
+ cpusetMemsFlagName := "cpuset-mems"
+ createFlags.StringVar(
+ &cf.CPUSetMems,
+ cpusetMemsFlagName, "",
+ "Memory nodes (MEMs) in which to allow execution (0-3, 0,1). Only effective on NUMA systems.",
+ )
+ _ = cmd.RegisterFlagCompletionFunc(cpusetMemsFlagName, completion.AutocompleteNone)
+
+ memorySwapFlagName := "memory-swap"
+ createFlags.StringVar(
+ &cf.MemorySwap,
+ memorySwapFlagName, "",
+ "Swap limit equal to memory plus swap: '-1' to enable unlimited swap",
+ )
+ _ = cmd.RegisterFlagCompletionFunc(memorySwapFlagName, completion.AutocompleteNone)
+
+ deviceReadBpsFlagName := "device-read-bps"
+ createFlags.StringSliceVar(
+ &cf.DeviceReadBPs,
+ deviceReadBpsFlagName, []string{},
+ "Limit read rate (bytes per second) from a device (e.g. --device-read-bps=/dev/sda:1mb)",
+ )
+ _ = cmd.RegisterFlagCompletionFunc(deviceReadBpsFlagName, completion.AutocompleteDefault)
+
+ deviceWriteBpsFlagName := "device-write-bps"
+ createFlags.StringSliceVar(
+ &cf.DeviceWriteBPs,
+ deviceWriteBpsFlagName, []string{},
+ "Limit write rate (bytes per second) to a device (e.g. --device-write-bps=/dev/sda:1mb)",
+ )
+ _ = cmd.RegisterFlagCompletionFunc(deviceWriteBpsFlagName, completion.AutocompleteDefault)
+
+ blkioWeightFlagName := "blkio-weight"
+ createFlags.StringVar(
+ &cf.BlkIOWeight,
+ blkioWeightFlagName, "",
+ "Block IO weight (relative weight) accepts a weight value between 10 and 1000.",
+ )
+ _ = cmd.RegisterFlagCompletionFunc(blkioWeightFlagName, completion.AutocompleteNone)
+
+ blkioWeightDeviceFlagName := "blkio-weight-device"
+ createFlags.StringSliceVar(
+ &cf.BlkIOWeightDevice,
+ blkioWeightDeviceFlagName, []string{},
+ "Block IO weight (relative device weight, format: `DEVICE_NAME:WEIGHT`)",
+ )
+ _ = cmd.RegisterFlagCompletionFunc(blkioWeightDeviceFlagName, completion.AutocompleteDefault)
}
diff --git a/cmd/podman/machine/info.go b/cmd/podman/machine/info.go
index 9932027d8..418060675 100644
--- a/cmd/podman/machine/info.go
+++ b/cmd/podman/machine/info.go
@@ -16,6 +16,7 @@ import (
"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/containers/podman/v4/pkg/machine"
"github.com/ghodss/yaml"
"github.com/spf13/cobra"
@@ -40,26 +41,6 @@ var (
inFormat string
)
-// Info contains info on the machine host and version info
-type Info struct {
- Host *HostInfo `json:"Host"`
- Version define.Version `json:"Version"`
-}
-
-// HostInfo contains info on the machine host
-type HostInfo struct {
- Arch string `json:"Arch"`
- CurrentMachine string `json:"CurrentMachine"`
- DefaultMachine string `json:"DefaultMachine"`
- EventsDir string `json:"EventsDir"`
- MachineConfigDir string `json:"MachineConfigDir"`
- MachineImageDir string `json:"MachineImageDir"`
- MachineState string `json:"MachineState"`
- NumberOfMachines int `json:"NumberOfMachines"`
- OS string `json:"OS"`
- VMType string `json:"VMType"`
-}
-
func init() {
registry.Commands = append(registry.Commands, registry.CliCommand{
Command: infoCmd,
@@ -69,11 +50,11 @@ func init() {
flags := infoCmd.Flags()
formatFlagName := "format"
flags.StringVarP(&inFormat, formatFlagName, "f", "", "Change the output format to JSON or a Go template")
- _ = infoCmd.RegisterFlagCompletionFunc(formatFlagName, common.AutocompleteFormat(&define.Info{}))
+ _ = infoCmd.RegisterFlagCompletionFunc(formatFlagName, common.AutocompleteFormat(&entities.MachineInfo{}))
}
func info(cmd *cobra.Command, args []string) error {
- info := Info{}
+ info := entities.MachineInfo{}
version, err := define.GetVersion()
if err != nil {
return fmt.Errorf("error getting version info %w", err)
@@ -112,8 +93,8 @@ func info(cmd *cobra.Command, args []string) error {
return nil
}
-func hostInfo() (*HostInfo, error) {
- host := HostInfo{}
+func hostInfo() (*entities.MachineHostInfo, error) {
+ host := entities.MachineHostInfo{}
host.Arch = runtime.GOARCH
host.OS = runtime.GOOS
diff --git a/cmd/podman/machine/ssh.go b/cmd/podman/machine/ssh.go
index cb2f62f51..8534b8efa 100644
--- a/cmd/podman/machine/ssh.go
+++ b/cmd/podman/machine/ssh.go
@@ -80,6 +80,11 @@ func ssh(cmd *cobra.Command, args []string) error {
}
}
+ vm, err = provider.LoadVMByName(vmName)
+ if err != nil {
+ return fmt.Errorf("vm %s not found: %w", vmName, err)
+ }
+
if !validVM && sshOpts.Username == "" {
sshOpts.Username, err = remoteConnectionUsername()
if err != nil {
@@ -87,10 +92,6 @@ func ssh(cmd *cobra.Command, args []string) error {
}
}
- vm, err = provider.LoadVMByName(vmName)
- if err != nil {
- return fmt.Errorf("vm %s not found: %w", vmName, err)
- }
err = vm.SSH(vmName, sshOpts)
return utils.HandleOSExecError(err)
}
diff --git a/contrib/cirrus/runner.sh b/contrib/cirrus/runner.sh
index 32f66bac2..1956968ea 100755
--- a/contrib/cirrus/runner.sh
+++ b/contrib/cirrus/runner.sh
@@ -246,6 +246,7 @@ function _run_build() {
if [[ "$runtime" != "$CI_DESIRED_RUNTIME" ]]; then
die "Built podman is using '$runtime'; this CI environment requires $CI_DESIRED_RUNTIME"
fi
+ msg "Built podman is using expected runtime='$runtime'"
}
function _run_altbuild() {
@@ -334,6 +335,9 @@ function _run_release() {
}
+# ***WARNING*** ***WARNING*** ***WARNING*** ***WARNING***
+# Please see gitlab comment in setup_environment.sh
+# ***WARNING*** ***WARNING*** ***WARNING*** ***WARNING***
function _run_gitlab() {
rootless_uid=$(id -u)
systemctl enable --now --user podman.socket
diff --git a/contrib/cirrus/setup_environment.sh b/contrib/cirrus/setup_environment.sh
index 4952f8dd2..c3b7811bc 100755
--- a/contrib/cirrus/setup_environment.sh
+++ b/contrib/cirrus/setup_environment.sh
@@ -71,27 +71,20 @@ fi
cd "${GOSRC}/"
-# Defined by lib.sh: Does the host support cgroups v1 or v2
+# Defined by lib.sh: Does the host support cgroups v1 or v2? Use runc or crun
+# respectively.
+# **IMPORTANT**: $OCI_RUNTIME is a fakeout! It is used only in e2e tests.
+# For actual podman, as in system tests, we force runtime in containers.conf
case "$CG_FS_TYPE" in
tmpfs)
if ((CONTAINER==0)); then
warn "Forcing testing with runc instead of crun"
- if [[ "$OS_RELEASE_ID" == "ubuntu" ]]; then
- # Need b/c using cri-o-runc package from OBS
- echo "OCI_RUNTIME=/usr/lib/cri-o-runc/sbin/runc" \
- >> /etc/ci_environment
- else
- echo "OCI_RUNTIME=runc" >> /etc/ci_environment
- fi
+ echo "OCI_RUNTIME=runc" >> /etc/ci_environment
+ printf "[engine]\nruntime=\"runc\"\n" >>/etc/containers/containers.conf
fi
;;
cgroup2fs)
- if ((CONTAINER==0)); then
- # This is necessary since we've built/installed from source,
- # which uses runc as the default.
- warn "Forcing testing with crun instead of runc"
- echo "OCI_RUNTIME=crun" >> /etc/ci_environment
- fi
+ # Nothing to do: podman defaults to crun
;;
*) die_unknown CG_FS_TYPE
esac
@@ -321,13 +314,30 @@ case "$TEST_FLAVOR" in
install_test_configs
;;
gitlab)
- # This only runs on Ubuntu for now
+ # ***WARNING*** ***WARNING*** ***WARNING*** ***WARNING***
+ # This sets up a special ubuntu environment exclusively for
+ # running the upstream gitlab-runner unit tests through
+ # podman as a drop-in replacement for the Docker daemon.
+ # Test and setup information can be found here:
+ # https://gitlab.com/gitlab-org/gitlab-runner/-/issues/27270#note_499585550
+ #
+ # Unless you know what you're doing, and/or are in contact
+ # with the upstream gitlab-runner developers/community,
+ # please don't make changes willy-nilly to this setup.
+ # It's designed to follow upstream gitlab-runner development
+ # and alert us if any podman change breaks their foundation.
+ #
+ # That said, if this task does break in strange ways or requires
+ # updates you're unsure of. Please consult with the upstream
+ # community through an issue near the one linked above. If
+ # an extended period of breakage is expected, please un-comment
+ # the related `allow_failures: $CI == $CI` line in `.cirrus.yml`.
+ # ***WARNING*** ***WARNING*** ***WARNING*** ***WARNING***
+
if [[ "$OS_RELEASE_ID" != "ubuntu" ]]; then
die "This test only runs on Ubuntu due to sheer laziness"
fi
- # Ref: https://gitlab.com/gitlab-org/gitlab-runner/-/issues/27270#note_499585550
-
remove_packaged_podman_files
make install PREFIX=/usr ETCDIR=/etc
@@ -351,7 +361,7 @@ case "$TEST_FLAVOR" in
slug="gitlab.com/gitlab-org/gitlab-runner"
helper_fqin="registry.gitlab.com/gitlab-org/gitlab-runner/gitlab-runner-helper:x86_64-latest-pwsh"
ssh="ssh $ROOTLESS_USER@localhost -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -o CheckHostIP=no env GOPATH=$GOPATH"
- showrun $ssh go get -u github.com/jstemmer/go-junit-report
+ showrun $ssh go install github.com/jstemmer/go-junit-report/v2@v2.0.0
showrun $ssh git clone https://$slug $GOPATH/src/$slug
showrun $ssh make -C $GOPATH/src/$slug development_setup
showrun $ssh bash -c "'cd $GOPATH/src/$slug && GOPATH=$GOPATH go get .'"
diff --git a/docs/source/markdown/podman-container-clone.1.md b/docs/source/markdown/podman-container-clone.1.md
index 3b9d79862..9baedfd36 100644
--- a/docs/source/markdown/podman-container-clone.1.md
+++ b/docs/source/markdown/podman-container-clone.1.md
@@ -11,6 +11,14 @@ podman\-container\-clone - Creates a copy of an existing container
## OPTIONS
+#### **--blkio-weight**=*weight*
+
+Block IO weight (relative weight) accepts a weight value between 10 and 1000.
+
+#### **--blkio-weight-device**=*weight*
+
+Block IO weight (relative device weight, format: `DEVICE_NAME:WEIGHT`).
+
#### **--cpu-period**=*limit*
Set the CPU period for the Completely Fair Scheduler (CFS), which is a
@@ -126,6 +134,14 @@ If none are specified, the original container's CPU memory nodes are used.
Remove the original container that we are cloning once used to mimic the configuration.
+#### **--device-read-bps**=*path*
+
+Limit read rate (bytes per second) from a device (e.g. --device-read-bps=/dev/sda:1mb).
+
+#### **--device-write-bps**=*path*
+
+Limit write rate (bytes per second) to a device (e.g. --device-write-bps=/dev/sda:1mb)
+
#### **--force**, **-f**
Force removal of the original container that we are cloning. Can only be used in conjunction with **--destroy**.
diff --git a/docs/source/markdown/podman-kube-play.1.md b/docs/source/markdown/podman-kube-play.1.md
index ee68380e6..f52989623 100644
--- a/docs/source/markdown/podman-kube-play.1.md
+++ b/docs/source/markdown/podman-kube-play.1.md
@@ -22,7 +22,7 @@ Currently, the supported Kubernetes kinds are:
Only two volume types are supported by kube play, the *hostPath* and *persistentVolumeClaim* volume types. For the *hostPath* volume type, only the *default (empty)*, *DirectoryOrCreate*, *Directory*, *FileOrCreate*, *File*, *Socket*, *CharDevice* and *BlockDevice* subtypes are supported. Podman interprets the value of *hostPath* *path* as a file path when it contains at least one forward slash, otherwise Podman treats the value as the name of a named volume. When using a *persistentVolumeClaim*, the value for *claimName* is the name for the Podman named volume.
-Note: When playing a kube YAML with init containers, the init container will be created with init type value `always`.
+Note: When playing a kube YAML with init containers, the init container will be created with init type value `once`. To change the default type, use the `io.podman.annotations.init.container.type` annotation to set the type to `always`.
Note: *hostPath* volume types created by kube play will be given an SELinux shared label (z), bind mounts are not relabeled (use `chcon -t container_file_t -R <directory>`).
diff --git a/docs/source/markdown/podman-network-create.1.md b/docs/source/markdown/podman-network-create.1.md
index 3587ac2a8..0ccc540f8 100644
--- a/docs/source/markdown/podman-network-create.1.md
+++ b/docs/source/markdown/podman-network-create.1.md
@@ -73,8 +73,9 @@ Set driver specific options.
All drivers accept the `mtu` option. The `mtu` option sets the Maximum Transmission Unit (MTU) and takes an integer value.
-Additionally the `bridge` driver supports the following option:
+Additionally the `bridge` driver supports the following options:
- `vlan`: This option assign VLAN tag and enables vlan\_filtering. Defaults to none.
+- `isolate`: This option isolates networks by blocking traffic between those that have this option enabled.
The `macvlan` and `ipvlan` driver support the following options:
- `parent`: The host device which should be used for the macvlan interface. Defaults to the default route interface.
diff --git a/docs/source/markdown/podman-pod-clone.1.md b/docs/source/markdown/podman-pod-clone.1.md
index 49084386c..5473407b0 100644
--- a/docs/source/markdown/podman-pod-clone.1.md
+++ b/docs/source/markdown/podman-pod-clone.1.md
@@ -11,10 +11,55 @@ podman\-pod\-clone - Creates a copy of an existing pod
## OPTIONS
+#### **--blkio-weight**=*weight*
+
+Block IO weight (relative weight) accepts a weight value between 10 and 1000.
+
+#### **--blkio-weight-device**=*weight*
+
+Block IO weight (relative device weight, format: `DEVICE_NAME:WEIGHT`).
+
#### **--cgroup-parent**=*path*
Path to cgroups under which the cgroup for the pod will be created. If the path is not absolute, the path is considered to be relative to the cgroups path of the init process. Cgroups will be created if they do not already exist.
+#### **--cpu-shares**, **-c**=*shares*
+
+CPU shares (relative weight)
+
+By default, all containers get the same proportion of CPU cycles. This proportion
+can be modified by changing the container's CPU share weighting relative
+to the weighting of all other running containers.
+
+To modify the proportion from the default of 1024, use the **--cpu-shares**
+flag to set the weighting to 2 or higher.
+
+The proportion will only apply when CPU-intensive processes are running.
+When tasks in one container are idle, other containers can use the
+left-over CPU time. The actual amount of CPU time will vary depending on
+the number of containers running on the system.
+
+For example, consider three containers, one has a cpu-share of 1024 and
+two others have a cpu-share setting of 512. When processes in all three
+containers attempt to use 100% of CPU, the first container would receive
+50% of the total CPU time. If you add a fourth container with a cpu-share
+of 1024, the first container only gets 33% of the CPU. The remaining containers
+receive 16.5%, 16.5% and 33% of the CPU.
+
+On a multi-core system, the shares of CPU time are distributed over all CPU
+cores. Even if a container is limited to less than 100% of CPU time, it can
+use 100% of each individual CPU core.
+
+For example, consider a system with more than three cores. If you start one
+container **{C0}** with **-c=512** running one process, and another container
+**{C1}** with **-c=1024** running two processes, this can result in the following
+division of CPU shares:
+
+PID container CPU CPU share
+100 {C0} 0 100% of CPU0
+101 {C1} 1 100% of CPU1
+102 {C1} 2 100% of CPU2
+
#### **--cpus**
Set a number of CPUs for the pod that overrides the original pods CPU limits. If none are specified, the original pod's Nano CPUs are used.
@@ -23,6 +68,15 @@ Set a number of CPUs for the pod that overrides the original pods CPU limits. If
CPUs in which to allow execution (0-3, 0,1). If none are specified, the original pod's CPUset is used.
+
+#### **--cpuset-mems**=*nodes*
+
+Memory nodes (MEMs) in which to allow execution (0-3, 0,1). Only effective on NUMA systems.
+
+If there are four memory nodes on the system (0-3), use `--cpuset-mems=0,1`
+then processes in the container will only use memory from the first
+two memory nodes.
+
#### **--destroy**
Remove the original pod that we are cloning once used to mimic the configuration.
@@ -48,6 +102,10 @@ device. The devices that Podman will load modules for when necessary are:
Limit read rate (bytes per second) from a device (e.g. --device-read-bps=/dev/sda:1mb).
+#### **--device-write-bps**=*path*
+
+Limit write rate (bytes per second) to a device (e.g. --device-write-bps=/dev/sda:1mb)
+
#### **--gidmap**=*pod_gid:host_gid:amount*
GID map for the user namespace. Using this flag will run all containers in the pod with user namespace enabled. It conflicts with the `--userns` and `--subgidname` flags.
@@ -90,6 +148,17 @@ RAM. If a limit of 0 is specified (not using **-m**), the container's memory is
not limited. The actual limit may be rounded up to a multiple of the operating
system's page size (the value would be very large, that's millions of trillions).
+#### **--memory-swap**=*limit*
+
+A limit value equal to memory plus swap. Must be used with the **-m**
+(**--memory**) flag. The swap `LIMIT` should always be larger than **-m**
+(**--memory**) value. By default, the swap `LIMIT` will be set to double
+the value of --memory.
+
+The format of `LIMIT` is `<number>[<unit>]`. Unit can be `b` (bytes),
+`k` (kibibytes), `m` (mebibytes), or `g` (gibibytes). If you don't specify a
+unit, `b` is used. Set LIMIT to `-1` to enable unlimited swap.
+
#### **--name**, **-n**
Set a custom name for the cloned pod. The default if not specified is of the syntax: **<ORIGINAL_NAME>-clone**
diff --git a/docs/source/markdown/podman-pod-create.1.md b/docs/source/markdown/podman-pod-create.1.md
index de9a34bfa..660112865 100644
--- a/docs/source/markdown/podman-pod-create.1.md
+++ b/docs/source/markdown/podman-pod-create.1.md
@@ -23,6 +23,9 @@ podman generates a UUID for each pod, and if a name is not assigned
to the container with **--name** then a random string name will be generated
for it. The name is useful any place you need to identify a pod.
+Note: resource limit related flags work by setting the limits explicitly in the pod's cgroup
+which by default, is the cgroup parent for all containers joining the pod. Containers are still delegated the ability to set their own resource limits when joining a pod meaning that if you run **podman pod create --cpus=5** you can also run **podman container create --pod=`<pod_id|pod_name>` --cpus=4** and the container will only see the smaller limit. containers do NOT get the pod level cgroup resources if they specify their own cgroup when joining a pod such as **--cgroupns=host**
+
## OPTIONS
#### **--add-host**=*host:ip*
@@ -33,10 +36,55 @@ Add a line to /etc/hosts. The format is hostname:ip. The **--add-host**
option can be set multiple times.
The /etc/hosts file is shared between all containers in the pod.
+#### **--blkio-weight**=*weight*
+
+Block IO weight (relative weight) accepts a weight value between 10 and 1000.
+
+#### **--blkio-weight-device**=*weight*
+
+Block IO weight (relative device weight, format: `DEVICE_NAME:WEIGHT`).
+
#### **--cgroup-parent**=*path*
Path to cgroups under which the cgroup for the pod will be created. If the path is not absolute, the path is considered to be relative to the cgroups path of the init process. Cgroups will be created if they do not already exist.
+#### **--cpu-shares**, **-c**=*shares*
+
+CPU shares (relative weight)
+
+By default, all containers get the same proportion of CPU cycles. This proportion
+can be modified by changing the container's CPU share weighting relative
+to the weighting of all other running containers.
+
+To modify the proportion from the default of 1024, use the **--cpu-shares**
+flag to set the weighting to 2 or higher.
+
+The proportion will only apply when CPU-intensive processes are running.
+When tasks in one container are idle, other containers can use the
+left-over CPU time. The actual amount of CPU time will vary depending on
+the number of containers running on the system.
+
+For example, consider three containers, one has a cpu-share of 1024 and
+two others have a cpu-share setting of 512. When processes in all three
+containers attempt to use 100% of CPU, the first container would receive
+50% of the total CPU time. If you add a fourth container with a cpu-share
+of 1024, the first container only gets 33% of the CPU. The remaining containers
+receive 16.5%, 16.5% and 33% of the CPU.
+
+On a multi-core system, the shares of CPU time are distributed over all CPU
+cores. Even if a container is limited to less than 100% of CPU time, it can
+use 100% of each individual CPU core.
+
+For example, consider a system with more than three cores. If you start one
+container **{C0}** with **-c=512** running one process, and another container
+**{C1}** with **-c=1024** running two processes, this can result in the following
+division of CPU shares:
+
+PID container CPU CPU share
+100 {C0} 0 100% of CPU0
+101 {C1} 1 100% of CPU1
+102 {C1} 2 100% of CPU2
+
#### **--cpus**=*amount*
Set the total number of CPUs delegated to the pod. Default is 0.000 which indicates that there is no limit on computation power.
@@ -52,7 +100,15 @@ Examples of the List Format:
0-4,9 # bits 0, 1, 2, 3, 4, and 9 set
0-2,7,12-14 # bits 0, 1, 2, 7, 12, 13, and 14 set
-#### **--device**=*host-device[:container-device][:permissions]*
+#### **--cpuset-mems**=*nodes*
+
+Memory nodes (MEMs) in which to allow execution (0-3, 0,1). Only effective on NUMA systems.
+
+If there are four memory nodes on the system (0-3), use `--cpuset-mems=0,1`
+then processes in the container will only use memory from the first
+two memory nodes.
+
+#### **--device**=_host-device_[**:**_container-device_][**:**_permissions_]
Add a host device to the pod. Optional *permissions* parameter
can be used to specify device permissions. It is a combination of
@@ -73,6 +129,10 @@ device. The devices that Podman will load modules for when necessary are:
Limit read rate (bytes per second) from a device (e.g. --device-read-bps=/dev/sda:1mb)
+#### **--device-write-bps**=*path*
+
+Limit write rate (bytes per second) to a device (e.g. --device-write-bps=/dev/sda:1mb)
+
#### **--dns**=*ipaddr*
Set custom DNS servers in the /etc/resolv.conf file that will be shared between all containers in the pod. A special option, "none" is allowed which disables creation of /etc/resolv.conf for the pod.
@@ -174,6 +234,16 @@ RAM. If a limit of 0 is specified (not using **-m**), the container's memory is
not limited. The actual limit may be rounded up to a multiple of the operating
system's page size (the value would be very large, that's millions of trillions).
+#### **--memory-swap**=*limit*
+
+A limit value equal to memory plus swap. Must be used with the **-m**
+(**--memory**) flag. The swap `LIMIT` should always be larger than **-m**
+(**--memory**) value. By default, the swap `LIMIT` will be set to double
+the value of --memory.
+
+The format of `LIMIT` is `<number>[<unit>]`. Unit can be `b` (bytes),
+`k` (kibibytes), `m` (mebibytes), or `g` (gibibytes). If you don't specify a
+unit, `b` is used. Set LIMIT to `-1` to enable unlimited swap.
#### **--name**, **-n**=*name*
@@ -603,7 +673,7 @@ $ podman pod create --network net1:ip=10.89.1.5 --network net2:ip=10.89.10.10
```
## SEE ALSO
-**[podman(1)](podman.1.md)**, **[podman-pod(1)](podman-pod.1.md)**, **[podman-kube-play(1)](podman-kube-play.1.md)**, **containers.conf(1)**
+**[podman(1)](podman.1.md)**, **[podman-pod(1)](podman-pod.1.md)**, **[podman-kube-play(1)](podman-kube-play.1.md)**, **containers.conf(1)**, **[cgroups(7)](https://man7.org/linux/man-pages/man7/cgroups.7.html)**
## HISTORY
diff --git a/go.mod b/go.mod
index 2a9bf3f19..978e475a8 100644
--- a/go.mod
+++ b/go.mod
@@ -14,14 +14,13 @@ require (
github.com/containers/buildah v1.26.1-0.20220716095526-d31d27c357ab
github.com/containers/common v0.48.1-0.20220720145307-26032247af78
github.com/containers/conmon v2.0.20+incompatible
- github.com/containers/image/v5 v5.21.2-0.20220714132403-2bb3f3e44c5c
+ github.com/containers/image/v5 v5.21.2-0.20220721072459-bf19265865b7
github.com/containers/ocicrypt v1.1.5
github.com/containers/psgo v1.7.2
github.com/containers/storage v1.41.1-0.20220718173332-b10c469fda0a
github.com/coreos/go-systemd/v22 v22.3.2
github.com/coreos/stream-metadata-go v0.0.0-20210225230131-70edb9eb47b3
github.com/cyphar/filepath-securejoin v0.2.3
- github.com/davecgh/go-spew v1.1.1
github.com/digitalocean/go-qemu v0.0.0-20210326154740-ac9e0b687001
github.com/docker/distribution v2.8.1+incompatible
github.com/docker/docker v20.10.17+incompatible
@@ -51,7 +50,6 @@ require (
github.com/opencontainers/runtime-spec v1.0.3-0.20211214071223-8958f93039ab
github.com/opencontainers/runtime-tools v0.9.1-0.20220714195903-17b3287fafb7
github.com/opencontainers/selinux v1.10.1
- github.com/pmezard/go-difflib v1.0.0
github.com/rootless-containers/rootlesskit v1.0.1
github.com/sirupsen/logrus v1.9.0
github.com/spf13/cobra v1.5.0
diff --git a/go.sum b/go.sum
index 162ae902d..7d28490f2 100644
--- a/go.sum
+++ b/go.sum
@@ -398,8 +398,9 @@ github.com/containers/common v0.48.1-0.20220720145307-26032247af78/go.mod h1:w9q
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.20220712113758-29aec5f7bbbf/go.mod h1:0+N0ZM9mgMmoZZc6uNcgnEsbX85Ne7b29cIW5lqWwVU=
-github.com/containers/image/v5 v5.21.2-0.20220714132403-2bb3f3e44c5c h1:ms1Vyzs9Eb17J38aFKrL0+ig2pVwQq3OleaO7VmQuV0=
github.com/containers/image/v5 v5.21.2-0.20220714132403-2bb3f3e44c5c/go.mod h1:ykVAVRj4DhQNMHZDVU+KCtXjWBKpqiUe669eF0WBEEc=
+github.com/containers/image/v5 v5.21.2-0.20220721072459-bf19265865b7 h1:ORdlKVGjzQY3x/5A9O9poemoguCj9BW3lSKofTdvTv4=
+github.com/containers/image/v5 v5.21.2-0.20220721072459-bf19265865b7/go.mod h1:Nl5bQr7zq+fXbYyGtv9ObVMkOkOD32xMyxMfnIueCKg=
github.com/containers/libtrust v0.0.0-20200511145503-9c3a6c22cd9a h1:spAGlqziZjCJL25C6F1zsQY05tfCKE9F5YwtEWWe6hU=
github.com/containers/libtrust v0.0.0-20200511145503-9c3a6c22cd9a/go.mod h1:9rfv8iPl1ZP7aqh9YA68wnZv2NUDbXdcdPHVz0pFbPY=
github.com/containers/ocicrypt v1.0.1/go.mod h1:MeJDzk1RJHv89LjsH0Sp5KTY3ZYkjXO/C+bKAeWFIrc=
@@ -1550,8 +1551,9 @@ github.com/tdakkota/asciicheck v0.0.0-20200416200610-e657995f937b/go.mod h1:yHp0
github.com/tenntenn/modver v1.0.1/go.mod h1:bePIyQPb7UeioSRkw3Q0XeMhYZSMx9B8ePqg6SAMGH0=
github.com/tenntenn/text/transform v0.0.0-20200319021203-7eef512accb3/go.mod h1:ON8b8w4BN/kE1EOhwT0o+d62W65a6aPw1nouo9LMgyY=
github.com/tetafro/godot v1.4.11/go.mod h1:LR3CJpxDVGlYOWn3ZZg1PgNZdTUvzsZWu8xaEohUpn8=
-github.com/theupdateframework/go-tuf v0.3.0 h1:od2sc5+BSkKZhmUG2o2rmruy0BGSmhrbDhCnpxh87X8=
github.com/theupdateframework/go-tuf v0.3.0/go.mod h1:E5XP0wXitrFUHe4b8cUcAAdxBW4LbfnqF4WXXGLgWNo=
+github.com/theupdateframework/go-tuf v0.3.1 h1:NkjMlCuLcDpHNtsWXY4lTmbbQQ5nOM7JSBbOKEEiI1c=
+github.com/theupdateframework/go-tuf v0.3.1/go.mod h1:lhHZ3Vt2pdAh15h0Cc6gWdlI+Okn2ZznD3q/cNjd5jw=
github.com/timakin/bodyclose v0.0.0-20200424151742-cb6215831a94/go.mod h1:Qimiffbc6q9tBWlVV6x0P9sat/ao1xEkREYPPj9hphk=
github.com/titanous/rocacheck v0.0.0-20171023193734-afe73141d399 h1:e/5i7d4oYZ+C1wj2THlRK+oAhjeS/TRQwMfkIuet3w0=
github.com/titanous/rocacheck v0.0.0-20171023193734-afe73141d399/go.mod h1:LdwHTNJT99C5fTAzDz0ud328OgXz+gierycbcIx2fRs=
diff --git a/hack/golangci-lint.sh b/hack/golangci-lint.sh
index 2eaf206d7..dd06004ac 100755
--- a/hack/golangci-lint.sh
+++ b/hack/golangci-lint.sh
@@ -9,9 +9,9 @@ BUILD_TAGS[abi]="${BUILD_TAGS[default]},systemd"
BUILD_TAGS[tunnel]="${BUILD_TAGS[default]},remote"
declare -A SKIP_DIRS
-SKIP_DIRS[abi]="pkg/machine/e2e"
+SKIP_DIRS[abi]=""
# TODO: add "remote" build tag to pkg/api
-SKIP_DIRS[tunnel]="pkg/api,pkg/machine/e2e"
+SKIP_DIRS[tunnel]="pkg/api"
[[ $1 == run ]] && shift
diff --git a/libpod/container_internal.go b/libpod/container_internal.go
index caf7f3849..7e330430c 100644
--- a/libpod/container_internal.go
+++ b/libpod/container_internal.go
@@ -22,12 +22,12 @@ import (
"github.com/containers/common/pkg/cgroups"
"github.com/containers/common/pkg/chown"
"github.com/containers/common/pkg/config"
+ "github.com/containers/common/pkg/hooks"
+ "github.com/containers/common/pkg/hooks/exec"
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"
- "github.com/containers/podman/v4/pkg/hooks"
- "github.com/containers/podman/v4/pkg/hooks/exec"
"github.com/containers/podman/v4/pkg/lookup"
"github.com/containers/podman/v4/pkg/rootless"
"github.com/containers/podman/v4/pkg/selinux"
diff --git a/libpod/container_internal_linux.go b/libpod/container_internal_linux.go
index 46e525add..a131ab367 100644
--- a/libpod/container_internal_linux.go
+++ b/libpod/container_internal_linux.go
@@ -2886,23 +2886,6 @@ func (c *Container) generatePasswdAndGroup() (string, string, error) {
}
}
- // Next, check if the container even has a /etc/passwd or /etc/group.
- // If it doesn't we don't want to create them ourselves.
- if needPasswd {
- exists, err := c.checkFileExistsInRootfs("/etc/passwd")
- if err != nil {
- return "", "", err
- }
- needPasswd = exists
- }
- if needGroup {
- exists, err := c.checkFileExistsInRootfs("/etc/group")
- if err != nil {
- return "", "", err
- }
- needGroup = exists
- }
-
// If we don't need a /etc/passwd or /etc/group at this point we can
// just return.
if !needPasswd && !needGroup {
@@ -2947,7 +2930,7 @@ func (c *Container) generatePasswdAndGroup() (string, string, error) {
return "", "", fmt.Errorf("error looking up location of container %s /etc/passwd: %w", c.ID(), err)
}
- f, err := os.OpenFile(containerPasswd, os.O_APPEND|os.O_WRONLY, 0600)
+ f, err := os.OpenFile(containerPasswd, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0600)
if err != nil {
return "", "", fmt.Errorf("container %s: %w", c.ID(), err)
}
@@ -2993,7 +2976,7 @@ func (c *Container) generatePasswdAndGroup() (string, string, error) {
return "", "", fmt.Errorf("error looking up location of container %s /etc/group: %w", c.ID(), err)
}
- f, err := os.OpenFile(containerGroup, os.O_APPEND|os.O_WRONLY, 0600)
+ f, err := os.OpenFile(containerGroup, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0600)
if err != nil {
return "", "", fmt.Errorf("container %s: %w", c.ID(), err)
}
@@ -3112,26 +3095,6 @@ func (c *Container) cleanupOverlayMounts() error {
return overlay.CleanupContent(c.config.StaticDir)
}
-// Check if a file exists at the given path in the container's root filesystem.
-// Container must already be mounted for this to be used.
-func (c *Container) checkFileExistsInRootfs(file string) (bool, error) {
- checkPath, err := securejoin.SecureJoin(c.state.Mountpoint, file)
- if err != nil {
- return false, fmt.Errorf("cannot create path to container %s file %q: %w", c.ID(), file, err)
- }
- stat, err := os.Stat(checkPath)
- if err != nil {
- if os.IsNotExist(err) {
- return false, nil
- }
- return false, fmt.Errorf("container %s: %w", c.ID(), err)
- }
- if stat.IsDir() {
- return false, nil
- }
- return true, nil
-}
-
// Creates and mounts an empty dir to mount secrets into, if it does not already exist
func (c *Container) createSecretMountDir() error {
src := filepath.Join(c.state.RunDir, "/run/secrets")
diff --git a/libpod/container_log.go b/libpod/container_log.go
index a9e0fe065..c49b54eb1 100644
--- a/libpod/container_log.go
+++ b/libpod/container_log.go
@@ -10,6 +10,7 @@ import (
"github.com/containers/podman/v4/libpod/define"
"github.com/containers/podman/v4/libpod/events"
"github.com/containers/podman/v4/libpod/logs"
+ "github.com/nxadm/tail"
"github.com/nxadm/tail/watch"
"github.com/sirupsen/logrus"
)
@@ -74,14 +75,19 @@ func (c *Container) readFromLogFile(ctx context.Context, options *logs.LogOption
go func() {
defer options.WaitGroup.Done()
-
- for line := range t.Lines {
+ var line *tail.Line
+ var ok bool
+ for {
select {
case <-ctx.Done():
// the consumer has cancelled
+ t.Kill(errors.New("hangup by client"))
return
- default:
- // fallthrough
+ case line, ok = <-t.Lines:
+ if !ok {
+ // channel was closed
+ return
+ }
}
nll, err := logs.NewLogLine(line.Text)
if err != nil {
diff --git a/libpod/container_log_linux.go b/libpod/container_log_linux.go
index 0686caed2..7e95f2449 100644
--- a/libpod/container_log_linux.go
+++ b/libpod/container_log_linux.go
@@ -178,8 +178,13 @@ func (c *Container) readFromJournal(ctx context.Context, options *logs.LogOption
if !options.Follow || !containerCouldBeLogging {
return
}
- // Sleep until something's happening on the journal.
- journal.Wait(sdjournal.IndefiniteWait)
+
+ // journal.Wait() is blocking, this would cause the goroutine to hang forever
+ // if no more journal entries are generated and thus if the client
+ // has closed the connection in the meantime to leak memory.
+ // Waiting only 5 seconds makes sure we can check if the client closed in the
+ // meantime at least every 5 seconds.
+ journal.Wait(5 * time.Second)
continue
}
lastReadCursor = cursor
diff --git a/libpod/define/annotations.go b/libpod/define/annotations.go
index 8f5279981..580286d6a 100644
--- a/libpod/define/annotations.go
+++ b/libpod/define/annotations.go
@@ -135,6 +135,11 @@ const (
// creating a checkpoint image to specify the name of host distribution on
// which the checkpoint was created.
CheckpointAnnotationDistributionName = "io.podman.annotations.checkpoint.distribution.name"
+
+ // InitContainerType is used by play kube when playing a kube yaml to specify the type
+ // of the init container.
+ InitContainerType = "io.podman.annotations.init.container.type"
+
// MaxKubeAnnotation is the max length of annotations allowed by Kubernetes.
MaxKubeAnnotation = 63
)
diff --git a/libpod/define/pod_inspect.go b/libpod/define/pod_inspect.go
index 2afef48c4..d56074882 100644
--- a/libpod/define/pod_inspect.go
+++ b/libpod/define/pod_inspect.go
@@ -57,20 +57,32 @@ type InspectPodData struct {
CPUPeriod uint64 `json:"cpu_period,omitempty"`
// CPUQuota contains the CPU quota of the pod
CPUQuota int64 `json:"cpu_quota,omitempty"`
+ // CPUShares contains the cpu shares for the pod
+ CPUShares uint64 `json:"cpu_shares,omitempty"`
// CPUSetCPUs contains linux specific CPU data for the pod
CPUSetCPUs string `json:"cpuset_cpus,omitempty"`
+ // CPUSetMems contains linux specific CPU data for the pod
+ CPUSetMems string `json:"cpuset_mems,omitempty"`
// Mounts contains volume related information for the pod
Mounts []InspectMount `json:"mounts,omitempty"`
// Devices contains the specified host devices
Devices []InspectDevice `json:"devices,omitempty"`
// BlkioDeviceReadBps contains the Read/Access limit for the pod's devices
BlkioDeviceReadBps []InspectBlkioThrottleDevice `json:"device_read_bps,omitempty"`
+ // BlkioDeviceReadBps contains the Read/Access limit for the pod's devices
+ BlkioDeviceWriteBps []InspectBlkioThrottleDevice `json:"device_write_bps,omitempty"`
// VolumesFrom contains the containers that the pod inherits mounts from
VolumesFrom []string `json:"volumes_from,omitempty"`
// SecurityOpt contains the specified security labels and related SELinux information
SecurityOpts []string `json:"security_opt,omitempty"`
// MemoryLimit contains the specified cgroup memory limit for the pod
MemoryLimit uint64 `json:"memory_limit,omitempty"`
+ // MemorySwap contains the specified memory swap limit for the pod
+ MemorySwap uint64 `json:"memory_swap,omitempty"`
+ // BlkioWeight contains the blkio weight limit for the pod
+ BlkioWeight uint64 `json:"blkio_weight,omitempty"`
+ // BlkioWeightDevice contains the blkio weight device limits for the pod
+ BlkioWeightDevice []InspectBlkioWeightDevice `json:"blkio_weight_device,omitempty"`
}
// InspectPodInfraConfig contains the configuration of the pod's infra
diff --git a/libpod/events/journal_linux.go b/libpod/events/journal_linux.go
index 0a0a768d0..16ef6504f 100644
--- a/libpod/events/journal_linux.go
+++ b/libpod/events/journal_linux.go
@@ -141,9 +141,18 @@ func (e EventJournalD) Read(ctx context.Context, options ReadOptions) error {
if !options.Stream || (len(options.Until) > 0 && time.Now().After(untilTime)) {
break
}
- t := sdjournal.IndefiniteWait
+
+ // j.Wait() is blocking, this would cause the goroutine to hang forever
+ // if no more journal entries are generated and thus if the client
+ // has closed the connection in the meantime to leak memory.
+ // Waiting only 5 seconds makes sure we can check if the client closed in the
+ // meantime at least every 5 seconds.
+ t := 5 * time.Second
if len(options.Until) > 0 {
- t = time.Until(untilTime)
+ until := time.Until(untilTime)
+ if until < t {
+ t = until
+ }
}
_ = j.Wait(t)
continue
diff --git a/libpod/events/logfile.go b/libpod/events/logfile.go
index 4dafd8600..c7dbf4850 100644
--- a/libpod/events/logfile.go
+++ b/libpod/events/logfile.go
@@ -108,23 +108,19 @@ func (e EventLogFile) Read(ctx context.Context, options ReadOptions) error {
}
}()
}
- funcDone := make(chan bool)
- copy := true
- go func() {
- select {
- case <-funcDone:
- // Do nothing
- case <-ctx.Done():
- copy = false
- t.Kill(errors.New("hangup by client"))
- }
- }()
- for line := range t.Lines {
+ var line *tail.Line
+ var ok bool
+ for {
select {
case <-ctx.Done():
// the consumer has cancelled
+ t.Kill(errors.New("hangup by client"))
return nil
- default:
+ case line, ok = <-t.Lines:
+ if !ok {
+ // channel was closed
+ return nil
+ }
// fallthrough
}
@@ -138,12 +134,10 @@ func (e EventLogFile) Read(ctx context.Context, options ReadOptions) error {
default:
return fmt.Errorf("event type %s is not valid in %s", event.Type.String(), e.options.LogFilePath)
}
- if copy && applyFilters(event, filterMap) {
+ if applyFilters(event, filterMap) {
options.EventChannel <- event
}
}
- funcDone <- true
- return nil
}
// String returns a string representation of the logger
diff --git a/libpod/options.go b/libpod/options.go
index f03980017..b31cb4ab2 100644
--- a/libpod/options.go
+++ b/libpod/options.go
@@ -2145,6 +2145,18 @@ func WithServiceContainer(id string) PodCreateOption {
}
}
+// WithPodResources sets resource limits to be applied to the pod's cgroup
+// these will be inherited by all containers unless overridden.
+func WithPodResources(resources specs.LinuxResources) PodCreateOption {
+ return func(pod *Pod) error {
+ if pod.valid {
+ return define.ErrPodFinalized
+ }
+ pod.config.ResourceLimits = resources
+ return nil
+ }
+}
+
// WithVolatile sets the volatile flag for the container storage.
// The option can potentially cause data loss when used on a container that must survive a machine reboot.
func WithVolatile() CtrCreateOption {
diff --git a/libpod/pod.go b/libpod/pod.go
index e059c9416..89f6d6cfe 100644
--- a/libpod/pod.go
+++ b/libpod/pod.go
@@ -83,6 +83,9 @@ type PodConfig struct {
// ID of the pod's lock
LockID uint32 `json:"lockID"`
+
+ // ResourceLimits hold the pod level resource limits
+ ResourceLimits specs.LinuxResources
}
// podState represents a pod's state
@@ -116,18 +119,7 @@ func (p *Pod) ResourceLim() *specs.LinuxResources {
empty := &specs.LinuxResources{
CPU: &specs.LinuxCPU{},
}
- infra, err := p.runtime.GetContainer(p.state.InfraContainerID)
- if err != nil {
- return empty
- }
- conf := infra.config.Spec
- if err != nil {
- return empty
- }
- if conf.Linux == nil || conf.Linux.Resources == nil {
- return empty
- }
- if err = JSONDeepCopy(conf.Linux.Resources, resCopy); err != nil {
+ if err := JSONDeepCopy(p.config.ResourceLimits, resCopy); err != nil {
return nil
}
if resCopy.CPU != nil {
@@ -139,51 +131,91 @@ func (p *Pod) ResourceLim() *specs.LinuxResources {
// CPUPeriod returns the pod CPU period
func (p *Pod) CPUPeriod() uint64 {
- if p.state.InfraContainerID == "" {
+ resLim := p.ResourceLim()
+ if resLim.CPU == nil || resLim.CPU.Period == nil {
return 0
}
- infra, err := p.runtime.GetContainer(p.state.InfraContainerID)
- if err != nil {
+ return *resLim.CPU.Period
+}
+
+// CPUQuota returns the pod CPU quota
+func (p *Pod) CPUQuota() int64 {
+ resLim := p.ResourceLim()
+ if resLim.CPU == nil || resLim.CPU.Quota == nil {
return 0
}
- conf := infra.config.Spec
- if conf != nil && conf.Linux != nil && conf.Linux.Resources != nil && conf.Linux.Resources.CPU != nil && conf.Linux.Resources.CPU.Period != nil {
- return *conf.Linux.Resources.CPU.Period
+ return *resLim.CPU.Quota
+}
+
+// MemoryLimit returns the pod Memory Limit
+func (p *Pod) MemoryLimit() uint64 {
+ resLim := p.ResourceLim()
+ if resLim.Memory == nil || resLim.Memory.Limit == nil {
+ return 0
}
- return 0
+ return uint64(*resLim.Memory.Limit)
}
-// CPUQuota returns the pod CPU quota
-func (p *Pod) CPUQuota() int64 {
- if p.state.InfraContainerID == "" {
+// MemorySwap returns the pod Memory swap limit
+func (p *Pod) MemorySwap() uint64 {
+ resLim := p.ResourceLim()
+ if resLim.Memory == nil || resLim.Memory.Swap == nil {
return 0
}
- infra, err := p.runtime.GetContainer(p.state.InfraContainerID)
- if err != nil {
+ return uint64(*resLim.Memory.Swap)
+}
+
+// BlkioWeight returns the pod blkio weight
+func (p *Pod) BlkioWeight() uint64 {
+ resLim := p.ResourceLim()
+ if resLim.BlockIO == nil || resLim.BlockIO.Weight == nil {
return 0
}
- conf := infra.config.Spec
- if conf != nil && conf.Linux != nil && conf.Linux.Resources != nil && conf.Linux.Resources.CPU != nil && conf.Linux.Resources.CPU.Quota != nil {
- return *conf.Linux.Resources.CPU.Quota
+ return uint64(*resLim.BlockIO.Weight)
+}
+
+// CPUSetMems returns the pod CPUSet memory nodes
+func (p *Pod) CPUSetMems() string {
+ resLim := p.ResourceLim()
+ if resLim.CPU == nil {
+ return ""
}
- return 0
+ return resLim.CPU.Mems
}
-// MemoryLimit returns the pod Memory Limit
-func (p *Pod) MemoryLimit() uint64 {
- if p.state.InfraContainerID == "" {
+// CPUShares returns the pod cpu shares
+func (p *Pod) CPUShares() uint64 {
+ resLim := p.ResourceLim()
+ if resLim.CPU == nil || resLim.CPU.Shares == nil {
return 0
}
- infra, err := p.runtime.GetContainer(p.state.InfraContainerID)
+ return *resLim.CPU.Shares
+}
+
+// BlkiThrottleReadBps returns the pod throttle devices
+func (p *Pod) BlkiThrottleReadBps() []define.InspectBlkioThrottleDevice {
+ resLim := p.ResourceLim()
+ if resLim.BlockIO == nil || resLim.BlockIO.ThrottleReadBpsDevice == nil {
+ return []define.InspectBlkioThrottleDevice{}
+ }
+ devs, err := blkioDeviceThrottle(nil, resLim.BlockIO.ThrottleReadBpsDevice)
if err != nil {
- return 0
+ return []define.InspectBlkioThrottleDevice{}
}
- conf := infra.config.Spec
- if conf != nil && conf.Linux != nil && conf.Linux.Resources != nil && conf.Linux.Resources.Memory != nil && conf.Linux.Resources.Memory.Limit != nil {
- val := *conf.Linux.Resources.Memory.Limit
- return uint64(val)
+ return devs
+}
+
+// BlkiThrottleWriteBps returns the pod throttle devices
+func (p *Pod) BlkiThrottleWriteBps() []define.InspectBlkioThrottleDevice {
+ resLim := p.ResourceLim()
+ if resLim.BlockIO == nil || resLim.BlockIO.ThrottleWriteBpsDevice == nil {
+ return []define.InspectBlkioThrottleDevice{}
+ }
+ devs, err := blkioDeviceThrottle(nil, resLim.BlockIO.ThrottleWriteBpsDevice)
+ if err != nil {
+ return []define.InspectBlkioThrottleDevice{}
}
- return 0
+ return devs
}
// NetworkMode returns the Network mode given by the user ex: pod, private...
diff --git a/libpod/pod_api.go b/libpod/pod_api.go
index c1d54d55e..29964ae95 100644
--- a/libpod/pod_api.go
+++ b/libpod/pod_api.go
@@ -659,7 +659,6 @@ func (p *Pod) Inspect() (*define.InspectPodData, error) {
var infraConfig *define.InspectPodInfraConfig
var inspectMounts []define.InspectMount
var devices []define.InspectDevice
- var deviceLimits []define.InspectBlkioThrottleDevice
var infraSecurity []string
if p.state.InfraContainerID != "" {
infra, err := p.runtime.GetContainer(p.state.InfraContainerID)
@@ -683,18 +682,6 @@ func (p *Pod) Inspect() (*define.InspectPodData, error) {
if err != nil {
return nil, err
}
- var nodes map[string]string
- devices, err = infra.GetDevices(false, *infra.config.Spec, nodes)
- if err != nil {
- return nil, err
- }
- spec := infra.config.Spec
- if spec.Linux != nil && spec.Linux.Resources != nil && spec.Linux.Resources.BlockIO != nil {
- deviceLimits, err = blkioDeviceThrottle(nodes, spec.Linux.Resources.BlockIO.ThrottleReadBpsDevice)
- if err != nil {
- return nil, err
- }
- }
if len(infra.config.ContainerNetworkConfig.DNSServer) > 0 {
infraConfig.DNSServer = make([]string, 0, len(infra.config.ContainerNetworkConfig.DNSServer))
@@ -731,33 +718,38 @@ func (p *Pod) Inspect() (*define.InspectPodData, error) {
}
inspectData := define.InspectPodData{
- ID: p.ID(),
- Name: p.Name(),
- Namespace: p.Namespace(),
- Created: p.CreatedTime(),
- CreateCommand: p.config.CreateCommand,
- ExitPolicy: string(p.config.ExitPolicy),
- State: podState,
- Hostname: p.config.Hostname,
- Labels: p.Labels(),
- CreateCgroup: p.config.UsePodCgroup,
- CgroupParent: p.CgroupParent(),
- CgroupPath: p.state.CgroupPath,
- CreateInfra: infraConfig != nil,
- InfraContainerID: p.state.InfraContainerID,
- InfraConfig: infraConfig,
- SharedNamespaces: sharesNS,
- NumContainers: uint(len(containers)),
- Containers: ctrs,
- CPUSetCPUs: p.ResourceLim().CPU.Cpus,
- CPUPeriod: p.CPUPeriod(),
- CPUQuota: p.CPUQuota(),
- MemoryLimit: p.MemoryLimit(),
- Mounts: inspectMounts,
- Devices: devices,
- BlkioDeviceReadBps: deviceLimits,
- VolumesFrom: p.VolumesFrom(),
- SecurityOpts: infraSecurity,
+ ID: p.ID(),
+ Name: p.Name(),
+ Namespace: p.Namespace(),
+ Created: p.CreatedTime(),
+ CreateCommand: p.config.CreateCommand,
+ ExitPolicy: string(p.config.ExitPolicy),
+ State: podState,
+ Hostname: p.config.Hostname,
+ Labels: p.Labels(),
+ CreateCgroup: p.config.UsePodCgroup,
+ CgroupParent: p.CgroupParent(),
+ CgroupPath: p.state.CgroupPath,
+ CreateInfra: infraConfig != nil,
+ InfraContainerID: p.state.InfraContainerID,
+ InfraConfig: infraConfig,
+ SharedNamespaces: sharesNS,
+ NumContainers: uint(len(containers)),
+ Containers: ctrs,
+ CPUSetCPUs: p.ResourceLim().CPU.Cpus,
+ CPUPeriod: p.CPUPeriod(),
+ CPUQuota: p.CPUQuota(),
+ MemoryLimit: p.MemoryLimit(),
+ Mounts: inspectMounts,
+ Devices: devices,
+ BlkioDeviceReadBps: p.BlkiThrottleReadBps(),
+ VolumesFrom: p.VolumesFrom(),
+ SecurityOpts: infraSecurity,
+ MemorySwap: p.MemorySwap(),
+ BlkioWeight: p.BlkioWeight(),
+ CPUSetMems: p.CPUSetMems(),
+ BlkioDeviceWriteBps: p.BlkiThrottleWriteBps(),
+ CPUShares: p.CPUShares(),
}
return &inspectData, nil
diff --git a/libpod/runtime_pod_linux.go b/libpod/runtime_pod_linux.go
index 75ff24e41..3bb22ec26 100644
--- a/libpod/runtime_pod_linux.go
+++ b/libpod/runtime_pod_linux.go
@@ -80,7 +80,7 @@ func (r *Runtime) NewPod(ctx context.Context, p specgen.PodSpecGenerator, option
p.InfraContainerSpec.CgroupParent = pod.state.CgroupPath
// cgroupfs + rootless = permission denied when creating the cgroup.
if !rootless.IsRootless() {
- res, err := GetLimits(p.InfraContainerSpec.ResourceLimits)
+ res, err := GetLimits(p.ResourceLimits)
if err != nil {
return nil, err
}
@@ -113,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()), p.InfraContainerSpec.ResourceLimits)
+ cgroupPath, err := systemdSliceFromPath(pod.config.CgroupParent, fmt.Sprintf("libpod_pod_%s", pod.ID()), p.ResourceLimits)
if err != nil {
return nil, fmt.Errorf("unable to create pod cgroup for pod %s: %w", pod.ID(), err)
}
diff --git a/pkg/api/handlers/compat/images.go b/pkg/api/handlers/compat/images.go
index 2f8d151d8..39bd165d6 100644
--- a/pkg/api/handlers/compat/images.go
+++ b/pkg/api/handlers/compat/images.go
@@ -23,6 +23,7 @@ import (
"github.com/containers/podman/v4/pkg/domain/entities"
"github.com/containers/podman/v4/pkg/domain/infra/abi"
"github.com/containers/storage"
+ "github.com/docker/docker/pkg/jsonmessage"
"github.com/gorilla/schema"
"github.com/opencontainers/go-digest"
"github.com/sirupsen/logrus"
@@ -325,16 +326,8 @@ func CreateImageFromImage(w http.ResponseWriter, r *http.Request) {
loop: // break out of for/select infinite loop
for {
- var report struct {
- Stream string `json:"stream,omitempty"`
- Status string `json:"status,omitempty"`
- Progress struct {
- Current uint64 `json:"current,omitempty"`
- Total int64 `json:"total,omitempty"`
- } `json:"progressDetail,omitempty"`
- Error string `json:"error,omitempty"`
- Id string `json:"id,omitempty"` //nolint:revive,stylecheck
- }
+ report := jsonmessage.JSONMessage{}
+ report.Progress = &jsonmessage.JSONProgress{}
select {
case e := <-progress:
switch e.Event {
@@ -342,14 +335,15 @@ loop: // break out of for/select infinite loop
report.Status = "Pulling fs layer"
case types.ProgressEventRead:
report.Status = "Downloading"
- report.Progress.Current = e.Offset
+ report.Progress.Current = int64(e.Offset)
report.Progress.Total = e.Artifact.Size
+ report.ProgressMessage = report.Progress.String()
case types.ProgressEventSkipped:
report.Status = "Already exists"
case types.ProgressEventDone:
report.Status = "Download complete"
}
- report.Id = e.Artifact.Digest.Encoded()[0:12]
+ report.ID = e.Artifact.Digest.Encoded()[0:12]
if err := enc.Encode(report); err != nil {
logrus.Warnf("Failed to json encode error %q", err.Error())
}
@@ -358,7 +352,11 @@ loop: // break out of for/select infinite loop
err := pullRes.err
pulledImages := pullRes.images
if err != nil {
- report.Error = err.Error()
+ msg := err.Error()
+ report.Error = &jsonmessage.JSONError{
+ Message: msg,
+ }
+ report.ErrorMessage = msg
} else {
if len(pulledImages) > 0 {
img := pulledImages[0].ID()
@@ -367,9 +365,13 @@ loop: // break out of for/select infinite loop
} else {
report.Status = "Download complete"
}
- report.Id = img[0:12]
+ report.ID = img[0:12]
} else {
- report.Error = "internal error: no images pulled"
+ msg := "internal error: no images pulled"
+ report.Error = &jsonmessage.JSONError{
+ Message: msg,
+ }
+ report.ErrorMessage = msg
}
}
if err := enc.Encode(report); err != nil {
diff --git a/pkg/api/handlers/compat/images_push.go b/pkg/api/handlers/compat/images_push.go
index bb82ef10d..f29808124 100644
--- a/pkg/api/handlers/compat/images_push.go
+++ b/pkg/api/handlers/compat/images_push.go
@@ -156,6 +156,7 @@ loop: // break out of for/select infinite loop
Current: int64(e.Offset),
Total: e.Artifact.Size,
}
+ report.ProgressMessage = report.Progress.String()
case types.ProgressEventSkipped:
report.Status = "Layer already exists"
case types.ProgressEventDone:
diff --git a/pkg/api/handlers/compat/networks.go b/pkg/api/handlers/compat/networks.go
index 65177218a..29d1398cf 100644
--- a/pkg/api/handlers/compat/networks.go
+++ b/pkg/api/handlers/compat/networks.go
@@ -23,6 +23,13 @@ import (
"github.com/sirupsen/logrus"
)
+func normalizeNetworkName(rt *libpod.Runtime, name string) (string, bool) {
+ if name == nettypes.BridgeNetworkDriver {
+ return rt.Network().DefaultNetworkName(), true
+ }
+ return name, false
+}
+
func InspectNetwork(w http.ResponseWriter, r *http.Request) {
runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime)
@@ -44,13 +51,13 @@ func InspectNetwork(w http.ResponseWriter, r *http.Request) {
utils.Error(w, http.StatusBadRequest, define.ErrInvalidArg)
return
}
- name := utils.GetName(r)
+ name, changed := normalizeNetworkName(runtime, utils.GetName(r))
net, err := runtime.Network().NetworkInspect(name)
if err != nil {
utils.NetworkNotFound(w, name, err)
return
}
- report, err := convertLibpodNetworktoDockerNetwork(runtime, net)
+ report, err := convertLibpodNetworktoDockerNetwork(runtime, &net, changed)
if err != nil {
utils.InternalServerError(w, err)
return
@@ -58,7 +65,7 @@ func InspectNetwork(w http.ResponseWriter, r *http.Request) {
utils.WriteResponse(w, http.StatusOK, report)
}
-func convertLibpodNetworktoDockerNetwork(runtime *libpod.Runtime, network nettypes.Network) (*types.NetworkResource, error) {
+func convertLibpodNetworktoDockerNetwork(runtime *libpod.Runtime, network *nettypes.Network, changeDefaultName bool) (*types.NetworkResource, error) {
cons, err := runtime.GetAllContainers()
if err != nil {
return nil, err
@@ -107,11 +114,15 @@ func convertLibpodNetworktoDockerNetwork(runtime *libpod.Runtime, network nettyp
Config: ipamConfigs,
}
+ name := network.Name
+ if changeDefaultName && name == runtime.Network().DefaultNetworkName() {
+ name = nettypes.BridgeNetworkDriver
+ }
report := types.NetworkResource{
- Name: network.Name,
- ID: network.ID,
- Driver: network.Driver,
- // TODO add Created: ,
+ Name: name,
+ ID: network.ID,
+ Driver: network.Driver,
+ Created: network.Created,
Internal: network.Internal,
EnableIPv6: network.IPv6Enabled,
Labels: network.Labels,
@@ -149,7 +160,7 @@ func ListNetworks(w http.ResponseWriter, r *http.Request) {
}
reports := make([]*types.NetworkResource, 0, len(nets))
for _, net := range nets {
- report, err := convertLibpodNetworktoDockerNetwork(runtime, net)
+ report, err := convertLibpodNetworktoDockerNetwork(runtime, &net, true)
if err != nil {
utils.InternalServerError(w, err)
return
@@ -182,27 +193,22 @@ func CreateNetwork(w http.ResponseWriter, r *http.Request) {
network.Options = make(map[string]string)
- // TODO: we should consider making this constants in c/common/libnetwork/types
+ // dockers bridge networks are always isolated from each other
+ if network.Driver == nettypes.BridgeNetworkDriver {
+ network.Options[nettypes.IsolateOption] = "true"
+ }
+
for opt, optVal := range networkCreate.Options {
switch opt {
- case "mtu":
+ case nettypes.MTUOption:
fallthrough
case "com.docker.network.driver.mtu":
- if network.Driver == nettypes.BridgeNetworkDriver {
- network.Options["mtu"] = optVal
- }
- case "icc":
- fallthrough
- case "com.docker.network.bridge.enable_icc":
- // TODO: needs to be implemented
- if network.Driver == nettypes.BridgeNetworkDriver {
- responseWarning = "com.docker.network.bridge.enable_icc is not currently implemented"
- }
+ network.Options[nettypes.MTUOption] = optVal
case "com.docker.network.bridge.name":
if network.Driver == nettypes.BridgeNetworkDriver {
network.NetworkInterface = optVal
}
- case "mode":
+ case nettypes.ModeOption:
if network.Driver == nettypes.MacVLANNetworkDriver || network.Driver == nettypes.IPVLANNetworkDriver {
network.Options[opt] = optVal
}
@@ -305,7 +311,7 @@ func RemoveNetwork(w http.ResponseWriter, r *http.Request) {
Timeout: query.Timeout,
}
- name := utils.GetName(r)
+ name, _ := normalizeNetworkName(runtime, utils.GetName(r))
reports, err := ic.NetworkRm(r.Context(), []string{name}, options)
if err != nil {
utils.Error(w, http.StatusInternalServerError, err)
@@ -340,7 +346,7 @@ func Connect(w http.ResponseWriter, r *http.Request) {
netOpts := nettypes.PerNetworkOptions{}
- name := utils.GetName(r)
+ name, _ := normalizeNetworkName(runtime, utils.GetName(r))
if netConnect.EndpointConfig != nil {
if netConnect.EndpointConfig.Aliases != nil {
netOpts.Aliases = netConnect.EndpointConfig.Aliases
@@ -416,7 +422,7 @@ func Disconnect(w http.ResponseWriter, r *http.Request) {
return
}
- name := utils.GetName(r)
+ name, _ := normalizeNetworkName(runtime, utils.GetName(r))
err := runtime.DisconnectContainerFromNetwork(netDisconnect.Container, name, netDisconnect.Force)
if err != nil {
if errors.Is(err, define.ErrNoSuchCtr) {
diff --git a/pkg/api/handlers/libpod/images.go b/pkg/api/handlers/libpod/images.go
index ed1c65f8e..67943ecf1 100644
--- a/pkg/api/handlers/libpod/images.go
+++ b/pkg/api/handlers/libpod/images.go
@@ -1,7 +1,6 @@
package libpod
import (
- "context"
"errors"
"fmt"
"io"
@@ -14,13 +13,11 @@ import (
"github.com/containers/buildah"
"github.com/containers/common/libimage"
"github.com/containers/image/v5/manifest"
- "github.com/containers/image/v5/types"
"github.com/containers/podman/v4/libpod"
"github.com/containers/podman/v4/libpod/define"
"github.com/containers/podman/v4/pkg/api/handlers"
"github.com/containers/podman/v4/pkg/api/handlers/utils"
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"
@@ -416,74 +413,6 @@ func ImagesImport(w http.ResponseWriter, r *http.Request) {
utils.WriteResponse(w, http.StatusOK, report)
}
-// PushImage is the handler for the compat http endpoint for pushing images.
-func PushImage(w http.ResponseWriter, r *http.Request) {
- decoder := r.Context().Value(api.DecoderKey).(*schema.Decoder)
- runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime)
-
- query := struct {
- All bool `schema:"all"`
- Destination string `schema:"destination"`
- Format string `schema:"format"`
- RemoveSignatures bool `schema:"removeSignatures"`
- TLSVerify bool `schema:"tlsVerify"`
- }{
- // 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, fmt.Errorf("failed to parse parameters for %s: %w", r.URL.String(), err))
- return
- }
-
- source := strings.TrimSuffix(utils.GetName(r), "/push") // GetName returns the entire path
- if _, err := utils.ParseStorageReference(source); err != nil {
- utils.Error(w, http.StatusBadRequest, err)
- return
- }
-
- destination := query.Destination
- if destination == "" {
- destination = source
- }
-
- if err := utils.IsRegistryReference(destination); err != nil {
- utils.Error(w, http.StatusBadRequest, err)
- return
- }
-
- authconf, authfile, err := auth.GetCredentials(r)
- if err != nil {
- utils.Error(w, http.StatusBadRequest, err)
- return
- }
- defer auth.RemoveAuthfile(authfile)
- var username, password string
- if authconf != nil {
- username = authconf.Username
- password = authconf.Password
- }
- options := entities.ImagePushOptions{
- All: query.All,
- Authfile: authfile,
- Format: query.Format,
- Password: password,
- Quiet: true,
- RemoveSignatures: query.RemoveSignatures,
- Username: username,
- }
- if _, found := r.URL.Query()["tlsVerify"]; found {
- options.SkipTLSVerify = types.NewOptionalBool(!query.TLSVerify)
- }
-
- imageEngine := abi.ImageEngine{Libpod: runtime}
- if err := imageEngine.Push(context.Background(), source, destination, options); err != nil {
- utils.Error(w, http.StatusBadRequest, fmt.Errorf("error pushing image %q: %w", destination, err))
- return
- }
-
- utils.WriteResponse(w, http.StatusOK, "")
-}
-
func CommitContainer(w http.ResponseWriter, r *http.Request) {
var (
destImage string
diff --git a/pkg/api/handlers/libpod/images_push.go b/pkg/api/handlers/libpod/images_push.go
new file mode 100644
index 000000000..f427dc01b
--- /dev/null
+++ b/pkg/api/handlers/libpod/images_push.go
@@ -0,0 +1,144 @@
+package libpod
+
+import (
+ "context"
+ "encoding/json"
+ "fmt"
+ "net/http"
+ "strings"
+
+ "github.com/containers/image/v5/types"
+ "github.com/containers/podman/v4/libpod"
+ "github.com/containers/podman/v4/pkg/api/handlers/utils"
+ api "github.com/containers/podman/v4/pkg/api/types"
+ "github.com/containers/podman/v4/pkg/auth"
+ "github.com/containers/podman/v4/pkg/channel"
+ "github.com/containers/podman/v4/pkg/domain/entities"
+ "github.com/containers/podman/v4/pkg/domain/infra/abi"
+ "github.com/gorilla/schema"
+ "github.com/sirupsen/logrus"
+)
+
+// PushImage is the handler for the compat http endpoint for pushing images.
+func PushImage(w http.ResponseWriter, r *http.Request) {
+ decoder := r.Context().Value(api.DecoderKey).(*schema.Decoder)
+ runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime)
+
+ query := struct {
+ All bool `schema:"all"`
+ Destination string `schema:"destination"`
+ Format string `schema:"format"`
+ RemoveSignatures bool `schema:"removeSignatures"`
+ TLSVerify bool `schema:"tlsVerify"`
+ Quiet bool `schema:"quiet"`
+ }{
+ // #14971: older versions did not sent *any* data, so we need
+ // to be quiet by default to remain backwards compatible
+ Quiet: true,
+ }
+ if err := decoder.Decode(&query, r.URL.Query()); err != nil {
+ utils.Error(w, http.StatusBadRequest, fmt.Errorf("failed to parse parameters for %s: %w", r.URL.String(), err))
+ return
+ }
+
+ source := strings.TrimSuffix(utils.GetName(r), "/push") // GetName returns the entire path
+ if _, err := utils.ParseStorageReference(source); err != nil {
+ utils.Error(w, http.StatusBadRequest, err)
+ return
+ }
+
+ destination := query.Destination
+ if destination == "" {
+ destination = source
+ }
+
+ if err := utils.IsRegistryReference(destination); err != nil {
+ utils.Error(w, http.StatusBadRequest, err)
+ return
+ }
+
+ authconf, authfile, err := auth.GetCredentials(r)
+ if err != nil {
+ utils.Error(w, http.StatusBadRequest, err)
+ return
+ }
+ defer auth.RemoveAuthfile(authfile)
+
+ var username, password string
+ if authconf != nil {
+ username = authconf.Username
+ password = authconf.Password
+ }
+ options := entities.ImagePushOptions{
+ All: query.All,
+ Authfile: authfile,
+ Format: query.Format,
+ Password: password,
+ Quiet: true,
+ RemoveSignatures: query.RemoveSignatures,
+ Username: username,
+ }
+
+ if _, found := r.URL.Query()["tlsVerify"]; found {
+ options.SkipTLSVerify = types.NewOptionalBool(!query.TLSVerify)
+ }
+
+ imageEngine := abi.ImageEngine{Libpod: runtime}
+
+ // Let's keep thing simple when running in quiet mode and push directly.
+ if query.Quiet {
+ if err := imageEngine.Push(context.Background(), source, destination, options); err != nil {
+ utils.Error(w, http.StatusBadRequest, fmt.Errorf("error pushing image %q: %w", destination, err))
+ return
+ }
+ utils.WriteResponse(w, http.StatusOK, "")
+ return
+ }
+
+ writer := channel.NewWriter(make(chan []byte))
+ defer writer.Close()
+ options.Writer = writer
+
+ pushCtx, pushCancel := context.WithCancel(r.Context())
+ var pushError error
+ go func() {
+ defer pushCancel()
+ pushError = imageEngine.Push(pushCtx, source, destination, options)
+ }()
+
+ flush := func() {
+ if flusher, ok := w.(http.Flusher); ok {
+ flusher.Flush()
+ }
+ }
+
+ w.WriteHeader(http.StatusOK)
+ w.Header().Set("Content-Type", "application/json")
+ flush()
+
+ enc := json.NewEncoder(w)
+ enc.SetEscapeHTML(true)
+ for {
+ var report entities.ImagePushReport
+ select {
+ case s := <-writer.Chan():
+ report.Stream = string(s)
+ if err := enc.Encode(report); err != nil {
+ logrus.Warnf("Failed to encode json: %v", err)
+ }
+ flush()
+ case <-pushCtx.Done():
+ if pushError != nil {
+ report.Error = pushError.Error()
+ if err := enc.Encode(report); err != nil {
+ logrus.Warnf("Failed to encode json: %v", err)
+ }
+ }
+ flush()
+ return
+ case <-r.Context().Done():
+ // Client has closed connection
+ return
+ }
+ }
+}
diff --git a/pkg/api/server/register_images.go b/pkg/api/server/register_images.go
index a2f46cb35..11ab8cae0 100644
--- a/pkg/api/server/register_images.go
+++ b/pkg/api/server/register_images.go
@@ -730,6 +730,11 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error {
// description: Require TLS verification.
// type: boolean
// default: true
+ // - in: query
+ // name: quiet
+ // description: "silences extra stream data on push"
+ // type: boolean
+ // default: true
// - in: header
// name: X-Registry-Auth
// type: string
diff --git a/pkg/bindings/images/images.go b/pkg/bindings/images/images.go
index cd5147629..bb7867c4e 100644
--- a/pkg/bindings/images/images.go
+++ b/pkg/bindings/images/images.go
@@ -267,46 +267,6 @@ func Import(ctx context.Context, r io.Reader, options *ImportOptions) (*entities
return &report, response.Process(&report)
}
-// Push is the binding for libpod's v2 endpoints for push images. Note that
-// `source` must be a referring to an image in the remote's container storage.
-// The destination must be a reference to a registry (i.e., of docker transport
-// or be normalized to one). Other transports are rejected as they do not make
-// sense in a remote context.
-func Push(ctx context.Context, source string, destination string, options *PushOptions) error {
- if options == nil {
- options = new(PushOptions)
- }
- conn, err := bindings.GetClient(ctx)
- if err != nil {
- return err
- }
- header, err := auth.MakeXRegistryAuthHeader(&imageTypes.SystemContext{AuthFilePath: options.GetAuthfile()}, options.GetUsername(), options.GetPassword())
- if err != nil {
- return err
- }
-
- params, err := options.ToParams()
- if err != nil {
- return err
- }
- // SkipTLSVerify is special. We need to delete the param added by
- // toparams and change the key and flip the bool
- if options.SkipTLSVerify != nil {
- params.Del("SkipTLSVerify")
- params.Set("tlsVerify", strconv.FormatBool(!options.GetSkipTLSVerify()))
- }
- params.Set("destination", destination)
-
- path := fmt.Sprintf("/images/%s/push", source)
- response, err := conn.DoRequest(ctx, nil, http.MethodPost, path, params, header)
- if err != nil {
- return err
- }
- defer response.Body.Close()
-
- return response.Process(err)
-}
-
// Search is the binding for libpod's v2 endpoints for Search images.
func Search(ctx context.Context, term string, options *SearchOptions) ([]entities.ImageSearchReport, error) {
if options == nil {
diff --git a/pkg/bindings/images/push.go b/pkg/bindings/images/push.go
new file mode 100644
index 000000000..8db3726e6
--- /dev/null
+++ b/pkg/bindings/images/push.go
@@ -0,0 +1,96 @@
+package images
+
+import (
+ "context"
+ "encoding/json"
+ "errors"
+ "fmt"
+ "io"
+ "io/ioutil"
+ "net/http"
+ "os"
+ "strconv"
+
+ imageTypes "github.com/containers/image/v5/types"
+ "github.com/containers/podman/v4/pkg/auth"
+ "github.com/containers/podman/v4/pkg/bindings"
+ "github.com/containers/podman/v4/pkg/domain/entities"
+)
+
+// Push is the binding for libpod's endpoints for push images. Note that
+// `source` must be a referring to an image in the remote's container storage.
+// The destination must be a reference to a registry (i.e., of docker transport
+// or be normalized to one). Other transports are rejected as they do not make
+// sense in a remote context.
+func Push(ctx context.Context, source string, destination string, options *PushOptions) error {
+ if options == nil {
+ options = new(PushOptions)
+ }
+ conn, err := bindings.GetClient(ctx)
+ if err != nil {
+ return err
+ }
+ header, err := auth.MakeXRegistryAuthHeader(&imageTypes.SystemContext{AuthFilePath: options.GetAuthfile()}, options.GetUsername(), options.GetPassword())
+ if err != nil {
+ return err
+ }
+
+ params, err := options.ToParams()
+ if err != nil {
+ return err
+ }
+ // SkipTLSVerify is special. We need to delete the param added by
+ // toparams and change the key and flip the bool
+ if options.SkipTLSVerify != nil {
+ params.Del("SkipTLSVerify")
+ params.Set("tlsVerify", strconv.FormatBool(!options.GetSkipTLSVerify()))
+ }
+ params.Set("destination", destination)
+
+ path := fmt.Sprintf("/images/%s/push", source)
+ response, err := conn.DoRequest(ctx, nil, http.MethodPost, path, params, header)
+ if err != nil {
+ return err
+ }
+ defer response.Body.Close()
+
+ if !response.IsSuccess() {
+ return response.Process(err)
+ }
+
+ // Historically push writes status to stderr
+ writer := io.Writer(os.Stderr)
+ if options.GetQuiet() {
+ writer = ioutil.Discard
+ }
+
+ dec := json.NewDecoder(response.Body)
+ for {
+ var report entities.ImagePushReport
+ if err := dec.Decode(&report); err != nil {
+ if errors.Is(err, io.EOF) {
+ break
+ }
+ return err
+ }
+
+ select {
+ case <-response.Request.Context().Done():
+ break
+ default:
+ // non-blocking select
+ }
+
+ switch {
+ case report.Stream != "":
+ fmt.Fprint(writer, report.Stream)
+ case report.Error != "":
+ // There can only be one error.
+ return errors.New(report.Error)
+ default:
+ return fmt.Errorf("failed to parse push results stream, unexpected input: %v", report)
+ }
+ }
+
+ return nil
+}
diff --git a/pkg/bindings/images/types.go b/pkg/bindings/images/types.go
index 3728ae5c0..0e672cdea 100644
--- a/pkg/bindings/images/types.go
+++ b/pkg/bindings/images/types.go
@@ -133,6 +133,8 @@ type PushOptions struct {
RemoveSignatures *bool
// Username for authenticating against the registry.
Username *string
+ // Quiet can be specified to suppress progress when pushing.
+ Quiet *bool
}
//go:generate go run ../generator/generator.go SearchOptions
diff --git a/pkg/bindings/images/types_push_options.go b/pkg/bindings/images/types_push_options.go
index 25f6c5546..63a19fb81 100644
--- a/pkg/bindings/images/types_push_options.go
+++ b/pkg/bindings/images/types_push_options.go
@@ -136,3 +136,18 @@ func (o *PushOptions) GetUsername() string {
}
return *o.Username
}
+
+// WithQuiet set field Quiet to given value
+func (o *PushOptions) WithQuiet(value bool) *PushOptions {
+ o.Quiet = &value
+ return o
+}
+
+// GetQuiet returns value of field Quiet
+func (o *PushOptions) GetQuiet() bool {
+ if o.Quiet == nil {
+ var z bool
+ return z
+ }
+ return *o.Quiet
+}
diff --git a/pkg/domain/entities/images.go b/pkg/domain/entities/images.go
index da317cfad..b8b346005 100644
--- a/pkg/domain/entities/images.go
+++ b/pkg/domain/entities/images.go
@@ -1,6 +1,7 @@
package entities
import (
+ "io"
"net/url"
"time"
@@ -192,8 +193,7 @@ type ImagePushOptions struct {
// image. Default is manifest type of source, with fallbacks.
// Ignored for remote calls.
Format string
- // Quiet can be specified to suppress pull progress when pulling. Ignored
- // for remote calls.
+ // Quiet can be specified to suppress push progress when pushing.
Quiet bool
// Rm indicates whether to remove the manifest list if push succeeds
Rm bool
@@ -211,6 +211,17 @@ type ImagePushOptions struct {
Progress chan types.ProgressProperties
// CompressionFormat is the format to use for the compression of the blobs
CompressionFormat string
+ // Writer is used to display copy information including progress bars.
+ Writer io.Writer
+}
+
+// ImagePushReport is the response from pushing an image.
+// Currently only used in the remote API.
+type ImagePushReport struct {
+ // Stream used to provide push progress
+ Stream string `json:"stream,omitempty"`
+ // Error contains text of errors from pushing
+ Error string `json:"error,omitempty"`
}
// ImageSearchOptions are the arguments for searching images.
diff --git a/pkg/domain/entities/machine.go b/pkg/domain/entities/machine.go
index 6ba53dbd1..4fd0413c9 100644
--- a/pkg/domain/entities/machine.go
+++ b/pkg/domain/entities/machine.go
@@ -1,5 +1,7 @@
package entities
+import "github.com/containers/podman/v4/libpod/define"
+
type ListReporter struct {
Name string
Default bool
@@ -16,3 +18,23 @@ type ListReporter struct {
RemoteUsername string
IdentityPath string
}
+
+// MachineInfo contains info on the machine host and version info
+type MachineInfo struct {
+ Host *MachineHostInfo `json:"Host"`
+ Version define.Version `json:"Version"`
+}
+
+// MachineHostInfo contains info on the machine host
+type MachineHostInfo struct {
+ Arch string `json:"Arch"`
+ CurrentMachine string `json:"CurrentMachine"`
+ DefaultMachine string `json:"DefaultMachine"`
+ EventsDir string `json:"EventsDir"`
+ MachineConfigDir string `json:"MachineConfigDir"`
+ MachineImageDir string `json:"MachineImageDir"`
+ MachineState string `json:"MachineState"`
+ NumberOfMachines int `json:"NumberOfMachines"`
+ OS string `json:"OS"`
+ VMType string `json:"VMType"`
+}
diff --git a/pkg/domain/infra/abi/images.go b/pkg/domain/infra/abi/images.go
index 38008c7b9..ff42b0367 100644
--- a/pkg/domain/infra/abi/images.go
+++ b/pkg/domain/infra/abi/images.go
@@ -305,6 +305,7 @@ func (ir *ImageEngine) Push(ctx context.Context, source string, destination stri
pushOptions.RemoveSignatures = options.RemoveSignatures
pushOptions.SignBy = options.SignBy
pushOptions.InsecureSkipTLSVerify = options.SkipTLSVerify
+ pushOptions.Writer = options.Writer
compressionFormat := options.CompressionFormat
if compressionFormat == "" {
@@ -322,7 +323,7 @@ func (ir *ImageEngine) Push(ctx context.Context, source string, destination stri
pushOptions.CompressionFormat = &algo
}
- if !options.Quiet {
+ if !options.Quiet && pushOptions.Writer == nil {
pushOptions.Writer = os.Stderr
}
diff --git a/pkg/domain/infra/abi/play.go b/pkg/domain/infra/abi/play.go
index 116604035..3f2fd5f92 100644
--- a/pkg/domain/infra/abi/play.go
+++ b/pkg/domain/infra/abi/play.go
@@ -520,13 +520,17 @@ func (ic *ContainerEngine) playKubePod(ctx context.Context, podName string, podY
for k, v := range podSpec.PodSpecGen.Labels { // add podYAML labels
labels[k] = v
}
+ initCtrType := annotations[define.InitContainerType]
+ if initCtrType == "" {
+ initCtrType = define.OneShotInitContainer
+ }
specgenOpts := kube.CtrSpecGenOptions{
Annotations: annotations,
ConfigMaps: configMaps,
Container: initCtr,
Image: pulledImage,
- InitContainerType: define.AlwaysInitContainer,
+ InitContainerType: initCtrType,
Labels: labels,
LogDriver: options.LogDriver,
LogOptions: options.LogOptions,
diff --git a/pkg/domain/infra/tunnel/images.go b/pkg/domain/infra/tunnel/images.go
index 18f750dcc..9ad408850 100644
--- a/pkg/domain/infra/tunnel/images.go
+++ b/pkg/domain/infra/tunnel/images.go
@@ -240,7 +240,7 @@ func (ir *ImageEngine) Import(ctx context.Context, opts entities.ImageImportOpti
func (ir *ImageEngine) Push(ctx context.Context, source string, destination string, opts entities.ImagePushOptions) error {
options := new(images.PushOptions)
- options.WithAll(opts.All).WithCompress(opts.Compress).WithUsername(opts.Username).WithPassword(opts.Password).WithAuthfile(opts.Authfile).WithFormat(opts.Format).WithRemoveSignatures(opts.RemoveSignatures)
+ options.WithAll(opts.All).WithCompress(opts.Compress).WithUsername(opts.Username).WithPassword(opts.Password).WithAuthfile(opts.Authfile).WithFormat(opts.Format).WithRemoveSignatures(opts.RemoveSignatures).WithQuiet(opts.Quiet)
if s := opts.SkipTLSVerify; s != types.OptionalBoolUndefined {
if s == types.OptionalBoolTrue {
diff --git a/pkg/hooks/0.1.0/hook.go b/pkg/hooks/0.1.0/hook.go
deleted file mode 100644
index c3df5fa1d..000000000
--- a/pkg/hooks/0.1.0/hook.go
+++ /dev/null
@@ -1,88 +0,0 @@
-// Package hook is the 0.1.0 hook configuration structure.
-package hook
-
-import (
- "encoding/json"
- "errors"
- "strings"
-
- current "github.com/containers/podman/v4/pkg/hooks/1.0.0"
- rspec "github.com/opencontainers/runtime-spec/specs-go"
-)
-
-// Version is the hook configuration version defined in this package.
-const Version = "0.1.0"
-
-// Hook is the hook configuration structure.
-type Hook struct {
- Hook *string `json:"hook"`
- Arguments []string `json:"arguments,omitempty"`
-
- // https://github.com/cri-o/cri-o/pull/1235
- Stages []string `json:"stages"`
- Stage []string `json:"stage"`
-
- Cmds []string `json:"cmds,omitempty"`
- Cmd []string `json:"cmd,omitempty"`
-
- Annotations []string `json:"annotations,omitempty"`
- Annotation []string `json:"annotation,omitempty"`
-
- HasBindMounts *bool `json:"hasbindmounts,omitempty"`
-}
-
-func Read(content []byte) (hook *current.Hook, err error) {
- var raw Hook
-
- if err = json.Unmarshal(content, &raw); err != nil {
- return nil, err
- }
-
- if raw.Hook == nil {
- return nil, errors.New("missing required property: hook")
- }
-
- if raw.Stages == nil {
- raw.Stages = raw.Stage
- } else if raw.Stage != nil {
- return nil, errors.New("cannot set both 'stage' and 'stages'")
- }
- if raw.Stages == nil {
- return nil, errors.New("missing required property: stages")
- }
-
- if raw.Cmds == nil {
- raw.Cmds = raw.Cmd
- } else if raw.Cmd != nil {
- return nil, errors.New("cannot set both 'cmd' and 'cmds'")
- }
-
- if raw.Annotations == nil {
- raw.Annotations = raw.Annotation
- } else if raw.Annotation != nil {
- return nil, errors.New("cannot set both 'annotation' and 'annotations'")
- }
-
- hook = &current.Hook{
- Version: current.Version,
- Hook: rspec.Hook{
- Path: *raw.Hook,
- },
- When: current.When{
- Commands: raw.Cmds,
- HasBindMounts: raw.HasBindMounts,
- Or: true,
- },
- Stages: raw.Stages,
- }
- if raw.Arguments != nil {
- hook.Hook.Args = append([]string{*raw.Hook}, raw.Arguments...)
- }
- if raw.Annotations != nil {
- hook.When.Annotations = map[string]string{
- ".*": strings.Join(raw.Annotations, "|"),
- }
- }
-
- return hook, nil
-}
diff --git a/pkg/hooks/0.1.0/hook_test.go b/pkg/hooks/0.1.0/hook_test.go
deleted file mode 100644
index 5beadbaaf..000000000
--- a/pkg/hooks/0.1.0/hook_test.go
+++ /dev/null
@@ -1,182 +0,0 @@
-package hook
-
-import (
- "testing"
-
- current "github.com/containers/podman/v4/pkg/hooks/1.0.0"
- rspec "github.com/opencontainers/runtime-spec/specs-go"
- "github.com/stretchr/testify/assert"
-)
-
-func TestGood(t *testing.T) {
- hook, err := Read([]byte("{\"hook\": \"/a/b/c\", \"stages\": [\"prestart\"], \"cmds\": [\"sh\"]}"))
- if err != nil {
- t.Fatal(err)
- }
- assert.Equal(t, &current.Hook{
- Version: current.Version,
- Hook: rspec.Hook{
- Path: "/a/b/c",
- },
- When: current.When{
- Commands: []string{"sh"},
- Or: true,
- },
- Stages: []string{"prestart"},
- }, hook)
-}
-
-func TestInvalidJSON(t *testing.T) {
- _, err := Read([]byte("{"))
- if err == nil {
- t.Fatal("unexpected success")
- }
- assert.Regexp(t, "^unexpected end of JSON input$", err.Error())
-}
-
-func TestArguments(t *testing.T) {
- hook, err := Read([]byte("{\"hook\": \"/a/b/c\", \"arguments\": [\"d\", \"e\"], \"stages\": [\"prestart\"], \"cmds\": [\"sh\"]}"))
- if err != nil {
- t.Fatal(err)
- }
- assert.Equal(t, &current.Hook{
- Version: current.Version,
- Hook: rspec.Hook{
- Path: "/a/b/c",
- Args: []string{"/a/b/c", "d", "e"},
- },
- When: current.When{
- Commands: []string{"sh"},
- Or: true,
- },
- Stages: []string{"prestart"},
- }, hook)
-}
-
-func TestEmptyObject(t *testing.T) {
- _, err := Read([]byte("{}"))
- if err == nil {
- t.Fatal("unexpected success")
- }
- assert.Regexp(t, "^missing required property: hook$", err.Error())
-}
-
-func TestNoStages(t *testing.T) {
- _, err := Read([]byte("{\"hook\": \"/a/b/c\"}"))
- if err == nil {
- t.Fatal("unexpected success")
- }
- assert.Regexp(t, "^missing required property: stages$", err.Error())
-}
-
-func TestStage(t *testing.T) {
- hook, err := Read([]byte("{\"hook\": \"/a/b/c\", \"stage\": [\"prestart\"]}"))
- if err != nil {
- t.Fatal(err)
- }
- assert.Equal(t, &current.Hook{
- Version: current.Version,
- Hook: rspec.Hook{
- Path: "/a/b/c",
- },
- When: current.When{Or: true},
- Stages: []string{"prestart"},
- }, hook)
-}
-
-func TestStagesAndStage(t *testing.T) {
- _, err := Read([]byte("{\"hook\": \"/a/b/c\", \"stages\": [\"prestart\"], \"stage\": [\"prestart\"]}"))
- if err == nil {
- t.Fatal("unexpected success")
- }
- assert.Regexp(t, "^cannot set both 'stage' and 'stages'$", err.Error())
-}
-
-func TestCmd(t *testing.T) {
- hook, err := Read([]byte("{\"hook\": \"/a/b/c\", \"stage\": [\"prestart\"], \"cmd\": [\"sh\"]}"))
- if err != nil {
- t.Fatal(err)
- }
- assert.Equal(t, &current.Hook{
- Version: current.Version,
- Hook: rspec.Hook{
- Path: "/a/b/c",
- },
- When: current.When{
- Commands: []string{"sh"},
- Or: true,
- },
- Stages: []string{"prestart"},
- }, hook)
-}
-
-func TestCmdsAndCmd(t *testing.T) {
- _, err := Read([]byte("{\"hook\": \"/a/b/c\", \"stages\": [\"prestart\"], \"cmds\": [\"sh\"], \"cmd\": [\"true\"]}"))
- if err == nil {
- t.Fatal("unexpected success")
- }
- assert.Regexp(t, "^cannot set both 'cmd' and 'cmds'$", err.Error())
-}
-
-func TestAnnotations(t *testing.T) {
- hook, err := Read([]byte("{\"hook\": \"/a/b/c\", \"stage\": [\"prestart\"], \"annotations\": [\"a\", \"b\"]}"))
- if err != nil {
- t.Fatal(err)
- }
- assert.Equal(t, &current.Hook{
- Version: current.Version,
- Hook: rspec.Hook{
- Path: "/a/b/c",
- },
- When: current.When{
- Annotations: map[string]string{".*": "a|b"},
- Or: true,
- },
- Stages: []string{"prestart"},
- }, hook)
-}
-
-func TestAnnotation(t *testing.T) {
- hook, err := Read([]byte("{\"hook\": \"/a/b/c\", \"stage\": [\"prestart\"], \"annotation\": [\"a\", \"b\"]}"))
- if err != nil {
- t.Fatal(err)
- }
- assert.Equal(t, &current.Hook{
- Version: current.Version,
- Hook: rspec.Hook{
- Path: "/a/b/c",
- },
- When: current.When{
- Annotations: map[string]string{".*": "a|b"},
- Or: true,
- },
- Stages: []string{"prestart"},
- }, hook)
-}
-
-func TestAnnotationsAndAnnotation(t *testing.T) {
- _, err := Read([]byte("{\"hook\": \"/a/b/c\", \"stages\": [\"prestart\"], \"annotations\": [\"a\"], \"annotation\": [\"b\"]}"))
- if err == nil {
- t.Fatal("unexpected success")
- }
- assert.Regexp(t, "^cannot set both 'annotation' and 'annotations'$", err.Error())
-}
-
-func TestHasBindMounts(t *testing.T) {
- hook, err := Read([]byte("{\"hook\": \"/a/b/c\", \"stage\": [\"prestart\"], \"hasbindmounts\": true}"))
- if err != nil {
- t.Fatal(err)
- }
- hasBindMounts := true
- assert.Equal(t, &current.Hook{
- Version: current.Version,
- Hook: rspec.Hook{
- Path: "/a/b/c",
- },
- When: current.When{
- HasBindMounts: &hasBindMounts,
- Or: true,
- },
- Stages: []string{"prestart"},
- }, hook)
-}
diff --git a/pkg/hooks/1.0.0/hook.go b/pkg/hooks/1.0.0/hook.go
deleted file mode 100644
index 71f940a64..000000000
--- a/pkg/hooks/1.0.0/hook.go
+++ /dev/null
@@ -1,89 +0,0 @@
-// Package hook is the 1.0.0 hook configuration structure.
-package hook
-
-import (
- "encoding/json"
- "errors"
- "fmt"
- "os"
- "regexp"
-
- rspec "github.com/opencontainers/runtime-spec/specs-go"
-)
-
-// Version is the hook configuration version defined in this package.
-const Version = "1.0.0"
-
-// Hook is the hook configuration structure.
-type Hook struct {
- Version string `json:"version"`
- Hook rspec.Hook `json:"hook"`
- When When `json:"when"`
- Stages []string `json:"stages"`
-}
-
-// Read reads hook JSON bytes, verifies them, and returns the hook configuration.
-func Read(content []byte) (hook *Hook, err error) {
- if err = json.Unmarshal(content, &hook); err != nil {
- return nil, err
- }
- return hook, nil
-}
-
-// Validate performs load-time hook validation.
-func (hook *Hook) Validate(extensionStages []string) (err error) {
- if hook == nil {
- return errors.New("nil hook")
- }
-
- if hook.Version != Version {
- return fmt.Errorf("unexpected hook version %q (expecting %v)", hook.Version, Version)
- }
-
- if hook.Hook.Path == "" {
- return errors.New("missing required property: hook.path")
- }
-
- if _, err := os.Stat(hook.Hook.Path); err != nil {
- return err
- }
-
- for key, value := range hook.When.Annotations {
- if _, err = regexp.Compile(key); err != nil {
- return fmt.Errorf("invalid annotation key %q: %w", key, err)
- }
- if _, err = regexp.Compile(value); err != nil {
- return fmt.Errorf("invalid annotation value %q: %w", value, err)
- }
- }
-
- for _, command := range hook.When.Commands {
- if _, err = regexp.Compile(command); err != nil {
- return fmt.Errorf("invalid command %q: %w", command, err)
- }
- }
-
- if hook.Stages == nil {
- return errors.New("missing required property: stages")
- }
-
- validStages := map[string]bool{
- "createContainer": true,
- "createRuntime": true,
- "prestart": true,
- "poststart": true,
- "poststop": true,
- "startContainer": true,
- }
- for _, stage := range extensionStages {
- validStages[stage] = true
- }
-
- for _, stage := range hook.Stages {
- if !validStages[stage] {
- return fmt.Errorf("unknown stage %q", stage)
- }
- }
-
- return nil
-}
diff --git a/pkg/hooks/1.0.0/hook_test.go b/pkg/hooks/1.0.0/hook_test.go
deleted file mode 100644
index bd6d6b654..000000000
--- a/pkg/hooks/1.0.0/hook_test.go
+++ /dev/null
@@ -1,214 +0,0 @@
-package hook
-
-import (
- "os"
- "path/filepath"
- "runtime"
- "testing"
-
- rspec "github.com/opencontainers/runtime-spec/specs-go"
- "github.com/stretchr/testify/assert"
-)
-
-// path is the path to an example hook executable.
-var path string
-
-func TestGoodRead(t *testing.T) {
- hook, err := Read([]byte("{\"version\": \"1.0.0\", \"hook\": {\"path\": \"/a/b/c\"}, \"when\": {\"always\": true}, \"stages\": [\"prestart\"]}"))
- if err != nil {
- t.Fatal(err)
- }
- always := true
- assert.Equal(t, &Hook{
- Version: Version,
- Hook: rspec.Hook{
- Path: "/a/b/c",
- },
- When: When{
- Always: &always,
- },
- Stages: []string{"prestart"},
- }, hook)
-}
-
-func TestInvalidJSON(t *testing.T) {
- _, err := Read([]byte("{"))
- if err == nil {
- t.Fatal("unexpected success")
- }
- assert.Regexp(t, "^unexpected end of JSON input$", err.Error())
-}
-
-func TestGoodValidate(t *testing.T) {
- always := true
- hook := &Hook{
- Version: Version,
- Hook: rspec.Hook{
- Path: path,
- },
- When: When{
- Always: &always,
- },
- Stages: []string{"prestart"},
- }
- err := hook.Validate([]string{})
- if err != nil {
- t.Fatal(err)
- }
-}
-
-func TestNilValidation(t *testing.T) {
- var hook *Hook
- err := hook.Validate([]string{})
- if err == nil {
- t.Fatal("unexpected success")
- }
- assert.Regexp(t, "^nil hook$", err.Error())
-}
-
-func TestWrongVersion(t *testing.T) {
- hook := Hook{Version: "0.1.0"}
- err := hook.Validate([]string{})
- if err == nil {
- t.Fatal("unexpected success")
- }
- assert.Regexp(t, "^unexpected hook version \"0.1.0\" \\(expecting 1.0.0\\)$", err.Error())
-}
-
-func TestNoHookPath(t *testing.T) {
- hook := Hook{
- Version: "1.0.0",
- Hook: rspec.Hook{},
- }
- err := hook.Validate([]string{})
- if err == nil {
- t.Fatal("unexpected success")
- }
- assert.Regexp(t, "^missing required property: hook.path$", err.Error())
-}
-
-func TestUnknownHookPath(t *testing.T) {
- hook := Hook{
- Version: "1.0.0",
- Hook: rspec.Hook{
- Path: filepath.Join("does", "not", "exist"),
- },
- }
- err := hook.Validate([]string{})
- if err == nil {
- t.Fatal("unexpected success")
- }
- assert.Regexp(t, "^stat does/not/exist: no such file or directory$", err.Error())
- if !os.IsNotExist(err) {
- t.Fatal("opaque wrapping for not-exist errors")
- }
-}
-
-func TestNoStages(t *testing.T) {
- hook := Hook{
- Version: "1.0.0",
- Hook: rspec.Hook{
- Path: path,
- },
- }
- err := hook.Validate([]string{})
- if err == nil {
- t.Fatal("unexpected success")
- }
- assert.Regexp(t, "^missing required property: stages$", err.Error())
-}
-
-func TestInvalidStage(t *testing.T) {
- hook := Hook{
- Version: "1.0.0",
- Hook: rspec.Hook{
- Path: path,
- },
- Stages: []string{"does-not-exist"},
- }
- err := hook.Validate([]string{})
- if err == nil {
- t.Fatal("unexpected success")
- }
- assert.Regexp(t, "^unknown stage \"does-not-exist\"$", err.Error())
-}
-
-func TestExtensionStage(t *testing.T) {
- hook := Hook{
- Version: "1.0.0",
- Hook: rspec.Hook{
- Path: path,
- },
- Stages: []string{"prestart", "b"},
- }
- err := hook.Validate([]string{"a", "b", "c"})
- if err != nil {
- t.Fatal(err)
- }
-}
-
-func TestInvalidAnnotationKey(t *testing.T) {
- hook := Hook{
- Version: "1.0.0",
- Hook: rspec.Hook{
- Path: path,
- },
- When: When{
- Annotations: map[string]string{
- "[": "a",
- },
- },
- Stages: []string{"prestart"},
- }
- err := hook.Validate([]string{})
- if err == nil {
- t.Fatal("unexpected success")
- }
- assert.Regexp(t, "^invalid annotation key \"\\[\": error parsing regexp: .*", err.Error())
-}
-
-func TestInvalidAnnotationValue(t *testing.T) {
- hook := Hook{
- Version: "1.0.0",
- Hook: rspec.Hook{
- Path: path,
- },
- When: When{
- Annotations: map[string]string{
- "a": "[",
- },
- },
- Stages: []string{"prestart"},
- }
- err := hook.Validate([]string{})
- if err == nil {
- t.Fatal("unexpected success")
- }
- assert.Regexp(t, "^invalid annotation value \"\\[\": error parsing regexp: .*", err.Error())
-}
-
-func TestInvalidCommand(t *testing.T) {
- hook := Hook{
- Version: "1.0.0",
- Hook: rspec.Hook{
- Path: path,
- },
- When: When{
- Commands: []string{"["},
- },
- Stages: []string{"prestart"},
- }
- err := hook.Validate([]string{})
- if err == nil {
- t.Fatal("unexpected success")
- }
- assert.Regexp(t, "^invalid command \"\\[\": error parsing regexp: .*", err.Error())
-}
-
-func init() {
- if runtime.GOOS != "windows" {
- path = "/bin/sh"
- } else {
- panic("we need a reliable executable path on Windows")
- }
-}
diff --git a/pkg/hooks/1.0.0/when.go b/pkg/hooks/1.0.0/when.go
deleted file mode 100644
index a1351890f..000000000
--- a/pkg/hooks/1.0.0/when.go
+++ /dev/null
@@ -1,96 +0,0 @@
-package hook
-
-import (
- "errors"
- "fmt"
- "regexp"
-
- rspec "github.com/opencontainers/runtime-spec/specs-go"
-)
-
-// When holds hook-injection conditions.
-type When struct {
- Always *bool `json:"always,omitempty"`
- Annotations map[string]string `json:"annotations,omitempty"`
- Commands []string `json:"commands,omitempty"`
- HasBindMounts *bool `json:"hasBindMounts,omitempty"`
-
- // Or enables any-of matching.
- //
- // Deprecated: this property is for is backwards-compatibility with
- // 0.1.0 hooks. It will be removed when we drop support for them.
- Or bool `json:"-"`
-}
-
-// Match returns true if the given conditions match the configuration.
-func (when *When) Match(config *rspec.Spec, annotations map[string]string, hasBindMounts bool) (match bool, err error) {
- matches := 0
-
- if when.Always != nil {
- if *when.Always {
- if when.Or {
- return true, nil
- }
- matches++
- } else if !when.Or {
- return false, nil
- }
- }
-
- if when.HasBindMounts != nil {
- if *when.HasBindMounts && hasBindMounts {
- if when.Or {
- return true, nil
- }
- matches++
- } else if !when.Or {
- return false, nil
- }
- }
-
- for keyPattern, valuePattern := range when.Annotations {
- match := false
- for key, value := range annotations {
- match, err = regexp.MatchString(keyPattern, key)
- if err != nil {
- return false, fmt.Errorf("annotation key: %w", err)
- }
- if match {
- match, err = regexp.MatchString(valuePattern, value)
- if err != nil {
- return false, fmt.Errorf("annotation value: %w", err)
- }
- if match {
- break
- }
- }
- }
- if match {
- if when.Or {
- return true, nil
- }
- matches++
- } else if !when.Or {
- return false, nil
- }
- }
-
- if config.Process != nil && len(when.Commands) > 0 {
- if len(config.Process.Args) == 0 {
- return false, errors.New("process.args must have at least one entry")
- }
- command := config.Process.Args[0]
- for _, cmdPattern := range when.Commands {
- match, err := regexp.MatchString(cmdPattern, command)
- if err != nil {
- return false, fmt.Errorf("command: %w", err)
- }
- if match {
- return true, nil
- }
- }
- return false, nil
- }
-
- return matches > 0, nil
-}
diff --git a/pkg/hooks/1.0.0/when_test.go b/pkg/hooks/1.0.0/when_test.go
deleted file mode 100644
index 94b0c3830..000000000
--- a/pkg/hooks/1.0.0/when_test.go
+++ /dev/null
@@ -1,329 +0,0 @@
-package hook
-
-import (
- "fmt"
- "testing"
-
- rspec "github.com/opencontainers/runtime-spec/specs-go"
- "github.com/stretchr/testify/assert"
-)
-
-func TestNoMatch(t *testing.T) {
- config := &rspec.Spec{}
- for _, o := range []bool{true, false} {
- or := o
- t.Run(fmt.Sprintf("or %t", or), func(t *testing.T) {
- when := When{Or: or}
- match, err := when.Match(config, map[string]string{}, false)
- if err != nil {
- t.Fatal(err)
- }
- assert.Equal(t, false, match)
- })
- }
-}
-
-func TestAlways(t *testing.T) {
- config := &rspec.Spec{}
- processStruct := &rspec.Process{
- Args: []string{"/bin/sh", "a", "b"},
- }
- for _, a := range []bool{true, false} {
- always := a
- for _, o := range []bool{true, false} {
- or := o
- for _, p := range []*rspec.Process{processStruct, nil} {
- process := p
- t.Run(fmt.Sprintf("always %t, or %t, has process %t", always, or, process != nil), func(t *testing.T) {
- config.Process = process
- when := When{Always: &always, Or: or}
- match, err := when.Match(config, map[string]string{}, false)
- if err != nil {
- t.Fatal(err)
- }
- assert.Equal(t, always, match)
- })
- }
- }
- }
-}
-
-func TestHasBindMountsAnd(t *testing.T) {
- hasBindMounts := true
- when := When{HasBindMounts: &hasBindMounts}
- config := &rspec.Spec{}
- for _, b := range []bool{false, true} {
- containerHasBindMounts := b
- t.Run(fmt.Sprintf("%t", containerHasBindMounts), func(t *testing.T) {
- match, err := when.Match(config, map[string]string{}, containerHasBindMounts)
- if err != nil {
- t.Fatal(err)
- }
- assert.Equal(t, containerHasBindMounts, match)
- })
- }
-}
-
-func TestHasBindMountsOr(t *testing.T) {
- hasBindMounts := true
- when := When{HasBindMounts: &hasBindMounts, Or: true}
- config := &rspec.Spec{}
- for _, b := range []bool{false, true} {
- containerHasBindMounts := b
- t.Run(fmt.Sprintf("%t", containerHasBindMounts), func(t *testing.T) {
- match, err := when.Match(config, map[string]string{}, containerHasBindMounts)
- if err != nil {
- t.Fatal(err)
- }
- assert.Equal(t, containerHasBindMounts, match)
- })
- }
-}
-
-func TestAnnotations(t *testing.T) {
- when := When{
- Annotations: map[string]string{
- "^a$": "^b$",
- "^c$": "^d$",
- },
- }
- config := &rspec.Spec{}
- for _, tt := range []struct {
- name string
- annotations map[string]string
- or bool
- match bool
- }{
- {
- name: "matching both, and",
- annotations: map[string]string{
- "a": "b",
- "c": "d",
- "e": "f",
- },
- or: false,
- match: true,
- },
- {
- name: "matching one, and",
- annotations: map[string]string{
- "a": "b",
- },
- or: false,
- match: false,
- },
- {
- name: "matching one, or",
- annotations: map[string]string{
- "a": "b",
- },
- or: true,
- match: true,
- },
- {
- name: "key-only, or",
- annotations: map[string]string{
- "a": "bc",
- },
- or: true,
- match: false,
- },
- {
- name: "value-only, or",
- annotations: map[string]string{
- "ac": "b",
- },
- or: true,
- match: false,
- },
- } {
- test := tt
- t.Run(test.name, func(t *testing.T) {
- when.Or = test.or
- match, err := when.Match(config, test.annotations, false)
- if err != nil {
- t.Fatal(err)
- }
- assert.Equal(t, test.match, match)
- })
- }
-}
-
-func TestCommands(t *testing.T) {
- when := When{
- Commands: []string{
- "^/bin/sh$",
- },
- }
- config := &rspec.Spec{}
- for _, tt := range []struct {
- name string
- process *rspec.Process
- match bool
- }{
- {
- name: "good",
- process: &rspec.Process{
- Args: []string{"/bin/sh", "a", "b"},
- },
- match: true,
- },
- {
- name: "extra characters",
- process: &rspec.Process{
- Args: []string{"/bin/shell", "a", "b"},
- },
- match: false,
- },
- {
- name: "process unset",
- match: false,
- },
- } {
- test := tt
- t.Run(test.name, func(t *testing.T) {
- config.Process = test.process
- match, err := when.Match(config, map[string]string{}, false)
- if err != nil {
- t.Fatal(err)
- }
- assert.Equal(t, test.match, match)
- })
- }
-}
-
-func TestCommandsEmptyProcessArgs(t *testing.T) {
- when := When{
- Commands: []string{
- "^/bin/sh$",
- },
- }
- config := &rspec.Spec{
- Process: &rspec.Process{},
- }
- _, err := when.Match(config, map[string]string{}, false)
- if err == nil {
- t.Fatal("unexpected success")
- }
- assert.Regexp(t, "^process\\.args must have at least one entry$", err.Error())
-}
-
-func TestHasBindMountsAndCommands(t *testing.T) {
- hasBindMounts := true
- when := When{
- HasBindMounts: &hasBindMounts,
- Commands: []string{
- "^/bin/sh$",
- },
- }
- config := &rspec.Spec{Process: &rspec.Process{}}
- for _, tt := range []struct {
- name string
- command string
- hasBindMounts bool
- or bool
- match bool
- }{
- {
- name: "both, and",
- command: "/bin/sh",
- hasBindMounts: true,
- or: false,
- match: true,
- },
- {
- name: "both, or",
- command: "/bin/sh",
- hasBindMounts: true,
- or: true,
- match: true,
- },
- {
- name: "bind, and",
- command: "/bin/shell",
- hasBindMounts: true,
- or: false,
- match: false,
- },
- {
- name: "bind, or",
- command: "/bin/shell",
- hasBindMounts: true,
- or: true,
- match: true,
- },
- {
- name: "command, and",
- command: "/bin/sh",
- hasBindMounts: false,
- or: false,
- match: false,
- },
- {
- name: "command, or",
- command: "/bin/sh",
- hasBindMounts: false,
- or: true,
- match: true,
- },
- {
- name: "neither, and",
- command: "/bin/shell",
- hasBindMounts: false,
- or: false,
- match: false,
- },
- {
- name: "neither, or",
- command: "/bin/shell",
- hasBindMounts: false,
- or: true,
- match: false,
- },
- } {
- test := tt
- t.Run(test.name, func(t *testing.T) {
- config.Process.Args = []string{test.command}
- when.Or = test.or
- match, err := when.Match(config, map[string]string{}, test.hasBindMounts)
- if err != nil {
- t.Fatal(err)
- }
- assert.Equal(t, test.match, match)
- })
- }
-}
-
-func TestInvalidRegexp(t *testing.T) {
- config := &rspec.Spec{Process: &rspec.Process{Args: []string{"/bin/sh"}}}
- for _, tt := range []struct {
- name string
- when When
- expected string
- }{
- {
- name: "invalid-annotation-key",
- when: When{Annotations: map[string]string{"[": "a"}},
- expected: "^annotation key: error parsing regexp: .*",
- },
- {
- name: "invalid-annotation-value",
- when: When{Annotations: map[string]string{"a": "["}},
- expected: "^annotation value: error parsing regexp: .*",
- },
- {
- name: "invalid-command",
- when: When{Commands: []string{"["}},
- expected: "^command: error parsing regexp: .*",
- },
- } {
- test := tt
- t.Run(test.name, func(t *testing.T) {
- _, err := test.when.Match(config, map[string]string{"a": "b"}, false)
- if err == nil {
- t.Fatal("unexpected success")
- }
- assert.Regexp(t, test.expected, err.Error())
- })
- }
-}
diff --git a/pkg/hooks/README.md b/pkg/hooks/README.md
deleted file mode 100644
index f6a03a775..000000000
--- a/pkg/hooks/README.md
+++ /dev/null
@@ -1,22 +0,0 @@
-# OCI Hooks Configuration
-
-For POSIX platforms, the [OCI runtime configuration][runtime-spec] supports [hooks][spec-hooks] for configuring custom actions related to the life cycle of the container.
-The way you enable the hooks above is by editing the OCI runtime configuration before running the OCI runtime (e.g. [`runc`][runc]).
-CRI-O and `podman create` create the OCI configuration for you, and this documentation allows developers to configure them to set their intended hooks.
-
-One problem with hooks is that the runtime actually stalls execution of the container before running the hooks and stalls completion of the container, until all hooks complete.
-This can cause some performance issues.
-Also a lot of hooks just check if certain configuration is set and then exit early, without doing anything.
-For example the [oci-systemd-hook][] only executes if the command is `init` or `systemd`, otherwise it just exits.
-This means if we automatically enabled all hooks, every container would have to execute `oci-systemd-hook`, even if they don't run systemd inside of the container.
-Performance would also suffer if we executed each hook at each stage ([pre-start][], [post-start][], and [post-stop][]).
-
-The hooks configuration is documented in [`oci-hooks.5`](docs/oci-hooks.5.md).
-
-[oci-systemd-hook]: https://github.com/projectatomic/oci-systemd-hook
-[post-start]: https://github.com/opencontainers/runtime-spec/blob/v1.0.1/config.md#poststart
-[post-stop]: https://github.com/opencontainers/runtime-spec/blob/v1.0.1/config.md#poststop
-[pre-start]: https://github.com/opencontainers/runtime-spec/blob/v1.0.1/config.md#prestart
-[runc]: https://github.com/opencontainers/runc
-[runtime-spec]: https://github.com/opencontainers/runtime-spec/blob/v1.0.1/spec.md
-[spec-hooks]: https://github.com/opencontainers/runtime-spec/blob/v1.0.1/config.md#posix-platform-hooks
diff --git a/pkg/hooks/docs/.gitignore b/pkg/hooks/docs/.gitignore
deleted file mode 100644
index 5d06cd466..000000000
--- a/pkg/hooks/docs/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-*.5
diff --git a/pkg/hooks/docs/oci-hooks.5.md b/pkg/hooks/docs/oci-hooks.5.md
deleted file mode 100644
index 9a1a35682..000000000
--- a/pkg/hooks/docs/oci-hooks.5.md
+++ /dev/null
@@ -1,182 +0,0 @@
-% oci-hooks 5 OCI Hooks Configuration
-% W. Trevor King
-% MAY 2018
-
-# NAME
-
-oci-hooks - OCI hooks configuration directories
-
-# SYNOPSIS
-
-`/usr/share/containers/oci/hooks.d/*.json`
-
-# DESCRIPTION
-
-Provides a way for users to configure the intended hooks for Open Container Initiative containers so they will only be executed for containers that need their functionality, and then only for the stages where they're needed.
-
-## Directories
-
-Hooks are configured with JSON files (ending with a `.json` extension) in a series of hook directories.
-The default directory is `/usr/share/containers/oci/hooks.d`, but tools consuming this format may change that default, include additional directories, or provide their callers with ways to adjust the configuration directories.
-
-If multiple directories are configured, a JSON filename in a preferred directory masks entries with the same filename in directories with lower precedence. For example, if a consuming tool watches for hooks in `/etc/containers/oci/hooks.d` and `/usr/share/containers/oci/hooks.d` (in order of decreasing precedence), then a hook definition in `/etc/containers/oci/hooks.d/01-my-hook.json` will mask any definition in `/usr/share/containers/oci/hooks.d/01-my-hook.json`.
-
-Tools consuming this format may also opt to monitor the hook directories for changes, in which case they will notice additions, changes, and removals to JSON files without needing to be restarted or otherwise signaled. When the tool monitors multiple hooks directories, the precedence discussed in the previous paragraph still applies. For example, if a consuming tool watches for hooks in `/etc/containers/oci/hooks.d` and `/usr/share/containers/oci/hooks.d` (in order of decreasing precedence), then writing a new hook definition to `/etc/containers/oci/hooks.d/01-my-hook.json` will mask the hook previously loaded from `/usr/share/containers/oci/hooks.d/01-my-hook.json`. Subsequent changes to `/usr/share/containers/oci/hooks.d/01-my-hook.json` will have no effect on the consuming tool as long as `/etc/containers/oci/hooks.d/01-my-hook.json` exists. Removing `/etc/containers/oci/hooks.d/01-my-hook.json` will reload the hook from `/usr/share/containers/oci/hooks.d/01-my-hook.json`.
-
-Hooks are injected in the order obtained by sorting the JSON file names, after converting them to lower case, based on their Unicode code points.
-For example, a matching hook defined in `01-my-hook.json` would be injected before matching hooks defined in `02-another-hook.json` and `01-UPPERCASE.json`.
-It is strongly recommended to make the sort order unambiguous depending on an ASCII-only prefix (like the `01`/`02` above).
-
-Each JSON file should contain an object with one of the following schemas.
-
-## 1.0.0 Hook Schema
-
-`version` (required string)
- Sets the hook-definition version. For this schema version, the value be `1.0.0`.
-
-`hook` (required object)
- The hook to inject, with the hook-entry schema defined by the 1.0.1 OCI Runtime Specification.
-
-`when` (required object)
- Conditions under which the hook is injected. The following properties can be specified, and at least one must be specified:
-
- * `always` (optional boolean)
- If set `true`, this condition matches.
- * `annotations` (optional object)
- If all `annotations` key/value pairs match a key/value pair from the configured annotations, this condition matches.
- Both keys and values must be POSIX extended regular expressions.
- * `commands` (optional array of strings)
- If the configured `process.args[0]` matches an entry, this condition matches.
- Entries must be POSIX extended regular expressions.
- * `hasBindMounts` (optional boolean)
- If `hasBindMounts` is true and the caller requested host-to-container bind mounts, this condition matches.
-
-`stages` (required array of strings)
- Stages when the hook must be injected. Entries must be chosen from the 1.0.1 OCI Runtime Specification hook stages or from extension stages supported by the package consumer.
-
-If *all* of the conditions set in `when` match, then the `hook` must be injected for the stages set in `stages`.
-
-## 0.1.0 Hook Schema
-
-`hook` (required string)
- Sets `path` in the injected hook.
-
-`arguments` (optional array of strings)
- Additional arguments to pass to the hook. The injected hook's `args` is `hook` with `arguments` appended.
-
-`stages` (required array of strings)
- Stages when the hook must be injected. `stage` is an allowed synonym for this property, but you must not set both `stages` and `stage`. Entries must be chosen from the 1.0.1 OCI Runtime Specification hook stages or from extension stages supported by the package consumer.
-
-`cmds` (optional array of strings)
- The hook must be injected if the configured `process.args[0]` matches an entry. `cmd` is an allowed synonym for this property, but you must not set both `cmds` and `cmd`. Entries must be POSIX extended regular expressions.
-
-`annotations` (optional array of strings)
- The hook must be injected if an `annotations` entry matches a value from the configured annotations. `annotation` is an allowed synonym for this property, but you must not set both `annotations` and `annotation`. Entries must be POSIX extended regular expressions.
-
-`hasbindmounts` (optional boolean)
- The hook must be injected if `hasBindMounts` is true and the caller requested host-to-container bind mounts.
-
-# EXAMPLE
-
-## 1.0.0 Hook Schema
-
-The following configuration injects `oci-systemd-hook` in the pre-start and post-stop stages if `process.args[0]` ends with `/init` or `/systemd`:
-
-```console
-$ cat /etc/containers/oci/hooks.d/oci-systemd-hook.json
-{
- "version": "1.0.0",
- "hook": {
- "path": "/usr/libexec/oci/hooks.d/oci-systemd-hook"
- },
- "when": {
- "commands": [".*/init$" , ".*/systemd$"]
- },
- "stages": ["prestart", "poststop"]
-}
-```
-
-The following example injects `oci-umount --debug` in the pre-start stage if the container is configured to bind-mount host directories into the container.
-
-```console
-$ cat /etc/containers/oci/hooks.d/oci-umount.json
-{
- "version": "1.0.0",
- "hook": {
- "path": "/usr/libexec/oci/hooks.d/oci-umount",
- "args": ["oci-umount", "--debug"],
- },
- "when": {
- "hasBindMounts": true
- },
- "stages": ["prestart"]
-}
-```
-
-The following example injects `nvidia-container-runtime-hook prestart` with particular environment variables in the pre-start stage if the container is configured with an `annotations` entry whose key matches `^com\.example\.department$` and whose value matches `.*fluid-dynamics.*`.
-
-```console
-$ cat /etc/containers/oci/hooks.d/nvidia.json
-{
- "version": "1.0.0",
- "hook": {
- "path": "/usr/sbin/nvidia-container-runtime-hook",
- "args": ["nvidia-container-runtime-hook", "prestart"],
- "env": [
- "NVIDIA_REQUIRE_CUDA=cuda>=9.1",
- "NVIDIA_VISIBLE_DEVICES=GPU-fef8089b"
- ]
- },
- "when": {
- "annotations": {
- "^com\\.example\\.department$": ".*fluid-dynamics$"
- }
- },
- "stages": ["prestart"]
-}
-```
-
-## 0.1.0 Hook Schema
-
-The following configuration injects `oci-systemd-hook` in the pre-start and post-stop stages if `process.args[0]` ends with `/init` or `/systemd`:
-
-```console
-$ cat /etc/containers/oci/hooks.d/oci-systemd-hook.json
-{
- "cmds": [".*/init$" , ".*/systemd$"],
- "hook": "/usr/libexec/oci/hooks.d/oci-systemd-hook",
- "stages": ["prestart", "poststop"]
-}
-```
-
-The following example injects `oci-umount --debug` in the pre-start stage if the container is configured to bind-mount host directories into the container.
-
-```console
-$ cat /etc/containers/oci/hooks.d/oci-umount.json
-{
- "hook": "/usr/libexec/oci/hooks.d/oci-umount",
- "arguments": ["--debug"],
- "hasbindmounts": true,
- "stages": ["prestart"]
-}
-```
-
-The following example injects `nvidia-container-runtime-hook prestart` in the pre-start stage if the container is configured with an `annotations` entry whose value matches `.*fluid-dynamics.*`.
-
-```console
-$ cat /etc/containers/oci/hooks.d/osystemd-hook.json
-{
- "hook": "/usr/sbin/nvidia-container-runtime-hook",
- "arguments": ["prestart"],
- "annotations: [".*fluid-dynamics.*"],
- "stages": ["prestart"]
-}
-```
-
-# SEE ALSO
-
-`oci-systemd-hook(1)`, `oci-umount(1)`, `locale(7)`
-
-* [OCI Runtime Specification, 1.0.1, POSIX-platform hooks](https://github.com/opencontainers/runtime-spec/blob/v1.0.1/config.md#posix-platform-hooks)
-* [OCI Runtime Specification, 1.0.1, process](https://github.com/opencontainers/runtime-spec/blob/v1.0.1/config.md#process)
-* [POSIX extended regular expressions (EREs)](https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap09.html#tag_09_04)
diff --git a/pkg/hooks/exec/exec.go b/pkg/hooks/exec/exec.go
deleted file mode 100644
index bc639245f..000000000
--- a/pkg/hooks/exec/exec.go
+++ /dev/null
@@ -1,69 +0,0 @@
-// Package exec provides utilities for executing Open Container Initiative runtime hooks.
-package exec
-
-import (
- "bytes"
- "context"
- "fmt"
- "io"
- osexec "os/exec"
- "time"
-
- rspec "github.com/opencontainers/runtime-spec/specs-go"
- "github.com/sirupsen/logrus"
-)
-
-// DefaultPostKillTimeout is the recommended default post-kill timeout.
-const DefaultPostKillTimeout = time.Duration(10) * time.Second
-
-// Run executes the hook and waits for it to complete or for the
-// context or hook-specified timeout to expire.
-func Run(ctx context.Context, hook *rspec.Hook, state []byte, stdout io.Writer, stderr io.Writer, postKillTimeout time.Duration) (hookErr, err error) {
- cmd := osexec.Cmd{
- Path: hook.Path,
- Args: hook.Args,
- Env: hook.Env,
- Stdin: bytes.NewReader(state),
- Stdout: stdout,
- Stderr: stderr,
- }
- if cmd.Env == nil {
- cmd.Env = []string{}
- }
-
- if hook.Timeout != nil {
- var cancel context.CancelFunc
- ctx, cancel = context.WithTimeout(ctx, time.Duration(*hook.Timeout)*time.Second)
- defer cancel()
- }
-
- err = cmd.Start()
- if err != nil {
- return err, err
- }
- exit := make(chan error, 1)
- go func() {
- err := cmd.Wait()
- if err != nil {
- err = fmt.Errorf("executing %v: %w", cmd.Args, err)
- }
- exit <- err
- }()
-
- select {
- case err = <-exit:
- return err, err
- case <-ctx.Done():
- if err := cmd.Process.Kill(); err != nil {
- logrus.Errorf("Failed to kill pid %v", cmd.Process)
- }
- timer := time.NewTimer(postKillTimeout)
- defer timer.Stop()
- select {
- case <-timer.C:
- err = fmt.Errorf("failed to reap process within %s of the kill signal", postKillTimeout)
- case err = <-exit:
- }
- return err, ctx.Err()
- }
-}
diff --git a/pkg/hooks/exec/exec_test.go b/pkg/hooks/exec/exec_test.go
deleted file mode 100644
index 1e105373d..000000000
--- a/pkg/hooks/exec/exec_test.go
+++ /dev/null
@@ -1,222 +0,0 @@
-package exec
-
-import (
- "bytes"
- "context"
- "fmt"
- "os"
- "runtime"
- "strings"
- "testing"
- "time"
-
- rspec "github.com/opencontainers/runtime-spec/specs-go"
- "github.com/stretchr/testify/assert"
-)
-
-// path is the path to an example hook executable.
-var path string
-
-// unavoidableEnvironmentKeys may be injected even if the hook
-// executable is executed with a requested empty environment.
-var unavoidableEnvironmentKeys []string
-
-func TestRun(t *testing.T) {
- ctx := context.Background()
- hook := &rspec.Hook{
- Path: path,
- Args: []string{"sh", "-c", "cat"},
- }
- var stderr, stdout bytes.Buffer
- hookErr, err := Run(ctx, hook, []byte("{}"), &stdout, &stderr, DefaultPostKillTimeout)
- if err != nil {
- t.Fatal(err)
- }
- if hookErr != nil {
- t.Fatal(hookErr)
- }
- assert.Equal(t, "{}", stdout.String())
- assert.Equal(t, "", stderr.String())
-}
-
-func TestRunIgnoreOutput(t *testing.T) {
- ctx := context.Background()
- hook := &rspec.Hook{
- Path: path,
- Args: []string{"sh", "-c", "cat"},
- }
- hookErr, err := Run(ctx, hook, []byte("{}"), nil, nil, DefaultPostKillTimeout)
- if err != nil {
- t.Fatal(err)
- }
- if hookErr != nil {
- t.Fatal(hookErr)
- }
-}
-
-func TestRunFailedStart(t *testing.T) {
- ctx := context.Background()
- hook := &rspec.Hook{
- Path: "/does/not/exist",
- }
- hookErr, err := Run(ctx, hook, []byte("{}"), nil, nil, DefaultPostKillTimeout)
- if err == nil {
- t.Fatal("unexpected success")
- }
- if !os.IsNotExist(err) {
- t.Fatal(err)
- }
- assert.Equal(t, err, hookErr)
-}
-
-func parseEnvironment(input string) (env map[string]string, err error) {
- env = map[string]string{}
- lines := strings.Split(input, "\n")
- for i, line := range lines {
- if line == "" && i == len(lines)-1 {
- continue // no content after the terminal newline
- }
- keyValue := strings.SplitN(line, "=", 2)
- if len(keyValue) < 2 {
- return env, fmt.Errorf("no = in environment line: %q", line)
- }
- env[keyValue[0]] = keyValue[1]
- }
- for _, key := range unavoidableEnvironmentKeys {
- delete(env, key)
- }
- return env, nil
-}
-
-func TestRunEnvironment(t *testing.T) {
- ctx := context.Background()
- hook := &rspec.Hook{
- Path: path,
- Args: []string{"sh", "-c", "env"},
- }
- for _, tt := range []struct {
- name string
- env []string
- expected map[string]string
- }{
- {
- name: "unset",
- expected: map[string]string{},
- },
- {
- name: "set empty",
- env: []string{},
- expected: map[string]string{},
- },
- {
- name: "set",
- env: []string{
- "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
- "TERM=xterm",
- },
- expected: map[string]string{
- "PATH": "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
- "TERM": "xterm",
- },
- },
- } {
- test := tt
- t.Run(test.name, func(t *testing.T) {
- var stderr, stdout bytes.Buffer
- hook.Env = test.env
- hookErr, err := Run(ctx, hook, []byte("{}"), &stdout, &stderr, DefaultPostKillTimeout)
- if err != nil {
- t.Fatal(err)
- }
- if hookErr != nil {
- t.Fatal(hookErr)
- }
- assert.Equal(t, "", stderr.String())
-
- env, err := parseEnvironment(stdout.String())
- if err != nil {
- t.Fatal(err)
- }
- assert.Equal(t, test.expected, env)
- })
- }
-}
-
-func TestRunCancel(t *testing.T) {
- hook := &rspec.Hook{
- Path: path,
- Args: []string{"sh", "-c", "echo waiting; sleep 2; echo done"},
- }
- one := 1
- for _, tt := range []struct {
- name string
- contextTimeout time.Duration
- hookTimeout *int
- expectedHookError string
- expectedRunError error
- expectedStdout string
- }{
- {
- name: "no timeouts",
- expectedStdout: "waiting\ndone\n",
- },
- {
- name: "context timeout",
- contextTimeout: time.Duration(1) * time.Second,
- expectedStdout: "waiting\n",
- expectedHookError: "^executing \\[sh -c echo waiting; sleep 2; echo done]: signal: killed$",
- expectedRunError: context.DeadlineExceeded,
- },
- {
- name: "hook timeout",
- hookTimeout: &one,
- expectedStdout: "waiting\n",
- expectedHookError: "^executing \\[sh -c echo waiting; sleep 2; echo done]: signal: killed$",
- expectedRunError: context.DeadlineExceeded,
- },
- } {
- test := tt
- t.Run(test.name, func(t *testing.T) {
- ctx := context.Background()
- var stderr, stdout bytes.Buffer
- if test.contextTimeout > 0 {
- var cancel context.CancelFunc
- ctx, cancel = context.WithTimeout(ctx, test.contextTimeout)
- defer cancel()
- }
- hook.Timeout = test.hookTimeout
- hookErr, err := Run(ctx, hook, []byte("{}"), &stdout, &stderr, DefaultPostKillTimeout)
- assert.Equal(t, test.expectedRunError, err)
- if test.expectedHookError == "" {
- if hookErr != nil {
- t.Fatal(hookErr)
- }
- } else {
- assert.Regexp(t, test.expectedHookError, hookErr.Error())
- }
- assert.Equal(t, "", stderr.String())
- assert.Equal(t, test.expectedStdout, stdout.String())
- })
- }
-}
-
-func TestRunKillTimeout(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Duration(500)*time.Millisecond)
- defer cancel()
- hook := &rspec.Hook{
- Path: path,
- Args: []string{"sh", "-c", "sleep 1"},
- }
- hookErr, err := Run(ctx, hook, []byte("{}"), nil, nil, time.Duration(0))
- assert.Equal(t, context.DeadlineExceeded, err)
- assert.Regexp(t, "^(failed to reap process within 0s of the kill signal|executing \\[sh -c sleep 1]: signal: killed)$", hookErr)
-}
-
-func init() {
- if runtime.GOOS != "windows" {
- path = "/bin/sh"
- unavoidableEnvironmentKeys = []string{"PWD", "SHLVL", "_"}
- } else {
- panic("we need a reliable executable path on Windows")
- }
-}
diff --git a/pkg/hooks/exec/runtimeconfigfilter.go b/pkg/hooks/exec/runtimeconfigfilter.go
deleted file mode 100644
index 72d4b8979..000000000
--- a/pkg/hooks/exec/runtimeconfigfilter.go
+++ /dev/null
@@ -1,72 +0,0 @@
-package exec
-
-import (
- "bytes"
- "context"
- "encoding/json"
- "fmt"
- "reflect"
- "time"
-
- "github.com/davecgh/go-spew/spew"
- spec "github.com/opencontainers/runtime-spec/specs-go"
- "github.com/pmezard/go-difflib/difflib"
- "github.com/sirupsen/logrus"
-)
-
-var spewConfig = spew.ConfigState{
- Indent: " ",
- DisablePointerAddresses: true,
- DisableCapacities: true,
- SortKeys: true,
-}
-
-// RuntimeConfigFilter calls a series of hooks. But instead of
-// passing container state on their standard input,
-// RuntimeConfigFilter passes the proposed runtime configuration (and
-// reads back a possibly-altered form from their standard output).
-func RuntimeConfigFilter(ctx context.Context, hooks []spec.Hook, config *spec.Spec, postKillTimeout time.Duration) (hookErr, err error) {
- data, err := json.Marshal(config)
- if err != nil {
- return nil, err
- }
- for i, hook := range hooks {
- hook := hook
- var stdout bytes.Buffer
- hookErr, err = Run(ctx, &hook, data, &stdout, nil, postKillTimeout)
- if err != nil {
- return hookErr, err
- }
-
- data = stdout.Bytes()
- var newConfig spec.Spec
- err = json.Unmarshal(data, &newConfig)
- if err != nil {
- logrus.Debugf("invalid JSON from config-filter hook %d:\n%s", i, string(data))
- return nil, fmt.Errorf("unmarshal output from config-filter hook %d: %w", i, err)
- }
-
- if !reflect.DeepEqual(config, &newConfig) {
- oldConfig := spewConfig.Sdump(config)
- newConfig := spewConfig.Sdump(&newConfig)
- diff, err := difflib.GetUnifiedDiffString(difflib.UnifiedDiff{
- A: difflib.SplitLines(oldConfig),
- B: difflib.SplitLines(newConfig),
- FromFile: "Old",
- FromDate: "",
- ToFile: "New",
- ToDate: "",
- Context: 1,
- })
- if err == nil {
- logrus.Debugf("precreate hook %d made configuration changes:\n%s", i, diff)
- } else {
- logrus.Warnf("Precreate hook %d made configuration changes, but we could not compute a diff: %v", i, err)
- }
- }
-
- *config = newConfig
- }
-
- return nil, nil
-}
diff --git a/pkg/hooks/exec/runtimeconfigfilter_test.go b/pkg/hooks/exec/runtimeconfigfilter_test.go
deleted file mode 100644
index a4e9b1fdb..000000000
--- a/pkg/hooks/exec/runtimeconfigfilter_test.go
+++ /dev/null
@@ -1,265 +0,0 @@
-package exec
-
-import (
- "context"
- "encoding/json"
- "errors"
- "os"
- "testing"
- "time"
-
- spec "github.com/opencontainers/runtime-spec/specs-go"
- "github.com/stretchr/testify/assert"
-)
-
-func TestRuntimeConfigFilter(t *testing.T) {
- unexpectedEndOfJSONInput := json.Unmarshal([]byte("{\n"), nil) //nolint:govet // this should force the error
- fileMode := os.FileMode(0600)
- rootUint32 := uint32(0)
- binUser := int(1)
- for _, tt := range []struct {
- name string
- contextTimeout time.Duration
- hooks []spec.Hook
- input *spec.Spec
- expected *spec.Spec
- expectedHookError string
- expectedRunError error
- expectedRunErrorString string
- }{
- {
- name: "no-op",
- hooks: []spec.Hook{
- {
- Path: path,
- Args: []string{"sh", "-c", "cat"},
- },
- },
- input: &spec.Spec{
- Version: "1.0.0",
- Root: &spec.Root{
- Path: "rootfs",
- },
- },
- expected: &spec.Spec{
- Version: "1.0.0",
- Root: &spec.Root{
- Path: "rootfs",
- },
- },
- },
- {
- name: "device injection",
- hooks: []spec.Hook{
- {
- Path: path,
- Args: []string{"sh", "-c", `sed 's|\("gid":0}\)|\1,{"path": "/dev/sda","type":"b","major":8,"minor":0,"fileMode":384,"uid":0,"gid":0}|'`},
- },
- },
- input: &spec.Spec{
- Version: "1.0.0",
- Root: &spec.Root{
- Path: "rootfs",
- },
- Linux: &spec.Linux{
- Devices: []spec.LinuxDevice{
- {
- Path: "/dev/fuse",
- Type: "c",
- Major: 10,
- Minor: 229,
- FileMode: &fileMode,
- UID: &rootUint32,
- GID: &rootUint32,
- },
- },
- },
- },
- expected: &spec.Spec{
- Version: "1.0.0",
- Root: &spec.Root{
- Path: "rootfs",
- },
- Linux: &spec.Linux{
- Devices: []spec.LinuxDevice{
- {
- Path: "/dev/fuse",
- Type: "c",
- Major: 10,
- Minor: 229,
- FileMode: &fileMode,
- UID: &rootUint32,
- GID: &rootUint32,
- },
- {
- Path: "/dev/sda",
- Type: "b",
- Major: 8,
- Minor: 0,
- FileMode: &fileMode,
- UID: &rootUint32,
- GID: &rootUint32,
- },
- },
- },
- },
- },
- {
- name: "chaining",
- hooks: []spec.Hook{
- {
- Path: path,
- Args: []string{"sh", "-c", `sed 's|\("gid":0}\)|\1,{"path": "/dev/sda","type":"b","major":8,"minor":0,"fileMode":384,"uid":0,"gid":0}|'`},
- },
- {
- Path: path,
- Args: []string{"sh", "-c", `sed 's|/dev/sda|/dev/sdb|'`},
- },
- },
- input: &spec.Spec{
- Version: "1.0.0",
- Root: &spec.Root{
- Path: "rootfs",
- },
- Linux: &spec.Linux{
- Devices: []spec.LinuxDevice{
- {
- Path: "/dev/fuse",
- Type: "c",
- Major: 10,
- Minor: 229,
- FileMode: &fileMode,
- UID: &rootUint32,
- GID: &rootUint32,
- },
- },
- },
- },
- expected: &spec.Spec{
- Version: "1.0.0",
- Root: &spec.Root{
- Path: "rootfs",
- },
- Linux: &spec.Linux{
- Devices: []spec.LinuxDevice{
- {
- Path: "/dev/fuse",
- Type: "c",
- Major: 10,
- Minor: 229,
- FileMode: &fileMode,
- UID: &rootUint32,
- GID: &rootUint32,
- },
- {
- Path: "/dev/sdb",
- Type: "b",
- Major: 8,
- Minor: 0,
- FileMode: &fileMode,
- UID: &rootUint32,
- GID: &rootUint32,
- },
- },
- },
- },
- },
- {
- name: "context timeout",
- contextTimeout: time.Duration(1) * time.Second,
- hooks: []spec.Hook{
- {
- Path: path,
- Args: []string{"sh", "-c", "sleep 2"},
- },
- },
- input: &spec.Spec{
- Version: "1.0.0",
- Root: &spec.Root{
- Path: "rootfs",
- },
- },
- expected: &spec.Spec{
- Version: "1.0.0",
- Root: &spec.Root{
- Path: "rootfs",
- },
- },
- expectedHookError: "^executing \\[sh -c sleep 2]: signal: killed$",
- expectedRunError: context.DeadlineExceeded,
- },
- {
- name: "hook timeout",
- hooks: []spec.Hook{
- {
- Path: path,
- Args: []string{"sh", "-c", "sleep 2"},
- Timeout: &binUser,
- },
- },
- input: &spec.Spec{
- Version: "1.0.0",
- Root: &spec.Root{
- Path: "rootfs",
- },
- },
- expected: &spec.Spec{
- Version: "1.0.0",
- Root: &spec.Root{
- Path: "rootfs",
- },
- },
- expectedHookError: "^executing \\[sh -c sleep 2]: signal: killed$",
- expectedRunError: context.DeadlineExceeded,
- },
- {
- name: "invalid JSON",
- hooks: []spec.Hook{
- {
- Path: path,
- Args: []string{"sh", "-c", "echo '{'"},
- },
- },
- input: &spec.Spec{
- Version: "1.0.0",
- Root: &spec.Root{
- Path: "rootfs",
- },
- },
- expected: &spec.Spec{
- Version: "1.0.0",
- Root: &spec.Root{
- Path: "rootfs",
- },
- },
- expectedRunError: unexpectedEndOfJSONInput,
- expectedRunErrorString: unexpectedEndOfJSONInput.Error(),
- },
- } {
- test := tt
- t.Run(test.name, func(t *testing.T) {
- ctx := context.Background()
- if test.contextTimeout > 0 {
- var cancel context.CancelFunc
- ctx, cancel = context.WithTimeout(ctx, test.contextTimeout)
- defer cancel()
- }
- hookErr, err := RuntimeConfigFilter(ctx, test.hooks, test.input, DefaultPostKillTimeout)
- if test.expectedRunError != nil {
- if test.expectedRunErrorString != "" {
- assert.Contains(t, err.Error(), test.expectedRunErrorString)
- } else {
- assert.True(t, errors.Is(err, test.expectedRunError))
- }
- }
- if test.expectedHookError == "" {
- if hookErr != nil {
- t.Fatal(hookErr)
- }
- } else {
- assert.Regexp(t, test.expectedHookError, hookErr.Error())
- }
- assert.Equal(t, test.expected, test.input)
- })
- }
-}
diff --git a/pkg/hooks/hooks.go b/pkg/hooks/hooks.go
deleted file mode 100644
index 14f98b1de..000000000
--- a/pkg/hooks/hooks.go
+++ /dev/null
@@ -1,145 +0,0 @@
-// Package hooks implements hook configuration and handling for CRI-O and libpod.
-package hooks
-
-import (
- "context"
- "fmt"
- "os"
- "sort"
- "strings"
- "sync"
-
- current "github.com/containers/podman/v4/pkg/hooks/1.0.0"
- rspec "github.com/opencontainers/runtime-spec/specs-go"
- "github.com/sirupsen/logrus"
-)
-
-// Version is the current hook configuration version.
-const Version = current.Version
-
-const (
- // DefaultDir is the default directory containing system hook configuration files.
- DefaultDir = "/usr/share/containers/oci/hooks.d"
-
- // OverrideDir is the directory for hook configuration files overriding the default entries.
- OverrideDir = "/etc/containers/oci/hooks.d"
-)
-
-// Manager provides an opaque interface for managing CRI-O hooks.
-type Manager struct {
- hooks map[string]*current.Hook
- directories []string
- extensionStages []string
- lock sync.Mutex
-}
-
-type namedHook struct {
- name string
- hook *current.Hook
-}
-
-// New creates a new hook manager. Directories are ordered by
-// increasing preference (hook configurations in later directories
-// override configurations with the same filename from earlier
-// directories).
-//
-// extensionStages allows callers to add additional stages beyond
-// those specified in the OCI Runtime Specification and to control
-// OCI-defined stages instead of delegating to the OCI runtime. See
-// Hooks() for more information.
-func New(ctx context.Context, directories []string, extensionStages []string) (manager *Manager, err error) {
- manager = &Manager{
- hooks: map[string]*current.Hook{},
- directories: directories,
- extensionStages: extensionStages,
- }
-
- for _, dir := range directories {
- err = ReadDir(dir, manager.extensionStages, manager.hooks)
- if err != nil && !os.IsNotExist(err) {
- return nil, err
- }
- }
-
- return manager, nil
-}
-
-// filenames returns sorted hook entries.
-func (m *Manager) namedHooks() (hooks []*namedHook) {
- m.lock.Lock()
- defer m.lock.Unlock()
-
- hooks = make([]*namedHook, len(m.hooks))
- i := 0
- for name, hook := range m.hooks {
- hooks[i] = &namedHook{
- name: name,
- hook: hook,
- }
- i++
- }
-
- return hooks
-}
-
-// Hooks injects OCI runtime hooks for a given container configuration.
-//
-// If extensionStages was set when initializing the Manager,
-// matching hooks requesting those stages will be returned in
-// extensionStageHooks. This takes precedence over their inclusion in
-// the OCI configuration. For example:
-//
-// manager, err := New(ctx, []string{DefaultDir}, []string{"poststop"})
-// extensionStageHooks, err := manager.Hooks(config, annotations, hasBindMounts)
-//
-// will have any matching post-stop hooks in extensionStageHooks and
-// will not insert them into config.Hooks.Poststop.
-func (m *Manager) Hooks(config *rspec.Spec, annotations map[string]string, hasBindMounts bool) (extensionStageHooks map[string][]rspec.Hook, err error) {
- hooks := m.namedHooks()
- sort.Slice(hooks, func(i, j int) bool { return strings.ToLower(hooks[i].name) < strings.ToLower(hooks[j].name) })
- localStages := map[string]bool{} // stages destined for extensionStageHooks
- for _, stage := range m.extensionStages {
- localStages[stage] = true
- }
- for _, namedHook := range hooks {
- match, err := namedHook.hook.When.Match(config, annotations, hasBindMounts)
- if err != nil {
- return extensionStageHooks, fmt.Errorf("matching hook %q: %w", namedHook.name, err)
- }
- if match {
- logrus.Debugf("hook %s matched; adding to stages %v", namedHook.name, namedHook.hook.Stages)
- if config.Hooks == nil {
- config.Hooks = &rspec.Hooks{}
- }
- for _, stage := range namedHook.hook.Stages {
- if _, ok := localStages[stage]; ok {
- if extensionStageHooks == nil {
- extensionStageHooks = map[string][]rspec.Hook{}
- }
- extensionStageHooks[stage] = append(extensionStageHooks[stage], namedHook.hook.Hook)
- } else {
- switch stage {
- case "createContainer":
- config.Hooks.CreateContainer = append(config.Hooks.CreateContainer, namedHook.hook.Hook)
- case "createRuntime":
- config.Hooks.CreateRuntime = append(config.Hooks.CreateRuntime, namedHook.hook.Hook)
- case "prestart":
- config.Hooks.Prestart = append(config.Hooks.Prestart, namedHook.hook.Hook)
- case "poststart":
- config.Hooks.Poststart = append(config.Hooks.Poststart, namedHook.hook.Hook)
- case "poststop":
- config.Hooks.Poststop = append(config.Hooks.Poststop, namedHook.hook.Hook)
- case "startContainer":
- config.Hooks.StartContainer = append(config.Hooks.StartContainer, namedHook.hook.Hook)
- default:
- return extensionStageHooks, fmt.Errorf("hook %q: unknown stage %q", namedHook.name, stage)
- }
- }
- }
- } else {
- logrus.Debugf("hook %s did not match", namedHook.name)
- }
- }
-
- return extensionStageHooks, nil
-}
diff --git a/pkg/hooks/hooks_test.go b/pkg/hooks/hooks_test.go
deleted file mode 100644
index d5d0c2a32..000000000
--- a/pkg/hooks/hooks_test.go
+++ /dev/null
@@ -1,218 +0,0 @@
-package hooks
-
-import (
- "context"
- "fmt"
- "io/ioutil"
- "path/filepath"
- "runtime"
- "testing"
-
- current "github.com/containers/podman/v4/pkg/hooks/1.0.0"
- rspec "github.com/opencontainers/runtime-spec/specs-go"
- "github.com/stretchr/testify/assert"
-)
-
-// path is the path to an example hook executable.
-var path string
-
-func TestGoodNew(t *testing.T) {
- ctx := context.Background()
-
- dir := t.TempDir()
-
- for i, name := range []string{
- "01-my-hook.json",
- "01-UPPERCASE.json",
- "02-another-hook.json",
- } {
- jsonPath := filepath.Join(dir, name)
- var extraStages string
- if i == 0 {
- extraStages = ", \"poststart\", \"poststop\""
- }
- err := ioutil.WriteFile(jsonPath, []byte(fmt.Sprintf("{\"version\": \"1.0.0\", \"hook\": {\"path\": \"%s\", \"timeout\": %d}, \"when\": {\"always\": true}, \"stages\": [\"prestart\"%s]}", path, i+1, extraStages)), 0644)
- if err != nil {
- t.Fatal(err)
- }
- }
-
- manager, err := New(ctx, []string{dir}, []string{})
- if err != nil {
- t.Fatal(err)
- }
-
- config := &rspec.Spec{}
- extensionStageHooks, err := manager.Hooks(config, map[string]string{}, false)
- if err != nil {
- t.Fatal(err)
- }
-
- one := 1
- two := 2
- three := 3
- assert.Equal(t, &rspec.Hooks{
- Prestart: []rspec.Hook{
- {
- Path: path,
- Timeout: &one,
- },
- {
- Path: path,
- Timeout: &two,
- },
- {
- Path: path,
- Timeout: &three,
- },
- },
- Poststart: []rspec.Hook{
- {
- Path: path,
- Timeout: &one,
- },
- },
- Poststop: []rspec.Hook{
- {
- Path: path,
- Timeout: &one,
- },
- },
- }, config.Hooks)
-
- var nilExtensionStageHooks map[string][]rspec.Hook
- assert.Equal(t, nilExtensionStageHooks, extensionStageHooks)
-}
-
-func TestBadNew(t *testing.T) {
- ctx := context.Background()
-
- dir := t.TempDir()
-
- jsonPath := filepath.Join(dir, "a.json")
- err := ioutil.WriteFile(jsonPath, []byte("{\"version\": \"-1\"}"), 0644)
- if err != nil {
- t.Fatal(err)
- }
-
- _, err = New(ctx, []string{dir}, []string{})
- if err == nil {
- t.Fatal("unexpected success")
- }
- assert.Regexp(t, "^parsing hook \"[^\"]*a.json\": unrecognized hook version: \"-1\"$", err.Error())
-}
-
-func TestBrokenMatch(t *testing.T) {
- manager := Manager{
- hooks: map[string]*current.Hook{
- "a.json": {
- Version: current.Version,
- Hook: rspec.Hook{
- Path: "/a/b/c",
- },
- When: current.When{
- Commands: []string{"["},
- },
- Stages: []string{"prestart"},
- },
- },
- }
- config := &rspec.Spec{
- Process: &rspec.Process{
- Args: []string{"/bin/sh"},
- },
- }
- extensionStageHooks, err := manager.Hooks(config, map[string]string{}, false)
- if err == nil {
- t.Fatal("unexpected success")
- }
- assert.Regexp(t, "^matching hook \"a\\.json\": command: error parsing regexp: .*", err.Error())
-
- var nilExtensionStageHooks map[string][]rspec.Hook
- assert.Equal(t, nilExtensionStageHooks, extensionStageHooks)
-}
-
-func TestInvalidStage(t *testing.T) {
- always := true
- manager := Manager{
- hooks: map[string]*current.Hook{
- "a.json": {
- Version: current.Version,
- Hook: rspec.Hook{
- Path: "/a/b/c",
- },
- When: current.When{
- Always: &always,
- },
- Stages: []string{"does-not-exist"},
- },
- },
- }
- extensionStageHooks, err := manager.Hooks(&rspec.Spec{}, map[string]string{}, false)
- if err == nil {
- t.Fatal("unexpected success")
- }
- assert.Regexp(t, "^hook \"a\\.json\": unknown stage \"does-not-exist\"$", err.Error())
-
- var nilExtensionStageHooks map[string][]rspec.Hook
- assert.Equal(t, nilExtensionStageHooks, extensionStageHooks)
-}
-
-func TestExtensionStage(t *testing.T) {
- always := true
- manager := Manager{
- hooks: map[string]*current.Hook{
- "a.json": {
- Version: current.Version,
- Hook: rspec.Hook{
- Path: "/a/b/c",
- },
- When: current.When{
- Always: &always,
- },
- Stages: []string{"prestart", "poststop", "a", "b"},
- },
- },
- extensionStages: []string{"poststop", "a", "b", "c"},
- }
-
- config := &rspec.Spec{}
- extensionStageHooks, err := manager.Hooks(config, map[string]string{}, false)
- if err != nil {
- t.Fatal(err)
- }
-
- assert.Equal(t, &rspec.Hooks{
- Prestart: []rspec.Hook{
- {
- Path: "/a/b/c",
- },
- },
- }, config.Hooks)
-
- assert.Equal(t, map[string][]rspec.Hook{
- "poststop": {
- {
- Path: "/a/b/c",
- },
- },
- "a": {
- {
- Path: "/a/b/c",
- },
- },
- "b": {
- {
- Path: "/a/b/c",
- },
- },
- }, extensionStageHooks)
-}
-
-func init() {
- if runtime.GOOS != "windows" {
- path = "/bin/sh"
- } else {
- panic("we need a reliable executable path on Windows")
- }
-}
diff --git a/pkg/hooks/monitor.go b/pkg/hooks/monitor.go
deleted file mode 100644
index d2d7140a5..000000000
--- a/pkg/hooks/monitor.go
+++ /dev/null
@@ -1,66 +0,0 @@
-package hooks
-
-import (
- "context"
-
- current "github.com/containers/podman/v4/pkg/hooks/1.0.0"
- "github.com/fsnotify/fsnotify"
- "github.com/sirupsen/logrus"
-)
-
-// Monitor dynamically monitors hook directories for additions,
-// updates, and removals.
-//
-// This function writes two empty structs to the sync channel: the
-// first is written after the watchers are established and the second
-// when this function exits. The expected usage is:
-//
-// ctx, cancel := context.WithCancel(context.Background())
-// sync := make(chan error, 2)
-// go m.Monitor(ctx, sync)
-// err := <-sync // block until writers are established
-// if err != nil {
-// return err // failed to establish watchers
-// }
-// // do stuff
-// cancel()
-// err = <-sync // block until monitor finishes
-func (m *Manager) Monitor(ctx context.Context, sync chan<- error) {
- watcher, err := fsnotify.NewWatcher()
- if err != nil {
- sync <- err
- return
- }
- defer watcher.Close()
-
- for _, dir := range m.directories {
- err = watcher.Add(dir)
- if err != nil {
- logrus.Errorf("Failed to watch %q for hooks", dir)
- sync <- err
- return
- }
- logrus.Debugf("monitoring %q for hooks", dir)
- }
-
- sync <- nil
-
- for {
- select {
- case event := <-watcher.Events:
- m.hooks = make(map[string]*current.Hook)
- for _, dir := range m.directories {
- err = ReadDir(dir, m.extensionStages, m.hooks)
- if err != nil {
- logrus.Errorf("Failed loading hooks for %s: %v", event.Name, err)
- }
- }
- case <-ctx.Done():
- err = ctx.Err()
- logrus.Debugf("hook monitoring canceled: %v", err)
- sync <- err
- close(sync)
- return
- }
- }
-}
diff --git a/pkg/hooks/monitor_test.go b/pkg/hooks/monitor_test.go
deleted file mode 100644
index 1067d2920..000000000
--- a/pkg/hooks/monitor_test.go
+++ /dev/null
@@ -1,324 +0,0 @@
-package hooks
-
-import (
- "context"
- "fmt"
- "io/ioutil"
- "os"
- "path/filepath"
- "testing"
- "time"
-
- rspec "github.com/opencontainers/runtime-spec/specs-go"
- "github.com/stretchr/testify/assert"
-)
-
-func TestMonitorOneDirGood(t *testing.T) {
- ctx, cancel := context.WithCancel(context.Background())
- dir := t.TempDir()
-
- manager, err := New(ctx, []string{dir}, []string{})
- if err != nil {
- t.Fatal(err)
- }
-
- sync := make(chan error, 2)
- go manager.Monitor(ctx, sync)
- err = <-sync
- if err != nil {
- t.Fatal(err)
- }
-
- jsonPath := filepath.Join(dir, "a.json")
-
- t.Run("good-addition", func(t *testing.T) {
- err = ioutil.WriteFile(jsonPath, []byte(fmt.Sprintf("{\"version\": \"1.0.0\", \"hook\": {\"path\": \"%s\"}, \"when\": {\"always\": true}, \"stages\": [\"prestart\", \"poststart\", \"poststop\"]}", path)), 0644)
- if err != nil {
- t.Fatal(err)
- }
-
- time.Sleep(100 * time.Millisecond) // wait for monitor to notice
-
- config := &rspec.Spec{}
- _, err = manager.Hooks(config, map[string]string{}, false)
- if err != nil {
- t.Fatal(err)
- }
-
- assert.Equal(t, &rspec.Hooks{
- Prestart: []rspec.Hook{
- {
- Path: path,
- },
- },
- Poststart: []rspec.Hook{
- {
- Path: path,
- },
- },
- Poststop: []rspec.Hook{
- {
- Path: path,
- },
- },
- }, config.Hooks)
- })
-
- t.Run("good-removal", func(t *testing.T) {
- err = os.Remove(jsonPath)
- if err != nil {
- t.Fatal(err)
- }
-
- time.Sleep(100 * time.Millisecond) // wait for monitor to notice
-
- config := &rspec.Spec{}
- expected := config.Hooks
- _, err = manager.Hooks(config, map[string]string{}, false)
- if err != nil {
- t.Fatal(err)
- }
- assert.Equal(t, expected, config.Hooks)
- })
-
- t.Run("bad-addition", func(t *testing.T) {
- err = ioutil.WriteFile(jsonPath, []byte("{\"version\": \"-1\"}"), 0644)
- if err != nil {
- t.Fatal(err)
- }
-
- time.Sleep(100 * time.Millisecond) // wait for monitor to notice
-
- config := &rspec.Spec{}
- expected := config.Hooks
- _, err = manager.Hooks(config, map[string]string{}, false)
- if err != nil {
- t.Fatal(err)
- }
- assert.Equal(t, expected, config.Hooks)
-
- err = os.Remove(jsonPath)
- if err != nil {
- t.Fatal(err)
- }
- })
-
- cancel()
- err = <-sync
- assert.Equal(t, context.Canceled, err)
-}
-
-func TestMonitorTwoDirGood(t *testing.T) {
- ctx, cancel := context.WithCancel(context.Background())
- primaryDir := t.TempDir()
- fallbackDir := t.TempDir()
-
- manager, err := New(ctx, []string{fallbackDir, primaryDir}, []string{})
- if err != nil {
- t.Fatal(err)
- }
-
- sync := make(chan error, 2)
- go manager.Monitor(ctx, sync)
- err = <-sync
- if err != nil {
- t.Fatal(err)
- }
-
- fallbackPath := filepath.Join(fallbackDir, "a.json")
- fallbackJSON := []byte(fmt.Sprintf("{\"version\": \"1.0.0\", \"hook\": {\"path\": \"%s\"}, \"when\": {\"always\": true}, \"stages\": [\"prestart\"]}", path))
- fallbackInjected := &rspec.Hooks{
- Prestart: []rspec.Hook{
- {
- Path: path,
- },
- },
- }
-
- t.Run("good-fallback-addition", func(t *testing.T) {
- err = ioutil.WriteFile(fallbackPath, fallbackJSON, 0644)
- if err != nil {
- t.Fatal(err)
- }
-
- time.Sleep(100 * time.Millisecond) // wait for monitor to notice
-
- config := &rspec.Spec{}
- _, err = manager.Hooks(config, map[string]string{}, false)
- if err != nil {
- t.Fatal(err)
- }
-
- assert.Equal(t, fallbackInjected, config.Hooks)
- })
-
- primaryPath := filepath.Join(primaryDir, "a.json")
- primaryJSON := []byte(fmt.Sprintf("{\"version\": \"1.0.0\", \"hook\": {\"path\": \"%s\", \"timeout\": 1}, \"when\": {\"always\": true}, \"stages\": [\"prestart\"]}", path))
- one := 1
- primaryInjected := &rspec.Hooks{
- Prestart: []rspec.Hook{
- {
- Path: path,
- Timeout: &one,
- },
- },
- }
-
- t.Run("good-primary-override", func(t *testing.T) {
- err = ioutil.WriteFile(primaryPath, primaryJSON, 0644)
- if err != nil {
- t.Fatal(err)
- }
-
- time.Sleep(100 * time.Millisecond) // wait for monitor to notice
-
- config := &rspec.Spec{}
- _, err = manager.Hooks(config, map[string]string{}, false)
- if err != nil {
- t.Fatal(err)
- }
-
- assert.Equal(t, primaryInjected, config.Hooks)
- })
-
- t.Run("good-fallback-removal", func(t *testing.T) {
- err = os.Remove(fallbackPath)
- if err != nil {
- t.Fatal(err)
- }
-
- time.Sleep(100 * time.Millisecond) // wait for monitor to notice
-
- config := &rspec.Spec{}
- _, err = manager.Hooks(config, map[string]string{}, false)
- if err != nil {
- t.Fatal(err)
- }
- assert.Equal(t, primaryInjected, config.Hooks) // masked by primary
- })
-
- t.Run("good-fallback-restore", func(t *testing.T) {
- err = ioutil.WriteFile(fallbackPath, fallbackJSON, 0644)
- if err != nil {
- t.Fatal(err)
- }
-
- time.Sleep(100 * time.Millisecond) // wait for monitor to notice
-
- config := &rspec.Spec{}
- _, err = manager.Hooks(config, map[string]string{}, false)
- if err != nil {
- t.Fatal(err)
- }
- assert.Equal(t, primaryInjected, config.Hooks) // masked by primary
- })
-
- primaryPath2 := filepath.Join(primaryDir, "0a.json") // 0a because it will be before a.json alphabetically
-
- t.Run("bad-primary-new-addition", func(t *testing.T) {
- err = ioutil.WriteFile(primaryPath2, []byte("{\"version\": \"-1\"}"), 0644)
- if err != nil {
- t.Fatal(err)
- }
-
- time.Sleep(100 * time.Millisecond) // wait for monitor to notice
-
- config := &rspec.Spec{}
- fmt.Println("expected: ", config.Hooks)
- expected := primaryInjected // 0a.json is bad, a.json is still good
- _, err = manager.Hooks(config, map[string]string{}, false)
- fmt.Println("actual: ", config.Hooks)
- if err != nil {
- t.Fatal(err)
- }
- assert.Equal(t, expected, config.Hooks)
- })
-
- t.Run("bad-primary-same-addition", func(t *testing.T) {
- err = ioutil.WriteFile(primaryPath, []byte("{\"version\": \"-1\"}"), 0644)
- if err != nil {
- t.Fatal(err)
- }
-
- time.Sleep(100 * time.Millisecond) // wait for monitor to notice
-
- config := &rspec.Spec{}
- expected := fallbackInjected
- _, err = manager.Hooks(config, map[string]string{}, false)
- if err != nil {
- t.Fatal(err)
- }
- assert.Equal(t, expected, config.Hooks)
- })
-
- t.Run("good-primary-removal", func(t *testing.T) {
- err = os.Remove(primaryPath)
- if err != nil {
- t.Fatal(err)
- }
-
- time.Sleep(100 * time.Millisecond) // wait for monitor to notice
-
- config := &rspec.Spec{}
- _, err = manager.Hooks(config, map[string]string{}, false)
- if err != nil {
- t.Fatal(err)
- }
- assert.Equal(t, fallbackInjected, config.Hooks)
- })
-
- t.Run("good-non-json-addition", func(t *testing.T) {
- err = ioutil.WriteFile(filepath.Join(fallbackDir, "README"), []byte("Hello"), 0644)
- if err != nil {
- t.Fatal(err)
- }
-
- time.Sleep(100 * time.Millisecond) // wait for monitor to notice
-
- config := &rspec.Spec{}
- _, err = manager.Hooks(config, map[string]string{}, false)
- if err != nil {
- t.Fatal(err)
- }
-
- assert.Equal(t, fallbackInjected, config.Hooks)
- })
-
- t.Run("good-fallback-removal", func(t *testing.T) {
- err = os.Remove(fallbackPath)
- if err != nil {
- t.Fatal(err)
- }
-
- time.Sleep(100 * time.Millisecond) // wait for monitor to notice
-
- config := &rspec.Spec{}
- expected := config.Hooks
- _, err = manager.Hooks(config, map[string]string{}, false)
- if err != nil {
- t.Fatal(err)
- }
- assert.Equal(t, expected, config.Hooks)
- })
-
- cancel()
- err = <-sync
- assert.Equal(t, context.Canceled, err)
-}
-
-func TestMonitorBadWatcher(t *testing.T) {
- ctx := context.Background()
-
- manager, err := New(ctx, []string{}, []string{})
- if err != nil {
- t.Fatal(err)
- }
- manager.directories = []string{"/does/not/exist"}
-
- sync := make(chan error, 2)
- go manager.Monitor(ctx, sync)
- err = <-sync
- if !os.IsNotExist(err) {
- t.Fatal("opaque wrapping for not-exist errors")
- }
-}
diff --git a/pkg/hooks/read.go b/pkg/hooks/read.go
deleted file mode 100644
index 379ed67ef..000000000
--- a/pkg/hooks/read.go
+++ /dev/null
@@ -1,101 +0,0 @@
-// Package hooks implements CRI-O's hook handling.
-package hooks
-
-import (
- "encoding/json"
- "errors"
- "fmt"
- "io/ioutil"
- "os"
- "path/filepath"
- "strings"
-
- old "github.com/containers/podman/v4/pkg/hooks/0.1.0"
- current "github.com/containers/podman/v4/pkg/hooks/1.0.0"
- "github.com/sirupsen/logrus"
-)
-
-type reader func(content []byte) (*current.Hook, error)
-
-var (
- // ErrNoJSONSuffix represents hook-add attempts where the filename
- // does not end in '.json'.
- ErrNoJSONSuffix = errors.New("hook filename does not end in '.json'")
-
- // Readers registers per-version hook readers.
- Readers = map[string]reader{}
-)
-
-// Read reads a hook JSON file, verifies it, and returns the hook configuration.
-func Read(path string, extensionStages []string) (*current.Hook, error) {
- if !strings.HasSuffix(path, ".json") {
- return nil, ErrNoJSONSuffix
- }
- content, err := ioutil.ReadFile(path)
- if err != nil {
- return nil, err
- }
- hook, err := read(content)
- if err != nil {
- return nil, fmt.Errorf("parsing hook %q: %w", path, err)
- }
- err = hook.Validate(extensionStages)
- return hook, err
-}
-
-func read(content []byte) (hook *current.Hook, err error) {
- var ver version
- if err := json.Unmarshal(content, &ver); err != nil {
- return nil, fmt.Errorf("version check: %w", err)
- }
- reader, ok := Readers[ver.Version]
- if !ok {
- return nil, fmt.Errorf("unrecognized hook version: %q", ver.Version)
- }
-
- hook, err = reader(content)
- if err != nil {
- return hook, fmt.Errorf("%v: %w", ver.Version, err)
- }
- return hook, err
-}
-
-// ReadDir reads hook JSON files from a directory into the given map,
-// clobbering any previous entries with the same filenames.
-func ReadDir(path string, extensionStages []string, hooks map[string]*current.Hook) error {
- logrus.Debugf("reading hooks from %s", path)
- files, err := ioutil.ReadDir(path)
- if err != nil {
- return err
- }
- res := err
- for _, file := range files {
- filePath := filepath.Join(path, file.Name())
- hook, err := Read(filePath, extensionStages)
- if err != nil {
- if err == ErrNoJSONSuffix {
- continue
- }
- if os.IsNotExist(err) {
- if err2, ok := err.(*os.PathError); ok && err2.Path == filePath {
- continue
- }
- }
- if res == nil {
- res = err
- } else {
- res = fmt.Errorf("%v: %w", err, res)
- }
- continue
- }
- hooks[file.Name()] = hook
- logrus.Debugf("added hook %s", filePath)
- }
- return res
-}
-
-func init() {
- Readers[current.Version] = current.Read
- Readers[old.Version] = old.Read
- Readers[""] = old.Read
-}
diff --git a/pkg/hooks/read_test.go b/pkg/hooks/read_test.go
deleted file mode 100644
index 381d66bbe..000000000
--- a/pkg/hooks/read_test.go
+++ /dev/null
@@ -1,194 +0,0 @@
-package hooks
-
-import (
- "fmt"
- "io/ioutil"
- "os"
- "path/filepath"
- "testing"
-
- current "github.com/containers/podman/v4/pkg/hooks/1.0.0"
- rspec "github.com/opencontainers/runtime-spec/specs-go"
- "github.com/stretchr/testify/assert"
-)
-
-func TestNoJSONSuffix(t *testing.T) {
- _, err := Read("abc", []string{})
- assert.Equal(t, err, ErrNoJSONSuffix)
-}
-
-func TestUnknownPath(t *testing.T) {
- _, err := Read(filepath.Join("does", "not", "exist.json"), []string{})
- if err == nil {
- t.Fatal("unexpected success")
- }
- assert.Regexp(t, "^open does/not/exist.json: no such file or directory$", err.Error())
- if !os.IsNotExist(err) {
- t.Fatal("opaque wrapping for not-exist errors")
- }
-}
-
-func TestGoodFile(t *testing.T) {
- dir := t.TempDir()
-
- jsonPath := filepath.Join(dir, "hook.json")
- err := ioutil.WriteFile(jsonPath, []byte(fmt.Sprintf("{\"version\": \"1.0.0\", \"hook\": {\"path\": \"%s\"}, \"when\": {\"always\": true}, \"stages\": [\"prestart\"]}", path)), 0644)
- if err != nil {
- t.Fatal(err)
- }
-
- hook, err := Read(jsonPath, []string{})
- if err != nil {
- t.Fatal(err)
- }
- always := true
- assert.Equal(t, &current.Hook{
- Version: current.Version,
- Hook: rspec.Hook{
- Path: path,
- },
- When: current.When{
- Always: &always,
- },
- Stages: []string{"prestart"},
- }, hook)
-}
-
-func TestBadFile(t *testing.T) {
- dir := t.TempDir()
-
- path := filepath.Join(dir, "hook.json")
- err := ioutil.WriteFile(path, []byte("{\"version\": \"1.0.0\", \"hook\": \"not-a-string\"}"), 0644)
- if err != nil {
- t.Fatal(err)
- }
-
- _, err = Read(path, []string{})
- if err == nil {
- t.Fatal("unexpected success")
- }
- assert.Regexp(t, "^parsing hook \"[^\"]*hook.json\": 1.0.0: json: cannot unmarshal string into Go struct field Hook.hook of type specs.Hook$", err.Error())
-}
-
-func TestGoodBytes(t *testing.T) {
- hook, err := read([]byte("{\"version\": \"1.0.0\", \"hook\": {\"path\": \"/a/b/c\"}, \"when\": {\"always\": true}, \"stages\": [\"prestart\"]}"))
- if err != nil {
- t.Fatal(err)
- }
- always := true
- assert.Equal(t, &current.Hook{
- Version: current.Version,
- Hook: rspec.Hook{
- Path: "/a/b/c",
- },
- When: current.When{
- Always: &always,
- },
- Stages: []string{"prestart"},
- }, hook)
-}
-
-func TestInvalidJSON(t *testing.T) {
- _, err := read([]byte("{"))
- if err == nil {
- t.Fatal("unexpected success")
- }
- assert.Regexp(t, "^version check: unexpected end of JSON input$", err.Error())
-}
-
-func TestInvalidVersion(t *testing.T) {
- _, err := read([]byte("{\"version\": \"-1\"}"))
- if err == nil {
- t.Fatal("unexpected success")
- }
- assert.Regexp(t, "^unrecognized hook version: \"-1\"$", err.Error())
-}
-
-func TestInvalidCurrentJSON(t *testing.T) {
- _, err := read([]byte("{\"version\": \"1.0.0\", \"hook\": \"not-a-string\"}"))
- if err == nil {
- t.Fatal("unexpected success")
- }
- assert.Regexp(t, "^1.0.0: json: cannot unmarshal string into Go struct field Hook.hook of type specs.Hook$", err.Error())
-}
-
-func TestGoodDir(t *testing.T) {
- dir := t.TempDir()
-
- err := ioutil.WriteFile(filepath.Join(dir, "README"), []byte("not a hook"), 0644)
- if err != nil {
- t.Fatal(err)
- }
-
- jsonPath := filepath.Join(dir, "a.json")
- err = ioutil.WriteFile(jsonPath, []byte(fmt.Sprintf("{\"version\": \"1.0.0\", \"hook\": {\"path\": \"%s\"}, \"when\": {\"always\": true}, \"stages\": [\"prestart\"]}", path)), 0644)
- if err != nil {
- t.Fatal(err)
- }
-
- hooks := map[string]*current.Hook{}
- err = ReadDir(dir, []string{}, hooks)
- if err != nil {
- t.Fatal(err)
- }
-
- always := true
- assert.Equal(t, map[string]*current.Hook{
- "a.json": {
- Version: current.Version,
- Hook: rspec.Hook{
- Path: path,
- },
- When: current.When{
- Always: &always,
- },
- Stages: []string{"prestart"},
- },
- }, hooks)
-}
-
-func TestUnknownDir(t *testing.T) {
- hooks := map[string]*current.Hook{}
- err := ReadDir(filepath.Join("does", "not", "exist"), []string{}, hooks)
- if err == nil {
- t.Fatal("unexpected success")
- }
- assert.Regexp(t, "^open does/not/exist: no such file or directory$", err.Error())
- if !os.IsNotExist(err) {
- t.Fatal("opaque wrapping for not-exist errors")
- }
-}
-
-func TestBadDir(t *testing.T) {
- dir := t.TempDir()
-
- jsonPath := filepath.Join(dir, "a.json")
- err := ioutil.WriteFile(jsonPath, []byte("{\"version\": \"-1\"}"), 0644)
- if err != nil {
- t.Fatal(err)
- }
-
- hooks := map[string]*current.Hook{}
- err = ReadDir(dir, []string{}, hooks)
- if err == nil {
- t.Fatal("unexpected success")
- }
- assert.Regexp(t, "^parsing hook \"[^\"]*a.json\": unrecognized hook version: \"-1\"$", err.Error())
-}
-
-func TestHookExecutableDoesNotExit(t *testing.T) {
- dir := t.TempDir()
-
- jsonPath := filepath.Join(dir, "hook.json")
- err := ioutil.WriteFile(jsonPath, []byte("{\"version\": \"1.0.0\", \"hook\": {\"path\": \"/does/not/exist\"}, \"when\": {\"always\": true}, \"stages\": [\"prestart\"]}"), 0644)
- if err != nil {
- t.Fatal(err)
- }
-
- hooks := map[string]*current.Hook{}
- err = ReadDir(dir, []string{}, hooks)
- if err == nil {
- t.Fatal("unexpected success")
- }
- assert.Regexp(t, "^stat /does/not/exist: no such file or directory$", err.Error())
-}
diff --git a/pkg/hooks/version.go b/pkg/hooks/version.go
deleted file mode 100644
index 637d8e2f4..000000000
--- a/pkg/hooks/version.go
+++ /dev/null
@@ -1,6 +0,0 @@
-package hooks
-
-// version a structure for checking the version of a hook configuration.
-type version struct {
- Version string `json:"version"`
-}
diff --git a/pkg/machine/e2e/basic_test.go b/pkg/machine/e2e/basic_test.go
index f67fb4c67..da0310485 100644
--- a/pkg/machine/e2e/basic_test.go
+++ b/pkg/machine/e2e/basic_test.go
@@ -1,4 +1,4 @@
-package e2e
+package e2e_test
import (
. "github.com/onsi/ginkgo"
@@ -20,7 +20,7 @@ var _ = Describe("run basic podman commands", func() {
})
It("Basic ops", func() {
- name := randomString(12)
+ name := randomString()
i := new(initMachine)
session, err := mb.setName(name).setCmd(i.withImagePath(mb.imagePath).withNow()).run()
Expect(err).To(BeNil())
diff --git a/pkg/machine/e2e/config_basic.go b/pkg/machine/e2e/config_basic_test.go
index be0896156..d1cb24174 100644
--- a/pkg/machine/e2e/config_basic.go
+++ b/pkg/machine/e2e/config_basic_test.go
@@ -1,8 +1,7 @@
-package e2e
+package e2e_test
type basicMachine struct {
args []string
- cmd []string
}
func (s basicMachine) buildCmd(m *machineTestBuilder) []string {
diff --git a/pkg/machine/e2e/config_info.go b/pkg/machine/e2e/config_info_test.go
index 410c7e518..4da40ab99 100644
--- a/pkg/machine/e2e/config_info.go
+++ b/pkg/machine/e2e/config_info_test.go
@@ -1,4 +1,4 @@
-package e2e
+package e2e_test
type infoMachine struct {
format string
diff --git a/pkg/machine/e2e/config_init.go b/pkg/machine/e2e/config_init_test.go
index 7f18cce7d..d6c7990b0 100644
--- a/pkg/machine/e2e/config_init.go
+++ b/pkg/machine/e2e/config_init_test.go
@@ -1,4 +1,4 @@
-package e2e
+package e2e_test
import (
"strconv"
@@ -25,7 +25,7 @@ type initMachine struct {
memory *uint
now bool
timezone string
- rootful bool
+ rootful bool //nolint:unused,structcheck
volumes []string
cmd []string
@@ -71,7 +71,7 @@ func (i *initMachine) withDiskSize(size uint) *initMachine {
return i
}
-func (i *initMachine) withIgnitionPath(path string) *initMachine {
+func (i *initMachine) withIgnitionPath(path string) *initMachine { //nolint:unused
i.ignitionPath = path
return i
}
diff --git a/pkg/machine/e2e/config_inspect.go b/pkg/machine/e2e/config_inspect_test.go
index 74c9a5d9c..ffd74220f 100644
--- a/pkg/machine/e2e/config_inspect.go
+++ b/pkg/machine/e2e/config_inspect_test.go
@@ -1,4 +1,4 @@
-package e2e
+package e2e_test
type inspectMachine struct {
/*
diff --git a/pkg/machine/e2e/config_list.go b/pkg/machine/e2e/config_list_test.go
index 150f984bc..78f9edc62 100644
--- a/pkg/machine/e2e/config_list.go
+++ b/pkg/machine/e2e/config_list_test.go
@@ -1,4 +1,4 @@
-package e2e
+package e2e_test
type listMachine struct {
/*
diff --git a/pkg/machine/e2e/config_rm.go b/pkg/machine/e2e/config_rm_test.go
index 6cf262a22..1f9c9b4ec 100644
--- a/pkg/machine/e2e/config_rm.go
+++ b/pkg/machine/e2e/config_rm_test.go
@@ -1,4 +1,4 @@
-package e2e
+package e2e_test
type rmMachine struct {
/*
@@ -40,17 +40,17 @@ func (i *rmMachine) withForce() *rmMachine {
return i
}
-func (i *rmMachine) withSaveIgnition() *rmMachine {
+func (i *rmMachine) withSaveIgnition() *rmMachine { //nolint:unused
i.saveIgnition = true
return i
}
-func (i *rmMachine) withSaveImage() *rmMachine {
+func (i *rmMachine) withSaveImage() *rmMachine { //nolint:unused
i.saveImage = true
return i
}
-func (i *rmMachine) withSaveKeys() *rmMachine {
+func (i *rmMachine) withSaveKeys() *rmMachine { //nolint:unused
i.saveKeys = true
return i
}
diff --git a/pkg/machine/e2e/config_set.go b/pkg/machine/e2e/config_set_test.go
index b310ab1b9..3c773b970 100644
--- a/pkg/machine/e2e/config_set.go
+++ b/pkg/machine/e2e/config_set_test.go
@@ -1,4 +1,4 @@
-package e2e
+package e2e_test
import (
"strconv"
diff --git a/pkg/machine/e2e/config_ssh.go b/pkg/machine/e2e/config_ssh_test.go
index b09eed47d..f062625fa 100644
--- a/pkg/machine/e2e/config_ssh.go
+++ b/pkg/machine/e2e/config_ssh_test.go
@@ -1,14 +1,12 @@
-package e2e
+package e2e_test
type sshMachine struct {
/*
--username string Username to use when ssh-ing into the VM.
*/
- username string
+ username string //nolint:unused
sshCommand []string
-
- cmd []string
}
func (s sshMachine) buildCmd(m *machineTestBuilder) []string {
@@ -22,7 +20,7 @@ func (s sshMachine) buildCmd(m *machineTestBuilder) []string {
return cmd
}
-func (s *sshMachine) withUsername(name string) *sshMachine {
+func (s *sshMachine) withUsername(name string) *sshMachine { //nolint:unused
s.username = name
return s
}
diff --git a/pkg/machine/e2e/config_start.go b/pkg/machine/e2e/config_start_test.go
index 86b1721f8..d9efbf489 100644
--- a/pkg/machine/e2e/config_start.go
+++ b/pkg/machine/e2e/config_start_test.go
@@ -1,10 +1,9 @@
-package e2e
+package e2e_test
type startMachine struct {
/*
No command line args other than a machine vm name (also not required)
*/
- cmd []string
}
func (s startMachine) buildCmd(m *machineTestBuilder) []string {
diff --git a/pkg/machine/e2e/config_stop.go b/pkg/machine/e2e/config_stop_test.go
index 04dcfb524..41142ec7e 100644
--- a/pkg/machine/e2e/config_stop.go
+++ b/pkg/machine/e2e/config_stop_test.go
@@ -1,10 +1,9 @@
-package e2e
+package e2e_test
type stopMachine struct {
/*
No command line args other than a machine vm name (also not required)
*/
- cmd []string
}
func (s stopMachine) buildCmd(m *machineTestBuilder) []string {
diff --git a/pkg/machine/e2e/config.go b/pkg/machine/e2e/config_test.go
index b3fe74b0c..9940e711b 100644
--- a/pkg/machine/e2e/config.go
+++ b/pkg/machine/e2e/config_test.go
@@ -1,4 +1,4 @@
-package e2e
+package e2e_test
import (
"encoding/json"
@@ -10,13 +10,11 @@ import (
"time"
"github.com/containers/podman/v4/pkg/machine"
- "github.com/containers/podman/v4/pkg/machine/qemu"
"github.com/containers/podman/v4/pkg/util"
"github.com/containers/storage/pkg/stringid"
- . "github.com/onsi/ginkgo" //nolint:golint,stylecheck
+ . "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
- "github.com/onsi/gomega/gexec"
- . "github.com/onsi/gomega/gexec" //nolint:golint,stylecheck
+ . "github.com/onsi/gomega/gexec"
)
var originalHomeDir = os.Getenv("HOME")
@@ -36,7 +34,7 @@ type MachineTestBuilder interface {
run() (*machineSession, error)
}
type machineSession struct {
- *gexec.Session
+ *Session
}
type machineTestBuilder struct {
@@ -47,10 +45,6 @@ type machineTestBuilder struct {
podmanBinary string
timeout time.Duration
}
-type qemuMachineInspectInfo struct {
- State machine.Status
- VM qemu.MachineVM
-}
// waitWithTimeout waits for a command to complete for a given
// number of seconds
@@ -121,7 +115,7 @@ func (m *machineTestBuilder) setCmd(mc machineCommand) *machineTestBuilder {
// If no name for the machine exists, we set a random name.
if !util.StringInSlice(m.name, m.names) {
if len(m.name) < 1 {
- m.name = randomString(12)
+ m.name = randomString()
}
m.names = append(m.names, m.name)
}
@@ -136,10 +130,10 @@ func (m *machineTestBuilder) setTimeout(timeout time.Duration) *machineTestBuild
// toQemuInspectInfo is only for inspecting qemu machines. Other providers will need
// to make their own.
-func (mb *machineTestBuilder) toQemuInspectInfo() ([]machine.InspectInfo, int, error) {
+func (m *machineTestBuilder) toQemuInspectInfo() ([]machine.InspectInfo, int, error) {
args := []string{"machine", "inspect"}
- args = append(args, mb.names...)
- session, err := runWrapper(mb.podmanBinary, args, defaultTimeout, true)
+ args = append(args, m.names...)
+ session, err := runWrapper(m.podmanBinary, args, defaultTimeout, true)
if err != nil {
return nil, -1, err
}
@@ -175,9 +169,7 @@ func runWrapper(podmanBinary string, cmdArgs []string, timeout time.Duration, wa
return &ms, nil
}
-func (m *machineTestBuilder) init() {}
-
// randomString returns a string of given length composed of random characters
-func randomString(n int) string {
+func randomString() string {
return stringid.GenerateRandomID()[0:12]
}
diff --git a/pkg/machine/e2e/info_test.go b/pkg/machine/e2e/info_test.go
index eeabb78af..fe0cfba32 100644
--- a/pkg/machine/e2e/info_test.go
+++ b/pkg/machine/e2e/info_test.go
@@ -1,7 +1,7 @@
-package e2e
+package e2e_test
import (
- "github.com/containers/podman/v4/cmd/podman/machine"
+ "github.com/containers/podman/v4/pkg/domain/entities"
jsoniter "github.com/json-iterator/go"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
@@ -51,7 +51,7 @@ var _ = Describe("podman machine info", func() {
Expect(err).NotTo(HaveOccurred())
Expect(infoSession).Should(Exit(0))
- infoReport := &machine.Info{}
+ infoReport := &entities.MachineInfo{}
err = jsoniter.Unmarshal(infoSession.Bytes(), infoReport)
Expect(err).To(BeNil())
})
diff --git a/pkg/machine/e2e/init_test.go b/pkg/machine/e2e/init_test.go
index 40f140cae..b246dc4da 100644
--- a/pkg/machine/e2e/init_test.go
+++ b/pkg/machine/e2e/init_test.go
@@ -1,4 +1,4 @@
-package e2e
+package e2e_test
import (
"io/ioutil"
@@ -78,7 +78,7 @@ var _ = Describe("podman machine init", func() {
})
It("machine init with cpus, disk size, memory, timezone", func() {
- name := randomString(12)
+ name := randomString()
i := new(initMachine)
session, err := mb.setName(name).setCmd(i.withImagePath(mb.imagePath).withCPUs(2).withDiskSize(102).withMemory(4000).withTimezone("Pacific/Honolulu")).run()
Expect(err).To(BeNil())
@@ -108,7 +108,7 @@ var _ = Describe("podman machine init", func() {
switch runtime.GOOS {
// os's handle memory differently
case "linux":
- Expect(memorySession.outputToString()).To(ContainSubstring("3821"))
+ Expect(memorySession.outputToString()).To(ContainSubstring("3822"))
case "darwin":
Expect(memorySession.outputToString()).To(ContainSubstring("3824"))
default:
@@ -130,7 +130,7 @@ var _ = Describe("podman machine init", func() {
mount := tmpDir + ":/testmountdir"
defer os.RemoveAll(tmpDir)
- name := randomString(12)
+ name := randomString()
i := new(initMachine)
session, err := mb.setName(name).setCmd(i.withImagePath(mb.imagePath).withVolume(mount)).run()
Expect(err).To(BeNil())
diff --git a/pkg/machine/e2e/inspect_test.go b/pkg/machine/e2e/inspect_test.go
index 93fb8cc2b..0ab928205 100644
--- a/pkg/machine/e2e/inspect_test.go
+++ b/pkg/machine/e2e/inspect_test.go
@@ -1,4 +1,4 @@
-package e2e
+package e2e_test
import (
"strings"
@@ -52,15 +52,15 @@ var _ = Describe("podman machine stop", func() {
})
It("inspect with go format", func() {
- name := randomString(12)
+ name := randomString()
i := new(initMachine)
session, err := mb.setName(name).setCmd(i.withImagePath(mb.imagePath)).run()
Expect(err).To(BeNil())
Expect(session).To(Exit(0))
// regular inspect should
- inspectJson := new(inspectMachine)
- inspectSession, err := mb.setName(name).setCmd(inspectJson).run()
+ inspectJSON := new(inspectMachine)
+ inspectSession, err := mb.setName(name).setCmd(inspectJSON).run()
Expect(err).To(BeNil())
Expect(inspectSession).To(Exit(0))
diff --git a/pkg/machine/e2e/list_test.go b/pkg/machine/e2e/list_test.go
index 8b7443d47..5c7ae6c5e 100644
--- a/pkg/machine/e2e/list_test.go
+++ b/pkg/machine/e2e/list_test.go
@@ -1,4 +1,4 @@
-package e2e
+package e2e_test
import (
"strings"
@@ -45,8 +45,8 @@ var _ = Describe("podman machine list", func() {
It("list machines with quiet or noheading", func() {
// Random names for machines to test list
- name1 := randomString(12)
- name2 := randomString(12)
+ name1 := randomString()
+ name2 := randomString()
list := new(listMachine)
firstList, err := mb.setCmd(list.withQuiet()).run()
@@ -109,7 +109,7 @@ var _ = Describe("podman machine list", func() {
It("list with --format", func() {
// Random names for machines to test list
- name1 := randomString(12)
+ name1 := randomString()
i := new(initMachine)
session, err := mb.setName(name1).setCmd(i.withImagePath(mb.imagePath)).run()
diff --git a/pkg/machine/e2e/machine_test.go b/pkg/machine/e2e/machine_test.go
index 7b063937d..5de04b9f7 100644
--- a/pkg/machine/e2e/machine_test.go
+++ b/pkg/machine/e2e/machine_test.go
@@ -1,4 +1,4 @@
-package e2e
+package e2e_test
import (
"fmt"
@@ -57,7 +57,7 @@ var _ = BeforeSuite(func() {
Fail(fmt.Sprintf("unable to create url for download: %q", err))
}
now := time.Now()
- if err := machine.DownloadVMImage(getMe, fqImageName+".xz"); err != nil {
+ if err := machine.DownloadVMImage(getMe, suiteImageName, fqImageName+".xz"); err != nil {
Fail(fmt.Sprintf("unable to download machine image: %q", err))
}
fmt.Println("Download took: ", time.Since(now).String())
diff --git a/pkg/machine/e2e/rm_test.go b/pkg/machine/e2e/rm_test.go
index 43b8c594c..e33eaf702 100644
--- a/pkg/machine/e2e/rm_test.go
+++ b/pkg/machine/e2e/rm_test.go
@@ -1,4 +1,4 @@
-package e2e
+package e2e_test
import (
. "github.com/onsi/ginkgo"
diff --git a/pkg/machine/e2e/set_test.go b/pkg/machine/e2e/set_test.go
index 80cb89488..4839e33da 100644
--- a/pkg/machine/e2e/set_test.go
+++ b/pkg/machine/e2e/set_test.go
@@ -1,4 +1,4 @@
-package e2e
+package e2e_test
import (
"runtime"
@@ -22,7 +22,7 @@ var _ = Describe("podman machine set", func() {
})
It("set machine cpus, disk, memory", func() {
- name := randomString(12)
+ name := randomString()
i := new(initMachine)
session, err := mb.setName(name).setCmd(i.withImagePath(mb.imagePath)).run()
Expect(err).To(BeNil())
@@ -62,7 +62,7 @@ var _ = Describe("podman machine set", func() {
switch runtime.GOOS {
// it seems macos and linux handle memory differently
case "linux":
- Expect(memorySession.outputToString()).To(ContainSubstring("3821"))
+ Expect(memorySession.outputToString()).To(ContainSubstring("3822"))
case "darwin":
Expect(memorySession.outputToString()).To(ContainSubstring("3824"))
default:
@@ -75,7 +75,7 @@ var _ = Describe("podman machine set", func() {
})
It("no settings should change if no flags", func() {
- name := randomString(12)
+ name := randomString()
i := new(initMachine)
session, err := mb.setName(name).setCmd(i.withImagePath(mb.imagePath)).run()
Expect(err).To(BeNil())
diff --git a/pkg/machine/e2e/ssh_test.go b/pkg/machine/e2e/ssh_test.go
index 9ee31ac26..52d714c91 100644
--- a/pkg/machine/e2e/ssh_test.go
+++ b/pkg/machine/e2e/ssh_test.go
@@ -1,4 +1,4 @@
-package e2e
+package e2e_test
import (
. "github.com/onsi/ginkgo"
@@ -20,17 +20,16 @@ var _ = Describe("podman machine ssh", func() {
})
It("bad machine name", func() {
- name := randomString(12)
+ name := randomString()
ssh := sshMachine{}
session, err := mb.setName(name).setCmd(ssh).run()
Expect(err).To(BeNil())
Expect(session).To(Exit(125))
- // TODO seems like stderr is not being returned; re-enabled when fixed
- //Expect(session.outputToString()).To(ContainSubstring("not exist"))
+ Expect(session.errorToString()).To(ContainSubstring("not exist"))
})
It("ssh to non-running machine", func() {
- name := randomString(12)
+ name := randomString()
i := new(initMachine)
session, err := mb.setName(name).setCmd(i.withImagePath(mb.imagePath)).run()
Expect(err).To(BeNil())
@@ -39,13 +38,12 @@ var _ = Describe("podman machine ssh", func() {
ssh := sshMachine{}
sshSession, err := mb.setName(name).setCmd(ssh).run()
Expect(err).To(BeNil())
- // TODO seems like stderr is not being returned; re-enabled when fixed
- //Expect(sshSession.outputToString()).To(ContainSubstring("is not running"))
+ Expect(sshSession.errorToString()).To(ContainSubstring("is not running"))
Expect(sshSession).To(Exit(125))
})
It("ssh to running machine and check os-type", func() {
- name := randomString(12)
+ name := randomString()
i := new(initMachine)
session, err := mb.setName(name).setCmd(i.withImagePath(mb.imagePath).withNow()).run()
Expect(err).To(BeNil())
diff --git a/pkg/machine/e2e/start_test.go b/pkg/machine/e2e/start_test.go
index 1de66eb9a..1f9405569 100644
--- a/pkg/machine/e2e/start_test.go
+++ b/pkg/machine/e2e/start_test.go
@@ -1,4 +1,4 @@
-package e2e
+package e2e_test
import (
"github.com/containers/podman/v4/pkg/machine"
diff --git a/pkg/machine/e2e/stop_test.go b/pkg/machine/e2e/stop_test.go
index 0c27045a6..621bbdb16 100644
--- a/pkg/machine/e2e/stop_test.go
+++ b/pkg/machine/e2e/stop_test.go
@@ -1,4 +1,4 @@
-package e2e
+package e2e_test
import (
. "github.com/onsi/ginkgo"
diff --git a/pkg/machine/fedora.go b/pkg/machine/fedora.go
index 7ac4692e3..497265269 100644
--- a/pkg/machine/fedora.go
+++ b/pkg/machine/fedora.go
@@ -105,11 +105,11 @@ func getFedoraDownload(releaseURL string) (*url.URL, string, int64, error) {
return nil, "", -1, fmt.Errorf("get request failed: %s: %w", verURL.String(), err)
}
+ defer resp.Body.Close()
bytes, err := io.ReadAll(&io.LimitedReader{R: resp.Body, N: 1024})
if err != nil {
return nil, "", -1, fmt.Errorf("failed reading: %s: %w", verURL.String(), err)
}
- _ = resp.Body.Close()
return downloadURL, strings.TrimSpace(string(bytes)), contentLen, nil
}
diff --git a/pkg/specgen/generate/container.go b/pkg/specgen/generate/container.go
index 2248c9235..9bb7caace 100644
--- a/pkg/specgen/generate/container.go
+++ b/pkg/specgen/generate/container.go
@@ -302,60 +302,6 @@ func CompleteSpec(ctx context.Context, r *libpod.Runtime, s *specgen.SpecGenerat
return warnings, nil
}
-// FinishThrottleDevices takes the temporary representation of the throttle
-// devices in the specgen and looks up the major and major minors. it then
-// sets the throttle devices proper in the specgen
-func FinishThrottleDevices(s *specgen.SpecGenerator) error {
- if bps := s.ThrottleReadBpsDevice; len(bps) > 0 {
- for k, v := range bps {
- statT := unix.Stat_t{}
- if err := unix.Stat(k, &statT); err != nil {
- return err
- }
- v.Major = (int64(unix.Major(uint64(statT.Rdev)))) //nolint: unconvert
- v.Minor = (int64(unix.Minor(uint64(statT.Rdev)))) //nolint: unconvert
- if s.ResourceLimits.BlockIO == nil {
- s.ResourceLimits.BlockIO = new(spec.LinuxBlockIO)
- }
- s.ResourceLimits.BlockIO.ThrottleReadBpsDevice = append(s.ResourceLimits.BlockIO.ThrottleReadBpsDevice, v)
- }
- }
- if bps := s.ThrottleWriteBpsDevice; len(bps) > 0 {
- for k, v := range bps {
- statT := unix.Stat_t{}
- if err := unix.Stat(k, &statT); err != nil {
- return err
- }
- v.Major = (int64(unix.Major(uint64(statT.Rdev)))) //nolint: unconvert
- v.Minor = (int64(unix.Minor(uint64(statT.Rdev)))) //nolint: unconvert
- s.ResourceLimits.BlockIO.ThrottleWriteBpsDevice = append(s.ResourceLimits.BlockIO.ThrottleWriteBpsDevice, v)
- }
- }
- if iops := s.ThrottleReadIOPSDevice; len(iops) > 0 {
- for k, v := range iops {
- statT := unix.Stat_t{}
- if err := unix.Stat(k, &statT); err != nil {
- return err
- }
- v.Major = (int64(unix.Major(uint64(statT.Rdev)))) //nolint: unconvert
- v.Minor = (int64(unix.Minor(uint64(statT.Rdev)))) //nolint: unconvert
- s.ResourceLimits.BlockIO.ThrottleReadIOPSDevice = append(s.ResourceLimits.BlockIO.ThrottleReadIOPSDevice, v)
- }
- }
- if iops := s.ThrottleWriteIOPSDevice; len(iops) > 0 {
- for k, v := range iops {
- statT := unix.Stat_t{}
- if err := unix.Stat(k, &statT); err != nil {
- return err
- }
- v.Major = (int64(unix.Major(uint64(statT.Rdev)))) //nolint: unconvert
- v.Minor = (int64(unix.Minor(uint64(statT.Rdev)))) //nolint: unconvert
- s.ResourceLimits.BlockIO.ThrottleWriteIOPSDevice = append(s.ResourceLimits.BlockIO.ThrottleWriteIOPSDevice, v)
- }
- }
- return nil
-}
-
// ConfigToSpec takes a completed container config and converts it back into a specgenerator for purposes of cloning an existing container
func ConfigToSpec(rt *libpod.Runtime, specg *specgen.SpecGenerator, contaierID string) (*libpod.Container, *libpod.InfraInherit, error) {
c, err := rt.LookupContainer(contaierID)
@@ -540,3 +486,63 @@ func mapSecurityConfig(c *libpod.ContainerConfig, s *specgen.SpecGenerator) {
s.Groups = c.Groups
s.HostUsers = c.HostUsers
}
+
+// FinishThrottleDevices takes the temporary representation of the throttle
+// devices in the specgen and looks up the major and major minors. it then
+// sets the throttle devices proper in the specgen
+func FinishThrottleDevices(s *specgen.SpecGenerator) error {
+ if s.ResourceLimits == nil {
+ s.ResourceLimits = &spec.LinuxResources{}
+ }
+ if s.ResourceLimits.BlockIO == nil {
+ s.ResourceLimits.BlockIO = &spec.LinuxBlockIO{}
+ }
+ if bps := s.ThrottleReadBpsDevice; len(bps) > 0 {
+ for k, v := range bps {
+ statT := unix.Stat_t{}
+ if err := unix.Stat(k, &statT); err != nil {
+ return fmt.Errorf("could not parse throttle device at %s: %w", k, err)
+ }
+ v.Major = (int64(unix.Major(uint64(statT.Rdev)))) //nolint: unconvert
+ v.Minor = (int64(unix.Minor(uint64(statT.Rdev)))) //nolint: unconvert
+ if s.ResourceLimits.BlockIO == nil {
+ s.ResourceLimits.BlockIO = new(spec.LinuxBlockIO)
+ }
+ s.ResourceLimits.BlockIO.ThrottleReadBpsDevice = append(s.ResourceLimits.BlockIO.ThrottleReadBpsDevice, v)
+ }
+ }
+ if bps := s.ThrottleWriteBpsDevice; len(bps) > 0 {
+ for k, v := range bps {
+ statT := unix.Stat_t{}
+ if err := unix.Stat(k, &statT); err != nil {
+ return fmt.Errorf("could not parse throttle device at %s: %w", k, err)
+ }
+ v.Major = (int64(unix.Major(uint64(statT.Rdev)))) //nolint: unconvert
+ v.Minor = (int64(unix.Minor(uint64(statT.Rdev)))) //nolint: unconvert
+ s.ResourceLimits.BlockIO.ThrottleWriteBpsDevice = append(s.ResourceLimits.BlockIO.ThrottleWriteBpsDevice, v)
+ }
+ }
+ if iops := s.ThrottleReadIOPSDevice; len(iops) > 0 {
+ for k, v := range iops {
+ statT := unix.Stat_t{}
+ if err := unix.Stat(k, &statT); err != nil {
+ return fmt.Errorf("could not parse throttle device at %s: %w", k, err)
+ }
+ v.Major = (int64(unix.Major(uint64(statT.Rdev)))) //nolint: unconvert
+ v.Minor = (int64(unix.Minor(uint64(statT.Rdev)))) //nolint: unconvert
+ s.ResourceLimits.BlockIO.ThrottleReadIOPSDevice = append(s.ResourceLimits.BlockIO.ThrottleReadIOPSDevice, v)
+ }
+ }
+ if iops := s.ThrottleWriteIOPSDevice; len(iops) > 0 {
+ for k, v := range iops {
+ statT := unix.Stat_t{}
+ if err := unix.Stat(k, &statT); err != nil {
+ return fmt.Errorf("could not parse throttle device at %s: %w", k, err)
+ }
+ v.Major = (int64(unix.Major(uint64(statT.Rdev)))) //nolint: unconvert
+ v.Minor = (int64(unix.Minor(uint64(statT.Rdev)))) //nolint: unconvert
+ s.ResourceLimits.BlockIO.ThrottleWriteIOPSDevice = append(s.ResourceLimits.BlockIO.ThrottleWriteIOPSDevice, v)
+ }
+ }
+ return nil
+}
diff --git a/pkg/specgen/generate/container_create.go b/pkg/specgen/generate/container_create.go
index 51d290bb4..389900820 100644
--- a/pkg/specgen/generate/container_create.go
+++ b/pkg/specgen/generate/container_create.go
@@ -55,9 +55,6 @@ func MakeContainer(ctx context.Context, rt *libpod.Runtime, s *specgen.SpecGener
}
}
- if err := FinishThrottleDevices(s); err != nil {
- return nil, nil, nil, err
- }
// Set defaults for unset namespaces
if s.PidNS.IsDefault() {
defaultNS, err := GetDefaultNamespaceMode("pid", rtc, pod)
diff --git a/pkg/specgen/generate/oci.go b/pkg/specgen/generate/oci.go
index bb5f2d0ec..f59fe1011 100644
--- a/pkg/specgen/generate/oci.go
+++ b/pkg/specgen/generate/oci.go
@@ -309,6 +309,17 @@ func SpecGenToOCI(ctx context.Context, s *specgen.SpecGenerator, rt *libpod.Runt
}
g.Config.Linux.Resources = s.ResourceLimits
}
+
+ weightDevices, err := WeightDevices(s.WeightDevice)
+ if err != nil {
+ return nil, err
+ }
+ if len(weightDevices) > 0 {
+ for _, dev := range weightDevices {
+ g.AddLinuxResourcesBlockIOWeightDevice(dev.Major, dev.Minor, *dev.Weight)
+ }
+ }
+
// Devices
// set the default rule at the beginning of device configuration
if !inUserNS && !s.Privileged {
@@ -345,14 +356,6 @@ func SpecGenToOCI(ctx context.Context, s *specgen.SpecGenerator, rt *libpod.Runt
}
}
- for k, v := range s.WeightDevice {
- statT := unix.Stat_t{}
- if err := unix.Stat(k, &statT); err != nil {
- return nil, fmt.Errorf("failed to inspect '%s' in --blkio-weight-device: %w", k, err)
- }
- g.AddLinuxResourcesBlockIOWeightDevice((int64(unix.Major(uint64(statT.Rdev)))), (int64(unix.Minor(uint64(statT.Rdev)))), *v.Weight) //nolint: unconvert
- }
-
BlockAccessToKernelFilesystems(s.Privileged, s.PidNS.IsHost(), s.Mask, s.Unmask, &g)
g.ClearProcessEnv()
@@ -413,3 +416,19 @@ func SpecGenToOCI(ctx context.Context, s *specgen.SpecGenerator, rt *libpod.Runt
return configSpec, nil
}
+
+func WeightDevices(wtDevices map[string]spec.LinuxWeightDevice) ([]spec.LinuxWeightDevice, error) {
+ devs := []spec.LinuxWeightDevice{}
+ for k, v := range wtDevices {
+ statT := unix.Stat_t{}
+ if err := unix.Stat(k, &statT); err != nil {
+ return nil, fmt.Errorf("failed to inspect '%s' in --blkio-weight-device: %w", k, err)
+ }
+ dev := new(spec.LinuxWeightDevice)
+ dev.Major = (int64(unix.Major(uint64(statT.Rdev)))) //nolint: unconvert
+ dev.Minor = (int64(unix.Minor(uint64(statT.Rdev)))) //nolint: unconvert
+ dev.Weight = v.Weight
+ devs = append(devs, *dev)
+ }
+ return devs, nil
+}
diff --git a/pkg/specgen/generate/pod_create.go b/pkg/specgen/generate/pod_create.go
index 212d613fe..4e6362c9b 100644
--- a/pkg/specgen/generate/pod_create.go
+++ b/pkg/specgen/generate/pod_create.go
@@ -13,6 +13,7 @@ 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/opencontainers/runtime-spec/specs-go"
"github.com/sirupsen/logrus"
)
@@ -21,6 +22,10 @@ func MakePod(p *entities.PodSpec, rt *libpod.Runtime) (*libpod.Pod, error) {
return nil, err
}
+ if p.PodSpecGen.ResourceLimits == nil {
+ p.PodSpecGen.ResourceLimits = &specs.LinuxResources{}
+ }
+
if !p.PodSpecGen.NoInfra {
imageName, err := PullOrBuildInfraImage(rt, p.PodSpecGen.InfraImage)
if err != nil {
@@ -38,10 +43,33 @@ func MakePod(p *entities.PodSpec, rt *libpod.Runtime) (*libpod.Pod, error) {
}
}
+ if !p.PodSpecGen.NoInfra {
+ err := FinishThrottleDevices(p.PodSpecGen.InfraContainerSpec)
+ if err != nil {
+ return nil, err
+ }
+ if p.PodSpecGen.InfraContainerSpec.ResourceLimits.BlockIO != nil {
+ p.PodSpecGen.ResourceLimits.BlockIO = p.PodSpecGen.InfraContainerSpec.ResourceLimits.BlockIO
+ }
+
+ weightDevices, err := WeightDevices(p.PodSpecGen.InfraContainerSpec.WeightDevice)
+ if err != nil {
+ return nil, err
+ }
+
+ if p.PodSpecGen.ResourceLimits != nil && len(weightDevices) > 0 {
+ if p.PodSpecGen.ResourceLimits.BlockIO == nil {
+ p.PodSpecGen.ResourceLimits.BlockIO = &specs.LinuxBlockIO{}
+ }
+ p.PodSpecGen.ResourceLimits.BlockIO.WeightDevice = weightDevices
+ }
+ }
+
options, err := createPodOptions(&p.PodSpecGen)
if err != nil {
return nil, err
}
+
pod, err := rt.NewPod(context.Background(), p.PodSpecGen, options...)
if err != nil {
return nil, err
@@ -55,6 +83,11 @@ func MakePod(p *entities.PodSpec, rt *libpod.Runtime) (*libpod.Pod, error) {
return nil, err
}
p.PodSpecGen.InfraContainerSpec.User = "" // infraSpec user will get incorrectly assigned via the container creation process, overwrite here
+ // infra's resource limits are used as a parsing tool,
+ // we do not want infra to get these resources in its cgroup
+ // make sure of that here.
+ p.PodSpecGen.InfraContainerSpec.ResourceLimits = nil
+ p.PodSpecGen.InfraContainerSpec.WeightDevice = nil
rtSpec, spec, opts, err := MakeContainer(context.Background(), rt, p.PodSpecGen.InfraContainerSpec, false, nil)
if err != nil {
return nil, err
@@ -122,6 +155,10 @@ func createPodOptions(p *specgen.PodSpecGenerator) ([]libpod.PodCreateOption, er
options = append(options, libpod.WithPodHostname(p.Hostname))
}
+ if p.ResourceLimits != nil {
+ options = append(options, libpod.WithPodResources(*p.ResourceLimits))
+ }
+
options = append(options, libpod.WithPodExitPolicy(p.ExitPolicy))
return options, nil
diff --git a/pkg/specgenutil/specgen.go b/pkg/specgenutil/specgen.go
index 9a7d50947..4ab019b5b 100644
--- a/pkg/specgenutil/specgen.go
+++ b/pkg/specgenutil/specgen.go
@@ -74,6 +74,12 @@ func getCPULimits(c *entities.ContainerCreateOptions) *specs.LinuxCPU {
func getIOLimits(s *specgen.SpecGenerator, c *entities.ContainerCreateOptions) (*specs.LinuxBlockIO, error) {
var err error
io := &specs.LinuxBlockIO{}
+ if s.ResourceLimits == nil {
+ s.ResourceLimits = &specs.LinuxResources{}
+ }
+ if s.ResourceLimits.BlockIO == nil {
+ s.ResourceLimits.BlockIO = &specs.LinuxBlockIO{}
+ }
hasLimits := false
if b := c.BlkIOWeight; len(b) > 0 {
u, err := strconv.ParseUint(b, 10, 16)
@@ -82,6 +88,7 @@ func getIOLimits(s *specgen.SpecGenerator, c *entities.ContainerCreateOptions) (
}
nu := uint16(u)
io.Weight = &nu
+ s.ResourceLimits.BlockIO.Weight = &nu
hasLimits = true
}
@@ -96,6 +103,7 @@ func getIOLimits(s *specgen.SpecGenerator, c *entities.ContainerCreateOptions) (
if s.ThrottleReadBpsDevice, err = parseThrottleBPSDevices(bps); err != nil {
return nil, err
}
+
hasLimits = true
}
@@ -123,6 +131,8 @@ func getIOLimits(s *specgen.SpecGenerator, c *entities.ContainerCreateOptions) (
if !hasLimits {
return nil, nil
}
+ io = s.ResourceLimits.BlockIO
+
return io, nil
}
@@ -509,7 +519,7 @@ func FillOutSpecGen(s *specgen.SpecGenerator, c *entities.ContainerCreateOptions
return err
}
}
- if s.ResourceLimits.BlockIO == nil || (len(c.BlkIOWeight) != 0 || len(c.BlkIOWeightDevice) != 0) {
+ if s.ResourceLimits.BlockIO == nil || (len(c.BlkIOWeight) != 0 || len(c.BlkIOWeightDevice) != 0 || len(c.DeviceReadBPs) != 0 || len(c.DeviceWriteBPs) != 0) {
s.ResourceLimits.BlockIO, err = getIOLimits(s, c)
if err != nil {
return err
diff --git a/podman.spec.rpkg b/podman.spec.rpkg
index 30653b658..7068c9745 100644
--- a/podman.spec.rpkg
+++ b/podman.spec.rpkg
@@ -200,8 +200,6 @@ PODMAN_VERSION=%{version} %{__make} DESTDIR=%{buildroot} PREFIX=%{_prefix} ETCDI
install -d -p %{buildroot}/%{_datadir}/%{name}/test/system
cp -pav test/system %{buildroot}/%{_datadir}/%{name}/test/
-mv pkg/hooks/README.md pkg/hooks/README-hooks.md
-
# do not include docker and podman-remote man pages in main package
for file in `find %{buildroot}%{_mandir}/man[15] -type f | sed "s,%{buildroot},," | grep -v -e remote -e docker`; do
echo "$file*" >> podman.file-list
@@ -211,7 +209,7 @@ done
# are going to be installed into target system where the rpm is installed.
%files -f %{name}.file-list
%license LICENSE
-%doc README.md CONTRIBUTING.md pkg/hooks/README-hooks.md install.md transfer.md
+%doc README.md CONTRIBUTING.md install.md transfer.md
%{_bindir}/%{name}
%dir %{_libexecdir}/%{name}
%{_libexecdir}/%{name}/rootlessport
diff --git a/test/apiv2/35-networks.at b/test/apiv2/35-networks.at
index fcff26521..07ba45efb 100644
--- a/test/apiv2/35-networks.at
+++ b/test/apiv2/35-networks.at
@@ -84,12 +84,24 @@ t GET networks?filters='{"dangling":["true","0"]}' 500 \
t GET networks?filters='{"name":["doesnotexists"]}' 200 \
"[]"
+# check default name in list endpoint
+t GET networks 200 \
+ .[].Name~.*bridge.*
+
# network inspect docker
t GET networks/$network1_id 200 \
.Name=network1 \
.Id=$network1_id \
.Scope=local
+# inspect default bridge network
+t GET networks/bridge 200 \
+ .Name=bridge
+
+# inspect default bridge network with real podman name should return real name
+t GET networks/podman 200 \
+ .Name=podman
+
# network create docker
t POST networks/create Name=net3\ IPAM='{"Config":[]}' 201
# network delete docker
diff --git a/test/compose/test-compose b/test/compose/test-compose
index 7c4bf2e91..99d063c25 100755
--- a/test/compose/test-compose
+++ b/test/compose/test-compose
@@ -212,7 +212,6 @@ function start_service() {
rm -f $DOCKER_SOCK
mkdir --mode 0755 $WORKDIR/{root,runroot,cni}
chcon --reference=/var/lib/containers $WORKDIR/root
- cp /etc/cni/net.d/*podman*conflist $WORKDIR/cni/
$PODMAN_BIN \
--log-level debug \
diff --git a/test/e2e/build_test.go b/test/e2e/build_test.go
index 9ecc2f8c6..424c7244e 100644
--- a/test/e2e/build_test.go
+++ b/test/e2e/build_test.go
@@ -524,6 +524,8 @@ subdir**`
// See https://github.com/containers/podman/issues/13535
It("Remote build .containerignore filtering embedded directory (#13535)", func() {
SkipIfNotRemote("Testing remote .containerignore file filtering")
+ Skip("FIXME: #15014: test times out in 'dd' on f36.")
+
podmanTest.RestartRemoteService()
// Switch to temp dir and restore it afterwards
@@ -552,7 +554,7 @@ subdir**`
Expect(ioutil.WriteFile(filepath.Join(subdirPath, "extra"), contents.Bytes(), 0644)).
ToNot(HaveOccurred())
randomFile := filepath.Join(subdirPath, "randomFile")
- dd := exec.Command("dd", "if=/dev/random", "of="+randomFile, "bs=1G", "count=1")
+ dd := exec.Command("dd", "if=/dev/urandom", "of="+randomFile, "bs=1G", "count=1")
ddSession, err := Start(dd, GinkgoWriter, GinkgoWriter)
Expect(err).ToNot(HaveOccurred())
Eventually(ddSession, "10s", "1s").Should(Exit(0))
diff --git a/test/e2e/checkpoint_image_test.go b/test/e2e/checkpoint_image_test.go
index 94320a70e..5700802e8 100644
--- a/test/e2e/checkpoint_image_test.go
+++ b/test/e2e/checkpoint_image_test.go
@@ -58,6 +58,7 @@ var _ = Describe("Podman checkpoint", func() {
})
It("podman checkpoint --create-image with running container", func() {
+ SkipIfContainerized("FIXME: #15015. All checkpoint tests hang when containerized.")
// Container image must be lowercase
checkpointImage := "alpine-checkpoint-" + strings.ToLower(RandomString(6))
containerName := "alpine-container-" + RandomString(6)
@@ -163,7 +164,8 @@ var _ = Describe("Podman checkpoint", func() {
Expect(podmanTest.NumberOfContainersRunning()).To(Equal(0))
})
- It("podman restore multiple containers from single checkpint image", func() {
+ It("podman restore multiple containers from single checkpoint image", func() {
+ SkipIfContainerized("FIXME: #15015. All checkpoint tests hang when containerized.")
// Container image must be lowercase
checkpointImage := "alpine-checkpoint-" + strings.ToLower(RandomString(6))
containerName := "alpine-container-" + RandomString(6)
@@ -225,7 +227,8 @@ var _ = Describe("Podman checkpoint", func() {
Expect(podmanTest.NumberOfContainersRunning()).To(Equal(0))
})
- It("podman restore multiple containers from multiple checkpint images", func() {
+ It("podman restore multiple containers from multiple checkpoint images", func() {
+ SkipIfContainerized("FIXME: #15015. All checkpoint tests hang when containerized.")
// Container image must be lowercase
checkpointImage1 := "alpine-checkpoint-" + strings.ToLower(RandomString(6))
checkpointImage2 := "alpine-checkpoint-" + strings.ToLower(RandomString(6))
diff --git a/test/e2e/checkpoint_test.go b/test/e2e/checkpoint_test.go
index 5ccafeb37..a4646b6d1 100644
--- a/test/e2e/checkpoint_test.go
+++ b/test/e2e/checkpoint_test.go
@@ -57,6 +57,7 @@ var _ = Describe("Podman checkpoint", func() {
BeforeEach(func() {
SkipIfRootless("checkpoint not supported in rootless mode")
+ SkipIfContainerized("FIXME: #15015. All checkpoint tests hang when containerized.")
tempdir, err = CreateTempDirInTempDir()
Expect(err).To(BeNil())
@@ -1128,6 +1129,10 @@ var _ = Describe("Podman checkpoint", func() {
share := share // copy into local scope, for use inside function
It(testName, func() {
+ if podmanTest.Host.Distribution == "ubuntu" && IsRemote() {
+ Skip("FIXME: #15018. Cannot restore --pod under cgroupsV1 and remote")
+ }
+
if !criu.CheckForCriu(criu.PodCriuVersion) {
Skip("CRIU is missing or too old.")
}
diff --git a/test/e2e/info_test.go b/test/e2e/info_test.go
index 2c2c82cb6..9d31deb55 100644
--- a/test/e2e/info_test.go
+++ b/test/e2e/info_test.go
@@ -152,4 +152,19 @@ var _ = Describe("Podman Info", func() {
Expect(session.OutputToString()).To(ContainSubstring("memory"))
Expect(session.OutputToString()).To(ContainSubstring("pids"))
})
+
+ It("Podman info: check desired runtime", func() {
+ // defined in .cirrus.yml
+ want := os.Getenv("CI_DESIRED_RUNTIME")
+ if want == "" {
+ if os.Getenv("CIRRUS_CI") == "" {
+ Skip("CI_DESIRED_RUNTIME is not set--this is OK because we're not running under Cirrus")
+ }
+ Fail("CIRRUS_CI is set, but CI_DESIRED_RUNTIME is not! See #14912")
+ }
+ session := podmanTest.Podman([]string{"info", "--format", "{{.Host.OCIRuntime.Name}}"})
+ session.WaitWithDefaultTimeout()
+ Expect(session).To(Exit(0))
+ Expect(session.OutputToString()).To(Equal(want))
+ })
})
diff --git a/test/e2e/kill_test.go b/test/e2e/kill_test.go
index 2a9a86729..9b33e2f0d 100644
--- a/test/e2e/kill_test.go
+++ b/test/e2e/kill_test.go
@@ -129,6 +129,7 @@ var _ = Describe("Podman kill", func() {
})
It("podman kill paused container", func() {
+ SkipIfRootlessCgroupsV1("pause is not supported for cgroupv1 rootless")
ctrName := "testctr"
session := podmanTest.RunTopContainer(ctrName)
session.WaitWithDefaultTimeout()
diff --git a/test/e2e/manifest_test.go b/test/e2e/manifest_test.go
index 1f58419a1..ecddf2935 100644
--- a/test/e2e/manifest_test.go
+++ b/test/e2e/manifest_test.go
@@ -293,6 +293,9 @@ var _ = Describe("Podman manifest", func() {
})
It("authenticated push", func() {
+ if podmanTest.Host.Distribution == "ubuntu" && IsRemote() {
+ Skip("FIXME: #15017. Registry times out.")
+ }
registryOptions := &podmanRegistry.Options{
Image: "docker-archive:" + imageTarPath(REGISTRY_IMAGE),
}
diff --git a/test/e2e/network_connect_disconnect_test.go b/test/e2e/network_connect_disconnect_test.go
index c9ffe6a8d..ece1b519d 100644
--- a/test/e2e/network_connect_disconnect_test.go
+++ b/test/e2e/network_connect_disconnect_test.go
@@ -71,6 +71,7 @@ var _ = Describe("Podman network connect and disconnect", func() {
})
It("podman network disconnect", func() {
+ SkipIfRootlessCgroupsV1("stats not supported under rootless CgroupsV1")
netName := "aliasTest" + stringid.GenerateNonCryptoID()
session := podmanTest.Podman([]string{"network", "create", netName})
session.WaitWithDefaultTimeout()
@@ -180,6 +181,7 @@ var _ = Describe("Podman network connect and disconnect", func() {
})
It("podman network connect", func() {
+ SkipIfRootlessCgroupsV1("stats not supported under rootless CgroupsV1")
netName := "aliasTest" + stringid.GenerateNonCryptoID()
session := podmanTest.Podman([]string{"network", "create", netName})
session.WaitWithDefaultTimeout()
diff --git a/test/e2e/play_kube_test.go b/test/e2e/play_kube_test.go
index ba8f3cc0d..1b4eefd45 100644
--- a/test/e2e/play_kube_test.go
+++ b/test/e2e/play_kube_test.go
@@ -1629,8 +1629,10 @@ var _ = Describe("Podman play kube", func() {
})
// If you have an init container in the pod yaml, podman should create and run the init container with play kube
- It("podman play kube test with init containers", func() {
- pod := getPod(withPodInitCtr(getCtr(withImage(ALPINE), withCmd([]string{"echo", "hello"}), withInitCtr(), withName("init-test"))), withCtr(getCtr(withImage(ALPINE), withCmd([]string{"top"}))))
+ // With annotation set to always
+ It("podman play kube test with init containers and annotation set", func() {
+ // With the init container type annotation set to always
+ pod := getPod(withAnnotation("io.podman.annotations.init.container.type", "always"), withPodInitCtr(getCtr(withImage(ALPINE), withCmd([]string{"echo", "hello"}), withInitCtr(), withName("init-test"))), withCtr(getCtr(withImage(ALPINE), withCmd([]string{"top"}))))
err := generateKubeYaml("pod", pod, kubeYaml)
Expect(err).To(BeNil())
@@ -1655,6 +1657,29 @@ var _ = Describe("Podman play kube", func() {
Expect(inspect.OutputToString()).To(ContainSubstring("running"))
})
+ // If you have an init container in the pod yaml, podman should create and run the init container with play kube
+ // Using default init container type (once)
+ It("podman play kube test with init container type set to default value", func() {
+ // Using the default init container type (once)
+ pod := getPod(withPodInitCtr(getCtr(withImage(ALPINE), withCmd([]string{"echo", "hello"}), withInitCtr(), withName("init-test"))), withCtr(getCtr(withImage(ALPINE), withCmd([]string{"top"}))))
+ err := generateKubeYaml("pod", pod, kubeYaml)
+ Expect(err).To(BeNil())
+
+ kube := podmanTest.Podman([]string{"play", "kube", kubeYaml})
+ kube.WaitWithDefaultTimeout()
+ Expect(kube).Should(Exit(0))
+
+ // Expect the number of containers created to be 2, infra and regular container
+ numOfCtrs := podmanTest.NumberOfContainers()
+ Expect(numOfCtrs).To(Equal(2))
+
+ // Regular container should be in running state
+ inspect := podmanTest.Podman([]string{"inspect", "--format", "{{.State.Status}}", "testPod-" + defaultCtrName})
+ inspect.WaitWithDefaultTimeout()
+ Expect(inspect).Should(Exit(0))
+ Expect(inspect.OutputToString()).To(ContainSubstring("running"))
+ })
+
// If you supply only args for a Container, the default Entrypoint defined in the Docker image is run with the args that you supplied.
It("podman play kube test correct command with only set args in yaml file", func() {
pod := getPod(withCtr(getCtr(withImage(REGISTRY_IMAGE), withCmd(nil), withArg([]string{"echo", "hello"}))))
diff --git a/test/e2e/pod_create_test.go b/test/e2e/pod_create_test.go
index 3caae2bd5..24d9d6854 100644
--- a/test/e2e/pod_create_test.go
+++ b/test/e2e/pod_create_test.go
@@ -1051,6 +1051,7 @@ ENTRYPOINT ["sleep","99999"]
It("podman pod create --share-parent test", func() {
SkipIfRootlessCgroupsV1("rootless cannot use cgroups with cgroupsv1")
+ SkipIfCgroupV1("FIXME: #15013: CgroupMode shows 'host' instead of CID under Cgroups V1.")
podCreate := podmanTest.Podman([]string{"pod", "create", "--share-parent=false"})
podCreate.WaitWithDefaultTimeout()
Expect(podCreate).Should(Exit(0))
diff --git a/test/e2e/push_test.go b/test/e2e/push_test.go
index 97567e40d..f2a103f6b 100644
--- a/test/e2e/push_test.go
+++ b/test/e2e/push_test.go
@@ -116,15 +116,26 @@ var _ = Describe("Podman push", func() {
push := podmanTest.Podman([]string{"push", "-q", "--tls-verify=false", "--remove-signatures", ALPINE, "localhost:5000/my-alpine"})
push.WaitWithDefaultTimeout()
Expect(push).Should(Exit(0))
+ Expect(len(push.ErrorToString())).To(Equal(0))
- SkipIfRemote("Remote does not support --digestfile")
- // Test --digestfile option
- push2 := podmanTest.Podman([]string{"push", "--tls-verify=false", "--digestfile=/tmp/digestfile.txt", "--remove-signatures", ALPINE, "localhost:5000/my-alpine"})
- push2.WaitWithDefaultTimeout()
- fi, err := os.Lstat("/tmp/digestfile.txt")
- Expect(err).To(BeNil())
- Expect(fi.Name()).To(Equal("digestfile.txt"))
- Expect(push2).Should(Exit(0))
+ push = podmanTest.Podman([]string{"push", "--tls-verify=false", "--remove-signatures", ALPINE, "localhost:5000/my-alpine"})
+ push.WaitWithDefaultTimeout()
+ Expect(push).Should(Exit(0))
+ output := push.ErrorToString()
+ Expect(output).To(ContainSubstring("Copying blob "))
+ Expect(output).To(ContainSubstring("Copying config "))
+ Expect(output).To(ContainSubstring("Writing manifest to image destination"))
+ Expect(output).To(ContainSubstring("Storing signatures"))
+
+ if !IsRemote() { // Remote does not support --digestfile
+ // Test --digestfile option
+ push2 := podmanTest.Podman([]string{"push", "--tls-verify=false", "--digestfile=/tmp/digestfile.txt", "--remove-signatures", ALPINE, "localhost:5000/my-alpine"})
+ push2.WaitWithDefaultTimeout()
+ fi, err := os.Lstat("/tmp/digestfile.txt")
+ Expect(err).To(BeNil())
+ Expect(fi.Name()).To(Equal("digestfile.txt"))
+ Expect(push2).Should(Exit(0))
+ }
})
It("podman push to local registry with authorization", func() {
diff --git a/test/e2e/run_passwd_test.go b/test/e2e/run_passwd_test.go
index 411e12218..20a6ee3b1 100644
--- a/test/e2e/run_passwd_test.go
+++ b/test/e2e/run_passwd_test.go
@@ -66,10 +66,15 @@ RUN rm -f /etc/passwd /etc/shadow /etc/group
USER 1000`, ALPINE)
imgName := "testimg"
podmanTest.BuildImage(dockerfile, imgName, "false")
- session := podmanTest.Podman([]string{"run", "--rm", imgName, "ls", "/etc/"})
+ session := podmanTest.Podman([]string{"run", "--passwd=false", "--rm", imgName, "ls", "/etc/"})
session.WaitWithDefaultTimeout()
Expect(session).Should(Exit(0))
Expect(session.OutputToString()).To(Not(ContainSubstring("passwd")))
+
+ // test that the /etc/passwd file is created
+ session = podmanTest.Podman([]string{"run", "--rm", "--user", "0:0", imgName, "ls", "/etc/passwd"})
+ session.WaitWithDefaultTimeout()
+ Expect(session).Should(Exit(0))
})
It("podman run with no user specified does not change --group specified", func() {
diff --git a/test/e2e/run_test.go b/test/e2e/run_test.go
index 6edb705a1..64b70f1ee 100644
--- a/test/e2e/run_test.go
+++ b/test/e2e/run_test.go
@@ -91,7 +91,8 @@ var _ = Describe("Podman run", func() {
if exitCode == 0 {
Expect(session.OutputToString()).To(ContainSubstring("aarch64"))
} else {
- Expect(session.ErrorToString()).To(ContainSubstring("Exec format error"))
+ // crun says 'Exec', runc says 'exec'. Handle either.
+ Expect(session.ErrorToString()).To(ContainSubstring("xec format error"))
}
})
@@ -714,6 +715,7 @@ USER bin`, BB)
})
It("podman run device-read-bps test", func() {
+ SkipIfCgroupV1("FIXME: #15035 - bps broken")
SkipIfRootless("FIXME: requested cgroup controller `io` is not available")
SkipIfRootlessCgroupsV1("Setting device-read-bps not supported on cgroupv1 for rootless users")
@@ -733,6 +735,7 @@ USER bin`, BB)
})
It("podman run device-write-bps test", func() {
+ SkipIfCgroupV1("FIXME: #15035 - bps broken")
SkipIfRootless("FIXME: requested cgroup controller `io` is not available")
SkipIfRootlessCgroupsV1("Setting device-write-bps not supported on cgroupv1 for rootless users")
@@ -751,6 +754,7 @@ USER bin`, BB)
})
It("podman run device-read-iops test", func() {
+ SkipIfCgroupV1("FIXME: #15035 - bps broken")
SkipIfRootless("FIXME: requested cgroup controller `io` is not available")
SkipIfRootlessCgroupsV1("Setting device-read-iops not supported on cgroupv1 for rootless users")
var session *PodmanSessionIntegration
@@ -769,6 +773,7 @@ USER bin`, BB)
})
It("podman run device-write-iops test", func() {
+ SkipIfCgroupV1("FIXME: #15035 - bps broken")
SkipIfRootless("FIXME: requested cgroup controller `io` is not available")
SkipIfRootlessCgroupsV1("Setting device-write-iops not supported on cgroupv1 for rootless users")
var session *PodmanSessionIntegration
diff --git a/test/system/001-basic.bats b/test/system/001-basic.bats
index 0d2a99d4b..cf37fc07c 100644
--- a/test/system/001-basic.bats
+++ b/test/system/001-basic.bats
@@ -61,8 +61,19 @@ function setup() {
}
@test "podman can pull an image" {
+ run_podman rmi -a
run_podman pull $IMAGE
+ # Regression test for https://github.com/containers/image/pull/1615
+ # Make sure no progress lines are duplicated
+ local -A line_seen
+ for line in "${lines[@]}"; do
+ if [[ -n "${line_seen[$line]}" ]]; then
+ die "duplicate podman-pull output line: $line"
+ fi
+ line_seen[$line]=1
+ done
+
# Also make sure that the tag@digest syntax is supported.
run_podman inspect --format "{{ .Digest }}" $IMAGE
digest=$output
diff --git a/test/system/005-info.bats b/test/system/005-info.bats
index 333553b07..43a345f11 100644
--- a/test/system/005-info.bats
+++ b/test/system/005-info.bats
@@ -55,7 +55,24 @@ host.slirp4netns.executable | $expr_path
dprint "# actual=<$actual> expect=<$expect>"
is "$actual" "$expect" "jq .$field"
done
+}
+
+@test "podman info - confirm desired runtime" {
+ if [[ -z "$CI_DESIRED_RUNTIME" ]]; then
+ # When running in Cirrus, CI_DESIRED_RUNTIME *must* be defined
+ # in .cirrus.yml so we can double-check that all CI VMs are
+ # using crun/runc as desired.
+ if [[ -n "$CIRRUS_CI" ]]; then
+ die "CIRRUS_CI is set, but CI_DESIRED_RUNTIME is not! See #14912"
+ fi
+
+ # Not running under Cirrus (e.g., gating tests, or dev laptop).
+ # Totally OK to skip this test.
+ skip "CI_DESIRED_RUNTIME is unset--OK, because we're not in Cirrus"
+ fi
+ run_podman info --format '{{.Host.OCIRuntime.Name}}'
+ is "$output" "$CI_DESIRED_RUNTIME" "CI_DESIRED_RUNTIME (from .cirrus.yml)"
}
# 2021-04-06 discussed in watercooler: RHEL must never use crun, even if
diff --git a/test/system/030-run.bats b/test/system/030-run.bats
index b3e3cef00..e62e7679f 100644
--- a/test/system/030-run.bats
+++ b/test/system/030-run.bats
@@ -70,6 +70,7 @@ echo $rand | 0 | $rand
}
@test "podman run - uidmapping has no /sys/kernel mounts" {
+ skip_if_cgroupsv1 "FIXME: #15025: run --uidmap fails on cgroups v1"
skip_if_rootless "cannot umount as rootless"
skip_if_remote "TODO Fix this for remote case"
@@ -805,6 +806,7 @@ EOF
# rhbz#1902979 : podman run fails to update /etc/hosts when --uidmap is provided
@test "podman run update /etc/hosts" {
+ skip_if_cgroupsv1 "FIXME: #15025: run --uidmap fails on cgroups v1"
HOST=$(random_string 25)
run_podman run --uidmap 0:10001:10002 --rm --hostname ${HOST} $IMAGE grep ${HOST} /etc/hosts
is "${lines[0]}" ".*${HOST}.*"
diff --git a/test/system/160-volumes.bats b/test/system/160-volumes.bats
index da60112a0..18e806699 100644
--- a/test/system/160-volumes.bats
+++ b/test/system/160-volumes.bats
@@ -149,16 +149,16 @@ EOF
# By default, volumes are mounted exec, but we have manually added the
# noexec option. This should fail.
- # ARGH. Unfortunately, runc (used for cgroups v1) produces a different error
+ # ARGH. Unfortunately, runc (used for cgroups v1) has different exit status
local expect_rc=126
- local expect_msg='.* OCI permission denied.*'
if [[ $(podman_runtime) = "runc" ]]; then
expect_rc=1
- expect_msg='.* exec user process caused.*permission denied'
fi
run_podman ${expect_rc} run --rm --volume $myvolume:/vol:noexec,z $IMAGE /vol/myscript
- is "$output" "$expect_msg" "run on volume, noexec"
+ # crun and runc emit different messages, and even runc is inconsistent
+ # with itself (output changed some time in 2022?). Deal with all.
+ assert "$output" =~ 'exec.* permission denied' "run on volume, noexec"
# With the default, it should pass
run_podman run --rm -v $myvolume:/vol:z $IMAGE /vol/myscript
diff --git a/test/system/170-run-userns.bats b/test/system/170-run-userns.bats
index 2ad9eb0b8..5ad7473da 100644
--- a/test/system/170-run-userns.bats
+++ b/test/system/170-run-userns.bats
@@ -30,6 +30,7 @@ function _require_crun() {
}
@test "podman --group-add without keep-groups while in a userns" {
+ skip_if_cgroupsv1 "FIXME: #15025: run --uidmap fails on cgroups v1"
skip_if_rootless "chroot is not allowed in rootless mode"
skip_if_remote "--group-add keep-groups not supported in remote mode"
run chroot --groups 1234,5678 / ${PODMAN} run --rm --uidmap 0:200000:5000 --group-add 457 $IMAGE id
@@ -37,6 +38,7 @@ function _require_crun() {
}
@test "rootful pod with custom ID mapping" {
+ skip_if_cgroupsv1 "FIXME: #15025: run --uidmap fails on cgroups v1"
skip_if_rootless "does not work rootless - rootful feature"
random_pod_name=$(random_string 30)
run_podman pod create --uidmap 0:200000:5000 --name=$random_pod_name
diff --git a/test/system/200-pod.bats b/test/system/200-pod.bats
index 7b7f5e8bb..667e2baef 100644
--- a/test/system/200-pod.bats
+++ b/test/system/200-pod.bats
@@ -2,12 +2,17 @@
load helpers
+LOOPDEVICE=
+
# This is a long ugly way to clean up pods and remove the pause image
function teardown() {
run_podman pod rm -f -t 0 -a
run_podman rm -f -t 0 -a
run_podman rmi --ignore $(pause_image)
basic_teardown
+ if [[ -n "$LOOPDEVICE" ]]; then
+ losetup -d $LOOPDEVICE
+ fi
}
@@ -474,31 +479,56 @@ spec:
@test "pod resource limits" {
skip_if_remote "resource limits only implemented on non-remote"
- if is_rootless; then
- skip "only meaningful for rootful"
+ skip_if_rootless "resource limits only work with root"
+ skip_if_cgroupsv1 "resource limits only meaningful on cgroups V2"
+
+ # create loopback device
+ lofile=${PODMAN_TMPDIR}/disk.img
+ fallocate -l 1k ${lofile}
+ LOOPDEVICE=$(losetup --show -f $lofile)
+
+ # tr needed because losetup seems to use %2d
+ lomajmin=$(losetup -l --noheadings --output MAJ:MIN $LOOPDEVICE | tr -d ' ')
+ run grep -w bfq /sys/block/$(basename ${LOOPDEVICE})/queue/scheduler
+ if [ $status -ne 0 ]; then
+ skip "BFQ scheduler is not supported on the system"
+ if [ -f ${lofile} ]; then
+ run_podman '?' rm -t 0 --all --force --ignore
+
+ while read path dev; do
+ if [[ "$path" == "$lofile" ]]; then
+ losetup -d $dev
+ fi
+ done < <(losetup -l --noheadings --output BACK-FILE,NAME)
+ rm ${lofile}
+ fi
fi
+ echo bfq > /sys/block/$(basename ${LOOPDEVICE})/queue/scheduler
+
+ expected_limits="
+cpu.max | 500000 100000
+memory.max | 5242880
+memory.swap.max | 1068498944
+io.max | $lomajmin rbps=1048576 wbps=1048576 riops=max wiops=max
+"
+
+ for cgm in systemd cgroupfs; do
+ local name=resources-$cgm
+ run_podman --cgroup-manager=$cgm pod create --name=$name --cpus=5 --memory=5m --memory-swap=1g --cpu-shares=1000 --cpuset-cpus=0 --cpuset-mems=0 --device-read-bps=${LOOPDEVICE}:1mb --device-write-bps=${LOOPDEVICE}:1mb --blkio-weight-device=${LOOPDEVICE}:123 --blkio-weight=50
+ run_podman --cgroup-manager=$cgm pod start $name
+ run_podman pod inspect --format '{{.CgroupPath}}' $name
+ local cgroup_path="$output"
+
+ while read unit expect; do
+ local actual=$(< /sys/fs/cgroup/$cgroup_path/$unit)
+ is "$actual" "$expect" "resource limit under $cgm: $unit"
+ done < <(parse_table "$expected_limits")
+ run_podman --cgroup-manager=$cgm pod rm -f $name
+ done
- local name1="resources1"
- run_podman --cgroup-manager=systemd pod create --name=$name1 --cpus=5 --memory=10m
- 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"
- local actual2=$(< /sys/fs/cgroup/$path1/memory.max)
- is "$actual2" "10485760" "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 --memory=10m --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"
- local actual2=$(< /sys/fs/cgroup/$path2/memory.max)
- is "$actual2" "10485760" "resource limits set properly"
- run_podman --cgroup-manager=cgroupfs pod rm $name2
+ # Clean up, and prevent duplicate cleanup in teardown
+ losetup -d $LOOPDEVICE
+ LOOPDEVICE=
}
@test "podman pod ps doesn't race with pod rm" {
diff --git a/test/system/251-system-service.bats b/test/system/251-system-service.bats
index edee4a28c..197d1cb18 100644
--- a/test/system/251-system-service.bats
+++ b/test/system/251-system-service.bats
@@ -17,6 +17,10 @@ function teardown() {
@test "podman-system-service containers survive service stop" {
skip_if_remote "podman system service unavailable over remote"
+ local runtime=$(podman_runtime)
+ if [[ "$runtime" != "crun" ]]; then
+ skip "survival code only implemented in crun; you're using $runtime"
+ fi
port=$(random_free_port)
URL=tcp://127.0.0.1:$port
diff --git a/test/system/400-unprivileged-access.bats b/test/system/400-unprivileged-access.bats
index 710ff066c..0d6be2d60 100644
--- a/test/system/400-unprivileged-access.bats
+++ b/test/system/400-unprivileged-access.bats
@@ -7,6 +7,7 @@
load helpers
@test "podman container storage is not accessible by unprivileged users" {
+ skip_if_cgroupsv1 "FIXME: #15025: run --uidmap fails on cgroups v1"
skip_if_rootless "test meaningless without suid"
skip_if_remote
diff --git a/test/system/500-networking.bats b/test/system/500-networking.bats
index 50eb15216..f45540f5f 100644
--- a/test/system/500-networking.bats
+++ b/test/system/500-networking.bats
@@ -84,6 +84,7 @@ load helpers
# Issue #5466 - port-forwarding doesn't work with this option and -d
@test "podman networking: port with --userns=keep-id for rootless or --uidmap=* for rootful" {
+ skip_if_cgroupsv1 "FIXME: #15025: run --uidmap fails on cgroups v1"
for cidr in "" "$(random_rfc1918_subnet).0/24"; do
myport=$(random_free_port 52000-52999)
if [[ -z $cidr ]]; then
@@ -744,6 +745,7 @@ EOF
}
@test "podman run /etc/* permissions" {
+ skip_if_cgroupsv1 "FIXME: #15025: run --uidmap fails on cgroups v1"
userns="--userns=keep-id"
if ! is_rootless; then
userns="--uidmap=0:1111111:65536 --gidmap=0:1111111:65536"
diff --git a/test/utils/utils.go b/test/utils/utils.go
index 36f5a9414..e84b57cc6 100644
--- a/test/utils/utils.go
+++ b/test/utils/utils.go
@@ -365,7 +365,11 @@ func (s *PodmanSession) WaitWithDefaultTimeout() {
// WaitWithTimeout waits for process finished with DefaultWaitTimeout
func (s *PodmanSession) WaitWithTimeout(timeout int) {
- Eventually(s, timeout).Should(Exit())
+ Eventually(s, timeout).Should(Exit(), func() string {
+ // in case of timeouts show output
+ return fmt.Sprintf("command timed out after %ds: %v\nSTDOUT: %s\nSTDERR: %s",
+ timeout, s.Command.Args, string(s.Out.Contents()), string(s.Err.Contents()))
+ })
os.Stdout.Sync()
os.Stderr.Sync()
fmt.Println("output:", s.OutputToString())
diff --git a/vendor/github.com/containers/image/v5/copy/copy.go b/vendor/github.com/containers/image/v5/copy/copy.go
index a318d3f82..ba5334e59 100644
--- a/vendor/github.com/containers/image/v5/copy/copy.go
+++ b/vendor/github.com/containers/image/v5/copy/copy.go
@@ -237,7 +237,7 @@ func Image(ctx context.Context, policyContext *signature.PolicyContext, destRef,
// If reportWriter is not a TTY (e.g., when piping to a file), do not
// print the progress bars to avoid long and hard to parse output.
- // createProgressBar() will print a single line instead.
+ // Instead use printCopyInfo() to print single line "Copying ..." messages.
progressOutput := reportWriter
if !isTTY(reportWriter) {
progressOutput = io.Discard
@@ -1107,6 +1107,7 @@ func (ic *imageCopier) copyConfig(ctx context.Context, src types.Image) error {
defer progressPool.Wait()
bar := ic.c.createProgressBar(progressPool, false, srcInfo, "config", "done")
defer bar.Abort(false)
+ ic.c.printCopyInfo("config", srcInfo)
configBlob, err := src.ConfigBlob(ctx)
if err != nil {
@@ -1162,6 +1163,8 @@ func (ic *imageCopier) copyLayer(ctx context.Context, srcInfo types.BlobInfo, to
}
}
+ ic.c.printCopyInfo("blob", srcInfo)
+
cachedDiffID := ic.c.blobInfoCache.UncompressedDigest(srcInfo.Digest) // May be ""
diffIDIsNeeded := ic.diffIDsAreNeeded && cachedDiffID == ""
// When encrypting to decrypting, only use the simple code path. We might be able to optimize more
diff --git a/vendor/github.com/containers/image/v5/copy/progress_bars.go b/vendor/github.com/containers/image/v5/copy/progress_bars.go
index 585d86057..85676f01c 100644
--- a/vendor/github.com/containers/image/v5/copy/progress_bars.go
+++ b/vendor/github.com/containers/image/v5/copy/progress_bars.go
@@ -38,7 +38,8 @@ type progressBar struct {
}
// createProgressBar creates a progressBar in pool. Note that if the copier's reportWriter
-// is io.Discard, the progress bar's output will be discarded
+// is io.Discard, the progress bar's output will be discarded. Callers may call printCopyInfo()
+// to print a single line instead.
//
// NOTE: Every progress bar created within a progress pool must either successfully
// complete or be aborted, or pool.Wait() will hang. That is typically done
@@ -95,15 +96,21 @@ func (c *copier) createProgressBar(pool *mpb.Progress, partial bool, info types.
),
)
}
- if c.progressOutput == io.Discard {
- c.Printf("Copying %s %s\n", kind, info.Digest)
- }
return &progressBar{
Bar: bar,
originalSize: info.Size,
}
}
+// printCopyInfo prints a "Copying ..." message on the copier if the output is
+// set to `io.Discard`. In that case, the progress bars won't be rendered but
+// we still want to indicate when blobs and configs are copied.
+func (c *copier) printCopyInfo(kind string, info types.BlobInfo) {
+ if c.progressOutput == io.Discard {
+ c.Printf("Copying %s %s\n", kind, info.Digest)
+ }
+}
+
// mark100PercentComplete marks the progres bars as 100% complete;
// it may do so by possibly advancing the current state if it is below the known total.
func (bar *progressBar) mark100PercentComplete() {
diff --git a/vendor/modules.txt b/vendor/modules.txt
index f4a8192bc..d7edfabe7 100644
--- a/vendor/modules.txt
+++ b/vendor/modules.txt
@@ -167,7 +167,7 @@ github.com/containers/common/version
# github.com/containers/conmon v2.0.20+incompatible
## explicit
github.com/containers/conmon/runner/config
-# github.com/containers/image/v5 v5.21.2-0.20220714132403-2bb3f3e44c5c
+# github.com/containers/image/v5 v5.21.2-0.20220721072459-bf19265865b7
## explicit
github.com/containers/image/v5/copy
github.com/containers/image/v5/directory
@@ -325,7 +325,6 @@ github.com/coreos/stream-metadata-go/stream/rhcos
## explicit
github.com/cyphar/filepath-securejoin
# github.com/davecgh/go-spew v1.1.1
-## explicit
github.com/davecgh/go-spew/spew
# github.com/digitalocean/go-libvirt v0.0.0-20201209184759-e2a69bcd5bd1
github.com/digitalocean/go-libvirt
@@ -625,7 +624,6 @@ github.com/ostreedev/ostree-go/pkg/otbuiltin
# github.com/pkg/errors v0.9.1
github.com/pkg/errors
# github.com/pmezard/go-difflib v1.0.0
-## explicit
github.com/pmezard/go-difflib/difflib
# github.com/proglottis/gpgme v0.1.3
github.com/proglottis/gpgme
@@ -689,7 +687,7 @@ github.com/sylabs/sif/v2/pkg/sif
github.com/syndtr/gocapability/capability
# github.com/tchap/go-patricia v2.3.0+incompatible
github.com/tchap/go-patricia/patricia
-# github.com/theupdateframework/go-tuf v0.3.0
+# github.com/theupdateframework/go-tuf v0.3.1
github.com/theupdateframework/go-tuf/encrypted
# github.com/titanous/rocacheck v0.0.0-20171023193734-afe73141d399
github.com/titanous/rocacheck