diff options
122 files changed, 3171 insertions, 2474 deletions
diff --git a/.cirrus.yml b/.cirrus.yml index 7bc00dbb5..325176179 100644 --- a/.cirrus.yml +++ b/.cirrus.yml @@ -67,9 +67,9 @@ env: RHEL_BASE_IMAGE: "rhel-guest-image-7-6-210-x86-64-qcow2-1548099756" #### - #### Default to NOT running in rootless-testing mode + #### Default to NOT operating in any special-case testing mode #### - ROOTLESS_USER: "" + SPECIALMODE: "none" # don't do anything special #### #### Credentials and other secret-sauces, decrypted at runtime when authorized. @@ -121,6 +121,7 @@ gating_task: gate_script: # N/B: entrypoint.sh resets $GOSRC (same as make clean) + - '/usr/local/bin/entrypoint.sh install.tools' - '/usr/local/bin/entrypoint.sh validate' - '/usr/local/bin/entrypoint.sh lint' - '${CIRRUS_WORKING_DIR}/${SCRIPT_BASE}/test/test_dot_cirrus_yaml.py' @@ -179,9 +180,9 @@ build_each_commit_task: gce_instance: image_project: "libpod-218412" zone: "us-central1-a" # Required by Cirrus for the time being - cpu: 2 - memory: "4Gb" - disk: 40 + cpu: 8 + memory: "8Gb" + disk: 200 image_name: "${FEDORA_CACHE_IMAGE_NAME}" timeout_in: 30m @@ -257,8 +258,8 @@ testing_task: master_script: '$CIRRUS_WORKING_DIR/$SCRIPT_BASE/notice_master_failure.sh' -# This task executes tests as a regular user on a system -rootless_testing_task: +# This task executes tests under unique environments/conditions +special_testing_task: depends_on: - "gating" @@ -274,15 +275,14 @@ rootless_testing_task: image_name: "${FEDORA_CACHE_IMAGE_NAME}" env: - ROOTLESS_USER: "olympiclongjumpingwithjesus" + matrix: + SPECIALMODE: 'rootless' # See docs + SPECIALMODE: 'in_podman' # See docs timeout_in: 120m setup_environment_script: '$SCRIPT_BASE/setup_environment.sh' - rootless_test_script: >- - ssh $ROOTLESS_USER@localhost - -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -o CheckHostIP=no - $CIRRUS_WORKING_DIR/$SCRIPT_BASE/rootless_test.sh + integration_test_script: '$SCRIPT_BASE/integration_test.sh' on_failure: master_script: '$CIRRUS_WORKING_DIR/$SCRIPT_BASE/notice_master_failure.sh' diff --git a/.papr.yml b/.papr.yml deleted file mode 100644 index ed20c6039..000000000 --- a/.papr.yml +++ /dev/null @@ -1,97 +0,0 @@ -branches: - - master - - auto - - try - -host: - distro: fedora/28/atomic - specs: - ram: 8192 - cpus: 4 -required: true -timeout: 90m - -tests: - - rpm-ostree usroverlay && rpm -Uvh https://kojipkgs.fedoraproject.org//packages/podman/0.10.1/1.gite4a1553.fc28/x86_64/podman-0.10.1-1.gite4a1553.fc28.x86_64.rpm - - CONTAINER_RUNTIME="podman" sh .papr_prepare.sh - -artifacts: - - build.log - -context: "FAH28 - Containerized (Podman in Podman)" - ---- - -host: - distro: centos/7/atomic/smoketested - specs: - ram: 8192 - cpus: 4 -extra-repos: - - name: epel - metalink: https://mirrors.fedoraproject.org/metalink?repo=epel-7&arch=$basearch - gpgcheck: 0 - - name: cri-o - baseurl: https://cbs.centos.org/repos/virt7-container-common-candidate/$basearch/os - gpgcheck: 0 - -required: true - -timeout: 90m - -tests: - - CONTAINER_RUNTIME="docker" sh .papr_prepare.sh - -artifacts: - - build.log - -context: "CAH 7-smoketested - Containerized (Podman in Docker)" - -#--- -# -#host: -# distro: centos/7/cloud -# specs: -# ram: 8192 -# cpus: 4 -#extra-repos: -# - name: epel -# metalink: https://mirrors.fedoraproject.org/metalink?repo=epel-7&arch=$basearch -# gpgcheck: 0 -# - name: cri-o -# baseurl: https://cbs.centos.org/repos/virt7-container-common-candidate/$basearch/os -# gpgcheck: 0 -# -#packages: -# - btrfs-progs-devel -# - glib2-devel -# - glibc-devel -# - glibc-static -# - git -# - go-md2man -# - gpgme-devel -# - libassuan-devel -# - libgpg-error-devel -# - libseccomp-devel -# - libselinux-devel -# - ostree-devel -# - pkgconfig -# - make -# - nc -# - go-compilers-golang-compiler -# - podman -# -#required: true -# -#timeout: 90m -# -#tests: -# - sed 's/^expand-check.*/expand-check=0/g' -i /etc/selinux/semanage.conf -# - sh .papr.sh -b -i -t -# -#artifacts: -# - build.log -# -#context: "CentOS 7 Cloud" -# -#--- diff --git a/.papr_prepare.sh b/.papr_prepare.sh deleted file mode 100644 index b93f7b91f..000000000 --- a/.papr_prepare.sh +++ /dev/null @@ -1,22 +0,0 @@ -#!/bin/bash -set -xeuo pipefail - -DIST=${DIST:=Fedora} -CONTAINER_RUNTIME=${CONTAINER_RUNTIME:=docker} -IMAGE=fedorapodmanbuild -if [[ ${DIST} != "Fedora" ]]; then - IMAGE=centospodmanbuild -fi - -# Since CRIU 3.11 has been pushed to Fedora 28 the checkpoint/restore -# test cases are actually run. As CRIU uses iptables to lock and unlock -# the network during checkpoint and restore it needs the following two -# modules loaded. -modprobe ip6table_nat || : -modprobe iptable_nat || : - -# Build the test image -${CONTAINER_RUNTIME} build -t ${IMAGE} -f Dockerfile.${DIST} . 2>build.log - -# Run the tests -${CONTAINER_RUNTIME} run --rm --privileged --net=host -v $PWD:/go/src/github.com/containers/libpod:Z --workdir /go/src/github.com/containers/libpod -e CGROUP_MANAGER=cgroupfs -e STORAGE_OPTIONS="--storage-driver=vfs" -e CRIO_ROOT="/go/src/github.com/containers/libpod" -e PODMAN_BINARY="/usr/bin/podman" -e CONMON_BINARY="/usr/libexec/podman/conmon" -e DIST=$DIST -e CONTAINER_RUNTIME=$CONTAINER_RUNTIME $IMAGE sh ./.papr.sh -b -i -t @@ -5,6 +5,8 @@ in the [API.md](https://github.com/containers/libpod/blob/master/API.md) file in [func BuildImage(build: BuildInfo) MoreResponse](#BuildImage) +[func BuildImageHierarchyMap(name: string) string](#BuildImageHierarchyMap) + [func Commit(name: string, image_name: string, changes: []string, author: string, message: string, pause: bool, manifestType: string) string](#Commit) [func ContainerArtifacts(name: string, artifactName: string) string](#ContainerArtifacts) @@ -25,12 +27,16 @@ in the [API.md](https://github.com/containers/libpod/blob/master/API.md) file in [func CreateContainer(create: Create) string](#CreateContainer) +[func CreateFromCC(in: []string) string](#CreateFromCC) + [func CreatePod(create: PodCreate) string](#CreatePod) [func DeleteStoppedContainers() []string](#DeleteStoppedContainers) [func DeleteUnusedImages() []string](#DeleteUnusedImages) +[func Diff(name: string) DiffInfo](#Diff) + [func ExportContainer(name: string, path: string) string](#ExportContainer) [func ExportImage(name: string, destination: string, compress: bool, tags: []string) string](#ExportImage) @@ -55,6 +61,8 @@ in the [API.md](https://github.com/containers/libpod/blob/master/API.md) file in [func GetInfo() PodmanInfo](#GetInfo) +[func GetLayersMapWithImageInfo() string](#GetLayersMapWithImageInfo) + [func GetPod(name: string) ListPodData](#GetPod) [func GetPodStats(name: string) string, ContainerStats](#GetPodStats) @@ -171,14 +179,10 @@ in the [API.md](https://github.com/containers/libpod/blob/master/API.md) file in [type Create](#Create) -[type CreateResourceConfig](#CreateResourceConfig) +[type DiffInfo](#DiffInfo) [type Event](#Event) -[type IDMap](#IDMap) - -[type IDMappingOptions](#IDMappingOptions) - [type Image](#Image) [type ImageHistory](#ImageHistory) @@ -255,6 +259,11 @@ method BuildImage(build: [BuildInfo](#BuildInfo)) [MoreResponse](#MoreResponse)< BuildImage takes a [BuildInfo](#BuildInfo) structure and builds an image. At a minimum, you must provide the 'dockerfile' and 'tags' options in the BuildInfo structure. It will return a [MoreResponse](#MoreResponse) structure that contains the build logs and resulting image ID. +### <a name="BuildImageHierarchyMap"></a>func BuildImageHierarchyMap +<div style="background-color: #E8E8E8; padding: 15px; margin: 10px; border-radius: 10px;"> + +method BuildImageHierarchyMap(name: [string](https://godoc.org/builtin#string)) [string](https://godoc.org/builtin#string)</div> +BuildImageHierarchyMap is for the development of Podman and should not be used. ### <a name="Commit"></a>func Commit <div style="background-color: #E8E8E8; padding: 15px; margin: 10px; border-radius: 10px;"> @@ -325,16 +334,12 @@ development of Podman only and generally should not be used. <div style="background-color: #E8E8E8; padding: 15px; margin: 10px; border-radius: 10px;"> method CreateContainer(create: [Create](#Create)) [string](https://godoc.org/builtin#string)</div> -CreateContainer creates a new container from an image. It uses a [Create](#Create) type for input. The minimum -input required for CreateContainer is an image name. If the image name is not found, an [ImageNotFound](#ImageNotFound) -error will be returned. Otherwise, the ID of the newly created container will be returned. -#### Example -~~~ -$ varlink call unix:/run/podman/io.podman/io.podman.CreateContainer '{"create": {"image": "alpine"}}' -{ - "container": "8759dafbc0a4dc3bcfb57eeb72e4331eb73c5cc09ab968e65ce45b9ad5c4b6bb" -} -~~~ +CreateContainer creates a new container from an image. It uses a [Create](#Create) type for input. +### <a name="CreateFromCC"></a>func CreateFromCC +<div style="background-color: #E8E8E8; padding: 15px; margin: 10px; border-radius: 10px;"> + +method CreateFromCC(in: [[]string](#[]string)) [string](https://godoc.org/builtin#string)</div> +This call is for the development of Podman only and should not be used. ### <a name="CreatePod"></a>func CreatePod <div style="background-color: #E8E8E8; padding: 15px; margin: 10px; border-radius: 10px;"> @@ -388,6 +393,11 @@ $ varlink call -m unix:/run/podman/io.podman/io.podman.DeleteUnusedImages ] } ~~~ +### <a name="Diff"></a>func Diff +<div style="background-color: #E8E8E8; padding: 15px; margin: 10px; border-radius: 10px;"> + +method Diff(name: [string](https://godoc.org/builtin#string)) [DiffInfo](#DiffInfo)</div> +Diff returns a diff between libpod objects ### <a name="ExportContainer"></a>func ExportContainer <div style="background-color: #E8E8E8; padding: 15px; margin: 10px; border-radius: 10px;"> @@ -511,6 +521,11 @@ If the image caGetImage returns be found, [ImageNotFound](#ImageNotFound) will b method GetInfo() [PodmanInfo](#PodmanInfo)</div> GetInfo returns a [PodmanInfo](#PodmanInfo) struct that describes podman and its host such as storage stats, build information of Podman, and system-wide registries. +### <a name="GetLayersMapWithImageInfo"></a>func GetLayersMapWithImageInfo +<div style="background-color: #E8E8E8; padding: 15px; margin: 10px; border-radius: 10px;"> + +method GetLayersMapWithImageInfo() [string](https://godoc.org/builtin#string)</div> +GetLayersMapWithImageInfo is for the development of Podman and should not be used. ### <a name="GetPod"></a>func GetPod <div style="background-color: #E8E8E8; padding: 15px; margin: 10px; border-radius: 10px;"> @@ -1249,223 +1264,221 @@ block_input [int](https://godoc.org/builtin#int) pids [int](https://godoc.org/builtin#int) ### <a name="Create"></a>type Create -Create is an input structure for creating containers. It closely resembles the -CreateConfig structure in libpod/pkg/spec. +Create is an input structure for creating containers. args [[]string](#[]string) -cap_add [[]string](#[]string) +addHost [](#) -cap_drop [[]string](#[]string) +annotation [](#) -conmon_pidfile [string](https://godoc.org/builtin#string) +attach [](#) -cgroup_parent [string](https://godoc.org/builtin#string) +blkioWeight [](#) -command [[]string](#[]string) +blkioWeightDevice [](#) -detach [bool](https://godoc.org/builtin#bool) +capAdd [](#) -devices [[]string](#[]string) +capDrop [](#) -dns_opt [[]string](#[]string) +cgroupParent [](#) -dns_search [[]string](#[]string) +cidFile [](#) -dns_servers [[]string](#[]string) +conmonPidfile [](#) -entrypoint [[]string](#[]string) +command [](#) -env [map[string]](#map[string]) +cpuPeriod [](#) -exposed_ports [[]string](#[]string) +cpuQuota [](#) -gidmap [[]string](#[]string) +cpuRtPeriod [](#) -group_add [[]string](#[]string) +cpuRtRuntime [](#) -host_add [[]string](#[]string) +cpuShares [](#) -hostname [string](https://godoc.org/builtin#string) +cpus [](#) -image [string](https://godoc.org/builtin#string) +cpuSetCpus [](#) -image_id [string](https://godoc.org/builtin#string) +cpuSetMems [](#) -init [bool](https://godoc.org/builtin#bool) +detach [](#) -init_path [string](https://godoc.org/builtin#string) +detachKeys [](#) -builtin_imgvolumes [[]string](#[]string) +device [](#) -id_mappings [IDMappingOptions](#IDMappingOptions) +deviceReadBps [](#) -image_volume_type [string](https://godoc.org/builtin#string) +deviceReadIops [](#) -interactive [bool](https://godoc.org/builtin#bool) +deviceWriteBps [](#) -ipc_mode [string](https://godoc.org/builtin#string) +deviceWriteIops [](#) -labels [map[string]](#map[string]) +dns [](#) -log_driver [string](https://godoc.org/builtin#string) +dnsOpt [](#) -log_driver_opt [[]string](#[]string) +dnsSearch [](#) -name [string](https://godoc.org/builtin#string) +dnsServers [](#) -net_mode [string](https://godoc.org/builtin#string) +entrypoint [](#) -network [string](https://godoc.org/builtin#string) +env [](#) -pid_mode [string](https://godoc.org/builtin#string) +envFile [](#) -pod [string](https://godoc.org/builtin#string) +expose [](#) -privileged [bool](https://godoc.org/builtin#bool) +gidmap [](#) -publish [[]string](#[]string) +groupadd [](#) -publish_all [bool](https://godoc.org/builtin#bool) +healthcheckCommand [](#) -quiet [bool](https://godoc.org/builtin#bool) +healthcheckInterval [](#) -readonly_rootfs [bool](https://godoc.org/builtin#bool) +healthcheckRetries [](#) -resources [CreateResourceConfig](#CreateResourceConfig) +healthcheckStartPeriod [](#) -rm [bool](https://godoc.org/builtin#bool) +healthcheckTimeout [](#) -shm_dir [string](https://godoc.org/builtin#string) +hostname [](#) -stop_signal [int](https://godoc.org/builtin#int) +imageVolume [](#) -stop_timeout [int](https://godoc.org/builtin#int) +init [](#) -subuidmap [string](https://godoc.org/builtin#string) +initPath [](#) -subgidmap [string](https://godoc.org/builtin#string) +interactive [](#) -subuidname [string](https://godoc.org/builtin#string) +ip [](#) -subgidname [string](https://godoc.org/builtin#string) +ipc [](#) -sys_ctl [map[string]](#map[string]) +kernelMemory [](#) -tmpfs [[]string](#[]string) +label [](#) -tty [bool](https://godoc.org/builtin#bool) +labelFile [](#) -uidmap [[]string](#[]string) +logDriver [](#) -userns_mode [string](https://godoc.org/builtin#string) +logOpt [](#) -user [string](https://godoc.org/builtin#string) +macAddress [](#) -uts_mode [string](https://godoc.org/builtin#string) +memory [](#) -volumes [[]string](#[]string) +memoryReservation [](#) -work_dir [string](https://godoc.org/builtin#string) +memorySwap [](#) -mount_label [string](https://godoc.org/builtin#string) +memorySwappiness [](#) -process_label [string](https://godoc.org/builtin#string) +name [](#) -no_new_privs [bool](https://godoc.org/builtin#bool) +net [](#) -apparmor_profile [string](https://godoc.org/builtin#string) +network [](#) -seccomp_profile_path [string](https://godoc.org/builtin#string) +noHosts [](#) -security_opts [[]string](#[]string) -### <a name="CreateResourceConfig"></a>type CreateResourceConfig +oomKillDisable [](#) -CreateResourceConfig is an input structure used to describe host attributes during -container creation. It is only valid inside a [Create](#Create) type. +oomScoreAdj [](#) -blkio_weight [int](https://godoc.org/builtin#int) +pid [](#) -blkio_weight_device [[]string](#[]string) +pidsLimit [](#) -cpu_period [int](https://godoc.org/builtin#int) +pod [](#) -cpu_quota [int](https://godoc.org/builtin#int) +privileged [](#) -cpu_rt_period [int](https://godoc.org/builtin#int) +publish [](#) -cpu_rt_runtime [int](https://godoc.org/builtin#int) +publishAll [](#) -cpu_shares [int](https://godoc.org/builtin#int) +quiet [](#) -cpus [float](https://golang.org/src/builtin/builtin.go#L58) +readonly [](#) -cpuset_cpus [string](https://godoc.org/builtin#string) +restart [](#) -cpuset_mems [string](https://godoc.org/builtin#string) +rm [](#) -device_read_bps [[]string](#[]string) +rootfs [](#) -device_read_iops [[]string](#[]string) +securityOpt [](#) -device_write_bps [[]string](#[]string) +shmSize [](#) -device_write_iops [[]string](#[]string) +stopSignal [](#) -disable_oomkiller [bool](https://godoc.org/builtin#bool) +stopTimeout [](#) -kernel_memory [int](https://godoc.org/builtin#int) +storageOpt [](#) -memory [int](https://godoc.org/builtin#int) +subuidname [](#) -memory_reservation [int](https://godoc.org/builtin#int) +subgidname [](#) -memory_swap [int](https://godoc.org/builtin#int) +sysctl [](#) -memory_swappiness [int](https://godoc.org/builtin#int) +systemd [](#) -oom_score_adj [int](https://godoc.org/builtin#int) +tmpfs [](#) -pids_limit [int](https://godoc.org/builtin#int) +tty [](#) -shm_size [int](https://godoc.org/builtin#int) +uidmap [](#) -ulimit [[]string](#[]string) -### <a name="Event"></a>type Event +ulimit [](#) -Event describes a libpod struct +user [](#) -id [string](https://godoc.org/builtin#string) +userns [](#) -image [string](https://godoc.org/builtin#string) +uts [](#) -name [string](https://godoc.org/builtin#string) +mount [](#) -status [string](https://godoc.org/builtin#string) +volume [](#) -time [string](https://godoc.org/builtin#string) +volumesFrom [](#) -type [string](https://godoc.org/builtin#string) -### <a name="IDMap"></a>type IDMap +workDir [](#) +### <a name="DiffInfo"></a>type DiffInfo -IDMap is used to describe user name spaces during container creation -container_id [int](https://godoc.org/builtin#int) -host_id [int](https://godoc.org/builtin#int) +path [string](https://godoc.org/builtin#string) -size [int](https://godoc.org/builtin#int) -### <a name="IDMappingOptions"></a>type IDMappingOptions +changeType [string](https://godoc.org/builtin#string) +### <a name="Event"></a>type Event + +Event describes a libpod struct + +id [string](https://godoc.org/builtin#string) -IDMappingOptions is an input structure used to described ids during container creation. +image [string](https://godoc.org/builtin#string) -host_uid_mapping [bool](https://godoc.org/builtin#bool) +name [string](https://godoc.org/builtin#string) -host_gid_mapping [bool](https://godoc.org/builtin#bool) +status [string](https://godoc.org/builtin#string) -uid_map [IDMap](#IDMap) +time [string](https://godoc.org/builtin#string) -gid_map [IDMap](#IDMap) +type [string](https://godoc.org/builtin#string) ### <a name="Image"></a>type Image @@ -1491,6 +1504,8 @@ containers [int](https://godoc.org/builtin#int) labels [map[string]](#map[string]) isParent [bool](https://godoc.org/builtin#bool) + +topLayer [string](https://godoc.org/builtin#string) ### <a name="ImageHistory"></a>type ImageHistory ImageHistory describes the returned structure from ImageHistory. diff --git a/Dockerfile b/Dockerfile index 6d44b963f..83cd3fccd 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,4 @@ -FROM golang:1.11 - -RUN echo 'deb http://httpredir.debian.org/debian jessie-backports main' > /etc/apt/sources.list.d/backports.list +FROM golang:1.12 RUN apt-get update && apt-get install -y \ apparmor \ @@ -23,6 +21,8 @@ RUN apt-get update && apt-get install -y \ libostree-dev \ libprotobuf-dev \ libprotobuf-c0-dev \ + libseccomp2 \ + libseccomp-dev \ libtool \ libudev-dev \ protobuf-c-compiler \ @@ -38,20 +38,11 @@ RUN apt-get update && apt-get install -y \ socat \ lsof \ xz-utils \ + unzip \ + python3-yaml \ --no-install-recommends \ && apt-get clean -ENV LIBSECCOMP_COMMIT release-2.3 -RUN set -x \ - && git clone https://github.com/seccomp/libseccomp "$GOPATH/src/github.com/seccomp/libseccomp" \ - && cd "$GOPATH/src/github.com/seccomp/libseccomp" \ - && git fetch origin --tags \ - && git checkout -q "$LIBSECCOMP_COMMIT" \ - && ./autogen.sh \ - && ./configure --prefix=/usr \ - && make all \ - && make install - # Install runc ENV RUNC_COMMIT 96ec2177ae841256168fcf76954f7177af9446eb RUN set -x \ diff --git a/Dockerfile.CentOS b/Dockerfile.centos index 605dc9df4..605dc9df4 100644 --- a/Dockerfile.CentOS +++ b/Dockerfile.centos diff --git a/Dockerfile.Fedora b/Dockerfile.fedora index e38e2e056..d4bcc11ea 100644 --- a/Dockerfile.Fedora +++ b/Dockerfile.fedora @@ -1,4 +1,4 @@ -FROM registry.fedoraproject.org/fedora:28 +FROM registry.fedoraproject.org/fedora:29 RUN dnf -y install btrfs-progs-devel \ atomic-registries \ @@ -1,6 +1,6 @@ GO ?= go DESTDIR ?= / -EPOCH_TEST_COMMIT ?= 1c45b42e9ff972d9645735118635e4186e6411f8 +EPOCH_TEST_COMMIT ?= 7b7397481960c85379d8eb1ed21e76da2ce8a4fc HEAD ?= HEAD CHANGELOG_BASE ?= HEAD~ CHANGELOG_TARGET ?= HEAD @@ -30,7 +30,6 @@ BASHINSTALLDIR=${PREFIX}/share/bash-completion/completions ZSHINSTALLDIR=${PREFIX}/share/zsh/site-functions SELINUXOPT ?= $(shell test -x /usr/sbin/selinuxenabled && selinuxenabled && echo -Z) -PACKAGES ?= $(shell $(GO) list -tags "${BUILDTAGS}" ./... | grep -v github.com/containers/libpod/vendor | grep -v e2e | grep -v system ) COMMIT_NO ?= $(shell git rev-parse HEAD 2> /dev/null || true) GIT_COMMIT ?= $(if $(shell git status --porcelain --untracked-files=no),${COMMIT_NO}-dirty,${COMMIT_NO}) @@ -172,7 +171,13 @@ testunit: libpodimage ## Run unittest on the built image ${CONTAINER_RUNTIME} run -e STORAGE_OPTIONS="--storage-driver=vfs" -e TESTFLAGS -e CGROUP_MANAGER=cgroupfs -e OCI_RUNTIME -e TRAVIS -t --privileged --rm -v ${CURDIR}:/go/src/${PROJECT} ${LIBPOD_IMAGE} make localunit localunit: test/goecho/goecho varlink_generate - $(GO) test -tags "$(BUILDTAGS)" -cover $(PACKAGES) + ginkgo \ + -r \ + --skipPackage test/e2e,pkg/apparmor \ + --cover \ + --covermode atomic \ + --tags "$(BUILDTAGS)" \ + --succinct $(MAKE) -C contrib/cirrus/packer test ginkgo: @@ -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.0.0](https://github.com/containers/libpod/releases/latest) +* [Latest Version: 1.2.0](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) ## Overview and scope diff --git a/changelog.txt b/changelog.txt index 7db579f3a..92a17f8d0 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,3 +1,208 @@ +- Changelog for v1.2.0 (2019-03-30) + * Update release notes for v1.2.0 + * Remove wait event + * Vendor Buildah 1.7.2 + * Add locking to ensure events file is concurrency-safe + * Alter container/pod/volume name regexp to match Docker + * test: test that an unprivileged user cannot access the storage + * userns: do not use an intermediate mount namespace + * volumes: push the chown logic to runtime_volume_linux.go + * Cleanup image2 -> image for imports + * Set blob cache directory based on GraphDriver + * utils: call GetRootlessRuntimeDir once + * rootless: set sticky bit on rundir + * oci: drop reference to runc + * Fix lint + * Ensure that we make a netns for CNI non-default nets + * rootless: change env prefix + * vendor buildah, image, storage, cni + * Default to SELinux private label for play kube mounts + * Add watch mode to podman ps + * Add all container status states to the podman-ps manual page. + * fix bug `system df` add blank space to the output + * fix bug remote-podman images --digests + * Use spaces instead of tab for JSON marshal indent + * Fix gofmt + * Remove ulele/deepcopier in favor of JSON deep copy + * doc: add note that pod publish ports are static once defined + * Sigh; disable pod-top test, it's unreliable (#2780) + * Resolve review comments + * Add a test that --add-host conflicts with --no-hosts + * Add manpages and completions for dns=none and no-hosts + * Add --no-hosts flag to disable management of /etc/hosts + * Add for --dns=none to disable creation of resolv.conf + * Add support to disable creation of network config files + * system df: reject invalid arguments + * rootless: fix regression when using exec on old containers + * Touchup commands.md + * size is optional for container inspection + * Add three test cases for podman attach test + * system df to show podman disk usage + * Add "died" event + * docs/podman-pod-create.1.md: add example with port mapping + * podman health check phase3 + * userns: use the intermediate mountns for volumes + * volume: create new volumes with right ownership + * utils: drop dead function + * troubleshooting: explain setup user: invalid argument + * Cirrus: Verify manpages for all subcommands exist + * Make "stopped" a valid state that maps to "exited" + * fix Bug 1688041-podman image save removes existing image + * podman: do not split --env on comma + * Need to pass the true paramater with --syslog in cobra + * Fix man page to mention race condition + * docs/podman-run.1.md: remove extra whitespace in --read-only + * man pages - consistency fixes + * Add new key and never-expiring test certificate + * Cirrus: Run vendor check in parallel + * Cirrus: Various fixes for rootless testing + * ps: fix segfault if the store is not initialized + * tests: re-enable some tests for rootless mode + * rootless: implement pod restart + * rootless: reimplement restart with rootless.Argument() + * test: fix SkipIfRootless() helper + * rootless, rm: fix retcode when the container is not found + * rootless: fix ps command + * rootless: fix pod kill + * Enable rootless integration tests + * BATS: new tests, and improvements to existing ones + * podman umount: error out if called with no args + * Export ConmonPidFile in 'podman inspect' for containers + * support GO template {{ json . }} + * Incorporate user from image inspect data in play kube + * Cirrus: Disable master-success IRC notices + * Cleanup messages on podman load + * Cirrus: Update VM Cache images + * podman logs on created container should exit + * Fix cut and paste errors in podman-pod-inspect + * rootless: fix pod top + * pod: fix segfault when there are no arguments to inspect + * output of port grouping in ps command added as example + * utils: split generation and writing of storage.conf + * Cirrus: Fix post-merge failure notice + * utils: avoid too long tmp directory + * podman image tree: fix usage message + * Cirrus: Notify on IRC if post-merge testing fails + * rootless: change default path for conmon.pid + * Add CLI storage conf example to run manpage + * Integration test tweaks + * display logs for multiple containers at the same time + * Make 'podman rm' exit with 125 if it had a bogus & a running container + * rootless: write the custom config file before reload + * Add support for SCTP port forwarding + * Make sure buildin volumes have the same ownership and permissions as image + * rootless: do not override user settings + * runtime: refactor NewRuntime and NewRuntimeFromConfig + * events: use os.SEEK_END instead of its value + * container: check containerInfo.Config before accessing it + * rootless: use Geteuid instead of Getuid + * rootless: use /tmp/libpod-rundir-$EUID for fallback + * build: fix build DIR -t TAG + * testcase added for listing range of ports in ps command + * port grouping in ps command output + * Update pull and pull-always in bud man page + * cirrus: upgrade slirp4netns + * rootless: fix CI regression when using slirp4netns + * save-load-export: clear cli-parsing default + * Bump timeout on a podman info test to default + * Replace skopeo-containers with containers-common + * slirp4netns: use --disable-host-loopback + * slirp4netns: set mtu to 65520 + * Tree implementation for podman images + * Replace buildah with podman in build doc + * zsh completion + * Usage messages: deduplicate '(default true)' et al + * Corrected detach man pages and code comments + * Add --replace flag to "podman container runlabel" + * rm: fix cleanup race + * Add gating tasks + * Add 'podman events' to podman(1) + * Vendor docker/docker, fsouza and more #2 + * Usability cleanup for 'inspect' + * Add event on container death + * Update vendor of Buildah and imagebuilder + * minor typo fix in 'podman top' usage + * healtcheck phase 2 + * Add event logging to libpod, even display to podman + * Fix SELinux on host shared systems in userns + * Fix broken link in io.podman.varlink + * move formats pkg to and vendor from buildah + * Ensure that tmpfs mounts do not have symlinks + * Update troubleshooting guide for Podman-in-Podman + * Buffer stdin to a file when importing "-" + * vendor psgo v1.2 + * preparation for remote-client create container + * Initialize field in InfoHost struct + * rootless: allow single mappings + * Remove --rm and --detach don't coexist note + * rootless: fix pod stop|rm if uid in the container != 0 + * rootless: fix rm when uid in the container != 0 + * rootless: disable pod stats + * rootless: do not create automatically a userns for pod kill + * rootless: support a custom arg to the new process + * slirp4netns: add builtin DNS server to resolv.conf + * errors: fix error cause comparison + * libpod: allow to configure path to the network-cmd binary + * build: honor --net + * pull: promote debug statement to error + * Fix generation of infra container command + * Remove an unused if statement I added + * Don't delete another container's resolv and hosts files + * Fix a potential segfault during infra container create + * We don't use crio-umount.conf + * Move secrets package to buildah + * Add troublshoot information about SELinux labeling of containers/storage + * test docs fixups + * Default to image entrypoint for infra container + * ginkgo status improvements + * rootless: propagate errors from info + * podman play kube defaults + * container runlabel respect $PWD + * Remove 'podman ps' restarting filter and fix stopped + * label parsing in non-quoted field + * More cleanup for failures on missing commands. + * add podman-healthcheck(1) to podman(1) + * Implement review feedback + * new system tests under BATS + * fix bug in podman images list all images with same name + * Fix help commands to show short and long description. + * implement showerror and accept HOST_PORT env which defaults to 8080 + * create: join also the mount ns of the dependency + * rootless: exec join the user+mount namespace + * oci: make explicit the extra files to the exec + * add test to cover networking + * tests to cover locks and parallel execution #2551 + * Yet another seemingly minor tweak to usage message + * Change LookupContainer logic to match Docker + * Implement podman-remote wait command and container subcommand + * Cirrus: Use imgts container to record metadata + * System-test: Documentation and TODO list + * podman-remote pod top|stats + * fix bug --device enable specifying directory as device + * add flag --extract tar file in podman cp + * Fix incorrect pod create failure + * libpod/container_internal: Split locale at the first dot, etc. + * Add volume mounting to podman play kube + * podman healthcheck run (phase 1) + * Append hosts to dependency container's /etc/hosts file + * rootless: fix clone syscall on s390 and cris archs + * Cirrus: Add dedicated rootless mode testing + * rootless: fill in correct storage conf default + * rm: set exit code to 1 if a specified container is not found + * Support filter image by reference to the repo name + * Bump gitvalidation epoch + * Bump to v1.2.0-dev + * Support podman-remote kill container(s) + * cirrus: Drop ginkgo, gomega, easyjson install + * Cirrus: Stop testing on RHEL + * Cirrus: Stop testing on RHEL + * Globally increase test timeout to 90-minutes + * cirrus: Drop ginkgo, gomega, easyjson install + * Cirrus: Add BATS package for all platforms + * Globally increase test timeout to 90-minutes + * exec: support --preserve-fds + * get_ci_vm.sh: Fix conflicting homedir files + - Changelog for v1.1.2 (2019-03-04) * Fix #2521 * Update release notes for v1.1.2 diff --git a/cmd/podman/attach.go b/cmd/podman/attach.go index 86e89cfd7..f326f53c3 100644 --- a/cmd/podman/attach.go +++ b/cmd/podman/attach.go @@ -6,6 +6,7 @@ import ( "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/cmd/podman/libpodruntime" "github.com/containers/libpod/libpod" + "github.com/containers/libpod/pkg/adapter" "github.com/pkg/errors" "github.com/spf13/cobra" ) @@ -78,7 +79,7 @@ func attachCmd(c *cliconfig.AttachValues) error { } // If the container is in a pod, also set to recursively start dependencies - if err := startAttachCtr(ctr, os.Stdout, os.Stderr, inputStream, c.DetachKeys, c.SigProxy, false, ctr.PodID() != ""); err != nil && errors.Cause(err) != libpod.ErrDetach { + if err := adapter.StartAttachCtr(getContext(), ctr, os.Stdout, os.Stderr, inputStream, c.DetachKeys, c.SigProxy, false, ctr.PodID() != ""); err != nil && errors.Cause(err) != libpod.ErrDetach { return errors.Wrapf(err, "error attaching to container %s", ctr.ID()) } diff --git a/cmd/podman/cliconfig/defaults.go b/cmd/podman/cliconfig/defaults.go new file mode 100644 index 000000000..d5dae0874 --- /dev/null +++ b/cmd/podman/cliconfig/defaults.go @@ -0,0 +1,21 @@ +package cliconfig + +const ( + // DefaultSystemD value + DefaultSystemD bool = true +) + +var ( + // DefaultHealthCheckInterval default value + DefaultHealthCheckInterval = "30s" + // DefaultHealthCheckRetries default value + DefaultHealthCheckRetries uint = 3 + // DefaultHealthCheckStartPeriod default value + DefaultHealthCheckStartPeriod = "0s" + // DefaultHealthCheckTimeout default value + DefaultHealthCheckTimeout = "30s" + // DefaultImageVolume default value + DefaultImageVolume = "bind" + // DefaultShmSize default value + DefaultShmSize = "65536k" +) diff --git a/cmd/podman/commands.go b/cmd/podman/commands.go index 875b2aec8..e9afcbc06 100644 --- a/cmd/podman/commands.go +++ b/cmd/podman/commands.go @@ -13,8 +13,6 @@ func getMainCommands() []*cobra.Command { rootCommands := []*cobra.Command{ _attachCommand, _commitCommand, - _createCommand, - _diffCommand, _execCommand, _generateCommand, _playCommand, @@ -27,7 +25,6 @@ func getMainCommands() []*cobra.Command { _refreshCommand, _restartCommand, _rmCommand, - _runCommand, _searchCommand, _startCommand, _statsCommand, @@ -57,8 +54,6 @@ func getContainerSubCommands() []*cobra.Command { _checkpointCommand, _cleanupCommand, _commitCommand, - _createCommand, - _diffCommand, _execCommand, _exportCommand, _killCommand, @@ -70,7 +65,6 @@ func getContainerSubCommands() []*cobra.Command { _restartCommand, _restoreCommand, _rmCommand, - _runCommand, _runlabelCommand, _startCommand, _statsCommand, diff --git a/cmd/podman/common.go b/cmd/podman/common.go index 10fed053e..ba4a3f519 100644 --- a/cmd/podman/common.go +++ b/cmd/podman/common.go @@ -294,19 +294,19 @@ func getCreateFlags(c *cliconfig.PodmanCommand) { "set a healthcheck command for the container ('none' disables the existing healthcheck)", ) createFlags.String( - "healthcheck-interval", "30s", + "healthcheck-interval", cliconfig.DefaultHealthCheckInterval, "set an interval for the healthchecks (a value of disable results in no automatic timer setup)", ) createFlags.Uint( - "healthcheck-retries", 3, + "healthcheck-retries", cliconfig.DefaultHealthCheckRetries, "the number of retries allowed before a healthcheck is considered to be unhealthy", ) createFlags.String( - "healthcheck-start-period", "0s", + "healthcheck-start-period", cliconfig.DefaultHealthCheckStartPeriod, "the initialization time needed for a container to bootstrap", ) createFlags.String( - "healthcheck-timeout", "30s", + "healthcheck-timeout", cliconfig.DefaultHealthCheckTimeout, "the maximum time allowed to complete the healthcheck before an interval is considered failed", ) createFlags.StringP( @@ -314,7 +314,7 @@ func getCreateFlags(c *cliconfig.PodmanCommand) { "Set container hostname", ) createFlags.String( - "image-volume", "bind", + "image-volume", cliconfig.DefaultImageVolume, "Tells podman how to handle the builtin image volumes. The options are: 'bind', 'tmpfs', or 'ignore'", ) createFlags.Bool( @@ -451,7 +451,7 @@ func getCreateFlags(c *cliconfig.PodmanCommand) { "Security Options (default [])", ) createFlags.String( - "shm-size", "65536k", + "shm-size", cliconfig.DefaultShmSize, "Size of `/dev/shm`. The format is `<number><unit>`", ) createFlags.String( @@ -480,7 +480,7 @@ func getCreateFlags(c *cliconfig.PodmanCommand) { "Sysctl options (default [])", ) createFlags.Bool( - "systemd", true, + "systemd", cliconfig.DefaultSystemD, "Run container in systemd mode if the command executable is systemd or init", ) createFlags.StringSlice( diff --git a/cmd/podman/container.go b/cmd/podman/container.go index 2e9cedbaa..d1c42f673 100644 --- a/cmd/podman/container.go +++ b/cmd/podman/container.go @@ -52,8 +52,11 @@ var ( containerCommands = []*cobra.Command{ _containerExistsCommand, _contInspectSubCommand, + _diffCommand, + _createCommand, _listSubCommand, _logsCommand, + _runCommand, } ) diff --git a/cmd/podman/cp.go b/cmd/podman/cp.go index 18fb2cb73..7dee37287 100644 --- a/cmd/podman/cp.go +++ b/cmd/podman/cp.go @@ -1,10 +1,9 @@ package main import ( - "io/ioutil" + "fmt" "os" "path/filepath" - "strconv" "strings" "github.com/containers/buildah/pkg/chrootuser" @@ -12,7 +11,6 @@ import ( "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/cmd/podman/libpodruntime" "github.com/containers/libpod/libpod" - "github.com/containers/libpod/pkg/rootless" "github.com/containers/storage" "github.com/containers/storage/pkg/archive" "github.com/containers/storage/pkg/chrootarchive" @@ -58,9 +56,6 @@ func cpCmd(c *cliconfig.CpValues) error { if len(args) != 2 { return errors.Errorf("you must provide a source path and a destination path") } - if os.Geteuid() != 0 { - rootless.SetSkipStorageSetup(true) - } runtime, err := libpodruntime.GetRuntime(&c.PodmanCommand) if err != nil { @@ -90,34 +85,6 @@ func copyBetweenHostAndContainer(runtime *libpod.Runtime, src string, dest strin ctr = destCtr } - if os.Geteuid() != 0 { - s, err := ctr.State() - if err != nil { - return err - } - var became bool - var ret int - if s == libpod.ContainerStateRunning || s == libpod.ContainerStatePaused { - data, err := ioutil.ReadFile(ctr.Config().ConmonPidFile) - if err != nil { - return errors.Wrapf(err, "cannot read conmon PID file %q", ctr.Config().ConmonPidFile) - } - conmonPid, err := strconv.Atoi(string(data)) - if err != nil { - return errors.Wrapf(err, "cannot parse PID %q", data) - } - became, ret, err = rootless.JoinDirectUserAndMountNS(uint(conmonPid)) - } else { - became, ret, err = rootless.BecomeRootInUserNS() - } - if err != nil { - return err - } - if became { - os.Exit(ret) - } - } - mountPoint, err := ctr.Mount() if err != nil { return err @@ -241,6 +208,11 @@ func copy(src, destPath, dest string, idMappingOpts storage.IDMappingOptions, ch if !srcfi.IsDir() && !strings.HasSuffix(dest, string(os.PathSeparator)) { destdir = filepath.Dir(destPath) } + _, err = os.Stat(destdir) + if err != nil && !os.IsNotExist(err) { + return errors.Wrapf(err, "error checking directory %q", destdir) + } + destDirIsExist := (err == nil) if err = os.MkdirAll(destdir, 0755); err != nil { return errors.Wrapf(err, "error creating directory %q", destdir) } @@ -253,6 +225,9 @@ func copy(src, destPath, dest string, idMappingOpts storage.IDMappingOptions, ch if srcfi.IsDir() { logrus.Debugf("copying %q to %q", srcPath+string(os.PathSeparator)+"*", dest+string(os.PathSeparator)+"*") + if destDirIsExist && !strings.HasSuffix(src, fmt.Sprintf("%s.", string(os.PathSeparator))) { + destPath = filepath.Join(destPath, filepath.Base(srcPath)) + } if err = copyWithTar(srcPath, destPath); err != nil { return errors.Wrapf(err, "error copying %q to %q", srcPath, dest) } diff --git a/cmd/podman/create.go b/cmd/podman/create.go index bceb606f6..1af3920dd 100644 --- a/cmd/podman/create.go +++ b/cmd/podman/create.go @@ -2,12 +2,9 @@ package main import ( "fmt" - "os" "github.com/containers/libpod/cmd/podman/cliconfig" - "github.com/containers/libpod/cmd/podman/libpodruntime" - "github.com/containers/libpod/cmd/podman/shared" - "github.com/containers/libpod/pkg/rootless" + "github.com/containers/libpod/pkg/adapter" "github.com/opentracing/opentracing-go" "github.com/pkg/errors" "github.com/spf13/cobra" @@ -54,22 +51,17 @@ func createCmd(c *cliconfig.CreateValues) error { return err } - if os.Geteuid() != 0 { - rootless.SetSkipStorageSetup(true) - } - - runtime, err := libpodruntime.GetRuntime(&c.PodmanCommand) + runtime, err := adapter.GetRuntime(&c.PodmanCommand) if err != nil { return errors.Wrapf(err, "error creating libpod runtime") } defer runtime.Shutdown(false) - ctr, _, err := shared.CreateContainer(getContext(), &c.PodmanCommand, runtime) + cid, err := runtime.CreateContainer(getContext(), c) if err != nil { return err } - - fmt.Printf("%s\n", ctr.ID()) + fmt.Printf("%s\n", cid) return nil } diff --git a/cmd/podman/diff.go b/cmd/podman/diff.go index e77e562d4..7f5a313f8 100644 --- a/cmd/podman/diff.go +++ b/cmd/podman/diff.go @@ -4,7 +4,7 @@ import ( "fmt" "github.com/containers/buildah/pkg/formats" "github.com/containers/libpod/cmd/podman/cliconfig" - "github.com/containers/libpod/cmd/podman/libpodruntime" + "github.com/containers/libpod/pkg/adapter" "github.com/containers/storage/pkg/archive" "github.com/pkg/errors" "github.com/spf13/cobra" @@ -86,18 +86,17 @@ func diffCmd(c *cliconfig.DiffValues) error { return errors.Errorf("container, image, or layer name must be specified: podman diff [options [...]] ID-NAME") } - runtime, err := libpodruntime.GetRuntime(&c.PodmanCommand) + runtime, err := adapter.GetRuntime(&c.PodmanCommand) if err != nil { return errors.Wrapf(err, "could not get runtime") } defer runtime.Shutdown(false) to := c.InputArgs[0] - changes, err := runtime.GetDiff("", to) + changes, err := runtime.Diff(c, to) if err != nil { return errors.Wrapf(err, "could not get changes for %q", to) } - diffOutput := []diffOutputParams{} outputFormat := c.Format diff --git a/cmd/podman/exec.go b/cmd/podman/exec.go index fc1c76e9f..f720a9aff 100644 --- a/cmd/podman/exec.go +++ b/cmd/podman/exec.go @@ -10,7 +10,6 @@ import ( "github.com/containers/libpod/cmd/podman/libpodruntime" "github.com/containers/libpod/cmd/podman/shared/parse" "github.com/containers/libpod/libpod" - "github.com/containers/libpod/pkg/rootless" "github.com/pkg/errors" "github.com/spf13/cobra" ) @@ -67,7 +66,6 @@ func execCmd(c *cliconfig.ExecValues) error { if c.Latest { argStart = 0 } - rootless.SetSkipStorageSetup(true) cmd := args[argStart:] runtime, err := libpodruntime.GetRuntime(&c.PodmanCommand) if err != nil { @@ -107,32 +105,6 @@ func execCmd(c *cliconfig.ExecValues) error { } - if os.Geteuid() != 0 { - var became bool - var ret int - - data, err := ioutil.ReadFile(ctr.Config().ConmonPidFile) - if err == nil { - conmonPid, err := strconv.Atoi(string(data)) - if err != nil { - return errors.Wrapf(err, "cannot parse PID %q", data) - } - became, ret, err = rootless.JoinDirectUserAndMountNS(uint(conmonPid)) - } else { - pid, err := ctr.PID() - if err != nil { - return err - } - became, ret, err = rootless.JoinNS(uint(pid), c.PreserveFDs) - } - if err != nil { - return err - } - if became { - os.Exit(ret) - } - } - // ENVIRONMENT VARIABLES env := map[string]string{} diff --git a/cmd/podman/export.go b/cmd/podman/export.go index 92633facd..db031aaf2 100644 --- a/cmd/podman/export.go +++ b/cmd/podman/export.go @@ -6,7 +6,6 @@ import ( "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/cmd/podman/shared/parse" "github.com/containers/libpod/pkg/adapter" - "github.com/containers/libpod/pkg/rootless" "github.com/pkg/errors" "github.com/sirupsen/logrus" "github.com/spf13/cobra" @@ -41,10 +40,6 @@ func init() { // exportCmd saves a container to a tarball on disk func exportCmd(c *cliconfig.ExportValues) error { - if os.Geteuid() != 0 { - rootless.SetSkipStorageSetup(true) - } - runtime, err := adapter.GetRuntime(&c.PodmanCommand) if err != nil { return errors.Wrapf(err, "could not get runtime") diff --git a/cmd/podman/generate_kube.go b/cmd/podman/generate_kube.go index 42cfba8d8..c58372899 100644 --- a/cmd/podman/generate_kube.go +++ b/cmd/podman/generate_kube.go @@ -5,7 +5,6 @@ import ( "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/cmd/podman/libpodruntime" "github.com/containers/libpod/libpod" - "github.com/containers/libpod/pkg/rootless" podmanVersion "github.com/containers/libpod/version" "github.com/ghodss/yaml" "github.com/pkg/errors" @@ -53,9 +52,6 @@ func generateKubeYAMLCmd(c *cliconfig.GenerateKubeValues) error { servicePorts []v1.ServicePort ) - if rootless.IsRootless() { - return errors.Wrapf(libpod.ErrNotImplemented, "rootless users") - } args := c.InputArgs if len(args) != 1 { return errors.Errorf("you must provide exactly one container|pod ID or name") diff --git a/cmd/podman/kill.go b/cmd/podman/kill.go index 2c1e13eaf..6019fbfec 100644 --- a/cmd/podman/kill.go +++ b/cmd/podman/kill.go @@ -4,12 +4,10 @@ import ( "fmt" "reflect" - "github.com/containers/libpod/pkg/adapter" - "github.com/opentracing/opentracing-go" - "github.com/containers/libpod/cmd/podman/cliconfig" - "github.com/containers/libpod/pkg/rootless" + "github.com/containers/libpod/pkg/adapter" "github.com/docker/docker/pkg/signal" + "github.com/opentracing/opentracing-go" "github.com/pkg/errors" "github.com/spf13/cobra" ) @@ -63,7 +61,6 @@ func killCmd(c *cliconfig.KillValues) error { return err } - rootless.SetSkipStorageSetup(true) runtime, err := adapter.GetRuntime(&c.PodmanCommand) if err != nil { return errors.Wrapf(err, "could not get runtime") diff --git a/cmd/podman/main.go b/cmd/podman/main.go index dd8b61408..b44cf9f0a 100644 --- a/cmd/podman/main.go +++ b/cmd/podman/main.go @@ -3,13 +3,16 @@ package main import ( "context" "io" + "io/ioutil" "log/syslog" "os" "runtime/pprof" + "strconv" "strings" "syscall" "github.com/containers/libpod/cmd/podman/cliconfig" + "github.com/containers/libpod/cmd/podman/libpodruntime" "github.com/containers/libpod/libpod" _ "github.com/containers/libpod/pkg/hooks/0.1.0" "github.com/containers/libpod/pkg/rootless" @@ -36,6 +39,8 @@ var ( // implemented. var mainCommands = []*cobra.Command{ _buildCommand, + _diffCommand, + _createCommand, _eventsCommand, _exportCommand, _historyCommand, @@ -50,6 +55,7 @@ var mainCommands = []*cobra.Command{ _pullCommand, _pushCommand, &_rmiCommand, + _runCommand, _saveCommand, _stopCommand, _tagCommand, @@ -59,36 +65,6 @@ var mainCommands = []*cobra.Command{ systemCommand.Command, } -var cmdsNotRequiringRootless = map[*cobra.Command]bool{ - _versionCommand: true, - _createCommand: true, - _execCommand: true, - _cpCommand: true, - _exportCommand: true, - //// `info` must be executed in an user namespace. - //// If this change, please also update libpod.refreshRootless() - _loginCommand: true, - _logoutCommand: true, - _mountCommand: true, - _killCommand: true, - _pauseCommand: true, - _podRmCommand: true, - _podKillCommand: true, - _podRestartCommand: true, - _podStatsCommand: true, - _podStopCommand: true, - _podTopCommand: true, - _restartCommand: true, - &_psCommand: true, - _rmCommand: true, - _runCommand: true, - _unpauseCommand: true, - _searchCommand: true, - _statsCommand: true, - _stopCommand: true, - _topCommand: true, -} - var rootCmd = &cobra.Command{ Use: "podman", Long: "manage pods and images", @@ -118,6 +94,9 @@ func init() { rootCmd.PersistentFlags().StringVar(&MainGlobalOpts.CniConfigDir, "cni-config-dir", "", "Path of the configuration directory for CNI networks") rootCmd.PersistentFlags().StringVar(&MainGlobalOpts.DefaultMountsFile, "default-mounts-file", "", "Path to default mounts file") rootCmd.PersistentFlags().MarkHidden("defaults-mount-file") + // Override default --help information of `--help` global flag + var dummyHelp bool + rootCmd.PersistentFlags().BoolVar(&dummyHelp, "help", false, "Help for podman") rootCmd.PersistentFlags().StringSliceVar(&MainGlobalOpts.HooksDir, "hooks-dir", []string{}, "Set the OCI hooks directory path (may be set multiple times)") rootCmd.PersistentFlags().StringVar(&MainGlobalOpts.LogLevel, "log-level", "error", "Log messages above specified level: debug, info, warn, error, fatal or panic") rootCmd.PersistentFlags().IntVar(&MainGlobalOpts.MaxWorks, "max-workers", 0, "The maximum number of workers for parallel operations") @@ -132,7 +111,10 @@ func init() { rootCmd.PersistentFlags().BoolVar(&MainGlobalOpts.Syslog, "syslog", false, "Output logging information to syslog as well as the console") rootCmd.PersistentFlags().StringVar(&MainGlobalOpts.TmpDir, "tmpdir", "", "Path to the tmp directory") - rootCmd.PersistentFlags().BoolVar(&MainGlobalOpts.Trace, "trace", false, "enable opentracing output") + rootCmd.PersistentFlags().BoolVar(&MainGlobalOpts.Trace, "trace", false, "Enable opentracing output") + // Override default --help information of `--version` global flag + var dummyVersion bool + rootCmd.PersistentFlags().BoolVar(&dummyVersion, "version", false, "Version for podman") rootCmd.AddCommand(mainCommands...) rootCmd.AddCommand(getMainCommands()...) @@ -146,18 +128,52 @@ func before(cmd *cobra.Command, args []string) error { logrus.Errorf(err.Error()) os.Exit(1) } - if rootless.IsRootless() { - notRequireRootless := cmdsNotRequiringRootless[cmd] - if !notRequireRootless && !strings.HasPrefix(cmd.Use, "help") { - became, ret, err := rootless.BecomeRootInUserNS() - if err != nil { - logrus.Errorf(err.Error()) - os.Exit(1) - } - if became { - os.Exit(ret) + if os.Geteuid() != 0 && cmd != _searchCommand && cmd != _versionCommand && !strings.HasPrefix(cmd.Use, "help") { + podmanCmd := cliconfig.PodmanCommand{ + cmd, + args, + MainGlobalOpts, + } + runtime, err := libpodruntime.GetRuntime(&podmanCmd) + if err != nil { + return errors.Wrapf(err, "could not get runtime") + } + defer runtime.Shutdown(false) + + ctrs, err := runtime.GetRunningContainers() + if err != nil { + logrus.Errorf(err.Error()) + os.Exit(1) + } + var became bool + var ret int + if len(ctrs) == 0 { + became, ret, err = rootless.BecomeRootInUserNS() + } else { + for _, ctr := range ctrs { + data, err := ioutil.ReadFile(ctr.Config().ConmonPidFile) + if err != nil { + logrus.Errorf(err.Error()) + os.Exit(1) + } + conmonPid, err := strconv.Atoi(string(data)) + if err != nil { + logrus.Errorf(err.Error()) + os.Exit(1) + } + became, ret, err = rootless.JoinUserAndMountNS(uint(conmonPid)) + if err == nil { + break + } } } + if err != nil { + logrus.Errorf(err.Error()) + os.Exit(1) + } + if became { + os.Exit(ret) + } } if MainGlobalOpts.Syslog { diff --git a/cmd/podman/mount.go b/cmd/podman/mount.go index 138548097..a70684a39 100644 --- a/cmd/podman/mount.go +++ b/cmd/podman/mount.go @@ -60,10 +60,6 @@ type jsonMountPoint struct { } func mountCmd(c *cliconfig.MountValues) error { - if os.Geteuid() != 0 { - rootless.SetSkipStorageSetup(true) - } - runtime, err := libpodruntime.GetRuntime(&c.PodmanCommand) if err != nil { return errors.Wrapf(err, "could not get runtime") diff --git a/cmd/podman/play_kube.go b/cmd/podman/play_kube.go index b468a7a89..cbe961279 100644 --- a/cmd/podman/play_kube.go +++ b/cmd/podman/play_kube.go @@ -15,7 +15,6 @@ import ( "github.com/containers/libpod/libpod" "github.com/containers/libpod/libpod/image" ns "github.com/containers/libpod/pkg/namespaces" - "github.com/containers/libpod/pkg/rootless" "github.com/containers/libpod/pkg/spec" "github.com/containers/storage" "github.com/cri-o/ocicni/pkg/ocicni" @@ -73,9 +72,6 @@ func playKubeYAMLCmd(c *cliconfig.KubePlayValues) error { ) ctx := getContext() - if rootless.IsRootless() { - return errors.Wrapf(libpod.ErrNotImplemented, "rootless users") - } args := c.InputArgs if len(args) > 1 { return errors.New("you can only play one kubernetes file at a time") @@ -243,6 +239,9 @@ func kubeContainerToCreateConfig(ctx context.Context, containerYAML v1.Container envs map[string]string ) + // The default for MemorySwappiness is -1, not 0 + containerConfig.Resources.MemorySwappiness = -1 + containerConfig.Runtime = runtime containerConfig.Image = containerYAML.Image containerConfig.ImageID = newImage.ID() @@ -270,7 +269,19 @@ func kubeContainerToCreateConfig(ctx context.Context, containerYAML v1.Container } } - containerConfig.Command = containerYAML.Command + containerConfig.Command = []string{} + if imageData != nil && imageData.Config != nil { + containerConfig.Command = append(containerConfig.Command, imageData.Config.Entrypoint...) + } + if len(containerConfig.Command) != 0 { + containerConfig.Command = append(containerConfig.Command, containerYAML.Command...) + } else if imageData != nil && imageData.Config != nil { + containerConfig.Command = append(containerConfig.Command, imageData.Config.Cmd...) + } + if imageData != nil && len(containerConfig.Command) == 0 { + return nil, errors.Errorf("No command specified in container YAML or as CMD or ENTRYPOINT in this image for %s", containerConfig.Name) + } + containerConfig.StopSignal = 15 // If the user does not pass in ID mappings, just set to basics diff --git a/cmd/podman/pod.go b/cmd/podman/pod.go index 9a9c7a702..2d9bca21d 100644 --- a/cmd/podman/pod.go +++ b/cmd/podman/pod.go @@ -1,12 +1,7 @@ package main import ( - "os" - "github.com/containers/libpod/cmd/podman/cliconfig" - "github.com/containers/libpod/pkg/adapter" - "github.com/containers/libpod/pkg/rootless" - "github.com/pkg/errors" "github.com/spf13/cobra" ) @@ -39,48 +34,6 @@ var podSubCommands = []*cobra.Command{ _podUnpauseCommand, } -func joinPodNS(runtime *adapter.LocalRuntime, all, latest bool, inputArgs []string) ([]string, bool, bool, error) { - if rootless.IsRootless() { - if os.Geteuid() == 0 { - return []string{rootless.Argument()}, false, false, nil - } else { - var err error - var pods []*adapter.Pod - if all { - pods, err = runtime.GetAllPods() - if err != nil { - return nil, false, false, errors.Wrapf(err, "unable to get pods") - } - } else if latest { - pod, err := runtime.GetLatestPod() - if err != nil { - return nil, false, false, errors.Wrapf(err, "unable to get latest pod") - } - pods = append(pods, pod) - } else { - for _, i := range inputArgs { - pod, err := runtime.LookupPod(i) - if err != nil { - return nil, false, false, errors.Wrapf(err, "unable to lookup pod %s", i) - } - pods = append(pods, pod) - } - } - for _, p := range pods { - _, ret, err := runtime.JoinOrCreateRootlessPod(p) - if err != nil { - return nil, false, false, err - } - if ret != 0 { - os.Exit(ret) - } - } - os.Exit(0) - } - } - return inputArgs, all, latest, nil -} - func init() { podCommand.AddCommand(podSubCommands...) podCommand.SetHelpTemplate(HelpTemplate()) diff --git a/cmd/podman/pod_kill.go b/cmd/podman/pod_kill.go index c538674a4..ebd7db762 100644 --- a/cmd/podman/pod_kill.go +++ b/cmd/podman/pod_kill.go @@ -6,7 +6,6 @@ import ( "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/pkg/adapter" - "github.com/containers/libpod/pkg/rootless" "github.com/docker/docker/pkg/signal" "github.com/pkg/errors" "github.com/sirupsen/logrus" @@ -49,7 +48,6 @@ func init() { // podKillCmd kills one or more pods with a signal func podKillCmd(c *cliconfig.PodKillValues) error { - rootless.SetSkipStorageSetup(true) runtime, err := adapter.GetRuntime(&c.PodmanCommand) if err != nil { return errors.Wrapf(err, "could not get runtime") diff --git a/cmd/podman/pod_restart.go b/cmd/podman/pod_restart.go index 9c8d28424..0765b98db 100644 --- a/cmd/podman/pod_restart.go +++ b/cmd/podman/pod_restart.go @@ -2,11 +2,9 @@ package main import ( "fmt" - "os" "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/pkg/adapter" - "github.com/containers/libpod/pkg/rootless" "github.com/pkg/errors" "github.com/sirupsen/logrus" "github.com/spf13/cobra" @@ -48,24 +46,12 @@ func init() { func podRestartCmd(c *cliconfig.PodRestartValues) error { var lastError error - if os.Geteuid() != 0 { - rootless.SetSkipStorageSetup(true) - } runtime, err := adapter.GetRuntime(&c.PodmanCommand) if err != nil { return errors.Wrapf(err, "could not get runtime") } defer runtime.Shutdown(false) - if rootless.IsRootless() { - var err error - - c.InputArgs, c.All, c.Latest, err = joinPodNS(runtime, c.All, c.Latest, c.InputArgs) - if err != nil { - return err - } - } - restartIDs, conErrors, restartErrors := runtime.RestartPods(getContext(), c) for _, p := range restartIDs { diff --git a/cmd/podman/pod_rm.go b/cmd/podman/pod_rm.go index 735676f8a..cd9f23fe1 100644 --- a/cmd/podman/pod_rm.go +++ b/cmd/podman/pod_rm.go @@ -2,11 +2,9 @@ package main import ( "fmt" - "os" "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/pkg/adapter" - "github.com/containers/libpod/pkg/rootless" "github.com/pkg/errors" "github.com/sirupsen/logrus" "github.com/spf13/cobra" @@ -48,23 +46,12 @@ func init() { // podRmCmd deletes pods func podRmCmd(c *cliconfig.PodRmValues) error { - if os.Geteuid() != 0 { - rootless.SetSkipStorageSetup(true) - } runtime, err := adapter.GetRuntime(&c.PodmanCommand) if err != nil { return errors.Wrapf(err, "could not get runtime") } defer runtime.Shutdown(false) - if rootless.IsRootless() { - var err error - c.InputArgs, c.All, c.Latest, err = joinPodNS(runtime, c.All, c.Latest, c.InputArgs) - if err != nil { - return err - } - } - podRmIds, podRmErrors := runtime.RemovePods(getContext(), c) for _, p := range podRmIds { fmt.Println(p) diff --git a/cmd/podman/pod_stop.go b/cmd/podman/pod_stop.go index 754a3a7db..f1b0ac51f 100644 --- a/cmd/podman/pod_stop.go +++ b/cmd/podman/pod_stop.go @@ -2,11 +2,9 @@ package main import ( "fmt" - "os" "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/pkg/adapter" - "github.com/containers/libpod/pkg/rootless" "github.com/pkg/errors" "github.com/sirupsen/logrus" "github.com/spf13/cobra" @@ -48,24 +46,12 @@ func init() { } func podStopCmd(c *cliconfig.PodStopValues) error { - if os.Geteuid() != 0 { - rootless.SetSkipStorageSetup(true) - } - runtime, err := adapter.GetRuntime(&c.PodmanCommand) if err != nil { return errors.Wrapf(err, "could not get runtime") } defer runtime.Shutdown(false) - if rootless.IsRootless() { - var err error - c.InputArgs, c.All, c.Latest, err = joinPodNS(runtime, c.All, c.Latest, c.InputArgs) - if err != nil { - return err - } - } - podStopIds, podStopErrors := runtime.StopPods(getContext(), c) for _, p := range podStopIds { fmt.Println(p) diff --git a/cmd/podman/pod_top.go b/cmd/podman/pod_top.go index f65d66df6..0d74dc3d6 100644 --- a/cmd/podman/pod_top.go +++ b/cmd/podman/pod_top.go @@ -9,7 +9,6 @@ import ( "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/libpod" - "github.com/containers/libpod/pkg/rootless" "github.com/pkg/errors" "github.com/spf13/cobra" ) @@ -54,10 +53,6 @@ func podTopCmd(c *cliconfig.PodTopValues) error { ) args := c.InputArgs - if os.Geteuid() != 0 { - rootless.SetSkipStorageSetup(true) - } - if c.ListDescriptors { descriptors, err := libpod.GetContainerPidInformationDescriptors() if err != nil { @@ -83,26 +78,6 @@ func podTopCmd(c *cliconfig.PodTopValues) error { descriptors = args[1:] } - if os.Geteuid() != 0 { - var pod *adapter.Pod - var err error - if c.Latest { - pod, err = runtime.GetLatestPod() - } else { - pod, err = runtime.LookupPod(c.InputArgs[0]) - } - if err != nil { - return errors.Wrapf(err, "unable to lookup requested container") - } - became, ret, err := runtime.JoinOrCreateRootlessPod(pod) - if err != nil { - return err - } - if became { - os.Exit(ret) - } - } - w := tabwriter.NewWriter(os.Stdout, 5, 1, 3, ' ', 0) psOutput, err := runtime.PodTop(c, descriptors) if err != nil { diff --git a/cmd/podman/ps.go b/cmd/podman/ps.go index 1f8db2739..759a03b86 100644 --- a/cmd/podman/ps.go +++ b/cmd/podman/ps.go @@ -17,7 +17,6 @@ import ( "github.com/containers/libpod/cmd/podman/libpodruntime" "github.com/containers/libpod/cmd/podman/shared" "github.com/containers/libpod/libpod" - "github.com/containers/libpod/pkg/rootless" "github.com/containers/libpod/pkg/util" "github.com/cri-o/ocicni/pkg/ocicni" "github.com/docker/go-units" @@ -202,13 +201,14 @@ func init() { } func psCmd(c *cliconfig.PsValues) error { - if os.Geteuid() != 0 { - rootless.SetSkipStorageSetup(true) - } if c.Bool("trace") { span, _ := opentracing.StartSpanFromContext(Ctx, "psCmd") defer span.Finish() } + // TODO disable when single rootless userns merges + if c.Bool("size") && os.Geteuid() != 0 { + return errors.New("the --size option is not presently supported without root") + } var watch bool diff --git a/cmd/podman/restart.go b/cmd/podman/restart.go index e6a6d8434..1553ab805 100644 --- a/cmd/podman/restart.go +++ b/cmd/podman/restart.go @@ -1,13 +1,10 @@ package main import ( - "os" - "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/cmd/podman/libpodruntime" "github.com/containers/libpod/cmd/podman/shared" "github.com/containers/libpod/libpod" - "github.com/containers/libpod/pkg/rootless" "github.com/pkg/errors" "github.com/sirupsen/logrus" "github.com/spf13/cobra" @@ -57,19 +54,6 @@ func restartCmd(c *cliconfig.RestartValues) error { restartContainers []*libpod.Container ) - if os.Geteuid() != 0 { - rootless.SetSkipStorageSetup(true) - } - if rootless.IsRootless() { - // If we are in the re-execed rootless environment, - // override the arg to deal only with one container. - if os.Geteuid() == 0 { - c.All = false - c.Latest = false - c.InputArgs = []string{rootless.Argument()} - } - } - args := c.InputArgs runOnly := c.Running all := c.All @@ -115,20 +99,6 @@ func restartCmd(c *cliconfig.RestartValues) error { } } - if os.Geteuid() != 0 { - // In rootless mode we can deal with one container at at time. - for _, c := range restartContainers { - _, ret, err := joinContainerOrCreateRootlessUserNS(runtime, c) - if err != nil { - return err - } - if ret != 0 { - os.Exit(ret) - } - } - os.Exit(0) - } - maxWorkers := shared.Parallelize("restart") if c.GlobalIsSet("max-workers") { maxWorkers = c.GlobalFlags.MaxWorks diff --git a/cmd/podman/rm.go b/cmd/podman/rm.go index 253771e14..52e281402 100644 --- a/cmd/podman/rm.go +++ b/cmd/podman/rm.go @@ -2,16 +2,12 @@ package main import ( "fmt" - "io/ioutil" - "os" - "strconv" "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/cmd/podman/libpodruntime" "github.com/containers/libpod/cmd/podman/shared" "github.com/containers/libpod/libpod" "github.com/containers/libpod/libpod/image" - "github.com/containers/libpod/pkg/rootless" "github.com/pkg/errors" "github.com/sirupsen/logrus" "github.com/spf13/cobra" @@ -52,39 +48,11 @@ func init() { markFlagHiddenForRemoteClient("latest", flags) } -func joinContainerOrCreateRootlessUserNS(runtime *libpod.Runtime, ctr *libpod.Container) (bool, int, error) { - if os.Geteuid() == 0 { - return false, 0, nil - } - s, err := ctr.State() - if err != nil { - return false, -1, err - } - opts := rootless.Opts{ - Argument: ctr.ID(), - } - if s == libpod.ContainerStateRunning || s == libpod.ContainerStatePaused { - data, err := ioutil.ReadFile(ctr.Config().ConmonPidFile) - if err != nil { - return false, -1, errors.Wrapf(err, "cannot read conmon PID file %q", ctr.Config().ConmonPidFile) - } - conmonPid, err := strconv.Atoi(string(data)) - if err != nil { - return false, -1, errors.Wrapf(err, "cannot parse PID %q", data) - } - return rootless.JoinDirectUserAndMountNSWithOpts(uint(conmonPid), &opts) - } - return rootless.BecomeRootInUserNSWithOpts(&opts) -} - // saveCmd saves the image to either docker-archive or oci func rmCmd(c *cliconfig.RmValues) error { var ( deleteFuncs []shared.ParallelWorkerInput ) - if os.Geteuid() != 0 { - rootless.SetSkipStorageSetup(true) - } ctx := getContext() runtime, err := libpodruntime.GetRuntime(&c.PodmanCommand) @@ -93,58 +61,6 @@ func rmCmd(c *cliconfig.RmValues) error { } defer runtime.Shutdown(false) - if rootless.IsRootless() { - // When running in rootless mode we cannot manage different containers and - // user namespaces from the same context, so be sure to re-exec once for each - // container we are dealing with. - // What we do is to first collect all the containers we want to delete, then - // we re-exec in each of the container namespaces and from there remove the single - // container. - var container *libpod.Container - if os.Geteuid() == 0 { - // We are in the namespace, override InputArgs with the single - // argument that was passed down to us. - c.All = false - c.Latest = false - c.InputArgs = []string{rootless.Argument()} - } else { - exitCode = 0 - var containers []*libpod.Container - if c.All { - containers, err = runtime.GetContainers() - } else if c.Latest { - container, err = runtime.GetLatestContainer() - if err != nil { - return errors.Wrapf(err, "unable to get latest pod") - } - containers = append(containers, container) - } else { - for _, c := range c.InputArgs { - container, err = runtime.LookupContainer(c) - if err != nil { - if errors.Cause(err) == libpod.ErrNoSuchCtr { - exitCode = 1 - continue - } - return err - } - containers = append(containers, container) - } - } - // Now we really delete the containers. - for _, c := range containers { - _, ret, err := joinContainerOrCreateRootlessUserNS(runtime, c) - if err != nil { - return err - } - if ret != 0 { - os.Exit(ret) - } - } - os.Exit(exitCode) - } - } - failureCnt := 0 delContainers, err := getAllOrLatestContainers(&c.PodmanCommand, runtime, -1, "all") if err != nil { diff --git a/cmd/podman/run.go b/cmd/podman/run.go index 3c26e98c1..bac5c3c18 100644 --- a/cmd/podman/run.go +++ b/cmd/podman/run.go @@ -1,21 +1,10 @@ package main import ( - "fmt" - "io/ioutil" - "os" - "path/filepath" - "strconv" - "strings" - "github.com/containers/libpod/cmd/podman/cliconfig" - "github.com/containers/libpod/cmd/podman/libpodruntime" - "github.com/containers/libpod/cmd/podman/shared" - "github.com/containers/libpod/libpod" - "github.com/containers/libpod/pkg/rootless" + "github.com/containers/libpod/pkg/adapter" opentracing "github.com/opentracing/opentracing-go" "github.com/pkg/errors" - "github.com/sirupsen/logrus" "github.com/spf13/cobra" ) @@ -57,147 +46,13 @@ func runCmd(c *cliconfig.RunValues) error { if err := createInit(&c.PodmanCommand); err != nil { return err } - if os.Geteuid() != 0 { - rootless.SetSkipStorageSetup(true) - } - runtime, err := libpodruntime.GetRuntime(&c.PodmanCommand) + runtime, err := adapter.GetRuntime(&c.PodmanCommand) if err != nil { return errors.Wrapf(err, "error creating libpod runtime") } defer runtime.Shutdown(false) - ctr, createConfig, err := shared.CreateContainer(getContext(), &c.PodmanCommand, runtime) - if err != nil { - return err - } - - if logrus.GetLevel() == logrus.DebugLevel { - cgroupPath, err := ctr.CGroupPath() - if err == nil { - logrus.Debugf("container %q has CgroupParent %q", ctr.ID(), cgroupPath) - } - } - - ctx := getContext() - // Handle detached start - if createConfig.Detach { - // if the container was created as part of a pod, also start its dependencies, if any. - if err := ctr.Start(ctx, c.IsSet("pod")); err != nil { - // This means the command did not exist - exitCode = 127 - if strings.Index(err.Error(), "permission denied") > -1 { - exitCode = 126 - } - return err - } - - fmt.Printf("%s\n", ctr.ID()) - exitCode = 0 - return nil - } - - outputStream := os.Stdout - errorStream := os.Stderr - inputStream := os.Stdin - - // If -i is not set, clear stdin - if !c.Bool("interactive") { - inputStream = nil - } - - // If attach is set, clear stdin/stdout/stderr and only attach requested - if c.IsSet("attach") || c.IsSet("a") { - outputStream = nil - errorStream = nil - if !c.Bool("interactive") { - inputStream = nil - } - - attachTo := c.StringSlice("attach") - for _, stream := range attachTo { - switch strings.ToLower(stream) { - case "stdout": - outputStream = os.Stdout - case "stderr": - errorStream = os.Stderr - case "stdin": - inputStream = os.Stdin - default: - return errors.Wrapf(libpod.ErrInvalidArg, "invalid stream %q for --attach - must be one of stdin, stdout, or stderr", stream) - } - } - } - // if the container was created as part of a pod, also start its dependencies, if any. - if err := startAttachCtr(ctr, outputStream, errorStream, inputStream, c.String("detach-keys"), c.Bool("sig-proxy"), true, c.IsSet("pod")); err != nil { - // We've manually detached from the container - // Do not perform cleanup, or wait for container exit code - // Just exit immediately - if errors.Cause(err) == libpod.ErrDetach { - exitCode = 0 - return nil - } - // This means the command did not exist - exitCode = 127 - if strings.Index(err.Error(), "permission denied") > -1 { - exitCode = 126 - } - if c.IsSet("rm") { - if deleteError := runtime.RemoveContainer(ctx, ctr, true, false); deleteError != nil { - logrus.Errorf("unable to remove container %s after failing to start and attach to it", ctr.ID()) - } - } - return err - } - - if ecode, err := ctr.Wait(); err != nil { - if errors.Cause(err) == libpod.ErrNoSuchCtr { - // The container may have been removed - // Go looking for an exit file - rtc, err := runtime.GetConfig() - if err != nil { - return err - } - ctrExitCode, err := readExitFile(rtc.TmpDir, ctr.ID()) - if err != nil { - logrus.Errorf("Cannot get exit code: %v", err) - exitCode = 127 - } else { - exitCode = ctrExitCode - } - } - } else { - exitCode = int(ecode) - } - - if c.IsSet("rm") { - runtime.RemoveContainer(ctx, ctr, false, true) - } - - return nil -} - -// Read a container's exit file -func readExitFile(runtimeTmp, ctrID string) (int, error) { - exitFile := filepath.Join(runtimeTmp, "exits", fmt.Sprintf("%s-old", ctrID)) - - logrus.Debugf("Attempting to read container %s exit code from file %s", ctrID, exitFile) - - // Check if it exists - if _, err := os.Stat(exitFile); err != nil { - return 0, errors.Wrapf(err, "error getting exit file for container %s", ctrID) - } - - // File exists, read it in and convert to int - statusStr, err := ioutil.ReadFile(exitFile) - if err != nil { - return 0, errors.Wrapf(err, "error reading exit file for container %s", ctrID) - } - - exitCode, err := strconv.Atoi(string(statusStr)) - if err != nil { - return 0, errors.Wrapf(err, "error parsing exit code for container %s", ctrID) - } - - return exitCode, nil + exitCode, err = runtime.Run(getContext(), c, exitCode) + return err } diff --git a/cmd/podman/run_test.go b/cmd/podman/run_test.go index a896f1dc7..27b34c323 100644 --- a/cmd/podman/run_test.go +++ b/cmd/podman/run_test.go @@ -8,6 +8,7 @@ import ( "github.com/containers/libpod/cmd/podman/shared" "github.com/containers/libpod/pkg/inspect" cc "github.com/containers/libpod/pkg/spec" + "github.com/containers/libpod/pkg/sysinfo" "github.com/docker/go-units" ociv1 "github.com/opencontainers/image-spec/specs-go/v1" spec "github.com/opencontainers/runtime-spec/specs-go" @@ -16,8 +17,9 @@ import ( ) var ( - cmd = []string{"podman", "test", "alpine"} - CLI *cliconfig.PodmanCommand + sysInfo = sysinfo.New(true) + cmd = []string{"podman", "test", "alpine"} + CLI *cliconfig.PodmanCommand ) // generates a mocked ImageData structure based on alpine @@ -81,7 +83,8 @@ func getRuntimeSpec(c *cliconfig.PodmanCommand) (*spec.Spec, error) { createConfig, err := parseCreateOpts(c, runtime, "alpine", generateAlpineImageData()) */ ctx := getContext() - createConfig, err := shared.ParseCreateOpts(ctx, c, nil, "alpine", generateAlpineImageData()) + genericResults := shared.NewIntermediateLayer(c) + createConfig, err := shared.ParseCreateOpts(ctx, &genericResults, nil, "alpine", generateAlpineImageData()) if err != nil { return nil, err } @@ -100,6 +103,9 @@ func TestPIDsLimit(t *testing.T) { if runtime.GOOS != "linux" { t.Skip("seccomp, which is enabled by default, is only supported on Linux") } + if !sysInfo.PidsLimit { + t.Skip("running test not supported by the host system") + } args := []string{"--pids-limit", "22"} a := createCLI(args) a.InputArgs = args @@ -119,6 +125,9 @@ func TestBLKIOWeightDevice(t *testing.T) { if runtime.GOOS != "linux" { t.Skip("seccomp, which is enabled by default, is only supported on Linux") } + if !sysInfo.BlkioWeightDevice { + t.Skip("running test not supported by the host system") + } args := []string{"--blkio-weight-device", "/dev/zero:100"} a := createCLI(args) a.InputArgs = args @@ -137,6 +146,9 @@ func TestMemorySwap(t *testing.T) { if runtime.GOOS != "linux" { t.Skip("seccomp, which is enabled by default, is only supported on Linux") } + if !sysInfo.SwapLimit { + t.Skip("running test not supported by the host system") + } args := []string{"--memory-swap", "45m", "--memory", "40m"} a := createCLI(args) a.InputArgs = args diff --git a/cmd/podman/search.go b/cmd/podman/search.go index a10b9d419..e614887fc 100644 --- a/cmd/podman/search.go +++ b/cmd/podman/search.go @@ -83,11 +83,25 @@ func searchCmd(c *cliconfig.SearchValues) error { if len(results) == 0 { return nil } - out := formats.StdoutTemplateArray{Output: searchToGeneric(results), Template: format, Fields: genSearchOutputMap()} + out := formats.StdoutTemplateArray{Output: searchToGeneric(results), Template: format, Fields: searchHeaderMap()} formats.Writer(out).Out() return nil } +// searchHeaderMap returns the headers of a SearchResult. +func searchHeaderMap() map[string]string { + s := new(image.SearchResult) + v := reflect.Indirect(reflect.ValueOf(s)) + values := make(map[string]string, v.NumField()) + + for i := 0; i < v.NumField(); i++ { + key := v.Type().Field(i).Name + value := key + values[key] = strings.ToUpper(splitCamelCase(value)) + } + return values +} + func genSearchFormat(format string) string { if format != "" { // "\t" from the command line is not being recognized as a tab diff --git a/cmd/podman/shared/create.go b/cmd/podman/shared/create.go index d927e5bf6..d694027db 100644 --- a/cmd/podman/shared/create.go +++ b/cmd/podman/shared/create.go @@ -5,7 +5,6 @@ import ( "encoding/json" "fmt" "io" - "io/ioutil" "os" "path/filepath" "strconv" @@ -14,7 +13,6 @@ import ( "time" "github.com/containers/image/manifest" - "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/cmd/podman/shared/parse" "github.com/containers/libpod/libpod" "github.com/containers/libpod/libpod/image" @@ -35,12 +33,7 @@ import ( "github.com/sirupsen/logrus" ) -// getContext returns a non-nil, empty context -func getContext() context.Context { - return context.TODO() -} - -func CreateContainer(ctx context.Context, c *cliconfig.PodmanCommand, runtime *libpod.Runtime) (*libpod.Container, *cc.CreateConfig, error) { +func CreateContainer(ctx context.Context, c *GenericCLIResults, runtime *libpod.Runtime) (*libpod.Container, *cc.CreateConfig, error) { var ( healthCheck *manifest.Schema2HealthConfig err error @@ -75,7 +68,8 @@ func CreateContainer(ctx context.Context, c *cliconfig.PodmanCommand, runtime *l imageName := "" var data *inspect.ImageData = nil - if rootfs == "" && !rootless.SkipStorageSetup() { + // Set the storage if we are running as euid == 0 and there is no rootfs specified + if rootfs == "" && os.Geteuid() == 0 { var writer io.Writer if !c.Bool("quiet") { writer = os.Stderr @@ -221,7 +215,7 @@ func parseSecurityOpt(config *cc.CreateConfig, securityOpts []string) error { return nil } -func configureEntrypoint(c *cliconfig.PodmanCommand, data *inspect.ImageData) []string { +func configureEntrypoint(c *GenericCLIResults, data *inspect.ImageData) []string { entrypoint := []string{} if c.IsSet("entrypoint") { // Force entrypoint to "" @@ -241,7 +235,7 @@ func configureEntrypoint(c *cliconfig.PodmanCommand, data *inspect.ImageData) [] return entrypoint } -func configurePod(c *cliconfig.PodmanCommand, runtime *libpod.Runtime, namespaces map[string]string, podName string) (map[string]string, error) { +func configurePod(c *GenericCLIResults, runtime *libpod.Runtime, namespaces map[string]string, podName string) (map[string]string, error) { pod, err := runtime.LookupPod(podName) if err != nil { return namespaces, err @@ -270,7 +264,7 @@ func configurePod(c *cliconfig.PodmanCommand, runtime *libpod.Runtime, namespace // Parses CLI options related to container creation into a config which can be // parsed into an OCI runtime spec -func ParseCreateOpts(ctx context.Context, c *cliconfig.PodmanCommand, runtime *libpod.Runtime, imageName string, data *inspect.ImageData) (*cc.CreateConfig, error) { +func ParseCreateOpts(ctx context.Context, c *GenericCLIResults, runtime *libpod.Runtime, imageName string, data *inspect.ImageData) (*cc.CreateConfig, error) { var ( inputCommand, command []string memoryLimit, memoryReservation, memorySwap, memoryKernel int64 @@ -353,14 +347,14 @@ func ParseCreateOpts(ctx context.Context, c *cliconfig.PodmanCommand, runtime *l tty := c.Bool("tty") - if c.Flag("cpu-period").Changed && c.Flag("cpus").Changed { + if c.Changed("cpu-period") && c.Changed("cpus") { return nil, errors.Errorf("--cpu-period and --cpus cannot be set together") } - if c.Flag("cpu-quota").Changed && c.Flag("cpus").Changed { + if c.Changed("cpu-quota") && c.Changed("cpus") { return nil, errors.Errorf("--cpu-quota and --cpus cannot be set together") } - if c.Bool("no-hosts") && c.Flag("add-host").Changed { + if c.Bool("no-hosts") && c.Changed("add-host") { return nil, errors.Errorf("--no-hosts and --add-host cannot be set together") } @@ -379,7 +373,7 @@ func ParseCreateOpts(ctx context.Context, c *cliconfig.PodmanCommand, runtime *l // However, that also involves setting up security opts // when the pod's namespace is integrated namespaceNet := c.String("network") - if c.Flag("net").Changed { + if c.Changed("net") { namespaceNet = c.String("net") } namespaces = map[string]string{ @@ -548,7 +542,7 @@ func ParseCreateOpts(ctx context.Context, c *cliconfig.PodmanCommand, runtime *l // WORKING DIRECTORY workDir := "/" - if c.IsSet("workdir") || c.IsSet("w") { + if c.IsSet("workdir") { workDir = c.String("workdir") } else if data != nil && data.Config.WorkingDir != "" { workDir = data.Config.WorkingDir @@ -624,14 +618,12 @@ func ParseCreateOpts(ctx context.Context, c *cliconfig.PodmanCommand, runtime *l // This is done because cobra cannot have two aliased flags. So we have to check // both network := c.String("network") - if c.Flag("net").Changed { + if c.Changed("net") { network = c.String("net") } - var memorySwappiness int64 - if c.Flags().Lookup("memory-swappiness") != nil { - memorySwappiness, _ = c.Flags().GetInt64("memory-swappiness") - } + memorySwappiness := c.Int64("memory-swappiness") + config := &cc.CreateConfig{ Runtime: runtime, Annotations: annotations, @@ -719,7 +711,7 @@ func ParseCreateOpts(ctx context.Context, c *cliconfig.PodmanCommand, runtime *l WorkDir: workDir, Rootfs: rootfs, VolumesFrom: c.StringSlice("volumes-from"), - Syslog: c.GlobalFlags.Syslog, + Syslog: c.Bool("syslog"), } if c.Bool("init") { initPath := c.String("init-path") @@ -758,71 +750,6 @@ type namespace interface { Container() string } -func joinOrCreateRootlessUserNamespace(createConfig *cc.CreateConfig, runtime *libpod.Runtime) (bool, int, error) { - if os.Geteuid() == 0 { - return false, 0, nil - } - - if createConfig.Pod != "" { - pod, err := runtime.LookupPod(createConfig.Pod) - if err != nil { - return false, -1, err - } - inspect, err := pod.Inspect() - for _, ctr := range inspect.Containers { - prevCtr, err := runtime.LookupContainer(ctr.ID) - if err != nil { - return false, -1, err - } - s, err := prevCtr.State() - if err != nil { - return false, -1, err - } - if s != libpod.ContainerStateRunning && s != libpod.ContainerStatePaused { - continue - } - data, err := ioutil.ReadFile(prevCtr.Config().ConmonPidFile) - if err != nil { - return false, -1, errors.Wrapf(err, "cannot read conmon PID file %q", prevCtr.Config().ConmonPidFile) - } - conmonPid, err := strconv.Atoi(string(data)) - if err != nil { - return false, -1, errors.Wrapf(err, "cannot parse PID %q", data) - } - return rootless.JoinDirectUserAndMountNS(uint(conmonPid)) - } - } - - namespacesStr := []string{string(createConfig.IpcMode), string(createConfig.NetMode), string(createConfig.UsernsMode), string(createConfig.PidMode), string(createConfig.UtsMode)} - for _, i := range namespacesStr { - if cc.IsNS(i) { - return rootless.JoinNSPath(cc.NS(i)) - } - } - - namespaces := []namespace{createConfig.IpcMode, createConfig.NetMode, createConfig.UsernsMode, createConfig.PidMode, createConfig.UtsMode} - for _, i := range namespaces { - if i.IsContainer() { - ctr, err := runtime.LookupContainer(i.Container()) - if err != nil { - return false, -1, err - } - pid, err := ctr.PID() - if err != nil { - return false, -1, err - } - if pid == 0 { - if createConfig.Pod != "" { - continue - } - return false, -1, errors.Errorf("dependency container %s is not running", ctr.ID()) - } - return rootless.JoinNS(uint(pid), 0) - } - } - return rootless.BecomeRootInUserNS() -} - func CreateContainerFromCreateConfig(r *libpod.Runtime, createConfig *cc.CreateConfig, ctx context.Context, pod *libpod.Pod) (*libpod.Container, error) { runtimeSpec, err := cc.CreateConfigToOCISpec(createConfig) if err != nil { @@ -833,13 +760,6 @@ func CreateContainerFromCreateConfig(r *libpod.Runtime, createConfig *cc.CreateC if err != nil { return nil, err } - became, ret, err := joinOrCreateRootlessUserNamespace(createConfig, r) - if err != nil { - return nil, err - } - if became { - os.Exit(ret) - } ctr, err := r.NewContainer(ctx, runtimeSpec, options...) if err != nil { @@ -861,7 +781,7 @@ var defaultEnvVariables = map[string]string{ "TERM": "xterm", } -func makeHealthCheckFromCli(c *cliconfig.PodmanCommand) (*manifest.Schema2HealthConfig, error) { +func makeHealthCheckFromCli(c *GenericCLIResults) (*manifest.Schema2HealthConfig, error) { inCommand := c.String("healthcheck-command") inInterval := c.String("healthcheck-interval") inRetries := c.Uint("healthcheck-retries") diff --git a/cmd/podman/shared/intermediate.go b/cmd/podman/shared/intermediate.go new file mode 100644 index 000000000..9afbd68c8 --- /dev/null +++ b/cmd/podman/shared/intermediate.go @@ -0,0 +1,465 @@ +package shared + +import ( + "github.com/containers/libpod/cmd/podman/cliconfig" + "github.com/sirupsen/logrus" +) + +/* +attention + +in this file you will see alot of struct duplication. this was done because people wanted a strongly typed +varlink mechanism. this resulted in us creating this intermediate layer that allows us to take the input +from the cli and make an intermediate layer which can be transferred as strongly typed structures over a varlink +interface. + +we intentionally avoided heavy use of reflection here because we were concerned about performance impacts to the +non-varlink intermediate layer generation. +*/ + +// GenericCLIResult describes the overall interface for dealing with +// the create command cli in both local and remote uses +type GenericCLIResult interface { + IsSet() bool + Name() string + Value() interface{} +} + +// CRStringSlice describes a string slice cli struct +type CRStringSlice struct { + Val []string + createResult +} + +// CRString describes a string cli struct +type CRString struct { + Val string + createResult +} + +// CRUint64 describes a uint64 cli struct +type CRUint64 struct { + Val uint64 + createResult +} + +// CRFloat64 describes a float64 cli struct +type CRFloat64 struct { + Val float64 + createResult +} + +//CRBool describes a bool cli struct +type CRBool struct { + Val bool + createResult +} + +// CRInt64 describes an int64 cli struct +type CRInt64 struct { + Val int64 + createResult +} + +// CRUint describes a uint cli struct +type CRUint struct { + Val uint + createResult +} + +// CRInt describes an int cli struct +type CRInt struct { + Val int + createResult +} + +// CRStringArray describes a stringarray cli struct +type CRStringArray struct { + Val []string + createResult +} + +type createResult struct { + Flag string + Changed bool +} + +// GenericCLIResults in the intermediate object between the cobra cli +// and createconfig +type GenericCLIResults struct { + results map[string]GenericCLIResult + InputArgs []string +} + +// IsSet returns a bool if the flag was changed +func (f GenericCLIResults) IsSet(flag string) bool { + r := f.findResult(flag) + if r == nil { + return false + } + return r.IsSet() +} + +// Value returns the value of the cli flag +func (f GenericCLIResults) Value(flag string) interface{} { + r := f.findResult(flag) + if r == nil { + return "" + } + return r.Value() +} + +func (f GenericCLIResults) findResult(flag string) GenericCLIResult { + val, ok := f.results[flag] + if ok { + return val + } + logrus.Errorf("unable to find flag %s", flag) + return nil +} + +// Bool is a wrapper to get a bool value from GenericCLIResults +func (f GenericCLIResults) Bool(flag string) bool { + r := f.findResult(flag) + if r == nil { + return false + } + return r.Value().(bool) +} + +// String is a wrapper to get a string value from GenericCLIResults +func (f GenericCLIResults) String(flag string) string { + r := f.findResult(flag) + if r == nil { + return "" + } + return r.Value().(string) +} + +// Uint is a wrapper to get an uint value from GenericCLIResults +func (f GenericCLIResults) Uint(flag string) uint { + r := f.findResult(flag) + if r == nil { + return 0 + } + return r.Value().(uint) +} + +// StringSlice is a wrapper to get a stringslice value from GenericCLIResults +func (f GenericCLIResults) StringSlice(flag string) []string { + r := f.findResult(flag) + if r == nil { + return []string{} + } + return r.Value().([]string) +} + +// StringArray is a wrapper to get a stringslice value from GenericCLIResults +func (f GenericCLIResults) StringArray(flag string) []string { + r := f.findResult(flag) + if r == nil { + return []string{} + } + return r.Value().([]string) +} + +// Uint64 is a wrapper to get an uint64 value from GenericCLIResults +func (f GenericCLIResults) Uint64(flag string) uint64 { + r := f.findResult(flag) + if r == nil { + return 0 + } + return r.Value().(uint64) +} + +// Int64 is a wrapper to get an int64 value from GenericCLIResults +func (f GenericCLIResults) Int64(flag string) int64 { + r := f.findResult(flag) + if r == nil { + return 0 + } + return r.Value().(int64) +} + +// Int is a wrapper to get an int value from GenericCLIResults +func (f GenericCLIResults) Int(flag string) int { + r := f.findResult(flag) + if r == nil { + return 0 + } + return r.Value().(int) +} + +// Float64 is a wrapper to get an float64 value from GenericCLIResults +func (f GenericCLIResults) Float64(flag string) float64 { + r := f.findResult(flag) + if r == nil { + return 0 + } + return r.Value().(float64) +} + +// Float64 is a wrapper to get an float64 value from GenericCLIResults +func (f GenericCLIResults) Changed(flag string) bool { + r := f.findResult(flag) + if r == nil { + return false + } + return r.IsSet() +} + +// IsSet ... +func (c CRStringSlice) IsSet() bool { return c.Changed } + +// Name ... +func (c CRStringSlice) Name() string { return c.Flag } + +// Value ... +func (c CRStringSlice) Value() interface{} { return c.Val } + +// IsSet ... +func (c CRString) IsSet() bool { return c.Changed } + +// Name ... +func (c CRString) Name() string { return c.Flag } + +// Value ... +func (c CRString) Value() interface{} { return c.Val } + +// IsSet ... +func (c CRUint64) IsSet() bool { return c.Changed } + +// Name ... +func (c CRUint64) Name() string { return c.Flag } + +// Value ... +func (c CRUint64) Value() interface{} { return c.Val } + +// IsSet ... +func (c CRFloat64) IsSet() bool { return c.Changed } + +// Name ... +func (c CRFloat64) Name() string { return c.Flag } + +// Value ... +func (c CRFloat64) Value() interface{} { return c.Val } + +// IsSet ... +func (c CRBool) IsSet() bool { return c.Changed } + +// Name ... +func (c CRBool) Name() string { return c.Flag } + +// Value ... +func (c CRBool) Value() interface{} { return c.Val } + +// IsSet ... +func (c CRInt64) IsSet() bool { return c.Changed } + +// Name ... +func (c CRInt64) Name() string { return c.Flag } + +// Value ... +func (c CRInt64) Value() interface{} { return c.Val } + +// IsSet ... +func (c CRUint) IsSet() bool { return c.Changed } + +// Name ... +func (c CRUint) Name() string { return c.Flag } + +// Value ... +func (c CRUint) Value() interface{} { return c.Val } + +// IsSet ... +func (c CRInt) IsSet() bool { return c.Changed } + +// Name ... +func (c CRInt) Name() string { return c.Flag } + +// Value ... +func (c CRInt) Value() interface{} { return c.Val } + +// IsSet ... +func (c CRStringArray) IsSet() bool { return c.Changed } + +// Name ... +func (c CRStringArray) Name() string { return c.Flag } + +// Value ... +func (c CRStringArray) Value() interface{} { return c.Val } + +func newCreateResult(c *cliconfig.PodmanCommand, flag string) createResult { + return createResult{ + Flag: flag, + Changed: c.IsSet(flag), + } +} + +func newCRStringSlice(c *cliconfig.PodmanCommand, flag string) CRStringSlice { + return CRStringSlice{ + Val: c.StringSlice(flag), + createResult: newCreateResult(c, flag), + } +} + +func newCRString(c *cliconfig.PodmanCommand, flag string) CRString { + return CRString{ + Val: c.String(flag), + createResult: newCreateResult(c, flag), + } +} + +func newCRUint64(c *cliconfig.PodmanCommand, flag string) CRUint64 { + return CRUint64{ + Val: c.Uint64(flag), + createResult: newCreateResult(c, flag), + } +} + +func newCRFloat64(c *cliconfig.PodmanCommand, flag string) CRFloat64 { + return CRFloat64{ + Val: c.Float64(flag), + createResult: newCreateResult(c, flag), + } +} + +func newCRBool(c *cliconfig.PodmanCommand, flag string) CRBool { + return CRBool{ + Val: c.Bool(flag), + createResult: newCreateResult(c, flag), + } +} + +func newCRInt64(c *cliconfig.PodmanCommand, flag string) CRInt64 { + return CRInt64{ + Val: c.Int64(flag), + createResult: newCreateResult(c, flag), + } +} + +func newCRUint(c *cliconfig.PodmanCommand, flag string) CRUint { + return CRUint{ + Val: c.Uint(flag), + createResult: newCreateResult(c, flag), + } +} + +func newCRInt(c *cliconfig.PodmanCommand, flag string) CRInt { + return CRInt{ + Val: c.Int(flag), + createResult: newCreateResult(c, flag), + } +} + +func newCRStringArray(c *cliconfig.PodmanCommand, flag string) CRStringArray { + return CRStringArray{ + Val: c.StringArray(flag), + createResult: newCreateResult(c, flag), + } +} + +// NewIntermediateLayer creates a GenericCLIResults from a create or run cli-command +func NewIntermediateLayer(c *cliconfig.PodmanCommand) GenericCLIResults { + m := make(map[string]GenericCLIResult) + + m["add-host"] = newCRStringSlice(c, "add-host") + m["annotation"] = newCRStringSlice(c, "annotation") + m["attach"] = newCRStringSlice(c, "attach") + m["blkio-weight"] = newCRString(c, "blkio-weight") + m["blkio-weight-device"] = newCRStringSlice(c, "blkio-weight-device") + m["cap-add"] = newCRStringSlice(c, "cap-add") + m["cap-drop"] = newCRStringSlice(c, "cap-drop") + m["cgroup-parent"] = newCRString(c, "cgroup-parent") + m["cidfile"] = newCRString(c, "cidfile") + m["conmon-pidfile"] = newCRString(c, "conmon-pidfile") + m["cpu-period"] = newCRUint64(c, "cpu-period") + m["cpu-quota"] = newCRInt64(c, "cpu-quota") + m["cpu-rt-period"] = newCRUint64(c, "cpu-rt-period") + m["cpu-rt-runtime"] = newCRInt64(c, "cpu-rt-runtime") + m["cpu-shares"] = newCRUint64(c, "cpu-shares") + m["cpus"] = newCRFloat64(c, "cpus") + m["cpuset-cpus"] = newCRString(c, "cpuset-cpus") + m["cpuset-mems"] = newCRString(c, "cpuset-mems") + m["detach"] = newCRBool(c, "detach") + m["detach-keys"] = newCRString(c, "detach-keys") + m["device"] = newCRStringSlice(c, "device") + m["device-read-bps"] = newCRStringSlice(c, "device-read-bps") + m["device-read-iops"] = newCRStringSlice(c, "device-read-iops") + m["device-write-bps"] = newCRStringSlice(c, "device-write-bps") + m["device-write-iops"] = newCRStringSlice(c, "device-write-iops") + m["dns"] = newCRStringSlice(c, "dns") + m["dns-opt"] = newCRStringSlice(c, "dns-opt") + m["dns-search"] = newCRStringSlice(c, "dns-search") + m["entrypoint"] = newCRString(c, "entrypoint") + m["env"] = newCRStringArray(c, "env") + m["env-file"] = newCRStringSlice(c, "env-file") + m["expose"] = newCRStringSlice(c, "expose") + m["gidmap"] = newCRStringSlice(c, "gidmap") + m["group-add"] = newCRStringSlice(c, "group-add") + m["help"] = newCRBool(c, "help") + m["healthcheck-command"] = newCRString(c, "healthcheck-command") + m["healthcheck-interval"] = newCRString(c, "healthcheck-interval") + m["healthcheck-retries"] = newCRUint(c, "healthcheck-retries") + m["healthcheck-start-period"] = newCRString(c, "healthcheck-start-period") + m["healthcheck-timeout"] = newCRString(c, "healthcheck-timeout") + m["hostname"] = newCRString(c, "hostname") + m["image-volume"] = newCRString(c, "image-volume") + m["init"] = newCRBool(c, "init") + m["init-path"] = newCRString(c, "init-path") + m["interactive"] = newCRBool(c, "interactive") + m["ip"] = newCRString(c, "ip") + m["ipc"] = newCRString(c, "ipc") + m["kernel-memory"] = newCRString(c, "kernel-memory") + m["label"] = newCRStringArray(c, "label") + m["label-file"] = newCRStringSlice(c, "label-file") + m["log-driver"] = newCRString(c, "log-driver") + m["log-opt"] = newCRStringSlice(c, "log-opt") + m["mac-address"] = newCRString(c, "mac-address") + m["memory"] = newCRString(c, "memory") + m["memory-reservation"] = newCRString(c, "memory-reservation") + m["memory-swap"] = newCRString(c, "memory-swap") + m["memory-swappiness"] = newCRInt64(c, "memory-swappiness") + m["name"] = newCRString(c, "name") + m["net"] = newCRString(c, "net") + m["network"] = newCRString(c, "network") + m["no-hosts"] = newCRBool(c, "no-hosts") + m["oom-kill-disable"] = newCRBool(c, "oom-kill-disable") + m["oom-score-adj"] = newCRInt(c, "oom-score-adj") + m["pid"] = newCRString(c, "pid") + m["pids-limit"] = newCRInt64(c, "pids-limit") + m["pod"] = newCRString(c, "pod") + m["privileged"] = newCRBool(c, "privileged") + m["publish"] = newCRStringSlice(c, "publish") + m["publish-all"] = newCRBool(c, "publish-all") + m["quiet"] = newCRBool(c, "quiet") + m["read-only"] = newCRBool(c, "read-only") + m["restart"] = newCRString(c, "restart") + m["rm"] = newCRBool(c, "rm") + m["rootfs"] = newCRBool(c, "rootfs") + m["security-opt"] = newCRStringArray(c, "security-opt") + m["shm-size"] = newCRString(c, "shm-size") + m["stop-signal"] = newCRString(c, "stop-signal") + m["stop-timeout"] = newCRUint(c, "stop-timeout") + m["storage-opt"] = newCRStringSlice(c, "storage-opt") + m["subgidname"] = newCRString(c, "subgidname") + m["subuidname"] = newCRString(c, "subuidname") + m["sysctl"] = newCRStringSlice(c, "sysctl") + m["systemd"] = newCRBool(c, "systemd") + m["tmpfs"] = newCRStringSlice(c, "tmpfs") + m["tty"] = newCRBool(c, "tty") + m["uidmap"] = newCRStringSlice(c, "uidmap") + m["ulimit"] = newCRStringSlice(c, "ulimit") + m["user"] = newCRString(c, "user") + m["userns"] = newCRString(c, "userns") + m["uts"] = newCRString(c, "uts") + m["mount"] = newCRStringArray(c, "mount") + m["volume"] = newCRStringArray(c, "volume") + m["volumes-from"] = newCRStringSlice(c, "volumes-from") + m["workdir"] = newCRString(c, "workdir") + // global flag + m["trace"] = newCRBool(c, "trace") + m["syslog"] = newCRBool(c, "syslog") + + return GenericCLIResults{m, c.InputArgs} +} diff --git a/cmd/podman/shared/intermediate_novarlink.go b/cmd/podman/shared/intermediate_novarlink.go new file mode 100644 index 000000000..26738ce48 --- /dev/null +++ b/cmd/podman/shared/intermediate_novarlink.go @@ -0,0 +1,70 @@ +// +build !varlink +// +build !remoteclient + +package shared + +/* +attention + +in this file you will see alot of struct duplication. this was done because people wanted a strongly typed +varlink mechanism. this resulted in us creating this intermediate layer that allows us to take the input +from the cli and make an intermediate layer which can be transferred as strongly typed structures over a varlink +interface. + +we intentionally avoided heavy use of reflection here because we were concerned about performance impacts to the +non-varlink intermediate layer generation. +*/ + +// ToString wrapper for build without varlink +func (c CRStringSlice) ToVarlink() interface{} { + var v interface{} + return v +} + +// ToString wrapper for build without varlink +func (c CRString) ToVarlink() interface{} { + var v interface{} + return v +} + +// ToString wrapper for build without varlink +func (c CRBool) ToVarlink() interface{} { + var v interface{} + return v +} + +// ToString wrapper for build without varlink +func (c CRUint64) ToVarlink() interface{} { + var v interface{} + return v +} + +// ToString wrapper for build without varlink +func (c CRInt64) ToVarlink() interface{} { + var v interface{} + return v +} + +// ToString wrapper for build without varlink +func (c CRFloat64) ToVarlink() interface{} { + var v interface{} + return v +} + +// ToString wrapper for build without varlink +func (c CRUint) ToVarlink() interface{} { + var v interface{} + return v +} + +// ToString wrapper for build without varlink +func (c CRStringArray) ToVarlink() interface{} { + var v interface{} + return v +} + +// ToString wrapper for build without varlink +func (c CRInt) ToVarlink() interface{} { + var v interface{} + return v +} diff --git a/cmd/podman/shared/intermediate_varlink.go b/cmd/podman/shared/intermediate_varlink.go new file mode 100644 index 000000000..95a0d6287 --- /dev/null +++ b/cmd/podman/shared/intermediate_varlink.go @@ -0,0 +1,427 @@ +// +build varlink remoteclient + +package shared + +import ( + "fmt" + "github.com/containers/libpod/cmd/podman/cliconfig" + "github.com/containers/libpod/cmd/podman/varlink" + "github.com/pkg/errors" +) + +// StringSliceToPtr converts a genericcliresult value into a *[]string +func StringSliceToPtr(g GenericCLIResult) *[]string { + if !g.IsSet() { + return nil + } + newT := g.Value().([]string) + return &newT +} + +// StringToPtr converts a genericcliresult value into a *string +func StringToPtr(g GenericCLIResult) *string { + if !g.IsSet() { + return nil + } + newT := g.Value().(string) + return &newT +} + +// BoolToPtr converts a genericcliresult value into a *bool +func BoolToPtr(g GenericCLIResult) *bool { + if !g.IsSet() { + return nil + } + newT := g.Value().(bool) + return &newT +} + +// AnyIntToInt64Ptr converts a genericcliresult value into an *int64 +func AnyIntToInt64Ptr(g GenericCLIResult) *int64 { + if !g.IsSet() { + return nil + } + var newT int64 + switch g.Value().(type) { + case int: + newT = int64(g.Value().(int)) + case int64: + newT = g.Value().(int64) + case uint64: + newT = int64(g.Value().(uint64)) + case uint: + newT = int64(g.Value().(uint)) + default: + panic(errors.Errorf("invalid int type")) + } + return &newT +} + +// Float64ToPtr converts a genericcliresult into a *float64 +func Float64ToPtr(g GenericCLIResult) *float64 { + if !g.IsSet() { + return nil + } + newT := g.Value().(float64) + return &newT +} + +// MakeVarlink creates a varlink transportable struct from GenericCLIResults +func (g GenericCLIResults) MakeVarlink() iopodman.Create { + v := iopodman.Create{ + Args: g.InputArgs, + AddHost: StringSliceToPtr(g.Find("add-host")), + Annotation: StringSliceToPtr(g.Find("annotation")), + Attach: StringSliceToPtr(g.Find("attach")), + BlkioWeight: StringToPtr(g.Find("blkio-weight")), + BlkioWeightDevice: StringSliceToPtr(g.Find("blkio-weight-device")), + CapAdd: StringSliceToPtr(g.Find("cap-add")), + CapDrop: StringSliceToPtr(g.Find("cap-drop")), + CgroupParent: StringToPtr(g.Find("cgroup-parent")), + CidFile: StringToPtr(g.Find("cidfile")), + ConmonPidfile: StringToPtr(g.Find("conmon-pidfile")), + CpuPeriod: AnyIntToInt64Ptr(g.Find("cpu-period")), + CpuQuota: AnyIntToInt64Ptr(g.Find("cpu-quota")), + CpuRtPeriod: AnyIntToInt64Ptr(g.Find("cpu-rt-period")), + CpuRtRuntime: AnyIntToInt64Ptr(g.Find("cpu-rt-runtime")), + CpuShares: AnyIntToInt64Ptr(g.Find("cpu-shares")), + Cpus: Float64ToPtr(g.Find("cpus")), + CpuSetCpus: StringToPtr(g.Find("cpuset-cpus")), + CpuSetMems: StringToPtr(g.Find("cpuset-mems")), + Detach: BoolToPtr(g.Find("detach")), + DetachKeys: StringToPtr(g.Find("detach-keys")), + Device: StringSliceToPtr(g.Find("device")), + DeviceReadBps: StringSliceToPtr(g.Find("device-read-bps")), + DeviceReadIops: StringSliceToPtr(g.Find("device-read-iops")), + DeviceWriteBps: StringSliceToPtr(g.Find("device-write-bps")), + DeviceWriteIops: StringSliceToPtr(g.Find("device-write-iops")), + Dns: StringSliceToPtr(g.Find("dns")), + DnsOpt: StringSliceToPtr(g.Find("dns-opt")), + DnsSearch: StringSliceToPtr(g.Find("dns-search")), + Entrypoint: StringToPtr(g.Find("entrypoint")), + Env: StringSliceToPtr(g.Find("env")), + EnvFile: StringSliceToPtr(g.Find("env-file")), + Expose: StringSliceToPtr(g.Find("expose")), + Gidmap: StringSliceToPtr(g.Find("gidmap")), + Groupadd: StringSliceToPtr(g.Find("group-add")), + HealthcheckCommand: StringToPtr(g.Find("healthcheck-command")), + HealthcheckInterval: StringToPtr(g.Find("healthcheck-interval")), + HealthcheckRetries: AnyIntToInt64Ptr(g.Find("healthcheck-retries")), + HealthcheckStartPeriod: StringToPtr(g.Find("healthcheck-start-period")), + HealthcheckTimeout: StringToPtr(g.Find("healthcheck-timeout")), + Hostname: StringToPtr(g.Find("hostname")), + ImageVolume: StringToPtr(g.Find("image-volume")), + Init: BoolToPtr(g.Find("init")), + InitPath: StringToPtr(g.Find("init-path")), + Interactive: BoolToPtr(g.Find("interactive")), + Ip: StringToPtr(g.Find("ip")), + Ipc: StringToPtr(g.Find("ipc")), + KernelMemory: StringToPtr(g.Find("kernel-memory")), + Label: StringSliceToPtr(g.Find("label")), + LabelFile: StringSliceToPtr(g.Find("label-file")), + LogDriver: StringToPtr(g.Find("log-driver")), + LogOpt: StringSliceToPtr(g.Find("log-opt")), + MacAddress: StringToPtr(g.Find("mac-address")), + Memory: StringToPtr(g.Find("memory")), + MemoryReservation: StringToPtr(g.Find("memory-reservation")), + MemorySwap: StringToPtr(g.Find("memory-swap")), + MemorySwappiness: AnyIntToInt64Ptr(g.Find("memory-swappiness")), + Name: StringToPtr(g.Find("name")), + Net: StringToPtr(g.Find("net")), + Network: StringToPtr(g.Find("network")), + OomKillDisable: BoolToPtr(g.Find("oom-kill-disable")), + OomScoreAdj: AnyIntToInt64Ptr(g.Find("oom-score-adj")), + Pid: StringToPtr(g.Find("pid")), + PidsLimit: AnyIntToInt64Ptr(g.Find("pids-limit")), + Pod: StringToPtr(g.Find("pod")), + Privileged: BoolToPtr(g.Find("privileged")), + Publish: StringSliceToPtr(g.Find("publish")), + PublishAll: BoolToPtr(g.Find("publish-all")), + Quiet: BoolToPtr(g.Find("quiet")), + Readonly: BoolToPtr(g.Find("read-only")), + Restart: StringToPtr(g.Find("restart")), + Rm: BoolToPtr(g.Find("rm")), + Rootfs: BoolToPtr(g.Find("rootfs")), + SecurityOpt: StringSliceToPtr(g.Find("security-opt")), + ShmSize: StringToPtr(g.Find("shm-size")), + StopSignal: StringToPtr(g.Find("stop-signal")), + StopTimeout: AnyIntToInt64Ptr(g.Find("stop-timeout")), + StorageOpt: StringSliceToPtr(g.Find("storage-opt")), + Subuidname: StringToPtr(g.Find("subuidname")), + Subgidname: StringToPtr(g.Find("subgidname")), + Sysctl: StringSliceToPtr(g.Find("sysctl")), + Systemd: BoolToPtr(g.Find("systemd")), + Tmpfs: StringSliceToPtr(g.Find("tmpfs")), + Tty: BoolToPtr(g.Find("tty")), + Uidmap: StringSliceToPtr(g.Find("uidmap")), + Ulimit: StringSliceToPtr(g.Find("ulimit")), + User: StringToPtr(g.Find("user")), + Userns: StringToPtr(g.Find("userns")), + Uts: StringToPtr(g.Find("uts")), + Mount: StringSliceToPtr(g.Find("mount")), + Volume: StringSliceToPtr(g.Find("volume")), + VolumesFrom: StringSliceToPtr(g.Find("volumes-from")), + WorkDir: StringToPtr(g.Find("workdir")), + } + + return v +} + +func stringSliceFromVarlink(v *[]string, flagName string, defaultValue *[]string) CRStringSlice { + cr := CRStringSlice{} + if v == nil { + cr.Val = []string{} + if defaultValue != nil { + cr.Val = *defaultValue + } + cr.Changed = false + } else { + cr.Val = *v + cr.Changed = true + } + cr.Flag = flagName + return cr +} + +func stringFromVarlink(v *string, flagName string, defaultValue *string) CRString { + cr := CRString{} + if v == nil { + cr.Val = "" + if defaultValue != nil { + cr.Val = *defaultValue + } + cr.Changed = false + } else { + cr.Val = *v + cr.Changed = true + } + cr.Flag = flagName + return cr +} + +func boolFromVarlink(v *bool, flagName string, defaultValue bool) CRBool { + cr := CRBool{} + if v == nil { + // In case a cli bool default value is true + cr.Val = defaultValue + cr.Changed = false + } else { + fmt.Println(flagName, cr.Val) + cr.Val = *v + cr.Changed = true + } + cr.Flag = flagName + return cr +} + +func uint64FromVarlink(v *int64, flagName string, defaultValue *uint64) CRUint64 { + cr := CRUint64{} + if v == nil { + cr.Val = 0 + if defaultValue != nil { + cr.Val = *defaultValue + } + cr.Changed = false + } else { + cr.Val = uint64(*v) + cr.Changed = true + } + cr.Flag = flagName + return cr +} + +func int64FromVarlink(v *int64, flagName string, defaultValue *int64) CRInt64 { + cr := CRInt64{} + if v == nil { + cr.Val = 0 + if defaultValue != nil { + cr.Val = *defaultValue + } + cr.Changed = false + } else { + cr.Val = *v + cr.Changed = true + } + cr.Flag = flagName + return cr +} + +func float64FromVarlink(v *float64, flagName string, defaultValue *float64) CRFloat64 { + cr := CRFloat64{} + if v == nil { + cr.Val = 0 + if defaultValue != nil { + cr.Val = *defaultValue + } + cr.Changed = false + } else { + cr.Val = *v + cr.Changed = true + } + cr.Flag = flagName + return cr +} + +func uintFromVarlink(v *int64, flagName string, defaultValue *uint) CRUint { + cr := CRUint{} + if v == nil { + cr.Val = 0 + if defaultValue != nil { + cr.Val = *defaultValue + } + cr.Changed = false + } else { + cr.Val = uint(*v) + cr.Changed = true + } + cr.Flag = flagName + return cr +} + +func stringArrayFromVarlink(v *[]string, flagName string, defaultValue *[]string) CRStringArray { + cr := CRStringArray{} + if v == nil { + cr.Val = []string{} + if defaultValue != nil { + cr.Val = *defaultValue + } + cr.Changed = false + } else { + cr.Val = *v + cr.Changed = true + } + cr.Flag = flagName + return cr +} + +func intFromVarlink(v *int64, flagName string, defaultValue *int) CRInt { + cr := CRInt{} + if v == nil { + if defaultValue != nil { + cr.Val = *defaultValue + } + cr.Val = 0 + cr.Changed = false + } else { + cr.Val = int(*v) + cr.Changed = true + } + cr.Flag = flagName + return cr +} + +// VarlinkCreateToGeneric creates a GenericCLIResults from the varlink create +// structure. +func VarlinkCreateToGeneric(opts iopodman.Create) GenericCLIResults { + + // TODO | WARN + // We do not get a default network over varlink. Unlike the other default values for some cli + // elements, it seems it gets set to the default anyway. + + m := make(map[string]GenericCLIResult) + m["add-host"] = stringSliceFromVarlink(opts.AddHost, "add-host", nil) + m["annotation"] = stringSliceFromVarlink(opts.Annotation, "annotation", nil) + m["attach"] = stringSliceFromVarlink(opts.Attach, "attach", nil) + m["blkio-weight"] = stringFromVarlink(opts.BlkioWeight, "blkio-weight", nil) + m["blkio-weight-device"] = stringSliceFromVarlink(opts.BlkioWeightDevice, "blkio-weight-device", nil) + m["cap-add"] = stringSliceFromVarlink(opts.CapAdd, "cap-add", nil) + m["cap-drop"] = stringSliceFromVarlink(opts.CapDrop, "cap-drop", nil) + m["cgroup-parent"] = stringFromVarlink(opts.CgroupParent, "cgroup-parent", nil) + m["cidfile"] = stringFromVarlink(opts.CidFile, "cidfile", nil) + m["conmon-pidfile"] = stringFromVarlink(opts.ConmonPidfile, "conmon-file", nil) + m["cpu-period"] = uint64FromVarlink(opts.CpuPeriod, "cpu-period", nil) + m["cpu-quota"] = int64FromVarlink(opts.CpuQuota, "quota", nil) + m["cpu-rt-period"] = uint64FromVarlink(opts.CpuRtPeriod, "cpu-rt-period", nil) + m["cpu-rt-runtime"] = int64FromVarlink(opts.CpuRtRuntime, "cpu-rt-quota", nil) + m["cpu-shares"] = uint64FromVarlink(opts.CpuShares, "cpu-shares", nil) + m["cpus"] = float64FromVarlink(opts.Cpus, "cpus", nil) + m["cpuset-cpus"] = stringFromVarlink(opts.CpuSetCpus, "cpuset-cpus", nil) + m["cpuset-mems"] = stringFromVarlink(opts.CpuSetMems, "cpuset-mems", nil) + m["detach"] = boolFromVarlink(opts.Detach, "detach", false) + m["detach-keys"] = stringFromVarlink(opts.DetachKeys, "detach-keys", nil) + m["device"] = stringSliceFromVarlink(opts.Device, "device", nil) + m["device-read-bps"] = stringSliceFromVarlink(opts.DeviceReadBps, "device-read-bps", nil) + m["device-read-iops"] = stringSliceFromVarlink(opts.DeviceReadIops, "device-read-iops", nil) + m["device-write-bps"] = stringSliceFromVarlink(opts.DeviceWriteBps, "write-device-bps", nil) + m["device-write-iops"] = stringSliceFromVarlink(opts.DeviceWriteIops, "write-device-iops", nil) + m["dns"] = stringSliceFromVarlink(opts.Dns, "dns", nil) + m["dns-opt"] = stringSliceFromVarlink(opts.DnsOpt, "dns-opt", nil) + m["dns-search"] = stringSliceFromVarlink(opts.DnsSearch, "dns-search", nil) + m["entrypoint"] = stringFromVarlink(opts.Entrypoint, "entrypoint", nil) + m["env"] = stringArrayFromVarlink(opts.Env, "env", nil) + m["env-file"] = stringSliceFromVarlink(opts.EnvFile, "env-file", nil) + m["expose"] = stringSliceFromVarlink(opts.Expose, "expose", nil) + m["gidmap"] = stringSliceFromVarlink(opts.Gidmap, "gidmap", nil) + m["group-add"] = stringSliceFromVarlink(opts.Groupadd, "group-add", nil) + m["healthcheck-command"] = stringFromVarlink(opts.HealthcheckCommand, "healthcheck-command", nil) + m["healthcheck-interval"] = stringFromVarlink(opts.HealthcheckInterval, "healthcheck-interval", &cliconfig.DefaultHealthCheckInterval) + m["healthcheck-retries"] = uintFromVarlink(opts.HealthcheckRetries, "healthcheck-retries", &cliconfig.DefaultHealthCheckRetries) + m["healthcheck-start-period"] = stringFromVarlink(opts.HealthcheckStartPeriod, "healthcheck-start-period", &cliconfig.DefaultHealthCheckStartPeriod) + m["healthcheck-timeout"] = stringFromVarlink(opts.HealthcheckTimeout, "healthcheck-timeout", &cliconfig.DefaultHealthCheckTimeout) + m["hostname"] = stringFromVarlink(opts.Hostname, "hostname", nil) + m["image-volume"] = stringFromVarlink(opts.ImageVolume, "image-volume", &cliconfig.DefaultImageVolume) + m["init"] = boolFromVarlink(opts.Init, "init", false) + m["init-path"] = stringFromVarlink(opts.InitPath, "init-path", nil) + m["interactive"] = boolFromVarlink(opts.Interactive, "interactive", false) + m["ip"] = stringFromVarlink(opts.Ip, "ip", nil) + m["ipc"] = stringFromVarlink(opts.Ipc, "ipc", nil) + m["kernel-memory"] = stringFromVarlink(opts.KernelMemory, "kernel-memory", nil) + m["label"] = stringArrayFromVarlink(opts.Label, "label", nil) + m["label-file"] = stringSliceFromVarlink(opts.LabelFile, "label-file", nil) + m["log-driver"] = stringFromVarlink(opts.LogDriver, "log-driver", nil) + m["log-opt"] = stringSliceFromVarlink(opts.LogOpt, "log-opt", nil) + m["mac-address"] = stringFromVarlink(opts.MacAddress, "mac-address", nil) + m["memory"] = stringFromVarlink(opts.Memory, "memory", nil) + m["memory-reservation"] = stringFromVarlink(opts.MemoryReservation, "memory-reservation", nil) + m["memory-swap"] = stringFromVarlink(opts.MemorySwap, "memory-swap", nil) + m["memory-swappiness"] = int64FromVarlink(opts.MemorySwappiness, "memory-swappiness", nil) + m["name"] = stringFromVarlink(opts.Name, "name", nil) + m["net"] = stringFromVarlink(opts.Net, "net", nil) + m["network"] = stringFromVarlink(opts.Network, "network", nil) + m["no-hosts"] = boolFromVarlink(opts.NoHosts, "no-hosts", false) + m["oom-kill-disable"] = boolFromVarlink(opts.OomKillDisable, "oon-kill-disable", false) + m["oom-score-adj"] = intFromVarlink(opts.OomScoreAdj, "oom-score-adj", nil) + m["pid"] = stringFromVarlink(opts.Pid, "pid", nil) + m["pids-limit"] = int64FromVarlink(opts.PidsLimit, "pids-limit", nil) + m["pod"] = stringFromVarlink(opts.Pod, "pod", nil) + m["privileged"] = boolFromVarlink(opts.Privileged, "privileged", false) + m["publish"] = stringSliceFromVarlink(opts.Publish, "publish", nil) + m["publish-all"] = boolFromVarlink(opts.PublishAll, "publish-all", false) + m["quiet"] = boolFromVarlink(opts.Quiet, "quiet", false) + m["read-only"] = boolFromVarlink(opts.Readonly, "read-only", false) + m["restart"] = stringFromVarlink(opts.Restart, "restart", nil) + m["rm"] = boolFromVarlink(opts.Rm, "rm", false) + m["rootfs"] = boolFromVarlink(opts.Rootfs, "rootfs", false) + m["security-opt"] = stringArrayFromVarlink(opts.SecurityOpt, "security-opt", nil) + m["shm-size"] = stringFromVarlink(opts.ShmSize, "shm-size", &cliconfig.DefaultShmSize) + m["stop-signal"] = stringFromVarlink(opts.StopSignal, "stop-signal", nil) + m["stop-timeout"] = uintFromVarlink(opts.StopTimeout, "stop-timeout", nil) + m["storage-opt"] = stringSliceFromVarlink(opts.StorageOpt, "storage-opt", nil) + m["subgidname"] = stringFromVarlink(opts.Subgidname, "subgidname", nil) + m["subuidname"] = stringFromVarlink(opts.Subuidname, "subuidname", nil) + m["sysctl"] = stringSliceFromVarlink(opts.Sysctl, "sysctl", nil) + m["systemd"] = boolFromVarlink(opts.Systemd, "systemd", cliconfig.DefaultSystemD) + m["tmpfs"] = stringSliceFromVarlink(opts.Tmpfs, "tmpfs", nil) + m["tty"] = boolFromVarlink(opts.Tty, "tty", false) + m["uidmap"] = stringSliceFromVarlink(opts.Uidmap, "uidmap", nil) + m["ulimit"] = stringSliceFromVarlink(opts.Ulimit, "ulimit", nil) + m["user"] = stringFromVarlink(opts.User, "user", nil) + m["userns"] = stringFromVarlink(opts.Userns, "userns", nil) + m["uts"] = stringFromVarlink(opts.Uts, "uts", nil) + m["mount"] = stringArrayFromVarlink(opts.Mount, "mount", nil) + m["volume"] = stringArrayFromVarlink(opts.Volume, "volume", nil) + m["volumes-from"] = stringSliceFromVarlink(opts.VolumesFrom, "volumes-from", nil) + m["workdir"] = stringFromVarlink(opts.WorkDir, "workdir", nil) + + gcli := GenericCLIResults{m, opts.Args} + return gcli +} + +// Find returns a flag from a GenericCLIResults by name +func (g GenericCLIResults) Find(name string) GenericCLIResult { + result, ok := g.results[name] + if ok { + return result + } + panic(errors.Errorf("unable to find generic flag for varlink %s", name)) +} diff --git a/cmd/podman/start.go b/cmd/podman/start.go index d17a78268..7d97319dd 100644 --- a/cmd/podman/start.go +++ b/cmd/podman/start.go @@ -7,6 +7,7 @@ import ( "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/cmd/podman/libpodruntime" "github.com/containers/libpod/libpod" + "github.com/containers/libpod/pkg/adapter" opentracing "github.com/opentracing/opentracing-go" "github.com/pkg/errors" "github.com/sirupsen/logrus" @@ -109,7 +110,7 @@ func startCmd(c *cliconfig.StartValues) error { // attach to the container and also start it not already running // If the container is in a pod, also set to recursively start dependencies - err = startAttachCtr(ctr, os.Stdout, os.Stderr, inputStream, c.DetachKeys, sigProxy, !ctrRunning, ctr.PodID() != "") + err = adapter.StartAttachCtr(ctx, ctr, os.Stdout, os.Stderr, inputStream, c.DetachKeys, sigProxy, !ctrRunning, ctr.PodID() != "") if errors.Cause(err) == libpod.ErrDetach { // User manually detached // Exit cleanly immediately @@ -133,7 +134,7 @@ func startCmd(c *cliconfig.StartValues) error { if err != nil { return err } - ctrExitCode, err := readExitFile(rtc.TmpDir, ctr.ID()) + ctrExitCode, err := adapter.ReadExitFile(rtc.TmpDir, ctr.ID()) if err != nil { logrus.Errorf("Cannot get exit code: %v", err) exitCode = 127 diff --git a/cmd/podman/stop.go b/cmd/podman/stop.go index 2a1470ad0..e27be64f6 100644 --- a/cmd/podman/stop.go +++ b/cmd/podman/stop.go @@ -7,7 +7,6 @@ import ( "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/libpod" "github.com/containers/libpod/pkg/adapter" - "github.com/containers/libpod/pkg/rootless" "github.com/opentracing/opentracing-go" "github.com/pkg/errors" "github.com/spf13/cobra" @@ -59,7 +58,6 @@ func stopCmd(c *cliconfig.StopValues) error { defer span.Finish() } - rootless.SetSkipStorageSetup(true) runtime, err := adapter.GetRuntime(&c.PodmanCommand) if err != nil { return errors.Wrapf(err, "could not get runtime") diff --git a/cmd/podman/top.go b/cmd/podman/top.go index 2512631c1..5d394d2d6 100644 --- a/cmd/podman/top.go +++ b/cmd/podman/top.go @@ -9,7 +9,6 @@ import ( "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/cmd/podman/libpodruntime" "github.com/containers/libpod/libpod" - "github.com/containers/libpod/pkg/rootless" "github.com/pkg/errors" "github.com/spf13/cobra" ) @@ -77,7 +76,6 @@ func topCmd(c *cliconfig.TopValues) error { return errors.Errorf("you must provide the name or id of a running container") } - rootless.SetSkipStorageSetup(true) runtime, err := libpodruntime.GetRuntime(&c.PodmanCommand) if err != nil { return errors.Wrapf(err, "error creating libpod runtime") @@ -104,18 +102,6 @@ func topCmd(c *cliconfig.TopValues) error { if conStat != libpod.ContainerStateRunning { return errors.Errorf("top can only be used on running containers") } - - pid, err := container.PID() - if err != nil { - return err - } - became, ret, err := rootless.JoinNS(uint(pid), 0) - if err != nil { - return err - } - if became { - os.Exit(ret) - } psOutput, err := container.GetContainerPidInformation(descriptors) if err != nil { return err diff --git a/cmd/podman/tree.go b/cmd/podman/tree.go index c56e35aef..371e88495 100644 --- a/cmd/podman/tree.go +++ b/cmd/podman/tree.go @@ -5,9 +5,9 @@ import ( "fmt" "github.com/containers/libpod/cmd/podman/cliconfig" - "github.com/containers/libpod/cmd/podman/libpodruntime" "github.com/containers/libpod/libpod/image" - units "github.com/docker/go-units" + "github.com/containers/libpod/pkg/adapter" + "github.com/docker/go-units" "github.com/pkg/errors" "github.com/spf13/cobra" ) @@ -41,16 +41,6 @@ func init() { treeCommand.Flags().BoolVar(&treeCommand.WhatRequires, "whatrequires", false, "Show all child images and layers of the specified image") } -// infoImage keep information of Image along with all associated layers -type infoImage struct { - // id of image - id string - // tags of image - tags []string - // layers stores all layers of image. - layers []image.LayerInfo -} - func treeCmd(c *cliconfig.TreeValues) error { args := c.InputArgs if len(args) == 0 { @@ -60,46 +50,33 @@ func treeCmd(c *cliconfig.TreeValues) error { return errors.Errorf("you must provide at most 1 argument") } - runtime, err := libpodruntime.GetRuntime(&c.PodmanCommand) + runtime, err := adapter.GetRuntime(&c.PodmanCommand) if err != nil { return errors.Wrapf(err, "error creating libpod runtime") } defer runtime.Shutdown(false) - - img, err := runtime.ImageRuntime().NewFromLocal(args[0]) + imageInfo, layerInfoMap, img, err := runtime.Tree(c) if err != nil { return err } + return printTree(imageInfo, layerInfoMap, img, c.WhatRequires) +} - // Fetch map of image-layers, which is used for printing output. - layerInfoMap, err := image.GetLayersMapWithImageInfo(runtime.ImageRuntime()) - if err != nil { - return errors.Wrapf(err, "error while retriving layers of image %q", img.InputName) - } - - // Create an imageInfo and fill the image and layer info - imageInfo := &infoImage{ - id: img.ID(), - tags: img.Names(), - } - +func printTree(imageInfo *image.InfoImage, layerInfoMap map[string]*image.LayerInfo, img *adapter.ContainerImage, whatRequires bool) error { size, err := img.Size(context.Background()) if err != nil { - return errors.Wrapf(err, "error while retriving image size") + return err } - fmt.Printf("Image ID: %s\n", imageInfo.id[:12]) - fmt.Printf("Tags:\t %s\n", imageInfo.tags) + + fmt.Printf("Image ID: %s\n", imageInfo.ID[:12]) + fmt.Printf("Tags:\t %s\n", imageInfo.Tags) fmt.Printf("Size:\t %v\n", units.HumanSizeWithPrecision(float64(*size), 4)) fmt.Printf(fmt.Sprintf("Image Layers\n")) - if !c.WhatRequires { + if !whatRequires { // fill imageInfo with layers associated with image. // the layers will be filled such that // (Start)RootLayer->...intermediate Parent Layer(s)-> TopLayer(End) - err := buildImageHierarchyMap(imageInfo, layerInfoMap, img.TopLayer()) - if err != nil { - return err - } // Build output from imageInfo into buffer printImageHierarchy(imageInfo) @@ -108,30 +85,8 @@ func treeCmd(c *cliconfig.TreeValues) error { // the layers will be filled such that // (Start)TopLayer->...intermediate Child Layer(s)-> Child TopLayer(End) // (Forks)... intermediate Child Layer(s) -> Child Top Layer(End) - err := printImageChildren(layerInfoMap, img.TopLayer(), "", true) - if err != nil { - return err - } - } - - return nil -} - -// Stores hierarchy of images such that all parent layers using which image is built are stored in imageInfo -// Layers are added such that (Start)RootLayer->...intermediate Parent Layer(s)-> TopLayer(End) -func buildImageHierarchyMap(imageInfo *infoImage, layerMap map[string]*image.LayerInfo, layerID string) error { - if layerID == "" { - return nil - } - ll, ok := layerMap[layerID] - if !ok { - return fmt.Errorf("lookup error: layerid %s not found", layerID) + return printImageChildren(layerInfoMap, img.TopLayer(), "", true) } - if err := buildImageHierarchyMap(imageInfo, layerMap, ll.ParentID); err != nil { - return err - } - - imageInfo.layers = append(imageInfo.layers, *ll) return nil } @@ -175,14 +130,14 @@ func printImageChildren(layerMap map[string]*image.LayerInfo, layerID string, pr } // prints the layers info of image -func printImageHierarchy(imageInfo *infoImage) { - for count, l := range imageInfo.layers { +func printImageHierarchy(imageInfo *image.InfoImage) { + for count, l := range imageInfo.Layers { var tags string intend := middleItem if len(l.RepoTags) > 0 { tags = fmt.Sprintf(" Top Layer of: %s", l.RepoTags) } - if count == len(imageInfo.layers)-1 { + if count == len(imageInfo.Layers)-1 { intend = lastItem } fmt.Printf("%s ID: %s Size: %7v%s\n", intend, l.ID[:12], units.HumanSizeWithPrecision(float64(l.Size), 4), tags) diff --git a/cmd/podman/utils.go b/cmd/podman/utils.go index 45d081512..c763940db 100644 --- a/cmd/podman/utils.go +++ b/cmd/podman/utils.go @@ -1,205 +1,11 @@ package main import ( - "context" "fmt" - "github.com/spf13/pflag" - "os" - gosignal "os/signal" - "github.com/containers/libpod/cmd/podman/cliconfig" - "github.com/containers/libpod/libpod" - "github.com/docker/docker/pkg/signal" - "github.com/docker/docker/pkg/term" - "github.com/pkg/errors" - "github.com/sirupsen/logrus" - "golang.org/x/crypto/ssh/terminal" - "k8s.io/client-go/tools/remotecommand" + "github.com/spf13/pflag" ) -type RawTtyFormatter struct { -} - -// Start (if required) and attach to a container -func startAttachCtr(ctr *libpod.Container, stdout, stderr, stdin *os.File, detachKeys string, sigProxy bool, startContainer bool, recursive bool) error { - ctx := context.Background() - resize := make(chan remotecommand.TerminalSize) - - haveTerminal := terminal.IsTerminal(int(os.Stdin.Fd())) - - // Check if we are attached to a terminal. If we are, generate resize - // events, and set the terminal to raw mode - if haveTerminal && ctr.Spec().Process.Terminal { - logrus.Debugf("Handling terminal attach") - - subCtx, cancel := context.WithCancel(ctx) - defer cancel() - - resizeTty(subCtx, resize) - - oldTermState, err := term.SaveState(os.Stdin.Fd()) - if err != nil { - return errors.Wrapf(err, "unable to save terminal state") - } - - logrus.SetFormatter(&RawTtyFormatter{}) - term.SetRawTerminal(os.Stdin.Fd()) - - defer restoreTerminal(oldTermState) - } - - streams := new(libpod.AttachStreams) - streams.OutputStream = stdout - streams.ErrorStream = stderr - streams.InputStream = stdin - streams.AttachOutput = true - streams.AttachError = true - streams.AttachInput = true - - if stdout == nil { - logrus.Debugf("Not attaching to stdout") - streams.AttachOutput = false - } - if stderr == nil { - logrus.Debugf("Not attaching to stderr") - streams.AttachError = false - } - if stdin == nil { - logrus.Debugf("Not attaching to stdin") - streams.AttachInput = false - } - - if !startContainer { - if sigProxy { - ProxySignals(ctr) - } - - return ctr.Attach(streams, detachKeys, resize) - } - - attachChan, err := ctr.StartAndAttach(getContext(), streams, detachKeys, resize, recursive) - if err != nil { - return err - } - - if sigProxy { - ProxySignals(ctr) - } - - if stdout == nil && stderr == nil { - fmt.Printf("%s\n", ctr.ID()) - } - - err = <-attachChan - if err != nil { - return errors.Wrapf(err, "error attaching to container %s", ctr.ID()) - } - - return nil -} - -// getResize returns a TerminalSize command matching stdin's current -// size on success, and nil on errors. -func getResize() *remotecommand.TerminalSize { - winsize, err := term.GetWinsize(os.Stdin.Fd()) - if err != nil { - logrus.Warnf("Could not get terminal size %v", err) - return nil - } - return &remotecommand.TerminalSize{ - Width: winsize.Width, - Height: winsize.Height, - } -} - -// Helper for prepareAttach - set up a goroutine to generate terminal resize events -func resizeTty(ctx context.Context, resize chan remotecommand.TerminalSize) { - sigchan := make(chan os.Signal, 1) - gosignal.Notify(sigchan, signal.SIGWINCH) - go func() { - defer close(resize) - // Update the terminal size immediately without waiting - // for a SIGWINCH to get the correct initial size. - resizeEvent := getResize() - for { - if resizeEvent == nil { - select { - case <-ctx.Done(): - return - case <-sigchan: - resizeEvent = getResize() - } - } else { - select { - case <-ctx.Done(): - return - case <-sigchan: - resizeEvent = getResize() - case resize <- *resizeEvent: - resizeEvent = nil - } - } - } - }() -} - -func restoreTerminal(state *term.State) error { - logrus.SetFormatter(&logrus.TextFormatter{}) - return term.RestoreTerminal(os.Stdin.Fd(), state) -} - -func (f *RawTtyFormatter) Format(entry *logrus.Entry) ([]byte, error) { - textFormatter := logrus.TextFormatter{} - bytes, err := textFormatter.Format(entry) - - if err == nil { - bytes = append(bytes, '\r') - } - - return bytes, err -} - -// For pod commands that have a latest and all flag, getPodsFromContext gets -// pods the user specifies. If there's an error before getting pods, the pods slice -// will be empty and error will be not nil. If an error occured after, the pod slice -// will hold all of the successful pods, and error will hold the last error. -// The remaining errors will be logged. On success, pods will hold all pods and -// error will be nil. -func getPodsFromContext(c *cliconfig.PodmanCommand, r *libpod.Runtime) ([]*libpod.Pod, error) { - args := c.InputArgs - var pods []*libpod.Pod - var lastError error - var err error - - if c.Bool("all") { - pods, err = r.GetAllPods() - if err != nil { - return nil, errors.Wrapf(err, "unable to get running pods") - } - } - - if c.Bool("latest") { - pod, err := r.GetLatestPod() - if err != nil { - return nil, errors.Wrapf(err, "unable to get latest pod") - } - pods = append(pods, pod) - } - - for _, i := range args { - pod, err := r.LookupPod(i) - if err != nil { - if lastError != nil { - logrus.Errorf("%q", lastError) - } - lastError = errors.Wrapf(err, "unable to find pod %s", i) - continue - } - pods = append(pods, pod) - } - return pods, lastError -} - //printParallelOutput takes the map of parallel worker results and outputs them // to stdout func printParallelOutput(m map[string]error, errCount int) error { diff --git a/cmd/podman/varlink/io.podman.varlink b/cmd/podman/varlink/io.podman.varlink index 5e996f46b..9098a9297 100644 --- a/cmd/podman/varlink/io.podman.varlink +++ b/cmd/podman/varlink/io.podman.varlink @@ -68,7 +68,8 @@ type Image ( virtualSize: int, containers: int, labels: [string]string, - isParent: bool + isParent: bool, + topLayer: string ) # ImageHistory describes the returned structure from ImageHistory. @@ -224,117 +225,104 @@ type Sockets( control_socket: string ) -# Create is an input structure for creating containers. It closely resembles the -# CreateConfig structure in libpod/pkg/spec. +# Create is an input structure for creating containers. type Create ( args: []string, - cap_add: []string, - cap_drop: []string, - conmon_pidfile: string, - cgroup_parent: string, - command: []string, - detach: bool, - devices: []string, - dns_opt: []string, - dns_search: []string, - dns_servers: []string, - entrypoint: []string, - env: [string]string, - exposed_ports: []string, - gidmap: []string, - group_add: []string, - host_add: []string, - hostname: string, - image: string, - image_id: string, - init: bool, - init_path: string, - builtin_imgvolumes: []string, - id_mappings: IDMappingOptions, - image_volume_type: string, - interactive: bool, - ipc_mode: string, - labels: [string]string, - log_driver: string, - log_driver_opt: []string, - name: string, - net_mode: string, - network: string, - pid_mode: string, - pod: string, - privileged: bool, - publish: []string, - publish_all: bool, - quiet: bool, - readonly_rootfs: bool, - resources: CreateResourceConfig, - rm: bool, - shm_dir: string, - stop_signal: int, - stop_timeout: int, - subuidmap: string, - subgidmap: string, - subuidname: string, - subgidname: string, - sys_ctl: [string]string, - tmpfs: []string, - tty: bool, - uidmap: []string, - userns_mode: string, - user: string, - uts_mode: string, - volumes: []string, - work_dir: string, - mount_label: string, - process_label: string, - no_new_privs: bool, - apparmor_profile: string, - seccomp_profile_path: string, - security_opts: []string -) - -# CreateResourceConfig is an input structure used to describe host attributes during -# container creation. It is only valid inside a [Create](#Create) type. -type CreateResourceConfig ( - blkio_weight: int, - blkio_weight_device: []string, - cpu_period: int, - cpu_quota: int, - cpu_rt_period: int, - cpu_rt_runtime: int, - cpu_shares: int, - cpus: float, - cpuset_cpus: string, - cpuset_mems: string, - device_read_bps: []string, - device_read_iops: []string, - device_write_bps: []string, - device_write_iops: []string, - disable_oomkiller: bool, - kernel_memory: int, - memory: int, - memory_reservation: int, - memory_swap: int, - memory_swappiness: int, - oom_score_adj: int, - pids_limit: int, - shm_size: int, - ulimit: []string -) - -# IDMappingOptions is an input structure used to described ids during container creation. -type IDMappingOptions ( - host_uid_mapping: bool, - host_gid_mapping: bool, - uid_map: IDMap, - gid_map: IDMap -) - -# IDMap is used to describe user name spaces during container creation -type IDMap ( - container_id: int, - host_id: int, - size: int + addHost: ?[]string, + annotation: ?[]string, + attach: ?[]string, + blkioWeight: ?string, + blkioWeightDevice: ?[]string, + capAdd: ?[]string, + capDrop: ?[]string, + cgroupParent: ?string, + cidFile: ?string, + conmonPidfile: ?string, + command: ?[]string, + cpuPeriod: ?int, + cpuQuota: ?int, + cpuRtPeriod: ?int, + cpuRtRuntime: ?int, + cpuShares: ?int, + cpus: ?float, + cpuSetCpus: ?string, + cpuSetMems: ?string, + detach: ?bool, + detachKeys: ?string, + device: ?[]string, + deviceReadBps: ?[]string, + deviceReadIops: ?[]string, + deviceWriteBps: ?[]string, + deviceWriteIops: ?[]string, + dns: ?[]string, + dnsOpt: ?[]string, + dnsSearch: ?[]string, + dnsServers: ?[]string, + entrypoint: ?string, + env: ?[]string, + envFile: ?[]string, + expose: ?[]string, + gidmap: ?[]string, + groupadd: ?[]string, + healthcheckCommand: ?string, + healthcheckInterval: ?string, + healthcheckRetries: ?int, + healthcheckStartPeriod: ?string, + healthcheckTimeout:?string, + hostname: ?string, + imageVolume: ?string, + init: ?bool, + initPath: ?string, + interactive: ?bool, + ip: ?string, + ipc: ?string, + kernelMemory: ?string, + label: ?[]string, + labelFile: ?[]string, + logDriver: ?string, + logOpt: ?[]string, + macAddress: ?string, + memory: ?string, + memoryReservation: ?string, + memorySwap: ?string, + memorySwappiness: ?int, + name: ?string, + net: ?string, + network: ?string, + noHosts: ?bool, + oomKillDisable: ?bool, + oomScoreAdj: ?int, + pid: ?string, + pidsLimit: ?int, + pod: ?string, + privileged: ?bool, + publish: ?[]string, + publishAll: ?bool, + quiet: ?bool, + readonly: ?bool, + restart: ?string, + rm: ?bool, + rootfs: ?bool, + securityOpt: ?[]string, + shmSize: ?string, + stopSignal: ?string, + stopTimeout: ?int, + storageOpt: ?[]string, + subuidname: ?string, + subgidname: ?string, + sysctl: ?[]string, + systemd: ?bool, + tmpfs: ?[]string, + tty: ?bool, + uidmap: ?[]string, + ulimit: ?[]string, + user: ?string, + userns: ?string, + uts: ?string, + mount: ?[]string, + volume: ?[]string, + volumesFrom: ?[]string, + workDir: ?string ) # BuildOptions are are used to describe describe physical attributes of the build @@ -461,6 +449,13 @@ type Event( type: string ) +type DiffInfo( + # path that is different + path: string, + # Add, Delete, Modify + changeType: string +) + # GetVersion returns version and build information of the podman service method GetVersion() -> ( version: string, @@ -490,16 +485,7 @@ method GetContainer(id: string) -> (container: Container) # user environment, results might differ from what you expect. method GetContainersByContext(all: bool, latest: bool, args: []string) -> (containers: []string) -# CreateContainer creates a new container from an image. It uses a [Create](#Create) type for input. The minimum -# input required for CreateContainer is an image name. If the image name is not found, an [ImageNotFound](#ImageNotFound) -# error will be returned. Otherwise, the ID of the newly created container will be returned. -# #### Example -# ~~~ -# $ varlink call unix:/run/podman/io.podman/io.podman.CreateContainer '{"create": {"image": "alpine"}}' -# { -# "container": "8759dafbc0a4dc3bcfb57eeb72e4331eb73c5cc09ab968e65ce45b9ad5c4b6bb" -# } -# ~~~ +# CreateContainer creates a new container from an image. It uses a [Create](#Create) type for input. method CreateContainer(create: Create) -> (container: string) # InspectContainer data takes a name or ID of a container returns the inspection @@ -1122,6 +1108,9 @@ method ContainerStateData(name: string) -> (config: string) # development of Podman only and generally should not be used. method PodStateData(name: string) -> (config: string) +# This call is for the development of Podman only and should not be used. +method CreateFromCC(in: []string) -> (id: string) + # Sendfile allows a remote client to send a file to the host method SendFile(type: string, length: int) -> (file_handle: string) @@ -1154,6 +1143,15 @@ method LoadImage(name: string, inputFile: string, quiet: bool, deleteFile: bool) # GetEvents returns known libpod events filtered by the options provided. method GetEvents(filter: []string, since: string, until: string) -> (events: Event) +# Diff returns a diff between libpod objects +method Diff(name: string) -> (diffs: []DiffInfo) + +# GetLayersMapWithImageInfo is for the development of Podman and should not be used. +method GetLayersMapWithImageInfo() -> (layerMap: string) + +# BuildImageHierarchyMap is for the development of Podman and should not be used. +method BuildImageHierarchyMap(name: string) -> (imageInfo: string) + # ImageNotFound means the image could not be found by the provided name or ID in local storage. error ImageNotFound (id: string, reason: string) diff --git a/contrib/cirrus/README.md b/contrib/cirrus/README.md index 0dabf5df6..ea358d2d7 100644 --- a/contrib/cirrus/README.md +++ b/contrib/cirrus/README.md @@ -63,26 +63,26 @@ task (pass or fail) is set based on the exit status of the last script to execut Total execution time is capped at 2-hours (includes all the above) but this script normally completes in less than an hour. -### ``rootless_testing`` Task +### ``special_testing`` Task + +This task exercises podman under specialized environments or conditions. +The specific differences from the ``testing`` task depend upon the +contents of the ``$SPECIALMODE`` environment variable. + +| Value | Meaning | +| rootless | Setup a regular user to build/run integration tests. | +| in_podman | Setup a container image, build/run integration tests inside container | ***N/B: Steps below are performed by automation*** 1. After `gating` passes, spin up one VM per - `matrix: image_name` item. Once accessible, ``ssh`` - into each VM as the `root` user. + `matrix: image_name` item. + +2. ``setup_environment.sh``: Mostly the same as + in ``testing`` task, then specialized depending on ``$SPECIALMODE``. + +3. Which tests and how they execute depends on ``$SPECIALMODE``. -2. ``setup_environment.sh``: Configure root's `.bash_profile` - the same as for other tasks. However, also add a regular - user account, chown all the source code to them. Set up - fresh ssh pub/priv. keys for the root user, adding the - public part to the user's `authorized_keys` file. - -3. As root, call ssh to connect to localhost as the user, - and run the ``rootless_test.sh`` script from the source - tree. This is needed so the user has a clean process tree - and environment - i.e. without `sudo`, `su`, `runuser`, - etc. in the mix. From here, all testing as the user may - be performed. ### ``optional_testing`` Task diff --git a/.papr.sh b/contrib/cirrus/container_test.sh index c5aada904..e6c1a3a47 100644 --- a/.papr.sh +++ b/contrib/cirrus/container_test.sh @@ -1,7 +1,7 @@ #!/bin/bash set -xeuo pipefail -export GOPATH=/go +export GOPATH=/var/tmp/go export PATH=$HOME/gopath/bin:$PATH:$GOPATH/bin export GOSRC=$GOPATH/src/github.com/containers/libpod @@ -125,7 +125,7 @@ fi # Run integration tests if [ $integrationtest -eq 1 ]; then make TAGS="${TAGS}" test-binaries - make varlink_generate GOPATH=/go - make ginkgo GOPATH=/go $INTEGRATION_TEST_ENVS - make ginkgo-remote GOPATH=/go $INTEGRATION_TEST_ENVS + make varlink_generate + make ginkgo $INTEGRATION_TEST_ENVS + make ginkgo-remote $INTEGRATION_TEST_ENVS fi diff --git a/contrib/cirrus/integration_test.sh b/contrib/cirrus/integration_test.sh index 58c8af289..8a2507f38 100755 --- a/contrib/cirrus/integration_test.sh +++ b/contrib/cirrus/integration_test.sh @@ -5,33 +5,64 @@ source $(dirname $0)/lib.sh req_env_var " GOSRC $GOSRC +SCRIPT_BASE $SCRIPT_BASE OS_RELEASE_ID $OS_RELEASE_ID OS_RELEASE_VER $OS_RELEASE_VER +CONTAINER_RUNTIME $CONTAINER_RUNTIME " -record_timestamp "integration test start" +exit_handler() { + set +ex + record_timestamp "integration test end" +} +trap exit_handler EXIT -clean_env +record_timestamp "integration test start" -set -x cd "$GOSRC" -case "${OS_RELEASE_ID}-${OS_RELEASE_VER}" in - ubuntu-18) - make install PREFIX=/usr ETCDIR=/etc - make test-binaries - SKIP_USERNS=1 make localintegration - ;; - fedora-29) ;& # Continue to the next item - fedora-28) ;& - centos-7) ;& - rhel-7) - make install PREFIX=/usr ETCDIR=/etc - make podman-remote - install bin/podman-remote /usr/bin - make test-binaries - make localintegration - ;; - *) bad_os_id_ver ;; -esac - -record_timestamp "integration test end" + +if [[ "$SPECIALMODE" == "in_podman" ]] +then + set -x + ${CONTAINER_RUNTIME} run --rm --privileged --net=host \ + -v $GOSRC:$GOSRC:Z \ + --workdir $GOSRC \ + -e "CGROUP_MANAGER=cgroupfs" \ + -e "STORAGE_OPTIONS=--storage-driver=vfs" \ + -e "CRIO_ROOT=$GOSRC" \ + -e "PODMAN_BINARY=/usr/bin/podman" \ + -e "CONMON_BINARY=/usr/libexec/podman/conmon" \ + -e "DIST=$OS_RELEASE_ID" \ + -e "CONTAINER_RUNTIME=$CONTAINER_RUNTIME" \ + ${OS_RELEASE_ID}podmanbuild bash $GOSRC/$SCRIPT_BASE/container_test.sh -b -i -t + + exit $? +elif [[ "$SPECIALMODE" == "rootless" ]] +then + req_env_var "ROOTLESS_USER $ROOTLESS_USER" + set -x + ssh $ROOTLESS_USER@localhost \ + -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -o CheckHostIP=no \ + $GOSRC/$SCRIPT_BASE/rootless_test.sh + exit $? +else + set -x + make + make install PREFIX=/usr ETCDIR=/etc + make test-binaries + clean_env + + case "${OS_RELEASE_ID}-${OS_RELEASE_VER}" in + ubuntu-18) ;; + fedora-29) ;& # Continue to the next item + fedora-28) ;& + centos-7) ;& + rhel-7) + make podman-remote + install bin/podman-remote /usr/bin + ;; + *) bad_os_id_ver ;; + esac + make localintegration + exit $? +fi diff --git a/contrib/cirrus/lib.sh b/contrib/cirrus/lib.sh index e941610e2..6c45b2c5d 100644 --- a/contrib/cirrus/lib.sh +++ b/contrib/cirrus/lib.sh @@ -18,6 +18,8 @@ CIRRUS_BUILD_ID=${CIRRUS_BUILD_ID:-DEADBEEF} # a human CIRRUS_BASE_SHA=${CIRRUS_BASE_SHA:-HEAD} CIRRUS_CHANGE_IN_REPO=${CIRRUS_CHANGE_IN_REPO:-FETCH_HEAD} TIMESTAMPS_FILEPATH="${TIMESTAMPS_FILEPATH:-/var/tmp/timestamps}" +SPECIALMODE="${SPECIALMODE:-none}" +export CONTAINER_RUNTIME=${CONTAINER_RUNTIME:-podman} if ! [[ "$PATH" =~ "/usr/local/bin" ]] then @@ -81,6 +83,7 @@ CIRRUS_USER_COLLABORATOR $CIRRUS_USER_COLLABORATOR CIRRUS_USER_PERMISSION $CIRRUS_USER_PERMISSION CIRRUS_WORKING_DIR $CIRRUS_WORKING_DIR CIRRUS_HTTP_CACHE_HOST $CIRRUS_HTTP_CACHE_HOST +SPECIALMODE $SPECIALMODE $(go env) PACKER_BUILDS $PACKER_BUILDS " | while read NAME VALUE @@ -127,15 +130,6 @@ bad_os_id_ver() { exit 42 } -run_rootless() { - if [[ -z "$ROOTLESS_USER" ]] - then - return 1 - else - return 0 - fi -} - stub() { echo "STUB: Pretending to do $1" } @@ -179,6 +173,13 @@ setup_rootless() { return 0 fi + # Only do this once + cd $GOSRC + make install.catatonit + go get github.com/onsi/ginkgo/ginkgo + go get github.com/onsi/gomega/... + dnf -y update runc + # Guarantee independence from specific values ROOTLESS_UID=$[RANDOM+1000] ROOTLESS_GID=$[RANDOM+1000] diff --git a/contrib/cirrus/rootless_test.sh b/contrib/cirrus/rootless_test.sh index d0e2ceb95..88b38f45b 100755 --- a/contrib/cirrus/rootless_test.sh +++ b/contrib/cirrus/rootless_test.sh @@ -12,9 +12,9 @@ OS_RELEASE_ID $OS_RELEASE_ID OS_RELEASE_VER $OS_RELEASE_VER " -if ! run_rootless +if [[ "$UID" == "0" ]] then - echo "Error: Expected rootless env. vars not set or empty" + echo "Error: Expected to be running as a regular user" exit 1 fi @@ -24,16 +24,9 @@ echo "Hello, my name is $USER and I live in $PWD can I be your friend?" record_timestamp "rootless test start" cd "$GOSRC" -case "${OS_RELEASE_ID}-${OS_RELEASE_VER}" in - ubuntu-18) ;& # Continue to the next item - fedora-29) ;& - fedora-28) - make - make varlink_generate - make test-binaries - make ginkgo - ;; - *) bad_os_id_ver ;; -esac +make +make varlink_generate +make test-binaries +make ginkgo record_timestamp "rootless test end" diff --git a/contrib/cirrus/setup_container_environment.sh b/contrib/cirrus/setup_container_environment.sh new file mode 100755 index 000000000..23df4fe8b --- /dev/null +++ b/contrib/cirrus/setup_container_environment.sh @@ -0,0 +1,23 @@ +#!/bin/bash +set -e + +source $(dirname $0)/lib.sh + +req_env_var " +GOSRC $GOSRC +OS_RELEASE_ID $OS_RELEASE_ID +CONTAINER_RUNTIME $CONTAINER_RUNTIME +" + +DIST=$OS_RELEASE_ID +IMAGE=${DIST}podmanbuild + +# Since CRIU 3.11 has been pushed to Fedora 28 the checkpoint/restore +# test cases are actually run. As CRIU uses iptables to lock and unlock +# the network during checkpoint and restore it needs the following two +# modules loaded. +modprobe ip6table_nat || : +modprobe iptable_nat || : + +# Build the test image +${CONTAINER_RUNTIME} build -t ${IMAGE} -f Dockerfile.${DIST} . diff --git a/contrib/cirrus/setup_environment.sh b/contrib/cirrus/setup_environment.sh index 96d0e1b55..55706954e 100755 --- a/contrib/cirrus/setup_environment.sh +++ b/contrib/cirrus/setup_environment.sh @@ -43,7 +43,6 @@ then "export OS_RELEASE_ID=\"$(os_release_id)\"" \ "export OS_RELEASE_VER=\"$(os_release_ver)\"" \ "export OS_REL_VER=\"$(os_release_id)-$(os_release_ver)\"" \ - "export ROOTLESS_USER=$ROOTLESS_USER" \ "export BUILT_IMAGE_SUFFIX=\"-$CIRRUS_REPO_NAME-${CIRRUS_CHANGE_IN_REPO:0:8}\"" \ "export GOPATH=\"/var/tmp/go\"" \ 'export PATH="$HOME/bin:$GOPATH/bin:/usr/local/bin:$PATH"' \ @@ -75,14 +74,17 @@ then # Reload to incorporate any changes from above source "$SCRIPT_BASE/lib.sh" - if run_rootless - then - setup_rootless - make install.catatonit - go get github.com/onsi/ginkgo/ginkgo - go get github.com/onsi/gomega/... - dnf -y update runc - fi + case "$SPECIALMODE" in + rootless) + X=$(echo "export ROOTLESS_USER='some${RANDOM}dude'" | \ + tee -a "$HOME/$ENVLIB") && eval "$X" && echo "$X" + setup_rootless + ;; + in_podman) # Assumed to be Fedora + dnf install -y podman buildah + $SCRIPT_BASE/setup_container_environment.sh + ;; + esac fi show_env_vars diff --git a/contrib/cirrus/unit_test.sh b/contrib/cirrus/unit_test.sh index fd9e82509..4ace19d10 100755 --- a/contrib/cirrus/unit_test.sh +++ b/contrib/cirrus/unit_test.sh @@ -15,17 +15,8 @@ clean_env set -x cd "$GOSRC" -case "${OS_RELEASE_ID}-${OS_RELEASE_VER}" in - ubuntu-18) ;& # Continue to the next item - fedora-29) ;& - fedora-28) ;& - centos-7) ;& - rhel-7) - make install.tools - make localunit - make - ;; - *) bad_os_id_ver ;; -esac +make install.tools +make localunit +make record_timestamp "unit test end" diff --git a/contrib/spec/podman.spec.in b/contrib/spec/podman.spec.in index 319bbe979..396304f8d 100644 --- a/contrib/spec/podman.spec.in +++ b/contrib/spec/podman.spec.in @@ -39,7 +39,7 @@ %global shortcommit_conmon %(c=%{commit_conmon}; echo ${c:0:7}) Name: podman -Version: 1.2.0 +Version: 1.3.0 Release: #COMMITDATE#.git%{shortcommit0}%{?dist} Summary: Manage Pods, Containers and Container Images License: ASL 2.0 diff --git a/docs/podman-container-checkpoint.1.md b/docs/podman-container-checkpoint.1.md index 666dbbb80..79dc12261 100644 --- a/docs/podman-container-checkpoint.1.md +++ b/docs/podman-container-checkpoint.1.md @@ -10,7 +10,7 @@ podman\-container\-checkpoint - Checkpoints one or more running containers Checkpoints all the processes in one or more containers. You may use container IDs or names as input. ## OPTIONS -**-k**, **--keep** +**--keep**, **-k** Keep all temporary log and statistics files created by CRIU during checkpointing. These files are not deleted if checkpointing fails for further debugging. If checkpointing succeeds these diff --git a/docs/podman-container-restore.1.md b/docs/podman-container-restore.1.md index e47d585cc..e41f7c1d8 100644 --- a/docs/podman-container-restore.1.md +++ b/docs/podman-container-restore.1.md @@ -10,7 +10,7 @@ podman\-container\-restore - Restores one or more running containers Restores a container from a checkpoint. You may use container IDs or names as input. ## OPTIONS -**-k**, **--keep** +**--keep**, **-k** Keep all temporary log and statistics files created by CRIU during checkpointing as well as restoring. These files are not deleted if restoring diff --git a/docs/podman-container-runlabel.1.md b/docs/podman-container-runlabel.1.md index 7fa9805e6..39d798804 100644 --- a/docs/podman-container-runlabel.1.md +++ b/docs/podman-container-runlabel.1.md @@ -76,11 +76,12 @@ The [username[:password]] to use to authenticate with the registry if required. If one or both values are not supplied, a command line prompt will appear and the value can be entered. The password is entered without echo. -**-h** **--help** +**--help** **-h** Print usage statement -**-n** **--name**="" - Use this name for creating content for the container. NAME will default to the IMAGENAME if it is not specified. +**--name** **-n**="" + +Use this name for creating content for the container. NAME will default to the IMAGENAME if it is not specified. **--quiet, -q** diff --git a/docs/podman-generate-kube.1.md b/docs/podman-generate-kube.1.md index b6fa78606..99029be90 100644 --- a/docs/podman-generate-kube.1.md +++ b/docs/podman-generate-kube.1.md @@ -5,7 +5,7 @@ podman-generate-kube - Generate Kubernetes YAML # SYNOPSIS -**podman generate kube** [*-s*][*--service*] *container* | *pod* +**podman generate kube** [*-s*|*--service*] *container* | *pod* # DESCRIPTION **podman generate kube** will generate Kubernetes Pod YAML (v1 specification) from a podman container or pod. Whether @@ -20,7 +20,8 @@ Note that the generated Kubernetes YAML file can be used to re-run the deploymen # OPTIONS: -**s** **--service** +**--service** **-s** + Generate a Kubernetes service object in addition to the Pods. ## Examples ## diff --git a/docs/podman-inspect.1.md b/docs/podman-inspect.1.md index 712891ad6..8b67c7dac 100644 --- a/docs/podman-inspect.1.md +++ b/docs/podman-inspect.1.md @@ -17,7 +17,7 @@ unspecified type. If a format is specified, the given template will be executed ## OPTIONS -**--type, t="TYPE"** +**--type, -t="TYPE"** Return JSON for the specified type. Type can be 'container', 'image' or 'all' (default: all) (Only meaningful when invoked as *podman inspect*) diff --git a/docs/podman-pod-kill.1.md b/docs/podman-pod-kill.1.md index 2a863d3d9..d617acd66 100644 --- a/docs/podman-pod-kill.1.md +++ b/docs/podman-pod-kill.1.md @@ -21,7 +21,7 @@ to run pods such as CRI-O, the last started pod could be from either of those me The latest option is not supported on the remote client. -**--signal, s** +**--signal, -s** Signal to send to the containers in the pod. For more information on Linux signals, refer to *man signal(7)*. diff --git a/docs/podman-pod-pause.1.md b/docs/podman-pod-pause.1.md index 418a9ee2a..0ed63a7f8 100644 --- a/docs/podman-pod-pause.1.md +++ b/docs/podman-pod-pause.1.md @@ -11,7 +11,7 @@ Pauses all the running processes in the containers of one or more pods. You may ## OPTIONS -**--all, a** +**--all, -a** Pause all pods. diff --git a/docs/podman-pod-rm.1.md b/docs/podman-pod-rm.1.md index aa26a1bbb..7cd7c26bc 100644 --- a/docs/podman-pod-rm.1.md +++ b/docs/podman-pod-rm.1.md @@ -11,7 +11,7 @@ podman\-pod\-rm - Remove one or more pods ## OPTIONS -**--all, a** +**--all, -a** Remove all pods. Can be used in conjunction with \-f as well. @@ -21,7 +21,7 @@ Instead of providing the pod name or ID, remove the last created pod. The latest option is not supported on the remote client. -**--force, f** +**--force, -f** Stop running containers and delete all stopped containers before removal of pod. diff --git a/docs/podman-pod-stop.1.md b/docs/podman-pod-stop.1.md index 338e04d67..787c672bd 100644 --- a/docs/podman-pod-stop.1.md +++ b/docs/podman-pod-stop.1.md @@ -21,7 +21,7 @@ Instead of providing the pod name or ID, stop the last created pod. The latest option is not supported on the remote client. -**--timeout, --time, t** +**--timeout, --time, -t** Timeout to wait before forcibly stopping the containers in the pod. diff --git a/docs/podman-pod-unpause.1.md b/docs/podman-pod-unpause.1.md index 1004e09f9..8930fde32 100644 --- a/docs/podman-pod-unpause.1.md +++ b/docs/podman-pod-unpause.1.md @@ -11,7 +11,7 @@ Unpauses all the paused processes in the containers of one or more pods. You ma ## OPTIONS -**--all, a** +**--all, -a** Unpause all pods. diff --git a/docs/podman-port.1.md b/docs/podman-port.1.md index 020a25d32..5adfae5f3 100644 --- a/docs/podman-port.1.md +++ b/docs/podman-port.1.md @@ -11,7 +11,7 @@ List port mappings for the *container* or lookup the public-facing port that is ## OPTIONS -**--all, a** +**--all, -a** List all known port mappings for running containers. When using this option, you cannot pass any container names or private ports/protocols as filters. diff --git a/docs/podman-rm.1.md b/docs/podman-rm.1.md index dc1729188..16d4027c9 100644 --- a/docs/podman-rm.1.md +++ b/docs/podman-rm.1.md @@ -13,11 +13,11 @@ podman\-container\-rm (podman\-rm) - Remove one or more containers ## OPTIONS -**--all, a** +**--all, -a** Remove all containers. Can be used in conjunction with -f as well. -**--force, f** +**--force, -f** Force the removal of running and paused containers. Forcing a containers removal also removes containers from container storage even if the container is not known to podman. @@ -32,7 +32,7 @@ The latest option is not supported on the remote client. **--volumes, -v** -Remove the volumes associated with the container. (Not yet implemented) +Remove the volumes associated with the container. ## EXAMPLE Remove a container by its name *mywebserver* diff --git a/docs/podman-volume-inspect.1.md b/docs/podman-volume-inspect.1.md index c22c80bb7..88cc3cf3e 100644 --- a/docs/podman-volume-inspect.1.md +++ b/docs/podman-volume-inspect.1.md @@ -15,7 +15,7 @@ existing volumes, use the **--all** flag. ## OPTIONS -**-a**, **--all**="" +**-a**, **--all** Inspect all volumes. diff --git a/docs/podman-volume-rm.1.md b/docs/podman-volume-rm.1.md index c23d7675c..8c3765235 100644 --- a/docs/podman-volume-rm.1.md +++ b/docs/podman-volume-rm.1.md @@ -21,7 +21,8 @@ Remove all volumes. **-f**, **--force**="" -Remove a volume by force, even if it is being used by a container +Remove a volume by force. +If it is being used by containers, the containers will be removed first. **--help** diff --git a/docs/podman-wait.1.md b/docs/podman-wait.1.md index 2d145527b..ed5e11ac7 100644 --- a/docs/podman-wait.1.md +++ b/docs/podman-wait.1.md @@ -17,7 +17,7 @@ After the container stops, the container's return code is printed. Print usage statement -**--interval, i**" +**--interval, -i**" Microseconds to wait before polling for completion **--latest, -l** diff --git a/docs/tutorials/podman_tutorial.md b/docs/tutorials/podman_tutorial.md index bfff90016..2abd9c50f 100644 --- a/docs/tutorials/podman_tutorial.md +++ b/docs/tutorials/podman_tutorial.md @@ -123,13 +123,14 @@ sudo make install PREFIX=/usr This sample container will run a very basic httpd server that serves only its index page. ```console -podman run -dt -e HTTPD_VAR_RUN=/var/run/httpd -e HTTPD_MAIN_CONF_D_PATH=/etc/httpd/conf.d \ +podman run -dt -p 8080:8080/tcp -e HTTPD_VAR_RUN=/var/run/httpd -e HTTPD_MAIN_CONF_D_PATH=/etc/httpd/conf.d \ -e HTTPD_MAIN_CONF_PATH=/etc/httpd/conf \ -e HTTPD_CONTAINER_SCRIPTS_PATH=/usr/share/container-scripts/httpd/ \ registry.fedoraproject.org/f27/httpd /usr/bin/run-httpd ``` Because the container is being run in detached mode, represented by the *-d* in the podman run command, podman -will print the container ID after it has run. +will print the container ID after it has run. Note that we use port forwarding to be able to +access the HTTP server. For successful running at least slirp4netns v0.3.0 is needed. ### Listing running containers The Podman *ps* command is used to list creating and running containers. @@ -140,10 +141,11 @@ podman ps Note: If you add *-a* to the *ps* command, Podman will show all containers. ### Inspecting a running container You can "inspect" a running container for metadata and details about itself. We can even use -the inspect subcommand to see what IP address was assigned to the container. +the inspect subcommand to see what IP address was assigned to the container. As the container is running in rootless mode, an IP address is not assigned and the value will be listed as "none" in the output from inspect. ```console -$ sudo podman inspect -l | grep IPAddress\": - "IPAddress": "10.88.6.140", +$ podman inspect -l | grep IPAddress\": + "SecondaryIPAddresses": null, + "IPAddress": "", ``` Note: The -l is a convenience argument for **latest container**. You can also use the container's ID instead diff --git a/libpod/boltdb_state.go b/libpod/boltdb_state.go index 92a7b1538..d8cfa2bda 100644 --- a/libpod/boltdb_state.go +++ b/libpod/boltdb_state.go @@ -1358,56 +1358,6 @@ func (s *BoltState) AddVolume(volume *Volume) error { return err } -// RemoveVolCtrDep updates the container dependencies sub bucket of the given volume. -// It deletes it from the bucket when found. -// This is important when force removing a volume and we want to get rid of the dependencies. -func (s *BoltState) RemoveVolCtrDep(volume *Volume, ctrID string) error { - if ctrID == "" { - return nil - } - - if !s.valid { - return ErrDBBadConfig - } - - if !volume.valid { - return ErrVolumeRemoved - } - - volName := []byte(volume.Name()) - - db, err := s.getDBCon() - if err != nil { - return err - } - defer s.closeDBCon(db) - - err = db.Update(func(tx *bolt.Tx) error { - volBkt, err := getVolBucket(tx) - if err != nil { - return err - } - - volDB := volBkt.Bucket(volName) - if volDB == nil { - volume.valid = false - return errors.Wrapf(ErrNoSuchVolume, "no volume with name %s found in database", volume.Name()) - } - - // Make a subbucket for the containers using the volume - ctrDepsBkt := volDB.Bucket(volDependenciesBkt) - depCtrID := []byte(ctrID) - if depExists := ctrDepsBkt.Get(depCtrID); depExists != nil { - if err := ctrDepsBkt.Delete(depCtrID); err != nil { - return errors.Wrapf(err, "error deleting container dependencies %q for volume %s in ctrDependencies bucket in DB", ctrID, volume.Name()) - } - } - - return nil - }) - return err -} - // RemoveVolume removes the given volume from the state func (s *BoltState) RemoveVolume(volume *Volume) error { if !s.valid { @@ -1433,6 +1383,11 @@ func (s *BoltState) RemoveVolume(volume *Volume) error { return err } + ctrBkt, err := getCtrBucket(tx) + if err != nil { + return err + } + // Check if the volume exists volDB := volBkt.Bucket(volName) if volDB == nil { @@ -1448,6 +1403,18 @@ func (s *BoltState) RemoveVolume(volume *Volume) error { if volCtrsBkt != nil { var deps []string err = volCtrsBkt.ForEach(func(id, value []byte) error { + // Alright, this is ugly. + // But we need it to work around the change in + // volume dependency handling, to make sure that + // older Podman versions don't cause DB + // corruption. + // Look up all dependencies and see that they + // still exist before appending. + ctrExists := ctrBkt.Bucket(id) + if ctrExists == nil { + return nil + } + deps = append(deps, string(id)) return nil }) @@ -1629,6 +1596,11 @@ func (s *BoltState) VolumeInUse(volume *Volume) ([]string, error) { return err } + ctrBucket, err := getCtrBucket(tx) + if err != nil { + return err + } + volDB := volBucket.Bucket([]byte(volume.Name())) if volDB == nil { volume.valid = false @@ -1642,6 +1614,13 @@ func (s *BoltState) VolumeInUse(volume *Volume) ([]string, error) { // Iterate through and add dependencies err = dependsBkt.ForEach(func(id, value []byte) error { + // Look up all dependencies and see that they + // still exist before appending. + ctrExists := ctrBucket.Bucket(id) + if ctrExists == nil { + return nil + } + depCtrs = append(depCtrs, string(id)) return nil diff --git a/libpod/boltdb_state_internal.go b/libpod/boltdb_state_internal.go index b6a0759b1..a6900a6d3 100644 --- a/libpod/boltdb_state_internal.go +++ b/libpod/boltdb_state_internal.go @@ -564,23 +564,17 @@ func (s *BoltState) addContainer(ctr *Container, pod *Pod) error { } } - // Add container to volume dependencies bucket if container is using a named volume - if ctr.runtime.config.VolumePath == "" { - return nil - } - for _, vol := range ctr.config.Spec.Mounts { - if strings.Contains(vol.Source, ctr.runtime.config.VolumePath) { - volName := strings.Split(vol.Source[len(ctr.runtime.config.VolumePath)+1:], "/")[0] - volDB := volBkt.Bucket([]byte(volName)) - if volDB == nil { - return errors.Wrapf(ErrNoSuchVolume, "no volume with name %s found in database", volName) - } + // Add container to named volume dependencies buckets + for _, vol := range ctr.config.NamedVolumes { + volDB := volBkt.Bucket([]byte(vol.Name)) + if volDB == nil { + return errors.Wrapf(ErrNoSuchVolume, "no volume with name %s found in database when adding container %s", vol.Name, ctr.ID()) + } - ctrDepsBkt := volDB.Bucket(volDependenciesBkt) - if depExists := ctrDepsBkt.Get(ctrID); depExists == nil { - if err := ctrDepsBkt.Put(ctrID, ctrID); err != nil { - return errors.Wrapf(err, "error storing container dependencies %q for volume %s in ctrDependencies bucket in DB", ctr.ID(), volName) - } + ctrDepsBkt := volDB.Bucket(volDependenciesBkt) + if depExists := ctrDepsBkt.Get(ctrID); depExists == nil { + if err := ctrDepsBkt.Put(ctrID, ctrID); err != nil { + return errors.Wrapf(err, "error adding container %s to volume %s dependencies", ctr.ID(), vol.Name) } } } @@ -745,22 +739,19 @@ func (s *BoltState) removeContainer(ctr *Container, pod *Pod, tx *bolt.Tx) error } } - // Remove container from volume dependencies bucket if container is using a named volume - for _, vol := range ctr.config.Spec.Mounts { - if strings.Contains(vol.Source, ctr.runtime.config.VolumePath) { - volName := strings.Split(vol.Source[len(ctr.runtime.config.VolumePath)+1:], "/")[0] - - volDB := volBkt.Bucket([]byte(volName)) - if volDB == nil { - // Let's assume the volume was already deleted and continue to remove the container - continue - } + // Remove container from named volume dependencies buckets + for _, vol := range ctr.config.NamedVolumes { + volDB := volBkt.Bucket([]byte(vol.Name)) + if volDB == nil { + // Let's assume the volume was already deleted and + // continue to remove the container + continue + } - ctrDepsBkt := volDB.Bucket(volDependenciesBkt) - if depExists := ctrDepsBkt.Get(ctrID); depExists != nil { - if err := ctrDepsBkt.Delete(ctrID); err != nil { - return errors.Wrapf(err, "error deleting container dependencies %q for volume %s in ctrDependencies bucket in DB", ctr.ID(), volName) - } + ctrDepsBkt := volDB.Bucket(volDependenciesBkt) + if depExists := ctrDepsBkt.Get(ctrID); depExists == nil { + if err := ctrDepsBkt.Delete(ctrID); err != nil { + return errors.Wrapf(err, "error deleting container %s dependency on volume %s", ctr.ID(), vol.Name) } } } diff --git a/libpod/container.go b/libpod/container.go index 739406e42..6d5e063ab 100644 --- a/libpod/container.go +++ b/libpod/container.go @@ -234,6 +234,8 @@ type ContainerConfig struct { // These include the SHM mount. // These must be unmounted before the container's rootfs is unmounted. Mounts []string `json:"mounts,omitempty"` + // NamedVolumes lists the named volumes to mount into the container. + NamedVolumes []*ContainerNamedVolume `json:"namedVolumes,omitempty"` // Security Config @@ -354,9 +356,6 @@ type ContainerConfig struct { // ExitCommand is the container's exit command. // This Command will be executed when the container exits ExitCommand []string `json:"exitCommand,omitempty"` - // LocalVolumes are the built-in volumes we get from the --volumes-from flag - // It picks up the built-in volumes of the container used by --volumes-from - LocalVolumes []spec.Mount // IsInfra is a bool indicating whether this container is an infra container used for // sharing kernel namespaces in a pod IsInfra bool `json:"pause"` @@ -368,6 +367,18 @@ type ContainerConfig struct { HealthCheckConfig *manifest.Schema2HealthConfig `json:"healthcheck"` } +// ContainerNamedVolume is a named volume that will be mounted into the +// container. Each named volume is a libpod Volume present in the state. +type ContainerNamedVolume struct { + // Name is the name of the volume to mount in. + // Must resolve to a valid volume present in this Podman. + Name string `json:"volumeName"` + // Dest is the mount's destination + Dest string `json:"dest"` + // Options are fstab style mount options + Options []string `json:"options,omitempty"` +} + // ContainerStatus returns a string representation for users // of a container state func (t ContainerStatus) String() string { @@ -488,6 +499,22 @@ func (c *Container) StaticDir() string { return c.config.StaticDir } +// NamedVolumes returns the container's named volumes. +// The name of each is guaranteed to point to a valid libpod Volume present in +// the state. +func (c *Container) NamedVolumes() []*ContainerNamedVolume { + volumes := []*ContainerNamedVolume{} + for _, vol := range c.config.NamedVolumes { + newVol := new(ContainerNamedVolume) + newVol.Name = vol.Name + newVol.Dest = vol.Dest + newVol.Options = vol.Options + volumes = append(volumes, newVol) + } + + return volumes +} + // Privileged returns whether the container is privileged func (c *Container) Privileged() bool { return c.config.Privileged diff --git a/libpod/container_internal.go b/libpod/container_internal.go index daa32007a..22df36c11 100644 --- a/libpod/container_internal.go +++ b/libpod/container_internal.go @@ -1403,22 +1403,6 @@ func getExcludedCGroups() (excludes []string) { return } -// namedVolumes returns named volumes for the container -func (c *Container) namedVolumes() ([]string, error) { - var volumes []string - for _, vol := range c.config.Spec.Mounts { - if strings.HasPrefix(vol.Source, c.runtime.config.VolumePath) { - volume := strings.TrimPrefix(vol.Source, c.runtime.config.VolumePath+"/") - split := strings.Split(volume, "/") - volume = split[0] - if _, err := c.runtime.state.Volume(volume); err == nil { - volumes = append(volumes, volume) - } - } - } - return volumes, nil -} - // this should be from chrootarchive. func (c *Container) copyWithTarFromImage(src, dest string) error { mountpoint, err := c.mount() diff --git a/libpod/container_internal_linux.go b/libpod/container_internal_linux.go index 504d6c135..4d6bf61a3 100644 --- a/libpod/container_internal_linux.go +++ b/libpod/container_internal_linux.go @@ -195,6 +195,7 @@ func (c *Container) generateSpec(ctx context.Context) (*spec.Spec, error) { if err := c.makeBindMounts(); err != nil { return nil, err } + // Check if the spec file mounts contain the label Relabel flags z or Z. // If they do, relabel the source directory and then remove the option. for i := range g.Config.Mounts { @@ -218,6 +219,23 @@ func (c *Container) generateSpec(ctx context.Context) (*spec.Spec, error) { g.SetProcessSelinuxLabel(c.ProcessLabel()) g.SetLinuxMountLabel(c.MountLabel()) + + // Add named volumes + for _, namedVol := range c.config.NamedVolumes { + volume, err := c.runtime.GetVolume(namedVol.Name) + if err != nil { + return nil, errors.Wrapf(err, "error retrieving volume %s to add to container %s", namedVol.Name, c.ID()) + } + mountPoint := volume.MountPoint() + volMount := spec.Mount{ + Type: "bind", + Source: mountPoint, + Destination: namedVol.Dest, + Options: namedVol.Options, + } + g.AddMount(volMount) + } + // Add bind mounts to container for dstPath, srcPath := range c.state.BindMounts { newMount := spec.Mount{ diff --git a/libpod/image/image.go b/libpod/image/image.go index 4862bf1d6..757d034a2 100644 --- a/libpod/image/image.go +++ b/libpod/image/image.go @@ -68,6 +68,16 @@ type Runtime struct { EventsLogFilePath string } +// InfoImage keep information of Image along with all associated layers +type InfoImage struct { + // ID of image + ID string + // Tags of image + Tags []string + // Layers stores all layers of image. + Layers []LayerInfo +} + // ErrRepoTagNotFound is the error returned when the image id given doesn't match a rep tag in store var ErrRepoTagNotFound = errors.New("unable to match user input to any specific repotag") @@ -351,6 +361,7 @@ func (i *Image) Remove(force bool) error { if _, err := i.imageruntime.store.DeleteImage(i.ID(), true); err != nil { return err } + i.newImageEvent(events.Remove) for parent != nil { nextParent, err := parent.GetParent() if err != nil { @@ -373,7 +384,6 @@ func (i *Image) Remove(force bool) error { } parent = nextParent } - defer i.newImageEvent(events.Remove) return nil } @@ -1277,3 +1287,21 @@ func GetLayersMapWithImageInfo(imageruntime *Runtime) (map[string]*LayerInfo, er } return layerInfoMap, nil } + +// BuildImageHierarchyMap stores hierarchy of images such that all parent layers using which image is built are stored in imageInfo +// Layers are added such that (Start)RootLayer->...intermediate Parent Layer(s)-> TopLayer(End) +func BuildImageHierarchyMap(imageInfo *InfoImage, layerMap map[string]*LayerInfo, layerID string) error { + if layerID == "" { + return nil + } + ll, ok := layerMap[layerID] + if !ok { + return fmt.Errorf("lookup error: layerid %s not found", layerID) + } + if err := BuildImageHierarchyMap(imageInfo, layerMap, ll.ParentID); err != nil { + return err + } + + imageInfo.Layers = append(imageInfo.Layers, *ll) + return nil +} diff --git a/libpod/image/search.go b/libpod/image/search.go index 2c66ce284..03a67636b 100644 --- a/libpod/image/search.go +++ b/libpod/image/search.go @@ -2,7 +2,6 @@ package image import ( "context" - "reflect" "strconv" "strings" "sync" @@ -10,7 +9,6 @@ import ( "github.com/containers/image/docker" "github.com/containers/image/types" sysreg "github.com/containers/libpod/pkg/registries" - "github.com/fatih/camelcase" "github.com/pkg/errors" "github.com/sirupsen/logrus" "golang.org/x/sync/semaphore" @@ -63,24 +61,6 @@ type SearchFilter struct { IsOfficial types.OptionalBool } -func splitCamelCase(src string) string { - entries := camelcase.Split(src) - return strings.Join(entries, " ") -} - -// HeaderMap returns the headers of a SearchResult. -func (s *SearchResult) HeaderMap() map[string]string { - v := reflect.Indirect(reflect.ValueOf(s)) - values := make(map[string]string, v.NumField()) - - for i := 0; i < v.NumField(); i++ { - key := v.Type().Field(i).Name - value := key - values[key] = strings.ToUpper(splitCamelCase(value)) - } - return values -} - // SearchImages searches images based on term and the specified SearchOptions // in all registries. func SearchImages(term string, options SearchOptions) ([]SearchResult, error) { diff --git a/libpod/in_memory_state.go b/libpod/in_memory_state.go index ab4fc8ba7..2669206df 100644 --- a/libpod/in_memory_state.go +++ b/libpod/in_memory_state.go @@ -249,11 +249,8 @@ func (s *InMemoryState) AddContainer(ctr *Container) error { } // Add container to volume dependencies - for _, vol := range ctr.config.Spec.Mounts { - if strings.Contains(vol.Source, ctr.runtime.config.VolumePath) { - volName := strings.Split(vol.Source[len(ctr.runtime.config.VolumePath)+1:], "/")[0] - s.addCtrToVolDependsMap(ctr.ID(), volName) - } + for _, vol := range ctr.config.NamedVolumes { + s.addCtrToVolDependsMap(ctr.ID(), vol.Name) } return nil @@ -306,12 +303,9 @@ func (s *InMemoryState) RemoveContainer(ctr *Container) error { s.removeCtrFromDependsMap(ctr.ID(), depCtr) } - // Remove container from volume dependencies - for _, vol := range ctr.config.Spec.Mounts { - if strings.Contains(vol.Source, ctr.runtime.config.VolumePath) { - volName := strings.Split(vol.Source[len(ctr.runtime.config.VolumePath)+1:], "/")[0] - s.removeCtrFromVolDependsMap(ctr.ID(), volName) - } + // Remove this container from volume dependencies + for _, vol := range ctr.config.NamedVolumes { + s.removeCtrFromVolDependsMap(ctr.ID(), vol.Name) } return nil @@ -492,22 +486,6 @@ func (s *InMemoryState) RemoveVolume(volume *Volume) error { return nil } -// RemoveVolCtrDep updates the container dependencies of the volume -func (s *InMemoryState) RemoveVolCtrDep(volume *Volume, ctrID string) error { - if !volume.valid { - return errors.Wrapf(ErrVolumeRemoved, "volume with name %s is not valid", volume.Name()) - } - - if _, ok := s.volumes[volume.Name()]; !ok { - return errors.Wrapf(ErrNoSuchVolume, "volume with name %s doesn't exists in state", volume.Name()) - } - - // Remove container that is using this volume - s.removeCtrFromVolDependsMap(ctrID, volume.Name()) - - return nil -} - // VolumeInUse checks if the given volume is being used by at least one container func (s *InMemoryState) VolumeInUse(volume *Volume) ([]string, error) { if !volume.valid { diff --git a/libpod/options.go b/libpod/options.go index 24f126e66..9326e54e4 100644 --- a/libpod/options.go +++ b/libpod/options.go @@ -13,7 +13,6 @@ import ( "github.com/containers/storage" "github.com/containers/storage/pkg/idtools" "github.com/cri-o/ocicni/pkg/ocicni" - spec "github.com/opencontainers/runtime-spec/specs-go" "github.com/pkg/errors" ) @@ -1111,24 +1110,6 @@ func WithUserVolumes(volumes []string) CtrCreateOption { } } -// WithLocalVolumes sets the built-in volumes of the container retrieved -// from a container passed in to the --volumes-from flag. -// This stores the built-in volume information in the Config so we can -// add them when creating the container. -func WithLocalVolumes(volumes []spec.Mount) CtrCreateOption { - return func(ctr *Container) error { - if ctr.valid { - return ErrCtrFinalized - } - - if volumes != nil { - ctr.config.LocalVolumes = append(ctr.config.LocalVolumes, volumes...) - } - - return nil - } -} - // WithEntrypoint sets the entrypoint of the container. // This is not used to change the container's spec, but will instead be used // during commit to populate the entrypoint of the new image. @@ -1255,6 +1236,35 @@ func withIsInfra() CtrCreateOption { } } +// WithNamedVolumes adds the given named volumes to the container. +func WithNamedVolumes(volumes []*ContainerNamedVolume) CtrCreateOption { + return func(ctr *Container) error { + if ctr.valid { + return ErrCtrFinalized + } + + destinations := make(map[string]bool) + + for _, vol := range volumes { + // Don't check if they already exist. + // If they don't we will automatically create them. + + if _, ok := destinations[vol.Dest]; ok { + return errors.Wrapf(ErrInvalidArg, "two volumes found with destination %s", vol.Dest) + } + destinations[vol.Dest] = true + + ctr.config.NamedVolumes = append(ctr.config.NamedVolumes, &ContainerNamedVolume{ + Name: vol.Name, + Dest: vol.Dest, + Options: vol.Options, + }) + } + + return nil + } +} + // Volume Creation Options // WithVolumeName sets the name of the volume. @@ -1274,68 +1284,72 @@ func WithVolumeName(name string) VolumeCreateOption { } } -// WithVolumeUID sets the uid of the owner. -func WithVolumeUID(uid int) VolumeCreateOption { +// WithVolumeLabels sets the labels of the volume. +func WithVolumeLabels(labels map[string]string) VolumeCreateOption { return func(volume *Volume) error { if volume.valid { return ErrVolumeFinalized } - volume.config.UID = uid + + volume.config.Labels = make(map[string]string) + for key, value := range labels { + volume.config.Labels[key] = value + } + return nil } } -// WithVolumeGID sets the gid of the owner. -func WithVolumeGID(gid int) VolumeCreateOption { +// WithVolumeDriver sets the driver of the volume. +func WithVolumeDriver(driver string) VolumeCreateOption { return func(volume *Volume) error { if volume.valid { return ErrVolumeFinalized } - volume.config.GID = gid + + volume.config.Driver = driver + return nil } } -// WithVolumeLabels sets the labels of the volume. -func WithVolumeLabels(labels map[string]string) VolumeCreateOption { +// WithVolumeOptions sets the options of the volume. +func WithVolumeOptions(options map[string]string) VolumeCreateOption { return func(volume *Volume) error { if volume.valid { return ErrVolumeFinalized } - volume.config.Labels = make(map[string]string) - for key, value := range labels { - volume.config.Labels[key] = value + volume.config.Options = make(map[string]string) + for key, value := range options { + volume.config.Options[key] = value } return nil } } -// WithVolumeDriver sets the driver of the volume. -func WithVolumeDriver(driver string) VolumeCreateOption { +// WithVolumeUID sets the UID that the volume will be created as. +func WithVolumeUID(uid int) VolumeCreateOption { return func(volume *Volume) error { if volume.valid { return ErrVolumeFinalized } - volume.config.Driver = driver + volume.config.UID = uid return nil } } -// WithVolumeOptions sets the options of the volume. -func WithVolumeOptions(options map[string]string) VolumeCreateOption { +// WithVolumeGID sets the GID that the volume will be created as. +func WithVolumeGID(gid int) VolumeCreateOption { return func(volume *Volume) error { if volume.valid { return ErrVolumeFinalized } - volume.config.Options = make(map[string]string) - for key, value := range options { - volume.config.Options[key] = value - } + volume.config.GID = gid return nil } diff --git a/libpod/runtime.go b/libpod/runtime.go index 6e54de558..4dd2707e8 100644 --- a/libpod/runtime.go +++ b/libpod/runtime.go @@ -4,7 +4,6 @@ import ( "fmt" "io/ioutil" "os" - "os/exec" "path/filepath" "sync" "syscall" @@ -742,7 +741,7 @@ func makeRuntime(runtime *Runtime) (err error) { // Set up containers/storage var store storage.Store - if rootless.SkipStorageSetup() { + if os.Geteuid() != 0 { logrus.Debug("Not configuring container store") } else { store, err = storage.GetStore(runtime.config.StorageConfig) @@ -926,16 +925,8 @@ func makeRuntime(runtime *Runtime) (err error) { // If we need to refresh the state, do it now - things are guaranteed to // be set up by now. if doRefresh { - if os.Geteuid() != 0 { - aliveLock.Unlock() - locked = false - if err2 := runtime.refreshRootless(); err2 != nil { - return err2 - } - } else { - if err2 := runtime.refresh(runtimeAliveFile); err2 != nil { - return err2 - } + if err2 := runtime.refresh(runtimeAliveFile); err2 != nil { + return err2 } } @@ -1009,21 +1000,6 @@ func (r *Runtime) Shutdown(force bool) error { return lastError } -// Reconfigures the runtime after a reboot for a rootless process -func (r *Runtime) refreshRootless() error { - // Take advantage of a command that requires a new userns - // so that we are running as the root user and able to use refresh() - cmd := exec.Command(os.Args[0], "info") - - if output, err := cmd.CombinedOutput(); err != nil { - if _, ok := err.(*exec.ExitError); !ok { - return errors.Wrapf(err, "Error waiting for info while refreshing state: %s", os.Args[0]) - } - return errors.Wrapf(err, "Error running %s info while refreshing state: %s", os.Args[0], output) - } - return nil -} - // Reconfigures the runtime after a reboot // Refreshes the state, recreating temporary files // Does not check validity as the runtime is not valid until after this has run diff --git a/libpod/runtime_ctr.go b/libpod/runtime_ctr.go index 506aee477..800b42851 100644 --- a/libpod/runtime_ctr.go +++ b/libpod/runtime_ctr.go @@ -2,11 +2,9 @@ package libpod import ( "context" - "io/ioutil" "os" "path" "path/filepath" - "strconv" "strings" "time" @@ -101,9 +99,6 @@ func (r *Runtime) newContainer(ctx context.Context, rSpec *spec.Spec, options .. ctr.state.State = ContainerStateConfigured ctr.runtime = r - ctr.valid = true - ctr.state.State = ContainerStateConfigured - var pod *Pod if ctr.config.Pod != "" { // Get the pod from state @@ -175,24 +170,29 @@ func (r *Runtime) newContainer(ctx context.Context, rSpec *spec.Spec, options .. ctr.config.ConmonPidFile = filepath.Join(ctr.config.StaticDir, "conmon.pid") } - // Go through the volume mounts and check for named volumes - // If the named volme already exists continue, otherwise create - // the storage for the named volume. - for i, vol := range ctr.config.Spec.Mounts { - if vol.Source[0] != '/' && isNamedVolume(vol.Source) { - volInfo, err := r.state.Volume(vol.Source) - if err != nil { - newVol, err := r.newVolume(ctx, WithVolumeName(vol.Source), withSetCtrSpecific(), WithVolumeUID(ctr.RootUID()), WithVolumeGID(ctr.RootGID())) - if err != nil { - return nil, errors.Wrapf(err, "error creating named volume %q", vol.Source) - } - ctr.config.Spec.Mounts[i].Source = newVol.MountPoint() - if err := ctr.copyWithTarFromImage(ctr.config.Spec.Mounts[i].Destination, ctr.config.Spec.Mounts[i].Source); err != nil && !os.IsNotExist(err) { - return nil, errors.Wrapf(err, "failed to copy content into new volume mount %q", vol.Source) - } - continue - } - ctr.config.Spec.Mounts[i].Source = volInfo.MountPoint() + // Go through named volumes and add them. + // If they don't exist they will be created using basic options. + for _, vol := range ctr.config.NamedVolumes { + // Check if it exists already + _, err := r.state.Volume(vol.Name) + if err == nil { + // The volume exists, we're good + continue + } else if errors.Cause(err) != ErrNoSuchVolume { + return nil, errors.Wrapf(err, "error retrieving named volume %s for new container", vol.Name) + } + + logrus.Debugf("Creating new volume %s for container", vol.Name) + + // The volume does not exist, so we need to create it. + newVol, err := r.newVolume(ctx, WithVolumeName(vol.Name), withSetCtrSpecific(), + WithVolumeUID(ctr.RootUID()), WithVolumeGID(ctr.RootGID())) + if err != nil { + return nil, errors.Wrapf(err, "error creating named volume %q", vol.Name) + } + + if err := ctr.copyWithTarFromImage(vol.Dest, newVol.MountPoint()); err != nil && !os.IsNotExist(err) { + return nil, errors.Wrapf(err, "Failed to copy content into new volume mount %q", vol.Name) } } @@ -346,13 +346,6 @@ func (r *Runtime) removeContainer(ctx context.Context, c *Container, force bool, return errors.Wrapf(ErrCtrExists, "container %s has dependent containers which must be removed before it: %s", c.ID(), depsStr) } - var volumes []string - if removeVolume { - volumes, err = c.namedVolumes() - if err != nil { - logrus.Errorf("unable to retrieve builtin volumes for container %v: %v", c.ID(), err) - } - } var cleanupErr error // Remove the container from the state if c.config.Pod != "" { @@ -417,8 +410,12 @@ func (r *Runtime) removeContainer(ctx context.Context, c *Container, force bool, } } - for _, v := range volumes { - if volume, err := runtime.state.Volume(v); err == nil { + if !removeVolume { + return cleanupErr + } + + for _, v := range c.config.NamedVolumes { + if volume, err := runtime.state.Volume(v.Name); err == nil { if !volume.IsCtrSpecific() { continue } @@ -550,51 +547,12 @@ func (r *Runtime) GetLatestContainer() (*Container, error) { return ctrs[lastCreatedIndex], nil } -// Check if volName is a named volume and not one of the default mounts we add to containers -func isNamedVolume(volName string) bool { - if volName != "proc" && volName != "tmpfs" && volName != "devpts" && volName != "shm" && volName != "mqueue" && volName != "sysfs" && volName != "cgroup" { - return true - } - return false -} - // Export is the libpod portion of exporting a container to a tar file func (r *Runtime) Export(name string, path string) error { ctr, err := r.LookupContainer(name) if err != nil { return err } - if os.Geteuid() != 0 { - state, err := ctr.State() - if err != nil { - return errors.Wrapf(err, "cannot read container state %q", ctr.ID()) - } - if state == ContainerStateRunning || state == ContainerStatePaused { - data, err := ioutil.ReadFile(ctr.Config().ConmonPidFile) - if err != nil { - return errors.Wrapf(err, "cannot read conmon PID file %q", ctr.Config().ConmonPidFile) - } - conmonPid, err := strconv.Atoi(string(data)) - if err != nil { - return errors.Wrapf(err, "cannot parse PID %q", data) - } - became, ret, err := rootless.JoinDirectUserAndMountNS(uint(conmonPid)) - if err != nil { - return err - } - if became { - os.Exit(ret) - } - } else { - became, ret, err := rootless.BecomeRootInUserNS() - if err != nil { - return err - } - if became { - os.Exit(ret) - } - } - } return ctr.Export(path) } diff --git a/libpod/runtime_volume_linux.go b/libpod/runtime_volume_linux.go index db5c29242..40040fc52 100644 --- a/libpod/runtime_volume_linux.go +++ b/libpod/runtime_volume_linux.go @@ -98,12 +98,26 @@ func (r *Runtime) removeVolume(ctx context.Context, v *Volume, force bool) error if !force { return errors.Wrapf(ErrVolumeBeingUsed, "volume %s is being used by the following container(s): %s", v.Name(), depsStr) } - // If using force, log the warning that the volume is being used by at least one container - logrus.Warnf("volume %s is being used by the following container(s): %s", v.Name(), depsStr) - // Remove the container dependencies so we can go ahead and delete the volume + + // We need to remove all containers using the volume for _, dep := range deps { - if err := r.state.RemoveVolCtrDep(v, dep); err != nil { - return errors.Wrapf(err, "unable to remove container dependency %q from volume %q while trying to delete volume by force", dep, v.Name()) + ctr, err := r.state.Container(dep) + if err != nil { + // If the container's removed, no point in + // erroring. + if errors.Cause(err) == ErrNoSuchCtr || errors.Cause(err) == ErrCtrRemoved { + continue + } + + return errors.Wrapf(err, "error removing container %s that depends on volume %s", dep, v.Name()) + } + + // TODO: do we want to set force here when removing + // containers? + // I'm inclined to say no, in case someone accidentally + // wipes a container they're using... + if err := r.removeContainer(ctx, ctr, false, false); err != nil { + return errors.Wrapf(err, "error removing container %s that depends on volume %s", ctr.ID(), v.Name()) } } } diff --git a/libpod/state.go b/libpod/state.go index 4296fc3cd..d0ad1a1f8 100644 --- a/libpod/state.go +++ b/libpod/state.go @@ -192,10 +192,6 @@ type State interface { // AddVolume adds the specified volume to state. The volume's name // must be unique within the list of existing volumes AddVolume(volume *Volume) error - // RemoveVolCtrDep updates the list of container dependencies that the - // volume has. It either deletes the dependent container ID from - // the sub-bucket - RemoveVolCtrDep(volume *Volume, ctrID string) error // RemoveVolume removes the specified volume. // Only volumes that have no container dependencies can be removed RemoveVolume(volume *Volume) error diff --git a/pkg/adapter/containers.go b/pkg/adapter/containers.go index 932d209cd..1bca99cec 100644 --- a/pkg/adapter/containers.go +++ b/pkg/adapter/containers.go @@ -5,12 +5,17 @@ package adapter import ( "context" "fmt" + "io/ioutil" + "os" + "path/filepath" "strconv" + "strings" "sync" "syscall" "time" "github.com/containers/libpod/cmd/podman/cliconfig" + "github.com/containers/libpod/cmd/podman/shared" "github.com/containers/libpod/libpod" "github.com/containers/libpod/pkg/adapter/shortcuts" "github.com/pkg/errors" @@ -154,3 +159,148 @@ func (r *LocalRuntime) Log(c *cliconfig.LogsValues, options *libpod.LogOptions) } return nil } + +// CreateContainer creates a libpod container +func (r *LocalRuntime) CreateContainer(ctx context.Context, c *cliconfig.CreateValues) (string, error) { + results := shared.NewIntermediateLayer(&c.PodmanCommand) + ctr, _, err := shared.CreateContainer(ctx, &results, r.Runtime) + return ctr.ID(), err +} + +// Run a libpod container +func (r *LocalRuntime) Run(ctx context.Context, c *cliconfig.RunValues, exitCode int) (int, error) { + results := shared.NewIntermediateLayer(&c.PodmanCommand) + + ctr, createConfig, err := shared.CreateContainer(ctx, &results, r.Runtime) + if err != nil { + return exitCode, err + } + + if logrus.GetLevel() == logrus.DebugLevel { + cgroupPath, err := ctr.CGroupPath() + if err == nil { + logrus.Debugf("container %q has CgroupParent %q", ctr.ID(), cgroupPath) + } + } + + // Handle detached start + if createConfig.Detach { + // if the container was created as part of a pod, also start its dependencies, if any. + if err := ctr.Start(ctx, c.IsSet("pod")); err != nil { + // This means the command did not exist + exitCode = 127 + if strings.Index(err.Error(), "permission denied") > -1 { + exitCode = 126 + } + return exitCode, err + } + + fmt.Printf("%s\n", ctr.ID()) + exitCode = 0 + return exitCode, nil + } + + outputStream := os.Stdout + errorStream := os.Stderr + inputStream := os.Stdin + + // If -i is not set, clear stdin + if !c.Bool("interactive") { + inputStream = nil + } + + // If attach is set, clear stdin/stdout/stderr and only attach requested + if c.IsSet("attach") || c.IsSet("a") { + outputStream = nil + errorStream = nil + if !c.Bool("interactive") { + inputStream = nil + } + + attachTo := c.StringSlice("attach") + for _, stream := range attachTo { + switch strings.ToLower(stream) { + case "stdout": + outputStream = os.Stdout + case "stderr": + errorStream = os.Stderr + case "stdin": + inputStream = os.Stdin + default: + return exitCode, errors.Wrapf(libpod.ErrInvalidArg, "invalid stream %q for --attach - must be one of stdin, stdout, or stderr", stream) + } + } + } + // if the container was created as part of a pod, also start its dependencies, if any. + if err := StartAttachCtr(ctx, ctr, outputStream, errorStream, inputStream, c.String("detach-keys"), c.Bool("sig-proxy"), true, c.IsSet("pod")); err != nil { + // We've manually detached from the container + // Do not perform cleanup, or wait for container exit code + // Just exit immediately + if errors.Cause(err) == libpod.ErrDetach { + exitCode = 0 + return exitCode, nil + } + // This means the command did not exist + exitCode = 127 + if strings.Index(err.Error(), "permission denied") > -1 { + exitCode = 126 + } + if c.IsSet("rm") { + if deleteError := r.Runtime.RemoveContainer(ctx, ctr, true, false); deleteError != nil { + logrus.Errorf("unable to remove container %s after failing to start and attach to it", ctr.ID()) + } + } + return exitCode, err + } + + if ecode, err := ctr.Wait(); err != nil { + if errors.Cause(err) == libpod.ErrNoSuchCtr { + // The container may have been removed + // Go looking for an exit file + config, err := r.Runtime.GetConfig() + if err != nil { + return exitCode, err + } + ctrExitCode, err := ReadExitFile(config.TmpDir, ctr.ID()) + if err != nil { + logrus.Errorf("Cannot get exit code: %v", err) + exitCode = 127 + } else { + exitCode = ctrExitCode + } + } + } else { + exitCode = int(ecode) + } + + if c.IsSet("rm") { + r.Runtime.RemoveContainer(ctx, ctr, false, true) + } + + return exitCode, nil +} + +// ReadExitFile reads a container's exit file +func ReadExitFile(runtimeTmp, ctrID string) (int, error) { + exitFile := filepath.Join(runtimeTmp, "exits", fmt.Sprintf("%s-old", ctrID)) + + logrus.Debugf("Attempting to read container %s exit code from file %s", ctrID, exitFile) + + // Check if it exists + if _, err := os.Stat(exitFile); err != nil { + return 0, errors.Wrapf(err, "error getting exit file for container %s", ctrID) + } + + // File exists, read it in and convert to int + statusStr, err := ioutil.ReadFile(exitFile) + if err != nil { + return 0, errors.Wrapf(err, "error reading exit file for container %s", ctrID) + } + + exitCode, err := strconv.Atoi(string(statusStr)) + if err != nil { + return 0, errors.Wrapf(err, "error parsing exit code for container %s", ctrID) + } + + return exitCode, nil +} diff --git a/pkg/adapter/containers_remote.go b/pkg/adapter/containers_remote.go index 2982d6cbb..3730827c7 100644 --- a/pkg/adapter/containers_remote.go +++ b/pkg/adapter/containers_remote.go @@ -262,3 +262,33 @@ func (r *LocalRuntime) Log(c *cliconfig.LogsValues, options *libpod.LogOptions) } return nil } + +// CreateContainer creates a container from the cli over varlink +func (r *LocalRuntime) CreateContainer(ctx context.Context, c *cliconfig.CreateValues) (string, error) { + if !c.Bool("detach") { + // TODO need to add attach when that function becomes available + return "", errors.New("the remote client only supports detached containers") + } + results := shared.NewIntermediateLayer(&c.PodmanCommand) + return iopodman.CreateContainer().Call(r.Conn, results.MakeVarlink()) +} + +// Run creates a container overvarlink and then starts it +func (r *LocalRuntime) Run(ctx context.Context, c *cliconfig.RunValues, exitCode int) (int, error) { + // TODO the exit codes for run need to be figured out for remote connections + if !c.Bool("detach") { + return 0, errors.New("the remote client only supports detached containers") + } + results := shared.NewIntermediateLayer(&c.PodmanCommand) + cid, err := iopodman.CreateContainer().Call(r.Conn, results.MakeVarlink()) + if err != nil { + return 0, err + } + fmt.Println(cid) + _, err = iopodman.StartContainer().Call(r.Conn, cid) + return 0, err +} + +func ReadExitFile(runtimeTmp, ctrID string) (int, error) { + return 0, libpod.ErrNotImplemented +} diff --git a/pkg/adapter/images.go b/pkg/adapter/images.go new file mode 100644 index 000000000..c8ea1cdea --- /dev/null +++ b/pkg/adapter/images.go @@ -0,0 +1,34 @@ +// +build !remoteclient + +package adapter + +import ( + "github.com/containers/libpod/cmd/podman/cliconfig" + "github.com/containers/libpod/libpod/image" + "github.com/pkg/errors" +) + +// Tree ... +func (r *LocalRuntime) Tree(c *cliconfig.TreeValues) (*image.InfoImage, map[string]*image.LayerInfo, *ContainerImage, error) { + img, err := r.NewImageFromLocal(c.InputArgs[0]) + if err != nil { + return nil, nil, nil, err + } + + // Fetch map of image-layers, which is used for printing output. + layerInfoMap, err := image.GetLayersMapWithImageInfo(r.Runtime.ImageRuntime()) + if err != nil { + return nil, nil, nil, errors.Wrapf(err, "error while retrieving layers of image %q", img.InputName) + } + + // Create an imageInfo and fill the image and layer info + imageInfo := &image.InfoImage{ + ID: img.ID(), + Tags: img.Names(), + } + + if err := image.BuildImageHierarchyMap(imageInfo, layerInfoMap, img.TopLayer()); err != nil { + return nil, nil, nil, err + } + return imageInfo, layerInfoMap, img, nil +} diff --git a/pkg/adapter/images_remote.go b/pkg/adapter/images_remote.go index e7b38dccc..722058d4a 100644 --- a/pkg/adapter/images_remote.go +++ b/pkg/adapter/images_remote.go @@ -6,8 +6,11 @@ import ( "context" "encoding/json" + "github.com/containers/libpod/cmd/podman/cliconfig" iopodman "github.com/containers/libpod/cmd/podman/varlink" + "github.com/containers/libpod/libpod/image" "github.com/containers/libpod/pkg/inspect" + "github.com/pkg/errors" ) // Inspect returns returns an ImageData struct from over a varlink connection @@ -22,3 +25,32 @@ func (i *ContainerImage) Inspect(ctx context.Context) (*inspect.ImageData, error } return &data, nil } + +// Tree ... +func (r *LocalRuntime) Tree(c *cliconfig.TreeValues) (*image.InfoImage, map[string]*image.LayerInfo, *ContainerImage, error) { + layerInfoMap := make(map[string]*image.LayerInfo) + imageInfo := &image.InfoImage{} + + img, err := r.NewImageFromLocal(c.InputArgs[0]) + if err != nil { + return nil, nil, nil, err + } + + reply, err := iopodman.GetLayersMapWithImageInfo().Call(r.Conn) + if err != nil { + return nil, nil, nil, errors.Wrap(err, "failed to obtain image layers") + } + if err := json.Unmarshal([]byte(reply), &layerInfoMap); err != nil { + return nil, nil, nil, errors.Wrap(err, "failed to unmarshal image layers") + } + + reply, err = iopodman.BuildImageHierarchyMap().Call(r.Conn, c.InputArgs[0]) + if err != nil { + return nil, nil, nil, errors.Wrap(err, "failed to get build image map") + } + if err := json.Unmarshal([]byte(reply), imageInfo); err != nil { + return nil, nil, nil, errors.Wrap(err, "failed to unmarshal build image map") + } + + return imageInfo, layerInfoMap, img, nil +} diff --git a/pkg/adapter/runtime.go b/pkg/adapter/runtime.go index 6a68a3aea..182a04044 100644 --- a/pkg/adapter/runtime.go +++ b/pkg/adapter/runtime.go @@ -8,7 +8,6 @@ import ( "io" "io/ioutil" "os" - "strconv" "text/template" "github.com/containers/buildah" @@ -23,6 +22,7 @@ import ( "github.com/containers/libpod/libpod/events" "github.com/containers/libpod/libpod/image" "github.com/containers/libpod/pkg/rootless" + "github.com/containers/storage/pkg/archive" "github.com/pkg/errors" ) @@ -123,38 +123,6 @@ func (r *LocalRuntime) Export(name string, path string) error { if err != nil { return errors.Wrapf(err, "error looking up container %q", name) } - if os.Geteuid() != 0 { - state, err := ctr.State() - if err != nil { - return errors.Wrapf(err, "cannot read container state %q", ctr.ID()) - } - if state == libpod.ContainerStateRunning || state == libpod.ContainerStatePaused { - data, err := ioutil.ReadFile(ctr.Config().ConmonPidFile) - if err != nil { - return errors.Wrapf(err, "cannot read conmon PID file %q", ctr.Config().ConmonPidFile) - } - conmonPid, err := strconv.Atoi(string(data)) - if err != nil { - return errors.Wrapf(err, "cannot parse PID %q", data) - } - became, ret, err := rootless.JoinDirectUserAndMountNS(uint(conmonPid)) - if err != nil { - return err - } - if became { - os.Exit(ret) - } - } else { - became, ret, err := rootless.BecomeRootInUserNS() - if err != nil { - return err - } - if became { - os.Exit(ret) - } - } - } - return ctr.Export(path) } @@ -342,46 +310,6 @@ func (r *LocalRuntime) HealthCheck(c *cliconfig.HealthCheckValues) (libpod.Healt return r.Runtime.HealthCheck(c.InputArgs[0]) } -// JoinOrCreateRootlessPod joins the specified pod if it is running or it creates a new user namespace -// if the pod is stopped -func (r *LocalRuntime) JoinOrCreateRootlessPod(pod *Pod) (bool, int, error) { - if os.Geteuid() == 0 { - return false, 0, nil - } - opts := rootless.Opts{ - Argument: pod.ID(), - } - - inspect, err := pod.Inspect() - if err != nil { - return false, 0, err - } - for _, ctr := range inspect.Containers { - prevCtr, err := r.LookupContainer(ctr.ID) - if err != nil { - return false, -1, err - } - s, err := prevCtr.State() - if err != nil { - return false, -1, err - } - if s != libpod.ContainerStateRunning && s != libpod.ContainerStatePaused { - continue - } - data, err := ioutil.ReadFile(prevCtr.Config().ConmonPidFile) - if err != nil { - return false, -1, errors.Wrapf(err, "cannot read conmon PID file %q", prevCtr.Config().ConmonPidFile) - } - conmonPid, err := strconv.Atoi(string(data)) - if err != nil { - return false, -1, errors.Wrapf(err, "cannot parse PID %q", data) - } - return rootless.JoinDirectUserAndMountNSWithOpts(uint(conmonPid), &opts) - } - - return rootless.BecomeRootInUserNSWithOpts(&opts) -} - // Events is a wrapper to libpod to obtain libpod/podman events func (r *LocalRuntime) Events(c *cliconfig.EventValues) error { var ( @@ -430,3 +358,8 @@ func (r *LocalRuntime) Events(c *cliconfig.EventValues) error { } return nil } + +// Diff shows the difference in two objects +func (r *LocalRuntime) Diff(c *cliconfig.DiffValues, to string) ([]archive.Change, error) { + return r.Runtime.GetDiff("", to) +} diff --git a/pkg/adapter/runtime_remote.go b/pkg/adapter/runtime_remote.go index dcc2d5aa6..807a9ad8f 100644 --- a/pkg/adapter/runtime_remote.go +++ b/pkg/adapter/runtime_remote.go @@ -82,6 +82,7 @@ type remoteImage struct { Digest digest.Digest isParent bool Runtime *LocalRuntime + TopLayer string } // Container ... @@ -147,6 +148,7 @@ func imageInListToContainerImage(i iopodman.Image, name string, runtime *LocalRu Names: i.RepoTags, isParent: i.IsParent, Runtime: runtime, + TopLayer: i.TopLayer, } return &ContainerImage{ri}, nil } @@ -280,6 +282,11 @@ func (ci *ContainerImage) Dangling() bool { return len(ci.Names()) == 0 } +// TopLayer returns an images top layer as a string +func (ci *ContainerImage) TopLayer() string { + return ci.remoteImage.TopLayer +} + // TagImage ... func (ci *ContainerImage) TagImage(tag string) error { _, err := iopodman.TagImage().Call(ci.Runtime.Conn, ci.ID(), tag) @@ -755,13 +762,6 @@ func (r *LocalRuntime) HealthCheck(c *cliconfig.HealthCheckValues) (libpod.Healt return -1, libpod.ErrNotImplemented } -// JoinOrCreateRootlessPod joins the specified pod if it is running or it creates a new user namespace -// if the pod is stopped -func (r *LocalRuntime) JoinOrCreateRootlessPod(pod *Pod) (bool, int, error) { - // Nothing to do in the remote case - return true, 0, nil -} - // Events monitors libpod/podman events over a varlink connection func (r *LocalRuntime) Events(c *cliconfig.EventValues) error { var more uint64 @@ -831,3 +831,30 @@ func (r *LocalRuntime) Events(c *cliconfig.EventValues) error { } return nil } + +// Diff ... +func (r *LocalRuntime) Diff(c *cliconfig.DiffValues, to string) ([]archive.Change, error) { + var changes []archive.Change + reply, err := iopodman.Diff().Call(r.Conn, to) + if err != nil { + return nil, err + } + for _, change := range reply { + changes = append(changes, archive.Change{Path: change.Path, Kind: stringToChangeType(change.ChangeType)}) + } + return changes, nil +} + +func stringToChangeType(change string) archive.ChangeType { + switch change { + case "A": + return archive.ChangeAdd + case "D": + return archive.ChangeDelete + default: + logrus.Errorf("'%s' is unknown archive type", change) + fallthrough + case "C": + return archive.ChangeModify + } +} diff --git a/cmd/podman/sigproxy.go b/pkg/adapter/sigproxy.go index 16861bad0..af968cb89 100644 --- a/cmd/podman/sigproxy.go +++ b/pkg/adapter/sigproxy.go @@ -1,4 +1,4 @@ -package main +package adapter import ( "os" @@ -9,6 +9,7 @@ import ( "github.com/sirupsen/logrus" ) +// ProxySignals ... func ProxySignals(ctr *libpod.Container) { sigBuffer := make(chan os.Signal, 128) signal.CatchAll(sigBuffer) diff --git a/pkg/adapter/terminal.go b/pkg/adapter/terminal.go new file mode 100644 index 000000000..0b608decf --- /dev/null +++ b/pkg/adapter/terminal.go @@ -0,0 +1,159 @@ +package adapter + +import ( + "context" + "fmt" + "os" + gosignal "os/signal" + + "github.com/containers/libpod/libpod" + "github.com/docker/docker/pkg/signal" + "github.com/docker/docker/pkg/term" + "github.com/pkg/errors" + "github.com/sirupsen/logrus" + "golang.org/x/crypto/ssh/terminal" + "k8s.io/client-go/tools/remotecommand" +) + +// RawTtyFormatter ... +type RawTtyFormatter struct { +} + +// StartAttachCtr starts and (if required) attaches to a container +func StartAttachCtr(ctx context.Context, ctr *libpod.Container, stdout, stderr, stdin *os.File, detachKeys string, sigProxy bool, startContainer bool, recursive bool) error { + resize := make(chan remotecommand.TerminalSize) + + haveTerminal := terminal.IsTerminal(int(os.Stdin.Fd())) + + // Check if we are attached to a terminal. If we are, generate resize + // events, and set the terminal to raw mode + if haveTerminal && ctr.Spec().Process.Terminal { + logrus.Debugf("Handling terminal attach") + + subCtx, cancel := context.WithCancel(ctx) + defer cancel() + + resizeTty(subCtx, resize) + + oldTermState, err := term.SaveState(os.Stdin.Fd()) + if err != nil { + return errors.Wrapf(err, "unable to save terminal state") + } + + logrus.SetFormatter(&RawTtyFormatter{}) + term.SetRawTerminal(os.Stdin.Fd()) + + defer restoreTerminal(oldTermState) + } + + streams := new(libpod.AttachStreams) + streams.OutputStream = stdout + streams.ErrorStream = stderr + streams.InputStream = stdin + streams.AttachOutput = true + streams.AttachError = true + streams.AttachInput = true + + if stdout == nil { + logrus.Debugf("Not attaching to stdout") + streams.AttachOutput = false + } + if stderr == nil { + logrus.Debugf("Not attaching to stderr") + streams.AttachError = false + } + if stdin == nil { + logrus.Debugf("Not attaching to stdin") + streams.AttachInput = false + } + + if !startContainer { + if sigProxy { + ProxySignals(ctr) + } + + return ctr.Attach(streams, detachKeys, resize) + } + + attachChan, err := ctr.StartAndAttach(ctx, streams, detachKeys, resize, recursive) + if err != nil { + return err + } + + if sigProxy { + ProxySignals(ctr) + } + + if stdout == nil && stderr == nil { + fmt.Printf("%s\n", ctr.ID()) + } + + err = <-attachChan + if err != nil { + return errors.Wrapf(err, "error attaching to container %s", ctr.ID()) + } + + return nil +} + +// getResize returns a TerminalSize command matching stdin's current +// size on success, and nil on errors. +func getResize() *remotecommand.TerminalSize { + winsize, err := term.GetWinsize(os.Stdin.Fd()) + if err != nil { + logrus.Warnf("Could not get terminal size %v", err) + return nil + } + return &remotecommand.TerminalSize{ + Width: winsize.Width, + Height: winsize.Height, + } +} + +// Helper for prepareAttach - set up a goroutine to generate terminal resize events +func resizeTty(ctx context.Context, resize chan remotecommand.TerminalSize) { + sigchan := make(chan os.Signal, 1) + gosignal.Notify(sigchan, signal.SIGWINCH) + go func() { + defer close(resize) + // Update the terminal size immediately without waiting + // for a SIGWINCH to get the correct initial size. + resizeEvent := getResize() + for { + if resizeEvent == nil { + select { + case <-ctx.Done(): + return + case <-sigchan: + resizeEvent = getResize() + } + } else { + select { + case <-ctx.Done(): + return + case <-sigchan: + resizeEvent = getResize() + case resize <- *resizeEvent: + resizeEvent = nil + } + } + } + }() +} + +func restoreTerminal(state *term.State) error { + logrus.SetFormatter(&logrus.TextFormatter{}) + return term.RestoreTerminal(os.Stdin.Fd(), state) +} + +// Format ... +func (f *RawTtyFormatter) Format(entry *logrus.Entry) ([]byte, error) { + textFormatter := logrus.TextFormatter{} + bytes, err := textFormatter.Format(entry) + + if err == nil { + bytes = append(bytes, '\r') + } + + return bytes, err +} diff --git a/pkg/annotations/annotations.go b/pkg/annotations/annotations.go index 008cca7ee..fe2591a0c 100644 --- a/pkg/annotations/annotations.go +++ b/pkg/annotations/annotations.go @@ -19,9 +19,18 @@ const ( // HostName is the container host name annotation HostName = "io.kubernetes.cri-o.HostName" + // CgroupParent is the sandbox cgroup parent + CgroupParent = "io.kubernetes.cri-o.CgroupParent" + // IP is the container ipv4 or ipv6 address IP = "io.kubernetes.cri-o.IP" + // NamespaceOptions store the options for namespaces + NamespaceOptions = "io.kubernetes.cri-o.NamespaceOptions" + + // SeccompProfilePath is the node seccomp profile path + SeccompProfilePath = "io.kubernetes.cri-o.SeccompProfilePath" + // Image is the container image ID annotation Image = "io.kubernetes.cri-o.Image" @@ -34,6 +43,9 @@ const ( // KubeName is the kubernetes name annotation KubeName = "io.kubernetes.cri-o.KubeName" + // PortMappings holds the port mappings for the sandbox + PortMappings = "io.kubernetes.cri-o.PortMappings" + // Labels are the kubernetes labels annotation Labels = "io.kubernetes.cri-o.Labels" @@ -46,6 +58,9 @@ const ( // Name is the pod name annotation Name = "io.kubernetes.cri-o.Name" + // Namespace is the pod namespace annotation + Namespace = "io.kubernetes.cri-o.Namespace" + // PrivilegedRuntime is the annotation for the privileged runtime path PrivilegedRuntime = "io.kubernetes.cri-o.PrivilegedRuntime" @@ -67,8 +82,8 @@ const ( // MountPoint is the mount point of the container rootfs MountPoint = "io.kubernetes.cri-o.MountPoint" - // TrustedSandbox is the annotation for trusted sandboxes - TrustedSandbox = "io.kubernetes.cri-o.TrustedSandbox" + // RuntimeHandler is the annotation for runtime handler + RuntimeHandler = "io.kubernetes.cri-o.RuntimeHandler" // TTY is the terminal path annotation TTY = "io.kubernetes.cri-o.TTY" @@ -79,8 +94,14 @@ const ( // StdinOnce is the stdin_once annotation StdinOnce = "io.kubernetes.cri-o.StdinOnce" - // Volumes is the volumes annotation + // Volumes is the volumes annotatoin Volumes = "io.kubernetes.cri-o.Volumes" + + // HostNetwork indicates whether the host network namespace is used or not + HostNetwork = "io.kubernetes.cri-o.HostNetwork" + + // CNIResult is the JSON string representation of the Result from CNI + CNIResult = "io.kubernetes.cri-o.CNIResult" ) // ContainerType values diff --git a/pkg/registrar/registrar_test.go b/pkg/registrar/registrar_test.go index 0c1ef312a..50af95915 100644 --- a/pkg/registrar/registrar_test.go +++ b/pkg/registrar/registrar_test.go @@ -1,119 +1,213 @@ -package registrar +package registrar_test import ( - "reflect" "testing" -) - -func TestReserve(t *testing.T) { - r := NewRegistrar() - - obj := "test1" - if err := r.Reserve("test", obj); err != nil { - t.Fatal(err) - } - - if err := r.Reserve("test", obj); err != nil { - t.Fatal(err) - } - - obj2 := "test2" - err := r.Reserve("test", obj2) - if err == nil { - t.Fatalf("expected error when reserving an already reserved name to another object") - } - if err != ErrNameReserved { - t.Fatal("expected `ErrNameReserved` error when attempting to reserve an already reserved name") - } -} - -func TestRelease(t *testing.T) { - r := NewRegistrar() - obj := "testing" - - if err := r.Reserve("test", obj); err != nil { - t.Fatal(err) - } - r.Release("test") - r.Release("test") // Ensure there is no panic here - if err := r.Reserve("test", obj); err != nil { - t.Fatal(err) - } -} - -func TestGetNames(t *testing.T) { - r := NewRegistrar() - obj := "testing" - names := []string{"test1", "test2"} - - for _, name := range names { - if err := r.Reserve(name, obj); err != nil { - t.Fatal(err) - } - } - r.Reserve("test3", "other") - - names2, err := r.GetNames(obj) - if err != nil { - t.Fatal(err) - } - - if !reflect.DeepEqual(names, names2) { - t.Fatalf("Exepected: %v, Got: %v", names, names2) - } -} + "github.com/containers/libpod/pkg/registrar" + . "github.com/containers/libpod/test/framework" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) -func TestDelete(t *testing.T) { - r := NewRegistrar() - obj := "testing" - names := []string{"test1", "test2"} - for _, name := range names { - if err := r.Reserve(name, obj); err != nil { - t.Fatal(err) - } - } - - r.Reserve("test3", "other") - r.Delete(obj) - - _, err := r.GetNames(obj) - if err == nil { - t.Fatal("expected error getting names for deleted key") - } - - if err != ErrNoSuchKey { - t.Fatal("expected `ErrNoSuchKey`") - } +// TestRegistrar runs the created specs +func TestRegistrar(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "Registrar") } -func TestGet(t *testing.T) { - r := NewRegistrar() - obj := "testing" - name := "test" - - _, err := r.Get(name) - if err == nil { - t.Fatal("expected error when key does not exist") - } - if err != ErrNameNotReserved { - t.Fatal(err) - } - - if err := r.Reserve(name, obj); err != nil { - t.Fatal(err) - } - - if _, err = r.Get(name); err != nil { - t.Fatal(err) - } - - r.Delete(obj) - _, err = r.Get(name) - if err == nil { - t.Fatal("expected error when key does not exist") - } - if err != ErrNameNotReserved { - t.Fatal(err) - } -} +// nolint: gochecknoglobals +var t *TestFramework + +var _ = BeforeSuite(func() { + t = NewTestFramework(NilFunc, NilFunc) + t.Setup() +}) + +var _ = AfterSuite(func() { + t.Teardown() +}) + +// The actual test suite +var _ = t.Describe("Registrar", func() { + // Constant test data needed by some tests + const ( + testKey = "testKey" + testName = "testName" + anotherKey = "anotherKey" + ) + + // The system under test + var sut *registrar.Registrar + + // Prepare the system under test and register a test name and key before + // each test + BeforeEach(func() { + sut = registrar.NewRegistrar() + Expect(sut.Reserve(testName, testKey)).To(BeNil()) + }) + + t.Describe("Reserve", func() { + It("should succeed to reserve a new registrar", func() { + // Given + // When + err := sut.Reserve("name", "key") + + // Then + Expect(err).To(BeNil()) + }) + + It("should succeed to reserve a registrar twice", func() { + // Given + // When + err := sut.Reserve(testName, testKey) + + // Then + Expect(err).To(BeNil()) + }) + + It("should fail to reserve an already reserved registrar", func() { + // Given + // When + err := sut.Reserve(testName, anotherKey) + + // Then + Expect(err).NotTo(BeNil()) + Expect(err).To(Equal(registrar.ErrNameReserved)) + }) + }) + + t.Describe("Release", func() { + It("should succeed to release a registered registrar multiple times", func() { + // Given + // When + // Then + sut.Release(testName) + sut.Release(testName) + }) + + It("should succeed to release a unknown registrar multiple times", func() { + // Given + // When + // Then + sut.Release(anotherKey) + sut.Release(anotherKey) + }) + + It("should succeed to release and re-register a registrar", func() { + // Given + // When + sut.Release(testName) + err := sut.Reserve(testName, testKey) + + // Then + Expect(err).To(BeNil()) + }) + }) + + t.Describe("GetNames", func() { + It("should succeed to retrieve a single name for a registrar", func() { + // Given + // When + names, err := sut.GetNames(testKey) + + // Then + Expect(err).To(BeNil()) + Expect(len(names)).To(Equal(1)) + Expect(names[0]).To(Equal(testName)) + }) + + It("should succeed to retrieve all names for a registrar", func() { + // Given + testNames := []string{"test1", "test2"} + for _, name := range testNames { + Expect(sut.Reserve(name, anotherKey)).To(BeNil()) + } + + // When + names, err := sut.GetNames(anotherKey) + + // Then + Expect(err).To(BeNil()) + Expect(len(names)).To(Equal(2)) + Expect(names).To(Equal(testNames)) + }) + }) + + t.Describe("GetNames", func() { + It("should succeed to retrieve a single name for a registrar", func() { + // Given + // When + names, err := sut.GetNames(testKey) + + // Then + Expect(err).To(BeNil()) + Expect(len(names)).To(Equal(1)) + Expect(names[0]).To(Equal(testName)) + }) + + It("should succeed to retrieve all names for a registrar", func() { + // Given + anotherKey := "anotherKey" + testNames := []string{"test1", "test2"} + for _, name := range testNames { + Expect(sut.Reserve(name, anotherKey)).To(BeNil()) + } + + // When + names, err := sut.GetNames(anotherKey) + + // Then + Expect(err).To(BeNil()) + Expect(len(names)).To(Equal(2)) + Expect(names).To(Equal(testNames)) + }) + }) + + t.Describe("Delete", func() { + It("should succeed to delete a registrar", func() { + // Given + // When + sut.Delete(testKey) + + // Then + names, err := sut.GetNames(testKey) + Expect(len(names)).To(BeZero()) + Expect(err).To(Equal(registrar.ErrNoSuchKey)) + }) + }) + + t.Describe("Get", func() { + It("should succeed to get a key for a registrar", func() { + // Given + // When + key, err := sut.Get(testName) + + // Then + Expect(err).To(BeNil()) + Expect(key).To(Equal(testKey)) + }) + + It("should fail to get a key for a not existing registrar", func() { + // Given + // When + key, err := sut.Get("notExistingName") + + // Then + Expect(key).To(BeEmpty()) + Expect(err).To(Equal(registrar.ErrNameNotReserved)) + }) + }) + + t.Describe("GetAll", func() { + It("should succeed to get all names", func() { + // Given + // When + names := sut.GetAll() + + // Then + Expect(len(names)).To(Equal(1)) + Expect(len(names[testKey])).To(Equal(1)) + Expect(names[testKey][0]).To(Equal(testName)) + }) + }) +}) diff --git a/pkg/rootless/rootless.go b/pkg/rootless/rootless.go deleted file mode 100644 index a531e43ce..000000000 --- a/pkg/rootless/rootless.go +++ /dev/null @@ -1,9 +0,0 @@ -package rootless - -// Opts allows to customize how re-execing to a rootless process is done -type Opts struct { - // Argument overrides the arguments on the command line - // for the re-execed process. The process in the namespace - // must use rootless.Argument() to read its value. - Argument string -} diff --git a/pkg/rootless/rootless_linux.c b/pkg/rootless/rootless_linux.c index 2e2c3acac..9cb79ed4d 100644 --- a/pkg/rootless/rootless_linux.c +++ b/pkg/rootless/rootless_linux.c @@ -13,10 +13,36 @@ #include <sys/wait.h> #include <string.h> #include <stdbool.h> +#include <sys/types.h> +#include <sys/prctl.h> +#include <dirent.h> static const char *_max_user_namespaces = "/proc/sys/user/max_user_namespaces"; static const char *_unprivileged_user_namespaces = "/proc/sys/kernel/unprivileged_userns_clone"; +static int n_files; + +static void __attribute__((constructor)) init() +{ + DIR *d; + + /* Store how many FDs were open before the Go runtime kicked in. */ + d = opendir ("/proc/self/fd"); + if (d) + { + struct dirent *ent; + + for (ent = readdir (d); ent; ent = readdir (d)) + { + int fd = atoi (ent->d_name); + if (fd > n_files && fd != dirfd (d)) + n_files = fd; + } + closedir (d); + } +} + + static int syscall_setresuid (uid_t ruid, uid_t euid, uid_t suid) { @@ -133,12 +159,25 @@ reexec_userns_join (int userns, int mountns) pid = fork (); if (pid < 0) fprintf (stderr, "cannot fork: %s\n", strerror (errno)); + if (pid) - return pid; + { + /* We passed down these fds, close them. */ + int f; + for (f = 3; f < n_files; f++) + close (f); + return pid; + } setenv ("_CONTAINERS_USERNS_CONFIGURED", "init", 1); setenv ("_CONTAINERS_ROOTLESS_UID", uid, 1); + if (prctl (PR_SET_PDEATHSIG, SIGTERM, 0, 0, 0) < 0) + { + fprintf (stderr, "cannot prctl(PR_SET_PDEATHSIG): %s\n", strerror (errno)); + _exit (EXIT_FAILURE); + } + if (setns (userns, 0) < 0) { fprintf (stderr, "cannot setns: %s\n", strerror (errno)); diff --git a/pkg/rootless/rootless_linux.go b/pkg/rootless/rootless_linux.go index 0be0e08bf..1d1b1713d 100644 --- a/pkg/rootless/rootless_linux.go +++ b/pkg/rootless/rootless_linux.go @@ -46,25 +46,6 @@ func IsRootless() bool { return isRootless } -var ( - skipStorageSetup = false -) - -// SetSkipStorageSetup tells the runtime to not setup containers/storage -func SetSkipStorageSetup(v bool) { - skipStorageSetup = v -} - -// SkipStorageSetup tells if we should skip the containers/storage setup -func SkipStorageSetup() bool { - return skipStorageSetup -} - -// Argument returns the argument that was set for the rootless session. -func Argument() string { - return os.Getenv("_CONTAINERS_ROOTLESS_ARG") -} - // GetRootlessUID returns the UID of the user in the parent userNS func GetRootlessUID() int { uidEnv := os.Getenv("_CONTAINERS_ROOTLESS_UID") @@ -104,51 +85,86 @@ func tryMappingTool(tool string, pid int, hostID int, mappings []idtools.IDMap) return nil } -// JoinNS re-exec podman in a new userNS and join the user namespace of the specified -// PID. -func JoinNS(pid uint, preserveFDs int) (bool, int, error) { - if os.Geteuid() == 0 || os.Getenv("_CONTAINERS_USERNS_CONFIGURED") != "" { - return false, -1, nil +func readUserNs(path string) (string, error) { + b := make([]byte, 256) + _, err := syscall.Readlink(path, b) + if err != nil { + return "", err } + return string(b), nil +} - userNS, err := getUserNSForPid(pid) +func readUserNsFd(fd uintptr) (string, error) { + return readUserNs(fmt.Sprintf("/proc/self/fd/%d", fd)) +} + +func getParentUserNs(fd uintptr) (uintptr, error) { + const nsGetParent = 0xb702 + ret, _, errno := syscall.Syscall(syscall.SYS_IOCTL, fd, uintptr(nsGetParent), 0) + if errno != 0 { + return 0, errno + } + return (uintptr)(unsafe.Pointer(ret)), nil +} + +// getUserNSFirstChild returns an open FD for the first direct child user namespace that created the process +// Each container creates a new user namespace where the runtime runs. The current process in the container +// might have created new user namespaces that are child of the initial namespace we created. +// This function finds the initial namespace created for the container that is a child of the current namespace. +// +// current ns +// / \ +// TARGET -> a [other containers] +// / +// b +// / +// NS READ USING THE PID -> c +func getUserNSFirstChild(fd uintptr) (*os.File, error) { + currentNS, err := readUserNs("/proc/self/ns/user") if err != nil { - return false, -1, err + return nil, err } - defer userNS.Close() - pidC := C.reexec_userns_join(C.int(userNS.Fd()), -1) - if int(pidC) < 0 { - return false, -1, errors.Errorf("cannot re-exec process") + ns, err := readUserNsFd(fd) + if err != nil { + return nil, errors.Wrapf(err, "cannot read user namespace") } - if preserveFDs > 0 { - for fd := 3; fd < 3+preserveFDs; fd++ { - // These fds were passed down to the runtime. Close them - // and not interfere - os.NewFile(uintptr(fd), fmt.Sprintf("fd-%d", fd)).Close() - } + if ns == currentNS { + return nil, errors.New("process running in the same user namespace") } - ret := C.reexec_in_user_namespace_wait(pidC) - if ret < 0 { - return false, -1, errors.New("error waiting for the re-exec process") - } + for { + nextFd, err := getParentUserNs(fd) + if err != nil { + return nil, errors.Wrapf(err, "cannot get parent user namespace") + } - return true, int(ret), nil -} + ns, err = readUserNsFd(nextFd) + if err != nil { + return nil, errors.Wrapf(err, "cannot read user namespace") + } -// JoinDirectUserAndMountNS re-exec podman in a new userNS and join the user and mount -// namespace of the specified PID without looking up its parent. Useful to join directly -// the conmon process. It is a convenience function for JoinDirectUserAndMountNSWithOpts -// with a default configuration. -func JoinDirectUserAndMountNS(pid uint) (bool, int, error) { - return JoinDirectUserAndMountNSWithOpts(pid, nil) + if ns == currentNS { + syscall.Close(int(nextFd)) + + // Drop O_CLOEXEC for the fd. + _, _, errno := syscall.Syscall(syscall.SYS_FCNTL, fd, syscall.F_SETFD, 0) + if errno != 0 { + syscall.Close(int(fd)) + return nil, errno + } + + return os.NewFile(fd, "userns child"), nil + } + syscall.Close(int(fd)) + fd = nextFd + } } -// JoinDirectUserAndMountNSWithOpts re-exec podman in a new userNS and join the user and -// mount namespace of the specified PID without looking up its parent. Useful to join -// directly the conmon process. -func JoinDirectUserAndMountNSWithOpts(pid uint, opts *Opts) (bool, int, error) { +// JoinUserAndMountNS re-exec podman in a new userNS and join the user and mount +// namespace of the specified PID without looking up its parent. Useful to join directly +// the conmon process. +func JoinUserAndMountNS(pid uint) (bool, int, error) { if os.Geteuid() == 0 || os.Getenv("_CONTAINERS_USERNS_CONFIGURED") != "" { return false, -1, nil } @@ -165,39 +181,11 @@ func JoinDirectUserAndMountNSWithOpts(pid uint, opts *Opts) (bool, int, error) { } defer userNS.Close() - if opts != nil && opts.Argument != "" { - if err := os.Setenv("_CONTAINERS_ROOTLESS_ARG", opts.Argument); err != nil { - return false, -1, err - } - } - - pidC := C.reexec_userns_join(C.int(userNS.Fd()), C.int(mountNS.Fd())) - if int(pidC) < 0 { - return false, -1, errors.Errorf("cannot re-exec process") - } - - ret := C.reexec_in_user_namespace_wait(pidC) - if ret < 0 { - return false, -1, errors.New("error waiting for the re-exec process") - } - - return true, int(ret), nil -} - -// JoinNSPath re-exec podman in a new userNS and join the owner user namespace of the -// specified path. -func JoinNSPath(path string) (bool, int, error) { - if os.Geteuid() == 0 || os.Getenv("_CONTAINERS_USERNS_CONFIGURED") != "" { - return false, -1, nil - } - - userNS, err := getUserNSForPath(path) + fd, err := getUserNSFirstChild(userNS.Fd()) if err != nil { return false, -1, err } - defer userNS.Close() - - pidC := C.reexec_userns_join(C.int(userNS.Fd()), -1) + pidC := C.reexec_userns_join(C.int(fd.Fd()), C.int(mountNS.Fd())) if int(pidC) < 0 { return false, -1, errors.Errorf("cannot re-exec process") } @@ -213,16 +201,8 @@ func JoinNSPath(path string) (bool, int, error) { // BecomeRootInUserNS re-exec podman in a new userNS. It returns whether podman was re-executed // into a new user namespace and the return code from the re-executed podman process. // If podman was re-executed the caller needs to propagate the error code returned by the child -// process. It is a convenience function for BecomeRootInUserNSWithOpts with a default configuration. -func BecomeRootInUserNS() (bool, int, error) { - return BecomeRootInUserNSWithOpts(nil) -} - -// BecomeRootInUserNSWithOpts re-exec podman in a new userNS. It returns whether podman was -// re-execute into a new user namespace and the return code from the re-executed podman process. -// If podman was re-executed the caller needs to propagate the error code returned by the child // process. -func BecomeRootInUserNSWithOpts(opts *Opts) (bool, int, error) { +func BecomeRootInUserNS() (bool, int, error) { if os.Geteuid() == 0 || os.Getenv("_CONTAINERS_USERNS_CONFIGURED") != "" { if os.Getenv("_CONTAINERS_USERNS_CONFIGURED") == "init" { return false, 0, runInUser() @@ -241,12 +221,6 @@ func BecomeRootInUserNSWithOpts(opts *Opts) (bool, int, error) { defer w.Close() defer w.Write([]byte("0")) - if opts != nil && opts.Argument != "" { - if err := os.Setenv("_CONTAINERS_ROOTLESS_ARG", opts.Argument); err != nil { - return false, -1, err - } - } - pidC := C.reexec_in_user_namespace(C.int(r.Fd())) pid := int(pidC) if pid < 0 { @@ -328,112 +302,3 @@ func BecomeRootInUserNSWithOpts(opts *Opts) (bool, int, error) { return true, int(ret), nil } - -func readUserNs(path string) (string, error) { - b := make([]byte, 256) - _, err := syscall.Readlink(path, b) - if err != nil { - return "", err - } - return string(b), nil -} - -func readUserNsFd(fd uintptr) (string, error) { - return readUserNs(fmt.Sprintf("/proc/self/fd/%d", fd)) -} - -func getOwner(fd uintptr) (uintptr, error) { - const nsGetUserns = 0xb701 - ret, _, errno := syscall.Syscall(syscall.SYS_IOCTL, fd, uintptr(nsGetUserns), 0) - if errno != 0 { - return 0, errno - } - return (uintptr)(unsafe.Pointer(ret)), nil -} - -func getParentUserNs(fd uintptr) (uintptr, error) { - const nsGetParent = 0xb702 - ret, _, errno := syscall.Syscall(syscall.SYS_IOCTL, fd, uintptr(nsGetParent), 0) - if errno != 0 { - return 0, errno - } - return (uintptr)(unsafe.Pointer(ret)), nil -} - -func getUserNSForPath(path string) (*os.File, error) { - u, err := os.Open(path) - if err != nil { - return nil, errors.Wrapf(err, "cannot open %s", path) - } - defer u.Close() - fd, err := getOwner(u.Fd()) - if err != nil { - return nil, err - } - - return getUserNSFirstChild(fd) -} - -func getUserNSForPid(pid uint) (*os.File, error) { - path := fmt.Sprintf("/proc/%d/ns/user", pid) - u, err := os.Open(path) - if err != nil { - return nil, errors.Wrapf(err, "cannot open %s", path) - } - - return getUserNSFirstChild(u.Fd()) -} - -// getUserNSFirstChild returns an open FD for the first direct child user namespace that created the process -// Each container creates a new user namespace where the runtime runs. The current process in the container -// might have created new user namespaces that are child of the initial namespace we created. -// This function finds the initial namespace created for the container that is a child of the current namespace. -// -// current ns -// / \ -// TARGET -> a [other containers] -// / -// b -// / -// NS READ USING THE PID -> c -func getUserNSFirstChild(fd uintptr) (*os.File, error) { - currentNS, err := readUserNs("/proc/self/ns/user") - if err != nil { - return nil, err - } - - ns, err := readUserNsFd(fd) - if err != nil { - return nil, errors.Wrapf(err, "cannot read user namespace") - } - if ns == currentNS { - return nil, errors.New("process running in the same user namespace") - } - - for { - nextFd, err := getParentUserNs(fd) - if err != nil { - return nil, errors.Wrapf(err, "cannot get parent user namespace") - } - - ns, err = readUserNsFd(nextFd) - if err != nil { - return nil, errors.Wrapf(err, "cannot read user namespace") - } - - if ns == currentNS { - syscall.Close(int(nextFd)) - - // Drop O_CLOEXEC for the fd. - _, _, errno := syscall.Syscall(syscall.SYS_FCNTL, fd, syscall.F_SETFD, 0) - if errno != 0 { - syscall.Close(int(fd)) - return nil, errno - } - - return os.NewFile(fd, "userns child"), nil - } - syscall.Close(int(fd)) - fd = nextFd - } -} diff --git a/pkg/rootless/rootless_unsupported.go b/pkg/rootless/rootless_unsupported.go index e01d7855c..47b5dd7cc 100644 --- a/pkg/rootless/rootless_unsupported.go +++ b/pkg/rootless/rootless_unsupported.go @@ -19,54 +19,15 @@ func BecomeRootInUserNS() (bool, int, error) { return false, -1, errors.New("this function is not supported on this os") } -// BecomeRootInUserNS is a stub function that always returns false and an -// error on unsupported OS's -func BecomeRootInUserNSWithOpts(opts *Opts) (bool, int, error) { - return false, -1, errors.New("this function is not supported on this os") -} - // GetRootlessUID returns the UID of the user in the parent userNS func GetRootlessUID() int { return -1 } -// SetSkipStorageSetup tells the runtime to not setup containers/storage -func SetSkipStorageSetup(bool) { -} - -// SkipStorageSetup tells if we should skip the containers/storage setup -func SkipStorageSetup() bool { - return false -} - -// JoinNS re-exec podman in a new userNS and join the user namespace of the specified -// PID. -func JoinNS(pid uint, preserveFDs int) (bool, int, error) { - return false, -1, errors.New("this function is not supported on this os") -} - -// JoinNSPath re-exec podman in a new userNS and join the owner user namespace of the -// specified path. -func JoinNSPath(path string) (bool, int, error) { - return false, -1, errors.New("this function is not supported on this os") -} - -// JoinDirectUserAndMountNSWithOpts re-exec podman in a new userNS and join the user and -// mount namespace of the specified PID without looking up its parent. Useful to join -// directly the conmon process. -func JoinDirectUserAndMountNSWithOpts(pid uint, opts *Opts) (bool, int, error) { - return false, -1, errors.New("this function is not supported on this os") -} - -// JoinDirectUserAndMountNS re-exec podman in a new userNS and join the user and mount +// JoinUserAndMountNS re-exec podman in a new userNS and join the user and mount // namespace of the specified PID without looking up its parent. Useful to join directly -// the conmon process. It is a convenience function for JoinDirectUserAndMountNSWithOpts +// the conmon process. It is a convenience function for JoinUserAndMountNSWithOpts // with a default configuration. -func JoinDirectUserAndMountNS(pid uint) (bool, int, error) { +func JoinUserAndMountNS(pid uint) (bool, int, error) { return false, -1, errors.New("this function is not supported on this os") } - -// Argument returns the argument that was set for the rootless session. -func Argument() string { - return "" -} diff --git a/pkg/spec/createconfig.go b/pkg/spec/createconfig.go index 0a12e3dca..e71d9d3db 100644 --- a/pkg/spec/createconfig.go +++ b/pkg/spec/createconfig.go @@ -1,7 +1,6 @@ package createconfig import ( - "encoding/json" "fmt" "net" "os" @@ -12,7 +11,6 @@ import ( "github.com/containers/image/manifest" "github.com/containers/libpod/libpod" "github.com/containers/libpod/pkg/namespaces" - "github.com/containers/libpod/pkg/rootless" "github.com/containers/storage" "github.com/containers/storage/pkg/stringid" "github.com/cri-o/ocicni/pkg/ocicni" @@ -24,18 +22,16 @@ import ( "golang.org/x/sys/unix" ) -type mountType string - // Type constants const ( bps = iota iops // TypeBind is the type for mounting host dir - TypeBind mountType = "bind" + TypeBind = "bind" // TypeVolume is the type for remote storage volumes - // TypeVolume mountType = "volume" // re-enable upon use + // TypeVolume = "volume" // re-enable upon use // TypeTmpfs is the type for mounting tmpfs - TypeTmpfs mountType = "tmpfs" + TypeTmpfs = "tmpfs" ) // CreateResourceConfig represents resource elements in CreateConfig @@ -131,15 +127,15 @@ type CreateConfig struct { Mounts []spec.Mount //mounts Volumes []string //volume VolumesFrom []string - WorkDir string //workdir - LabelOpts []string //SecurityOpts - NoNewPrivs bool //SecurityOpts - ApparmorProfile string //SecurityOpts - SeccompProfilePath string //SecurityOpts + NamedVolumes []*libpod.ContainerNamedVolume // Filled in by CreateConfigToOCISpec + WorkDir string //workdir + LabelOpts []string //SecurityOpts + NoNewPrivs bool //SecurityOpts + ApparmorProfile string //SecurityOpts + SeccompProfilePath string //SecurityOpts SecurityOpts []string Rootfs string - LocalVolumes []spec.Mount //Keeps track of the built-in volumes of container used in the --volumes-from flag - Syslog bool // Whether to enable syslog on exit commands + Syslog bool // Whether to enable syslog on exit commands } func u32Ptr(i int64) *uint32 { u := uint32(i); return &u } @@ -173,9 +169,9 @@ func (c *CreateConfig) AddContainerInitBinary(path string) error { c.Command = append([]string{"/dev/init", "--"}, c.Command...) c.Mounts = append(c.Mounts, spec.Mount{ Destination: "/dev/init", - Type: "bind", + Type: TypeBind, Source: path, - Options: []string{"bind", "ro"}, + Options: []string{TypeBind, "ro"}, }) return nil } @@ -218,9 +214,9 @@ func (c *CreateConfig) initFSMounts() []spec.Mount { return mounts } -//GetVolumeMounts takes user provided input for bind mounts and creates Mount structs +// GetVolumeMounts takes user provided input for bind mounts and creates Mount structs func (c *CreateConfig) GetVolumeMounts(specMounts []spec.Mount) ([]spec.Mount, error) { - m := c.LocalVolumes + m := []spec.Mount{} for _, i := range c.Volumes { var options []string spliti := strings.Split(i, ":") @@ -256,9 +252,11 @@ func (c *CreateConfig) GetVolumeMounts(specMounts []spec.Mount) ([]spec.Mount, e mount.Source = "tmpfs" mount.Options = append(mount.Options, "tmpcopyup") } else { + // TODO: Move support for this and tmpfs into libpod + // Should tmpfs also be handled as named volumes? Wouldn't be hard // This will cause a new local Volume to be created on your system mount.Source = stringid.GenerateNonCryptoID() - mount.Options = append(mount.Options, "bind") + mount.Options = append(mount.Options, TypeBind) } m = append(m, mount) } @@ -269,13 +267,12 @@ func (c *CreateConfig) GetVolumeMounts(specMounts []spec.Mount) ([]spec.Mount, e // GetVolumesFrom reads the create-config artifact of the container to get volumes from // and adds it to c.Volumes of the current container. func (c *CreateConfig) GetVolumesFrom() error { - var options string - - if rootless.SkipStorageSetup() { + if os.Geteuid() != 0 { return nil } for _, vol := range c.VolumesFrom { + options := "" splitVol := strings.SplitN(vol, ":", 2) if len(splitVol) == 2 { options = splitVol[1] @@ -284,41 +281,60 @@ func (c *CreateConfig) GetVolumesFrom() error { if err != nil { return errors.Wrapf(err, "error looking up container %q", splitVol[0]) } - inspect, err := ctr.Inspect(false) - if err != nil { - return errors.Wrapf(err, "error inspecting %q", splitVol[0]) - } - var createArtifact CreateConfig - artifact, err := ctr.GetArtifact("create-config") - if err != nil { - return errors.Wrapf(err, "error getting create-config artifact for %q", splitVol[0]) + + logrus.Debugf("Adding volumes from container %s", ctr.ID()) + + // Look up the container's user volumes. This gets us the + // destinations of all mounts the user added to the container. + userVolumesArr := ctr.UserVolumes() + + // We're going to need to access them a lot, so convert to a map + // to reduce looping. + // We'll also use the map to indicate if we missed any volumes along the way. + userVolumes := make(map[string]bool) + for _, dest := range userVolumesArr { + userVolumes[dest] = false } - if err := json.Unmarshal(artifact, &createArtifact); err != nil { - return err + + // Now we get the container's spec and loop through its volumes + // and append them in if we can find them. + spec := ctr.Spec() + if spec == nil { + return errors.Errorf("error retrieving container %s spec", ctr.ID()) } - for key := range createArtifact.BuiltinImgVolumes { - for _, m := range inspect.Mounts { - if m.Destination == key { - c.LocalVolumes = append(c.LocalVolumes, m) - break + for _, mnt := range spec.Mounts { + if mnt.Type != TypeBind { + continue + } + if _, exists := userVolumes[mnt.Destination]; exists { + userVolumes[mnt.Destination] = true + localOptions := options + if localOptions == "" { + localOptions = strings.Join(mnt.Options, ",") } + c.Volumes = append(c.Volumes, fmt.Sprintf("%s:%s:%s", mnt.Source, mnt.Destination, localOptions)) } } - for _, i := range createArtifact.Volumes { - // Volumes format is host-dir:ctr-dir[:options], so get the host and ctr dir - // and add on the options given by the user to the flag. - spliti := strings.SplitN(i, ":", 3) - // Throw error if mounting volume from container with Z option (private label) - // Override this by adding 'z' to options. - if len(spliti) > 2 && strings.Contains(spliti[2], "Z") && !strings.Contains(options, "z") { - return errors.Errorf("volume mounted with private option 'Z' in %q. Use option 'z' to mount in current container", ctr.ID()) + // We're done with the spec mounts. Add named volumes. + // Add these unconditionally - none of them are automatically + // part of the container, as some spec mounts are. + namedVolumes := ctr.NamedVolumes() + for _, namedVol := range namedVolumes { + if _, exists := userVolumes[namedVol.Dest]; exists { + userVolumes[namedVol.Dest] = true } - if options == "" { - // Mount the volumes with the default options - c.Volumes = append(c.Volumes, createArtifact.Volumes...) - } else { - c.Volumes = append(c.Volumes, spliti[0]+":"+spliti[1]+":"+options) + localOptions := options + if localOptions == "" { + localOptions = strings.Join(namedVol.Options, ",") + } + c.Volumes = append(c.Volumes, fmt.Sprintf("%s:%s:%s", namedVol.Name, namedVol.Dest, localOptions)) + } + + // Check if we missed any volumes + for volDest, found := range userVolumes { + if !found { + logrus.Warnf("Unable to match volume %s from container %s for volumes-from", volDest, ctr.ID()) } } } @@ -418,14 +434,20 @@ func (c *CreateConfig) GetContainerCreateOptions(runtime *libpod.Runtime, pod *l // others, if they are included volumes := make([]string, 0, len(c.Volumes)) for _, vol := range c.Volumes { - volumes = append(volumes, strings.SplitN(vol, ":", 2)[0]) + // We always want the volume destination + splitVol := strings.SplitN(vol, ":", 3) + if len(splitVol) > 1 { + volumes = append(volumes, splitVol[1]) + } else { + volumes = append(volumes, splitVol[0]) + } } options = append(options, libpod.WithUserVolumes(volumes)) } - if len(c.LocalVolumes) != 0 { - options = append(options, libpod.WithLocalVolumes(c.LocalVolumes)) + if len(c.NamedVolumes) != 0 { + options = append(options, libpod.WithNamedVolumes(c.NamedVolumes)) } if len(c.Command) != 0 { @@ -539,7 +561,7 @@ func (c *CreateConfig) GetContainerCreateOptions(runtime *libpod.Runtime, pod *l options = append(options, libpod.WithPrivileged(c.Privileged)) - useImageVolumes := c.ImageVolumeType == "bind" + useImageVolumes := c.ImageVolumeType == TypeBind // Gather up the options for NewContainer which consist of With... funcs options = append(options, libpod.WithRootFSFromImage(c.ImageID, c.Image, useImageVolumes)) options = append(options, libpod.WithSecLabels(c.LabelOpts)) diff --git a/pkg/spec/spec.go b/pkg/spec/spec.go index a61741f73..9b6bd089e 100644 --- a/pkg/spec/spec.go +++ b/pkg/spec/spec.go @@ -6,6 +6,7 @@ import ( "path/filepath" "strings" + "github.com/containers/libpod/libpod" "github.com/containers/libpod/pkg/rootless" "github.com/containers/storage/pkg/mount" pmount "github.com/containers/storage/pkg/mount" @@ -48,6 +49,33 @@ func supercedeUserMounts(mounts []spec.Mount, configMount []spec.Mount) []spec.M return configMount } +// Split named volumes from normal volumes +func splitNamedVolumes(mounts []spec.Mount) ([]spec.Mount, []*libpod.ContainerNamedVolume) { + newMounts := make([]spec.Mount, 0) + namedVolumes := make([]*libpod.ContainerNamedVolume, 0) + for _, mount := range mounts { + // If it's not a named volume, append unconditionally + if mount.Type != TypeBind { + newMounts = append(newMounts, mount) + continue + } + // Volumes that are not named volumes must be an absolute or + // relative path. + // Volume names may not begin with a non-alphanumeric character + // so the HasPrefix() check is safe here. + if strings.HasPrefix(mount.Source, "/") || strings.HasPrefix(mount.Source, ".") { + newMounts = append(newMounts, mount) + } else { + namedVolume := new(libpod.ContainerNamedVolume) + namedVolume.Name = mount.Source + namedVolume.Dest = mount.Destination + namedVolume.Options = mount.Options + namedVolumes = append(namedVolumes, namedVolume) + } + } + return newMounts, namedVolumes +} + func getAvailableGids() (int64, error) { idMap, err := user.ParseIDMapFile("/proc/self/gid_map") if err != nil { @@ -99,7 +127,7 @@ func CreateConfigToOCISpec(config *CreateConfig) (*spec.Spec, error) { //nolint } sysMnt := spec.Mount{ Destination: "/sys", - Type: "bind", + Type: TypeBind, Source: "/sys", Options: []string{"rprivate", "nosuid", "noexec", "nodev", r, "rbind"}, } @@ -126,7 +154,7 @@ func CreateConfigToOCISpec(config *CreateConfig) (*spec.Spec, error) { //nolint g.RemoveMount("/dev/mqueue") devMqueue := spec.Mount{ Destination: "/dev/mqueue", - Type: "bind", + Type: TypeBind, Source: "/dev/mqueue", Options: []string{"bind", "nosuid", "noexec", "nodev"}, } @@ -136,7 +164,7 @@ func CreateConfigToOCISpec(config *CreateConfig) (*spec.Spec, error) { //nolint g.RemoveMount("/proc") procMount := spec.Mount{ Destination: "/proc", - Type: "bind", + Type: TypeBind, Source: "/proc", Options: []string{"rbind", "nosuid", "noexec", "nodev"}, } @@ -377,6 +405,12 @@ func CreateConfigToOCISpec(config *CreateConfig) (*spec.Spec, error) { //nolint configSpec.Mounts = supercedeUserMounts(volumeMounts, configSpec.Mounts) //--mount configSpec.Mounts = supercedeUserMounts(config.initFSMounts(), configSpec.Mounts) + + // Split normal mounts and named volumes + newMounts, namedVolumes := splitNamedVolumes(configSpec.Mounts) + configSpec.Mounts = newMounts + config.NamedVolumes = namedVolumes + // BLOCK IO blkio, err := config.CreateBlockIO() if err != nil { diff --git a/pkg/varlinkapi/config.go b/pkg/varlinkapi/config.go index f557d04e5..e75170547 100644 --- a/pkg/varlinkapi/config.go +++ b/pkg/varlinkapi/config.go @@ -1,3 +1,5 @@ +// +build varlink + package varlinkapi import ( diff --git a/pkg/varlinkapi/containers.go b/pkg/varlinkapi/containers.go index 7a6ae3507..ac1352dac 100644 --- a/pkg/varlinkapi/containers.go +++ b/pkg/varlinkapi/containers.go @@ -1,3 +1,5 @@ +// +build varlink + package varlinkapi import ( @@ -583,27 +585,6 @@ func (i *LibpodAPI) GetContainerStatsWithHistory(call iopodman.VarlinkCall, prev return call.ReplyGetContainerStatsWithHistory(cStats) } -// ContainerStatsToLibpodContainerStats converts the varlink containerstats to a libpod -// container stats -func ContainerStatsToLibpodContainerStats(stats iopodman.ContainerStats) libpod.ContainerStats { - cstats := libpod.ContainerStats{ - ContainerID: stats.Id, - Name: stats.Name, - CPU: stats.Cpu, - CPUNano: uint64(stats.Cpu_nano), - SystemNano: uint64(stats.System_nano), - MemUsage: uint64(stats.Mem_usage), - MemLimit: uint64(stats.Mem_limit), - MemPerc: stats.Mem_perc, - NetInput: uint64(stats.Net_input), - NetOutput: uint64(stats.Net_output), - BlockInput: uint64(stats.Block_input), - BlockOutput: uint64(stats.Block_output), - PIDs: uint64(stats.Pids), - } - return cstats -} - // GetContainersLogs is the varlink endpoint to obtain one or more container logs func (i *LibpodAPI) GetContainersLogs(call iopodman.VarlinkCall, names []string, follow, latest bool, since string, tail int64, timestamps bool) error { var wg sync.WaitGroup diff --git a/pkg/varlinkapi/containers_create.go b/pkg/varlinkapi/containers_create.go index 8990ac001..6b23dce5e 100644 --- a/pkg/varlinkapi/containers_create.go +++ b/pkg/varlinkapi/containers_create.go @@ -1,220 +1,18 @@ +// +build varlink + package varlinkapi import ( - "context" - "encoding/json" - "fmt" - "os" - "strings" - "syscall" - + "github.com/containers/libpod/cmd/podman/shared" "github.com/containers/libpod/cmd/podman/varlink" - "github.com/containers/libpod/libpod" - "github.com/containers/libpod/libpod/image" - "github.com/containers/libpod/pkg/inspect" - "github.com/containers/libpod/pkg/namespaces" - "github.com/containers/libpod/pkg/rootless" - cc "github.com/containers/libpod/pkg/spec" - "github.com/containers/libpod/pkg/util" - "github.com/docker/docker/pkg/signal" - "github.com/sirupsen/logrus" ) // CreateContainer ... func (i *LibpodAPI) CreateContainer(call iopodman.VarlinkCall, config iopodman.Create) error { - rtc, err := i.Runtime.GetConfig() - if err != nil { - return call.ReplyErrorOccurred(err.Error()) - } - ctx := getContext() - - newImage, err := i.Runtime.ImageRuntime().New(ctx, config.Image, rtc.SignaturePolicyPath, "", os.Stderr, nil, image.SigningOptions{}, false, nil) - if err != nil { - return call.ReplyErrorOccurred(err.Error()) - } - data, err := newImage.Inspect(ctx) - - createConfig, err := varlinkCreateToCreateConfig(ctx, config, i.Runtime, config.Image, data) - if err != nil { - return call.ReplyErrorOccurred(err.Error()) - } - - runtimeSpec, err := cc.CreateConfigToOCISpec(createConfig) + generic := shared.VarlinkCreateToGeneric(config) + ctr, _, err := shared.CreateContainer(getContext(), &generic, i.Runtime) if err != nil { return call.ReplyErrorOccurred(err.Error()) } - - // TODO fix when doing remote client and dealing with the ability to create a container - // within a non-existing pod (i.e. --pod new:foobar) - options, err := createConfig.GetContainerCreateOptions(i.Runtime, nil) - if err != nil { - return call.ReplyErrorOccurred(err.Error()) - } - - ctr, err := i.Runtime.NewContainer(ctx, runtimeSpec, options...) - if err != nil { - return call.ReplyErrorOccurred(err.Error()) - } - createConfigJSON, err := json.Marshal(createConfig) - if err != nil { - return call.ReplyErrorOccurred(err.Error()) - } - if err := ctr.AddArtifact("create-config", createConfigJSON); err != nil { - return call.ReplyErrorOccurred(err.Error()) - } - - logrus.Debug("new container created ", ctr.ID()) - return call.ReplyCreateContainer(ctr.ID()) } - -// varlinkCreateToCreateConfig takes the varlink input struct and maps it to a pointer -// of a CreateConfig, which eventually can be used to create the OCI spec. -func varlinkCreateToCreateConfig(ctx context.Context, create iopodman.Create, runtime *libpod.Runtime, imageName string, data *inspect.ImageData) (*cc.CreateConfig, error) { - idmappings, err := util.ParseIDMapping(create.Uidmap, create.Gidmap, create.Subuidname, create.Subgidname) - if err != nil { - return nil, err - } - inputCommand := create.Command - entrypoint := create.Entrypoint - - // ENTRYPOINT - // User input entrypoint takes priority over image entrypoint - if len(entrypoint) == 0 { - entrypoint = data.Config.Entrypoint - } - // if entrypoint=, we need to clear the entrypoint - if len(entrypoint) == 1 && strings.Join(create.Entrypoint, "") == "" { - entrypoint = []string{} - } - // Build the command - // If we have an entry point, it goes first - command := entrypoint - if len(inputCommand) > 0 { - // User command overrides data CMD - command = append(command, inputCommand...) - } else if len(data.Config.Cmd) > 0 && len(command) == 0 { - // If not user command, add CMD - command = append(command, data.Config.Cmd...) - } - - stopSignal := syscall.SIGTERM - if create.Stop_signal > 0 { - stopSignal, err = signal.ParseSignal(fmt.Sprintf("%d", create.Stop_signal)) - if err != nil { - return nil, err - } - } - - user := create.User - if user == "" { - user = data.Config.User - } - - // EXPOSED PORTS - portBindings, err := cc.ExposedPorts(create.Exposed_ports, create.Publish, create.Publish_all, data.Config.ExposedPorts) - if err != nil { - return nil, err - } - - // NETWORK MODE - networkMode := create.Net_mode - if networkMode == "" { - if rootless.IsRootless() { - networkMode = "slirp4netns" - } else { - networkMode = "bridge" - } - } - - // WORKING DIR - workDir := create.Work_dir - if workDir == "" { - workDir = "/" - } - - imageID := data.ID - var ImageVolumes map[string]struct{} - if data != nil && create.Image_volume_type != "ignore" { - ImageVolumes = data.Config.Volumes - } - - config := &cc.CreateConfig{ - Runtime: runtime, - BuiltinImgVolumes: ImageVolumes, - ConmonPidFile: create.Conmon_pidfile, - ImageVolumeType: create.Image_volume_type, - CapAdd: create.Cap_add, - CapDrop: create.Cap_drop, - CgroupParent: create.Cgroup_parent, - Command: command, - Detach: create.Detach, - Devices: create.Devices, - DNSOpt: create.Dns_opt, - DNSSearch: create.Dns_search, - DNSServers: create.Dns_servers, - Entrypoint: create.Entrypoint, - Env: create.Env, - GroupAdd: create.Group_add, - Hostname: create.Hostname, - HostAdd: create.Host_add, - IDMappings: idmappings, - Image: imageName, - ImageID: imageID, - Interactive: create.Interactive, - Labels: create.Labels, - LogDriver: create.Log_driver, - LogDriverOpt: create.Log_driver_opt, - Name: create.Name, - Network: networkMode, - IpcMode: namespaces.IpcMode(create.Ipc_mode), - NetMode: namespaces.NetworkMode(networkMode), - UtsMode: namespaces.UTSMode(create.Uts_mode), - PidMode: namespaces.PidMode(create.Pid_mode), - Pod: create.Pod, - Privileged: create.Privileged, - Publish: create.Publish, - PublishAll: create.Publish_all, - PortBindings: portBindings, - Quiet: create.Quiet, - ReadOnlyRootfs: create.Readonly_rootfs, - Resources: cc.CreateResourceConfig{ - BlkioWeight: uint16(create.Resources.Blkio_weight), - BlkioWeightDevice: create.Resources.Blkio_weight_device, - CPUShares: uint64(create.Resources.Cpu_shares), - CPUPeriod: uint64(create.Resources.Cpu_period), - CPUsetCPUs: create.Resources.Cpuset_cpus, - CPUsetMems: create.Resources.Cpuset_mems, - CPUQuota: create.Resources.Cpu_quota, - CPURtPeriod: uint64(create.Resources.Cpu_rt_period), - CPURtRuntime: create.Resources.Cpu_rt_runtime, - CPUs: create.Resources.Cpus, - DeviceReadBps: create.Resources.Device_read_bps, - DeviceReadIOps: create.Resources.Device_write_bps, - DeviceWriteBps: create.Resources.Device_read_iops, - DeviceWriteIOps: create.Resources.Device_write_iops, - DisableOomKiller: create.Resources.Disable_oomkiller, - ShmSize: create.Resources.Shm_size, - Memory: create.Resources.Memory, - MemoryReservation: create.Resources.Memory_reservation, - MemorySwap: create.Resources.Memory_swap, - MemorySwappiness: int(create.Resources.Memory_swappiness), - KernelMemory: create.Resources.Kernel_memory, - OomScoreAdj: int(create.Resources.Oom_score_adj), - PidsLimit: create.Resources.Pids_limit, - Ulimit: create.Resources.Ulimit, - }, - Rm: create.Rm, - StopSignal: stopSignal, - StopTimeout: uint(create.Stop_timeout), - Sysctl: create.Sys_ctl, - Tmpfs: create.Tmpfs, - Tty: create.Tty, - User: user, - UsernsMode: namespaces.UsernsMode(create.Userns_mode), - Volumes: create.Volumes, - WorkDir: workDir, - } - - return config, nil -} diff --git a/pkg/varlinkapi/events.go b/pkg/varlinkapi/events.go index 47c628ead..1e5696fbe 100644 --- a/pkg/varlinkapi/events.go +++ b/pkg/varlinkapi/events.go @@ -1,3 +1,5 @@ +// +build varlink + package varlinkapi import ( diff --git a/pkg/varlinkapi/images.go b/pkg/varlinkapi/images.go index 0ca867410..95aa1b335 100644 --- a/pkg/varlinkapi/images.go +++ b/pkg/varlinkapi/images.go @@ -1,3 +1,5 @@ +// +build varlink + package varlinkapi import ( @@ -103,6 +105,7 @@ func (i *LibpodAPI) GetImage(call iopodman.VarlinkCall, id string) error { VirtualSize: newImage.VirtualSize, Containers: int64(len(containers)), Labels: labels, + TopLayer: newImage.TopLayer(), } return call.ReplyGetImage(il) } @@ -910,3 +913,53 @@ func (i *LibpodAPI) LoadImage(call iopodman.VarlinkCall, name, inputFile string, } return call.ReplyLoadImage(br) } + +// Diff ... +func (i *LibpodAPI) Diff(call iopodman.VarlinkCall, name string) error { + var response []iopodman.DiffInfo + changes, err := i.Runtime.GetDiff("", name) + if err != nil { + return call.ReplyErrorOccurred(err.Error()) + } + for _, change := range changes { + response = append(response, iopodman.DiffInfo{Path: change.Path, ChangeType: change.Kind.String()}) + } + return call.ReplyDiff(response) +} + +// GetLayersMapWithImageInfo is a development only endpoint to obtain layer information for an image. +func (i *LibpodAPI) GetLayersMapWithImageInfo(call iopodman.VarlinkCall) error { + layerInfo, err := image.GetLayersMapWithImageInfo(i.Runtime.ImageRuntime()) + if err != nil { + return call.ReplyErrorOccurred(err.Error()) + } + b, err := json.Marshal(layerInfo) + if err != nil { + return call.ReplyErrorOccurred(err.Error()) + } + return call.ReplyGetLayersMapWithImageInfo(string(b)) +} + +// BuildImageHierarchyMap ... +func (i *LibpodAPI) BuildImageHierarchyMap(call iopodman.VarlinkCall, name string) error { + img, err := i.Runtime.ImageRuntime().NewFromLocal(name) + if err != nil { + return call.ReplyErrorOccurred(err.Error()) + } + imageInfo := &image.InfoImage{ + ID: img.ID(), + Tags: img.Names(), + } + layerInfo, err := image.GetLayersMapWithImageInfo(i.Runtime.ImageRuntime()) + if err != nil { + return call.ReplyErrorOccurred(err.Error()) + } + if err := image.BuildImageHierarchyMap(imageInfo, layerInfo, img.TopLayer()); err != nil { + return call.ReplyErrorOccurred(err.Error()) + } + b, err := json.Marshal(imageInfo) + if err != nil { + return call.ReplyErrorOccurred(err.Error()) + } + return call.ReplyBuildImageHierarchyMap(string(b)) +} diff --git a/pkg/varlinkapi/mount.go b/pkg/varlinkapi/mount.go index 3b4fe87e3..63ce44291 100644 --- a/pkg/varlinkapi/mount.go +++ b/pkg/varlinkapi/mount.go @@ -1,3 +1,5 @@ +// +build varlink + package varlinkapi import ( diff --git a/pkg/varlinkapi/pods.go b/pkg/varlinkapi/pods.go index c79cee4c2..ac8e24747 100644 --- a/pkg/varlinkapi/pods.go +++ b/pkg/varlinkapi/pods.go @@ -1,3 +1,5 @@ +// +build varlink + package varlinkapi import ( diff --git a/pkg/varlinkapi/remote_client.go b/pkg/varlinkapi/remote_client.go new file mode 100644 index 000000000..dd0613494 --- /dev/null +++ b/pkg/varlinkapi/remote_client.go @@ -0,0 +1,29 @@ +// +build varlink remoteclient + +package varlinkapi + +import ( + "github.com/containers/libpod/cmd/podman/varlink" + "github.com/containers/libpod/libpod" +) + +// ContainerStatsToLibpodContainerStats converts the varlink containerstats to a libpod +// container stats +func ContainerStatsToLibpodContainerStats(stats iopodman.ContainerStats) libpod.ContainerStats { + cstats := libpod.ContainerStats{ + ContainerID: stats.Id, + Name: stats.Name, + CPU: stats.Cpu, + CPUNano: uint64(stats.Cpu_nano), + SystemNano: uint64(stats.System_nano), + MemUsage: uint64(stats.Mem_usage), + MemLimit: uint64(stats.Mem_limit), + MemPerc: stats.Mem_perc, + NetInput: uint64(stats.Net_input), + NetOutput: uint64(stats.Net_output), + BlockInput: uint64(stats.Block_input), + BlockOutput: uint64(stats.Block_output), + PIDs: uint64(stats.Pids), + } + return cstats +} diff --git a/pkg/varlinkapi/system.go b/pkg/varlinkapi/system.go index 816143e9f..7f436a954 100644 --- a/pkg/varlinkapi/system.go +++ b/pkg/varlinkapi/system.go @@ -1,3 +1,5 @@ +// +build varlink + package varlinkapi import ( diff --git a/pkg/varlinkapi/transfers.go b/pkg/varlinkapi/transfers.go index 9a97bc810..96f76bcdc 100644 --- a/pkg/varlinkapi/transfers.go +++ b/pkg/varlinkapi/transfers.go @@ -1,3 +1,5 @@ +// +build varlink + package varlinkapi import ( diff --git a/pkg/varlinkapi/util.go b/pkg/varlinkapi/util.go index 7e487c03a..3c4b9b79a 100644 --- a/pkg/varlinkapi/util.go +++ b/pkg/varlinkapi/util.go @@ -1,3 +1,5 @@ +// +build varlink + package varlinkapi import ( diff --git a/pkg/varlinkapi/volumes.go b/pkg/varlinkapi/volumes.go index 02874d2b1..19ba38e7c 100644 --- a/pkg/varlinkapi/volumes.go +++ b/pkg/varlinkapi/volumes.go @@ -1,3 +1,5 @@ +// +build varlink + package varlinkapi import ( diff --git a/test/e2e/commit_test.go b/test/e2e/commit_test.go index bf9c88de5..fe4ae64cf 100644 --- a/test/e2e/commit_test.go +++ b/test/e2e/commit_test.go @@ -144,7 +144,7 @@ var _ = Describe("Podman commit", func() { inspect.WaitWithDefaultTimeout() Expect(inspect.ExitCode()).To(Equal(0)) image := inspect.InspectImageJSON() - _, ok := image[0].Config.Volumes["/tmp"] + _, ok := image[0].Config.Volumes["/foo"] Expect(ok).To(BeTrue()) r := podmanTest.Podman([]string{"run", "newimage"}) diff --git a/test/e2e/cp_test.go b/test/e2e/cp_test.go index f89865264..591f533d6 100644 --- a/test/e2e/cp_test.go +++ b/test/e2e/cp_test.go @@ -112,4 +112,37 @@ var _ = Describe("Podman cp", func() { } Expect(string(output)).To(Equal("copy from host to container directory")) }) + + It("podman cp dir to dir", func() { + path, err := os.Getwd() + if err != nil { + os.Exit(1) + } + testDirPath := filepath.Join(path, "TestDir") + err = os.Mkdir(testDirPath, 0777) + if err != nil { + os.Exit(1) + } + + session := podmanTest.Podman([]string{"create", ALPINE, "ls", "/foodir"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + name := session.OutputToString() + + session = podmanTest.Podman([]string{"cp", testDirPath, name + ":/foodir"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + session = podmanTest.Podman([]string{"start", "-a", name}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + Expect(len(session.OutputToStringArray())).To(Equal(0)) + + session = podmanTest.Podman([]string{"cp", testDirPath, name + ":/foodir"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + session = podmanTest.Podman([]string{"start", "-a", name}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + Expect(session.OutputToString()).To(Equal("TestDir")) + }) }) diff --git a/test/e2e/generate_kube_test.go b/test/e2e/generate_kube_test.go index 5bcf3b347..2f0af7e5f 100644 --- a/test/e2e/generate_kube_test.go +++ b/test/e2e/generate_kube_test.go @@ -48,7 +48,6 @@ var _ = Describe("Podman generate kube", func() { }) It("podman generate kube on container", func() { - SkipIfRootless() session := podmanTest.RunTopContainer("top") session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) @@ -62,7 +61,6 @@ var _ = Describe("Podman generate kube", func() { }) It("podman generate service kube on container", func() { - SkipIfRootless() session := podmanTest.RunTopContainer("top") session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) @@ -76,7 +74,6 @@ var _ = Describe("Podman generate kube", func() { }) It("podman generate kube on pod", func() { - SkipIfRootless() _, rc, _ := podmanTest.CreatePod("toppod") Expect(rc).To(Equal(0)) @@ -93,7 +90,6 @@ var _ = Describe("Podman generate kube", func() { }) It("podman generate service kube on pod", func() { - SkipIfRootless() _, rc, _ := podmanTest.CreatePod("toppod") Expect(rc).To(Equal(0)) diff --git a/test/e2e/healthcheck_run_test.go b/test/e2e/healthcheck_run_test.go index cd2365ce7..60be86ebc 100644 --- a/test/e2e/healthcheck_run_test.go +++ b/test/e2e/healthcheck_run_test.go @@ -42,7 +42,6 @@ var _ = Describe("Podman healthcheck run", func() { }) It("podman healthcheck on valid container", func() { - SkipIfRootless() podmanTest.RestoreArtifact(healthcheck) session := podmanTest.Podman([]string{"run", "-dt", "--name", "hc", healthcheck}) session.WaitWithDefaultTimeout() @@ -135,7 +134,6 @@ var _ = Describe("Podman healthcheck run", func() { }) It("podman healthcheck good check results in healthy even in start-period", func() { - SkipIfRootless() session := podmanTest.Podman([]string{"run", "-dt", "--name", "hc", "--healthcheck-start-period", "2m", "--healthcheck-retries", "2", "--healthcheck-command", "\"CMD-SHELL\" \"ls\" \"||\" \"exit\" \"1\"", ALPINE, "top"}) session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) @@ -149,7 +147,6 @@ var _ = Describe("Podman healthcheck run", func() { }) It("podman healthcheck single healthy result changes failed to healthy", func() { - SkipIfRootless() session := podmanTest.Podman([]string{"run", "-dt", "--name", "hc", "--healthcheck-retries", "2", "--healthcheck-command", "\"CMD-SHELL\" \"ls\" \"/foo\" \"||\" \"exit\" \"1\"", ALPINE, "top"}) session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) diff --git a/test/e2e/pod_rm_test.go b/test/e2e/pod_rm_test.go index f9d7abe8f..5da3d563b 100644 --- a/test/e2e/pod_rm_test.go +++ b/test/e2e/pod_rm_test.go @@ -3,6 +3,7 @@ package integration import ( + "fmt" "os" . "github.com/containers/libpod/test/utils" @@ -95,28 +96,41 @@ var _ = Describe("Podman pod rm", func() { }) It("podman pod rm -a doesn't remove a running container", func() { + fmt.Printf("To start, there are %d pods\n", podmanTest.NumberOfPods()) _, ec, podid1 := podmanTest.CreatePod("") Expect(ec).To(Equal(0)) _, ec, _ = podmanTest.CreatePod("") Expect(ec).To(Equal(0)) + fmt.Printf("Started %d pods\n", podmanTest.NumberOfPods()) session := podmanTest.RunTopContainerInPod("", podid1) session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) + podmanTest.WaitForContainer() + Expect(podmanTest.NumberOfContainersRunning()).To(Equal(1)) + fmt.Printf("Started container running in one pod") + num_pods := podmanTest.NumberOfPods() + Expect(num_pods).To(Equal(2)) + ps := podmanTest.Podman([]string{"pod", "ps"}) + ps.WaitWithDefaultTimeout() + fmt.Printf("Current %d pod(s):\n%s\n", num_pods, ps.OutputToString()) + + fmt.Printf("Removing all empty pods\n") result := podmanTest.Podman([]string{"pod", "rm", "-a"}) result.WaitWithDefaultTimeout() Expect(result.ExitCode()).To(Not(Equal(0))) - - result = podmanTest.Podman([]string{"ps", "-q"}) - result.WaitWithDefaultTimeout() - Expect(len(result.OutputToStringArray())).To(Equal(1)) - - // one pod should have been deleted - result = podmanTest.Podman([]string{"pod", "ps", "-q"}) - result.WaitWithDefaultTimeout() - Expect(len(result.OutputToStringArray())).To(Equal(1)) + foundExpectedError, _ := result.ErrorGrepString("contains containers and cannot be removed") + Expect(foundExpectedError).To(Equal(true)) + + num_pods = podmanTest.NumberOfPods() + ps = podmanTest.Podman([]string{"pod", "ps"}) + ps.WaitWithDefaultTimeout() + fmt.Printf("Final %d pod(s):\n%s\n", num_pods, ps.OutputToString()) + Expect(num_pods).To(Equal(1)) + // Confirm top container still running inside remaining pod + Expect(podmanTest.NumberOfContainersRunning()).To(Equal(1)) }) It("podman pod rm -fa removes everything", func() { diff --git a/test/e2e/run_test.go b/test/e2e/run_test.go index 2daf2fe5b..a89ee491b 100644 --- a/test/e2e/run_test.go +++ b/test/e2e/run_test.go @@ -611,7 +611,6 @@ USER mail` session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) Expect(session.OutputToString()).To(ContainSubstring("data")) - }) It("podman run --volumes flag with multiple volumes", func() { diff --git a/test/e2e/volume_rm_test.go b/test/e2e/volume_rm_test.go index 888474670..39628d56f 100644 --- a/test/e2e/volume_rm_test.go +++ b/test/e2e/volume_rm_test.go @@ -32,7 +32,7 @@ var _ = Describe("Podman volume rm", func() { }) - It("podman rm volume", func() { + It("podman volume rm", func() { session := podmanTest.Podman([]string{"volume", "create", "myvol"}) session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) @@ -47,7 +47,7 @@ var _ = Describe("Podman volume rm", func() { Expect(len(session.OutputToStringArray())).To(Equal(0)) }) - It("podman rm with --force flag", func() { + It("podman volume rm with --force flag", func() { SkipIfRemote() session := podmanTest.Podman([]string{"create", "-v", "myvol:/myvol", ALPINE, "ls"}) cid := session.OutputToString() diff --git a/test/framework/framework.go b/test/framework/framework.go new file mode 100644 index 000000000..52401faf8 --- /dev/null +++ b/test/framework/framework.go @@ -0,0 +1,56 @@ +package framework + +import ( + "fmt" + + "github.com/onsi/ginkgo" + "github.com/onsi/gomega" +) + +// TestFramework is used to support commonnly used test features +type TestFramework struct { + setup func(*TestFramework) error + teardown func(*TestFramework) error + TestError error +} + +// NewTestFramework creates a new test framework instance for a given `setup` +// and `teardown` function +func NewTestFramework( + setup func(*TestFramework) error, + teardown func(*TestFramework) error, +) *TestFramework { + return &TestFramework{ + setup, + teardown, + fmt.Errorf("error"), + } +} + +// NilFn is a convenience function which simply does nothing +func NilFunc(f *TestFramework) error { + return nil +} + +// Setup is the global initialization function which runs before each test +// suite +func (t *TestFramework) Setup() { + // Global initialization for the whole framework goes in here + + // Setup the actual test suite + gomega.Expect(t.setup(t)).To(gomega.Succeed()) +} + +// Teardown is the global deinitialization function which runs after each test +// suite +func (t *TestFramework) Teardown() { + // Global deinitialization for the whole framework goes in here + + // Teardown the actual test suite + gomega.Expect(t.teardown(t)).To(gomega.Succeed()) +} + +// Describe is a convenience wrapper around the `ginkgo.Describe` function +func (t *TestFramework) Describe(text string, body func()) bool { + return ginkgo.Describe("libpod: "+text, body) +} diff --git a/version/version.go b/version/version.go index 89b5fbd8b..29a576317 100644 --- a/version/version.go +++ b/version/version.go @@ -4,7 +4,7 @@ package version // NOTE: remember to bump the version at the top // of the top-level README.md file when this is // bumped. -const Version = "1.2.0-dev" +const Version = "1.3.0-dev" // RemoteAPIVersion is the version for the remote // client API. It is used to determine compatibility |