diff options
163 files changed, 2209 insertions, 1410 deletions
diff --git a/.cirrus.yml b/.cirrus.yml index 8ae1bb2f2..2aae343e8 100644 --- a/.cirrus.yml +++ b/.cirrus.yml @@ -39,7 +39,7 @@ env: UBUNTU_NAME: "ubuntu-19" PRIOR_UBUNTU_NAME: "ubuntu-18" - _BUILT_IMAGE_SUFFIX: "libpod-6301182083727360" + _BUILT_IMAGE_SUFFIX: "libpod-6224667180531712" # From the packer output of 'build_vm_images_script' FEDORA_CACHE_IMAGE_NAME: "${FEDORA_NAME}-${_BUILT_IMAGE_SUFFIX}" PRIOR_FEDORA_CACHE_IMAGE_NAME: "${PRIOR_FEDORA_NAME}-${_BUILT_IMAGE_SUFFIX}" UBUNTU_CACHE_IMAGE_NAME: "${UBUNTU_NAME}-${_BUILT_IMAGE_SUFFIX}" @@ -156,6 +156,32 @@ gating_task: failed_branch_script: '$CIRRUS_WORKING_DIR/$SCRIPT_BASE/notice_branch_failure.sh' +# Ensure these container images can build +container_image_build_task: + alias: 'container_image_build' + depends_on: + - "gating" + + # Only run for PRs, quay.io will automatically build after bramch-push + only_if: $CIRRUS_BRANCH != $DEST_BRANCH + + matrix: + - name: "build in_podman image ${FEDORA_NAME} " + container: + dockerfile: Dockerfile + - name: "build in_podman image ${UBUNTU_NAME}" + container: + dockerfile: Dockerfile.ubuntu + - name: "build gate image $DEST_BRANCH branch" + container: + dockerfile: contrib/gate/Dockerfile + + container: + dockerfile: Dockerfile + + script: make install.remote + + # This task checks to make sure that we can still build an rpm from the # source code using contrib/rpm/podman.spec.in rpmbuild_task: @@ -389,8 +415,7 @@ testing_task: - "varlink_api" - "build_each_commit" - "build_without_cgo" - - allow_failures: $CI == 'true' + - "container_image_build" # Only test build cache-images, if that's what's requested only_if: >- @@ -681,6 +706,7 @@ test_build_cache_images_task: depends_on: - "gating" + - 'container_image_build' # VMs created by packer are not cleaned up by cirrus, must allow task to complete auto_cancellation: $CI != "true" @@ -782,6 +808,7 @@ success_task: - "varlink_api" - "build_each_commit" - "build_without_cgo" + - "container_image_build" - "meta" - "image_prune" - "testing" diff --git a/Dockerfile b/Dockerfile index f85c47937..623747295 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,117 +1,26 @@ -FROM golang:1.12 - -RUN apt-get update && apt-get install -y \ - apparmor \ - autoconf \ - automake \ - bison \ - build-essential \ - curl \ - e2fslibs-dev \ - file \ - gawk \ - gettext \ - go-md2man \ - iptables \ - pkg-config \ - libaio-dev \ - libcap-dev \ - libfuse-dev \ - libnet-dev \ - libnl-3-dev \ - libprotobuf-dev \ - libprotobuf-c-dev \ - libseccomp2 \ - libseccomp-dev \ - libtool \ - libudev-dev \ - protobuf-c-compiler \ - protobuf-compiler \ - libglib2.0-dev \ - libapparmor-dev \ - btrfs-tools \ - libdevmapper1.02.1 \ - libdevmapper-dev \ - libgpgme11-dev \ - liblzma-dev \ - netcat \ - socat \ - lsof \ - xz-utils \ - unzip \ - python3-yaml \ - --no-install-recommends \ - && apt-get clean - -# Install runc -ENV RUNC_COMMIT 029124da7af7360afa781a0234d1b083550f797c -RUN set -x \ - && export GOPATH="$(mktemp -d)" \ - && git clone https://github.com/opencontainers/runc.git "$GOPATH/src/github.com/opencontainers/runc" \ - && cd "$GOPATH/src/github.com/opencontainers/runc" \ - && git fetch origin --tags \ - && git checkout --detach -q "$RUNC_COMMIT" \ - && make static BUILDTAGS="seccomp selinux" \ - && cp runc /usr/bin/runc \ - && rm -rf "$GOPATH" - -# Install conmon -ENV CONMON_COMMIT 65fe0226d85b69fc9e527e376795c9791199153d -RUN set -x \ - && export GOPATH="$(mktemp -d)" \ - && git clone https://github.com/containers/conmon.git "$GOPATH/src/github.com/containers/conmon.git" \ - && cd "$GOPATH/src/github.com/containers/conmon.git" \ - && git fetch origin --tags \ - && git checkout --detach -q "$CONMON_COMMIT" \ - && make \ - && install -D -m 755 bin/conmon /usr/libexec/podman/conmon \ - && rm -rf "$GOPATH" - -# Install CNI plugins -ENV CNI_COMMIT 485be65581341430f9106a194a98f0f2412245fb -RUN set -x \ - && export GOPATH="$(mktemp -d)" GOCACHE="$(mktemp -d)" \ - && git clone https://github.com/containernetworking/plugins.git "$GOPATH/src/github.com/containernetworking/plugins" \ - && cd "$GOPATH/src/github.com/containernetworking/plugins" \ - && git checkout --detach -q "$CNI_COMMIT" \ - && ./build_linux.sh \ - && mkdir -p /usr/libexec/cni \ - && cp bin/* /usr/libexec/cni \ - && rm -rf "$GOPATH" - -# Install ginkgo -RUN set -x \ - && export GOPATH=/go \ - && go get -u github.com/onsi/ginkgo/ginkgo \ - && install -D -m 755 "$GOPATH"/bin/ginkgo /usr/bin/ - -# Install gomega -RUN set -x \ - && export GOPATH=/go \ - && go get github.com/onsi/gomega/... - -# Install latest stable criu version -RUN set -x \ - && cd /tmp \ - && git clone https://github.com/checkpoint-restore/criu.git \ - && cd criu \ - && make \ - && install -D -m 755 criu/criu /usr/sbin/ \ - && rm -rf /tmp/criu - -# Install cni config -#RUN make install.cni -RUN mkdir -p /etc/cni/net.d/ -COPY cni/87-podman-bridge.conflist /etc/cni/net.d/87-podman-bridge.conflist - -# Make sure we have some policy for pulling images -RUN mkdir -p /etc/containers && curl https://raw.githubusercontent.com/projectatomic/registries/master/registries.fedora -o /etc/containers/registries.conf - -COPY test/policy.json /etc/containers/policy.json -COPY test/redhat_sigstore.yaml /etc/containers/registries.d/registry.access.redhat.com.yaml - -ADD . /go/src/github.com/containers/libpod - -RUN set -x && cd /go/src/github.com/containers/libpod - -WORKDIR /go/src/github.com/containers/libpod +FROM registry.fedoraproject.org/fedora:latest + +# This container image is utilized by the containers CI automation system +# for building and testing libpod inside a container environment. +# It is assumed that the source to be tested will overwrite $GOSRC (below) +# at runtime. +ENV GOPATH=/var/tmp/go +ENV GOSRC=$GOPATH/src/github.com/containers/libpod +ENV SCRIPT_BASE=./contrib/cirrus +ENV PACKER_BASE=$SCRIPT_BASE/packer + +# Only add minimal tooling necessary to complete setup. +ADD /$SCRIPT_BASE $GOSRC/$SCRIPT_BASE +ADD /hack/install_catatonit.sh $GOSRC/hack/ +ADD /cni/*.conflist $GOSRC/cni/ +ADD /test/*.json $GOSRC/test/ +ADD /test/*.conf $GOSRC/test/ +WORKDIR $GOSRC + +# Re-use repositories and package setup as in VMs under CI +RUN bash $PACKER_BASE/fedora_packaging.sh && \ + dnf clean all && \ + rm -rf /var/cache/dnf + +# Mirror steps taken under CI +RUN bash -c 'source $GOSRC/$SCRIPT_BASE/lib.sh && install_test_configs' diff --git a/Dockerfile.centos b/Dockerfile.centos deleted file mode 100644 index f5a2b891c..000000000 --- a/Dockerfile.centos +++ /dev/null @@ -1,77 +0,0 @@ -FROM registry.centos.org/centos/centos:7 - -RUN yum -y install btrfs-progs-devel \ - atomic-registries \ - autoconf \ - automake \ - bzip2 \ - device-mapper-devel \ - findutils \ - file \ - git \ - glibc-static \ - glib2-devel \ - gnupg \ - golang \ - golang-github-cpuguy83-go-md2man \ - gpgme-devel \ - libassuan-devel \ - libseccomp-devel \ - libselinux-devel \ - libtool \ - containers-common \ - runc \ - make \ - lsof \ - which\ - golang-github-cpuguy83-go-md2man \ - nmap-ncat \ - xz \ - iptables && yum clean all - -# Install CNI plugins -ENV CNI_COMMIT 485be65581341430f9106a194a98f0f2412245fb -RUN set -x \ - && export GOPATH="$(mktemp -d)" GOCACHE="$(mktemp -d)" \ - && git clone https://github.com/containernetworking/plugins.git "$GOPATH/src/github.com/containernetworking/plugins" \ - && cd "$GOPATH/src/github.com/containernetworking/plugins" \ - && git checkout --detach -q "$CNI_COMMIT" \ - && ./build_linux.sh \ - && mkdir -p /usr/libexec/cni \ - && cp bin/* /usr/libexec/cni \ - && rm -rf "$GOPATH" - -# Install ginkgo -RUN set -x \ - && export GOPATH=/go \ - && go get -u github.com/onsi/ginkgo/ginkgo \ - && install -D -m 755 "$GOPATH"/bin/ginkgo /usr/bin/ - -# Install gomega -RUN set -x \ - && export GOPATH=/go \ - && go get github.com/onsi/gomega/... - -# Install conmon -ENV CONMON_COMMIT 6f3572558b97bc60dd8f8c7f0807748e6ce2c440 -RUN set -x \ - && export GOPATH="$(mktemp -d)" \ - && git clone https://github.com/containers/conmon.git "$GOPATH/src/github.com/containers/conmon.git" \ - && cd "$GOPATH/src/github.com/containers/conmon.git" \ - && git fetch origin --tags \ - && git checkout --detach -q "$CONMON_COMMIT" \ - && make \ - && install -D -m 755 bin/conmon /usr/libexec/podman/conmon \ - && rm -rf "$GOPATH" - -# Install cni config -#RUN make install.cni -RUN mkdir -p /etc/cni/net.d/ -COPY cni/87-podman-bridge.conflist /etc/cni/net.d/87-podman-bridge.conflist - -# Make sure we have some policy for pulling images -RUN mkdir -p /etc/containers -COPY test/policy.json /etc/containers/policy.json -COPY test/redhat_sigstore.yaml /etc/containers/registries.d/registry.access.redhat.com.yaml - -WORKDIR /go/src/github.com/containers/libpod diff --git a/Dockerfile.fedora b/Dockerfile.fedora deleted file mode 100644 index 45b2c3670..000000000 --- a/Dockerfile.fedora +++ /dev/null @@ -1,73 +0,0 @@ -FROM registry.fedoraproject.org/fedora:30 - -RUN dnf -y install btrfs-progs-devel \ - atomic-registries \ - autoconf \ - automake \ - bzip2 \ - device-mapper-devel \ - file \ - findutils \ - git \ - glib2-devel \ - glibc-static \ - gnupg \ - golang \ - golang-github-cpuguy83-go-md2man \ - gpgme-devel \ - libassuan-devel \ - libseccomp-devel \ - libselinux-devel \ - libtool \ - containers-common \ - runc \ - make \ - lsof \ - which\ - golang-github-cpuguy83-go-md2man \ - procps-ng \ - nmap-ncat \ - xz \ - slirp4netns \ - container-selinux \ - containernetworking-plugins \ - iproute \ - iptables && dnf clean all - -# Install ginkgo -RUN set -x \ - && export GOPATH=/go GOCACHE="$(mktemp -d)" \ - && go get -u github.com/onsi/ginkgo/ginkgo \ - && install -D -m 755 "$GOPATH"/bin/ginkgo /usr/bin/ - -# Install gomega -RUN set -x \ - && export GOPATH=/go GOCACHE="$(mktemp -d)" \ - && go get github.com/onsi/gomega/... - -# Install conmon -ENV CONMON_COMMIT 6f3572558b97bc60dd8f8c7f0807748e6ce2c440 -RUN set -x \ - && export GOPATH="$(mktemp -d)" GOCACHE="$(mktemp -d)" \ - && git clone https://github.com/containers/conmon.git "$GOPATH/src/github.com/containers/conmon.git" \ - && cd "$GOPATH/src/github.com/containers/conmon.git" \ - && git fetch origin --tags \ - && git checkout --detach -q "$CONMON_COMMIT" \ - && make \ - && install -D -m 755 bin/conmon /usr/libexec/podman/conmon \ - && rm -rf "$GOPATH" - -# Install cni config -#RUN make install.cni -RUN mkdir -p /etc/cni/net.d/ -COPY cni/87-podman-bridge.conflist /etc/cni/net.d/87-podman-bridge.conflist - -# Make sure we have some policy for pulling images -RUN mkdir -p /etc/containers -COPY test/policy.json /etc/containers/policy.json -COPY test/redhat_sigstore.yaml /etc/containers/registries.d/registry.access.redhat.com.yaml - -# Install varlink stuff -RUN pip3 install varlink - -WORKDIR /go/src/github.com/containers/libpod diff --git a/Dockerfile.ubuntu b/Dockerfile.ubuntu new file mode 100644 index 000000000..3a8f837b9 --- /dev/null +++ b/Dockerfile.ubuntu @@ -0,0 +1,29 @@ +# Must resemble $UBUNTU_BASE_IMAGE in ./contrib/cirrus/lib.sh +FROM ubuntu:latest + +# This container image is intended for building and testing libpod +# from inside a container environment. It is assumed that the source +# to be tested will overwrite $GOSRC (below) at runtime. +ENV GOPATH=/var/tmp/go +ENV GOSRC=$GOPATH/src/github.com/containers/libpod +ENV SCRIPT_BASE=./contrib/cirrus +ENV PACKER_BASE=$SCRIPT_BASE/packer + +RUN export DEBIAN_FRONTEND="noninteractive" && \ + apt-get -qq update --yes && \ + apt-get -qq upgrade --yes && \ + apt-get -qq install curl git && \ + apt-get -qq autoremove --yes && \ + rm -rf /var/cache/apt + +# Only add minimal tooling necessary to complete setup. +ADD / $GOSRC +WORKDIR $GOSRC + +# Re-use repositories and package setup as in VMs under CI +RUN bash $PACKER_BASE/ubuntu_packaging.sh && \ + apt-get -qq autoremove --yes && \ + rm -rf /var/cache/apt + +# Mirror steps taken under CI +RUN bash -c 'source $GOSRC/$SCRIPT_BASE/lib.sh && install_test_configs' @@ -470,25 +470,35 @@ changelog: ## Generate changelog .PHONY: install install: .gopathok install.bin install.remote install.man install.cni install.systemd ## Install binaries to system locations -.PHONY: install.remote -install.remote: podman-remote +.PHONY: install.remote-nobuild +install.remote-nobuild: install ${SELINUXOPT} -d -m 755 $(DESTDIR)$(BINDIR) install ${SELINUXOPT} -m 755 bin/podman-remote $(DESTDIR)$(BINDIR)/podman-remote test -z "${SELINUXOPT}" || chcon --verbose --reference=$(DESTDIR)$(BINDIR)/podman-remote bin/podman-remote -.PHONY: install.bin -install.bin: podman +.PHONY: install.remote +install.remote: podman-remote install.remote-nobuild + +.PHONY: install.bin-nobuild +install.bin-nobuild: install ${SELINUXOPT} -d -m 755 $(DESTDIR)$(BINDIR) install ${SELINUXOPT} -m 755 bin/podman $(DESTDIR)$(BINDIR)/podman test -z "${SELINUXOPT}" || chcon --verbose --reference=$(DESTDIR)$(BINDIR)/podman bin/podman -install.man: docs +.PHONY: install.bin +install.bin: podman install.bin-nobuild + +.PHONY: install.man-nobuild +install.man-nobuild: install ${SELINUXOPT} -d -m 755 $(DESTDIR)$(MANDIR)/man1 install ${SELINUXOPT} -d -m 755 $(DESTDIR)$(MANDIR)/man5 install ${SELINUXOPT} -m 644 $(filter %.1,$(MANPAGES_DEST)) -t $(DESTDIR)$(MANDIR)/man1 install ${SELINUXOPT} -m 644 $(filter %.5,$(MANPAGES_DEST)) -t $(DESTDIR)$(MANDIR)/man5 install ${SELINUXOPT} -m 644 docs/source/markdown/links/*1 -t $(DESTDIR)$(MANDIR)/man1 +.PHONY: install.man +install.man: docs install.man-nobuild + .PHONY: install.config install.config: install ${SELINUXOPT} -d -m 755 $(DESTDIR)$(SHAREDIR_CONTAINERS) @@ -5,7 +5,7 @@ Libpod provides a library for applications looking to use the Container Pod concept, popularized by Kubernetes. Libpod also contains the Pod Manager tool `(Podman)`. Podman manages pods, containers, container images, and container volumes. -* [Latest Version: 1.9.0](https://github.com/containers/libpod/releases/latest) +* [Latest Version: 1.9.1](https://github.com/containers/libpod/releases/latest) * [Continuous Integration:](contrib/cirrus/README.md) [![Build Status](https://api.cirrus-ci.com/github/containers/libpod.svg)](https://cirrus-ci.com/github/containers/libpod/master) * [GoDoc: ![GoDoc](https://godoc.org/github.com/containers/libpod/libpod?status.svg)](https://godoc.org/github.com/containers/libpod/libpod) * Automated continuous release downloads (including remote-client): diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index aef66545f..6657529b9 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -1,5 +1,14 @@ # Release Notes +## 1.9.1 +### Bugfixes +- Fixed a bug where healthchecks could become nonfunctional if container log paths were manually set with `--log-path` and multiple container logs were placed in the same directory ([#5915](https://github.com/containers/libpod/issues/5915)) +- Fixed a bug where rootless Podman could, when using an older `libpod.conf`, print numerous warning messages about an invalid CGroup manager config +- Fixed a bug where rootless Podman would sometimes fail to close the rootless user namespace when joining it ([#5873](https://github.com/containers/libpod/issues/5873)) + +### Misc +- Updated containers/common to v0.8.2 + ## 1.9.0 ### Features - Experimental support has been added for `podman run --userns=auto`, which automatically allocates a unique UID and GID range for the new container's user namespace diff --git a/cmd/podman/common/create.go b/cmd/podman/common/create.go index a0aed984c..0f9476754 100644 --- a/cmd/podman/common/create.go +++ b/cmd/podman/common/create.go @@ -49,9 +49,7 @@ func GetCreateFlags(cf *ContainerCLIOpts) *pflag.FlagSet { "cap-drop", []string{}, "Drop capabilities from the container", ) - cgroupNS := "" - createFlags.StringVar( - &cgroupNS, + createFlags.String( "cgroupns", containerConfig.CgroupNS(), "cgroup namespace to use", ) @@ -155,9 +153,7 @@ func GetCreateFlags(cf *ContainerCLIOpts) *pflag.FlagSet { "device-write-iops", []string{}, "Limit write rate (IO per second) to a device (e.g. --device-write-iops=/dev/sda:1000)", ) - createFlags.StringVar( - &cf.Entrypoint, - "entrypoint", "", + createFlags.String("entrypoint", "", "Overwrite the default ENTRYPOINT of the image", ) createFlags.StringArrayVarP( @@ -248,9 +244,7 @@ func GetCreateFlags(cf *ContainerCLIOpts) *pflag.FlagSet { "interactive", "i", false, "Keep STDIN open even if not attached", ) - ipcNS := "" - createFlags.StringVar( - &ipcNS, + createFlags.String( "ipc", containerConfig.IPCNS(), "IPC namespace to use", ) @@ -331,9 +325,7 @@ func GetCreateFlags(cf *ContainerCLIOpts) *pflag.FlagSet { "use `OS` instead of the running OS for choosing images", ) // markFlagHidden(createFlags, "override-os") - pid := "" - createFlags.StringVar( - &pid, + createFlags.String( "pid", containerConfig.PidNS(), "PID namespace to use", ) @@ -397,9 +389,7 @@ func GetCreateFlags(cf *ContainerCLIOpts) *pflag.FlagSet { "security-opt", containerConfig.SecurityOptions(), "Security Options", ) - shmSize := "" - createFlags.StringVar( - &shmSize, + createFlags.String( "shm-size", containerConfig.ShmSize(), "Size of /dev/shm "+sizeWithUnitFormat, ) @@ -464,15 +454,11 @@ func GetCreateFlags(cf *ContainerCLIOpts) *pflag.FlagSet { "user", "u", "", "Username or UID (format: <name|uid>[:<group|gid>])", ) - userNS := "" - createFlags.StringVar( - &userNS, + createFlags.String( "userns", containerConfig.Containers.UserNS, "User namespace to use", ) - utsNS := "" - createFlags.StringVar( - &utsNS, + createFlags.String( "uts", containerConfig.Containers.UTSNS, "UTS namespace to use", ) diff --git a/cmd/podman/common/create_opts.go b/cmd/podman/common/create_opts.go index 2f08bb6a6..c275b1674 100644 --- a/cmd/podman/common/create_opts.go +++ b/cmd/podman/common/create_opts.go @@ -31,7 +31,7 @@ type ContainerCLIOpts struct { DeviceReadIOPs []string DeviceWriteBPs []string DeviceWriteIOPs []string - Entrypoint string + Entrypoint *string env []string EnvHost bool EnvFile []string diff --git a/cmd/podman/common/inspect.go b/cmd/podman/common/inspect.go deleted file mode 100644 index dfc6fe679..000000000 --- a/cmd/podman/common/inspect.go +++ /dev/null @@ -1,18 +0,0 @@ -package common - -import ( - "github.com/containers/libpod/pkg/domain/entities" - "github.com/spf13/cobra" -) - -// AddInspectFlagSet takes a command and adds the inspect flags and returns an InspectOptions object -// Since this cannot live in `package main` it lives here until a better home is found -func AddInspectFlagSet(cmd *cobra.Command) *entities.InspectOptions { - opts := entities.InspectOptions{} - - flags := cmd.Flags() - flags.BoolVarP(&opts.Size, "size", "s", false, "Display total file size") - flags.StringVarP(&opts.Format, "format", "f", "", "Change the output format to a Go template") - - return &opts -} diff --git a/cmd/podman/common/ports.go b/cmd/podman/common/ports.go index a96bafabd..2092bbe53 100644 --- a/cmd/podman/common/ports.go +++ b/cmd/podman/common/ports.go @@ -9,10 +9,10 @@ func verifyExpose(expose []string) error { // add the expose ports from the user (--expose) // can be single or a range for _, expose := range expose { - //support two formats for expose, original format <portnum>/[<proto>] or <startport-endport>/[<proto>] + // support two formats for expose, original format <portnum>/[<proto>] or <startport-endport>/[<proto>] _, port := nat.SplitProtoPort(expose) - //parse the start and end port and create a sequence of ports to expose - //if expose a port, the start and end port are the same + // parse the start and end port and create a sequence of ports to expose + // if expose a port, the start and end port are the same _, _, err := nat.ParsePortRange(port) if err != nil { return errors.Wrapf(err, "invalid range format for --expose: %s", expose) diff --git a/cmd/podman/common/specgen.go b/cmd/podman/common/specgen.go index 5d5816ea4..7250f88bb 100644 --- a/cmd/podman/common/specgen.go +++ b/cmd/podman/common/specgen.go @@ -192,7 +192,7 @@ func getMemoryLimits(s *specgen.SpecGenerator, c *ContainerCLIOpts, args []strin func FillOutSpecGen(s *specgen.SpecGenerator, c *ContainerCLIOpts, args []string) error { var ( err error - //namespaces map[string]string + // namespaces map[string]string ) // validate flags as needed @@ -364,20 +364,20 @@ func FillOutSpecGen(s *specgen.SpecGenerator, c *ContainerCLIOpts, args []string s.WorkDir = workDir entrypoint := []string{} userCommand := []string{} - if ep := c.Entrypoint; len(ep) > 0 { - // Check if entrypoint specified is json - if err := json.Unmarshal([]byte(c.Entrypoint), &entrypoint); err != nil { - entrypoint = append(entrypoint, ep) + if c.Entrypoint != nil { + if ep := *c.Entrypoint; len(ep) > 0 { + // Check if entrypoint specified is json + if err := json.Unmarshal([]byte(*c.Entrypoint), &entrypoint); err != nil { + entrypoint = append(entrypoint, ep) + } } + s.Entrypoint = entrypoint } - var command []string - s.Entrypoint = entrypoint - // Build the command // If we have an entry point, it goes first - if len(entrypoint) > 0 { + if c.Entrypoint != nil { command = entrypoint } if len(inputCommand) > 0 { @@ -386,9 +386,12 @@ func FillOutSpecGen(s *specgen.SpecGenerator, c *ContainerCLIOpts, args []string userCommand = append(userCommand, inputCommand...) } - if len(inputCommand) > 0 { + switch { + case len(inputCommand) > 0: s.Command = userCommand - } else { + case c.Entrypoint != nil: + s.Command = []string{} + default: s.Command = command } @@ -466,24 +469,6 @@ func FillOutSpecGen(s *specgen.SpecGenerator, c *ContainerCLIOpts, args []string s.CgroupParent = c.CGroupParent s.CgroupsMode = c.CGroupsMode s.Groups = c.GroupAdd - // TODO WTF - //cgroup := &cc.CgroupConfig{ - // Cgroupns: c.String("cgroupns"), - //} - // - //userns := &cc.UserConfig{ - // GroupAdd: c.StringSlice("group-add"), - // IDMappings: idmappings, - // UsernsMode: usernsMode, - // User: user, - //} - // - //uts := &cc.UtsConfig{ - // UtsMode: utsMode, - // NoHosts: c.Bool("no-hosts"), - // HostAdd: c.StringSlice("add-host"), - // Hostname: c.String("hostname"), - //} s.Hostname = c.Hostname sysctl := map[string]string{} @@ -503,7 +488,7 @@ func FillOutSpecGen(s *specgen.SpecGenerator, c *ContainerCLIOpts, args []string // TODO // ouitside of specgen and oci though // defaults to true, check spec/storage - //s.readon = c.ReadOnlyTmpFS + // s.readon = c.ReadOnlyTmpFS // TODO convert to map? // check if key=value and convert sysmap := make(map[string]string) @@ -546,7 +531,7 @@ func FillOutSpecGen(s *specgen.SpecGenerator, c *ContainerCLIOpts, args []string // Only add read-only tmpfs mounts in case that we are read-only and the // read-only tmpfs flag has been set. - mounts, volumes, err := parseVolumes(c.Volume, c.Mount, c.TmpFS, (c.ReadOnlyTmpFS && c.ReadOnly)) + mounts, volumes, err := parseVolumes(c.Volume, c.Mount, c.TmpFS, c.ReadOnlyTmpFS && c.ReadOnly) if err != nil { return err } @@ -554,12 +539,12 @@ func FillOutSpecGen(s *specgen.SpecGenerator, c *ContainerCLIOpts, args []string s.Volumes = volumes // TODO any idea why this was done - //devices := rtc.Containers.Devices + // devices := rtc.Containers.Devices // TODO conflict on populate? // - //if c.Changed("device") { + // if c.Changed("device") { // devices = append(devices, c.StringSlice("device")...) - //} + // } for _, dev := range c.Devices { s.Devices = append(s.Devices, specs.LinuxDevice{Path: dev}) @@ -571,7 +556,7 @@ func FillOutSpecGen(s *specgen.SpecGenerator, c *ContainerCLIOpts, args []string // initpath s.Stdin = c.Interactive // quiet - //DeviceCgroupRules: c.StringSlice("device-cgroup-rule"), + // DeviceCgroupRules: c.StringSlice("device-cgroup-rule"), // Rlimits/Ulimits for _, u := range c.Ulimit { @@ -591,10 +576,10 @@ func FillOutSpecGen(s *specgen.SpecGenerator, c *ContainerCLIOpts, args []string s.Rlimits = append(s.Rlimits, rl) } - //Tmpfs: c.StringArray("tmpfs"), + // Tmpfs: c.StringArray("tmpfs"), // TODO how to handle this? - //Syslog: c.Bool("syslog"), + // Syslog: c.Bool("syslog"), logOpts := make(map[string]string) for _, o := range c.LogOptions { @@ -620,7 +605,7 @@ func FillOutSpecGen(s *specgen.SpecGenerator, c *ContainerCLIOpts, args []string s.StopTimeout = &c.StopTimeout // TODO where should we do this? - //func verifyContainerResources(config *cc.CreateConfig, update bool) ([]string, error) { + // func verifyContainerResources(config *cc.CreateConfig, update bool) ([]string, error) { return nil } diff --git a/cmd/podman/containers/attach.go b/cmd/podman/containers/attach.go index 78b52ad1b..119b47d3f 100644 --- a/cmd/podman/containers/attach.go +++ b/cmd/podman/containers/attach.go @@ -4,6 +4,7 @@ import ( "os" "github.com/containers/libpod/cmd/podman/registry" + "github.com/containers/libpod/cmd/podman/validate" "github.com/containers/libpod/pkg/domain/entities" "github.com/pkg/errors" "github.com/spf13/cobra" @@ -17,12 +18,7 @@ var ( Short: "Attach to a running container", Long: attachDescription, RunE: attach, - Args: func(cmd *cobra.Command, args []string) error { - if len(args) > 1 || (len(args) == 0 && !cmd.Flag("latest").Changed) { - return errors.Errorf("attach requires the name or id of one running container or the latest flag") - } - return nil - }, + Args: validate.IdOrLatestArgs, Example: `podman attach ctrID podman attach 1234 podman attach --no-stdin foobar`, @@ -33,6 +29,7 @@ var ( Short: attachCommand.Short, Long: attachCommand.Long, RunE: attachCommand.RunE, + Args: validate.IdOrLatestArgs, Example: `podman container attach ctrID podman container attach 1234 podman container attach --no-stdin foobar`, @@ -71,11 +68,18 @@ func init() { } func attach(cmd *cobra.Command, args []string) error { + if len(args) > 1 || (len(args) == 0 && !attachOpts.Latest) { + return errors.Errorf("attach requires the name or id of one running container or the latest flag") + } + var name string + if len(args) > 0 { + name = args[0] + } attachOpts.Stdin = os.Stdin if attachOpts.NoStdin { attachOpts.Stdin = nil } attachOpts.Stdout = os.Stdout attachOpts.Stderr = os.Stderr - return registry.ContainerEngine().ContainerAttach(registry.GetContext(), args[0], attachOpts) + return registry.ContainerEngine().ContainerAttach(registry.GetContext(), name, attachOpts) } diff --git a/cmd/podman/containers/commit.go b/cmd/podman/containers/commit.go index 137e486eb..b3c3d7626 100644 --- a/cmd/podman/containers/commit.go +++ b/cmd/podman/containers/commit.go @@ -30,6 +30,7 @@ var ( } containerCommitCommand = &cobra.Command{ + Args: cobra.MinimumNArgs(1), Use: commitCommand.Use, Short: commitCommand.Short, Long: commitCommand.Long, diff --git a/cmd/podman/containers/container.go b/cmd/podman/containers/container.go index 97b73cdd0..a102318fb 100644 --- a/cmd/podman/containers/container.go +++ b/cmd/podman/containers/container.go @@ -2,6 +2,7 @@ package containers import ( "github.com/containers/libpod/cmd/podman/registry" + "github.com/containers/libpod/cmd/podman/validate" "github.com/containers/libpod/pkg/domain/entities" "github.com/containers/libpod/pkg/util" "github.com/spf13/cobra" @@ -17,7 +18,7 @@ var ( Short: "Manage containers", Long: "Manage containers", TraverseChildren: true, - RunE: registry.SubCommandExists, + RunE: validate.SubCommandExists, } containerConfig = util.DefaultContainerConfig() diff --git a/cmd/podman/containers/create.go b/cmd/podman/containers/create.go index da550b606..7927da04d 100644 --- a/cmd/podman/containers/create.go +++ b/cmd/podman/containers/create.go @@ -1,8 +1,10 @@ package containers import ( + "context" "fmt" "os" + "strings" "github.com/containers/common/pkg/config" "github.com/containers/libpod/cmd/podman/common" @@ -33,6 +35,7 @@ var ( } containerCreateCommand = &cobra.Command{ + Args: cobra.MinimumNArgs(1), Use: createCommand.Use, Short: createCommand.Short, Long: createCommand.Long, @@ -59,7 +62,7 @@ func init() { Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, Command: createCommand, }) - //common.GetCreateFlags(createCommand) + // common.GetCreateFlags(createCommand) flags := createCommand.Flags() createFlags(flags) @@ -105,6 +108,10 @@ func create(cmd *cobra.Command, args []string) error { return err } + if _, err := createPodIfNecessary(s); err != nil { + return err + } + report, err := registry.ContainerEngine().ContainerCreate(registry.GetContext(), s) if err != nil { return err @@ -159,6 +166,10 @@ func createInit(c *cobra.Command) error { if c.Flag("cgroupns").Changed { cliVals.CGroupsNS = c.Flag("cgroupns").Value.String() } + if c.Flag("entrypoint").Changed { + val := c.Flag("entrypoint").Value.String() + cliVals.Entrypoint = &val + } // Docker-compatibility: the "-h" flag for run/create is reserved for // the hostname (see https://github.com/containers/libpod/issues/1367). @@ -180,8 +191,10 @@ func pullImage(imageName string) error { return errors.New("unable to find a name and tag match for busybox in repotags: no such image") } _, pullErr := registry.ImageEngine().Pull(registry.GetContext(), imageName, entities.ImagePullOptions{ - Authfile: cliVals.Authfile, - Quiet: cliVals.Quiet, + Authfile: cliVals.Authfile, + Quiet: cliVals.Quiet, + OverrideArch: cliVals.OverrideArch, + OverrideOS: cliVals.OverrideOS, }) if pullErr != nil { return pullErr @@ -203,3 +216,25 @@ func openCidFile(cidfile string) (*os.File, error) { } return cidFile, nil } + +// createPodIfNecessary automatically creates a pod when requested. if the pod name +// has the form new:ID, the pod ID is created and the name in the spec generator is replaced +// with ID. +func createPodIfNecessary(s *specgen.SpecGenerator) (*entities.PodCreateReport, error) { + if !strings.HasPrefix(s.Pod, "new:") { + return nil, nil + } + podName := strings.Replace(s.Pod, "new:", "", 1) + if len(podName) < 1 { + return nil, errors.Errorf("new pod name must be at least one character") + } + createOptions := entities.PodCreateOptions{ + Name: podName, + Infra: true, + Net: &entities.NetOptions{ + PublishPorts: s.PortMappings, + }, + } + s.Pod = podName + return registry.ContainerEngine().PodCreate(context.Background(), createOptions) +} diff --git a/cmd/podman/containers/diff.go b/cmd/podman/containers/diff.go index 046dac53e..59b788010 100644 --- a/cmd/podman/containers/diff.go +++ b/cmd/podman/containers/diff.go @@ -3,6 +3,7 @@ package containers import ( "github.com/containers/libpod/cmd/podman/registry" "github.com/containers/libpod/cmd/podman/report" + "github.com/containers/libpod/cmd/podman/validate" "github.com/containers/libpod/pkg/domain/entities" "github.com/pkg/errors" "github.com/spf13/cobra" @@ -12,7 +13,7 @@ var ( // podman container _diff_ diffCmd = &cobra.Command{ Use: "diff [flags] CONTAINER", - Args: registry.IdOrLatestArgs, + Args: validate.IdOrLatestArgs, Short: "Inspect changes on container's file systems", Long: `Displays changes on a container filesystem. The container will be compared to its parent layer.`, RunE: diff, diff --git a/cmd/podman/containers/exec.go b/cmd/podman/containers/exec.go index 3749c934a..0992b3862 100644 --- a/cmd/podman/containers/exec.go +++ b/cmd/podman/containers/exec.go @@ -16,20 +16,22 @@ var ( execDescription = `Execute the specified command inside a running container. ` execCommand = &cobra.Command{ - Use: "exec [flags] CONTAINER [COMMAND [ARG...]]", - Short: "Run a process in a running container", - Long: execDescription, - RunE: exec, + Use: "exec [flags] CONTAINER [COMMAND [ARG...]]", + Short: "Run a process in a running container", + Long: execDescription, + RunE: exec, + DisableFlagsInUseLine: true, Example: `podman exec -it ctrID ls podman exec -it -w /tmp myCtr pwd podman exec --user root ctrID ls`, } containerExecCommand = &cobra.Command{ - Use: execCommand.Use, - Short: execCommand.Short, - Long: execCommand.Long, - RunE: execCommand.RunE, + Use: execCommand.Use, + Short: execCommand.Short, + Long: execCommand.Long, + RunE: execCommand.RunE, + DisableFlagsInUseLine: true, Example: `podman container exec -it ctrID ls podman container exec -it -w /tmp myCtr pwd podman container exec --user root ctrID ls`, @@ -70,7 +72,7 @@ func init() { registry.Commands = append(registry.Commands, registry.CliCommand{ Mode: []entities.EngineMode{entities.ABIMode}, Command: containerExecCommand, - Parent: containerCommitCommand, + Parent: containerCmd, }) containerExecFlags := containerExecCommand.Flags() @@ -79,6 +81,10 @@ func init() { func exec(cmd *cobra.Command, args []string) error { var nameOrId string + + if len(args) == 0 && !execOpts.Latest { + return errors.New("exec requires the name or ID of a container or the --latest flag") + } execOpts.Cmd = args if !execOpts.Latest { execOpts.Cmd = args[1:] diff --git a/cmd/podman/containers/exists.go b/cmd/podman/containers/exists.go index e640ca5e1..81ba8a282 100644 --- a/cmd/podman/containers/exists.go +++ b/cmd/podman/containers/exists.go @@ -17,8 +17,9 @@ var ( Long: containerExistsDescription, Example: `podman container exists containerID podman container exists myctr || podman run --name myctr [etc...]`, - RunE: exists, - Args: cobra.ExactArgs(1), + RunE: exists, + Args: cobra.ExactArgs(1), + DisableFlagsInUseLine: true, } ) diff --git a/cmd/podman/containers/export.go b/cmd/podman/containers/export.go index fb5bd468f..bbb6a6bc9 100644 --- a/cmd/podman/containers/export.go +++ b/cmd/podman/containers/export.go @@ -28,6 +28,7 @@ var ( } containerExportCommand = &cobra.Command{ + Args: cobra.MinimumNArgs(1), Use: exportCommand.Use, Short: exportCommand.Short, Long: exportCommand.Long, diff --git a/cmd/podman/containers/inspect.go b/cmd/podman/containers/inspect.go index f9ef1ddbd..4549a4ef6 100644 --- a/cmd/podman/containers/inspect.go +++ b/cmd/podman/containers/inspect.go @@ -1,15 +1,8 @@ package containers import ( - "context" - "fmt" - "os" - "strings" - "text/template" - - "github.com/containers/libpod/cmd/podman/common" + "github.com/containers/libpod/cmd/podman/inspect" "github.com/containers/libpod/cmd/podman/registry" - "github.com/containers/libpod/pkg/domain/entities" "github.com/spf13/cobra" ) @@ -20,7 +13,7 @@ var ( Use: "inspect [flags] CONTAINER", Short: "Display the configuration of a container", Long: `Displays the low-level information on a container identified by name or ID.`, - RunE: inspect, + RunE: inspectExec, Example: `podman container inspect myCtr podman container inspect -l --format '{{.Id}} {{.Config.Labels}}'`, } @@ -33,45 +26,9 @@ func init() { Command: inspectCmd, Parent: containerCmd, }) - inspectOpts = common.AddInspectFlagSet(inspectCmd) - flags := inspectCmd.Flags() - - if !registry.IsRemote() { - flags.BoolVarP(&inspectOpts.Latest, "latest", "l", false, "Act on the latest container podman is aware of") - } - -} - -func inspect(cmd *cobra.Command, args []string) error { - responses, err := registry.ContainerEngine().ContainerInspect(context.Background(), args, *inspectOpts) - if err != nil { - return err - } - if inspectOpts.Format == "" { - b, err := json.MarshalIndent(responses, "", " ") - if err != nil { - return err - } - fmt.Println(string(b)) - return nil - } - format := inspectOpts.Format - if !strings.HasSuffix(format, "\n") { - format += "\n" - } - tmpl, err := template.New("inspect").Parse(format) - if err != nil { - return err - } - for _, i := range responses { - if err := tmpl.Execute(os.Stdout, i); err != nil { - return err - } - } - return nil + inspectOpts = inspect.AddInspectFlagSet(inspectCmd) } -func Inspect(cmd *cobra.Command, args []string, options *entities.InspectOptions) error { - inspectOpts = options - return inspect(cmd, args) +func inspectExec(cmd *cobra.Command, args []string) error { + return inspect.Inspect(args, *inspectOpts) } diff --git a/cmd/podman/containers/kill.go b/cmd/podman/containers/kill.go index 8b4a384fe..ef85aad7d 100644 --- a/cmd/podman/containers/kill.go +++ b/cmd/podman/containers/kill.go @@ -30,6 +30,9 @@ var ( } containerKillCommand = &cobra.Command{ + Args: func(cmd *cobra.Command, args []string) error { + return parse.CheckAllLatestAndCIDFile(cmd, args, false, false) + }, Use: killCommand.Use, Short: killCommand.Short, Long: killCommand.Long, diff --git a/cmd/podman/containers/list.go b/cmd/podman/containers/list.go index b5019ddd2..c200a49aa 100644 --- a/cmd/podman/containers/list.go +++ b/cmd/podman/containers/list.go @@ -2,6 +2,7 @@ package containers import ( "github.com/containers/libpod/cmd/podman/registry" + "github.com/containers/libpod/cmd/podman/validate" "github.com/containers/libpod/pkg/domain/entities" "github.com/spf13/cobra" ) @@ -11,7 +12,7 @@ var ( listCmd = &cobra.Command{ Use: "list", Aliases: []string{"ls"}, - Args: cobra.NoArgs, + Args: validate.NoArgs, Short: "List containers", Long: "Prints out information about the containers", RunE: ps, diff --git a/cmd/podman/containers/port.go b/cmd/podman/containers/port.go index 0e50140ca..2e3386aa9 100644 --- a/cmd/podman/containers/port.go +++ b/cmd/podman/containers/port.go @@ -109,7 +109,7 @@ func port(cmd *cobra.Command, args []string) error { fmt.Printf("%d/%s -> %s:%d\n", v.ContainerPort, v.Protocol, hostIP, v.HostPort) continue } - if v == userPort { + if v.ContainerPort == userPort.ContainerPort { fmt.Printf("%s:%d\n", hostIP, v.HostPort) found = true break diff --git a/cmd/podman/containers/ps.go b/cmd/podman/containers/ps.go index 82434e9cc..c5696a158 100644 --- a/cmd/podman/containers/ps.go +++ b/cmd/podman/containers/ps.go @@ -13,6 +13,7 @@ import ( tm "github.com/buger/goterm" "github.com/containers/buildah/pkg/formats" "github.com/containers/libpod/cmd/podman/registry" + "github.com/containers/libpod/cmd/podman/validate" "github.com/containers/libpod/pkg/domain/entities" "github.com/cri-o/ocicni/pkg/ocicni" "github.com/docker/go-units" @@ -25,7 +26,7 @@ var ( psDescription = "Prints out information about the containers" psCommand = &cobra.Command{ Use: "ps", - Args: checkFlags, + Args: validate.NoArgs, Short: "List containers", Long: psDescription, RunE: ps, @@ -40,7 +41,7 @@ var ( } filters []string noTrunc bool - defaultHeaders string = "CONTAINER ID\tIMAGE\tCOMMAND\tCREATED\tSTATUS\tPORTS\tNAMES" + defaultHeaders = "CONTAINER ID\tIMAGE\tCOMMAND\tCREATED\tSTATUS\tPORTS\tNAMES" ) func init() { @@ -63,9 +64,12 @@ func listFlagSet(flags *pflag.FlagSet) { flags.BoolVarP(&listOpts.Pod, "pod", "p", false, "Print the ID and name of the pod the containers are associated with") flags.BoolVarP(&listOpts.Quiet, "quiet", "q", false, "Print the numeric IDs of the containers only") flags.BoolVarP(&listOpts.Size, "size", "s", false, "Display the total file sizes") - flags.StringVar(&listOpts.Sort, "sort", "created", "Sort output by command, created, id, image, names, runningfor, size, or status") flags.BoolVar(&listOpts.Sync, "sync", false, "Sync container state with OCI runtime") flags.UintVarP(&listOpts.Watch, "watch", "w", 0, "Watch the ps output on an interval in seconds") + + created := validate.ChoiceValue(&listOpts.Sort, "command", "created", "id", "image", "names", "runningfor", "size", "status") + flags.Var(created, "sort", "Sort output by: "+created.Choices()) + if registry.IsRemote() { _ = flags.MarkHidden("latest") } @@ -141,6 +145,9 @@ func getResponses() ([]entities.ListContainer, error) { func ps(cmd *cobra.Command, args []string) error { var responses []psReporter + if err := checkFlags(cmd, args); err != nil { + return err + } for _, f := range filters { split := strings.SplitN(f, "=", 2) if len(split) == 1 { @@ -171,7 +178,7 @@ func ps(cmd *cobra.Command, args []string) error { headers, format := createPsOut() if cmd.Flag("format").Changed { - format = listOpts.Format + format = strings.TrimPrefix(listOpts.Format, "table ") if !strings.HasPrefix(format, "\n") { format += "\n" } @@ -348,7 +355,7 @@ func portsToString(ports []ocicni.PortMapping) string { if len(ports) == 0 { return "" } - //Sort the ports, so grouping continuous ports become easy. + // Sort the ports, so grouping continuous ports become easy. sort.Slice(ports, func(i, j int) bool { return comparePorts(ports[i], ports[j]) }) diff --git a/cmd/podman/containers/rm.go b/cmd/podman/containers/rm.go index 3021853a9..96549cead 100644 --- a/cmd/podman/containers/rm.go +++ b/cmd/podman/containers/rm.go @@ -38,6 +38,9 @@ var ( Short: rmCommand.Use, Long: rmCommand.Long, RunE: rmCommand.RunE, + Args: func(cmd *cobra.Command, args []string) error { + return parse.CheckAllLatestAndCIDFile(cmd, args, false, true) + }, Example: `podman container rm imageID podman container rm mywebserver myflaskserver 860a4b23 podman container rm --force --all diff --git a/cmd/podman/containers/run.go b/cmd/podman/containers/run.go index e3fe4cd0b..b13983e37 100644 --- a/cmd/podman/containers/run.go +++ b/cmd/podman/containers/run.go @@ -20,6 +20,7 @@ import ( var ( runDescription = "Runs a command in a new container from the given image" runCommand = &cobra.Command{ + Args: cobra.MinimumNArgs(1), Use: "run [flags] IMAGE [COMMAND [ARG...]]", Short: "Run a command in a new container", Long: runDescription, @@ -30,6 +31,7 @@ var ( } containerRunCommand = &cobra.Command{ + Args: cobra.MinimumNArgs(1), Use: runCommand.Use, Short: runCommand.Short, Long: runCommand.Long, @@ -144,6 +146,10 @@ func run(cmd *cobra.Command, args []string) error { } runOpts.Spec = s + if _, err := createPodIfNecessary(s); err != nil { + return err + } + report, err := registry.ContainerEngine().ContainerRun(registry.GetContext(), runOpts) // report.ExitCode is set by ContainerRun even it it returns an error if report != nil { diff --git a/cmd/podman/containers/start.go b/cmd/podman/containers/start.go index 381bf8e26..ce78d24ed 100644 --- a/cmd/podman/containers/start.go +++ b/cmd/podman/containers/start.go @@ -99,7 +99,7 @@ func start(cmd *cobra.Command, args []string) error { for _, r := range responses { if r.Err == nil { - fmt.Println(r.Id) + fmt.Println(r.RawInput) } else { errs = append(errs, r.Err) } diff --git a/cmd/podman/containers/stop.go b/cmd/podman/containers/stop.go index 4a451134a..22c487961 100644 --- a/cmd/podman/containers/stop.go +++ b/cmd/podman/containers/stop.go @@ -34,6 +34,9 @@ var ( Short: stopCommand.Short, Long: stopCommand.Long, RunE: stopCommand.RunE, + Args: func(cmd *cobra.Command, args []string) error { + return parse.CheckAllLatestAndCIDFile(cmd, args, false, true) + }, Example: `podman container stop ctrID podman container stop --latest podman container stop --time 2 mywebserver 6e534f14da9d`, diff --git a/cmd/podman/containers/unmount.go b/cmd/podman/containers/unmount.go index a4550abbd..d0ca202fe 100644 --- a/cmd/podman/containers/unmount.go +++ b/cmd/podman/containers/unmount.go @@ -27,6 +27,9 @@ var ( Args: func(cmd *cobra.Command, args []string) error { return parse.CheckAllLatestAndCIDFile(cmd, args, false, false) }, + Annotations: map[string]string{ + registry.ParentNSRequired: "", + }, Example: `podman umount ctrID podman umount ctrID1 ctrID2 ctrID3 podman umount --all`, @@ -37,6 +40,9 @@ var ( Short: umountCommand.Short, Long: umountCommand.Long, RunE: umountCommand.RunE, + Args: func(cmd *cobra.Command, args []string) error { + return parse.CheckAllLatestAndCIDFile(cmd, args, false, false) + }, Example: `podman container umount ctrID podman container umount ctrID1 ctrID2 ctrID3 podman container umount --all`, diff --git a/cmd/podman/containers/wait.go b/cmd/podman/containers/wait.go index da746361d..1f4d4159b 100644 --- a/cmd/podman/containers/wait.go +++ b/cmd/podman/containers/wait.go @@ -7,6 +7,7 @@ import ( "github.com/containers/libpod/cmd/podman/registry" "github.com/containers/libpod/cmd/podman/utils" + "github.com/containers/libpod/cmd/podman/validate" "github.com/containers/libpod/libpod/define" "github.com/containers/libpod/pkg/domain/entities" "github.com/pkg/errors" @@ -22,7 +23,7 @@ var ( Short: "Block on one or more containers", Long: waitDescription, RunE: wait, - Args: registry.IdOrLatestArgs, + Args: validate.IdOrLatestArgs, Example: `podman wait --latest podman wait --interval 5000 ctrID podman wait ctrID1 ctrID2`, @@ -33,6 +34,7 @@ var ( Short: waitCommand.Short, Long: waitCommand.Long, RunE: waitCommand.RunE, + Args: validate.IdOrLatestArgs, Example: `podman container wait --latest podman container wait --interval 5000 ctrID podman container wait ctrID1 ctrID2`, diff --git a/cmd/podman/diff.go b/cmd/podman/diff.go index ec94c0918..1ff2fce40 100644 --- a/cmd/podman/diff.go +++ b/cmd/podman/diff.go @@ -6,6 +6,7 @@ import ( "github.com/containers/libpod/cmd/podman/containers" "github.com/containers/libpod/cmd/podman/images" "github.com/containers/libpod/cmd/podman/registry" + "github.com/containers/libpod/cmd/podman/validate" "github.com/containers/libpod/pkg/domain/entities" "github.com/spf13/cobra" ) @@ -17,7 +18,7 @@ var ( diffDescription = `Displays changes on a container or image's filesystem. The container or image will be compared to its parent layer.` diffCmd = &cobra.Command{ Use: "diff [flags] {CONTAINER_ID | IMAGE_ID}", - Args: registry.IdOrLatestArgs, + Args: validate.IdOrLatestArgs, Short: "Display the changes of object's file system", Long: diffDescription, TraverseChildren: true, diff --git a/cmd/podman/generate/generate.go b/cmd/podman/generate/generate.go new file mode 100644 index 000000000..b112e666a --- /dev/null +++ b/cmd/podman/generate/generate.go @@ -0,0 +1,28 @@ +package pods + +import ( + "github.com/containers/libpod/cmd/podman/registry" + "github.com/containers/libpod/cmd/podman/validate" + "github.com/containers/libpod/pkg/domain/entities" + "github.com/containers/libpod/pkg/util" + "github.com/spf13/cobra" +) + +var ( + // Command: podman _generate_ + generateCmd = &cobra.Command{ + Use: "generate", + Short: "Generate structured data based on containers and pods.", + Long: "Generate structured data (e.g., Kubernetes yaml or systemd units) based on containers and pods.", + TraverseChildren: true, + RunE: validate.SubCommandExists, + } + containerConfig = util.DefaultContainerConfig() +) + +func init() { + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode}, + Command: generateCmd, + }) +} diff --git a/cmd/podman/generate/systemd.go b/cmd/podman/generate/systemd.go new file mode 100644 index 000000000..55d770249 --- /dev/null +++ b/cmd/podman/generate/systemd.go @@ -0,0 +1,57 @@ +package pods + +import ( + "fmt" + + "github.com/containers/libpod/cmd/podman/registry" + "github.com/containers/libpod/cmd/podman/utils" + "github.com/containers/libpod/pkg/domain/entities" + "github.com/spf13/cobra" +) + +var ( + systemdTimeout uint + systemdOptions = entities.GenerateSystemdOptions{} + systemdDescription = `Generate systemd units for a pod or container. + The generated units can later be controlled via systemctl(1).` + + systemdCmd = &cobra.Command{ + Use: "systemd [flags] CTR|POD", + Short: "Generate systemd units.", + Long: systemdDescription, + RunE: systemd, + Args: cobra.MinimumNArgs(1), + Example: `podman generate systemd CTR + podman generate systemd --new --time 10 CTR + podman generate systemd --files --name POD`, + } +) + +func init() { + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, + Command: systemdCmd, + Parent: generateCmd, + }) + flags := systemdCmd.Flags() + flags.BoolVarP(&systemdOptions.Name, "name", "n", false, "Use container/pod names instead of IDs") + flags.BoolVarP(&systemdOptions.Files, "files", "f", false, "Generate .service files instead of printing to stdout") + flags.UintVarP(&systemdTimeout, "time", "t", containerConfig.Engine.StopTimeout, "Stop timeout override") + flags.StringVar(&systemdOptions.RestartPolicy, "restart-policy", "on-failure", "Systemd restart-policy") + flags.BoolVarP(&systemdOptions.New, "new", "", false, "Create a new container instead of starting an existing one") + flags.SetNormalizeFunc(utils.AliasFlags) +} + +func systemd(cmd *cobra.Command, args []string) error { + if cmd.Flags().Changed("time") { + systemdOptions.StopTimeout = &systemdTimeout + } + + report, err := registry.ContainerEngine().GenerateSystemd(registry.GetContext(), args[0], systemdOptions) + if err != nil { + return err + } + + fmt.Println(report.Output) + return nil +} diff --git a/cmd/podman/healthcheck/healthcheck.go b/cmd/podman/healthcheck/healthcheck.go index 794a94615..ce90dba31 100644 --- a/cmd/podman/healthcheck/healthcheck.go +++ b/cmd/podman/healthcheck/healthcheck.go @@ -2,6 +2,7 @@ package healthcheck import ( "github.com/containers/libpod/cmd/podman/registry" + "github.com/containers/libpod/cmd/podman/validate" "github.com/containers/libpod/pkg/domain/entities" "github.com/spf13/cobra" ) @@ -13,7 +14,7 @@ var ( Short: "Manage Healthcheck", Long: "Manage Healthcheck", TraverseChildren: true, - RunE: registry.SubCommandExists, + RunE: validate.SubCommandExists, } ) diff --git a/cmd/podman/images/exists.go b/cmd/podman/images/exists.go index 6464e6cd8..13191113f 100644 --- a/cmd/podman/images/exists.go +++ b/cmd/podman/images/exists.go @@ -15,6 +15,7 @@ var ( RunE: exists, Example: `podman image exists ID podman image exists IMAGE && podman pull IMAGE`, + DisableFlagsInUseLine: true, } ) diff --git a/cmd/podman/images/history.go b/cmd/podman/images/history.go index b8d216cc1..ce153aa46 100644 --- a/cmd/podman/images/history.go +++ b/cmd/podman/images/history.go @@ -89,22 +89,20 @@ func history(cmd *cobra.Command, args []string) error { hdr := "ID\tCREATED\tCREATED BY\tSIZE\tCOMMENT\n" row := "{{.ID}}\t{{.Created}}\t{{.CreatedBy}}\t{{.Size}}\t{{.Comment}}\n" - if len(opts.format) > 0 { + switch { + case len(opts.format) > 0: hdr = "" row = opts.format if !strings.HasSuffix(opts.format, "\n") { row += "\n" } - } else { - switch { - case opts.human: - row = "{{.ID}}\t{{.Created}}\t{{.CreatedBy}}\t{{.Size}}\t{{.Comment}}\n" - case opts.noTrunc: - row = "{{.ID}}\t{{.Created}}\t{{.CreatedBy}}\t{{.Size}}\t{{.Comment}}\n" - case opts.quiet: - hdr = "" - row = "{{.ID}}\n" - } + case opts.quiet: + hdr = "" + row = "{{.ID}}\n" + case opts.human: + row = "{{.ID}}\t{{.Created}}\t{{.CreatedBy}}\t{{.Size}}\t{{.Comment}}\n" + case opts.noTrunc: + row = "{{.ID}}\t{{.Created}}\t{{.CreatedBy}}\t{{.Size}}\t{{.Comment}}\n" } format := hdr + "{{range . }}" + row + "{{end}}" diff --git a/cmd/podman/images/image.go b/cmd/podman/images/image.go index 604f49251..790c16c05 100644 --- a/cmd/podman/images/image.go +++ b/cmd/podman/images/image.go @@ -2,6 +2,7 @@ package images import ( "github.com/containers/libpod/cmd/podman/registry" + "github.com/containers/libpod/cmd/podman/validate" "github.com/containers/libpod/pkg/domain/entities" "github.com/spf13/cobra" ) @@ -16,7 +17,7 @@ var ( Short: "Manage images", Long: "Manage images", TraverseChildren: true, - RunE: registry.SubCommandExists, + RunE: validate.SubCommandExists, } ) diff --git a/cmd/podman/images/images.go b/cmd/podman/images/images.go index fd3ede26a..96ef344bf 100644 --- a/cmd/podman/images/images.go +++ b/cmd/podman/images/images.go @@ -11,12 +11,13 @@ import ( var ( // podman _images_ Alias for podman image _list_ imagesCmd = &cobra.Command{ - Use: strings.Replace(listCmd.Use, "list", "images", 1), - Args: listCmd.Args, - Short: listCmd.Short, - Long: listCmd.Long, - RunE: listCmd.RunE, - Example: strings.Replace(listCmd.Example, "podman image list", "podman images", -1), + Use: strings.Replace(listCmd.Use, "list", "images", 1), + Args: listCmd.Args, + Short: listCmd.Short, + Long: listCmd.Long, + RunE: listCmd.RunE, + Example: strings.Replace(listCmd.Example, "podman image list", "podman images", -1), + DisableFlagsInUseLine: true, } ) diff --git a/cmd/podman/images/inspect.go b/cmd/podman/images/inspect.go index 91c9445eb..8c727eb07 100644 --- a/cmd/podman/images/inspect.go +++ b/cmd/podman/images/inspect.go @@ -1,18 +1,9 @@ package images import ( - "context" - "fmt" - "os" - "strings" - "text/tabwriter" - "text/template" - - "github.com/containers/buildah/pkg/formats" - "github.com/containers/libpod/cmd/podman/common" + "github.com/containers/libpod/cmd/podman/inspect" "github.com/containers/libpod/cmd/podman/registry" "github.com/containers/libpod/pkg/domain/entities" - "github.com/pkg/errors" "github.com/spf13/cobra" ) @@ -21,8 +12,8 @@ var ( inspectCmd = &cobra.Command{ Use: "inspect [flags] IMAGE", Short: "Display the configuration of an image", - Long: `Displays the low-level information on an image identified by name or ID.`, - RunE: inspect, + Long: `Displays the low-level information of an image identified by name or ID.`, + RunE: inspectExec, Example: `podman inspect alpine podman inspect --format "imageId: {{.Id}} size: {{.Size}}" alpine podman inspect --format "image: {{.ImageName}} driver: {{.Driver}}" myctr`, @@ -36,78 +27,11 @@ func init() { Command: inspectCmd, Parent: imageCmd, }) - inspectOpts = common.AddInspectFlagSet(inspectCmd) -} - -func inspect(cmd *cobra.Command, args []string) error { - if inspectOpts.Size { - return fmt.Errorf("--size can only be used for containers") - } - if inspectOpts.Latest { - return fmt.Errorf("--latest can only be used for containers") - } - if len(args) == 0 { - return errors.Errorf("image name must be specified: podman image inspect [options [...]] name") - } - - results, err := registry.ImageEngine().Inspect(context.Background(), args, *inspectOpts) - if err != nil { - return err - } - - if len(results.Images) > 0 { - if inspectOpts.Format == "" { - buf, err := json.MarshalIndent(results.Images, "", " ") - if err != nil { - return err - } - fmt.Println(string(buf)) - - for id, e := range results.Errors { - fmt.Fprintf(os.Stderr, "%s: %s\n", id, e.Error()) - } - return nil - } - row := inspectFormat(inspectOpts.Format) - format := "{{range . }}" + row + "{{end}}" - tmpl, err := template.New("inspect").Parse(format) - if err != nil { - return err - } - - w := tabwriter.NewWriter(os.Stdout, 8, 2, 2, ' ', 0) - defer func() { _ = w.Flush() }() - err = tmpl.Execute(w, results.Images) - if err != nil { - return err - } - } - - var lastErr error - for id, e := range results.Errors { - if lastErr != nil { - fmt.Fprintf(os.Stderr, "%s: %s\n", id, lastErr.Error()) - } - lastErr = e - } - return lastErr -} - -func inspectFormat(row string) string { - r := strings.NewReplacer("{{.Id}}", formats.IDString, - ".Src", ".Source", - ".Dst", ".Destination", - ".ImageID", ".Image", - ) - row = r.Replace(row) - - if !strings.HasSuffix(row, "\n") { - row += "\n" - } - return row + inspectOpts = inspect.AddInspectFlagSet(inspectCmd) + flags := inspectCmd.Flags() + _ = flags.MarkHidden("latest") // Shared with container-inspect but not wanted here. } -func Inspect(cmd *cobra.Command, args []string, options *entities.InspectOptions) error { - inspectOpts = options - return inspect(cmd, args) +func inspectExec(cmd *cobra.Command, args []string) error { + return inspect.Inspect(args, *inspectOpts) } diff --git a/cmd/podman/images/list.go b/cmd/podman/images/list.go index b979cb6af..83c039ed3 100644 --- a/cmd/podman/images/list.go +++ b/cmd/podman/images/list.go @@ -32,7 +32,7 @@ type listFlagType struct { var ( // Command: podman image _list_ listCmd = &cobra.Command{ - Use: "list [flag] [IMAGE]", + Use: "list [FLAGS] [IMAGE]", Aliases: []string{"ls"}, Args: cobra.MaximumNArgs(1), Short: "List images in local storage", @@ -41,6 +41,7 @@ var ( Example: `podman image list --format json podman image list --sort repository --format "table {{.ID}} {{.Repository}} {{.Tag}}" podman image list --filter dangling=true`, + DisableFlagsInUseLine: true, } // Options to pull data @@ -98,14 +99,29 @@ func images(cmd *cobra.Command, args []string) error { return err } - imageS := summaries - sort.Slice(imageS, sortFunc(listFlag.sort, imageS)) + switch { + case listFlag.quiet: + return writeId(summaries) + case cmd.Flag("format").Changed && listFlag.format == "json": + return writeJSON(summaries) + default: + return writeTemplate(summaries) + } +} - if cmd.Flag("format").Changed && listFlag.format == "json" { - return writeJSON(imageS) - } else { - return writeTemplate(imageS, err) +func writeId(imageS []*entities.ImageSummary) error { + var ids = map[string]struct{}{} + for _, e := range imageS { + i := "sha256:" + e.ID + if !listFlag.noTrunc { + i = fmt.Sprintf("%12.12s", e.ID) + } + ids[i] = struct{}{} } + for k := range ids { + fmt.Fprint(os.Stdout, k+"\n") + } + return nil } func writeJSON(imageS []*entities.ImageSummary) error { @@ -130,7 +146,7 @@ func writeJSON(imageS []*entities.ImageSummary) error { return enc.Encode(imgs) } -func writeTemplate(imageS []*entities.ImageSummary, err error) error { +func writeTemplate(imageS []*entities.ImageSummary) error { var ( hdr, row string ) @@ -142,10 +158,11 @@ func writeTemplate(imageS []*entities.ImageSummary, err error) error { h.Repository, h.Tag = tokenRepoTag(tag) imgs = append(imgs, h) } - if e.IsReadOnly() { - listFlag.readOnly = true - } + listFlag.readOnly = e.IsReadOnly() } + + sort.Slice(imgs, sortFunc(listFlag.sort, imgs)) + if len(listFlag.format) < 1 { hdr, row = imageListFormat(listFlag) } else { @@ -175,37 +192,33 @@ func tokenRepoTag(tag string) (string, string) { } } -func sortFunc(key string, data []*entities.ImageSummary) func(i, j int) bool { +func sortFunc(key string, data []imageReporter) func(i, j int) bool { switch key { case "id": return func(i, j int) bool { - return data[i].ID < data[j].ID + return data[i].ID() < data[j].ID() } case "repository": return func(i, j int) bool { - return data[i].RepoTags[0] < data[j].RepoTags[0] + return data[i].Repository < data[j].Repository } case "size": return func(i, j int) bool { - return data[i].Size < data[j].Size + return data[i].size() < data[j].size() } case "tag": return func(i, j int) bool { - return data[i].RepoTags[0] < data[j].RepoTags[0] + return data[i].Tag < data[j].Tag } default: // case "created": return func(i, j int) bool { - return data[i].Created.After(data[j].Created) + return data[i].created().After(data[j].created()) } } } func imageListFormat(flags listFlagType) (string, string) { - if flags.quiet { - return "", "{{.ID}}\n" - } - // Defaults hdr := "REPOSITORY\tTAG" row := "{{.Repository}}\t{{if .Tag}}{{.Tag}}{{else}}<none>{{end}}" @@ -262,6 +275,10 @@ func (i imageReporter) Created() string { return units.HumanDuration(time.Since(i.ImageSummary.Created)) + " ago" } +func (i imageReporter) created() time.Time { + return i.ImageSummary.Created +} + func (i imageReporter) Size() string { s := units.HumanSizeWithPrecision(float64(i.ImageSummary.Size), 3) j := strings.LastIndexFunc(s, unicode.IsNumber) @@ -271,3 +288,19 @@ func (i imageReporter) Size() string { func (i imageReporter) History() string { return strings.Join(i.ImageSummary.History, ", ") } + +func (i imageReporter) CreatedAt() string { + return i.ImageSummary.Created.String() +} + +func (i imageReporter) CreatedSince() string { + return i.Created() +} + +func (i imageReporter) CreatedTime() string { + return i.CreatedAt() +} + +func (i imageReporter) size() int64 { + return i.ImageSummary.Size +} diff --git a/cmd/podman/images/prune.go b/cmd/podman/images/prune.go index b90d889be..53a1966c1 100644 --- a/cmd/podman/images/prune.go +++ b/cmd/podman/images/prune.go @@ -7,6 +7,7 @@ import ( "strings" "github.com/containers/libpod/cmd/podman/registry" + "github.com/containers/libpod/cmd/podman/validate" "github.com/containers/libpod/pkg/domain/entities" "github.com/pkg/errors" "github.com/spf13/cobra" @@ -18,7 +19,7 @@ var ( If an image is not being used by a container, it will be removed from the system.` pruneCmd = &cobra.Command{ Use: "prune", - Args: cobra.NoArgs, + Args: validate.NoArgs, Short: "Remove unused images", Long: pruneDescription, RunE: prune, diff --git a/cmd/podman/images/pull.go b/cmd/podman/images/pull.go index f996d0681..9f4cbc50e 100644 --- a/cmd/podman/images/pull.go +++ b/cmd/podman/images/pull.go @@ -45,6 +45,7 @@ var ( Short: pullCmd.Short, Long: pullCmd.Long, RunE: pullCmd.RunE, + Args: cobra.ExactArgs(1), Example: `podman image pull imageName podman image pull fedora:latest`, } @@ -93,23 +94,22 @@ func pullFlags(flags *pflag.FlagSet) { // imagePull is implement the command for pulling images. func imagePull(cmd *cobra.Command, args []string) error { - pullOptsAPI := pullOptions.ImagePullOptions // TLS verification in c/image is controlled via a `types.OptionalBool` // which allows for distinguishing among set-true, set-false, unspecified // which is important to implement a sane way of dealing with defaults of // boolean CLI flags. if cmd.Flags().Changed("tls-verify") { - pullOptsAPI.TLSVerify = types.NewOptionalBool(pullOptions.TLSVerifyCLI) + pullOptions.SkipTLSVerify = types.NewOptionalBool(!pullOptions.TLSVerifyCLI) } - if pullOptsAPI.Authfile != "" { - if _, err := os.Stat(pullOptsAPI.Authfile); err != nil { - return errors.Wrapf(err, "error getting authfile %s", pullOptsAPI.Authfile) + if pullOptions.Authfile != "" { + if _, err := os.Stat(pullOptions.Authfile); err != nil { + return errors.Wrapf(err, "error getting authfile %s", pullOptions.Authfile) } } // Let's do all the remaining Yoga in the API to prevent us from // scattering logic across (too) many parts of the code. - pullReport, err := registry.ImageEngine().Pull(registry.GetContext(), args[0], pullOptsAPI) + pullReport, err := registry.ImageEngine().Pull(registry.GetContext(), args[0], pullOptions.ImagePullOptions) if err != nil { return err } diff --git a/cmd/podman/images/push.go b/cmd/podman/images/push.go index ef2ffd0d7..0b3502d61 100644 --- a/cmd/podman/images/push.go +++ b/cmd/podman/images/push.go @@ -98,6 +98,7 @@ func imagePush(cmd *cobra.Command, args []string) error { switch len(args) { case 1: source = args[0] + destination = args[0] case 2: source = args[0] destination = args[1] @@ -107,22 +108,21 @@ func imagePush(cmd *cobra.Command, args []string) error { return errors.New("push requires at least one image name, or optionally a second to specify a different destination") } - pushOptsAPI := pushOptions.ImagePushOptions // TLS verification in c/image is controlled via a `types.OptionalBool` // which allows for distinguishing among set-true, set-false, unspecified // which is important to implement a sane way of dealing with defaults of // boolean CLI flags. if cmd.Flags().Changed("tls-verify") { - pushOptsAPI.TLSVerify = types.NewOptionalBool(pushOptions.TLSVerifyCLI) + pushOptions.SkipTLSVerify = types.NewOptionalBool(!pushOptions.TLSVerifyCLI) } - if pushOptsAPI.Authfile != "" { - if _, err := os.Stat(pushOptsAPI.Authfile); err != nil { - return errors.Wrapf(err, "error getting authfile %s", pushOptsAPI.Authfile) + if pushOptions.Authfile != "" { + if _, err := os.Stat(pushOptions.Authfile); err != nil { + return errors.Wrapf(err, "error getting authfile %s", pushOptions.Authfile) } } // Let's do all the remaining Yoga in the API to prevent us from scattering // logic across (too) many parts of the code. - return registry.ImageEngine().Push(registry.GetContext(), source, destination, pushOptsAPI) + return registry.ImageEngine().Push(registry.GetContext(), source, destination, pushOptions.ImagePushOptions) } diff --git a/cmd/podman/images/rm.go b/cmd/podman/images/rm.go index da6a90d2b..1cf5fa365 100644 --- a/cmd/podman/images/rm.go +++ b/cmd/podman/images/rm.go @@ -54,7 +54,10 @@ func rm(cmd *cobra.Command, args []string) error { fmt.Println("Untagged: " + u) } for _, d := range report.Deleted { - fmt.Println("Deleted: " + d) + // Make sure an image was deleted (and not just untagged); else print it + if len(d) > 0 { + fmt.Println("Deleted: " + d) + } } registry.SetExitCode(report.ExitCode) } diff --git a/cmd/podman/images/search.go b/cmd/podman/images/search.go index fdad94d45..a8abfb339 100644 --- a/cmd/podman/images/search.go +++ b/cmd/podman/images/search.go @@ -1,6 +1,7 @@ package images import ( + "os" "reflect" "strings" @@ -47,14 +48,15 @@ var ( // Command: podman image search imageSearchCmd = &cobra.Command{ - Use: searchCmd.Use, - Short: searchCmd.Short, - Long: searchCmd.Long, - RunE: searchCmd.RunE, - Args: searchCmd.Args, + Use: searchCmd.Use, + Short: searchCmd.Short, + Long: searchCmd.Long, + RunE: searchCmd.RunE, + Args: searchCmd.Args, + Annotations: searchCmd.Annotations, Example: `podman image search --filter=is-official --limit 3 alpine - podman image search registry.fedoraproject.org/ # only works with v2 registries - podman image search --format "table {{.Index}} {{.Name}}" registry.fedoraproject.org/fedora`, + podman image search registry.fedoraproject.org/ # only works with v2 registries + podman image search --format "table {{.Index}} {{.Name}}" registry.fedoraproject.org/fedora`, } ) @@ -103,16 +105,21 @@ func imageSearch(cmd *cobra.Command, args []string) error { return errors.Errorf("search requires exactly one argument") } - sarchOptsAPI := searchOptions.ImageSearchOptions // TLS verification in c/image is controlled via a `types.OptionalBool` // which allows for distinguishing among set-true, set-false, unspecified // which is important to implement a sane way of dealing with defaults of // boolean CLI flags. if cmd.Flags().Changed("tls-verify") { - sarchOptsAPI.TLSVerify = types.NewOptionalBool(pullOptions.TLSVerifyCLI) + searchOptions.SkipTLSVerify = types.NewOptionalBool(!searchOptions.TLSVerifyCLI) } - searchReport, err := registry.ImageEngine().Search(registry.GetContext(), searchTerm, sarchOptsAPI) + if searchOptions.Authfile != "" { + if _, err := os.Stat(searchOptions.Authfile); err != nil { + return errors.Wrapf(err, "error getting authfile %s", searchOptions.Authfile) + } + } + + searchReport, err := registry.ImageEngine().Search(registry.GetContext(), searchTerm, searchOptions.ImageSearchOptions) if err != nil { return err } diff --git a/cmd/podman/inspect.go b/cmd/podman/inspect.go index 93bf58bdd..a5fdaedc2 100644 --- a/cmd/podman/inspect.go +++ b/cmd/podman/inspect.go @@ -1,31 +1,26 @@ package main import ( - "fmt" - - "github.com/containers/libpod/cmd/podman/common" - "github.com/containers/libpod/cmd/podman/containers" - "github.com/containers/libpod/cmd/podman/images" + "github.com/containers/libpod/cmd/podman/inspect" "github.com/containers/libpod/cmd/podman/registry" "github.com/containers/libpod/pkg/domain/entities" "github.com/spf13/cobra" ) -// Inspect is one of the outlier commands in that it operates on images/containers/... - var ( - inspectOpts *entities.InspectOptions - // Command: podman _inspect_ Object_ID inspectCmd = &cobra.Command{ Use: "inspect [flags] {CONTAINER_ID | IMAGE_ID}", Short: "Display the configuration of object denoted by ID", Long: "Displays the low-level information on an object identified by name or ID", TraverseChildren: true, - RunE: inspect, - Example: `podman inspect alpine - podman inspect --format "imageId: {{.Id}} size: {{.Size}}" alpine`, + RunE: inspectExec, + Example: `podman inspect fedora + podman inspect --type image fedora + podman inspect CtrID ImgID + podman inspect --format "imageId: {{.Id}} size: {{.Size}}" fedora`, } + inspectOpts *entities.InspectOptions ) func init() { @@ -33,26 +28,9 @@ func init() { Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, Command: inspectCmd, }) - inspectOpts = common.AddInspectFlagSet(inspectCmd) - flags := inspectCmd.Flags() - flags.StringVarP(&inspectOpts.Type, "type", "t", "", "Return JSON for specified type, (image or container) (default \"all\")") - if !registry.IsRemote() { - flags.BoolVarP(&inspectOpts.Latest, "latest", "l", false, "Act on the latest container podman is aware of (containers only)") - } + inspectOpts = inspect.AddInspectFlagSet(inspectCmd) } -func inspect(cmd *cobra.Command, args []string) error { - switch inspectOpts.Type { - case "image": - return images.Inspect(cmd, args, inspectOpts) - case "container": - return containers.Inspect(cmd, args, inspectOpts) - case "": - if err := images.Inspect(cmd, args, inspectOpts); err == nil { - return nil - } - return containers.Inspect(cmd, args, inspectOpts) - default: - return fmt.Errorf("invalid type %q is must be 'container' or 'image'", inspectOpts.Type) - } +func inspectExec(cmd *cobra.Command, args []string) error { + return inspect.Inspect(args, *inspectOpts) } diff --git a/cmd/podman/inspect/inspect.go b/cmd/podman/inspect/inspect.go new file mode 100644 index 000000000..223ce00f0 --- /dev/null +++ b/cmd/podman/inspect/inspect.go @@ -0,0 +1,159 @@ +package inspect + +import ( + "context" + "fmt" + "strings" + + "github.com/containers/buildah/pkg/formats" + "github.com/containers/libpod/cmd/podman/registry" + "github.com/containers/libpod/pkg/domain/entities" + "github.com/pkg/errors" + "github.com/spf13/cobra" +) + +const ( + // ImageType is the image type. + ImageType = "image" + // ContainerType is the container type. + ContainerType = "container" + // AllType can be of type ImageType or ContainerType. + AllType = "all" +) + +// AddInspectFlagSet takes a command and adds the inspect flags and returns an +// InspectOptions object. +func AddInspectFlagSet(cmd *cobra.Command) *entities.InspectOptions { + opts := entities.InspectOptions{} + + flags := cmd.Flags() + flags.BoolVarP(&opts.Size, "size", "s", false, "Display total file size") + flags.StringVarP(&opts.Format, "format", "f", "json", "Format the output to a Go template or json") + flags.StringVarP(&opts.Type, "type", "t", AllType, fmt.Sprintf("Specify inspect-oject type (%q, %q or %q)", ImageType, ContainerType, AllType)) + flags.BoolVarP(&opts.Latest, "latest", "l", false, "Act on the latest container Podman is aware of") + + return &opts +} + +// Inspect inspects the specified container/image names or IDs. +func Inspect(namesOrIDs []string, options entities.InspectOptions) error { + inspector, err := newInspector(options) + if err != nil { + return err + } + return inspector.inspect(namesOrIDs) +} + +// inspector allows for inspecting images and containers. +type inspector struct { + containerEngine entities.ContainerEngine + imageEngine entities.ImageEngine + options entities.InspectOptions +} + +// newInspector creates a new inspector based on the specified options. +func newInspector(options entities.InspectOptions) (*inspector, error) { + switch options.Type { + case ImageType, ContainerType, AllType: + // Valid types. + default: + return nil, errors.Errorf("invalid type %q: must be %q, %q or %q", options.Type, ImageType, ContainerType, AllType) + } + if options.Type == ImageType { + if options.Latest { + return nil, errors.Errorf("latest is not supported for type %q", ImageType) + } + if options.Size { + return nil, errors.Errorf("size is not supported for type %q", ImageType) + } + } + return &inspector{ + containerEngine: registry.ContainerEngine(), + imageEngine: registry.ImageEngine(), + options: options, + }, nil +} + +// inspect inspects the specified container/image names or IDs. +func (i *inspector) inspect(namesOrIDs []string) error { + // data - dumping place for inspection results. + var data []interface{} + ctx := context.Background() + + if len(namesOrIDs) == 0 { + if !i.options.Latest { + return errors.New("no containers or images specified") + } + } + + tmpType := i.options.Type + if i.options.Latest { + if len(namesOrIDs) > 0 { + return errors.New("latest and containers are not allowed") + } + tmpType = ContainerType // -l works with --type=all + } + + // Inspect - note that AllType requires us to expensively query one-by-one. + switch tmpType { + case AllType: + all, err := i.inspectAll(ctx, namesOrIDs) + if err != nil { + return err + } + data = all + case ImageType: + imgData, err := i.imageEngine.Inspect(ctx, namesOrIDs, i.options) + if err != nil { + return err + } + for i := range imgData { + data = append(data, imgData[i]) + } + case ContainerType: + ctrData, err := i.containerEngine.ContainerInspect(ctx, namesOrIDs, i.options) + if err != nil { + return err + } + for i := range ctrData { + data = append(data, ctrData[i]) + } + default: + return errors.Errorf("invalid type %q: must be %q, %q or %q", i.options.Type, ImageType, ContainerType, AllType) + } + + var out formats.Writer + if i.options.Format == "json" || i.options.Format == "" { // "" for backwards compat + out = formats.JSONStructArray{Output: data} + } else { + out = formats.StdoutTemplateArray{Output: data, Template: inspectFormat(i.options.Format)} + } + return out.Out() +} + +func (i *inspector) inspectAll(ctx context.Context, namesOrIDs []string) ([]interface{}, error) { + var data []interface{} + for _, name := range namesOrIDs { + imgData, err := i.imageEngine.Inspect(ctx, []string{name}, i.options) + if err == nil { + data = append(data, imgData[0]) + continue + } + ctrData, err := i.containerEngine.ContainerInspect(ctx, []string{name}, i.options) + if err != nil { + return nil, err + } + data = append(data, ctrData[0]) + } + return data, nil +} + +func inspectFormat(row string) string { + r := strings.NewReplacer( + "{{.Id}}", formats.IDString, + ".Src", ".Source", + ".Dst", ".Destination", + ".ImageID", ".Image", + ) + return r.Replace(row) +} diff --git a/cmd/podman/login.go b/cmd/podman/login.go index 1843a764d..9de805d15 100644 --- a/cmd/podman/login.go +++ b/cmd/podman/login.go @@ -19,7 +19,7 @@ type loginOptionsWrapper struct { var ( loginOptions = loginOptionsWrapper{} loginCommand = &cobra.Command{ - Use: "login [flags] REGISTRY", + Use: "login [flags] [REGISTRY]", Short: "Login to a container registry", Long: "Login to a container registry on a specified server.", RunE: login, diff --git a/cmd/podman/logout.go b/cmd/podman/logout.go index 77bdc92b4..c21711fc0 100644 --- a/cmd/podman/logout.go +++ b/cmd/podman/logout.go @@ -14,7 +14,7 @@ import ( var ( logoutOptions = auth.LogoutOptions{} logoutCommand = &cobra.Command{ - Use: "logout [flags] REGISTRY", + Use: "logout [flags] [REGISTRY]", Short: "Logout of a container registry", Long: "Remove the cached username and password for the registry.", RunE: logout, diff --git a/cmd/podman/main.go b/cmd/podman/main.go index 8109eca2f..3a8958b6d 100644 --- a/cmd/podman/main.go +++ b/cmd/podman/main.go @@ -1,18 +1,21 @@ package main import ( + "fmt" "os" _ "github.com/containers/libpod/cmd/podman/containers" + _ "github.com/containers/libpod/cmd/podman/generate" _ "github.com/containers/libpod/cmd/podman/healthcheck" _ "github.com/containers/libpod/cmd/podman/images" _ "github.com/containers/libpod/cmd/podman/manifest" - _ "github.com/containers/libpod/cmd/podman/networks" _ "github.com/containers/libpod/cmd/podman/pods" "github.com/containers/libpod/cmd/podman/registry" _ "github.com/containers/libpod/cmd/podman/system" _ "github.com/containers/libpod/cmd/podman/volumes" + "github.com/containers/libpod/pkg/rootless" "github.com/containers/storage/pkg/reexec" + "github.com/spf13/cobra" ) func main() { @@ -26,6 +29,14 @@ func main() { for _, c := range registry.Commands { for _, m := range c.Mode { if cfg.EngineMode == m { + // Command cannot be run rootless + _, found := c.Command.Annotations[registry.ParentNSRequired] + if rootless.IsRootless() && found { + c.Command.RunE = func(cmd *cobra.Command, args []string) error { + return fmt.Errorf("cannot `%s` in rootless mode", cmd.CommandPath()) + } + } + parent := rootCmd if c.Parent != nil { parent = c.Parent diff --git a/cmd/podman/manifest/add.go b/cmd/podman/manifest/add.go index c83beff7a..38f832fad 100644 --- a/cmd/podman/manifest/add.go +++ b/cmd/podman/manifest/add.go @@ -13,7 +13,7 @@ import ( var ( manifestAddOpts = entities.ManifestAddOptions{} addCmd = &cobra.Command{ - Use: "add", + Use: "add [flags] LIST LIST", Short: "Add images to a manifest list or image index", Long: "Adds an image to a manifest list or image index.", RunE: add, diff --git a/cmd/podman/manifest/create.go b/cmd/podman/manifest/create.go index 4f3e27774..9c0097b90 100644 --- a/cmd/podman/manifest/create.go +++ b/cmd/podman/manifest/create.go @@ -13,7 +13,7 @@ import ( var ( manifestCreateOpts = entities.ManifestCreateOptions{} createCmd = &cobra.Command{ - Use: "create", + Use: "create [flags] LIST [IMAGE]", Short: "Create manifest list or image index", Long: "Creates manifest lists or image indexes.", RunE: create, diff --git a/cmd/podman/manifest/inspect.go b/cmd/podman/manifest/inspect.go index 36ecdc87b..5112aa5b2 100644 --- a/cmd/podman/manifest/inspect.go +++ b/cmd/podman/manifest/inspect.go @@ -12,7 +12,7 @@ import ( var ( inspectCmd = &cobra.Command{ - Use: "inspect IMAGE", + Use: "inspect [flags] IMAGE", Short: "Display the contents of a manifest list or image index", Long: "Display the contents of a manifest list or image index.", RunE: inspect, diff --git a/cmd/podman/manifest/manifest.go b/cmd/podman/manifest/manifest.go index b9ac7ea68..b78879b34 100644 --- a/cmd/podman/manifest/manifest.go +++ b/cmd/podman/manifest/manifest.go @@ -2,6 +2,7 @@ package manifest import ( "github.com/containers/libpod/cmd/podman/registry" + "github.com/containers/libpod/cmd/podman/validate" "github.com/containers/libpod/pkg/domain/entities" "github.com/spf13/cobra" ) @@ -13,7 +14,7 @@ var ( Short: "Manipulate manifest lists and image indexes", Long: manifestDescription, TraverseChildren: true, - RunE: registry.SubCommandExists, + RunE: validate.SubCommandExists, Example: `podman manifest create localhost/list podman manifest inspect localhost/list`, } diff --git a/cmd/podman/networks/network.go b/cmd/podman/networks/network.go index 3cee86bcc..e2a928312 100644 --- a/cmd/podman/networks/network.go +++ b/cmd/podman/networks/network.go @@ -2,6 +2,7 @@ package images import ( "github.com/containers/libpod/cmd/podman/registry" + "github.com/containers/libpod/cmd/podman/validate" "github.com/containers/libpod/pkg/domain/entities" "github.com/spf13/cobra" ) @@ -13,10 +14,13 @@ var ( Short: "Manage networks", Long: "Manage networks", TraverseChildren: true, - RunE: registry.SubCommandExists, + RunE: validate.SubCommandExists, } ) +// TODO add the following to main.go to get networks back onto the +// command list. +// _ "github.com/containers/libpod/cmd/podman/networks" func init() { registry.Commands = append(registry.Commands, registry.CliCommand{ Mode: []entities.EngineMode{entities.ABIMode}, diff --git a/cmd/podman/parse/net.go b/cmd/podman/parse/net.go index 03cda268c..f93c4ab1e 100644 --- a/cmd/podman/parse/net.go +++ b/cmd/podman/parse/net.go @@ -1,4 +1,4 @@ -//nolint +// nolint // most of these validate and parse functions have been taken from projectatomic/docker // and modified for cri-o package parse @@ -46,7 +46,7 @@ var ( // validateExtraHost validates that the specified string is a valid extrahost and returns it. // ExtraHost is in the form of name:ip where the ip has to be a valid ip (ipv4 or ipv6). // for add-host flag -func ValidateExtraHost(val string) (string, error) { //nolint +func ValidateExtraHost(val string) (string, error) { // nolint // allow for IPv6 addresses in extra hosts by only splitting on first ":" arr := strings.SplitN(val, ":", 2) if len(arr) != 2 || len(arr[0]) == 0 { diff --git a/cmd/podman/parse/net_test.go b/cmd/podman/parse/net_test.go index a6ddc2be9..51c8509df 100644 --- a/cmd/podman/parse/net_test.go +++ b/cmd/podman/parse/net_test.go @@ -1,4 +1,4 @@ -//nolint +// nolint // most of these validate and parse functions have been taken from projectatomic/docker // and modified for cri-o package parse @@ -41,7 +41,7 @@ func TestValidateExtraHost(t *testing.T) { want string wantErr bool }{ - //2001:0db8:85a3:0000:0000:8a2e:0370:7334 + // 2001:0db8:85a3:0000:0000:8a2e:0370:7334 {name: "good-ipv4", args: args{val: "foobar:192.168.1.1"}, want: "foobar:192.168.1.1", wantErr: false}, {name: "bad-ipv4", args: args{val: "foobar:999.999.999.99"}, want: "", wantErr: true}, {name: "bad-ipv4", args: args{val: "foobar:999.999.999"}, want: "", wantErr: true}, diff --git a/cmd/podman/pods/create.go b/cmd/podman/pods/create.go index ff21166f3..85b96d37b 100644 --- a/cmd/podman/pods/create.go +++ b/cmd/podman/pods/create.go @@ -9,6 +9,7 @@ import ( "github.com/containers/libpod/cmd/podman/common" "github.com/containers/libpod/cmd/podman/parse" "github.com/containers/libpod/cmd/podman/registry" + "github.com/containers/libpod/cmd/podman/validate" "github.com/containers/libpod/pkg/domain/entities" "github.com/containers/libpod/pkg/errorhandling" "github.com/containers/libpod/pkg/specgen" @@ -24,7 +25,7 @@ var ( createCommand = &cobra.Command{ Use: "create", - Args: cobra.NoArgs, + Args: validate.NoArgs, Short: "Create a new empty pod", Long: podCreateDescription, RunE: create, @@ -116,7 +117,7 @@ func create(cmd *cobra.Command, args []string) error { case "slip4netns": n.NSMode = specgen.Slirp default: - if strings.HasPrefix(netInput, "container:") { //nolint + if strings.HasPrefix(netInput, "container:") { // nolint split := strings.Split(netInput, ":") if len(split) != 2 { return errors.Errorf("invalid network paramater: %q", netInput) diff --git a/cmd/podman/pods/exists.go b/cmd/podman/pods/exists.go index 5a94bf150..cf3e3eae5 100644 --- a/cmd/podman/pods/exists.go +++ b/cmd/podman/pods/exists.go @@ -19,6 +19,7 @@ var ( Args: cobra.ExactArgs(1), Example: `podman pod exists podID podman pod exists mypod || podman pod create --name mypod`, + DisableFlagsInUseLine: true, } ) diff --git a/cmd/podman/pods/pod.go b/cmd/podman/pods/pod.go index e86b8aba4..edca08202 100644 --- a/cmd/podman/pods/pod.go +++ b/cmd/podman/pods/pod.go @@ -2,6 +2,7 @@ package pods import ( "github.com/containers/libpod/cmd/podman/registry" + "github.com/containers/libpod/cmd/podman/validate" "github.com/containers/libpod/pkg/domain/entities" "github.com/containers/libpod/pkg/util" "github.com/spf13/cobra" @@ -17,7 +18,7 @@ var ( Short: "Manage pods", Long: "Manage pods", TraverseChildren: true, - RunE: registry.SubCommandExists, + RunE: validate.SubCommandExists, } containerConfig = util.DefaultContainerConfig() ) diff --git a/cmd/podman/pods/ps.go b/cmd/podman/pods/ps.go index 6d0d9cf7f..b97dfeb66 100644 --- a/cmd/podman/pods/ps.go +++ b/cmd/podman/pods/ps.go @@ -12,6 +12,7 @@ import ( "time" "github.com/containers/libpod/cmd/podman/registry" + "github.com/containers/libpod/cmd/podman/validate" "github.com/containers/libpod/pkg/domain/entities" "github.com/docker/go-units" "github.com/pkg/errors" @@ -28,11 +29,12 @@ var ( Short: "list pods", Long: psDescription, RunE: pods, + Args: validate.NoArgs, } ) var ( - defaultHeaders string = "POD ID\tNAME\tSTATUS\tCREATED" + defaultHeaders = "POD ID\tNAME\tSTATUS\tCREATED" inputFilters []string noTrunc bool psInput entities.PodPSOptions diff --git a/cmd/podman/pods/rm.go b/cmd/podman/pods/rm.go index ea3a6476a..4b9882f8a 100644 --- a/cmd/podman/pods/rm.go +++ b/cmd/podman/pods/rm.go @@ -41,10 +41,10 @@ func init() { }) flags := rmCommand.Flags() - flags.BoolVarP(&rmOptions.All, "all", "a", false, "Restart all running pods") + flags.BoolVarP(&rmOptions.All, "all", "a", false, "Remove all running pods") flags.BoolVarP(&rmOptions.Force, "force", "f", false, "Force removal of a running pod by first stopping all containers, then removing all containers in the pod. The default is false") flags.BoolVarP(&rmOptions.Ignore, "ignore", "i", false, "Ignore errors when a specified pod is missing") - flags.BoolVarP(&rmOptions.Latest, "latest", "l", false, "Restart the latest pod podman is aware of") + flags.BoolVarP(&rmOptions.Latest, "latest", "l", false, "Remove the latest pod podman is aware of") if registry.IsRemote() { _ = flags.MarkHidden("latest") _ = flags.MarkHidden("ignore") diff --git a/cmd/podman/registry/registry.go b/cmd/podman/registry/registry.go index 2e9d59d10..69e2babfc 100644 --- a/cmd/podman/registry/registry.go +++ b/cmd/podman/registry/registry.go @@ -5,7 +5,6 @@ import ( "github.com/containers/libpod/pkg/domain/entities" "github.com/containers/libpod/pkg/domain/infra" - "github.com/pkg/errors" "github.com/spf13/cobra" ) @@ -76,21 +75,6 @@ func NewContainerEngine(cmd *cobra.Command, args []string) (entities.ContainerEn return containerEngine, nil } -func SubCommandExists(cmd *cobra.Command, args []string) error { - if len(args) > 0 { - return errors.Errorf("unrecognized command `%[1]s %[2]s`\nTry '%[1]s --help' for more information.", cmd.CommandPath(), args[0]) - } - return errors.Errorf("missing command '%[1]s COMMAND'\nTry '%[1]s --help' for more information.", cmd.CommandPath()) -} - -// IdOrLatestArgs used to validate a nameOrId was provided or the "--latest" flag -func IdOrLatestArgs(cmd *cobra.Command, args []string) error { - if len(args) > 1 || (len(args) == 0 && !cmd.Flag("latest").Changed) { - return errors.New(`command requires a name, id or the "--latest" flag`) - } - return nil -} - type PodmanOptionsKey struct{} func Context() context.Context { diff --git a/cmd/podman/root.go b/cmd/podman/root.go index 84c3867f2..375faf8b1 100644 --- a/cmd/podman/root.go +++ b/cmd/podman/root.go @@ -9,6 +9,7 @@ import ( "strings" "github.com/containers/libpod/cmd/podman/registry" + "github.com/containers/libpod/cmd/podman/validate" "github.com/containers/libpod/pkg/domain/entities" "github.com/containers/libpod/pkg/rootless" "github.com/containers/libpod/pkg/tracing" @@ -34,7 +35,7 @@ Description: // UsageTemplate is the usage template for podman commands // This blocks the displaying of the global options. The main podman // command should not use this. -const usageTemplate = `Usage(v2):{{if (and .Runnable (not .HasAvailableSubCommands))}} +const usageTemplate = `Usage:{{if (and .Runnable (not .HasAvailableSubCommands))}} {{.UseLine}}{{end}}{{if .HasAvailableSubCommands}} {{.CommandPath}} [command]{{end}}{{if gt (len .Aliases) 0}} @@ -60,7 +61,7 @@ var ( SilenceErrors: true, TraverseChildren: true, PersistentPreRunE: persistentPreRunE, - RunE: registry.SubCommandExists, + RunE: validate.SubCommandExists, PersistentPostRunE: persistentPostRunE, Version: version.Version, } @@ -78,6 +79,10 @@ func init() { ) rootFlags(registry.PodmanConfig(), rootCmd.PersistentFlags()) + + // "version" is a local flag to avoid collisions with sub-commands that use "-v" + var dummyVersion bool + rootCmd.Flags().BoolVarP(&dummyVersion, "version", "v", false, "Version of Podman") } func Execute() { @@ -96,7 +101,7 @@ func Execute() { func persistentPreRunE(cmd *cobra.Command, args []string) error { // TODO: Remove trace statement in podman V2.1 - logrus.Debugf("Called %s.PersistentPreRunE()", cmd.Name()) + logrus.Debugf("Called %s.PersistentPreRunE(%s)", cmd.Name(), strings.Join(os.Args, " ")) cfg := registry.PodmanConfig() @@ -145,7 +150,7 @@ func persistentPreRunE(cmd *cobra.Command, args []string) error { func persistentPostRunE(cmd *cobra.Command, args []string) error { // TODO: Remove trace statement in podman V2.1 - logrus.Debugf("Called %s.PersistentPostRunE()", cmd.Name()) + logrus.Debugf("Called %s.PersistentPostRunE(%s)", cmd.Name(), strings.Join(os.Args, " ")) cfg := registry.PodmanConfig() if cmd.Flag("cpu-profile").Changed { @@ -206,11 +211,6 @@ func rootFlags(opts *entities.PodmanConfig, flags *pflag.FlagSet) { flags.StringVarP(&opts.Uri, "remote", "r", "", "URL to access Podman service") flags.StringSliceVar(&opts.Identities, "identity", []string{}, "path to SSH identity file") - // Override default --help information of `--version` global flag - // TODO: restore -v option for version without breaking -v for volumes - var dummyVersion bool - flags.BoolVar(&dummyVersion, "version", false, "Version of Podman") - cfg := opts.Config flags.StringVar(&cfg.Engine.CgroupManager, "cgroup-manager", cfg.Engine.CgroupManager, opts.CGroupUsage) flags.StringVar(&opts.CpuProfile, "cpu-profile", "", "Path for the cpu profiling results") diff --git a/cmd/podman/system/events.go b/cmd/podman/system/events.go index 3c1943b55..6aae62dc0 100644 --- a/cmd/podman/system/events.go +++ b/cmd/podman/system/events.go @@ -8,6 +8,7 @@ import ( "github.com/containers/buildah/pkg/formats" "github.com/containers/libpod/cmd/podman/registry" + "github.com/containers/libpod/cmd/podman/validate" "github.com/containers/libpod/libpod/events" "github.com/containers/libpod/pkg/domain/entities" "github.com/pkg/errors" @@ -18,7 +19,7 @@ var ( eventsDescription = "Monitor podman events" eventsCommand = &cobra.Command{ Use: "events", - Args: cobra.NoArgs, + Args: validate.NoArgs, Short: "Show podman events", Long: eventsDescription, RunE: eventsCmd, diff --git a/cmd/podman/system/info.go b/cmd/podman/system/info.go index 8b36ef549..26be794c5 100644 --- a/cmd/podman/system/info.go +++ b/cmd/podman/system/info.go @@ -6,6 +6,7 @@ import ( "text/template" "github.com/containers/libpod/cmd/podman/registry" + "github.com/containers/libpod/cmd/podman/validate" "github.com/containers/libpod/pkg/domain/entities" "github.com/ghodss/yaml" "github.com/spf13/cobra" @@ -18,7 +19,7 @@ var ( ` infoCommand = &cobra.Command{ Use: "info", - Args: cobra.NoArgs, + Args: validate.NoArgs, Long: infoDescription, Short: "Display podman system information", RunE: info, diff --git a/cmd/podman/system/system.go b/cmd/podman/system/system.go index 2d55e8c13..d9691ad2a 100644 --- a/cmd/podman/system/system.go +++ b/cmd/podman/system/system.go @@ -2,6 +2,7 @@ package system import ( "github.com/containers/libpod/cmd/podman/registry" + "github.com/containers/libpod/cmd/podman/validate" "github.com/containers/libpod/pkg/domain/entities" "github.com/spf13/cobra" ) @@ -16,7 +17,7 @@ var ( Short: "Manage podman", Long: "Manage podman", TraverseChildren: true, - RunE: registry.SubCommandExists, + RunE: validate.SubCommandExists, } ) diff --git a/cmd/podman/system/version.go b/cmd/podman/system/version.go index 5d3874de3..065eef309 100644 --- a/cmd/podman/system/version.go +++ b/cmd/podman/system/version.go @@ -10,6 +10,7 @@ import ( "github.com/containers/buildah/pkg/formats" "github.com/containers/libpod/cmd/podman/registry" + "github.com/containers/libpod/cmd/podman/validate" "github.com/containers/libpod/libpod/define" "github.com/containers/libpod/pkg/domain/entities" "github.com/pkg/errors" @@ -19,7 +20,7 @@ import ( var ( versionCommand = &cobra.Command{ Use: "version", - Args: cobra.NoArgs, + Args: validate.NoArgs, Short: "Display the Podman Version Information", RunE: version, Annotations: map[string]string{ @@ -55,14 +56,14 @@ func version(cmd *cobra.Command, args []string) error { // TODO we need to discuss how to implement // this more. current endpoints dont have a // version endpoint. maybe we use info? - //if remote { + // if remote { // v.Server, err = getRemoteVersion(c) // if err != nil { // return err // } - //} else { + // } else { v.Server = v.Client - //} + // } versionOutputFormat := versionFormat if versionOutputFormat != "" { diff --git a/cmd/podman/utils/alias.go b/cmd/podman/utils/alias.go index 54b3c5e89..e484461c5 100644 --- a/cmd/podman/utils/alias.go +++ b/cmd/podman/utils/alias.go @@ -2,7 +2,7 @@ package utils import "github.com/spf13/pflag" -// AliasFlags is a function to handle backwards compatability with old flags +// AliasFlags is a function to handle backwards compatibility with old flags func AliasFlags(f *pflag.FlagSet, name string) pflag.NormalizedName { switch name { case "healthcheck-command": diff --git a/cmd/podman/validate/args.go b/cmd/podman/validate/args.go new file mode 100644 index 000000000..14b4d7897 --- /dev/null +++ b/cmd/podman/validate/args.go @@ -0,0 +1,32 @@ +package validate + +import ( + "fmt" + + "github.com/pkg/errors" + "github.com/spf13/cobra" +) + +// NoArgs returns an error if any args are included. +func NoArgs(cmd *cobra.Command, args []string) error { + if len(args) > 0 { + return fmt.Errorf("`%s` takes no arguments", cmd.CommandPath()) + } + return nil +} + +// SubCommandExists returns an error if no sub command is provided +func SubCommandExists(cmd *cobra.Command, args []string) error { + if len(args) > 0 { + return errors.Errorf("unrecognized command `%[1]s %[2]s`\nTry '%[1]s --help' for more information.", cmd.CommandPath(), args[0]) + } + return errors.Errorf("missing command '%[1]s COMMAND'\nTry '%[1]s --help' for more information.", cmd.CommandPath()) +} + +// IdOrLatestArgs used to validate a nameOrId was provided or the "--latest" flag +func IdOrLatestArgs(cmd *cobra.Command, args []string) error { + if len(args) > 1 || (len(args) == 0 && !cmd.Flag("latest").Changed) { + return fmt.Errorf("`%s` requires a name, id or the \"--latest\" flag", cmd.CommandPath()) + } + return nil +} diff --git a/cmd/podman/validate/choice.go b/cmd/podman/validate/choice.go new file mode 100644 index 000000000..572c5f4a5 --- /dev/null +++ b/cmd/podman/validate/choice.go @@ -0,0 +1,46 @@ +package validate + +import ( + "fmt" + "strings" +) + +// Honors cobra.Value interface +type choiceValue struct { + value *string + choices []string +} + +// ChoiceValue may be used in cobra FlagSet methods Var/VarP/VarPF() to select from a set of values +// +// Example: +// created := validate.ChoiceValue(&opts.Sort, "command", "created", "id", "image", "names", "runningfor", "size", "status") +// flags.Var(created, "sort", "Sort output by: "+created.Choices()) +func ChoiceValue(p *string, choices ...string) *choiceValue { + return &choiceValue{ + value: p, + choices: choices, + } +} + +func (c *choiceValue) String() string { + return *c.value +} + +func (c *choiceValue) Set(value string) error { + for _, v := range c.choices { + if v == value { + *c.value = value + return nil + } + } + return fmt.Errorf("%q is not a valid value. Choose from: %q", value, c.Choices()) +} + +func (c *choiceValue) Choices() string { + return strings.Join(c.choices, ", ") +} + +func (c *choiceValue) Type() string { + return "choice" +} diff --git a/cmd/podman/volumes/list.go b/cmd/podman/volumes/list.go index f75de6b4b..72bf9f25b 100644 --- a/cmd/podman/volumes/list.go +++ b/cmd/podman/volumes/list.go @@ -2,6 +2,7 @@ package volumes import ( "context" + "fmt" "html/template" "io" "os" @@ -9,6 +10,7 @@ import ( "text/tabwriter" "github.com/containers/libpod/cmd/podman/registry" + "github.com/containers/libpod/cmd/podman/validate" "github.com/containers/libpod/pkg/domain/entities" "github.com/pkg/errors" "github.com/spf13/cobra" @@ -23,7 +25,7 @@ and the output format can be changed to JSON or a user specified Go template.` lsCommand = &cobra.Command{ Use: "ls", Aliases: []string{"list"}, - Args: cobra.NoArgs, + Args: validate.NoArgs, Short: "List volumes", Long: volumeLsDescription, RunE: list, @@ -57,6 +59,9 @@ func list(cmd *cobra.Command, args []string) error { if cliOpts.Quiet && cmd.Flag("format").Changed { return errors.New("quiet and format flags cannot be used together") } + if len(cliOpts.Filter) > 0 { + lsOpts.Filter = make(map[string][]string) + } for _, f := range cliOpts.Filter { filterSplit := strings.Split(f, "=") if len(filterSplit) < 2 { @@ -68,6 +73,10 @@ func list(cmd *cobra.Command, args []string) error { if err != nil { return err } + if cliOpts.Format == "json" { + return outputJSON(responses) + } + if len(responses) < 1 { return nil } @@ -99,3 +108,12 @@ func list(cmd *cobra.Command, args []string) error { } return nil } + +func outputJSON(vols []*entities.VolumeListReport) error { + b, err := json.MarshalIndent(vols, "", " ") + if err != nil { + return err + } + fmt.Println(string(b)) + return nil +} diff --git a/cmd/podman/volumes/prune.go b/cmd/podman/volumes/prune.go index 197a9da9b..2c3ed88f3 100644 --- a/cmd/podman/volumes/prune.go +++ b/cmd/podman/volumes/prune.go @@ -9,6 +9,7 @@ import ( "github.com/containers/libpod/cmd/podman/registry" "github.com/containers/libpod/cmd/podman/utils" + "github.com/containers/libpod/cmd/podman/validate" "github.com/containers/libpod/pkg/domain/entities" "github.com/pkg/errors" "github.com/spf13/cobra" @@ -21,7 +22,7 @@ var ( Note all data will be destroyed.` pruneCommand = &cobra.Command{ Use: "prune", - Args: cobra.NoArgs, + Args: validate.NoArgs, Short: "Remove all unused volumes", Long: volumePruneDescription, RunE: prune, diff --git a/cmd/podman/volumes/volume.go b/cmd/podman/volumes/volume.go index 4d74ff084..3e90d178c 100644 --- a/cmd/podman/volumes/volume.go +++ b/cmd/podman/volumes/volume.go @@ -2,6 +2,7 @@ package volumes import ( "github.com/containers/libpod/cmd/podman/registry" + "github.com/containers/libpod/cmd/podman/validate" "github.com/containers/libpod/pkg/domain/entities" "github.com/spf13/cobra" ) @@ -16,7 +17,7 @@ var ( Short: "Manage volumes", Long: "Volumes are created in and can be shared between containers", TraverseChildren: true, - RunE: registry.SubCommandExists, + RunE: validate.SubCommandExists, } ) diff --git a/contrib/cirrus/lib.sh b/contrib/cirrus/lib.sh index 04f14eeb3..750aec3b6 100644 --- a/contrib/cirrus/lib.sh +++ b/contrib/cirrus/lib.sh @@ -6,6 +6,11 @@ # Global details persist here source /etc/environment # not always loaded under all circumstances +# Automation environment doesn't automatically load for Ubuntu 18 +if [[ -r '/usr/share/automation/environment' ]]; then + source '/usr/share/automation/environment' +fi + # Under some contexts these values are not set, make sure they are. export USER="$(whoami)" export HOME="$(getent passwd $USER | cut -d : -f 6)" @@ -64,18 +69,23 @@ export PACKER_BUILDS="${PACKER_BUILDS:-ubuntu-18,ubuntu-19,fedora-32,fedora-31}" export UBUNTU_BASE_IMAGE="ubuntu-1910-eoan-v20200211" export PRIOR_UBUNTU_BASE_IMAGE="ubuntu-1804-bionic-v20200218" # Manually produced base-image names (see $SCRIPT_BASE/README.md) -export FEDORA_BASE_IMAGE="fedora-cloud-base-32-n-0-1586202964" -export PRIOR_FEDORA_BASE_IMAGE="fedora-cloud-base-31-1-9-1586202964" +export FEDORA_BASE_IMAGE="fedora-cloud-base-32-1-6-1588257430" +export PRIOR_FEDORA_BASE_IMAGE="fedora-cloud-base-31-1-9-1588257430" export BUILT_IMAGE_SUFFIX="${BUILT_IMAGE_SUFFIX:--$CIRRUS_REPO_NAME-${CIRRUS_BUILD_ID}}" # IN_PODMAN container image IN_PODMAN_IMAGE="quay.io/libpod/in_podman:$DEST_BRANCH" # Image for uploading releases UPLDREL_IMAGE="quay.io/libpod/upldrel:master" +# This is needed under some environments/contexts +SUDO='' +[[ "$UID" -eq 0 ]] || \ + SUDO='sudo -E' + # Avoid getting stuck waiting for user input export DEBIAN_FRONTEND="noninteractive" -SUDOAPTGET="ooe.sh sudo -E apt-get -qq --yes" -SUDOAPTADD="ooe.sh sudo -E add-apt-repository --yes" +SUDOAPTGET="$SUDO apt-get -qq --yes" +SUDOAPTADD="$SUDO add-apt-repository --yes" # Regex that finds enabled periodic apt configuration items PERIODIC_APT_RE='^(APT::Periodic::.+")1"\;' # Short-cuts for retrying/timeout calls @@ -109,6 +119,9 @@ OS_REL_VER="${OS_RELEASE_ID}-${OS_RELEASE_VER}" # Type of filesystem used for cgroups CG_FS_TYPE="$(stat -f -c %T /sys/fs/cgroup)" +# When building images, the version of automation tooling to install +INSTALL_AUTOMATION_VERSION=1.1.3 + # Installed into cache-images, supports overrides # by user-data in case of breakage or for debugging. CUSTOM_CLOUD_CONFIG_DEFAULTS="$GOSRC/$PACKER_BASE/cloud-init/$OS_RELEASE_ID/cloud.cfg.d" @@ -354,25 +367,18 @@ setup_rootless() { die 11 "Timeout exceeded waiting for localhost ssh capability" } -# Helper/wrapper script to only show stderr/stdout on non-zero exit -install_ooe() { - req_env_var SCRIPT_BASE - echo "Installing script to mask stdout/stderr unless non-zero exit." - sudo install -D -m 755 "$GOSRC/$SCRIPT_BASE/ooe.sh" /usr/local/bin/ooe.sh -} - # Grab a newer version of git from software collections # https://www.softwarecollections.org/en/ # and use it with a wrapper install_scl_git() { echo "Installing SoftwareCollections updated 'git' version." - ooe.sh sudo yum -y install rh-git29 - cat << "EOF" | sudo tee /usr/bin/git + ooe.sh $SUDO yum -y install rh-git29 + cat << "EOF" | $SUDO tee /usr/bin/git #!/bin/bash scl enable rh-git29 -- git $@ EOF - sudo chmod 755 /usr/bin/git + $SUDO chmod 755 /usr/bin/git } install_test_configs() { @@ -414,9 +420,9 @@ remove_packaged_podman_files() { if [[ "$OS_RELEASE_ID" =~ "ubuntu" ]] then - LISTING_CMD="sudo -E dpkg-query -L podman" + LISTING_CMD="$SUDO dpkg-query -L podman" else - LISTING_CMD='sudo rpm -ql podman' + LISTING_CMD='$SUDO rpm -ql podman' fi # yum/dnf/dpkg may list system directories, only remove files @@ -424,7 +430,7 @@ remove_packaged_podman_files() { do # Sub-directories may contain unrelated/valuable stuff if [[ -d "$fullpath" ]]; then continue; fi - ooe.sh sudo rm -vf "$fullpath" + ooe.sh $SUDO rm -vf "$fullpath" done # Be super extra sure and careful vs performant and completely safe @@ -447,43 +453,60 @@ systemd_banish() { $GOSRC/$PACKER_BASE/systemd_banish.sh } +# This can be removed when the kernel bug fix is included in Fedora +workaround_bfq_bug() { + if [[ "$OS_RELEASE_ID" == "fedora" ]] && [[ $OS_RELEASE_VER -le 32 ]]; then + warn "Switching io scheduler to 'deadline' to avoid RHBZ 1767539" + warn "aka https://bugzilla.kernel.org/show_bug.cgi?id=205447" + echo "mq-deadline" | sudo tee /sys/block/sda/queue/scheduler > /dev/null + echo -n "IO Scheduler set to: " + $SUDO cat /sys/block/sda/queue/scheduler + fi +} + +# Warning: DO NOT USE. +# This is called by other functions as the very last step during the VM Image build +# process. It's purpose is to "reset" the image, so all the first-boot operations +# happen at test runtime (like generating new ssh host keys, resizing partitions, etc.) _finalize() { set +e # Don't fail at the very end if [[ -d "$CUSTOM_CLOUD_CONFIG_DEFAULTS" ]] then echo "Installing custom cloud-init defaults" - sudo cp -v "$CUSTOM_CLOUD_CONFIG_DEFAULTS"/* /etc/cloud/cloud.cfg.d/ + $SUDO cp -v "$CUSTOM_CLOUD_CONFIG_DEFAULTS"/* /etc/cloud/cloud.cfg.d/ else echo "Could not find any files in $CUSTOM_CLOUD_CONFIG_DEFAULTS" fi echo "Re-initializing so next boot does 'first-boot' setup again." cd / - sudo rm -rf /var/lib/cloud/instanc* - sudo rm -rf /root/.ssh/* - sudo rm -rf /etc/ssh/*key* - sudo rm -rf /etc/ssh/moduli - sudo rm -rf /home/* - sudo rm -rf /tmp/* - sudo rm -rf /tmp/.??* - sudo sync - sudo fstrim -av + $SUDO rm -rf /var/lib/cloud/instanc* + $SUDO rm -rf /root/.ssh/* + $SUDO rm -rf /etc/ssh/*key* + $SUDO rm -rf /etc/ssh/moduli + $SUDO rm -rf /home/* + $SUDO rm -rf /tmp/* + $SUDO rm -rf /tmp/.??* + $SUDO sync + $SUDO fstrim -av } +# Called during VM Image setup, not intended for general use. rh_finalize() { set +e # Don't fail at the very end echo "Resetting to fresh-state for usage as cloud-image." PKG=$(type -P dnf || type -P yum || echo "") - sudo $PKG clean all - sudo rm -rf /var/cache/{yum,dnf} - sudo rm -f /etc/udev/rules.d/*-persistent-*.rules - sudo touch /.unconfigured # force firstboot to run + $SUDO $PKG clean all + $SUDO rm -rf /var/cache/{yum,dnf} + $SUDO rm -f /etc/udev/rules.d/*-persistent-*.rules + $SUDO touch /.unconfigured # force firstboot to run _finalize } +# Called during VM Image setup, not intended for general use. ubuntu_finalize() { set +e # Don't fail at the very end echo "Resetting to fresh-state for usage as cloud-image." $LILTO $SUDOAPTGET autoremove - sudo rm -rf /var/cache/apt + $SUDO rm -rf /var/cache/apt _finalize } diff --git a/contrib/cirrus/packer/fedora_base-setup.sh b/contrib/cirrus/packer/fedora_base-setup.sh index 29c23117f..f271abee0 100644 --- a/contrib/cirrus/packer/fedora_base-setup.sh +++ b/contrib/cirrus/packer/fedora_base-setup.sh @@ -8,16 +8,14 @@ set -e # Load in library (copied by packer, before this script was run) source $GOSRC/$SCRIPT_BASE/lib.sh -install_ooe - echo "Updating packages" -ooe.sh dnf -y update +dnf -y update echo "Installing necessary packages and google services" -ooe.sh dnf -y install rng-tools google-compute-engine-tools google-compute-engine-oslogin ethtool +dnf -y install rng-tools google-compute-engine-tools google-compute-engine-oslogin ethtool echo "Enabling services" -ooe.sh systemctl enable rngd +systemctl enable rngd # There is a race that can happen on boot between the GCE services configuring # the VM, and cloud-init trying to do similar activities. Use a customized @@ -25,6 +23,19 @@ ooe.sh systemctl enable rngd echo "Setting cloud-init service to start after google-network-daemon.service" cp -v $GOSRC/$PACKER_BASE/cloud-init/fedora/cloud-init.service /etc/systemd/system/ +# ref: https://cloud.google.com/compute/docs/startupscript +# The mechanism used by Cirrus-CI to execute tasks on the system is through an +# "agent" process launched as a GCP startup-script (from the metadata service). +# This agent is responsible for cloning the repository and executing all task +# scripts and other operations. Therefor, on SELinux-enforcing systems, the +# service must be labeled properly to ensure it's child processes can +# run with the proper contexts. +METADATA_SERVICE_CTX=unconfined_u:unconfined_r:unconfined_t:s0 +METADATA_SERVICE_PATH=systemd/system/google-startup-scripts.service +sed -r -e \ + "s/Type=oneshot/Type=oneshot\nSELinuxContext=$METADATA_SERVICE_CTX/" \ + /lib/$METADATA_SERVICE_PATH > /etc/$METADATA_SERVICE_PATH + # Ensure there are no disruptive periodic services enabled by default in image systemd_banish diff --git a/contrib/cirrus/packer/fedora_packaging.sh b/contrib/cirrus/packer/fedora_packaging.sh new file mode 100644 index 000000000..e80d48bc8 --- /dev/null +++ b/contrib/cirrus/packer/fedora_packaging.sh @@ -0,0 +1,141 @@ +#!/bin/bash + +# This script is called from fedora_setup.sh and various Dockerfiles. +# It's not intended to be used outside of those contexts. It assumes the lib.sh +# library has already been sourced, and that all "ground-up" package-related activity +# needs to be done, including repository setup and initial update. + +set -e + +echo "Updating/Installing repos and packages for $OS_REL_VER" + +source $GOSRC/$SCRIPT_BASE/lib.sh + +# Pre-req. to install automation tooing +$LILTO $SUDO dnf install -y git + +# Install common automation tooling (i.e. ooe.sh) +curl --silent --show-error --location \ + --url "https://raw.githubusercontent.com/containers/automation/master/bin/install_automation.sh" | \ + $SUDO env INSTALL_PREFIX=/usr/share /bin/bash -s - "$INSTALL_AUTOMATION_VERSION" +# Reload installed environment right now (happens automatically in a new process) +source /usr/share/automation/environment + +# Set this to 1 to NOT enable updates-testing repository +DISABLE_UPDATES_TESTING=${DISABLE_UPDATES_TESTING:0} + +# Do not enable update-stesting on the previous Fedora release +if ((DISABLE_UPDATES_TESTING!=0)); then + warn "Enabling updates-testing repository for image based on $FEDORA_BASE_IMAGE" + $LILTO $SUDO ooe.sh dnf install -y 'dnf-command(config-manager)' + $LILTO $SUDO ooe.sh dnf config-manager --set-enabled updates-testing +else + warn "NOT enabling updates-testing repository for image based on $PRIOR_FEDORA_BASE_IMAGE" +fi + +$BIGTO ooe.sh $SUDO dnf update -y + +REMOVE_PACKAGES=() +INSTALL_PACKAGES=(\ + autoconf + automake + bash-completion + bats + bridge-utils + btrfs-progs-devel + buildah + bzip2 + conmon + container-selinux + containernetworking-plugins + containers-common + criu + device-mapper-devel + dnsmasq + emacs-nox + file + findutils + fuse3 + fuse3-devel + gcc + git + glib2-devel + glibc-static + gnupg + go-md2man + golang + gpgme-devel + iproute + iptables + jq + libassuan-devel + libcap-devel + libmsi1 + libnet + libnet-devel + libnl3-devel + libseccomp + libseccomp-devel + libselinux-devel + libtool + libvarlink-util + lsof + make + msitools + nmap-ncat + ostree-devel + pandoc + podman + procps-ng + protobuf + protobuf-c + protobuf-c-devel + protobuf-devel + python + python3-dateutil + python3-psutil + python3-pytoml + rsync + selinux-policy-devel + skopeo + skopeo-containers + slirp4netns + unzip + vim + wget + which + xz + zip +) + +case "$OS_RELEASE_VER" in + 30) + INSTALL_PACKAGES+=(\ + atomic-registries + golang-github-cpuguy83-go-md2man + python2-future + runc + ) + REMOVE_PACKAGES+=(crun) + ;; + 31) + INSTALL_PACKAGES+=(crun) + REMOVE_PACKAGES+=(runc) + ;; + 32) + INSTALL_PACKAGES+=(crun) + REMOVE_PACKAGES+=(runc) + ;; + *) + bad_os_id_ver ;; +esac + +echo "Installing general build/test dependencies for Fedora '$OS_RELEASE_VER'" +$BIGTO ooe.sh $SUDO dnf install -y ${INSTALL_PACKAGES[@]} + +[[ ${#REMOVE_PACKAGES[@]} -eq 0 ]] || \ + $LILTO ooe.sh $SUDO dnf erase -y ${REMOVE_PACKAGES[@]} + +export GOPATH="$(mktemp -d)" +trap "$SUDO rm -rf $GOPATH" EXIT +ooe.sh $SUDO $GOSRC/hack/install_catatonit.sh diff --git a/contrib/cirrus/packer/fedora_setup.sh b/contrib/cirrus/packer/fedora_setup.sh index fcef7360b..3830b3bc4 100644 --- a/contrib/cirrus/packer/fedora_setup.sh +++ b/contrib/cirrus/packer/fedora_setup.sh @@ -6,139 +6,26 @@ set -e # Load in library (copied by packer, before this script was run) -source /tmp/libpod/$SCRIPT_BASE/lib.sh +source $GOSRC/$SCRIPT_BASE/lib.sh -req_env_var SCRIPT_BASE PACKER_BUILDER_NAME GOSRC FEDORA_BASE_IMAGE OS_RELEASE_ID OS_RELEASE_VER +req_env_var SCRIPT_BASE PACKER_BASE INSTALL_AUTOMATION_VERSION PACKER_BUILDER_NAME GOSRC FEDORA_BASE_IMAGE OS_RELEASE_ID OS_RELEASE_VER -install_ooe - -if [[ $OS_RELEASE_VER -le 31 ]]; then - warn "Switching io scheduler to 'deadline' to avoid RHBZ 1767539" - warn "aka https://bugzilla.kernel.org/show_bug.cgi?id=205447" - echo "mq-deadline" | sudo tee /sys/block/sda/queue/scheduler > /dev/null - sudo cat /sys/block/sda/queue/scheduler -fi - -export GOPATH="$(mktemp -d)" -trap "sudo rm -rf $GOPATH" EXIT - -$BIGTO ooe.sh sudo dnf update -y +workaround_bfq_bug # Do not enable update-stesting on the previous Fedora release if [[ "$FEDORA_BASE_IMAGE" =~ "${OS_RELEASE_ID}-cloud-base-${OS_RELEASE_VER}" ]]; then - warn "Enabling updates-testing repository for image based on $FEDORA_BASE_IMAGE" - $LILTO ooe.sh sudo dnf install -y 'dnf-command(config-manager)' - $LILTO ooe.sh sudo dnf config-manager --set-enabled updates-testing + DISABLE_UPDATES_TESTING=0 else - warn "NOT enabling updates-testing repository for image based on $PRIOR_FEDORA_BASE_IMAGE" + DISABLE_UPDATES_TESTING=1 fi -REMOVE_PACKAGES=() -INSTALL_PACKAGES=(\ - autoconf - automake - bash-completion - bats - bridge-utils - btrfs-progs-devel - buildah - bzip2 - conmon - container-selinux - containernetworking-plugins - containers-common - criu - device-mapper-devel - dnsmasq - emacs-nox - file - findutils - fuse3 - fuse3-devel - gcc - git - glib2-devel - glibc-static - gnupg - go-md2man - golang - gpgme-devel - iproute - iptables - jq - libassuan-devel - libcap-devel - libmsi1 - libnet - libnet-devel - libnl3-devel - libseccomp - libseccomp-devel - libselinux-devel - libtool - libvarlink-util - lsof - make - msitools - nmap-ncat - ostree-devel - pandoc - podman - procps-ng - protobuf - protobuf-c - protobuf-c-devel - protobuf-devel - python - python3-dateutil - python3-psutil - python3-pytoml - rsync - selinux-policy-devel - skopeo - skopeo-containers - slirp4netns - unzip - vim - wget - which - xz - zip -) - -case "$OS_RELEASE_VER" in - 30) - INSTALL_PACKAGES+=(\ - atomic-registries - golang-github-cpuguy83-go-md2man - python2-future - runc - ) - REMOVE_PACKAGES+=(crun) - ;; - 31) - INSTALL_PACKAGES+=(crun) - REMOVE_PACKAGES+=(runc) - ;; - 32) - INSTALL_PACKAGES+=(crun) - REMOVE_PACKAGES+=(runc) - ;; - *) - bad_os_id_ver ;; -esac - -echo "Installing general build/test dependencies for Fedora '$OS_RELEASE_VER'" -$BIGTO ooe.sh sudo dnf install -y ${INSTALL_PACKAGES[@]} - -[[ "${#REMOVE_PACKAGES[@]}" -eq "0" ]] || \ - $LILTO ooe.sh sudo dnf erase -y ${REMOVE_PACKAGES[@]} +bash $PACKER_BASE/fedora_packaging.sh +# Load installed environment right now (happens automatically in a new process) +source /usr/share/automation/environment echo "Enabling cgroup management from containers" ooe.sh sudo setsebool container_manage_cgroup true -ooe.sh sudo /tmp/libpod/hack/install_catatonit.sh - # Ensure there are no disruptive periodic services enabled by default in image systemd_banish diff --git a/contrib/cirrus/packer/libpod_base_images.yml b/contrib/cirrus/packer/libpod_base_images.yml index a66fac31c..f53bfafc5 100644 --- a/contrib/cirrus/packer/libpod_base_images.yml +++ b/contrib/cirrus/packer/libpod_base_images.yml @@ -17,9 +17,9 @@ variables: PRIOR_UBUNTU_BASE_IMAGE: # Latest Fedora release - FEDORA_IMAGE_URL: "https://dl.fedoraproject.org/pub/fedora/linux/development/32/Cloud/x86_64/images/Fedora-Cloud-Base-32-20200406.n.0.x86_64.qcow2" - FEDORA_CSUM_URL: "https://dl.fedoraproject.org/pub/fedora/linux/development/32/Cloud/x86_64/images/Fedora-Cloud-32-x86_64-20200406.n.0-CHECKSUM" - FEDORA_BASE_IMAGE_NAME: 'fedora-cloud-base-32-n-0' + FEDORA_IMAGE_URL: "https://dl.fedoraproject.org/pub/fedora/linux/releases/32/Cloud/x86_64/images/Fedora-Cloud-Base-32-1.6.x86_64.qcow2" + FEDORA_CSUM_URL: "https://dl.fedoraproject.org/pub/fedora/linux/releases/32/Cloud/x86_64/images/Fedora-Cloud-32-1.6-x86_64-CHECKSUM" + FEDORA_BASE_IMAGE_NAME: 'fedora-cloud-base-32-1-6' # Prior Fedora release PRIOR_FEDORA_IMAGE_URL: "https://dl.fedoraproject.org/pub/fedora/linux/releases/31/Cloud/x86_64/images/Fedora-Cloud-Base-31-1.9.x86_64.qcow2" diff --git a/contrib/cirrus/packer/libpod_images.yml b/contrib/cirrus/packer/libpod_images.yml index c23439201..e33ad775e 100644 --- a/contrib/cirrus/packer/libpod_images.yml +++ b/contrib/cirrus/packer/libpod_images.yml @@ -71,6 +71,7 @@ provisioners: environment_vars: - 'PACKER_BUILDER_NAME={{build_name}}' - 'GOSRC=/tmp/libpod' + - 'PACKER_BASE={{user `PACKER_BASE`}}' - 'SCRIPT_BASE={{user `SCRIPT_BASE`}}' post-processors: diff --git a/contrib/cirrus/packer/ubuntu_packaging.sh b/contrib/cirrus/packer/ubuntu_packaging.sh new file mode 100644 index 000000000..b57bc95e9 --- /dev/null +++ b/contrib/cirrus/packer/ubuntu_packaging.sh @@ -0,0 +1,168 @@ +#!/bin/bash + +# This script is called from ubuntu_setup.sh and various Dockerfiles. +# It's not intended to be used outside of those contexts. It assumes the lib.sh +# library has already been sourced, and that all "ground-up" package-related activity +# needs to be done, including repository setup and initial update. + +set -e + +echo "Updating/Installing repos and packages for $OS_REL_VER" + +source $GOSRC/$SCRIPT_BASE/lib.sh + +echo "Updating/configuring package repositories." +$BIGTO $SUDOAPTGET update + +echo "Installing deps to add third-party repositories and automation tooling" +$LILTO $SUDOAPTGET install software-properties-common git curl + +# Install common automation tooling (i.e. ooe.sh) +curl --silent --show-error --location \ + --url "https://raw.githubusercontent.com/containers/automation/master/bin/install_automation.sh" | \ + $SUDO env INSTALL_PREFIX=/usr/share /bin/bash -s - "$INSTALL_AUTOMATION_VERSION" +# Reload installed environment right now (happens automatically in a new process) +source /usr/share/automation/environment + +$LILTO ooe.sh $SUDOAPTADD ppa:criu/ppa + +# Install newer version of golang +if [[ "$OS_RELEASE_VER" -eq "18" ]] +then + $LILTO ooe.sh $SUDOAPTADD ppa:longsleep/golang-backports +fi + +echo "Configuring/Instaling deps from Open build server" +VERSION_ID=$(source /etc/os-release; echo $VERSION_ID) +echo "deb http://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/stable/xUbuntu_$VERSION_ID/ /" \ + | ooe.sh $SUDO tee /etc/apt/sources.list.d/devel:kubic:libcontainers:stable.list +ooe.sh curl -L -o /tmp/Release.key "https://download.opensuse.org/repositories/devel:kubic:libcontainers:stable/xUbuntu_${VERSION_ID}/Release.key" +ooe.sh $SUDO apt-key add - < /tmp/Release.key + +INSTALL_PACKAGES=(\ + apparmor + aufs-tools + autoconf + automake + bash-completion + bison + build-essential + buildah + bzip2 + conmon + containernetworking-plugins + containers-common + coreutils + cri-o-runc + criu + curl + dnsmasq + e2fslibs-dev + emacs-nox + file + gawk + gcc + gettext + git + go-md2man + golang + iproute2 + iptables + jq + libaio-dev + libapparmor-dev + libcap-dev + libdevmapper-dev + libdevmapper1.02.1 + libfuse-dev + libfuse2 + libglib2.0-dev + libgpgme11-dev + liblzma-dev + libnet1 + libnet1-dev + libnl-3-dev + libprotobuf-c-dev + libprotobuf-dev + libseccomp-dev + libseccomp2 + libselinux-dev + libsystemd-dev + libtool + libudev-dev + libvarlink + lsof + make + netcat + openssl + pkg-config + podman + protobuf-c-compiler + protobuf-compiler + python-future + python-minimal + python-protobuf + python3-dateutil + python3-pip + python3-psutil + python3-pytoml + python3-setuptools + rsync + runc + scons + skopeo + slirp4netns + socat + sudo + unzip + vim + wget + xz-utils + yum-utils + zip + zlib1g-dev +) + +if [[ $OS_RELEASE_VER -ge 19 ]] +then + INSTALL_PACKAGES+=(\ + bats + btrfs-progs + fuse3 + libbtrfs-dev + libfuse3-dev + ) +else + echo "Downloading version of bats with fix for a \$IFS related bug in 'run' command" + cd /tmp + BATS_URL='http://launchpadlibrarian.net/438140887/bats_1.1.0+git104-g1c83a1b-1_all.deb' + curl -L -O "$BATS_URL" + cd - + INSTALL_PACKAGES+=(\ + /tmp/$(basename $BATS_URL) + btrfs-tools + ) +fi + +# Do this at the last possible moment to avoid dpkg lock conflicts +echo "Upgrading all packages" +$BIGTO ooe.sh $SUDOAPTGET upgrade + +echo "Installing general testing and system dependencies" +# Necessary to update cache of newly added repos +$LILTO ooe.sh $SUDOAPTGET update +$BIGTO ooe.sh $SUDOAPTGET install ${INSTALL_PACKAGES[@]} + +export GOPATH="$(mktemp -d)" +trap "$SUDO rm -rf $GOPATH" EXIT +echo "Installing cataonit and libseccomp.sudo" +cd $GOSRC +ooe.sh $SUDO hack/install_catatonit.sh +ooe.sh $SUDO make install.libseccomp.sudo + +CRIO_RUNC_PATH="/usr/lib/cri-o-runc/sbin/runc" +if $SUDO dpkg -L cri-o-runc | grep -m 1 -q "$CRIO_RUNC_PATH" +then + echo "Linking $CRIO_RUNC_PATH to /usr/bin/runc for ease of testing." + $SUDO ln -f "$CRIO_RUNC_PATH" "/usr/bin/runc" +fi diff --git a/contrib/cirrus/packer/ubuntu_setup.sh b/contrib/cirrus/packer/ubuntu_setup.sh index 4b6e99358..2febbd265 100644 --- a/contrib/cirrus/packer/ubuntu_setup.sh +++ b/contrib/cirrus/packer/ubuntu_setup.sh @@ -8,164 +8,21 @@ set -e # Load in library (copied by packer, before this script was run) source $GOSRC/$SCRIPT_BASE/lib.sh -req_env_var SCRIPT_BASE +req_env_var SCRIPT_BASE PACKER_BASE INSTALL_AUTOMATION_VERSION PACKER_BUILDER_NAME GOSRC UBUNTU_BASE_IMAGE OS_RELEASE_ID OS_RELEASE_VER -install_ooe - -export GOPATH="$(mktemp -d)" -trap "sudo rm -rf $GOPATH" EXIT +# Ensure there are no disruptive periodic services enabled by default in image +systemd_banish # Stop disruption upon boot ASAP after booting echo "Disabling all packaging activity on boot" -# Don't let sed process sed's temporary files -_FILEPATHS=$(sudo ls -1 /etc/apt/apt.conf.d) -for filename in $_FILEPATHS; do \ +for filename in $(sudo ls -1 /etc/apt/apt.conf.d); do \ echo "Checking/Patching $filename" sudo sed -i -r -e "s/$PERIODIC_APT_RE/"'\10"\;/' "/etc/apt/apt.conf.d/$filename"; done -echo "Updating/configuring package repositories." -$BIGTO $SUDOAPTGET update - -echo "Upgrading all packages" -$BIGTO $SUDOAPTGET upgrade - -echo "Adding third-party repositories and PPAs" -$LILTO $SUDOAPTGET install software-properties-common -$LILTO $SUDOAPTADD ppa:criu/ppa -if [[ "$OS_RELEASE_VER" -eq "18" ]] -then - $LILTO $SUDOAPTADD ppa:longsleep/golang-backports -fi - -echo "Configuring/Instaling deps from Open build server" -VERSION_ID=$(source /etc/os-release; echo $VERSION_ID) -echo "deb http://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/stable/xUbuntu_$VERSION_ID/ /" \ - | ooe.sh sudo tee /etc/apt/sources.list.d/devel:kubic:libcontainers:stable.list -ooe.sh curl -L -o /tmp/Release.key "https://download.opensuse.org/repositories/devel:kubic:libcontainers:stable/xUbuntu_${VERSION_ID}/Release.key" -ooe.sh sudo apt-key add - < /tmp/Release.key - -INSTALL_PACKAGES=(\ - apparmor - aufs-tools - autoconf - automake - bash-completion - bison - build-essential - buildah - bzip2 - conmon - containernetworking-plugins - containers-common - coreutils - cri-o-runc - criu - curl - dnsmasq - e2fslibs-dev - emacs-nox - file - gawk - gcc - gettext - git - go-md2man - golang - iproute2 - iptables - jq - libaio-dev - libapparmor-dev - libcap-dev - libdevmapper-dev - libdevmapper1.02.1 - libfuse-dev - libfuse2 - libglib2.0-dev - libgpgme11-dev - liblzma-dev - libnet1 - libnet1-dev - libnl-3-dev - libprotobuf-c-dev - libprotobuf-dev - libseccomp-dev - libseccomp2 - libselinux-dev - libsystemd-dev - libtool - libudev-dev - libvarlink - lsof - make - netcat - openssl - pkg-config - podman - protobuf-c-compiler - protobuf-compiler - python-future - python-minimal - python-protobuf - python3-dateutil - python3-pip - python3-psutil - python3-pytoml - python3-setuptools - rsync - runc - scons - skopeo - slirp4netns - socat - unzip - vim - wget - xz-utils - yum-utils - zip - zlib1g-dev -) - -if [[ "$OS_RELEASE_VER" -ge "19" ]] -then - INSTALL_PACKAGES+=(\ - bats - btrfs-progs - fuse3 - libbtrfs-dev - libfuse3-dev - ) -else - echo "Downloading version of bats with fix for a \$IFS related bug in 'run' command" - cd /tmp - BATS_URL='http://launchpadlibrarian.net/438140887/bats_1.1.0+git104-g1c83a1b-1_all.deb' - curl -L -O "$BATS_URL" - cd - - INSTALL_PACKAGES+=(\ - /tmp/$(basename $BATS_URL) - btrfs-tools - ) -fi - -echo "Installing general testing and system dependencies" -# Necessary to update cache of newly added repos -$LILTO $SUDOAPTGET update -$BIGTO $SUDOAPTGET install ${INSTALL_PACKAGES[@]} - -echo "Installing cataonit and libseccomp.sudo" -ooe.sh sudo /tmp/libpod/hack/install_catatonit.sh -ooe.sh sudo make -C /tmp/libpod install.libseccomp.sudo - -# Ensure there are no disruptive periodic services enabled by default in image -systemd_banish +bash $PACKER_BASE/ubuntu_packaging.sh -CRIO_RUNC_PATH="/usr/lib/cri-o-runc/sbin/runc" -if sudo dpkg -L cri-o-runc | grep -m 1 -q "$CRIO_RUNC_PATH" -then - echo "Linking $CRIO_RUNC_PATH to /usr/bin/runc for ease of testing." - sudo ln -f "$CRIO_RUNC_PATH" "/usr/bin/runc" -fi +# Load installed environment right now (happens automatically in a new process) +source /usr/share/automation/environment echo "Making Ubuntu kernel to enable cgroup swap accounting as it is not the default." SEDCMD='s/^GRUB_CMDLINE_LINUX="(.*)"/GRUB_CMDLINE_LINUX="\1 cgroup_enable=memory swapaccount=1"/g' diff --git a/contrib/cirrus/setup_environment.sh b/contrib/cirrus/setup_environment.sh index 57c9ec52a..6bec9625e 100755 --- a/contrib/cirrus/setup_environment.sh +++ b/contrib/cirrus/setup_environment.sh @@ -43,16 +43,8 @@ case "${OS_RELEASE_ID}" in fedora) # All SELinux distros need this for systemd-in-a-container setsebool container_manage_cgroup true - if [[ "$ADD_SECOND_PARTITION" == "true" ]]; then - bash "$SCRIPT_BASE/add_second_partition.sh" - fi - if [[ $OS_RELEASE_VER -le 31 ]]; then - warn "Switching io scheduler to 'deadline' to avoid RHBZ 1767539" - warn "aka https://bugzilla.kernel.org/show_bug.cgi?id=205447" - echo "mq-deadline" > /sys/block/sda/queue/scheduler - cat /sys/block/sda/queue/scheduler - fi + workaround_bfq_bug if [[ "$ADD_SECOND_PARTITION" == "true" ]]; then bash "$SCRIPT_BASE/add_second_partition.sh" diff --git a/contrib/podmanimage/stable/Dockerfile b/contrib/podmanimage/stable/Dockerfile index 7aeb5bbdc..912d16e1c 100644 --- a/contrib/podmanimage/stable/Dockerfile +++ b/contrib/podmanimage/stable/Dockerfile @@ -13,10 +13,10 @@ FROM fedora:latest # up space. RUN useradd podman; yum -y update; yum -y reinstall shadow-utils; yum -y install podman fuse-overlayfs --exclude container-selinux; rm -rf /var/cache /var/log/dnf* /var/log/yum.* -# Adjust storage.conf to enable Fuse storage. -RUN sed -i -e 's|^#mount_program|mount_program|g' -e '/additionalimage.*/a "/var/lib/shared",' /etc/containers/storage.conf -RUN mkdir -p /var/lib/shared/overlay-images /var/lib/shared/overlay-layers; touch /var/lib/shared/overlay-images/images.lock; touch /var/lib/shared/overlay-layers/layers.lock - ADD https://raw.githubusercontent.com/containers/libpod/master/contrib/podmanimage/stable/containers.conf /etc/containers/ +# chmod containers.conf and adjust storage.conf to enable Fuse storage. +RUN chmod 644 /etc/containers/containers.conf; sed -i -e 's|^#mount_program|mount_program|g' -e '/additionalimage.*/a "/var/lib/shared",' /etc/containers/storage.conf +RUN mkdir -p /var/lib/shared/overlay-images /var/lib/shared/overlay-layers; touch /var/lib/shared/overlay-images/images.lock; touch /var/lib/shared/overlay-layers/layers.lock + ENV _CONTAINERS_USERNS_CONFIGURED="" diff --git a/contrib/podmanimage/stable/manual/Containerfile b/contrib/podmanimage/stable/manual/Containerfile index afc4f5ffd..4375ea4f4 100644 --- a/contrib/podmanimage/stable/manual/Containerfile +++ b/contrib/podmanimage/stable/manual/Containerfile @@ -26,10 +26,11 @@ FROM fedora:latest COPY /tmp/podman-1.7.0-3.fc30.x86_64.rpm /tmp RUN yum -y install /tmp/podman-1.7.0-3.fc30.x86_64.rpm fuse-overlayfs --exclude container-selinux; rm -rf /var/cache /var/log/dnf* /var/log/yum.* /tmp/podman*.rpm -# Adjust storage.conf to enable Fuse storage. -RUN sed -i -e 's|^#mount_program|mount_program|g' -e '/additionalimage.*/a "/var/lib/shared",' /etc/containers/storage.conf +ADD https://raw.githubusercontent.com/containers/libpod/master/contrib/podmanimage/stable/containers.conf /etc/containers/ + +# chmod containers.conf and adjust storage.conf to enable Fuse storage. +RUN chmod 644 /etc/containers/containers.conf; sed -i -e 's|^#mount_program|mount_program|g' -e '/additionalimage.*/a "/var/lib/shared",' /etc/containers/storage.conf RUN mkdir -p /var/lib/shared/overlay-images /var/lib/shared/overlay-layers; touch /var/lib/shared/overlay-images/images.lock; touch /var/lib/shared/overlay-layers/layers.lock -ADD https://raw.githubusercontent.com/containers/libpod/master/contrib/podmanimage/stable/containers.conf /etc/containers/ ENV _CONTAINERS_USERNS_CONFIGURED="" diff --git a/contrib/podmanimage/testing/Dockerfile b/contrib/podmanimage/testing/Dockerfile index 3a7a0b7f8..31265a0ea 100644 --- a/contrib/podmanimage/testing/Dockerfile +++ b/contrib/podmanimage/testing/Dockerfile @@ -15,10 +15,10 @@ FROM fedora:latest # up space. RUN useradd podman; yum -y update; yum -y reinstall shadow-utils; yum -y install podman fuse-overlayfs --exclude container-selinux --enablerepo updates-testing; rm -rf /var/cache /var/log/dnf* /var/log/yum.* -# Adjust storage.conf to enable Fuse storage. -RUN sed -i -e 's|^#mount_program|mount_program|g' -e '/additionalimage.*/a "/var/lib/shared",' /etc/containers/storage.conf -RUN mkdir -p /var/lib/shared/overlay-images /var/lib/shared/overlay-layers; touch /var/lib/shared/overlay-images/images.lock; touch /var/lib/shared/overlay-layers/layers.lock - ADD https://raw.githubusercontent.com/containers/libpod/master/contrib/podmanimage/stable/containers.conf /etc/containers/ +# chmod containers.conf and adjust storage.conf to enable Fuse storage. +RUN chmod 644 /etc/containers/containers.conf; sed -i -e 's|^#mount_program|mount_program|g' -e '/additionalimage.*/a "/var/lib/shared",' /etc/containers/storage.conf +RUN mkdir -p /var/lib/shared/overlay-images /var/lib/shared/overlay-layers; touch /var/lib/shared/overlay-images/images.lock; touch /var/lib/shared/overlay-layers/layers.lock + ENV _CONTAINERS_USERNS_CONFIGURED="" diff --git a/contrib/podmanimage/upstream/Dockerfile b/contrib/podmanimage/upstream/Dockerfile index 3b2f49094..541670aa2 100644 --- a/contrib/podmanimage/upstream/Dockerfile +++ b/contrib/podmanimage/upstream/Dockerfile @@ -63,10 +63,10 @@ RUN useradd podman; yum -y update; yum -y reinstall shadow-utils; yum -y install yum -y remove git golang go-md2man make; \ yum clean all; -# Adjust storage.conf to enable Fuse storage. -RUN sed -i -e 's|^#mount_program|mount_program|g' -e '/additionalimage.*/a "/var/lib/shared",' /etc/containers/storage.conf -RUN mkdir -p /var/lib/shared/overlay-images /var/lib/shared/overlay-layers; touch /var/lib/shared/overlay-images/images.lock; touch /var/lib/shared/overlay-layers/layers.lock - ADD https://raw.githubusercontent.com/containers/libpod/master/contrib/podmanimage/stable/containers.conf /etc/containers/ +# chmod containers.conf and adjust storage.conf to enable Fuse storage. +RUN chmod 644 /etc/containers/containers.conf; sed -i -e 's|^#mount_program|mount_program|g' -e '/additionalimage.*/a "/var/lib/shared",' /etc/containers/storage.conf +RUN mkdir -p /var/lib/shared/overlay-images /var/lib/shared/overlay-layers; touch /var/lib/shared/overlay-images/images.lock; touch /var/lib/shared/overlay-layers/layers.lock + ENV _CONTAINERS_USERNS_CONFIGURED="" diff --git a/contrib/spec/podman.spec.in b/contrib/spec/podman.spec.in index afc50f854..1dfbdf208 100644 --- a/contrib/spec/podman.spec.in +++ b/contrib/spec/podman.spec.in @@ -377,12 +377,6 @@ Man pages for the %{name} commands # untar conmon tar zxf %{SOURCE1} -sed -i 's/install.remote: podman-remote/install.remote:/' Makefile -sed -i 's/install.bin: podman/install.bin:/' Makefile -%if %{with doc} -sed -i 's/install.man: docs/install.man:/' Makefile -%endif - %build mkdir _build pushd _build @@ -417,22 +411,15 @@ popd %install install -dp %{buildroot}%{_unitdir} install -dp %{buildroot}%{_usr}/lib/systemd/user -%if %{with doc} -PODMAN_VERSION=%{version} %{__make} PREFIX=%{buildroot}%{_prefix} ETCDIR=%{buildroot}%{_sysconfdir} \ - install.bin \ - install.remote \ - install.man \ - install.cni \ - install.systemd \ - install.completions -%else PODMAN_VERSION=%{version} %{__make} PREFIX=%{buildroot}%{_prefix} ETCDIR=%{buildroot}%{_sysconfdir} \ - install.bin \ - install.remote \ + install.bin-nobuild \ + install.remote-nobuild \ +%if %{with doc} + install.man-nobuild \ +%endif install.cni \ install.systemd \ install.completions -%endif mv pkg/hooks/README.md pkg/hooks/README-hooks.md @@ -13,7 +13,7 @@ require ( github.com/containers/common v0.9.5 github.com/containers/conmon v2.0.14+incompatible github.com/containers/image/v5 v5.4.3 - github.com/containers/psgo v1.4.0 + github.com/containers/psgo v1.5.0 github.com/containers/storage v1.18.2 github.com/coreos/go-systemd/v22 v22.0.0 github.com/cri-o/ocicni v0.1.1-0.20190920040751-deac903fd99b @@ -76,8 +76,8 @@ github.com/containers/libtrust v0.0.0-20190913040956-14b96171aa3b h1:Q8ePgVfHDpl github.com/containers/libtrust v0.0.0-20190913040956-14b96171aa3b/go.mod h1:9rfv8iPl1ZP7aqh9YA68wnZv2NUDbXdcdPHVz0pFbPY= github.com/containers/ocicrypt v1.0.2 h1:Q0/IPs8ohfbXNxEfyJ2pFVmvJu5BhqJUAmc6ES9NKbo= github.com/containers/ocicrypt v1.0.2/go.mod h1:nsOhbP19flrX6rE7ieGFvBlr7modwmNjsqWarIUce4M= -github.com/containers/psgo v1.4.0 h1:D8B4fZCCZhYgc8hDyMPCiShOinmOB1TP1qe46sSC19k= -github.com/containers/psgo v1.4.0/go.mod h1:ENXXLQ5E1At4K0EUsGogXBJi/C28gwqkONWeLPI9fJ8= +github.com/containers/psgo v1.5.0 h1:uofUREsrm0Ls5K4tkEIFPqWSHKyg3Bvoqo/Q2eDmj8g= +github.com/containers/psgo v1.5.0/go.mod h1:2ubh0SsreMZjSXW1Hif58JrEcFudQyIy9EzPUWfawVU= github.com/containers/storage v1.18.2 h1:4cgFbrrgr9nR9xCeOmfpyxk1MtXYZGr7XGPJfAVkGmc= github.com/containers/storage v1.18.2/go.mod h1:WTBMf+a9ZZ/LbmEVeLHH2TX4CikWbO1Bt+/m58ZHVPg= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= diff --git a/libpod/image/image.go b/libpod/image/image.go index bbf803056..60787b826 100644 --- a/libpod/image/image.go +++ b/libpod/image/image.go @@ -1487,14 +1487,14 @@ func (i *Image) Save(ctx context.Context, source, format, output string, moreTag } manifestType = manifest.DockerV2Schema2MediaType case "docker-archive", "": - dst := output destImageName := imageNameForSaveDestination(i, source) - if destImageName != "" { - dst = fmt.Sprintf("%s:%s", dst, destImageName) + ref, err := dockerArchiveDstReference(destImageName) + if err != nil { + return err } - destRef, err = dockerarchive.ParseReference(dst) // FIXME? Add dockerarchive.NewReference + destRef, err = dockerarchive.NewReference(output, ref) if err != nil { - return errors.Wrapf(err, "error getting Docker archive ImageReference for %q", dst) + return errors.Wrapf(err, "error getting Docker archive ImageReference for %s:%v", output, ref) } default: return errors.Errorf("unknown format option %q", format) @@ -1514,6 +1514,23 @@ func (i *Image) Save(ctx context.Context, source, format, output string, moreTag return nil } +// dockerArchiveDestReference returns a NamedTagged reference for a tagged image and nil for untagged image. +func dockerArchiveDstReference(normalizedInput string) (reference.NamedTagged, error) { + if normalizedInput == "" { + return nil, nil + } + ref, err := reference.ParseNormalizedNamed(normalizedInput) + if err != nil { + return nil, errors.Wrapf(err, "docker-archive parsing reference %s", normalizedInput) + } + ref = reference.TagNameOnly(ref) + namedTagged, isTagged := ref.(reference.NamedTagged) + if !isTagged { + namedTagged = nil + } + return namedTagged, nil +} + // GetConfigBlob returns a schema2image. If the image is not a schema2, then // it will return an error func (i *Image) GetConfigBlob(ctx context.Context) (*manifest.Schema2Image, error) { diff --git a/libpod/options.go b/libpod/options.go index b4e436b63..33b423bce 100644 --- a/libpod/options.go +++ b/libpod/options.go @@ -1400,8 +1400,13 @@ func WithVolumeDriver(driver string) VolumeCreateOption { if volume.valid { return define.ErrVolumeFinalized } + // only local driver is possible rn + if driver != define.VolumeDriverLocal { + return define.ErrNotImplemented - return define.ErrNotImplemented + } + volume.config.Driver = define.VolumeDriverLocal + return nil } } diff --git a/libpod/runtime_ctr.go b/libpod/runtime_ctr.go index 3dc8d3d0f..1d880531e 100644 --- a/libpod/runtime_ctr.go +++ b/libpod/runtime_ctr.go @@ -869,7 +869,8 @@ func (r *Runtime) PruneContainers(filterFuncs []ContainerFilter) (map[string]int logrus.Error(err) return false } - if state == define.ContainerStateStopped || state == define.ContainerStateExited { + if state == define.ContainerStateStopped || state == define.ContainerStateExited || + state == define.ContainerStateCreated || state == define.ContainerStateConfigured { return true } return false diff --git a/pkg/api/handlers/compat/containers_prune.go b/pkg/api/handlers/compat/containers_prune.go index b4e98ac1f..9d77f612b 100644 --- a/pkg/api/handlers/compat/containers_prune.go +++ b/pkg/api/handlers/compat/containers_prune.go @@ -38,21 +38,24 @@ func PruneContainers(w http.ResponseWriter, r *http.Request) { filterFuncs = append(filterFuncs, generatedFunc) } } - prunedContainers, pruneErrors, err := runtime.PruneContainers(filterFuncs) - if err != nil { - utils.InternalServerError(w, err) - return - } // Libpod response differs if utils.IsLibpodRequest(r) { - report := &entities.ContainerPruneReport{ - Err: pruneErrors, - ID: prunedContainers, + report, err := PruneContainersHelper(w, r, filterFuncs) + if err != nil { + utils.InternalServerError(w, err) + return } + utils.WriteResponse(w, http.StatusOK, report) return } + + prunedContainers, pruneErrors, err := runtime.PruneContainers(filterFuncs) + if err != nil { + utils.InternalServerError(w, err) + return + } for ctrID, size := range prunedContainers { if pruneErrors[ctrID] == nil { space += size @@ -65,3 +68,19 @@ func PruneContainers(w http.ResponseWriter, r *http.Request) { } utils.WriteResponse(w, http.StatusOK, report) } + +func PruneContainersHelper(w http.ResponseWriter, r *http.Request, filterFuncs []libpod.ContainerFilter) ( + *entities.ContainerPruneReport, error) { + runtime := r.Context().Value("runtime").(*libpod.Runtime) + prunedContainers, pruneErrors, err := runtime.PruneContainers(filterFuncs) + if err != nil { + utils.InternalServerError(w, err) + return nil, err + } + + report := &entities.ContainerPruneReport{ + Err: pruneErrors, + ID: prunedContainers, + } + return report, nil +} diff --git a/pkg/api/handlers/libpod/pods.go b/pkg/api/handlers/libpod/pods.go index 0b15ab0d6..c3f8d5d66 100644 --- a/pkg/api/handlers/libpod/pods.go +++ b/pkg/api/handlers/libpod/pods.go @@ -231,14 +231,22 @@ func PodRestart(w http.ResponseWriter, r *http.Request) { } func PodPrune(w http.ResponseWriter, r *http.Request) { + reports, err := PodPruneHelper(w, r) + if err != nil { + utils.InternalServerError(w, err) + return + } + utils.WriteResponse(w, http.StatusOK, reports) +} + +func PodPruneHelper(w http.ResponseWriter, r *http.Request) ([]*entities.PodPruneReport, error) { var ( runtime = r.Context().Value("runtime").(*libpod.Runtime) reports []*entities.PodPruneReport ) responses, err := runtime.PrunePods(r.Context()) if err != nil { - utils.InternalServerError(w, err) - return + return nil, err } for k, v := range responses { reports = append(reports, &entities.PodPruneReport{ @@ -246,7 +254,7 @@ func PodPrune(w http.ResponseWriter, r *http.Request) { Id: k, }) } - utils.WriteResponse(w, http.StatusOK, reports) + return reports, nil } func PodPause(w http.ResponseWriter, r *http.Request) { diff --git a/pkg/api/handlers/libpod/system.go b/pkg/api/handlers/libpod/system.go new file mode 100644 index 000000000..98e33bf10 --- /dev/null +++ b/pkg/api/handlers/libpod/system.go @@ -0,0 +1,71 @@ +package libpod + +import ( + "net/http" + + "github.com/containers/libpod/libpod" + "github.com/containers/libpod/pkg/api/handlers/compat" + "github.com/containers/libpod/pkg/api/handlers/utils" + "github.com/containers/libpod/pkg/domain/entities" + "github.com/gorilla/schema" + "github.com/pkg/errors" +) + +// SystemPrune removes unused data +func SystemPrune(w http.ResponseWriter, r *http.Request) { + var ( + decoder = r.Context().Value("decoder").(*schema.Decoder) + runtime = r.Context().Value("runtime").(*libpod.Runtime) + systemPruneReport = new(entities.SystemPruneReport) + ) + query := struct { + All bool `schema:"all"` + Volumes bool `schema:"volumes"` + }{} + + if err := decoder.Decode(&query, r.URL.Query()); err != nil { + utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest, + errors.Wrapf(err, "Failed to parse parameters for %s", r.URL.String())) + return + } + + podPruneReport, err := PodPruneHelper(w, r) + if err != nil { + utils.InternalServerError(w, err) + return + } + systemPruneReport.PodPruneReport = podPruneReport + + // We could parallelize this, should we? + containerPruneReport, err := compat.PruneContainersHelper(w, r, nil) + if err != nil { + utils.InternalServerError(w, err) + return + } + systemPruneReport.ContainerPruneReport = containerPruneReport + + results, err := runtime.ImageRuntime().PruneImages(r.Context(), query.All, nil) + if err != nil { + utils.InternalServerError(w, err) + return + } + + report := entities.ImagePruneReport{ + Report: entities.Report{ + Id: results, + Err: nil, + }, + } + + systemPruneReport.ImagePruneReport = &report + + if query.Volumes { + volumePruneReport, err := pruneVolumesHelper(w, r) + if err != nil { + utils.InternalServerError(w, err) + return + } + systemPruneReport.VolumePruneReport = volumePruneReport + } + utils.WriteResponse(w, http.StatusOK, systemPruneReport) +} diff --git a/pkg/api/handlers/libpod/volumes.go b/pkg/api/handlers/libpod/volumes.go index 18c561a0d..c42ca407b 100644 --- a/pkg/api/handlers/libpod/volumes.go +++ b/pkg/api/handlers/libpod/volumes.go @@ -147,14 +147,22 @@ func ListVolumes(w http.ResponseWriter, r *http.Request) { } func PruneVolumes(w http.ResponseWriter, r *http.Request) { + reports, err := pruneVolumesHelper(w, r) + if err != nil { + utils.InternalServerError(w, err) + return + } + utils.WriteResponse(w, http.StatusOK, reports) +} + +func pruneVolumesHelper(w http.ResponseWriter, r *http.Request) ([]*entities.VolumePruneReport, error) { var ( runtime = r.Context().Value("runtime").(*libpod.Runtime) reports []*entities.VolumePruneReport ) pruned, err := runtime.PruneVolumes(r.Context()) if err != nil { - utils.InternalServerError(w, err) - return + return nil, err } for k, v := range pruned { reports = append(reports, &entities.VolumePruneReport{ @@ -162,9 +170,8 @@ func PruneVolumes(w http.ResponseWriter, r *http.Request) { Id: k, }) } - utils.WriteResponse(w, http.StatusOK, reports) + return reports, nil } - func RemoveVolume(w http.ResponseWriter, r *http.Request) { var ( runtime = r.Context().Value("runtime").(*libpod.Runtime) diff --git a/pkg/api/server/register_system.go b/pkg/api/server/register_system.go index 708ccd39b..7375a75c1 100644 --- a/pkg/api/server/register_system.go +++ b/pkg/api/server/register_system.go @@ -4,6 +4,7 @@ import ( "net/http" "github.com/containers/libpod/pkg/api/handlers/compat" + "github.com/containers/libpod/pkg/api/handlers/libpod" "github.com/gorilla/mux" ) @@ -11,5 +12,21 @@ func (s *APIServer) registerSystemHandlers(r *mux.Router) error { r.Handle(VersionedPath("/system/df"), s.APIHandler(compat.GetDiskUsage)).Methods(http.MethodGet) // Added non version path to URI to support docker non versioned paths r.Handle("/system/df", s.APIHandler(compat.GetDiskUsage)).Methods(http.MethodGet) + // Swagger:operation POST /libpod/system/prune libpod pruneSystem + // --- + // tags: + // - system + // summary: Prune unused data + // produces: + // - application/json + // responses: + // 200: + // $ref: '#/responses/SystemPruneReport' + // 400: + // $ref: "#/responses/BadParamError" + // 500: + // $ref: "#/responses/InternalError" + r.Handle(VersionedPath("/libpod/system/prune"), s.APIHandler(libpod.SystemPrune)).Methods(http.MethodPost) + return nil } diff --git a/pkg/bindings/images/images.go b/pkg/bindings/images/images.go index 06f01c7a0..4d8ae6a6e 100644 --- a/pkg/bindings/images/images.go +++ b/pkg/bindings/images/images.go @@ -57,7 +57,7 @@ func List(ctx context.Context, all *bool, filters map[string][]string) ([]*entit // Get performs an image inspect. To have the on-disk size of the image calculated, you can // use the optional size parameter. -func GetImage(ctx context.Context, nameOrID string, size *bool) (*entities.ImageData, error) { +func GetImage(ctx context.Context, nameOrID string, size *bool) (*entities.ImageInspectReport, error) { conn, err := bindings.GetClient(ctx) if err != nil { return nil, err @@ -66,7 +66,7 @@ func GetImage(ctx context.Context, nameOrID string, size *bool) (*entities.Image if size != nil { params.Set("size", strconv.FormatBool(*size)) } - inspectedData := entities.ImageData{} + inspectedData := entities.ImageInspectReport{} response, err := conn.DoRequest(nil, http.MethodGet, "/images/%s/json", params, nameOrID) if err != nil { return &inspectedData, err @@ -273,9 +273,10 @@ func Pull(ctx context.Context, rawImage string, options entities.ImagePullOption params.Set("credentials", options.Credentials) params.Set("overrideArch", options.OverrideArch) params.Set("overrideOS", options.OverrideOS) - if options.TLSVerify != types.OptionalBoolUndefined { - val := bool(options.TLSVerify == types.OptionalBoolTrue) - params.Set("tlsVerify", strconv.FormatBool(val)) + if options.SkipTLSVerify != types.OptionalBoolUndefined { + // Note: we have to verify if skipped is false. + verifyTLS := bool(options.SkipTLSVerify == types.OptionalBoolFalse) + params.Set("tlsVerify", strconv.FormatBool(verifyTLS)) } params.Set("allTags", strconv.FormatBool(options.AllTags)) @@ -310,9 +311,10 @@ func Push(ctx context.Context, source string, destination string, options entiti params := url.Values{} params.Set("credentials", options.Credentials) params.Set("destination", destination) - if options.TLSVerify != types.OptionalBoolUndefined { - val := bool(options.TLSVerify == types.OptionalBoolTrue) - params.Set("tlsVerify", strconv.FormatBool(val)) + if options.SkipTLSVerify != types.OptionalBoolUndefined { + // Note: we have to verify if skipped is false. + verifyTLS := bool(options.SkipTLSVerify == types.OptionalBoolFalse) + params.Set("tlsVerify", strconv.FormatBool(verifyTLS)) } path := fmt.Sprintf("/images/%s/push", source) @@ -333,9 +335,10 @@ func Search(ctx context.Context, term string, opts entities.ImageSearchOptions) params.Set("filters", f) } - if opts.TLSVerify != types.OptionalBoolUndefined { - val := bool(opts.TLSVerify == types.OptionalBoolTrue) - params.Set("tlsVerify", strconv.FormatBool(val)) + if opts.SkipTLSVerify != types.OptionalBoolUndefined { + // Note: we have to verify if skipped is false. + verifyTLS := bool(opts.SkipTLSVerify == types.OptionalBoolFalse) + params.Set("tlsVerify", strconv.FormatBool(verifyTLS)) } response, err := conn.DoRequest(nil, http.MethodGet, "/images/search", params) diff --git a/pkg/bindings/system/system.go b/pkg/bindings/system/system.go index e2f264139..df6b529de 100644 --- a/pkg/bindings/system/system.go +++ b/pkg/bindings/system/system.go @@ -6,6 +6,7 @@ import ( "io" "net/http" "net/url" + "strconv" "github.com/containers/libpod/pkg/bindings" "github.com/containers/libpod/pkg/domain/entities" @@ -59,3 +60,26 @@ func Events(ctx context.Context, eventChan chan (entities.Event), cancelChan cha } return nil } + +// Prune removes all unused system data. +func Prune(ctx context.Context, all, volumes *bool) (*entities.SystemPruneReport, error) { + var ( + report entities.SystemPruneReport + ) + conn, err := bindings.GetClient(ctx) + if err != nil { + return nil, err + } + params := url.Values{} + if all != nil { + params.Set("All", strconv.FormatBool(*all)) + } + if volumes != nil { + params.Set("Volumes", strconv.FormatBool(*volumes)) + } + response, err := conn.DoRequest(nil, http.MethodPost, "/system/prune", params) + if err != nil { + return nil, err + } + return &report, response.Process(&report) +} diff --git a/pkg/bindings/test/system_test.go b/pkg/bindings/test/system_test.go index 3abc26b34..87e6d56dc 100644 --- a/pkg/bindings/test/system_test.go +++ b/pkg/bindings/test/system_test.go @@ -4,7 +4,12 @@ import ( "time" "github.com/containers/libpod/pkg/api/handlers" + "github.com/containers/libpod/pkg/bindings" + "github.com/containers/libpod/pkg/bindings/containers" + "github.com/containers/libpod/pkg/bindings/pods" "github.com/containers/libpod/pkg/bindings/system" + "github.com/containers/libpod/pkg/bindings/volumes" + "github.com/containers/libpod/pkg/domain/entities" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" "github.com/onsi/gomega/gexec" @@ -12,13 +17,16 @@ import ( var _ = Describe("Podman system", func() { var ( - bt *bindingTest - s *gexec.Session + bt *bindingTest + s *gexec.Session + newpod string ) BeforeEach(func() { bt = newBindingTest() bt.RestoreImagesFromCache() + newpod = "newpod" + bt.Podcreate(&newpod) s = bt.startAPIService() time.Sleep(1 * time.Second) err := bt.NewConnection() @@ -48,4 +56,98 @@ var _ = Describe("Podman system", func() { cancelChan <- true Expect(len(messages)).To(BeNumerically("==", 3)) }) + + It("podman system prune - pod,container stopped", func() { + // Start and stop a pod to enter in exited state. + _, err := pods.Start(bt.conn, newpod) + Expect(err).To(BeNil()) + _, err = pods.Stop(bt.conn, newpod, nil) + Expect(err).To(BeNil()) + // Start and stop a container to enter in exited state. + var name = "top" + _, err = bt.RunTopContainer(&name, &bindings.PFalse, nil) + Expect(err).To(BeNil()) + err = containers.Stop(bt.conn, name, nil) + Expect(err).To(BeNil()) + + systemPruneResponse, err := system.Prune(bt.conn, &bindings.PTrue, &bindings.PFalse) + Expect(err).To(BeNil()) + Expect(len(systemPruneResponse.PodPruneReport)).To(Equal(1)) + Expect(len(systemPruneResponse.ContainerPruneReport.ID)).To(Equal(1)) + Expect(len(systemPruneResponse.ImagePruneReport.Report.Id)). + To(BeNumerically(">", 0)) + Expect(systemPruneResponse.ImagePruneReport.Report.Id). + To(ContainElement("docker.io/library/alpine:latest")) + Expect(len(systemPruneResponse.VolumePruneReport)).To(Equal(0)) + }) + + It("podman system prune running alpine container", func() { + // Start and stop a pod to enter in exited state. + _, err := pods.Start(bt.conn, newpod) + Expect(err).To(BeNil()) + _, err = pods.Stop(bt.conn, newpod, nil) + Expect(err).To(BeNil()) + + // Start and stop a container to enter in exited state. + var name = "top" + _, err = bt.RunTopContainer(&name, &bindings.PFalse, nil) + Expect(err).To(BeNil()) + err = containers.Stop(bt.conn, name, nil) + Expect(err).To(BeNil()) + + // Start container and leave in running + var name2 = "top2" + _, err = bt.RunTopContainer(&name2, &bindings.PFalse, nil) + Expect(err).To(BeNil()) + + // Adding an unused volume + _, err = volumes.Create(bt.conn, entities.VolumeCreateOptions{}) + Expect(err).To(BeNil()) + + systemPruneResponse, err := system.Prune(bt.conn, &bindings.PTrue, &bindings.PFalse) + Expect(err).To(BeNil()) + Expect(len(systemPruneResponse.PodPruneReport)).To(Equal(1)) + Expect(len(systemPruneResponse.ContainerPruneReport.ID)).To(Equal(1)) + Expect(len(systemPruneResponse.ImagePruneReport.Report.Id)). + To(BeNumerically(">", 0)) + // Alpine image should not be pruned as used by running container + Expect(systemPruneResponse.ImagePruneReport.Report.Id). + ToNot(ContainElement("docker.io/library/alpine:latest")) + // Though unsed volume is available it should not be pruned as flag set to false. + Expect(len(systemPruneResponse.VolumePruneReport)).To(Equal(0)) + }) + + It("podman system prune running alpine container volume prune", func() { + // Start a pod and leave it running + _, err := pods.Start(bt.conn, newpod) + Expect(err).To(BeNil()) + + // Start and stop a container to enter in exited state. + var name = "top" + _, err = bt.RunTopContainer(&name, &bindings.PFalse, nil) + Expect(err).To(BeNil()) + err = containers.Stop(bt.conn, name, nil) + Expect(err).To(BeNil()) + + // Start second container and leave in running + var name2 = "top2" + _, err = bt.RunTopContainer(&name2, &bindings.PFalse, nil) + Expect(err).To(BeNil()) + + // Adding an unused volume should work + _, err = volumes.Create(bt.conn, entities.VolumeCreateOptions{}) + Expect(err).To(BeNil()) + + systemPruneResponse, err := system.Prune(bt.conn, &bindings.PTrue, &bindings.PTrue) + Expect(err).To(BeNil()) + Expect(len(systemPruneResponse.PodPruneReport)).To(Equal(0)) + Expect(len(systemPruneResponse.ContainerPruneReport.ID)).To(Equal(1)) + Expect(len(systemPruneResponse.ImagePruneReport.Report.Id)). + To(BeNumerically(">", 0)) + // Alpine image should not be pruned as used by running container + Expect(systemPruneResponse.ImagePruneReport.Report.Id). + ToNot(ContainElement("docker.io/library/alpine:latest")) + // Volume should be pruned now as flag set true + Expect(len(systemPruneResponse.VolumePruneReport)).To(Equal(1)) + }) }) diff --git a/pkg/domain/entities/containers.go b/pkg/domain/entities/containers.go index e58258b75..622e8eb5b 100644 --- a/pkg/domain/entities/containers.go +++ b/pkg/domain/entities/containers.go @@ -227,6 +227,7 @@ type ContainerStartOptions struct { // containers from the cli type ContainerStartReport struct { Id string + RawInput string Err error ExitCode int } diff --git a/pkg/domain/entities/engine_container.go b/pkg/domain/entities/engine_container.go index 502279bcf..eebf4c033 100644 --- a/pkg/domain/entities/engine_container.go +++ b/pkg/domain/entities/engine_container.go @@ -41,6 +41,7 @@ type ContainerEngine interface { ContainerUnpause(ctx context.Context, namesOrIds []string, options PauseUnPauseOptions) ([]*PauseUnpauseReport, error) ContainerWait(ctx context.Context, namesOrIds []string, options WaitOptions) ([]WaitReport, error) Events(ctx context.Context, opts EventsOptions) error + GenerateSystemd(ctx context.Context, nameOrID string, opts GenerateSystemdOptions) (*GenerateSystemdReport, error) HealthCheckRun(ctx context.Context, nameOrId string, options HealthCheckOptions) (*define.HealthCheckResults, error) Info(ctx context.Context) (*define.Info, error) PodCreate(ctx context.Context, opts PodCreateOptions) (*PodCreateReport, error) diff --git a/pkg/domain/entities/engine_image.go b/pkg/domain/entities/engine_image.go index b118a4104..46a96ca20 100644 --- a/pkg/domain/entities/engine_image.go +++ b/pkg/domain/entities/engine_image.go @@ -13,7 +13,7 @@ type ImageEngine interface { Exists(ctx context.Context, nameOrId string) (*BoolReport, error) History(ctx context.Context, nameOrId string, opts ImageHistoryOptions) (*ImageHistoryReport, error) Import(ctx context.Context, opts ImageImportOptions) (*ImageImportReport, error) - Inspect(ctx context.Context, names []string, opts InspectOptions) (*ImageInspectReport, error) + Inspect(ctx context.Context, namesOrIDs []string, opts InspectOptions) ([]*ImageInspectReport, error) List(ctx context.Context, opts ImageListOptions) ([]*ImageSummary, error) Load(ctx context.Context, opts ImageLoadOptions) (*ImageLoadReport, error) Prune(ctx context.Context, opts ImagePruneOptions) (*ImagePruneReport, error) diff --git a/pkg/domain/entities/generate.go b/pkg/domain/entities/generate.go new file mode 100644 index 000000000..6d65b52f8 --- /dev/null +++ b/pkg/domain/entities/generate.go @@ -0,0 +1,22 @@ +package entities + +// GenerateSystemdOptions control the generation of systemd unit files. +type GenerateSystemdOptions struct { + // Files - generate files instead of printing to stdout. + Files bool + // Name - use container/pod name instead of its ID. + Name bool + // New - create a new container instead of starting a new one. + New bool + // RestartPolicy - systemd restart policy. + RestartPolicy string + // StopTimeout - time when stopping the container. + StopTimeout *uint +} + +// GenerateSystemdReport +type GenerateSystemdReport struct { + // Output of the generate process. Either the generated files or their + // entire content. + Output string +} diff --git a/pkg/domain/entities/images.go b/pkg/domain/entities/images.go index 460965b34..74f27e25f 100644 --- a/pkg/domain/entities/images.go +++ b/pkg/domain/entities/images.go @@ -141,8 +141,8 @@ type ImagePullOptions struct { Quiet bool // SignaturePolicy to use when pulling. Ignored for remote calls. SignaturePolicy string - // TLSVerify to enable/disable HTTPS and certificate verification. - TLSVerify types.OptionalBool + // SkipTLSVerify to skip HTTPS and certificate verification. + SkipTLSVerify types.OptionalBool } // ImagePullReport is the response from pulling one or more images. @@ -183,8 +183,8 @@ type ImagePushOptions struct { // SignBy adds a signature at the destination using the specified key. // Ignored for remote calls. SignBy string - // TLSVerify to enable/disable HTTPS and certificate verification. - TLSVerify types.OptionalBool + // SkipTLSVerify to skip HTTPS and certificate verification. + SkipTLSVerify types.OptionalBool } // ImageSearchOptions are the arguments for searching images. @@ -198,8 +198,8 @@ type ImageSearchOptions struct { Limit int // NoTrunc will not truncate the output. NoTrunc bool - // TLSVerify to enable/disable HTTPS and certificate verification. - TLSVerify types.OptionalBool + // SkipTLSVerify to skip HTTPS and certificate verification. + SkipTLSVerify types.OptionalBool } // ImageSearchReport is the response from searching images. @@ -218,6 +218,7 @@ type ImageSearchReport struct { Automated string } +// Image List Options type ImageListOptions struct { All bool `json:"all" schema:"all"` Filter []string `json:"Filter,omitempty"` @@ -238,13 +239,9 @@ type ImagePruneReport struct { type ImageTagOptions struct{} type ImageUntagOptions struct{} -type ImageData struct { - *inspect.ImageData -} - +// ImageInspectReport is the data when inspecting an image. type ImageInspectReport struct { - Images []*ImageData - Errors map[string]error + *inspect.ImageData } type ImageLoadOptions struct { diff --git a/pkg/domain/entities/system.go b/pkg/domain/entities/system.go index 3ddc04293..de93a382f 100644 --- a/pkg/domain/entities/system.go +++ b/pkg/domain/entities/system.go @@ -12,3 +12,17 @@ type ServiceOptions struct { Timeout time.Duration // duration of inactivity the service should wait before shutting down Command *cobra.Command // CLI command provided. Used in V1 code } + +// SystemPruneOptions provides options to prune system. +type SystemPruneOptions struct { + All bool + Volume bool +} + +// SystemPruneReport provides report after system prune is executed. +type SystemPruneReport struct { + PodPruneReport []*PodPruneReport + *ContainerPruneReport + *ImagePruneReport + VolumePruneReport []*VolumePruneReport +} diff --git a/pkg/domain/entities/types.go b/pkg/domain/entities/types.go index d742cc53d..9fbe04c9a 100644 --- a/pkg/domain/entities/types.go +++ b/pkg/domain/entities/types.go @@ -47,10 +47,14 @@ type NetOptions struct { // All CLI inspect commands and inspect sub-commands use the same options type InspectOptions struct { + // Format - change the output to JSON or a Go template. Format string `json:",omitempty"` - Latest bool `json:",omitempty"` - Size bool `json:",omitempty"` - Type string `json:",omitempty"` + // Latest - inspect the latest container Podman is aware of. + Latest bool `json:",omitempty"` + // Size (containers only) - display total file size. + Size bool `json:",omitempty"` + // Type -- return JSON for specified type. + Type string `json:",omitempty"` } // All API and CLI diff commands and diff sub-commands use the same options diff --git a/pkg/domain/infra/abi/containers.go b/pkg/domain/infra/abi/containers.go index 286d37c34..f4996583a 100644 --- a/pkg/domain/infra/abi/containers.go +++ b/pkg/domain/infra/abi/containers.go @@ -32,9 +32,9 @@ import ( "github.com/sirupsen/logrus" ) -// getContainersByContext gets pods whether all, latest, or a slice of names/ids -// is specified. -func getContainersByContext(all, latest bool, names []string, runtime *libpod.Runtime) (ctrs []*libpod.Container, err error) { +// getContainersAndInputByContext gets containers whether all, latest, or a slice of names/ids +// is specified. It also returns a list of the corresponding input name used to lookup each container. +func getContainersAndInputByContext(all, latest bool, names []string, runtime *libpod.Runtime) (ctrs []*libpod.Container, rawInput []string, err error) { var ctr *libpod.Container ctrs = []*libpod.Container{} @@ -43,6 +43,7 @@ func getContainersByContext(all, latest bool, names []string, runtime *libpod.Ru ctrs, err = runtime.GetAllContainers() case latest: ctr, err = runtime.GetLatestContainer() + rawInput = append(rawInput, ctr.ID()) ctrs = append(ctrs, ctr) default: for _, n := range names { @@ -54,6 +55,7 @@ func getContainersByContext(all, latest bool, names []string, runtime *libpod.Ru err = e } } else { + rawInput = append(rawInput, n) ctrs = append(ctrs, ctr) } } @@ -61,6 +63,13 @@ func getContainersByContext(all, latest bool, names []string, runtime *libpod.Ru return } +// getContainersByContext gets containers whether all, latest, or a slice of names/ids +// is specified. +func getContainersByContext(all, latest bool, names []string, runtime *libpod.Runtime) (ctrs []*libpod.Container, err error) { + ctrs, _, err = getContainersAndInputByContext(all, latest, names, runtime) + return +} + // TODO: Should return *entities.ContainerExistsReport, error func (ic *ContainerEngine) ContainerExists(ctx context.Context, nameOrId string) (*entities.BoolReport, error) { _, err := ic.Libpod.LookupContainer(nameOrId) @@ -514,7 +523,7 @@ func (ic *ContainerEngine) ContainerAttach(ctx context.Context, nameOrId string, } // If the container is in a pod, also set to recursively start dependencies - if err := terminal.StartAttachCtr(ctx, ctr, options.Stdin, options.Stderr, options.Stdin, options.DetachKeys, options.SigProxy, false, ctr.PodID() != ""); err != nil && errors.Cause(err) != define.ErrDetach { + if err := terminal.StartAttachCtr(ctx, ctr, options.Stdout, options.Stderr, options.Stdin, options.DetachKeys, options.SigProxy, false, ctr.PodID() != ""); err != nil && errors.Cause(err) != define.ErrDetach { return errors.Wrapf(err, "error attaching to container %s", ctr.ID()) } return nil @@ -555,12 +564,14 @@ func (ic *ContainerEngine) ContainerExec(ctx context.Context, nameOrId string, o func (ic *ContainerEngine) ContainerStart(ctx context.Context, namesOrIds []string, options entities.ContainerStartOptions) ([]*entities.ContainerStartReport, error) { var reports []*entities.ContainerStartReport var exitCode = define.ExecErrorCodeGeneric - ctrs, err := getContainersByContext(false, options.Latest, namesOrIds, ic.Libpod) + ctrs, rawInputs, err := getContainersAndInputByContext(false, options.Latest, namesOrIds, ic.Libpod) if err != nil { return nil, err } // There can only be one container if attach was used - for _, ctr := range ctrs { + for i := range ctrs { + ctr := ctrs[i] + rawInput := rawInputs[i] ctrState, err := ctr.State() if err != nil { return nil, err @@ -574,6 +585,7 @@ func (ic *ContainerEngine) ContainerStart(ctx context.Context, namesOrIds []stri // Exit cleanly immediately reports = append(reports, &entities.ContainerStartReport{ Id: ctr.ID(), + RawInput: rawInput, Err: nil, ExitCode: 0, }) @@ -584,6 +596,7 @@ func (ic *ContainerEngine) ContainerStart(ctx context.Context, namesOrIds []stri logrus.Debugf("Deadlock error: %v", err) reports = append(reports, &entities.ContainerStartReport{ Id: ctr.ID(), + RawInput: rawInput, Err: err, ExitCode: define.ExitCode(err), }) @@ -593,6 +606,7 @@ func (ic *ContainerEngine) ContainerStart(ctx context.Context, namesOrIds []stri if ctrRunning { reports = append(reports, &entities.ContainerStartReport{ Id: ctr.ID(), + RawInput: rawInput, Err: nil, ExitCode: 0, }) @@ -602,6 +616,7 @@ func (ic *ContainerEngine) ContainerStart(ctx context.Context, namesOrIds []stri if err != nil { reports = append(reports, &entities.ContainerStartReport{ Id: ctr.ID(), + RawInput: rawInput, Err: err, ExitCode: exitCode, }) @@ -624,6 +639,7 @@ func (ic *ContainerEngine) ContainerStart(ctx context.Context, namesOrIds []stri } reports = append(reports, &entities.ContainerStartReport{ Id: ctr.ID(), + RawInput: rawInput, Err: err, ExitCode: exitCode, }) @@ -636,6 +652,7 @@ func (ic *ContainerEngine) ContainerStart(ctx context.Context, namesOrIds []stri // If the container is in a pod, also set to recursively start dependencies report := &entities.ContainerStartReport{ Id: ctr.ID(), + RawInput: rawInput, ExitCode: 125, } if err := ctr.Start(ctx, ctr.PodID() != ""); err != nil { @@ -949,7 +966,7 @@ func (ic *ContainerEngine) Config(_ context.Context) (*config.Config, error) { func (ic *ContainerEngine) ContainerPort(ctx context.Context, nameOrId string, options entities.ContainerPortOptions) ([]*entities.ContainerPortReport, error) { var reports []*entities.ContainerPortReport - ctrs, err := getContainersByContext(options.All, false, []string{nameOrId}, ic.Libpod) + ctrs, err := getContainersByContext(options.All, options.Latest, []string{nameOrId}, ic.Libpod) if err != nil { return nil, err } diff --git a/pkg/domain/infra/abi/generate.go b/pkg/domain/infra/abi/generate.go new file mode 100644 index 000000000..f69ba560e --- /dev/null +++ b/pkg/domain/infra/abi/generate.go @@ -0,0 +1,174 @@ +package abi + +import ( + "context" + "fmt" + "strings" + + "github.com/containers/libpod/libpod" + "github.com/containers/libpod/pkg/domain/entities" + "github.com/containers/libpod/pkg/systemd/generate" + "github.com/pkg/errors" +) + +func (ic *ContainerEngine) GenerateSystemd(ctx context.Context, nameOrID string, options entities.GenerateSystemdOptions) (*entities.GenerateSystemdReport, error) { + opts := generate.Options{ + Files: options.Files, + New: options.New, + } + + // First assume it's a container. + if info, found, err := ic.generateSystemdgenContainerInfo(nameOrID, nil, options); found && err != nil { + return nil, err + } else if found && err == nil { + output, err := generate.CreateContainerSystemdUnit(info, opts) + if err != nil { + return nil, err + } + return &entities.GenerateSystemdReport{Output: output}, nil + } + + // --new does not support pods. + if options.New { + return nil, errors.Errorf("error generating systemd unit files: cannot generate generic files for a pod") + } + + // We're either having a pod or garbage. + pod, err := ic.Libpod.LookupPod(nameOrID) + if err != nil { + return nil, err + } + + // Error out if the pod has no infra container, which we require to be the + // main service. + if !pod.HasInfraContainer() { + return nil, fmt.Errorf("error generating systemd unit files: Pod %q has no infra container", pod.Name()) + } + + // Generate a systemdgen.ContainerInfo for the infra container. This + // ContainerInfo acts as the main service of the pod. + infraID, err := pod.InfraContainerID() + if err != nil { + return nil, nil + } + podInfo, _, err := ic.generateSystemdgenContainerInfo(infraID, pod, options) + if err != nil { + return nil, err + } + + // Compute the container-dependency graph for the Pod. + containers, err := pod.AllContainers() + if err != nil { + return nil, err + } + if len(containers) == 0 { + return nil, fmt.Errorf("error generating systemd unit files: Pod %q has no containers", pod.Name()) + } + graph, err := libpod.BuildContainerGraph(containers) + if err != nil { + return nil, err + } + + // Traverse the dependency graph and create systemdgen.ContainerInfo's for + // each container. + containerInfos := []*generate.ContainerInfo{podInfo} + for ctr, dependencies := range graph.DependencyMap() { + // Skip the infra container as we already generated it. + if ctr.ID() == infraID { + continue + } + ctrInfo, _, err := ic.generateSystemdgenContainerInfo(ctr.ID(), nil, options) + if err != nil { + return nil, err + } + // Now add the container's dependencies and at the container as a + // required service of the infra container. + for _, dep := range dependencies { + if dep.ID() == infraID { + ctrInfo.BoundToServices = append(ctrInfo.BoundToServices, podInfo.ServiceName) + } else { + _, serviceName := generateServiceName(dep, nil, options) + ctrInfo.BoundToServices = append(ctrInfo.BoundToServices, serviceName) + } + } + podInfo.RequiredServices = append(podInfo.RequiredServices, ctrInfo.ServiceName) + containerInfos = append(containerInfos, ctrInfo) + } + + // Now generate the systemd service for all containers. + builder := strings.Builder{} + for i, info := range containerInfos { + if i > 0 { + builder.WriteByte('\n') + } + out, err := generate.CreateContainerSystemdUnit(info, opts) + if err != nil { + return nil, err + } + builder.WriteString(out) + } + + return &entities.GenerateSystemdReport{Output: builder.String()}, nil +} + +// generateSystemdgenContainerInfo is a helper to generate a +// systemdgen.ContainerInfo for `GenerateSystemd`. +func (ic *ContainerEngine) generateSystemdgenContainerInfo(nameOrID string, pod *libpod.Pod, options entities.GenerateSystemdOptions) (*generate.ContainerInfo, bool, error) { + ctr, err := ic.Libpod.LookupContainer(nameOrID) + if err != nil { + return nil, false, err + } + + timeout := ctr.StopTimeout() + if options.StopTimeout != nil { + timeout = *options.StopTimeout + } + + config := ctr.Config() + conmonPidFile := config.ConmonPidFile + if conmonPidFile == "" { + return nil, true, errors.Errorf("conmon PID file path is empty, try to recreate the container with --conmon-pidfile flag") + } + + createCommand := []string{} + if config.CreateCommand != nil { + createCommand = config.CreateCommand + } else if options.New { + return nil, true, errors.Errorf("cannot use --new on container %q: no create command found", nameOrID) + } + + name, serviceName := generateServiceName(ctr, pod, options) + info := &generate.ContainerInfo{ + ServiceName: serviceName, + ContainerName: name, + RestartPolicy: options.RestartPolicy, + PIDFile: conmonPidFile, + StopTimeout: timeout, + GenerateTimestamp: true, + CreateCommand: createCommand, + } + + return info, true, nil +} + +// generateServiceName generates the container name and the service name for systemd service. +func generateServiceName(ctr *libpod.Container, pod *libpod.Pod, options entities.GenerateSystemdOptions) (string, string) { + var kind, name, ctrName string + if pod == nil { + kind = "container" + name = ctr.ID() + if options.Name { + name = ctr.Name() + } + ctrName = name + } else { + kind = "pod" + name = pod.ID() + ctrName = ctr.ID() + if options.Name { + name = pod.Name() + ctrName = ctr.Name() + } + } + return ctrName, fmt.Sprintf("%s-%s", kind, name) +} diff --git a/pkg/domain/infra/abi/images.go b/pkg/domain/infra/abi/images.go index 724bc5343..be788b2bf 100644 --- a/pkg/domain/infra/abi/images.go +++ b/pkg/domain/infra/abi/images.go @@ -46,7 +46,6 @@ func (ir *ImageEngine) Prune(ctx context.Context, opts entities.ImagePruneOption Id: results, Err: nil, }, - Size: 0, } return &report, nil } @@ -119,7 +118,7 @@ func (ir *ImageEngine) Pull(ctx context.Context, rawImage string, options entiti DockerCertPath: options.CertDir, OSChoice: options.OverrideOS, ArchitectureChoice: options.OverrideArch, - DockerInsecureSkipTLSVerify: options.TLSVerify, + DockerInsecureSkipTLSVerify: options.SkipTLSVerify, } if !options.AllTags { @@ -171,29 +170,24 @@ func (ir *ImageEngine) Pull(ctx context.Context, rawImage string, options entiti return &entities.ImagePullReport{Images: foundIDs}, nil } -func (ir *ImageEngine) Inspect(ctx context.Context, names []string, opts entities.InspectOptions) (*entities.ImageInspectReport, error) { - report := entities.ImageInspectReport{ - Errors: make(map[string]error), - } - - for _, id := range names { - img, err := ir.Libpod.ImageRuntime().NewFromLocal(id) +func (ir *ImageEngine) Inspect(ctx context.Context, namesOrIDs []string, opts entities.InspectOptions) ([]*entities.ImageInspectReport, error) { + reports := []*entities.ImageInspectReport{} + for _, i := range namesOrIDs { + img, err := ir.Libpod.ImageRuntime().NewFromLocal(i) if err != nil { - report.Errors[id] = err - continue + return nil, err } - - results, err := img.Inspect(ctx) + result, err := img.Inspect(ctx) if err != nil { - report.Errors[id] = err - continue + return nil, err } - - cookedResults := entities.ImageData{} - _ = domainUtils.DeepCopy(&cookedResults, results) - report.Images = append(report.Images, &cookedResults) + report := entities.ImageInspectReport{} + if err := domainUtils.DeepCopy(&report, result); err != nil { + return nil, err + } + reports = append(reports, &report) } - return &report, nil + return reports, nil } func (ir *ImageEngine) Push(ctx context.Context, source string, destination string, options entities.ImagePushOptions) error { @@ -227,7 +221,7 @@ func (ir *ImageEngine) Push(ctx context.Context, source string, destination stri dockerRegistryOptions := image.DockerRegistryOptions{ DockerRegistryCreds: registryCreds, DockerCertPath: options.CertDir, - DockerInsecureSkipTLSVerify: options.TLSVerify, + DockerInsecureSkipTLSVerify: options.SkipTLSVerify, } signOptions := image.SigningOptions{ @@ -376,7 +370,7 @@ func (ir *ImageEngine) Search(ctx context.Context, term string, opts entities.Im Filter: *filter, Limit: opts.Limit, NoTrunc: opts.NoTrunc, - InsecureSkipTLSVerify: opts.TLSVerify, + InsecureSkipTLSVerify: opts.SkipTLSVerify, } searchResults, err := image.SearchImages(term, searchOpts) @@ -475,6 +469,8 @@ func (ir *ImageEngine) Remove(ctx context.Context, images []string, opts entitie switch errors.Cause(err) { case nil: break + case define.ErrNoSuchImage: + inUseErrors = true // ExitCode is expected case storage.ErrImageUsedByContainer: inUseErrors = true // Important for exit codes in Podman. return errors.New( @@ -546,7 +542,7 @@ func (ir *ImageEngine) Remove(ctx context.Context, images []string, opts entitie noSuchImageErrors = true // Important for exit codes in Podman. fallthrough default: - deleteError = multierror.Append(deleteError, err) + deleteError = multierror.Append(deleteError, errors.Wrapf(err, "failed to remove image '%s'", id)) continue } diff --git a/pkg/domain/infra/abi/images_list.go b/pkg/domain/infra/abi/images_list.go index 9add915ea..c559e250c 100644 --- a/pkg/domain/infra/abi/images_list.go +++ b/pkg/domain/infra/abi/images_list.go @@ -25,8 +25,8 @@ func (ir *ImageEngine) List(ctx context.Context, opts entities.ImageListOptions) return nil, err } - summaries := make([]*entities.ImageSummary, len(images)) - for i, img := range images { + var summaries []*entities.ImageSummary + for _, img := range images { var repoTags []string if opts.All { pairs, err := libpodImage.ReposToMap(img.Names()) @@ -41,6 +41,9 @@ func (ir *ImageEngine) List(ctx context.Context, opts entities.ImageListOptions) } } else { repoTags, _ = img.RepoTags() + if len(repoTags) == 0 { + continue + } } digests := make([]string, len(img.Digests())) @@ -72,7 +75,7 @@ func (ir *ImageEngine) List(ctx context.Context, opts entities.ImageListOptions) sz, _ := img.Size(context.TODO()) e.Size = int64(*sz) - summaries[i] = &e + summaries = append(summaries, &e) } return summaries, nil } diff --git a/pkg/domain/infra/abi/pods.go b/pkg/domain/infra/abi/pods.go index 7c06f9a4e..b286bcf0d 100644 --- a/pkg/domain/infra/abi/pods.go +++ b/pkg/domain/infra/abi/pods.go @@ -236,8 +236,6 @@ func (ic *ContainerEngine) PodRm(ctx context.Context, namesOrIds []string, optio err := ic.Libpod.RemovePod(ctx, p, true, options.Force) if err != nil { report.Err = err - reports = append(reports, &report) - continue } reports = append(reports, &report) } diff --git a/pkg/domain/infra/tunnel/generate.go b/pkg/domain/infra/tunnel/generate.go new file mode 100644 index 000000000..3cd483053 --- /dev/null +++ b/pkg/domain/infra/tunnel/generate.go @@ -0,0 +1,12 @@ +package tunnel + +import ( + "context" + + "github.com/containers/libpod/pkg/domain/entities" + "github.com/pkg/errors" +) + +func (ic *ContainerEngine) GenerateSystemd(ctx context.Context, nameOrID string, options entities.GenerateSystemdOptions) (*entities.GenerateSystemdReport, error) { + return nil, errors.New("not implemented for tunnel") +} diff --git a/pkg/domain/infra/tunnel/images.go b/pkg/domain/infra/tunnel/images.go index 66e4e6e3f..dcc5fc3e7 100644 --- a/pkg/domain/infra/tunnel/images.go +++ b/pkg/domain/infra/tunnel/images.go @@ -143,16 +143,16 @@ func (ir *ImageEngine) Untag(ctx context.Context, nameOrId string, tags []string return nil } -func (ir *ImageEngine) Inspect(_ context.Context, names []string, opts entities.InspectOptions) (*entities.ImageInspectReport, error) { - report := entities.ImageInspectReport{} - for _, id := range names { - r, err := images.GetImage(ir.ClientCxt, id, &opts.Size) +func (ir *ImageEngine) Inspect(ctx context.Context, namesOrIDs []string, opts entities.InspectOptions) ([]*entities.ImageInspectReport, error) { + reports := []*entities.ImageInspectReport{} + for _, i := range namesOrIDs { + r, err := images.GetImage(ir.ClientCxt, i, &opts.Size) if err != nil { - report.Errors[id] = err + return nil, err } - report.Images = append(report.Images, r) + reports = append(reports, r) } - return &report, nil + return reports, nil } func (ir *ImageEngine) Load(ctx context.Context, opts entities.ImageLoadOptions) (*entities.ImageLoadReport, error) { diff --git a/pkg/rootless/rootless_linux.c b/pkg/rootless/rootless_linux.c index 72d461cdc..716db81dc 100644 --- a/pkg/rootless/rootless_linux.c +++ b/pkg/rootless/rootless_linux.c @@ -535,32 +535,30 @@ create_pause_process (const char *pause_pid_file_path, char **argv) } } -static void -join_namespace_or_die (int pid_to_join, const char *ns_file) +static int +open_namespace (int pid_to_join, const char *ns_file) { char ns_path[PATH_MAX]; int ret; - int fd; ret = snprintf (ns_path, PATH_MAX, "/proc/%d/ns/%s", pid_to_join, ns_file); if (ret == PATH_MAX) { fprintf (stderr, "internal error: namespace path too long\n"); - _exit (EXIT_FAILURE); + return -1; } - fd = open (ns_path, O_CLOEXEC | O_RDONLY); - if (fd < 0) - { - fprintf (stderr, "cannot open: %s\n", ns_path); - _exit (EXIT_FAILURE); - } - if (setns (fd, 0) < 0) + return open (ns_path, O_CLOEXEC | O_RDONLY); +} + +static void +join_namespace_or_die (const char *name, int ns_fd) +{ + if (setns (ns_fd, 0) < 0) { - fprintf (stderr, "cannot set namespace to %s: %s\n", ns_path, strerror (errno)); + fprintf (stderr, "cannot set %s namespace\n", name); _exit (EXIT_FAILURE); } - close (fd); } int @@ -570,6 +568,8 @@ reexec_userns_join (int pid_to_join, char *pause_pid_file_path) char gid[16]; char **argv; int pid; + int mnt_ns = -1; + int user_ns = -1; char *cwd = getcwd (NULL, 0); sigset_t sigset, oldsigset; @@ -589,14 +589,28 @@ reexec_userns_join (int pid_to_join, char *pause_pid_file_path) _exit (EXIT_FAILURE); } + user_ns = open_namespace (pid_to_join, "user"); + if (user_ns < 0) + return user_ns; + mnt_ns = open_namespace (pid_to_join, "mnt"); + if (mnt_ns < 0) + { + close (user_ns); + return mnt_ns; + } + pid = fork (); if (pid < 0) fprintf (stderr, "cannot fork: %s\n", strerror (errno)); if (pid) { - /* We passed down these fds, close them. */ int f; + + /* We passed down these fds, close them. */ + close (user_ns); + close (mnt_ns); + for (f = 3; f < open_files_max_fd; f++) if (open_files_set == NULL || FD_ISSET (f % FD_SETSIZE, &(open_files_set[f / FD_SETSIZE]))) close (f); @@ -634,8 +648,10 @@ reexec_userns_join (int pid_to_join, char *pause_pid_file_path) _exit (EXIT_FAILURE); } - join_namespace_or_die (pid_to_join, "user"); - join_namespace_or_die (pid_to_join, "mnt"); + join_namespace_or_die ("user", user_ns); + join_namespace_or_die ("mnt", mnt_ns); + close (user_ns); + close (mnt_ns); if (syscall_setresgid (0, 0, 0) < 0) { diff --git a/pkg/rootlessport/rootlessport_linux.go b/pkg/rootlessport/rootlessport_linux.go index 1c1ed39df..c686d80fc 100644 --- a/pkg/rootlessport/rootlessport_linux.go +++ b/pkg/rootlessport/rootlessport_linux.go @@ -102,25 +102,27 @@ func parent() error { return err } - sigC := make(chan os.Signal, 1) - signal.Notify(sigC, unix.SIGPIPE) - defer func() { - // dummy signal to terminate the goroutine - sigC <- unix.SIGKILL - }() + exitC := make(chan os.Signal, 1) + defer close(exitC) + go func() { + sigC := make(chan os.Signal, 1) + signal.Notify(sigC, unix.SIGPIPE) defer func() { signal.Stop(sigC) close(sigC) }() - s := <-sigC - if s == unix.SIGPIPE { - if f, err := os.OpenFile("/dev/null", os.O_WRONLY, 0755); err == nil { - unix.Dup2(int(f.Fd()), 1) // nolint:errcheck - unix.Dup2(int(f.Fd()), 2) // nolint:errcheck - f.Close() + select { + case s := <-sigC: + if s == unix.SIGPIPE { + if f, err := os.OpenFile("/dev/null", os.O_WRONLY, 0755); err == nil { + unix.Dup2(int(f.Fd()), 1) // nolint:errcheck + unix.Dup2(int(f.Fd()), 2) // nolint:errcheck + f.Close() + } } + case <-exitC: } }() diff --git a/pkg/spec/spec.go b/pkg/spec/spec.go index cb2403dec..41ed5f1f0 100644 --- a/pkg/spec/spec.go +++ b/pkg/spec/spec.go @@ -328,10 +328,6 @@ func (config *CreateConfig) createConfigToOCISpec(runtime *libpod.Runtime, userM } defaultEnv = env.Join(env.DefaultEnvVariables, defaultEnv) } - config.Env = env.Join(defaultEnv, config.Env) - for name, val := range config.Env { - g.AddProcessEnv(name, val) - } if err := addRlimits(config, &g); err != nil { return nil, err @@ -362,6 +358,11 @@ func (config *CreateConfig) createConfigToOCISpec(runtime *libpod.Runtime, userM if err := config.Cgroup.ConfigureGenerator(&g); err != nil { return nil, err } + + config.Env = env.Join(defaultEnv, config.Env) + for name, val := range config.Env { + g.AddProcessEnv(name, val) + } configSpec := g.Config // If the container image specifies an label with a diff --git a/pkg/specgen/generate/container.go b/pkg/specgen/generate/container.go index b27dd1cc2..92a2b4d35 100644 --- a/pkg/specgen/generate/container.go +++ b/pkg/specgen/generate/container.go @@ -3,6 +3,7 @@ package generate import ( "context" + "github.com/containers/image/v5/manifest" "github.com/containers/libpod/libpod" ann "github.com/containers/libpod/pkg/annotations" envLib "github.com/containers/libpod/pkg/env" @@ -22,7 +23,12 @@ func CompleteSpec(ctx context.Context, r *libpod.Runtime, s *specgen.SpecGenerat return err } - if s.HealthConfig == nil { + _, mediaType, err := newImage.Manifest(ctx) + if err != nil { + return err + } + + if s.HealthConfig == nil && mediaType == manifest.DockerV2Schema2MediaType { s.HealthConfig, err = newImage.GetHealthCheck(ctx) if err != nil { return err @@ -126,13 +132,6 @@ func CompleteSpec(ctx context.Context, r *libpod.Runtime, s *specgen.SpecGenerat if err != nil { return err } - - // TODO This should be enabled when namespaces actually work - //case usernsMode.IsKeepID(): - // user = fmt.Sprintf("%d:%d", rootless.GetRootlessUID(), rootless.GetRootlessGID()) - if len(s.User) == 0 { - s.User = "0" - } } if err := finishThrottleDevices(s); err != nil { return err diff --git a/pkg/specgen/generate/container_create.go b/pkg/specgen/generate/container_create.go index bb84f0618..14836035d 100644 --- a/pkg/specgen/generate/container_create.go +++ b/pkg/specgen/generate/container_create.go @@ -24,11 +24,10 @@ func MakeContainer(ctx context.Context, rt *libpod.Runtime, s *specgen.SpecGener // If joining a pod, retrieve the pod for use. var pod *libpod.Pod if s.Pod != "" { - foundPod, err := rt.LookupPod(s.Pod) + pod, err = rt.LookupPod(s.Pod) if err != nil { return nil, errors.Wrapf(err, "error retrieving pod %s", s.Pod) } - pod = foundPod } // Set defaults for unset namespaces @@ -76,6 +75,7 @@ func MakeContainer(ctx context.Context, rt *libpod.Runtime, s *specgen.SpecGener } options := []libpod.CtrCreateOption{} + options = append(options, libpod.WithCreateCommand()) var newImage *image.Image if s.Rootfs != "" { @@ -129,12 +129,8 @@ func createContainerOptions(rt *libpod.Runtime, s *specgen.SpecGenerator, pod *l logrus.Debugf("setting container name %s", s.Name) options = append(options, libpod.WithName(s.Name)) } - if s.Pod != "" { - pod, err := rt.LookupPod(s.Pod) - if err != nil { - return nil, err - } - logrus.Debugf("adding container to pod %s", s.Pod) + if pod != nil { + logrus.Debugf("adding container to pod %s", pod.Name()) options = append(options, rt.WithPod(pod)) } destinations := []string{} @@ -159,11 +155,12 @@ func createContainerOptions(rt *libpod.Runtime, s *specgen.SpecGenerator, pod *l options = append(options, libpod.WithNamedVolumes(vols)) } - if len(s.Command) != 0 { + if s.Command != nil { options = append(options, libpod.WithCommand(s.Command)) } - - options = append(options, libpod.WithEntrypoint(s.Entrypoint)) + if s.Entrypoint != nil { + options = append(options, libpod.WithEntrypoint(s.Entrypoint)) + } if s.StopSignal != nil { options = append(options, libpod.WithStopSignal(*s.StopSignal)) } diff --git a/pkg/specgen/generate/oci.go b/pkg/specgen/generate/oci.go index f2292f500..7993777fb 100644 --- a/pkg/specgen/generate/oci.go +++ b/pkg/specgen/generate/oci.go @@ -89,7 +89,7 @@ func makeCommand(ctx context.Context, s *specgen.SpecGenerator, img *image.Image finalCommand = append(finalCommand, entrypoint...) command := s.Command - if len(command) == 0 && img != nil { + if command == nil && img != nil { newCmd, err := img.Cmd(ctx) if err != nil { return nil, err diff --git a/pkg/specgen/namespaces.go b/pkg/specgen/namespaces.go index f0161a793..396563267 100644 --- a/pkg/specgen/namespaces.go +++ b/pkg/specgen/namespaces.go @@ -216,6 +216,8 @@ func ParseNetworkNamespace(ns string) (Namespace, []string, error) { toReturn := Namespace{} var cniNetworks []string switch { + case ns == "slirp4netns": + toReturn.NSMode = Slirp case ns == "pod": toReturn.NSMode = FromPod case ns == "bridge": diff --git a/test/apiv2/10-images.at b/test/apiv2/10-images.at index 42ec028d0..1c8da0c2f 100644 --- a/test/apiv2/10-images.at +++ b/test/apiv2/10-images.at @@ -7,15 +7,15 @@ podman pull -q $IMAGE t GET libpod/images/json 200 \ - .[0].Id~[0-9a-f]\\{64\\} -iid=$(jq -r '.[0].Id' <<<"$output") + .[0].ID~[0-9a-f]\\{64\\} +iid=$(jq -r '.[0].ID' <<<"$output") t GET libpod/images/$iid/exists 204 t GET libpod/images/$PODMAN_TEST_IMAGE_NAME/exists 204 # FIXME: compare to actual podman info t GET libpod/images/json 200 \ - .[0].Id=${iid} + .[0].ID=${iid} t GET libpod/images/$iid/json 200 \ .Id=$iid \ diff --git a/test/e2e/attach_test.go b/test/e2e/attach_test.go index 6ca8a537c..7233d169c 100644 --- a/test/e2e/attach_test.go +++ b/test/e2e/attach_test.go @@ -20,7 +20,6 @@ var _ = Describe("Podman attach", func() { ) BeforeEach(func() { - Skip(v2fail) tempdir, err = CreateTempDirInTempDir() if err != nil { os.Exit(1) diff --git a/test/e2e/build_test.go b/test/e2e/build_test.go index 3ccee3575..76651283a 100644 --- a/test/e2e/build_test.go +++ b/test/e2e/build_test.go @@ -22,7 +22,6 @@ var _ = Describe("Podman build", func() { ) BeforeEach(func() { - Skip(v2fail) tempdir, err = CreateTempDirInTempDir() if err != nil { os.Exit(1) @@ -178,6 +177,7 @@ var _ = Describe("Podman build", func() { }) It("podman Test PATH in built image", func() { + Skip(v2fail) // Run error - we don't set data from the image (i.e., PATH) yet path := "/tmp:/bin:/usr/bin:/usr/sbin" session := podmanTest.PodmanNoCache([]string{ "build", "-f", "build/basicalpine/Containerfile.path", "-t", "test-path", diff --git a/test/e2e/common_test.go b/test/e2e/common_test.go index 160af1bd5..68f733b41 100644 --- a/test/e2e/common_test.go +++ b/test/e2e/common_test.go @@ -1,7 +1,6 @@ package integration import ( - "encoding/json" "fmt" "io/ioutil" "math/rand" @@ -21,9 +20,10 @@ import ( "github.com/containers/storage" "github.com/containers/storage/pkg/reexec" "github.com/containers/storage/pkg/stringid" + jsoniter "github.com/json-iterator/go" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" - "github.com/onsi/gomega/gexec" + . "github.com/onsi/gomega/gexec" "github.com/pkg/errors" ) @@ -314,7 +314,7 @@ func (p *PodmanTestIntegration) createArtifact(image string) { // image and returns json func (s *PodmanSessionIntegration) InspectImageJSON() []inspect.ImageData { var i []inspect.ImageData - err := json.Unmarshal(s.Out.Contents(), &i) + err := jsoniter.Unmarshal(s.Out.Contents(), &i) Expect(err).To(BeNil()) return i } @@ -324,7 +324,7 @@ func (p *PodmanTestIntegration) InspectContainer(name string) []define.InspectCo cmd := []string{"inspect", name} session := p.Podman(cmd) session.WaitWithDefaultTimeout() - Expect(session.ExitCode()).To(Equal(0)) + Expect(session).Should(Exit(0)) return session.InspectContainerToJSON() } @@ -419,7 +419,7 @@ func (p *PodmanTestIntegration) PodmanPID(args []string) (*PodmanSessionIntegrat podmanOptions := p.MakeOptions(args, false, false) fmt.Printf("Running: %s %s\n", p.PodmanBinary, strings.Join(podmanOptions, " ")) command := exec.Command(p.PodmanBinary, podmanOptions...) - session, err := gexec.Start(command, GinkgoWriter, GinkgoWriter) + session, err := Start(command, GinkgoWriter, GinkgoWriter) if err != nil { Fail(fmt.Sprintf("unable to run podman command: %s", strings.Join(podmanOptions, " "))) } @@ -494,7 +494,7 @@ func (p *PodmanTestIntegration) PullImage(image string) error { // container and returns json func (s *PodmanSessionIntegration) InspectContainerToJSON() []define.InspectContainerData { var i []define.InspectContainerData - err := json.Unmarshal(s.Out.Contents(), &i) + err := jsoniter.Unmarshal(s.Out.Contents(), &i) Expect(err).To(BeNil()) return i } @@ -502,7 +502,7 @@ func (s *PodmanSessionIntegration) InspectContainerToJSON() []define.InspectCont // InspectPodToJSON takes the sessions output from a pod inspect and returns json func (s *PodmanSessionIntegration) InspectPodToJSON() define.InspectPodData { var i define.InspectPodData - err := json.Unmarshal(s.Out.Contents(), &i) + err := jsoniter.Unmarshal(s.Out.Contents(), &i) Expect(err).To(BeNil()) return i } diff --git a/test/e2e/cp_test.go b/test/e2e/cp_test.go index 2ff6fe65e..f95f8646c 100644 --- a/test/e2e/cp_test.go +++ b/test/e2e/cp_test.go @@ -22,7 +22,6 @@ var _ = Describe("Podman cp", func() { ) BeforeEach(func() { - Skip(v2fail) tempdir, err = CreateTempDirInTempDir() if err != nil { os.Exit(1) @@ -96,7 +95,7 @@ var _ = Describe("Podman cp", func() { }) It("podman cp dir to dir", func() { - testDirPath := filepath.Join(podmanTest.RunRoot, "TestDir") + testDirPath := filepath.Join(podmanTest.RunRoot, "TestDir1") session := podmanTest.Podman([]string{"create", ALPINE, "ls", "/foodir"}) session.WaitWithDefaultTimeout() @@ -105,6 +104,7 @@ var _ = Describe("Podman cp", func() { err := os.Mkdir(testDirPath, 0755) Expect(err).To(BeNil()) + defer os.RemoveAll(testDirPath) session = podmanTest.Podman([]string{"cp", testDirPath, name + ":/foodir"}) session.WaitWithDefaultTimeout() @@ -138,8 +138,6 @@ var _ = Describe("Podman cp", func() { res, err := cmd.Output() Expect(err).To(BeNil()) Expect(len(res)).To(Equal(0)) - - os.RemoveAll(testDirPath) }) It("podman cp stdin/stdout", func() { @@ -148,9 +146,10 @@ var _ = Describe("Podman cp", func() { Expect(session.ExitCode()).To(Equal(0)) name := session.OutputToString() - testDirPath := filepath.Join(podmanTest.RunRoot, "TestDir") + testDirPath := filepath.Join(podmanTest.RunRoot, "TestDir2") err := os.Mkdir(testDirPath, 0755) Expect(err).To(BeNil()) + defer os.RemoveAll(testDirPath) cmd := exec.Command("tar", "-zcvf", "file.tar.gz", testDirPath) _, err = cmd.Output() Expect(err).To(BeNil()) @@ -169,7 +168,6 @@ var _ = Describe("Podman cp", func() { session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) - os.RemoveAll(testDirPath) os.Remove("file.tar.gz") }) @@ -185,9 +183,10 @@ var _ = Describe("Podman cp", func() { path, err := os.Getwd() Expect(err).To(BeNil()) - testDirPath := filepath.Join(path, "TestDir") + testDirPath := filepath.Join(path, "TestDir3") err = os.Mkdir(testDirPath, 0777) Expect(err).To(BeNil()) + defer os.RemoveAll(testDirPath) cmd := exec.Command("tar", "-cvf", "file.tar", testDirPath) _, err = cmd.Output() Expect(err).To(BeNil()) @@ -202,7 +201,6 @@ var _ = Describe("Podman cp", func() { Expect(session.OutputToString()).To(ContainSubstring("file.tar")) os.Remove("file.tar") - os.RemoveAll(testDirPath) }) It("podman cp symlink", func() { diff --git a/test/e2e/create_test.go b/test/e2e/create_test.go index 82346823a..10742a0e8 100644 --- a/test/e2e/create_test.go +++ b/test/e2e/create_test.go @@ -18,7 +18,6 @@ var _ = Describe("Podman create", func() { ) BeforeEach(func() { - Skip(v2fail) tempdir, err = CreateTempDirInTempDir() if err != nil { os.Exit(1) diff --git a/test/e2e/exec_test.go b/test/e2e/exec_test.go index 3aac4b35b..8b95794d2 100644 --- a/test/e2e/exec_test.go +++ b/test/e2e/exec_test.go @@ -18,7 +18,6 @@ var _ = Describe("Podman exec", func() { ) BeforeEach(func() { - Skip(v2fail) tempdir, err = CreateTempDirInTempDir() if err != nil { os.Exit(1) diff --git a/test/e2e/generate_systemd_test.go b/test/e2e/generate_systemd_test.go index 2901e7ac6..abfca4db9 100644 --- a/test/e2e/generate_systemd_test.go +++ b/test/e2e/generate_systemd_test.go @@ -18,7 +18,6 @@ var _ = Describe("Podman generate systemd", func() { ) BeforeEach(func() { - Skip(v2fail) tempdir, err = CreateTempDirInTempDir() if err != nil { os.Exit(1) diff --git a/test/e2e/images_test.go b/test/e2e/images_test.go index c0165e060..d7295b67a 100644 --- a/test/e2e/images_test.go +++ b/test/e2e/images_test.go @@ -10,6 +10,7 @@ import ( "github.com/docker/go-units" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" + . "github.com/onsi/gomega/gexec" ) var _ = Describe("Podman images", func() { @@ -20,7 +21,6 @@ var _ = Describe("Podman images", func() { ) BeforeEach(func() { - Skip(v2fail) tempdir, err = CreateTempDirInTempDir() if err != nil { os.Exit(1) @@ -39,7 +39,7 @@ var _ = Describe("Podman images", func() { It("podman images", func() { session := podmanTest.Podman([]string{"images"}) session.WaitWithDefaultTimeout() - Expect(session.ExitCode()).To(Equal(0)) + Expect(session).Should(Exit(0)) Expect(len(session.OutputToStringArray())).To(BeNumerically(">", 2)) Expect(session.LineInOuputStartsWith("docker.io/library/alpine")).To(BeTrue()) Expect(session.LineInOuputStartsWith("docker.io/library/busybox")).To(BeTrue()) @@ -48,11 +48,11 @@ var _ = Describe("Podman images", func() { It("podman images with no images prints header", func() { rmi := podmanTest.PodmanNoCache([]string{"rmi", "-a"}) rmi.WaitWithDefaultTimeout() - Expect(rmi.ExitCode()).To(Equal(0)) + Expect(rmi).Should(Exit(0)) session := podmanTest.PodmanNoCache([]string{"images"}) session.WaitWithDefaultTimeout() - Expect(session.ExitCode()).To(Equal(0)) + Expect(session).Should(Exit(0)) Expect(len(session.OutputToStringArray())).To(Equal(1)) Expect(session.LineInOutputContains("REPOSITORY")).To(BeTrue()) }) @@ -60,7 +60,7 @@ var _ = Describe("Podman images", func() { It("podman image List", func() { session := podmanTest.Podman([]string{"image", "list"}) session.WaitWithDefaultTimeout() - Expect(session.ExitCode()).To(Equal(0)) + Expect(session).Should(Exit(0)) Expect(len(session.OutputToStringArray())).To(BeNumerically(">", 2)) Expect(session.LineInOuputStartsWith("docker.io/library/alpine")).To(BeTrue()) Expect(session.LineInOuputStartsWith("docker.io/library/busybox")).To(BeTrue()) @@ -71,15 +71,15 @@ var _ = Describe("Podman images", func() { podmanTest.RestoreAllArtifacts() session := podmanTest.PodmanNoCache([]string{"tag", ALPINE, "foo:a", "foo:b", "foo:c"}) session.WaitWithDefaultTimeout() - Expect(session.ExitCode()).To(Equal(0)) + Expect(session).Should(Exit(0)) // tag "foo:c" to "bar:{a,b}" session = podmanTest.PodmanNoCache([]string{"tag", "foo:c", "bar:a", "bar:b"}) session.WaitWithDefaultTimeout() - Expect(session.ExitCode()).To(Equal(0)) + Expect(session).Should(Exit(0)) // check all previous and the newly tagged images session = podmanTest.PodmanNoCache([]string{"images"}) session.WaitWithDefaultTimeout() - Expect(session.ExitCode()).To(Equal(0)) + Expect(session).Should(Exit(0)) session.LineInOutputContainsTag("docker.io/library/alpine", "latest") session.LineInOutputContainsTag("docker.io/library/busybox", "glibc") session.LineInOutputContainsTag("foo", "a") @@ -89,14 +89,14 @@ var _ = Describe("Podman images", func() { session.LineInOutputContainsTag("bar", "b") session = podmanTest.PodmanNoCache([]string{"images", "-qn"}) session.WaitWithDefaultTimeout() - Expect(session.ExitCode()).To(Equal(0)) + Expect(session).Should(Exit(0)) Expect(len(session.OutputToStringArray())).To(BeNumerically("==", 2)) }) It("podman images with digests", func() { session := podmanTest.Podman([]string{"images", "--digests"}) session.WaitWithDefaultTimeout() - Expect(session.ExitCode()).To(Equal(0)) + Expect(session).Should(Exit(0)) Expect(len(session.OutputToStringArray())).To(BeNumerically(">", 2)) Expect(session.LineInOuputStartsWith("docker.io/library/alpine")).To(BeTrue()) Expect(session.LineInOuputStartsWith("docker.io/library/busybox")).To(BeTrue()) @@ -105,14 +105,14 @@ var _ = Describe("Podman images", func() { It("podman empty images list in JSON format", func() { session := podmanTest.Podman([]string{"images", "--format=json", "not-existing-image"}) session.WaitWithDefaultTimeout() - Expect(session.ExitCode()).To(Equal(0)) + Expect(session).Should(Exit(0)) Expect(session.IsJSONOutputValid()).To(BeTrue()) }) It("podman images in JSON format", func() { session := podmanTest.Podman([]string{"images", "--format=json"}) session.WaitWithDefaultTimeout() - Expect(session.ExitCode()).To(Equal(0)) + Expect(session).Should(Exit(0)) Expect(session.IsJSONOutputValid()).To(BeTrue()) }) @@ -120,13 +120,13 @@ var _ = Describe("Podman images", func() { formatStr := "{{.ID}}\t{{.Created}}\t{{.CreatedAt}}\t{{.CreatedSince}}\t{{.CreatedTime}}" session := podmanTest.Podman([]string{"images", fmt.Sprintf("--format=%s", formatStr)}) session.WaitWithDefaultTimeout() - Expect(session.ExitCode()).To(Equal(0)) + Expect(session).Should(Exit(0)) }) It("podman images with short options", func() { session := podmanTest.Podman([]string{"images", "-qn"}) session.WaitWithDefaultTimeout() - Expect(session.ExitCode()).To(Equal(0)) + Expect(session).Should(Exit(0)) Expect(len(session.OutputToStringArray())).To(BeNumerically(">", 1)) }) @@ -134,19 +134,19 @@ var _ = Describe("Podman images", func() { podmanTest.RestoreAllArtifacts() session := podmanTest.PodmanNoCache([]string{"images", "-q", ALPINE}) session.WaitWithDefaultTimeout() - Expect(session.ExitCode()).To(Equal(0)) + Expect(session).Should(Exit(0)) Expect(len(session.OutputToStringArray())).To(Equal(1)) session = podmanTest.PodmanNoCache([]string{"tag", ALPINE, "foo:a"}) session.WaitWithDefaultTimeout() - Expect(session.ExitCode()).To(Equal(0)) + Expect(session).Should(Exit(0)) session = podmanTest.PodmanNoCache([]string{"tag", BB, "foo:b"}) session.WaitWithDefaultTimeout() - Expect(session.ExitCode()).To(Equal(0)) + Expect(session).Should(Exit(0)) session = podmanTest.PodmanNoCache([]string{"images", "-q", "foo"}) session.WaitWithDefaultTimeout() - Expect(session.ExitCode()).To(Equal(0)) + Expect(session).Should(Exit(0)) Expect(len(session.OutputToStringArray())).To(Equal(2)) }) @@ -157,24 +157,24 @@ var _ = Describe("Podman images", func() { podmanTest.RestoreAllArtifacts() result := podmanTest.PodmanNoCache([]string{"images", "-q", "-f", "reference=docker.io*"}) result.WaitWithDefaultTimeout() - Expect(result.ExitCode()).To(Equal(0)) + Expect(result).Should(Exit(0)) Expect(len(result.OutputToStringArray())).To(Equal(2)) retapline := podmanTest.PodmanNoCache([]string{"images", "-f", "reference=a*pine"}) retapline.WaitWithDefaultTimeout() - Expect(retapline.ExitCode()).To(Equal(0)) + Expect(retapline).Should(Exit(0)) Expect(len(retapline.OutputToStringArray())).To(Equal(2)) Expect(retapline.LineInOutputContains("alpine")).To(BeTrue()) retapline = podmanTest.PodmanNoCache([]string{"images", "-f", "reference=alpine"}) retapline.WaitWithDefaultTimeout() - Expect(retapline.ExitCode()).To(Equal(0)) + Expect(retapline).Should(Exit(0)) Expect(len(retapline.OutputToStringArray())).To(Equal(2)) Expect(retapline.LineInOutputContains("alpine")).To(BeTrue()) retnone := podmanTest.PodmanNoCache([]string{"images", "-q", "-f", "reference=bogus"}) retnone.WaitWithDefaultTimeout() - Expect(retnone.ExitCode()).To(Equal(0)) + Expect(retnone).Should(Exit(0)) Expect(len(retnone.OutputToStringArray())).To(Equal(0)) }) @@ -188,7 +188,7 @@ RUN apk update && apk add man podmanTest.BuildImage(dockerfile, "foobar.com/before:latest", "false") result := podmanTest.Podman([]string{"images", "-q", "-f", "before=foobar.com/before:latest"}) result.WaitWithDefaultTimeout() - Expect(result.ExitCode()).To(Equal(0)) + Expect(result).Should(Exit(0)) Expect(len(result.OutputToStringArray()) >= 1).To(BeTrue()) }) @@ -199,14 +199,14 @@ RUN apk update && apk add man podmanTest.RestoreAllArtifacts() rmi := podmanTest.PodmanNoCache([]string{"rmi", "busybox"}) rmi.WaitWithDefaultTimeout() - Expect(rmi.ExitCode()).To(Equal(0)) + Expect(rmi).Should(Exit(0)) dockerfile := `FROM docker.io/library/alpine:latest ` podmanTest.BuildImage(dockerfile, "foobar.com/before:latest", "false") result := podmanTest.PodmanNoCache([]string{"images", "-q", "-f", "after=docker.io/library/alpine:latest"}) result.WaitWithDefaultTimeout() - Expect(result.ExitCode()).To(Equal(0)) + Expect(result).Should(Exit(0)) Expect(len(result.OutputToStringArray())).To(Equal(0)) }) @@ -217,14 +217,14 @@ RUN apk update && apk add man podmanTest.RestoreAllArtifacts() rmi := podmanTest.PodmanNoCache([]string{"image", "rm", "busybox"}) rmi.WaitWithDefaultTimeout() - Expect(rmi.ExitCode()).To(Equal(0)) + Expect(rmi).Should(Exit(0)) dockerfile := `FROM docker.io/library/alpine:latest ` podmanTest.BuildImage(dockerfile, "foobar.com/before:latest", "false") result := podmanTest.PodmanNoCache([]string{"image", "list", "-q", "-f", "after=docker.io/library/alpine:latest"}) result.WaitWithDefaultTimeout() - Expect(result.ExitCode()).To(Equal(0)) + Expect(result).Should(Exit(0)) Expect(len(result.OutputToStringArray())).To(Equal(0)) }) @@ -238,7 +238,7 @@ RUN apk update && apk add man podmanTest.BuildImage(dockerfile, "foobar.com/before:latest", "false") result := podmanTest.Podman([]string{"images", "-q", "-f", "dangling=true"}) result.WaitWithDefaultTimeout() - Expect(result.ExitCode()).To(Equal(0)) + Expect(result).Should(Exit(0)) Expect(len(result.OutputToStringArray())).To(Equal(0)) }) @@ -248,13 +248,13 @@ RUN apk update && apk add man } session := podmanTest.Podman([]string{"inspect", "--format=json", ALPINE}) session.WaitWithDefaultTimeout() - Expect(session.ExitCode()).To(Equal(0)) + Expect(session).Should(Exit(0)) Expect(session.IsJSONOutputValid()).To(BeTrue()) imageData := session.InspectImageJSON() - result := podmanTest.Podman([]string{"images", fmt.Sprintf("sha256:%s", imageData[0].ID)}) + result := podmanTest.Podman([]string{"images", "sha256:" + imageData[0].ID}) result.WaitWithDefaultTimeout() - Expect(result.ExitCode()).To(Equal(0)) + Expect(result).Should(Exit(0)) }) It("podman check for image with sha256: prefix", func() { @@ -263,13 +263,13 @@ RUN apk update && apk add man } session := podmanTest.Podman([]string{"image", "inspect", "--format=json", ALPINE}) session.WaitWithDefaultTimeout() - Expect(session.ExitCode()).To(Equal(0)) + Expect(session).Should(Exit(0)) Expect(session.IsJSONOutputValid()).To(BeTrue()) imageData := session.InspectImageJSON() result := podmanTest.Podman([]string{"image", "ls", fmt.Sprintf("sha256:%s", imageData[0].ID)}) result.WaitWithDefaultTimeout() - Expect(result.ExitCode()).To(Equal(0)) + Expect(result).Should(Exit(0)) }) It("podman images sort by values", func() { @@ -277,7 +277,7 @@ RUN apk update && apk add man f := fmt.Sprintf("{{.%s}}", format) session := podmanTest.Podman([]string{"images", "--sort", value, "--format", f}) session.WaitWithDefaultTimeout() - Expect(session.ExitCode()).To(Equal(result)) + Expect(session).Should(Exit(result)) return session.OutputToStringArray() } @@ -298,7 +298,9 @@ RUN apk update && apk add man return size1 < size2 })).To(BeTrue()) sortedArr = sortValueTest("tag", 0, "Tag") - Expect(sort.SliceIsSorted(sortedArr, func(i, j int) bool { return sortedArr[i] < sortedArr[j] })).To(BeTrue()) + Expect(sort.SliceIsSorted(sortedArr, + func(i, j int) bool { return sortedArr[i] < sortedArr[j] })). + To(BeTrue()) sortValueTest("badvalue", 125, "Tag") sortValueTest("id", 125, "badvalue") @@ -317,12 +319,12 @@ ENV foo=bar podmanTest.BuildImage(dockerfile, "test", "true") session := podmanTest.PodmanNoCache([]string{"images"}) session.WaitWithDefaultTimeout() - Expect(session.ExitCode()).To(Equal(0)) + Expect(session).Should(Exit(0)) Expect(len(session.OutputToStringArray())).To(Equal(4)) session2 := podmanTest.PodmanNoCache([]string{"images", "--all"}) session2.WaitWithDefaultTimeout() - Expect(session2.ExitCode()).To(Equal(0)) + Expect(session2).Should(Exit(0)) Expect(len(session2.OutputToStringArray())).To(Equal(6)) }) @@ -335,7 +337,7 @@ LABEL "com.example.vendor"="Example Vendor" podmanTest.BuildImage(dockerfile, "test", "true") session := podmanTest.Podman([]string{"images", "-f", "label=version=1.0"}) session.WaitWithDefaultTimeout() - Expect(session.ExitCode()).To(Equal(0)) + Expect(session).Should(Exit(0)) Expect(len(session.OutputToStringArray())).To(Equal(2)) }) @@ -357,52 +359,52 @@ LABEL "com.example.vendor"="Example Vendor" session := podmanTest.Podman([]string{"images", "foo"}) session.WaitWithDefaultTimeout() - Expect(session.ExitCode()).To(Equal(0)) + Expect(session).Should(Exit(0)) output := session.OutputToString() Expect(output).To(Not(MatchRegexp("<missing>"))) Expect(output).To(Not(MatchRegexp("error"))) session = podmanTest.Podman([]string{"image", "tree", "foo"}) session.WaitWithDefaultTimeout() - Expect(session.ExitCode()).To(Equal(0)) + Expect(session).Should(Exit(0)) output = session.OutputToString() Expect(output).To(MatchRegexp("No Image Layers")) session = podmanTest.Podman([]string{"history", "foo"}) session.WaitWithDefaultTimeout() - Expect(session.ExitCode()).To(Equal(0)) + Expect(session).Should(Exit(0)) output = session.OutputToString() Expect(output).To(Not(MatchRegexp("error"))) session = podmanTest.Podman([]string{"history", "--quiet", "foo"}) session.WaitWithDefaultTimeout() - Expect(session.ExitCode()).To(Equal(0)) + Expect(session).Should(Exit(0)) Expect(len(session.OutputToStringArray())).To(Equal(6)) session = podmanTest.Podman([]string{"image", "list", "foo"}) session.WaitWithDefaultTimeout() - Expect(session.ExitCode()).To(Equal(0)) + Expect(session).Should(Exit(0)) output = session.OutputToString() Expect(output).To(Not(MatchRegexp("<missing>"))) Expect(output).To(Not(MatchRegexp("error"))) session = podmanTest.Podman([]string{"image", "list"}) session.WaitWithDefaultTimeout() - Expect(session.ExitCode()).To(Equal(0)) + Expect(session).Should(Exit(0)) output = session.OutputToString() Expect(output).To(Not(MatchRegexp("<missing>"))) Expect(output).To(Not(MatchRegexp("error"))) session = podmanTest.Podman([]string{"inspect", "foo"}) session.WaitWithDefaultTimeout() - Expect(session.ExitCode()).To(Equal(0)) + Expect(session).Should(Exit(0)) output = session.OutputToString() Expect(output).To(Not(MatchRegexp("<missing>"))) Expect(output).To(Not(MatchRegexp("error"))) session = podmanTest.Podman([]string{"inspect", "--format", "{{.RootFS.Layers}}", "foo"}) session.WaitWithDefaultTimeout() - Expect(session.ExitCode()).To(Equal(0)) + Expect(session).Should(Exit(0)) output = session.OutputToString() Expect(output).To(Equal("[]")) }) @@ -414,11 +416,11 @@ LABEL "com.example.vendor"="Example Vendor" podmanTest.BuildImage(dockerfile, "foobar.com/before:latest", "false") result := podmanTest.Podman([]string{"images", "-f", "readonly=true"}) result.WaitWithDefaultTimeout() - Expect(result.ExitCode()).To(Equal(0)) + Expect(result).Should(Exit(0)) result1 := podmanTest.Podman([]string{"images", "--filter", "readonly=false"}) result1.WaitWithDefaultTimeout() - Expect(result1.ExitCode()).To(Equal(0)) + Expect(result1).Should(Exit(0)) Expect(result.OutputToStringArray()).To(Not(Equal(result1.OutputToStringArray()))) }) diff --git a/test/e2e/inspect_test.go b/test/e2e/inspect_test.go index 5ec1b51bb..ebac087ac 100644 --- a/test/e2e/inspect_test.go +++ b/test/e2e/inspect_test.go @@ -17,7 +17,6 @@ var _ = Describe("Podman inspect", func() { ) BeforeEach(func() { - Skip(v2fail) tempdir, err = CreateTempDirInTempDir() if err != nil { os.Exit(1) diff --git a/test/e2e/prune_test.go b/test/e2e/prune_test.go index e8a208c3c..466a4f739 100644 --- a/test/e2e/prune_test.go +++ b/test/e2e/prune_test.go @@ -22,7 +22,6 @@ var _ = Describe("Podman prune", func() { ) BeforeEach(func() { - Skip(v2fail) tempdir, err = CreateTempDirInTempDir() if err != nil { os.Exit(1) @@ -149,6 +148,7 @@ var _ = Describe("Podman prune", func() { It("podman system image prune unused images", func() { SkipIfRemote() + Skip(v2fail) podmanTest.RestoreAllArtifacts() podmanTest.BuildImage(pruneImage, "alpine_bash:latest", "true") prune := podmanTest.PodmanNoCache([]string{"system", "prune", "-a", "--force"}) @@ -162,6 +162,7 @@ var _ = Describe("Podman prune", func() { }) It("podman system prune pods", func() { + Skip(v2fail) session := podmanTest.Podman([]string{"pod", "create"}) session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) diff --git a/test/e2e/push_test.go b/test/e2e/push_test.go index 0991da867..0747257be 100644 --- a/test/e2e/push_test.go +++ b/test/e2e/push_test.go @@ -22,7 +22,6 @@ var _ = Describe("Podman push", func() { ) BeforeEach(func() { - Skip(v2fail) tempdir, err = CreateTempDirInTempDir() if err != nil { os.Exit(1) diff --git a/test/e2e/rmi_test.go b/test/e2e/rmi_test.go index d556cbc72..6c0b01bd5 100644 --- a/test/e2e/rmi_test.go +++ b/test/e2e/rmi_test.go @@ -141,12 +141,13 @@ var _ = Describe("Podman rmi", func() { session = podmanTest.PodmanNoCache([]string{"images", "-q", "-a"}) session.WaitWithDefaultTimeout() Expect(session).Should(Exit(0)) - Expect(len(session.OutputToStringArray())).To(Equal(2)) - untaggedImg := session.OutputToStringArray()[1] + Expect(len(session.OutputToStringArray())).To(Equal(2), + "Output from 'podman images -q -a':'%s'", session.Out.Contents()) + untaggedImg := session.OutputToStringArray()[0] session = podmanTest.PodmanNoCache([]string{"rmi", "-f", untaggedImg}) session.WaitWithDefaultTimeout() - Expect(session).Should(Exit(2)) + Expect(session).Should(Exit(2), "UntaggedImg is '%s'", untaggedImg) }) It("podman rmi image that is created from another named imaged", func() { diff --git a/test/e2e/run_entrypoint_test.go b/test/e2e/run_entrypoint_test.go index ebc06b36c..b1344a371 100644 --- a/test/e2e/run_entrypoint_test.go +++ b/test/e2e/run_entrypoint_test.go @@ -18,7 +18,6 @@ var _ = Describe("Podman run entrypoint", func() { ) BeforeEach(func() { - Skip(v2fail) tempdir, err = CreateTempDirInTempDir() if err != nil { os.Exit(1) diff --git a/test/e2e/run_env_test.go b/test/e2e/run_env_test.go new file mode 100644 index 000000000..867913a08 --- /dev/null +++ b/test/e2e/run_env_test.go @@ -0,0 +1,138 @@ +// +build !remoteclient + +package integration + +import ( + "os" + + . "github.com/containers/libpod/test/utils" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +var _ = Describe("Podman run", func() { + var ( + tempdir string + err error + podmanTest *PodmanTestIntegration + ) + + BeforeEach(func() { + tempdir, err = CreateTempDirInTempDir() + if err != nil { + os.Exit(1) + } + podmanTest = PodmanTestCreate(tempdir) + podmanTest.Setup() + podmanTest.SeedImages() + }) + + AfterEach(func() { + podmanTest.Cleanup() + f := CurrentGinkgoTestDescription() + processTestResult(f) + + }) + + It("podman run environment test", func() { + session := podmanTest.Podman([]string{"run", "--rm", ALPINE, "printenv", "HOME"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + match, _ := session.GrepString("/root") + Expect(match).Should(BeTrue()) + + session = podmanTest.Podman([]string{"run", "--rm", "--user", "2", ALPINE, "printenv", "HOME"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + match, _ = session.GrepString("/sbin") + Expect(match).Should(BeTrue()) + + session = podmanTest.Podman([]string{"run", "--rm", "--env", "HOME=/foo", ALPINE, "printenv", "HOME"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + match, _ = session.GrepString("/foo") + Expect(match).Should(BeTrue()) + + session = podmanTest.Podman([]string{"run", "--rm", "--env", "FOO=BAR,BAZ", ALPINE, "printenv", "FOO"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + match, _ = session.GrepString("BAR,BAZ") + Expect(match).Should(BeTrue()) + + session = podmanTest.Podman([]string{"run", "--rm", "--env", "PATH=/bin", ALPINE, "printenv", "PATH"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + match, _ = session.GrepString("/bin") + Expect(match).Should(BeTrue()) + + os.Setenv("FOO", "BAR") + session = podmanTest.Podman([]string{"run", "--rm", "--env", "FOO", ALPINE, "printenv", "FOO"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + match, _ = session.GrepString("BAR") + Expect(match).Should(BeTrue()) + os.Unsetenv("FOO") + + session = podmanTest.Podman([]string{"run", "--rm", "--env", "FOO", ALPINE, "printenv", "FOO"}) + session.WaitWithDefaultTimeout() + Expect(len(session.OutputToString())).To(Equal(0)) + Expect(session.ExitCode()).To(Equal(1)) + + session = podmanTest.Podman([]string{"run", "--rm", ALPINE, "printenv"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + + // This currently does not work + // Re-enable when hostname is an env variable + session = podmanTest.Podman([]string{"run", "--rm", ALPINE, "sh", "-c", "printenv"}) + session.Wait(10) + Expect(session.ExitCode()).To(Equal(0)) + match, _ = session.GrepString("HOSTNAME") + Expect(match).Should(BeTrue()) + }) + + It("podman run --host-env environment test", func() { + env := append(os.Environ(), "FOO=BAR") + session := podmanTest.PodmanAsUser([]string{"run", "--rm", "--env-host", ALPINE, "/bin/printenv", "FOO"}, 0, 0, "", env) + + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + match, _ := session.GrepString("BAR") + Expect(match).Should(BeTrue()) + + session = podmanTest.PodmanAsUser([]string{"run", "--rm", "--env", "FOO=BAR1", "--env-host", ALPINE, "/bin/printenv", "FOO"}, 0, 0, "", env) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + match, _ = session.GrepString("BAR1") + Expect(match).Should(BeTrue()) + os.Unsetenv("FOO") + }) + + It("podman run --http-proxy test", func() { + os.Setenv("http_proxy", "1.2.3.4") + session := podmanTest.Podman([]string{"run", "--rm", ALPINE, "printenv", "http_proxy"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + match, _ := session.GrepString("1.2.3.4") + Expect(match).Should(BeTrue()) + + session = podmanTest.Podman([]string{"run", "--http-proxy=false", ALPINE, "printenv", "http_proxy"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(1)) + Expect(session.OutputToString()).To(Equal("")) + + session = podmanTest.Podman([]string{"run", "--env", "http_proxy=5.6.7.8", ALPINE, "printenv", "http_proxy"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + match, _ = session.GrepString("5.6.7.8") + Expect(match).Should(BeTrue()) + os.Unsetenv("http_proxy") + + session = podmanTest.Podman([]string{"run", "--http-proxy=false", "--env", "http_proxy=5.6.7.8", ALPINE, "printenv", "http_proxy"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + match, _ = session.GrepString("5.6.7.8") + Expect(match).Should(BeTrue()) + os.Unsetenv("http_proxy") + }) +}) diff --git a/test/e2e/run_test.go b/test/e2e/run_test.go index 7d4039819..59215c7e5 100644 --- a/test/e2e/run_test.go +++ b/test/e2e/run_test.go @@ -193,80 +193,6 @@ var _ = Describe("Podman run", func() { Expect(session.ExitCode()).To(Equal(0)) }) - It("podman run environment test", func() { - session := podmanTest.Podman([]string{"run", "--rm", ALPINE, "printenv", "HOME"}) - session.WaitWithDefaultTimeout() - Expect(session.ExitCode()).To(Equal(0)) - match, _ := session.GrepString("/root") - Expect(match).Should(BeTrue()) - - session = podmanTest.Podman([]string{"run", "--rm", "--user", "2", ALPINE, "printenv", "HOME"}) - session.WaitWithDefaultTimeout() - Expect(session.ExitCode()).To(Equal(0)) - match, _ = session.GrepString("/sbin") - Expect(match).Should(BeTrue()) - - session = podmanTest.Podman([]string{"run", "--rm", "--env", "HOME=/foo", ALPINE, "printenv", "HOME"}) - session.WaitWithDefaultTimeout() - Expect(session.ExitCode()).To(Equal(0)) - match, _ = session.GrepString("/foo") - Expect(match).Should(BeTrue()) - - session = podmanTest.Podman([]string{"run", "--rm", "--env", "FOO=BAR,BAZ", ALPINE, "printenv", "FOO"}) - session.WaitWithDefaultTimeout() - Expect(session.ExitCode()).To(Equal(0)) - match, _ = session.GrepString("BAR,BAZ") - Expect(match).Should(BeTrue()) - - session = podmanTest.Podman([]string{"run", "--rm", "--env", "PATH=/bin", ALPINE, "printenv", "PATH"}) - session.WaitWithDefaultTimeout() - Expect(session.ExitCode()).To(Equal(0)) - match, _ = session.GrepString("/bin") - Expect(match).Should(BeTrue()) - - os.Setenv("FOO", "BAR") - session = podmanTest.Podman([]string{"run", "--rm", "--env", "FOO", ALPINE, "printenv", "FOO"}) - session.WaitWithDefaultTimeout() - Expect(session.ExitCode()).To(Equal(0)) - match, _ = session.GrepString("BAR") - Expect(match).Should(BeTrue()) - os.Unsetenv("FOO") - - session = podmanTest.Podman([]string{"run", "--rm", "--env", "FOO", ALPINE, "printenv", "FOO"}) - session.WaitWithDefaultTimeout() - Expect(len(session.OutputToString())).To(Equal(0)) - Expect(session.ExitCode()).To(Equal(1)) - - session = podmanTest.Podman([]string{"run", "--rm", ALPINE, "printenv"}) - session.WaitWithDefaultTimeout() - Expect(session.ExitCode()).To(Equal(0)) - - // This currently does not work - // Re-enable when hostname is an env variable - session = podmanTest.Podman([]string{"run", "--rm", ALPINE, "sh", "-c", "printenv"}) - session.Wait(10) - Expect(session.ExitCode()).To(Equal(0)) - match, _ = session.GrepString("HOSTNAME") - Expect(match).Should(BeTrue()) - }) - - It("podman run --host-env environment test", func() { - env := append(os.Environ(), "FOO=BAR") - session := podmanTest.PodmanAsUser([]string{"run", "--rm", "--env-host", ALPINE, "/bin/printenv", "FOO"}, 0, 0, "", env) - - session.WaitWithDefaultTimeout() - Expect(session.ExitCode()).To(Equal(0)) - match, _ := session.GrepString("BAR") - Expect(match).Should(BeTrue()) - - session = podmanTest.PodmanAsUser([]string{"run", "--rm", "--env", "FOO=BAR1", "--env-host", ALPINE, "/bin/printenv", "FOO"}, 0, 0, "", env) - session.WaitWithDefaultTimeout() - Expect(session.ExitCode()).To(Equal(0)) - match, _ = session.GrepString("BAR1") - Expect(match).Should(BeTrue()) - os.Unsetenv("FOO") - }) - It("podman run limits test", func() { SkipIfRootless() session := podmanTest.Podman([]string{"run", "--rm", "--ulimit", "rtprio=99", "--cap-add=sys_nice", fedoraMinimal, "cat", "/proc/self/sched"}) @@ -708,7 +634,6 @@ USER mail` }) It("podman run --volumes-from flag with built-in volumes", func() { - Skip(v2fail) session := podmanTest.Podman([]string{"create", redis, "sh"}) session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) @@ -803,7 +728,6 @@ USER mail` }) It("podman run --pod automatically", func() { - Skip(v2fail) session := podmanTest.Podman([]string{"run", "--pod", "new:foobar", ALPINE, "ls"}) session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) @@ -875,21 +799,6 @@ USER mail` Expect(session).To(ExitWithError()) }) - It("podman run --http-proxy test", func() { - os.Setenv("http_proxy", "1.2.3.4") - session := podmanTest.Podman([]string{"run", "--rm", ALPINE, "printenv", "http_proxy"}) - session.WaitWithDefaultTimeout() - Expect(session.ExitCode()).To(Equal(0)) - match, _ := session.GrepString("1.2.3.4") - Expect(match).Should(BeTrue()) - - session = podmanTest.Podman([]string{"run", "--http-proxy=false", ALPINE, "printenv", "http_proxy"}) - session.WaitWithDefaultTimeout() - Expect(session.ExitCode()).To(Equal(1)) - Expect(session.OutputToString()).To(Equal("")) - os.Unsetenv("http_proxy") - }) - It("podman run with restart-policy always restarts containers", func() { testDir := filepath.Join(podmanTest.RunRoot, "restart-test") diff --git a/test/e2e/run_volume_test.go b/test/e2e/run_volume_test.go index 1d9538912..1f892d9f8 100644 --- a/test/e2e/run_volume_test.go +++ b/test/e2e/run_volume_test.go @@ -222,7 +222,6 @@ var _ = Describe("Podman run with volumes", func() { }) It("podman run with tmpfs named volume mounts and unmounts", func() { - Skip(v2fail) SkipIfRootless() volName := "testvol" mkVolume := podmanTest.Podman([]string{"volume", "create", "--opt", "type=tmpfs", "--opt", "device=tmpfs", "--opt", "o=nodev", "testvol"}) @@ -279,7 +278,6 @@ var _ = Describe("Podman run with volumes", func() { }) It("podman named volume copyup", func() { - Skip(v2fail) baselineSession := podmanTest.Podman([]string{"run", "--rm", "-t", "-i", ALPINE, "ls", "/etc/apk/"}) baselineSession.WaitWithDefaultTimeout() Expect(baselineSession.ExitCode()).To(Equal(0)) @@ -311,7 +309,6 @@ var _ = Describe("Podman run with volumes", func() { }) It("podman run with anonymous volume", func() { - Skip(v2fail) list1 := podmanTest.Podman([]string{"volume", "list", "--quiet"}) list1.WaitWithDefaultTimeout() Expect(list1.ExitCode()).To(Equal(0)) @@ -330,7 +327,6 @@ var _ = Describe("Podman run with volumes", func() { }) It("podman rm -v removes anonymous volume", func() { - Skip(v2fail) list1 := podmanTest.Podman([]string{"volume", "list", "--quiet"}) list1.WaitWithDefaultTimeout() Expect(list1.ExitCode()).To(Equal(0)) @@ -359,7 +355,6 @@ var _ = Describe("Podman run with volumes", func() { }) It("podman rm -v retains named volume", func() { - Skip(v2fail) list1 := podmanTest.Podman([]string{"volume", "list", "--quiet"}) list1.WaitWithDefaultTimeout() Expect(list1.ExitCode()).To(Equal(0)) @@ -398,7 +393,6 @@ var _ = Describe("Podman run with volumes", func() { }) It("podman mount with invalid option fails", func() { - Skip(v2fail) volName := "testVol" volCreate := podmanTest.Podman([]string{"volume", "create", "--opt", "type=tmpfs", "--opt", "device=tmpfs", "--opt", "o=invalid", volName}) volCreate.WaitWithDefaultTimeout() @@ -410,7 +404,6 @@ var _ = Describe("Podman run with volumes", func() { }) It("Podman fix for CVE-2020-1726", func() { - Skip(v2fail) volName := "testVol" volCreate := podmanTest.Podman([]string{"volume", "create", volName}) volCreate.WaitWithDefaultTimeout() diff --git a/test/e2e/save_test.go b/test/e2e/save_test.go index 60825f975..aaa5ae180 100644 --- a/test/e2e/save_test.go +++ b/test/e2e/save_test.go @@ -116,4 +116,16 @@ var _ = Describe("Podman save", func() { Expect(save).To(ExitWithError()) }) + It("podman save image with digest reference", func() { + // pull a digest reference + session := podmanTest.PodmanNoCache([]string{"pull", ALPINELISTDIGEST}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + + // save a digest reference should exit without error. + outfile := filepath.Join(podmanTest.TempDir, "temp.tar") + save := podmanTest.PodmanNoCache([]string{"save", "-o", outfile, ALPINELISTDIGEST}) + save.WaitWithDefaultTimeout() + Expect(save.ExitCode()).To(Equal(0)) + }) }) diff --git a/test/e2e/search_test.go b/test/e2e/search_test.go index 3c64fa05f..9ba0241fe 100644 --- a/test/e2e/search_test.go +++ b/test/e2e/search_test.go @@ -68,7 +68,6 @@ registries = ['{{.Host}}:{{.Port}}']` registryFileTwoTmpl := template.Must(template.New("registryFileTwo").Parse(regFileContents2)) BeforeEach(func() { - Skip(v2fail) tempdir, err = CreateTempDirInTempDir() if err != nil { os.Exit(1) diff --git a/test/e2e/start_test.go b/test/e2e/start_test.go index 5f6f5a8cf..6af0b7068 100644 --- a/test/e2e/start_test.go +++ b/test/e2e/start_test.go @@ -17,7 +17,6 @@ var _ = Describe("Podman start", func() { ) BeforeEach(func() { - Skip(v2fail) tempdir, err = CreateTempDirInTempDir() if err != nil { os.Exit(1) @@ -66,10 +65,11 @@ var _ = Describe("Podman start", func() { session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) cid := session.OutputToString() - session = podmanTest.Podman([]string{"container", "start", cid[0:10]}) + shortID := cid[0:10] + session = podmanTest.Podman([]string{"container", "start", shortID}) session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) - Expect(session.OutputToString()).To(Equal(cid)) + Expect(session.OutputToString()).To(Equal(shortID)) }) It("podman start single container by name", func() { diff --git a/test/e2e/version_test.go b/test/e2e/version_test.go index 4d2e14589..e353b9f97 100644 --- a/test/e2e/version_test.go +++ b/test/e2e/version_test.go @@ -7,6 +7,7 @@ import ( "github.com/containers/libpod/version" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" + . "github.com/onsi/gomega/gexec" ) var _ = Describe("Podman version", func() { @@ -35,54 +36,49 @@ var _ = Describe("Podman version", func() { It("podman version", func() { session := podmanTest.Podman([]string{"version"}) session.WaitWithDefaultTimeout() - Expect(session.ExitCode()).To(Equal(0)) - Expect(len(session.OutputToStringArray())).To(BeNumerically(">", 2)) - ok, _ := session.GrepString(version.Version) - Expect(ok).To(BeTrue()) + Expect(session).Should(Exit(0)) + Expect(session.Out.Contents()).Should(ContainSubstring(version.Version)) }) It("podman -v", func() { - Skip(v2fail) session := podmanTest.Podman([]string{"-v"}) session.WaitWithDefaultTimeout() - Expect(session.ExitCode()).To(Equal(0)) - ok, _ := session.GrepString(version.Version) - Expect(ok).To(BeTrue()) + Expect(session).Should(Exit(0)) + Expect(session.Out.Contents()).Should(ContainSubstring(version.Version)) }) It("podman --version", func() { session := podmanTest.Podman([]string{"--version"}) session.WaitWithDefaultTimeout() - Expect(session.ExitCode()).To(Equal(0)) - ok, _ := session.GrepString(version.Version) - Expect(ok).To(BeTrue()) + Expect(session).Should(Exit(0)) + Expect(session.Out.Contents()).Should(ContainSubstring(version.Version)) }) It("podman version --format json", func() { session := podmanTest.Podman([]string{"version", "--format", "json"}) session.WaitWithDefaultTimeout() - Expect(session.ExitCode()).To(Equal(0)) + Expect(session).Should(Exit(0)) Expect(session.IsJSONOutputValid()).To(BeTrue()) }) It("podman version --format json", func() { session := podmanTest.Podman([]string{"version", "--format", "{{ json .}}"}) session.WaitWithDefaultTimeout() - Expect(session.ExitCode()).To(Equal(0)) + Expect(session).Should(Exit(0)) Expect(session.IsJSONOutputValid()).To(BeTrue()) }) It("podman version --format GO template", func() { session := podmanTest.Podman([]string{"version", "--format", "{{ .Client.Version }}"}) session.WaitWithDefaultTimeout() - Expect(session.ExitCode()).To(Equal(0)) + Expect(session).Should(Exit(0)) session = podmanTest.Podman([]string{"version", "--format", "{{ .Server.Version }}"}) session.WaitWithDefaultTimeout() - Expect(session.ExitCode()).To(Equal(0)) + Expect(session).Should(Exit(0)) session = podmanTest.Podman([]string{"version", "--format", "{{ .Version }}"}) session.WaitWithDefaultTimeout() - Expect(session.ExitCode()).To(Equal(0)) + Expect(session).Should(Exit(0)) }) }) diff --git a/test/e2e/volume_create_test.go b/test/e2e/volume_create_test.go index 4cfc5bfc9..71023f9e2 100644 --- a/test/e2e/volume_create_test.go +++ b/test/e2e/volume_create_test.go @@ -17,7 +17,6 @@ var _ = Describe("Podman volume create", func() { ) BeforeEach(func() { - Skip(v2fail) tempdir, err = CreateTempDirInTempDir() if err != nil { os.Exit(1) diff --git a/test/e2e/volume_inspect_test.go b/test/e2e/volume_inspect_test.go index 1197fa552..5015e0535 100644 --- a/test/e2e/volume_inspect_test.go +++ b/test/e2e/volume_inspect_test.go @@ -17,7 +17,6 @@ var _ = Describe("Podman volume inspect", func() { ) BeforeEach(func() { - Skip(v2fail) tempdir, err = CreateTempDirInTempDir() if err != nil { os.Exit(1) diff --git a/test/e2e/volume_ls_test.go b/test/e2e/volume_ls_test.go index 4073df59d..7664e64bb 100644 --- a/test/e2e/volume_ls_test.go +++ b/test/e2e/volume_ls_test.go @@ -16,7 +16,6 @@ var _ = Describe("Podman volume ls", func() { ) BeforeEach(func() { - Skip(v2fail) tempdir, err = CreateTempDirInTempDir() if err != nil { os.Exit(1) @@ -56,6 +55,7 @@ var _ = Describe("Podman volume ls", func() { }) It("podman ls volume with Go template", func() { + Skip(v2fail) session := podmanTest.Podman([]string{"volume", "create", "myvol"}) session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) diff --git a/test/e2e/volume_prune_test.go b/test/e2e/volume_prune_test.go index 137a2c41b..b9ea90568 100644 --- a/test/e2e/volume_prune_test.go +++ b/test/e2e/volume_prune_test.go @@ -18,7 +18,6 @@ var _ = Describe("Podman volume prune", func() { ) BeforeEach(func() { - Skip(v2fail) tempdir, err = CreateTempDirInTempDir() if err != nil { os.Exit(1) @@ -66,6 +65,7 @@ var _ = Describe("Podman volume prune", func() { }) It("podman system prune --volume", func() { + Skip(v2fail) session := podmanTest.Podman([]string{"volume", "create"}) session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) diff --git a/test/e2e/volume_rm_test.go b/test/e2e/volume_rm_test.go index e67cfcd11..6f2020828 100644 --- a/test/e2e/volume_rm_test.go +++ b/test/e2e/volume_rm_test.go @@ -16,7 +16,6 @@ var _ = Describe("Podman volume rm", func() { ) BeforeEach(func() { - Skip(v2fail) tempdir, err = CreateTempDirInTempDir() if err != nil { os.Exit(1) diff --git a/test/system/005-info.bats b/test/system/005-info.bats index 3c06103e8..c53ba8125 100644 --- a/test/system/005-info.bats +++ b/test/system/005-info.bats @@ -8,19 +8,19 @@ load helpers run_podman info expected_keys=" -buildahversion: *[0-9.]\\\+ +buildahVersion: *[0-9.]\\\+ conmon:\\\s\\\+package: distribution: -ociruntime:\\\s\\\+name: +ociRuntime:\\\s\\\+name: os: rootless: registries: store: -graphdrivername: -graphroot: -graphstatus: -imagestore:\\\s\\\+number: 1 -runroot: +graphDriverName: +graphRoot: +graphStatus: +imageStore:\\\s\\\+number: 1 +runRoot: " while read expect; do is "$output" ".*$expect" "output includes '$expect'" diff --git a/test/system/015-help.bats b/test/system/015-help.bats index fd4be87b2..6c3d617dc 100644 --- a/test/system/015-help.bats +++ b/test/system/015-help.bats @@ -55,11 +55,24 @@ function check_help() { # If usage has required arguments, try running without them if expr "$usage" : '.*\[flags\] [A-Z]' >/dev/null; then - if [ "$cmd" != "stats"]; then - dprint "podman $@ $cmd (without required args)" - run_podman 125 "$@" $cmd - is "$output" "Error:" + # Exceptions: these commands don't work rootless + if is_rootless; then + # "pause is not supported for rootless containers" + if [ "$cmd" = "pause" -o "$cmd" = "unpause" ]; then + continue + fi + # "network rm" too + if [ "$@" = "network" -a "$cmd" = "rm" ]; then + continue + fi fi + + # The </dev/null protects us from 'podman login' which will + # try to read username/password from stdin. + dprint "podman $@ $cmd (without required args)" + run_podman 125 "$@" $cmd </dev/null + is "$output" "Error:.* \(require\|specif\|must\|provide\|need\|choose\|accepts\)" \ + "'podman $@ $cmd' without required arg" fi count=$(expr $count + 1) diff --git a/test/system/150-login.bats b/test/system/150-login.bats index e33217e14..e3e2b7aca 100644 --- a/test/system/150-login.bats +++ b/test/system/150-login.bats @@ -165,6 +165,7 @@ function setup() { # Some push tests @test "podman push fail" { + # Create an invalid authfile authfile=${PODMAN_LOGIN_WORKDIR}/auth-$(random_string 10).json rm -f $authfile @@ -197,6 +198,7 @@ EOF # # https://github.com/containers/skopeo/issues/651 # + run_podman pull busybox # Preserve its ID for later comparison against push/pulled image diff --git a/test/system/250-generate-systemd.bats b/test/system/250-generate-systemd.bats index 80199af5f..6155d6ace 100644 --- a/test/system/250-generate-systemd.bats +++ b/test/system/250-generate-systemd.bats @@ -10,6 +10,8 @@ SERVICE_NAME="podman_test_$(random_string)" UNIT_DIR="$HOME/.config/systemd/user" UNIT_FILE="$UNIT_DIR/$SERVICE_NAME.service" +# FIXME: the must run as root (because of CI). It's also broken... + function setup() { skip_if_not_systemd skip_if_remote diff --git a/test/utils/utils.go b/test/utils/utils.go index 6ab8604a4..0131e023d 100644 --- a/test/utils/utils.go +++ b/test/utils/utils.go @@ -206,7 +206,7 @@ func WaitContainerReady(p PodmanTestCommon, id string, expStr string, timeout in // OutputToString formats session output to string func (s *PodmanSession) OutputToString() string { - fields := strings.Fields(fmt.Sprintf("%s", s.Out.Contents())) + fields := strings.Fields(string(s.Out.Contents())) return strings.Join(fields, " ") } diff --git a/vendor/github.com/containers/psgo/CODE-OF-CONDUCT.md b/vendor/github.com/containers/psgo/CODE-OF-CONDUCT.md new file mode 100644 index 000000000..1081553f8 --- /dev/null +++ b/vendor/github.com/containers/psgo/CODE-OF-CONDUCT.md @@ -0,0 +1,3 @@ +## The psgo Project Community Code of Conduct + +The psgo project follows the [Containers Community Code of Conduct](https://github.com/containers/common/blob/master/CODE-OF-CONDUCT.md). diff --git a/vendor/github.com/containers/psgo/Makefile b/vendor/github.com/containers/psgo/Makefile index 361820784..831dfa31f 100644 --- a/vendor/github.com/containers/psgo/Makefile +++ b/vendor/github.com/containers/psgo/Makefile @@ -16,6 +16,8 @@ ifeq ($(shell go help mod >/dev/null 2>&1 && echo true), true) GO_BUILD=GO111MODULE=on $(GO) build -mod=vendor endif +GOBIN ?= $(GO)/bin + all: validate build .PHONY: build @@ -34,12 +36,7 @@ vendor: .PHONY: validate validate: .install.lint - @which gofmt >/dev/null 2>/dev/null || (echo "ERROR: gofmt not found." && false) - test -z "$$(gofmt -s -l . | grep -vE 'vendor/' | tee /dev/stderr)" - @which golangci-lint >/dev/null 2>/dev/null|| (echo "ERROR: golangci-lint not found." && false) - test -z "$$(golangci-lint run)" - @go doc cmd/vet >/dev/null 2>/dev/null|| (echo "ERROR: go vet not found." && false) - test -z "$$($(GO) vet $$($(GO) list $(PROJECT)/...) 2>&1 | tee /dev/stderr)" + $(GOBIN)/golangci-lint run .PHONY: test test: test-unit test-integration @@ -59,8 +56,7 @@ install: .PHONY: .install.lint .install.lint: - # Workaround for https://github.com/golangci/golangci-lint/issues/523 - go get -u github.com/golangci/golangci-lint/cmd/golangci-lint + VERSION=1.24.0 GOBIN=$(GOBIN) sh ./hack/install_golangci.sh .PHONY: uninstall uninstall: diff --git a/vendor/github.com/containers/psgo/go.mod b/vendor/github.com/containers/psgo/go.mod index d9d54c5f7..5f3341aa9 100644 --- a/vendor/github.com/containers/psgo/go.mod +++ b/vendor/github.com/containers/psgo/go.mod @@ -6,6 +6,6 @@ require ( github.com/opencontainers/runc v0.0.0-20190425234816-dae70e8efea4 github.com/pkg/errors v0.0.0-20190227000051-27936f6d90f9 github.com/sirupsen/logrus v0.0.0-20190403091019-9b3cdde74fbe - github.com/stretchr/testify v1.4.0 + github.com/stretchr/testify v1.5.1 golang.org/x/sys v0.0.0-20190425145619-16072639606e ) diff --git a/vendor/github.com/containers/psgo/go.sum b/vendor/github.com/containers/psgo/go.sum index bbdd99730..781b26f2b 100644 --- a/vendor/github.com/containers/psgo/go.sum +++ b/vendor/github.com/containers/psgo/go.sum @@ -16,8 +16,8 @@ github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= -github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190425145619-16072639606e h1:4ktJgTV34+N3qOZUc5fAaG3Pb11qzMm3PkAoTAgUZ2I= golang.org/x/sys v0.0.0-20190425145619-16072639606e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= diff --git a/vendor/github.com/containers/psgo/internal/proc/stat.go b/vendor/github.com/containers/psgo/internal/proc/stat.go index 866a5cdda..e3286704c 100644 --- a/vendor/github.com/containers/psgo/internal/proc/stat.go +++ b/vendor/github.com/containers/psgo/internal/proc/stat.go @@ -15,6 +15,7 @@ package proc import ( + "errors" "fmt" "io/ioutil" "strings" @@ -112,21 +113,31 @@ type Stat struct { } // readStat is used for mocking in unit tests. -var readStat = func(path string) ([]string, error) { - data, err := ioutil.ReadFile(path) +var readStat = func(path string) (string, error) { + rawData, err := ioutil.ReadFile(path) if err != nil { - return nil, err + return "", err } - return strings.Fields(string(data)), nil + return string(rawData), nil } // ParseStat parses the /proc/$pid/stat file and returns a Stat. func ParseStat(pid string) (*Stat, error) { - fields, err := readStat(fmt.Sprintf("/proc/%s/stat", pid)) + data, err := readStat(fmt.Sprintf("/proc/%s/stat", pid)) if err != nil { return nil, err } + firstParen := strings.IndexByte(data, '(') + lastParen := strings.LastIndexByte(data, ')') + if firstParen == -1 || lastParen == -1 { + return nil, errors.New("invalid format in stat") + } + pidstr := data[0 : firstParen-1] + comm := data[firstParen+1 : lastParen] + rest := strings.Fields(data[lastParen+1:]) + fields := append([]string{pidstr, comm}, rest...) + fieldAt := func(i int) string { return fields[i-1] } diff --git a/vendor/github.com/containers/psgo/internal/process/process.go b/vendor/github.com/containers/psgo/internal/process/process.go index a936cc4ef..b46a39f46 100644 --- a/vendor/github.com/containers/psgo/internal/process/process.go +++ b/vendor/github.com/containers/psgo/internal/process/process.go @@ -192,7 +192,7 @@ func (p *Process) ElapsedTime() (time.Duration, error) { if err != nil { return 0, err } - return (time.Now()).Sub(startTime), nil + return time.Since(startTime), nil } // StarTime returns the time.Time when process p was started. diff --git a/vendor/github.com/containers/psgo/psgo.go b/vendor/github.com/containers/psgo/psgo.go index 30b8b74ce..57132c94e 100644 --- a/vendor/github.com/containers/psgo/psgo.go +++ b/vendor/github.com/containers/psgo/psgo.go @@ -306,6 +306,11 @@ var ( procFn: processHGROUP, }, { + normal: "rss", + header: "RSS", + procFn: processRSS, + }, + { normal: "state", header: "STATE", procFn: processState, @@ -663,12 +668,7 @@ func processARGS(p *process.Process, ctx *psContext) (string, error) { // processCOMM returns the command name (i.e., executable name) of process p. func processCOMM(p *process.Process, ctx *psContext) (string, error) { - // ps (1) returns "[$name]" if command/args are empty - if p.CmdLine[0] == "" { - return processName(p, ctx) - } - spl := strings.Split(p.CmdLine[0], "/") - return spl[len(spl)-1], nil + return p.Stat.Comm, nil } // processNICE returns the nice value of process p. @@ -867,6 +867,16 @@ func processHGROUP(p *process.Process, ctx *psContext) (string, error) { return "?", nil } +// processRSS returns the resident set size of process p in KiB (1024-byte +// units). +func processRSS(p *process.Process, ctx *psContext) (string, error) { + if p.Status.VMRSS == "" { + // probably a kernel thread + return "0", nil + } + return p.Status.VMRSS, nil +} + // processState returns the process state of process p. func processState(p *process.Process, ctx *psContext) (string, error) { return p.Status.State, nil diff --git a/vendor/modules.txt b/vendor/modules.txt index 0a6d8ccd5..c1d803f84 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -143,7 +143,7 @@ github.com/containers/ocicrypt/keywrap/pgp github.com/containers/ocicrypt/keywrap/pkcs7 github.com/containers/ocicrypt/spec github.com/containers/ocicrypt/utils -# github.com/containers/psgo v1.4.0 +# github.com/containers/psgo v1.5.0 github.com/containers/psgo github.com/containers/psgo/internal/capabilities github.com/containers/psgo/internal/cgroups |