diff options
38 files changed, 419 insertions, 239 deletions
diff --git a/.packit.yaml b/.packit.yaml new file mode 100644 index 000000000..3d7b49297 --- /dev/null +++ b/.packit.yaml @@ -0,0 +1,18 @@ +# See the documentation for more information: +# https://packit.dev/docs/configuration/ + +upstream_package_name: podman +downstream_package_name: podman + +actions: + post-upstream-clone: + - "curl -O https://src.fedoraproject.org/rpms/podman/raw/main/f/podman.spec" + +jobs: + - job: production_build + trigger: pull_request + targets: &production_dist_targets + - fedora-36 + - fedora-37 + - fedora-rawhide + scratch: true diff --git a/cmd/podman/images/pull.go b/cmd/podman/images/pull.go index 8211ceba5..fe9d1e9b6 100644 --- a/cmd/podman/images/pull.go +++ b/cmd/podman/images/pull.go @@ -155,6 +155,11 @@ func imagePull(cmd *cobra.Command, args []string) error { pullOptions.Username = creds.Username pullOptions.Password = creds.Password } + + if !pullOptions.Quiet { + pullOptions.Writer = os.Stderr + } + // Let's do all the remaining Yoga in the API to prevent us from // scattering logic across (too) many parts of the code. var errs utils.OutputErrors diff --git a/cmd/podman/images/push.go b/cmd/podman/images/push.go index 1734900de..fa60860db 100644 --- a/cmd/podman/images/push.go +++ b/cmd/podman/images/push.go @@ -164,6 +164,10 @@ func imagePush(cmd *cobra.Command, args []string) error { pushOptions.Password = creds.Password } + if !pushOptions.Quiet { + pushOptions.Writer = os.Stderr + } + if err := common.PrepareSigningPassphrase(&pushOptions.ImagePushOptions, pushOptions.SignPassphraseFileCLI); err != nil { return err } diff --git a/cmd/podman/manifest/push.go b/cmd/podman/manifest/push.go index fd67769b8..c8893ff2e 100644 --- a/cmd/podman/manifest/push.go +++ b/cmd/podman/manifest/push.go @@ -4,6 +4,7 @@ import ( "errors" "fmt" "io/ioutil" + "os" "github.com/containers/common/pkg/auth" "github.com/containers/common/pkg/completion" @@ -122,6 +123,10 @@ func push(cmd *cobra.Command, args []string) error { manifestPushOpts.Password = creds.Password } + if !manifestPushOpts.Quiet { + manifestPushOpts.Writer = os.Stderr + } + if err := common.PrepareSigningPassphrase(&manifestPushOpts.ImagePushOptions, manifestPushOpts.SignPassphraseFileCLI); err != nil { return err } diff --git a/docs/source/markdown/.gitignore b/docs/source/markdown/.gitignore index 2bdcce197..85aed3be0 100644 --- a/docs/source/markdown/.gitignore +++ b/docs/source/markdown/.gitignore @@ -3,6 +3,7 @@ podman-build.1.md podman-container-clone.1.md podman-container-runlabel.1.md podman-create.1.md +podman-exec.1.md podman-image-sign.1.md podman-kill.1.md podman-kube-play.1.md diff --git a/docs/source/markdown/options/ignore.md b/docs/source/markdown/options/ignore.md new file mode 100644 index 000000000..231d75957 --- /dev/null +++ b/docs/source/markdown/options/ignore.md @@ -0,0 +1,5 @@ +#### **--ignore**, **-i** + +Ignore errors when specified <<containers|pods>> are not in the container store. A user +might have decided to manually remove a <<container|pod>> which would lead to a failure +during the ExecStop directive of a systemd service referencing that <<container|pod>>. diff --git a/docs/source/markdown/options/workdir.md b/docs/source/markdown/options/workdir.md new file mode 100644 index 000000000..12f3ddd44 --- /dev/null +++ b/docs/source/markdown/options/workdir.md @@ -0,0 +1,7 @@ +#### **--workdir**, **-w**=*dir* + +Working directory inside the container. + +The default working directory for running binaries within a container is the root directory (**/**). +The image developer can set a different default with the WORKDIR instruction. The operator +can override the working directory by using the **-w** option. diff --git a/docs/source/markdown/podman-create.1.md.in b/docs/source/markdown/podman-create.1.md.in index 9518568bb..8710af110 100644 --- a/docs/source/markdown/podman-create.1.md.in +++ b/docs/source/markdown/podman-create.1.md.in @@ -975,13 +975,7 @@ If the location of the volume from the source container overlaps with data residing on a target container, then the volume hides that data on the target. -#### **--workdir**, **-w**=*dir* - -Working directory inside the container - -The default working directory for running binaries within a container is the root directory (/). -The image developer can set a different default with the WORKDIR instruction. The operator -can override the working directory by using the **-w** option. +@@option workdir ## EXAMPLES diff --git a/docs/source/markdown/podman-exec.1.md b/docs/source/markdown/podman-exec.1.md.in index da61f3456..4f78f1c31 100644 --- a/docs/source/markdown/podman-exec.1.md +++ b/docs/source/markdown/podman-exec.1.md.in @@ -70,13 +70,7 @@ Sets the username or UID used and optionally the groupname or GID for the specif The following examples are all valid: --user [user | user:group | uid | uid:gid | user:gid | uid:group ] -#### **--workdir**, **-w**=*path* - -Working directory inside the container - -The default working directory for running binaries within a container is the root directory (/). -The image developer can set a different default with the WORKDIR instruction, which can be overridden -when creating the container. +@@option workdir ## Exit Status diff --git a/docs/source/markdown/podman-pod-rm.1.md.in b/docs/source/markdown/podman-pod-rm.1.md.in index 54646ebe3..82e28acb1 100644 --- a/docs/source/markdown/podman-pod-rm.1.md.in +++ b/docs/source/markdown/podman-pod-rm.1.md.in @@ -19,11 +19,7 @@ Remove all pods. Can be used in conjunction with \-f as well. Stop running containers and delete all stopped containers before removal of pod. -#### **--ignore**, **-i** - -Ignore errors when specified pods are not in the container store. A user might -have decided to manually remove a pod which would lead to a failure during the -ExecStop directive of a systemd service referencing that pod. +@@option ignore #### **--latest**, **-l** diff --git a/docs/source/markdown/podman-pod-stop.1.md.in b/docs/source/markdown/podman-pod-stop.1.md.in index 879645cd9..3655c3938 100644 --- a/docs/source/markdown/podman-pod-stop.1.md.in +++ b/docs/source/markdown/podman-pod-stop.1.md.in @@ -15,11 +15,7 @@ Stop containers in one or more pods. You may use pod IDs or names as input. Stops all pods -#### **--ignore**, **-i** - -Ignore errors when specified pods are not in the container store. A user might -have decided to manually remove a pod which would lead to a failure during the -ExecStop directive of a systemd service referencing that pod. +@@option ignore #### **--latest**, **-l** diff --git a/docs/source/markdown/podman-rm.1.md.in b/docs/source/markdown/podman-rm.1.md.in index fa3031b29..c0fa94d82 100644 --- a/docs/source/markdown/podman-rm.1.md.in +++ b/docs/source/markdown/podman-rm.1.md.in @@ -56,11 +56,7 @@ Containers could have been created by a different container engine. In addition, forcing can be used to remove unusable containers, e.g. containers whose OCI runtime has become unavailable. -#### **--ignore**, **-i** - -Ignore errors when specified containers are not in the container store. A user -might have decided to manually remove a container which would lead to a failure -during the ExecStop directive of a systemd service referencing that container. +@@option ignore #### **--latest**, **-l** diff --git a/docs/source/markdown/podman-run.1.md.in b/docs/source/markdown/podman-run.1.md.in index 3e31ccc45..a0cd49d4e 100644 --- a/docs/source/markdown/podman-run.1.md.in +++ b/docs/source/markdown/podman-run.1.md.in @@ -1034,13 +1034,7 @@ If the location of the volume from the source container overlaps with data residing on a target container, then the volume hides that data on the target. -#### **--workdir**, **-w**=*dir* - -Working directory inside the container. - -The default working directory for running binaries within a container is the root directory (**/**). -The image developer can set a different default with the WORKDIR instruction. The operator -can override the working directory by using the **-w** option. +@@option workdir ## Exit Status diff --git a/docs/source/markdown/podman-stop.1.md.in b/docs/source/markdown/podman-stop.1.md.in index 04fc9387f..9aaccdfaa 100644 --- a/docs/source/markdown/podman-stop.1.md.in +++ b/docs/source/markdown/podman-stop.1.md.in @@ -47,11 +47,7 @@ Valid filters are listed below: | pod | [Pod] name or full or partial ID of pod | | network | [Network] name or full ID of network | -#### **--ignore**, **-i** - -Ignore errors when specified containers are not in the container store. A user -might have decided to manually remove a container which would lead to a failure -during the ExecStop directive of a systemd service referencing that container. +@@option ignore #### **--latest**, **-l** diff --git a/libpod/info.go b/libpod/info.go index 8db6df8cc..1990dc044 100644 --- a/libpod/info.go +++ b/libpod/info.go @@ -1,6 +1,3 @@ -//go:build linux -// +build linux - package libpod import ( @@ -8,27 +5,21 @@ import ( "bytes" "errors" "fmt" - "io/ioutil" "math" "os" - "os/exec" "runtime" - "strconv" "strings" "syscall" "time" "github.com/containers/buildah" - "github.com/containers/common/pkg/apparmor" - "github.com/containers/common/pkg/cgroups" - "github.com/containers/common/pkg/seccomp" + "github.com/containers/buildah/pkg/util" "github.com/containers/image/v5/pkg/sysregistriesv2" "github.com/containers/podman/v4/libpod/define" "github.com/containers/podman/v4/libpod/linkmode" "github.com/containers/podman/v4/pkg/rootless" "github.com/containers/storage" "github.com/containers/storage/pkg/system" - "github.com/opencontainers/selinux/go-selinux" "github.com/sirupsen/logrus" ) @@ -94,7 +85,7 @@ func (r *Runtime) hostInfo() (*define.HostInfo, error) { hostDistributionInfo := r.GetHostDistributionInfo() - kv, err := readKernelVersion() + kv, err := util.ReadKernelVersion() if err != nil { return nil, fmt.Errorf("error reading kernel version: %w", err) } @@ -104,94 +95,30 @@ func (r *Runtime) hostInfo() (*define.HostInfo, error) { return nil, fmt.Errorf("error getting hostname: %w", err) } - seccompProfilePath, err := DefaultSeccompPath() - if err != nil { - return nil, fmt.Errorf("error getting Seccomp profile path: %w", err) - } - - // Cgroups version - unified, err := cgroups.IsCgroup2UnifiedMode() - if err != nil { - return nil, fmt.Errorf("error reading cgroups mode: %w", err) - } - - // Get Map of all available controllers - availableControllers, err := cgroups.GetAvailableControllers(nil, unified) - if err != nil { - return nil, fmt.Errorf("error getting available cgroup controllers: %w", err) - } cpuUtil, err := getCPUUtilization() if err != nil { return nil, err } info := define.HostInfo{ - Arch: runtime.GOARCH, - BuildahVersion: buildah.Version, - CgroupManager: r.config.Engine.CgroupManager, - CgroupControllers: availableControllers, - Linkmode: linkmode.Linkmode(), - CPUs: runtime.NumCPU(), - CPUUtilization: cpuUtil, - Distribution: hostDistributionInfo, - LogDriver: r.config.Containers.LogDriver, - EventLogger: r.eventer.String(), - Hostname: host, - IDMappings: define.IDMappings{}, - Kernel: kv, - MemFree: mi.MemFree, - MemTotal: mi.MemTotal, - NetworkBackend: r.config.Network.NetworkBackend, - OS: runtime.GOOS, - Security: define.SecurityInfo{ - AppArmorEnabled: apparmor.IsEnabled(), - DefaultCapabilities: strings.Join(r.config.Containers.DefaultCapabilities, ","), - Rootless: rootless.IsRootless(), - SECCOMPEnabled: seccomp.IsEnabled(), - SECCOMPProfilePath: seccompProfilePath, - SELinuxEnabled: selinux.GetEnabled(), - }, - Slirp4NetNS: define.SlirpInfo{}, - SwapFree: mi.SwapFree, - SwapTotal: mi.SwapTotal, - } - - cgroupVersion := "v1" - if unified { - cgroupVersion = "v2" - } - info.CgroupsVersion = cgroupVersion - - slirp4netnsPath := r.config.Engine.NetworkCmdPath - if slirp4netnsPath == "" { - slirp4netnsPath, _ = exec.LookPath("slirp4netns") - } - if slirp4netnsPath != "" { - version, err := programVersion(slirp4netnsPath) - if err != nil { - logrus.Warnf("Failed to retrieve program version for %s: %v", slirp4netnsPath, err) - } - program := define.SlirpInfo{ - Executable: slirp4netnsPath, - Package: packageVersion(slirp4netnsPath), - Version: version, - } - info.Slirp4NetNS = program - } - - if rootless.IsRootless() { - uidmappings, err := rootless.ReadMappingsProc("/proc/self/uid_map") - if err != nil { - return nil, fmt.Errorf("error reading uid mappings: %w", err) - } - gidmappings, err := rootless.ReadMappingsProc("/proc/self/gid_map") - if err != nil { - return nil, fmt.Errorf("error reading gid mappings: %w", err) - } - idmappings := define.IDMappings{ - GIDMap: gidmappings, - UIDMap: uidmappings, - } - info.IDMappings = idmappings + Arch: runtime.GOARCH, + BuildahVersion: buildah.Version, + Linkmode: linkmode.Linkmode(), + CPUs: runtime.NumCPU(), + CPUUtilization: cpuUtil, + Distribution: hostDistributionInfo, + LogDriver: r.config.Containers.LogDriver, + EventLogger: r.eventer.String(), + Hostname: host, + Kernel: kv, + MemFree: mi.MemFree, + MemTotal: mi.MemTotal, + NetworkBackend: r.config.Network.NetworkBackend, + OS: runtime.GOOS, + SwapFree: mi.SwapFree, + SwapTotal: mi.SwapTotal, + } + if err := r.setPlatformHostInfo(&info); err != nil { + return nil, err } conmonInfo, ociruntimeInfo, err := r.defaultOCIRuntime.RuntimeInfo() @@ -202,7 +129,7 @@ func (r *Runtime) hostInfo() (*define.HostInfo, error) { info.OCIRuntime = ociruntimeInfo } - duration, err := procUptime() + duration, err := util.ReadUptime() if err != nil { return nil, fmt.Errorf("error reading up time: %w", err) } @@ -332,31 +259,6 @@ func (r *Runtime) storeInfo() (*define.StoreInfo, error) { return &info, nil } -func readKernelVersion() (string, error) { - buf, err := ioutil.ReadFile("/proc/version") - if err != nil { - return "", err - } - f := bytes.Fields(buf) - if len(f) < 3 { - return string(bytes.TrimSpace(buf)), nil - } - return string(f[2]), nil -} - -func procUptime() (time.Duration, error) { - var zero time.Duration - buf, err := ioutil.ReadFile("/proc/uptime") - if err != nil { - return zero, err - } - f := bytes.Fields(buf) - if len(f) < 1 { - return zero, errors.New("unable to parse uptime from /proc/uptime") - } - return time.ParseDuration(string(f[0]) + "s") -} - // GetHostDistributionInfo returns a map containing the host's distribution and version func (r *Runtime) GetHostDistributionInfo() define.DistributionInfo { // Populate values in case we cannot find the values @@ -388,43 +290,3 @@ func (r *Runtime) GetHostDistributionInfo() define.DistributionInfo { } return dist } - -// getCPUUtilization Returns a CPUUsage object that summarizes CPU -// usage for userspace, system, and idle time. -func getCPUUtilization() (*define.CPUUsage, error) { - f, err := os.Open("/proc/stat") - if err != nil { - return nil, err - } - defer f.Close() - scanner := bufio.NewScanner(f) - // Read first line of /proc/stat that has entries for system ("cpu" line) - for scanner.Scan() { - break - } - // column 1 is user, column 3 is system, column 4 is idle - stats := strings.Fields(scanner.Text()) - return statToPercent(stats) -} - -func statToPercent(stats []string) (*define.CPUUsage, error) { - userTotal, err := strconv.ParseFloat(stats[1], 64) - if err != nil { - return nil, fmt.Errorf("unable to parse user value %q: %w", stats[1], err) - } - systemTotal, err := strconv.ParseFloat(stats[3], 64) - if err != nil { - return nil, fmt.Errorf("unable to parse system value %q: %w", stats[3], err) - } - idleTotal, err := strconv.ParseFloat(stats[4], 64) - if err != nil { - return nil, fmt.Errorf("unable to parse idle value %q: %w", stats[4], err) - } - total := userTotal + systemTotal + idleTotal - s := define.CPUUsage{ - UserPercent: math.Round((userTotal/total*100)*100) / 100, - SystemPercent: math.Round((systemTotal/total*100)*100) / 100, - IdlePercent: math.Round((idleTotal/total*100)*100) / 100, - } - return &s, nil -} diff --git a/libpod/info_freebsd.go b/libpod/info_freebsd.go new file mode 100644 index 000000000..ef7b6817c --- /dev/null +++ b/libpod/info_freebsd.go @@ -0,0 +1,40 @@ +package libpod + +import ( + "fmt" + "unsafe" + + "github.com/containers/podman/v4/libpod/define" + "golang.org/x/sys/unix" +) + +func (r *Runtime) setPlatformHostInfo(info *define.HostInfo) error { + return nil +} + +func timeToPercent(time uint64, total uint64) float64 { + return 100.0 * float64(time) / float64(total) +} + +// getCPUUtilization Returns a CPUUsage object that summarizes CPU +// usage for userspace, system, and idle time. +func getCPUUtilization() (*define.CPUUsage, error) { + buf, err := unix.SysctlRaw("kern.cp_time") + if err != nil { + return nil, fmt.Errorf("error reading sysctl kern.cp_time: %w", err) + } + + var total uint64 = 0 + var times [unix.CPUSTATES]uint64 + + for i := 0; i < unix.CPUSTATES; i++ { + val := *(*uint64)(unsafe.Pointer(&buf[8*i])) + times[i] = val + total += val + } + return &define.CPUUsage{ + UserPercent: timeToPercent(times[unix.CP_USER], total), + SystemPercent: timeToPercent(times[unix.CP_SYS], total), + IdlePercent: timeToPercent(times[unix.CP_IDLE], total), + }, nil +} diff --git a/libpod/info_linux.go b/libpod/info_linux.go new file mode 100644 index 000000000..801dcdb43 --- /dev/null +++ b/libpod/info_linux.go @@ -0,0 +1,132 @@ +package libpod + +import ( + "bufio" + "fmt" + "math" + "os" + "os/exec" + "strconv" + "strings" + + "github.com/containers/common/pkg/apparmor" + "github.com/containers/common/pkg/cgroups" + "github.com/containers/common/pkg/seccomp" + "github.com/containers/podman/v4/libpod/define" + "github.com/containers/podman/v4/pkg/rootless" + "github.com/opencontainers/selinux/go-selinux" + "github.com/sirupsen/logrus" +) + +func (r *Runtime) setPlatformHostInfo(info *define.HostInfo) error { + seccompProfilePath, err := DefaultSeccompPath() + if err != nil { + return fmt.Errorf("error getting Seccomp profile path: %w", err) + } + + // Cgroups version + unified, err := cgroups.IsCgroup2UnifiedMode() + if err != nil { + return fmt.Errorf("error reading cgroups mode: %w", err) + } + + // Get Map of all available controllers + availableControllers, err := cgroups.GetAvailableControllers(nil, unified) + if err != nil { + return fmt.Errorf("error getting available cgroup controllers: %w", err) + } + + info.CgroupManager = r.config.Engine.CgroupManager + info.CgroupControllers = availableControllers + info.IDMappings = define.IDMappings{} + info.Security = define.SecurityInfo{ + AppArmorEnabled: apparmor.IsEnabled(), + DefaultCapabilities: strings.Join(r.config.Containers.DefaultCapabilities, ","), + Rootless: rootless.IsRootless(), + SECCOMPEnabled: seccomp.IsEnabled(), + SECCOMPProfilePath: seccompProfilePath, + SELinuxEnabled: selinux.GetEnabled(), + } + info.Slirp4NetNS = define.SlirpInfo{} + + cgroupVersion := "v1" + if unified { + cgroupVersion = "v2" + } + info.CgroupsVersion = cgroupVersion + + slirp4netnsPath := r.config.Engine.NetworkCmdPath + if slirp4netnsPath == "" { + slirp4netnsPath, _ = exec.LookPath("slirp4netns") + } + if slirp4netnsPath != "" { + version, err := programVersion(slirp4netnsPath) + if err != nil { + logrus.Warnf("Failed to retrieve program version for %s: %v", slirp4netnsPath, err) + } + program := define.SlirpInfo{ + Executable: slirp4netnsPath, + Package: packageVersion(slirp4netnsPath), + Version: version, + } + info.Slirp4NetNS = program + } + + if rootless.IsRootless() { + uidmappings, err := rootless.ReadMappingsProc("/proc/self/uid_map") + if err != nil { + return fmt.Errorf("error reading uid mappings: %w", err) + } + gidmappings, err := rootless.ReadMappingsProc("/proc/self/gid_map") + if err != nil { + return fmt.Errorf("error reading gid mappings: %w", err) + } + idmappings := define.IDMappings{ + GIDMap: gidmappings, + UIDMap: uidmappings, + } + info.IDMappings = idmappings + } + + return nil +} + +func statToPercent(stats []string) (*define.CPUUsage, error) { + userTotal, err := strconv.ParseFloat(stats[1], 64) + if err != nil { + return nil, fmt.Errorf("unable to parse user value %q: %w", stats[1], err) + } + systemTotal, err := strconv.ParseFloat(stats[3], 64) + if err != nil { + return nil, fmt.Errorf("unable to parse system value %q: %w", stats[3], err) + } + idleTotal, err := strconv.ParseFloat(stats[4], 64) + if err != nil { + return nil, fmt.Errorf("unable to parse idle value %q: %w", stats[4], err) + } + total := userTotal + systemTotal + idleTotal + s := define.CPUUsage{ + UserPercent: math.Round((userTotal/total*100)*100) / 100, + SystemPercent: math.Round((systemTotal/total*100)*100) / 100, + IdlePercent: math.Round((idleTotal/total*100)*100) / 100, + } + return &s, nil +} + +// getCPUUtilization Returns a CPUUsage object that summarizes CPU +// usage for userspace, system, and idle time. +func getCPUUtilization() (*define.CPUUsage, error) { + f, err := os.Open("/proc/stat") + if err != nil { + return nil, err + } + defer f.Close() + scanner := bufio.NewScanner(f) + // Read first line of /proc/stat that has entries for system ("cpu" line) + for scanner.Scan() { + break + } + // column 1 is user, column 3 is system, column 4 is idle + stats := strings.Fields(scanner.Text()) + return statToPercent(stats) +} diff --git a/libpod/info_unsupported.go b/libpod/info_unsupported.go index 53ee4b32f..0aed51247 100644 --- a/libpod/info_unsupported.go +++ b/libpod/info_unsupported.go @@ -1,5 +1,5 @@ -//go:build !linux -// +build !linux +//go:build !linux && !freebsd +// +build !linux,!freebsd package libpod diff --git a/pkg/api/handlers/compat/auth.go b/pkg/api/handlers/compat/auth.go index 37d2b784d..ee478b9e3 100644 --- a/pkg/api/handlers/compat/auth.go +++ b/pkg/api/handlers/compat/auth.go @@ -1,7 +1,6 @@ package compat import ( - "context" "encoding/json" "errors" "fmt" @@ -44,7 +43,7 @@ func Auth(w http.ResponseWriter, r *http.Request) { fmt.Println("Authenticating with existing credentials...") registry := stripAddressOfScheme(authConfig.ServerAddress) - if err := DockerClient.CheckAuth(context.Background(), sysCtx, authConfig.Username, authConfig.Password, registry); err == nil { + if err := DockerClient.CheckAuth(r.Context(), sysCtx, authConfig.Username, authConfig.Password, registry); err == nil { utils.WriteResponse(w, http.StatusOK, entities.AuthReport{ IdentityToken: "", Status: "Login Succeeded", diff --git a/pkg/api/handlers/compat/images_build.go b/pkg/api/handlers/compat/images_build.go index a00f0b089..020991cc7 100644 --- a/pkg/api/handlers/compat/images_build.go +++ b/pkg/api/handlers/compat/images_build.go @@ -694,7 +694,7 @@ func BuildImage(w http.ResponseWriter, r *http.Request) { success bool ) - runCtx, cancel := context.WithCancel(context.Background()) + runCtx, cancel := context.WithCancel(r.Context()) go func() { defer cancel() imageID, _, err = runtime.Build(r.Context(), buildOptions, containerFiles...) diff --git a/pkg/api/handlers/libpod/containers_create.go b/pkg/api/handlers/libpod/containers_create.go index 1307c267a..429f45f91 100644 --- a/pkg/api/handlers/libpod/containers_create.go +++ b/pkg/api/handlers/libpod/containers_create.go @@ -1,7 +1,6 @@ package libpod import ( - "context" "encoding/json" "fmt" "net/http" @@ -63,12 +62,12 @@ func CreateContainer(w http.ResponseWriter, r *http.Request) { utils.InternalServerError(w, err) return } - rtSpec, spec, opts, err := generate.MakeContainer(context.Background(), runtime, &sg, false, nil) + rtSpec, spec, opts, err := generate.MakeContainer(r.Context(), runtime, &sg, false, nil) if err != nil { utils.InternalServerError(w, err) return } - ctr, err := generate.ExecuteCreate(context.Background(), runtime, rtSpec, spec, false, opts...) + ctr, err := generate.ExecuteCreate(r.Context(), runtime, rtSpec, spec, false, opts...) if err != nil { utils.InternalServerError(w, err) return diff --git a/pkg/api/handlers/libpod/images_pull.go b/pkg/api/handlers/libpod/images_pull.go index 7e24ae5ac..57b2e3a78 100644 --- a/pkg/api/handlers/libpod/images_pull.go +++ b/pkg/api/handlers/libpod/images_pull.go @@ -82,17 +82,32 @@ func ImagesPull(w http.ResponseWriter, r *http.Request) { pullOptions.IdentityToken = authConf.IdentityToken } - writer := channel.NewWriter(make(chan []byte)) - defer writer.Close() - - pullOptions.Writer = writer - pullPolicy, err := config.ParsePullPolicy(query.PullPolicy) if err != nil { utils.Error(w, http.StatusBadRequest, err) return } + // Let's keep thing simple when running in quiet mode and pull directly. + if query.Quiet { + images, err := runtime.LibimageRuntime().Pull(r.Context(), query.Reference, pullPolicy, pullOptions) + var report entities.ImagePullReport + if err != nil { + report.Error = err.Error() + } + for _, image := range images { + report.Images = append(report.Images, image.ID()) + // Pull last ID from list and publish in 'id' stanza. This maintains previous API contract + report.ID = image.ID() + } + utils.WriteResponse(w, http.StatusOK, report) + return + } + + writer := channel.NewWriter(make(chan []byte)) + defer writer.Close() + pullOptions.Writer = writer + var pulledImages []*libimage.Image var pullError error runCtx, cancel := context.WithCancel(r.Context()) @@ -118,10 +133,8 @@ func ImagesPull(w http.ResponseWriter, r *http.Request) { select { case s := <-writer.Chan(): report.Stream = string(s) - if !query.Quiet { - if err := enc.Encode(report); err != nil { - logrus.Warnf("Failed to encode json: %v", err) - } + if err := enc.Encode(report); err != nil { + logrus.Warnf("Failed to encode json: %v", err) } flush() case <-runCtx.Done(): diff --git a/pkg/api/handlers/libpod/images_push.go b/pkg/api/handlers/libpod/images_push.go index e931fd2f9..be6f5b131 100644 --- a/pkg/api/handlers/libpod/images_push.go +++ b/pkg/api/handlers/libpod/images_push.go @@ -90,7 +90,7 @@ func PushImage(w http.ResponseWriter, r *http.Request) { // 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 { + if err := imageEngine.Push(r.Context(), source, destination, options); err != nil { utils.Error(w, http.StatusBadRequest, fmt.Errorf("error pushing image %q: %w", destination, err)) return } diff --git a/pkg/api/handlers/libpod/manifests.go b/pkg/api/handlers/libpod/manifests.go index fa83bbfe1..8391def5c 100644 --- a/pkg/api/handlers/libpod/manifests.go +++ b/pkg/api/handlers/libpod/manifests.go @@ -293,7 +293,7 @@ func ManifestPushV3(w http.ResponseWriter, r *http.Request) { options.SkipTLSVerify = types.NewOptionalBool(!query.TLSVerify) } imageEngine := abi.ImageEngine{Libpod: runtime} - digest, err := imageEngine.ManifestPush(context.Background(), source, query.Destination, options) + digest, err := imageEngine.ManifestPush(r.Context(), source, query.Destination, options) if err != nil { utils.Error(w, http.StatusBadRequest, fmt.Errorf("error pushing image %q: %w", query.Destination, err)) return @@ -367,7 +367,7 @@ func ManifestPush(w http.ResponseWriter, r *http.Request) { // Let's keep thing simple when running in quiet mode and push directly. if query.Quiet { - digest, err := imageEngine.ManifestPush(context.Background(), source, destination, options) + digest, err := imageEngine.ManifestPush(r.Context(), source, destination, options) if err != nil { utils.Error(w, http.StatusBadRequest, fmt.Errorf("error pushing image %q: %w", destination, err)) return diff --git a/pkg/api/handlers/types.go b/pkg/api/handlers/types.go index b533e131c..6d245d950 100644 --- a/pkg/api/handlers/types.go +++ b/pkg/api/handlers/types.go @@ -162,7 +162,7 @@ type ExecStartConfig struct { func ImageDataToImageInspect(ctx context.Context, l *libimage.Image) (*ImageInspect, error) { options := &libimage.InspectOptions{WithParent: true, WithSize: true} - info, err := l.Inspect(context.Background(), options) + info, err := l.Inspect(ctx, options) if err != nil { return nil, err } diff --git a/pkg/bindings/images/pull.go b/pkg/bindings/images/pull.go index 1a4aa3038..109981c63 100644 --- a/pkg/bindings/images/pull.go +++ b/pkg/bindings/images/pull.go @@ -6,7 +6,6 @@ import ( "errors" "fmt" "io" - "io/ioutil" "net/http" "os" "strconv" @@ -57,10 +56,14 @@ func Pull(ctx context.Context, rawImage string, options *PullOptions) ([]string, return nil, response.Process(err) } - // Historically pull writes status to stderr - stderr := io.Writer(os.Stderr) + var writer io.Writer if options.GetQuiet() { - stderr = ioutil.Discard + writer = io.Discard + } else if progressWriter := options.GetProgressWriter(); progressWriter != nil { + writer = progressWriter + } else { + // Historically push writes status to stderr + writer = os.Stderr } dec := json.NewDecoder(response.Body) @@ -84,7 +87,7 @@ func Pull(ctx context.Context, rawImage string, options *PullOptions) ([]string, switch { case report.Stream != "": - fmt.Fprint(stderr, report.Stream) + fmt.Fprint(writer, report.Stream) case report.Error != "": pullErrors = append(pullErrors, errors.New(report.Error)) case len(report.Images) > 0: diff --git a/pkg/bindings/images/push.go b/pkg/bindings/images/push.go index 5069dd780..f1e059f8c 100644 --- a/pkg/bindings/images/push.go +++ b/pkg/bindings/images/push.go @@ -6,7 +6,6 @@ import ( "errors" "fmt" "io" - "io/ioutil" "net/http" "os" "strconv" @@ -58,12 +57,14 @@ func Push(ctx context.Context, source string, destination string, options *PushO return response.Process(err) } - // Historically push writes status to stderr - writer := io.Writer(os.Stderr) + var writer io.Writer if options.GetQuiet() { - writer = ioutil.Discard + writer = io.Discard } else if progressWriter := options.GetProgressWriter(); progressWriter != nil { writer = progressWriter + } else { + // Historically push writes status to stderr + writer = os.Stderr } dec := json.NewDecoder(response.Body) diff --git a/pkg/bindings/images/types.go b/pkg/bindings/images/types.go index 7b28c499e..3ecfb9e09 100644 --- a/pkg/bindings/images/types.go +++ b/pkg/bindings/images/types.go @@ -182,6 +182,8 @@ type PullOptions struct { Policy *string // Password for authenticating against the registry. Password *string + // ProgressWriter is a writer where pull progress are sent. + ProgressWriter *io.Writer // Quiet can be specified to suppress pull progress when pulling. Ignored // for remote calls. Quiet *bool diff --git a/pkg/bindings/images/types_pull_options.go b/pkg/bindings/images/types_pull_options.go index 4cd525185..c1a88fd9e 100644 --- a/pkg/bindings/images/types_pull_options.go +++ b/pkg/bindings/images/types_pull_options.go @@ -2,6 +2,7 @@ package images import ( + "io" "net/url" "github.com/containers/podman/v4/pkg/bindings/internal/util" @@ -107,6 +108,21 @@ func (o *PullOptions) GetPassword() string { return *o.Password } +// WithProgressWriter set field ProgressWriter to given value +func (o *PullOptions) WithProgressWriter(value io.Writer) *PullOptions { + o.ProgressWriter = &value + return o +} + +// GetProgressWriter returns value of field ProgressWriter +func (o *PullOptions) GetProgressWriter() io.Writer { + if o.ProgressWriter == nil { + var z io.Writer + return z + } + return *o.ProgressWriter +} + // WithQuiet set field Quiet to given value func (o *PullOptions) WithQuiet(value bool) *PullOptions { o.Quiet = &value diff --git a/pkg/bindings/manifests/manifests.go b/pkg/bindings/manifests/manifests.go index 49e4089f5..0163d21a0 100644 --- a/pkg/bindings/manifests/manifests.go +++ b/pkg/bindings/manifests/manifests.go @@ -182,12 +182,14 @@ func Push(ctx context.Context, name, destination string, options *images.PushOpt return "", response.Process(err) } - // Historically push writes status to stderr - writer := io.Writer(os.Stderr) + var writer io.Writer if options.GetQuiet() { writer = io.Discard } else if progressWriter := options.GetProgressWriter(); progressWriter != nil { writer = progressWriter + } else { + // Historically push writes status to stderr + writer = os.Stderr } dec := json.NewDecoder(response.Body) diff --git a/pkg/bindings/test/images_test.go b/pkg/bindings/test/images_test.go index 9c9796661..53c5a1e83 100644 --- a/pkg/bindings/test/images_test.go +++ b/pkg/bindings/test/images_test.go @@ -1,11 +1,14 @@ package bindings_test import ( + "bytes" + "fmt" "net/http" "os" "path/filepath" "time" + podmanRegistry "github.com/containers/podman/v4/hack/podman-registry-go" "github.com/containers/podman/v4/pkg/bindings" "github.com/containers/podman/v4/pkg/bindings/containers" "github.com/containers/podman/v4/pkg/bindings/images" @@ -362,9 +365,14 @@ var _ = Describe("Podman images", func() { It("Image Pull", func() { rawImage := "docker.io/library/busybox:latest" - pulledImages, err := images.Pull(bt.conn, rawImage, nil) + var writer bytes.Buffer + pullOpts := new(images.PullOptions).WithProgressWriter(&writer) + pulledImages, err := images.Pull(bt.conn, rawImage, pullOpts) Expect(err).NotTo(HaveOccurred()) Expect(len(pulledImages)).To(Equal(1)) + output := writer.String() + Expect(output).To(ContainSubstring("Trying to pull ")) + Expect(output).To(ContainSubstring("Getting image source signatures")) exists, err := images.Exists(bt.conn, rawImage, nil) Expect(err).NotTo(HaveOccurred()) @@ -380,7 +388,19 @@ var _ = Describe("Podman images", func() { }) It("Image Push", func() { - Skip("TODO: implement test for image push to registry") + registry, err := podmanRegistry.Start() + Expect(err).To(BeNil()) + + var writer bytes.Buffer + pushOpts := new(images.PushOptions).WithUsername(registry.User).WithPassword(registry.Password).WithSkipTLSVerify(true).WithProgressWriter(&writer).WithQuiet(false) + err = images.Push(bt.conn, alpine.name, fmt.Sprintf("localhost:%s/test:latest", registry.Port), pushOpts) + Expect(err).ToNot(HaveOccurred()) + + output := writer.String() + 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")) }) It("Build no options", func() { diff --git a/pkg/bindings/test/manifests_test.go b/pkg/bindings/test/manifests_test.go index 6a34ef5a6..d6749f920 100644 --- a/pkg/bindings/test/manifests_test.go +++ b/pkg/bindings/test/manifests_test.go @@ -1,9 +1,12 @@ package bindings_test import ( + "bytes" + "fmt" "net/http" "time" + podmanRegistry "github.com/containers/podman/v4/hack/podman-registry-go" "github.com/containers/podman/v4/pkg/bindings" "github.com/containers/podman/v4/pkg/bindings/images" "github.com/containers/podman/v4/pkg/bindings/manifests" @@ -12,7 +15,7 @@ import ( "github.com/onsi/gomega/gexec" ) -var _ = Describe("podman manifest", func() { +var _ = Describe("Podman manifests", func() { var ( bt *bindingTest s *gexec.Session @@ -172,7 +175,21 @@ var _ = Describe("podman manifest", func() { Expect(list.Manifests[0].Platform.OS).To(Equal("foo")) }) - It("push manifest", func() { - Skip("TODO: implement test for manifest push to registry") + It("Manifest Push", func() { + registry, err := podmanRegistry.Start() + Expect(err).To(BeNil()) + + name := "quay.io/libpod/foobar:latest" + _, err = manifests.Create(bt.conn, name, []string{alpine.name}, nil) + Expect(err).ToNot(HaveOccurred()) + + var writer bytes.Buffer + pushOpts := new(images.PushOptions).WithUsername(registry.User).WithPassword(registry.Password).WithAll(true).WithSkipTLSVerify(true).WithProgressWriter(&writer).WithQuiet(false) + _, err = manifests.Push(bt.conn, name, fmt.Sprintf("localhost:%s/test:latest", registry.Port), pushOpts) + Expect(err).ToNot(HaveOccurred()) + + output := writer.String() + Expect(output).To(ContainSubstring("Writing manifest list to image destination")) + Expect(output).To(ContainSubstring("Storing list signatures")) }) }) diff --git a/pkg/domain/entities/images.go b/pkg/domain/entities/images.go index 21c1372b9..cad11b0ab 100644 --- a/pkg/domain/entities/images.go +++ b/pkg/domain/entities/images.go @@ -156,6 +156,8 @@ type ImagePullOptions struct { SkipTLSVerify types.OptionalBool // PullPolicy whether to pull new image PullPolicy config.PullPolicy + // Writer is used to display copy information including progress bars. + Writer io.Writer } // ImagePullReport is the response from pulling one or more images. diff --git a/pkg/domain/infra/abi/images.go b/pkg/domain/infra/abi/images.go index 77d1bf0db..f9839f62f 100644 --- a/pkg/domain/infra/abi/images.go +++ b/pkg/domain/infra/abi/images.go @@ -237,8 +237,9 @@ func (ir *ImageEngine) Pull(ctx context.Context, rawImage string, options entiti pullOptions.Variant = options.Variant pullOptions.SignaturePolicyPath = options.SignaturePolicy pullOptions.InsecureSkipTLSVerify = options.SkipTLSVerify + pullOptions.Writer = options.Writer - if !options.Quiet { + if !options.Quiet && pullOptions.Writer == nil { pullOptions.Writer = os.Stderr } diff --git a/pkg/domain/infra/tunnel/images.go b/pkg/domain/infra/tunnel/images.go index bb3014099..2716aaf2a 100644 --- a/pkg/domain/infra/tunnel/images.go +++ b/pkg/domain/infra/tunnel/images.go @@ -110,6 +110,7 @@ func (ir *ImageEngine) Pull(ctx context.Context, rawImage string, opts entities. options.WithAllTags(opts.AllTags).WithAuthfile(opts.Authfile).WithArch(opts.Arch).WithOS(opts.OS) options.WithVariant(opts.Variant).WithPassword(opts.Password) options.WithQuiet(opts.Quiet).WithUsername(opts.Username).WithPolicy(opts.PullPolicy.String()) + options.WithProgressWriter(opts.Writer) if s := opts.SkipTLSVerify; s != types.OptionalBoolUndefined { if s == types.OptionalBoolTrue { options.WithSkipTLSVerify(true) diff --git a/test/e2e/manifest_test.go b/test/e2e/manifest_test.go index 1c4aad710..60b72dcaa 100644 --- a/test/e2e/manifest_test.go +++ b/test/e2e/manifest_test.go @@ -350,6 +350,33 @@ var _ = Describe("Podman manifest", func() { Expect(foundZstdFile).To(BeTrue()) }) + It("push progress", func() { + SkipIfRemote("manifest push to dir not supported in remote mode") + + session := podmanTest.Podman([]string{"manifest", "create", "foo", imageList}) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + + dest := filepath.Join(podmanTest.TempDir, "pushed") + err := os.MkdirAll(dest, os.ModePerm) + Expect(err).To(BeNil()) + defer func() { + os.RemoveAll(dest) + }() + + session = podmanTest.Podman([]string{"push", "foo", "-q", "dir:" + dest}) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + Expect(session.ErrorToString()).To(BeEmpty()) + + session = podmanTest.Podman([]string{"push", "foo", "dir:" + dest}) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + output := session.ErrorToString() + Expect(output).To(ContainSubstring("Writing manifest list to image destination")) + Expect(output).To(ContainSubstring("Storing list signatures")) + }) + It("authenticated push", func() { registryOptions := &podmanRegistry.Options{ Image: "docker-archive:" + imageTarPath(REGISTRY_IMAGE), diff --git a/test/e2e/pull_test.go b/test/e2e/pull_test.go index 12f14fdc8..ba717f393 100644 --- a/test/e2e/pull_test.go +++ b/test/e2e/pull_test.go @@ -545,4 +545,18 @@ var _ = Describe("Podman pull", func() { Expect(data[0]).To(HaveField("Os", runtime.GOOS)) Expect(data[0]).To(HaveField("Architecture", "arm64")) }) + + It("podman pull progress", func() { + session := podmanTest.Podman([]string{"pull", ALPINE}) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + output := session.ErrorToString() + Expect(output).To(ContainSubstring("Getting image source signatures")) + Expect(output).To(ContainSubstring("Copying blob ")) + + session = podmanTest.Podman([]string{"pull", "-q", ALPINE}) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + Expect(session.ErrorToString()).To(BeEmpty()) + }) }) diff --git a/test/e2e/run_memory_test.go b/test/e2e/run_memory_test.go index 083020f08..3f611040b 100644 --- a/test/e2e/run_memory_test.go +++ b/test/e2e/run_memory_test.go @@ -66,6 +66,24 @@ var _ = Describe("Podman run memory", func() { Expect(session.OutputToString()).To(Equal("41943040")) }) + It("podman run memory-swap test", func() { + var ( + session *PodmanSessionIntegration + expect string + ) + + if CGROUPSV2 { + session = podmanTest.Podman([]string{"run", "--memory=20m", "--memory-swap=30M", "--net=none", ALPINE, "sh", "-c", "cat /sys/fs/cgroup/$(sed -e 's|0::||' < /proc/self/cgroup)/memory.swap.max"}) + expect = "10485760" + } else { + session = podmanTest.Podman([]string{"run", "--memory=20m", "--memory-swap=30M", ALPINE, "cat", "/sys/fs/cgroup/memory/memory.memsw.limit_in_bytes"}) + expect = "31457280" + } + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + Expect(session.OutputToString()).To(Equal(expect)) + }) + for _, limit := range []string{"0", "15", "100"} { limit := limit // Keep this value in a proper scope testName := fmt.Sprintf("podman run memory-swappiness test(%s)", limit) |